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_finder.h"
     31 #include "chrome/test/chromedriver/chrome/device_manager.h"
     32 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
     33 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
     34 #include "chrome/test/chromedriver/chrome/log.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/zip.h"
     39 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
     40 
     41 namespace {
     42 
     43 const char* kCommonSwitches[] = {
     44   "ignore-certificate-errors", "metrics-recording-only"};
     45 
     46 Status UnpackAutomationExtension(const base::FilePath& temp_dir,
     47                                  base::FilePath* automation_extension) {
     48   std::string decoded_extension;
     49   if (!base::Base64Decode(kAutomationExtension, &decoded_extension))
     50     return Status(kUnknownError, "failed to base64decode automation extension");
     51 
     52   base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip");
     53   int size = static_cast<int>(decoded_extension.length());
     54   if (file_util::WriteFile(extension_zip, decoded_extension.c_str(), size)
     55       != size) {
     56     return Status(kUnknownError, "failed to write automation extension zip");
     57   }
     58 
     59   base::FilePath extension_dir = temp_dir.AppendASCII("internal");
     60   if (!zip::Unzip(extension_zip, extension_dir))
     61     return Status(kUnknownError, "failed to unzip automation extension");
     62 
     63   *automation_extension = extension_dir;
     64   return Status(kOk);
     65 }
     66 
     67 Status PrepareCommandLine(int port,
     68                           const Capabilities& capabilities,
     69                           CommandLine* prepared_command,
     70                           base::ScopedTempDir* user_data_dir,
     71                           base::ScopedTempDir* extension_dir) {
     72   CommandLine command = capabilities.command;
     73   base::FilePath program = command.GetProgram();
     74   if (program.empty()) {
     75     if (!FindChrome(&program))
     76       return Status(kUnknownError, "cannot find Chrome binary");
     77     command.SetProgram(program);
     78   } else if (!base::PathExists(program)) {
     79     return Status(kUnknownError,
     80                   base::StringPrintf("no chrome binary at %" PRFilePath,
     81                                      program.value().c_str()));
     82   }
     83 
     84   command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port));
     85   command.AppendSwitch("no-first-run");
     86   command.AppendSwitch("enable-logging");
     87   command.AppendSwitchASCII("logging-level", "1");
     88   command.AppendArg("data:text/html;charset=utf-8,");
     89 
     90   if (!command.HasSwitch("user-data-dir")) {
     91     if (!user_data_dir->CreateUniqueTempDir())
     92       return Status(kUnknownError, "cannot create temp dir for user data dir");
     93     command.AppendSwitchPath("user-data-dir", user_data_dir->path());
     94     Status status = internal::PrepareUserDataDir(
     95         user_data_dir->path(), capabilities.prefs.get(),
     96         capabilities.local_state.get());
     97     if (status.IsError())
     98       return status;
     99   }
    100 
    101   if (!extension_dir->CreateUniqueTempDir()) {
    102     return Status(kUnknownError,
    103                   "cannot create temp dir for unpacking extensions");
    104   }
    105   Status status = internal::ProcessExtensions(
    106       capabilities.extensions, extension_dir->path(), true, &command);
    107   if (status.IsError())
    108     return status;
    109 
    110   *prepared_command = command;
    111   return Status(kOk);
    112 }
    113 
    114 Status WaitForDevToolsAndCheckVersion(
    115     int port,
    116     URLRequestContextGetter* context_getter,
    117     const SyncWebSocketFactory& socket_factory,
    118     Log* log,
    119     scoped_ptr<DevToolsHttpClient>* user_client) {
    120   scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient(
    121       port, context_getter, socket_factory, log));
    122   base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20);
    123   Status status = client->Init(deadline - base::Time::Now());
    124   if (status.IsError())
    125     return status;
    126   if (client->build_no() < kMinimumSupportedChromeBuildNo) {
    127     return Status(kUnknownError, "Chrome version must be >= " +
    128         GetMinimumSupportedChromeVersion());
    129   }
    130 
    131   while (base::Time::Now() < deadline) {
    132     WebViewsInfo views_info;
    133     client->GetWebViewsInfo(&views_info);
    134     if (views_info.GetSize()) {
    135       *user_client = client.Pass();
    136       return Status(kOk);
    137     }
    138     base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
    139   }
    140   return Status(kUnknownError, "unable to discover open pages");
    141 }
    142 
    143 Status LaunchDesktopChrome(
    144     URLRequestContextGetter* context_getter,
    145     int port,
    146     const SyncWebSocketFactory& socket_factory,
    147     Log* log,
    148     const Capabilities& capabilities,
    149     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
    150     scoped_ptr<Chrome>* chrome) {
    151   CommandLine command(CommandLine::NO_PROGRAM);
    152   base::ScopedTempDir user_data_dir;
    153   base::ScopedTempDir extension_dir;
    154   Status status = PrepareCommandLine(port, capabilities,
    155                                      &command, &user_data_dir, &extension_dir);
    156   if (status.IsError())
    157     return status;
    158 
    159   for (size_t i = 0; i < arraysize(kCommonSwitches); i++)
    160     command.AppendSwitch(kCommonSwitches[i]);
    161   base::LaunchOptions options;
    162 
    163 #if !defined(OS_WIN)
    164   base::EnvironmentVector environ;
    165   if (!capabilities.log_path.empty()) {
    166     environ.push_back(
    167         base::EnvironmentVector::value_type("CHROME_LOG_FILE",
    168                                             capabilities.log_path));
    169     options.environ = &environ;
    170   }
    171   if (capabilities.detach)
    172     options.new_process_group = true;
    173 #endif
    174 
    175 #if defined(OS_WIN)
    176   std::string command_string = base::WideToUTF8(command.GetCommandLineString());
    177 #else
    178   std::string command_string = command.GetCommandLineString();
    179 #endif
    180   log->AddEntry(Log::kLog, "Launching chrome: " + command_string);
    181   base::ProcessHandle process;
    182   if (!base::LaunchProcess(command, options, &process))
    183     return Status(kUnknownError, "chrome failed to start");
    184 
    185   scoped_ptr<DevToolsHttpClient> devtools_client;
    186   status = WaitForDevToolsAndCheckVersion(
    187       port, context_getter, socket_factory, log, &devtools_client);
    188 
    189   if (status.IsError()) {
    190     int exit_code;
    191     base::TerminationStatus chrome_status =
    192         base::GetTerminationStatus(process, &exit_code);
    193     if (chrome_status != base::TERMINATION_STATUS_STILL_RUNNING) {
    194       std::string termination_reason;
    195       switch (chrome_status) {
    196         case base::TERMINATION_STATUS_NORMAL_TERMINATION:
    197           termination_reason = "exited normally";
    198           break;
    199         case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
    200           termination_reason = "exited abnormally";
    201           break;
    202         case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
    203           termination_reason = "was killed";
    204           break;
    205         case base::TERMINATION_STATUS_PROCESS_CRASHED:
    206           termination_reason = "crashed";
    207           break;
    208         default:
    209           termination_reason = "unknown";
    210           break;
    211       }
    212       return Status(kUnknownError,
    213                     "Chrome failed to start: " + termination_reason);
    214     }
    215     if (!base::KillProcess(process, 0, true)) {
    216       int exit_code;
    217       if (base::GetTerminationStatus(process, &exit_code) ==
    218           base::TERMINATION_STATUS_STILL_RUNNING)
    219         return Status(kUnknownError, "cannot kill Chrome", status);
    220     }
    221     return status;
    222   }
    223   chrome->reset(new ChromeDesktopImpl(devtools_client.Pass(),
    224                                       devtools_event_listeners,
    225                                       log,
    226                                       process,
    227                                       &user_data_dir,
    228                                       &extension_dir));
    229   return Status(kOk);
    230 }
    231 
    232 Status LaunchAndroidChrome(
    233     URLRequestContextGetter* context_getter,
    234     int port,
    235     const SyncWebSocketFactory& socket_factory,
    236     Log* log,
    237     const Capabilities& capabilities,
    238     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
    239     DeviceManager* device_manager,
    240     scoped_ptr<Chrome>* chrome) {
    241   Status status(kOk);
    242   scoped_ptr<Device> device;
    243   if (capabilities.android_device_serial.empty()) {
    244     status = device_manager->AcquireDevice(&device);
    245   } else {
    246     status = device_manager->AcquireSpecificDevice(
    247         capabilities.android_device_serial, &device);
    248   }
    249   if (!status.IsOk())
    250     return status;
    251 
    252   std::string args(capabilities.android_args);
    253   for (size_t i = 0; i < arraysize(kCommonSwitches); i++)
    254     args += "--" + std::string(kCommonSwitches[i]) + " ";
    255   args += "--disable-fre --enable-remote-debugging";
    256 
    257   status = device->StartApp(capabilities.android_package,
    258                             capabilities.android_activity,
    259                             capabilities.android_process,
    260                             args, port);
    261   if (!status.IsOk()) {
    262     device->StopApp();
    263     return status;
    264   }
    265 
    266   scoped_ptr<DevToolsHttpClient> devtools_client;
    267   status = WaitForDevToolsAndCheckVersion(port,
    268                                           context_getter,
    269                                           socket_factory,
    270                                           log,
    271                                           &devtools_client);
    272   if (status.IsError())
    273     return status;
    274 
    275   chrome->reset(new ChromeAndroidImpl(
    276       devtools_client.Pass(), devtools_event_listeners, device.Pass(), log));
    277   return Status(kOk);
    278 }
    279 
    280 }  // namespace
    281 
    282 Status LaunchChrome(
    283     URLRequestContextGetter* context_getter,
    284     int port,
    285     const SyncWebSocketFactory& socket_factory,
    286     Log* log,
    287     DeviceManager* device_manager,
    288     const Capabilities& capabilities,
    289     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
    290     scoped_ptr<Chrome>* chrome) {
    291   if (capabilities.IsAndroid()) {
    292     return LaunchAndroidChrome(
    293         context_getter, port, socket_factory, log, capabilities,
    294         devtools_event_listeners, device_manager, chrome);
    295   } else {
    296     return LaunchDesktopChrome(
    297         context_getter, port, socket_factory, log, capabilities,
    298         devtools_event_listeners, chrome);
    299   }
    300 }
    301 
    302 namespace internal {
    303 
    304 Status ProcessExtensions(const std::vector<std::string>& extensions,
    305                          const base::FilePath& temp_dir,
    306                          bool include_automation_extension,
    307                          CommandLine* command) {
    308   std::vector<base::FilePath::StringType> extension_paths;
    309   size_t count = 0;
    310   for (std::vector<std::string>::const_iterator it = extensions.begin();
    311        it != extensions.end(); ++it) {
    312     std::string extension_base64;
    313     // Decodes extension string.
    314     // Some WebDriver client base64 encoders follow RFC 1521, which require that
    315     // 'encoded lines be no more than 76 characters long'. Just remove any
    316     // newlines.
    317     RemoveChars(*it, "\n", &extension_base64);
    318     std::string decoded_extension;
    319     if (!base::Base64Decode(extension_base64, &decoded_extension))
    320       return Status(kUnknownError, "failed to base64 decode extension");
    321 
    322     // Writes decoded extension into a temporary .crx file.
    323     base::ScopedTempDir temp_crx_dir;
    324     if (!temp_crx_dir.CreateUniqueTempDir())
    325       return Status(kUnknownError,
    326                     "cannot create temp dir for writing extension CRX file");
    327     base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
    328     int size = static_cast<int>(decoded_extension.length());
    329     if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size)
    330         != size) {
    331       return Status(kUnknownError, "failed to write extension file");
    332     }
    333 
    334     // Unzips the temporary .crx file.
    335     count++;
    336     base::FilePath extension_dir = temp_dir.AppendASCII(
    337         base::StringPrintf("extension%" PRIuS, count));
    338     if (!zip::Unzip(extension_crx, extension_dir))
    339       return Status(kUnknownError, "failed to unzip the extension CRX file");
    340     extension_paths.push_back(extension_dir.value());
    341   }
    342 
    343   if (include_automation_extension) {
    344     base::FilePath automation_extension;
    345     Status status = UnpackAutomationExtension(temp_dir, &automation_extension);
    346     if (status.IsError())
    347       return status;
    348     if (command->HasSwitch("disable-extensions")) {
    349       command->AppendSwitchNative("load-component-extension",
    350                                   automation_extension.value());
    351     } else {
    352       extension_paths.push_back(automation_extension.value());
    353     }
    354   }
    355 
    356   if (extension_paths.size()) {
    357     base::FilePath::StringType extension_paths_value = JoinString(
    358         extension_paths, FILE_PATH_LITERAL(','));
    359     command->AppendSwitchNative("load-extension", extension_paths_value);
    360   }
    361   return Status(kOk);
    362 }
    363 
    364 Status WritePrefsFile(
    365     const std::string& template_string,
    366     const base::DictionaryValue* custom_prefs,
    367     const base::FilePath& path) {
    368   int code;
    369   std::string error_msg;
    370   scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError(
    371           template_string, 0, &code, &error_msg));
    372   base::DictionaryValue* prefs;
    373   if (!template_value || !template_value->GetAsDictionary(&prefs)) {
    374     return Status(kUnknownError,
    375                   "cannot parse internal JSON template: " + error_msg);
    376   }
    377 
    378   if (custom_prefs) {
    379     for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd();
    380          it.Advance()) {
    381       prefs->Set(it.key(), it.value().DeepCopy());
    382     }
    383   }
    384 
    385   std::string prefs_str;
    386   base::JSONWriter::Write(prefs, &prefs_str);
    387   if (static_cast<int>(prefs_str.length()) != file_util::WriteFile(
    388           path, prefs_str.c_str(), prefs_str.length())) {
    389     return Status(kUnknownError, "failed to write prefs file");
    390   }
    391   return Status(kOk);
    392 }
    393 
    394 Status PrepareUserDataDir(
    395     const base::FilePath& user_data_dir,
    396     const base::DictionaryValue* custom_prefs,
    397     const base::DictionaryValue* custom_local_state) {
    398   base::FilePath default_dir = user_data_dir.AppendASCII("Default");
    399   if (!file_util::CreateDirectory(default_dir))
    400     return Status(kUnknownError, "cannot create default profile directory");
    401 
    402   Status status = WritePrefsFile(
    403       kPreferences,
    404       custom_prefs,
    405       default_dir.AppendASCII("Preferences"));
    406   if (status.IsError())
    407     return status;
    408 
    409   status = WritePrefsFile(
    410       kLocalState,
    411       custom_local_state,
    412       user_data_dir.AppendASCII("Local State"));
    413   if (status.IsError())
    414     return status;
    415 
    416   // Write empty "First Run" file, otherwise Chrome will wipe the default
    417   // profile that was written.
    418   if (file_util::WriteFile(
    419           user_data_dir.AppendASCII("First Run"), "", 0) != 0) {
    420     return Status(kUnknownError, "failed to write first run file");
    421   }
    422   return Status(kOk);
    423 }
    424 
    425 }  // namespace internal
    426