Home | History | Annotate | Download | only in chromedriver
      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 
     50 namespace {
     51 
     52 const char* kCommonSwitches[] = {
     53     "ignore-certificate-errors", "metrics-recording-only"};
     54 
     55 #if defined(OS_LINUX)
     56 const char* kEnableCrashReport = "enable-crash-reporter-for-testing";
     57 #endif
     58 
     59 Status 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 
     80 Status 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 
    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(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 
    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.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->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.IsOk()) {
    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     return status;
    381 
    382   chrome->reset(new ChromeAndroidImpl(devtools_client.Pass(),
    383                                       devtools_event_listeners,
    384                                       port_reservation.Pass(),
    385                                       device.Pass()));
    386   return Status(kOk);
    387 }
    388 
    389 }  // namespace
    390 
    391 Status LaunchChrome(
    392     URLRequestContextGetter* context_getter,
    393     const SyncWebSocketFactory& socket_factory,
    394     DeviceManager* device_manager,
    395     PortServer* port_server,
    396     PortManager* port_manager,
    397     const Capabilities& capabilities,
    398     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
    399     scoped_ptr<Chrome>* chrome) {
    400   if (capabilities.IsExistingBrowser()) {
    401     return LaunchExistingChromeSession(
    402         context_getter, socket_factory,
    403         capabilities, devtools_event_listeners, chrome);
    404   }
    405 
    406   int port = 0;
    407   scoped_ptr<PortReservation> port_reservation;
    408   Status port_status(kOk);
    409   if (port_server)
    410     port_status = port_server->ReservePort(&port, &port_reservation);
    411   else
    412     port_status = port_manager->ReservePort(&port, &port_reservation);
    413   if (port_status.IsError())
    414     return Status(kUnknownError, "cannot reserve port for Chrome", port_status);
    415 
    416   if (capabilities.IsAndroid()) {
    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     return LaunchDesktopChrome(context_getter,
    427                                port,
    428                                port_reservation.Pass(),
    429                                socket_factory,
    430                                capabilities,
    431                                devtools_event_listeners,
    432                                chrome);
    433   }
    434 }
    435 
    436 namespace internal {
    437 
    438 void ConvertHexadecimalToIDAlphabet(std::string* id) {
    439   for (size_t i = 0; i < id->size(); ++i) {
    440     int val;
    441     if (base::HexStringToInt(base::StringPiece(id->begin() + i,
    442                                                id->begin() + i + 1),
    443                              &val)) {
    444       (*id)[i] = val + 'a';
    445     } else {
    446       (*id)[i] = 'a';
    447     }
    448   }
    449 }
    450 
    451 std::string GenerateExtensionId(const std::string& input) {
    452   uint8 hash[16];
    453   crypto::SHA256HashString(input, hash, sizeof(hash));
    454   std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
    455   ConvertHexadecimalToIDAlphabet(&output);
    456   return output;
    457 }
    458 
    459 Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest,
    460                                   const std::string& id,
    461                                   std::string* bg_page) {
    462   std::string bg_page_name;
    463   bool persistent = true;
    464   manifest->GetBoolean("background.persistent", &persistent);
    465   const base::Value* unused_value;
    466   if (manifest->Get("background.scripts", &unused_value))
    467     bg_page_name = "_generated_background_page.html";
    468   manifest->GetString("background.page", &bg_page_name);
    469   manifest->GetString("background_page", &bg_page_name);
    470   if (bg_page_name.empty() || !persistent)
    471     return Status(kOk);
    472   *bg_page = "chrome-extension://" + id + "/" + bg_page_name;
    473   return Status(kOk);
    474 }
    475 
    476 Status ProcessExtension(const std::string& extension,
    477                         const base::FilePath& temp_dir,
    478                         base::FilePath* path,
    479                         std::string* bg_page) {
    480   // Decodes extension string.
    481   // Some WebDriver client base64 encoders follow RFC 1521, which require that
    482   // 'encoded lines be no more than 76 characters long'. Just remove any
    483   // newlines.
    484   std::string extension_base64;
    485   base::RemoveChars(extension, "\n", &extension_base64);
    486   std::string decoded_extension;
    487   if (!base::Base64Decode(extension_base64, &decoded_extension))
    488     return Status(kUnknownError, "cannot base64 decode");
    489 
    490   // Get extension's ID from public key in crx file.
    491   // Assumes crx v2. See http://developer.chrome.com/extensions/crx.html.
    492   std::string key_len_str = decoded_extension.substr(8, 4);
    493   if (key_len_str.size() != 4)
    494     return Status(kUnknownError, "cannot extract public key length");
    495   uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
    496   std::string public_key = decoded_extension.substr(16, key_len);
    497   if (key_len != public_key.size())
    498     return Status(kUnknownError, "invalid public key length");
    499   std::string public_key_base64;
    500   base::Base64Encode(public_key, &public_key_base64);
    501   std::string id = GenerateExtensionId(public_key);
    502 
    503   // Unzip the crx file.
    504   base::ScopedTempDir temp_crx_dir;
    505   if (!temp_crx_dir.CreateUniqueTempDir())
    506     return Status(kUnknownError, "cannot create temp dir");
    507   base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
    508   int size = static_cast<int>(decoded_extension.length());
    509   if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
    510       size) {
    511     return Status(kUnknownError, "cannot write file");
    512   }
    513   base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id);
    514   if (!zip::Unzip(extension_crx, extension_dir))
    515     return Status(kUnknownError, "cannot unzip");
    516 
    517   // Parse the manifest and set the 'key' if not already present.
    518   base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json"));
    519   std::string manifest_data;
    520   if (!base::ReadFileToString(manifest_path, &manifest_data))
    521     return Status(kUnknownError, "cannot read manifest");
    522   scoped_ptr<base::Value> manifest_value(base::JSONReader::Read(manifest_data));
    523   base::DictionaryValue* manifest;
    524   if (!manifest_value || !manifest_value->GetAsDictionary(&manifest))
    525     return Status(kUnknownError, "invalid manifest");
    526 
    527   std::string manifest_key_base64;
    528   if (manifest->GetString("key", &manifest_key_base64)) {
    529     // If there is a key in both the header and the manifest, use the key in the
    530     // manifest. This allows chromedriver users users who generate dummy crxs
    531     // to set the manifest key and have a consistent ID.
    532     std::string manifest_key;
    533     if (!base::Base64Decode(manifest_key_base64, &manifest_key))
    534       return Status(kUnknownError, "'key' in manifest is not base64 encoded");
    535     std::string manifest_id = GenerateExtensionId(manifest_key);
    536     if (id != manifest_id) {
    537       LOG(WARNING)
    538           << "Public key in crx header is different from key in manifest"
    539           << std::endl << "key from header:   " << public_key_base64
    540           << std::endl << "key from manifest: " << manifest_key_base64
    541           << std::endl << "generated extension id from header key:   " << id
    542           << std::endl << "generated extension id from manifest key: "
    543           << manifest_id;
    544       id = manifest_id;
    545     }
    546   } else {
    547     manifest->SetString("key", public_key_base64);
    548     base::JSONWriter::Write(manifest, &manifest_data);
    549     if (file_util::WriteFile(
    550             manifest_path, manifest_data.c_str(), manifest_data.size()) !=
    551         static_cast<int>(manifest_data.size())) {
    552       return Status(kUnknownError, "cannot add 'key' to manifest");
    553     }
    554   }
    555 
    556   // Get extension's background page URL, if there is one.
    557   std::string bg_page_tmp;
    558   Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp);
    559   if (status.IsError())
    560     return status;
    561 
    562   *path = extension_dir;
    563   if (bg_page_tmp.size())
    564     *bg_page = bg_page_tmp;
    565   return Status(kOk);
    566 }
    567 
    568 void UpdateExtensionSwitch(Switches* switches,
    569                            const char name[],
    570                            const base::FilePath::StringType& extension) {
    571   base::FilePath::StringType value = switches->GetSwitchValueNative(name);
    572   if (value.length())
    573     value += FILE_PATH_LITERAL(",");
    574   value += extension;
    575   switches->SetSwitch(name, value);
    576 }
    577 
    578 Status ProcessExtensions(const std::vector<std::string>& extensions,
    579                          const base::FilePath& temp_dir,
    580                          bool include_automation_extension,
    581                          Switches* switches,
    582                          std::vector<std::string>* bg_pages) {
    583   std::vector<std::string> bg_pages_tmp;
    584   std::vector<base::FilePath::StringType> extension_paths;
    585   for (size_t i = 0; i < extensions.size(); ++i) {
    586     base::FilePath path;
    587     std::string bg_page;
    588     Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page);
    589     if (status.IsError()) {
    590       return Status(
    591           kUnknownError,
    592           base::StringPrintf("cannot process extension #%" PRIuS, i + 1),
    593           status);
    594     }
    595     extension_paths.push_back(path.value());
    596     if (bg_page.length())
    597       bg_pages_tmp.push_back(bg_page);
    598   }
    599 
    600   if (include_automation_extension) {
    601     base::FilePath automation_extension;
    602     Status status = UnpackAutomationExtension(temp_dir, &automation_extension);
    603     if (status.IsError())
    604       return status;
    605     if (switches->HasSwitch("disable-extensions")) {
    606       UpdateExtensionSwitch(switches, "load-component-extension",
    607                             automation_extension.value());
    608     } else {
    609       extension_paths.push_back(automation_extension.value());
    610     }
    611   }
    612 
    613   if (extension_paths.size()) {
    614     base::FilePath::StringType extension_paths_value = JoinString(
    615         extension_paths, FILE_PATH_LITERAL(','));
    616     UpdateExtensionSwitch(switches, "load-extension", extension_paths_value);
    617   }
    618   bg_pages->swap(bg_pages_tmp);
    619   return Status(kOk);
    620 }
    621 
    622 Status WritePrefsFile(
    623     const std::string& template_string,
    624     const base::DictionaryValue* custom_prefs,
    625     const base::FilePath& path) {
    626   int code;
    627   std::string error_msg;
    628   scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError(
    629           template_string, 0, &code, &error_msg));
    630   base::DictionaryValue* prefs;
    631   if (!template_value || !template_value->GetAsDictionary(&prefs)) {
    632     return Status(kUnknownError,
    633                   "cannot parse internal JSON template: " + error_msg);
    634   }
    635 
    636   if (custom_prefs) {
    637     for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd();
    638          it.Advance()) {
    639       prefs->Set(it.key(), it.value().DeepCopy());
    640     }
    641   }
    642 
    643   std::string prefs_str;
    644   base::JSONWriter::Write(prefs, &prefs_str);
    645   VLOG(0) << "Populating " << path.BaseName().value()
    646           << " file: " << PrettyPrintValue(*prefs);
    647   if (static_cast<int>(prefs_str.length()) != file_util::WriteFile(
    648           path, prefs_str.c_str(), prefs_str.length())) {
    649     return Status(kUnknownError, "failed to write prefs file");
    650   }
    651   return Status(kOk);
    652 }
    653 
    654 Status PrepareUserDataDir(
    655     const base::FilePath& user_data_dir,
    656     const base::DictionaryValue* custom_prefs,
    657     const base::DictionaryValue* custom_local_state) {
    658   base::FilePath default_dir = user_data_dir.AppendASCII("Default");
    659   if (!base::CreateDirectory(default_dir))
    660     return Status(kUnknownError, "cannot create default profile directory");
    661 
    662   Status status = WritePrefsFile(
    663       kPreferences,
    664       custom_prefs,
    665       default_dir.AppendASCII("Preferences"));
    666   if (status.IsError())
    667     return status;
    668 
    669   status = WritePrefsFile(
    670       kLocalState,
    671       custom_local_state,
    672       user_data_dir.AppendASCII("Local State"));
    673   if (status.IsError())
    674     return status;
    675 
    676   // Write empty "First Run" file, otherwise Chrome will wipe the default
    677   // profile that was written.
    678   if (file_util::WriteFile(
    679           user_data_dir.AppendASCII("First Run"), "", 0) != 0) {
    680     return Status(kUnknownError, "failed to write first run file");
    681   }
    682   return Status(kOk);
    683 }
    684 
    685 }  // namespace internal
    686