chrome_launcher.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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.h" 20#include "base/process_util.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 41namespace { 42 43const char* kCommonSwitches[] = { 44 "ignore-certificate-errors", "metrics-recording-only"}; 45 46Status 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 67Status 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 114Status 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 143Status 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 232Status 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.device_serial.empty()) { 244 status = device_manager->AcquireDevice(&device); 245 } else { 246 status = device_manager->AcquireSpecificDevice( 247 capabilities.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->StartChrome(capabilities.android_package, port, args); 258 if (!status.IsOk()) 259 return status; 260 261 scoped_ptr<DevToolsHttpClient> devtools_client; 262 status = WaitForDevToolsAndCheckVersion(port, 263 context_getter, 264 socket_factory, 265 log, 266 &devtools_client); 267 if (status.IsError()) 268 return status; 269 270 chrome->reset(new ChromeAndroidImpl( 271 devtools_client.Pass(), devtools_event_listeners, device.Pass(), log)); 272 return Status(kOk); 273} 274 275} // namespace 276 277Status LaunchChrome( 278 URLRequestContextGetter* context_getter, 279 int port, 280 const SyncWebSocketFactory& socket_factory, 281 Log* log, 282 DeviceManager* device_manager, 283 const Capabilities& capabilities, 284 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 285 scoped_ptr<Chrome>* chrome) { 286 if (capabilities.IsAndroid()) { 287 return LaunchAndroidChrome( 288 context_getter, port, socket_factory, log, capabilities, 289 devtools_event_listeners, device_manager, chrome); 290 } else { 291 return LaunchDesktopChrome( 292 context_getter, port, socket_factory, log, capabilities, 293 devtools_event_listeners, chrome); 294 } 295} 296 297namespace internal { 298 299Status ProcessExtensions(const std::vector<std::string>& extensions, 300 const base::FilePath& temp_dir, 301 bool include_automation_extension, 302 CommandLine* command) { 303 std::vector<base::FilePath::StringType> extension_paths; 304 size_t count = 0; 305 for (std::vector<std::string>::const_iterator it = extensions.begin(); 306 it != extensions.end(); ++it) { 307 std::string extension_base64; 308 // Decodes extension string. 309 // Some WebDriver client base64 encoders follow RFC 1521, which require that 310 // 'encoded lines be no more than 76 characters long'. Just remove any 311 // newlines. 312 RemoveChars(*it, "\n", &extension_base64); 313 std::string decoded_extension; 314 if (!base::Base64Decode(extension_base64, &decoded_extension)) 315 return Status(kUnknownError, "failed to base64 decode extension"); 316 317 // Writes decoded extension into a temporary .crx file. 318 base::ScopedTempDir temp_crx_dir; 319 if (!temp_crx_dir.CreateUniqueTempDir()) 320 return Status(kUnknownError, 321 "cannot create temp dir for writing extension CRX file"); 322 base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); 323 int size = static_cast<int>(decoded_extension.length()); 324 if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) 325 != size) { 326 return Status(kUnknownError, "failed to write extension file"); 327 } 328 329 // Unzips the temporary .crx file. 330 count++; 331 base::FilePath extension_dir = temp_dir.AppendASCII( 332 base::StringPrintf("extension%" PRIuS, count)); 333 if (!zip::Unzip(extension_crx, extension_dir)) 334 return Status(kUnknownError, "failed to unzip the extension CRX file"); 335 extension_paths.push_back(extension_dir.value()); 336 } 337 338 if (include_automation_extension) { 339 base::FilePath automation_extension; 340 Status status = UnpackAutomationExtension(temp_dir, &automation_extension); 341 if (status.IsError()) 342 return status; 343 if (command->HasSwitch("disable-extensions")) { 344 command->AppendSwitchNative("load-component-extension", 345 automation_extension.value()); 346 } else { 347 extension_paths.push_back(automation_extension.value()); 348 } 349 } 350 351 if (extension_paths.size()) { 352 base::FilePath::StringType extension_paths_value = JoinString( 353 extension_paths, FILE_PATH_LITERAL(',')); 354 command->AppendSwitchNative("load-extension", extension_paths_value); 355 } 356 return Status(kOk); 357} 358 359Status WritePrefsFile( 360 const std::string& template_string, 361 const base::DictionaryValue* custom_prefs, 362 const base::FilePath& path) { 363 int code; 364 std::string error_msg; 365 scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError( 366 template_string, 0, &code, &error_msg)); 367 base::DictionaryValue* prefs; 368 if (!template_value || !template_value->GetAsDictionary(&prefs)) { 369 return Status(kUnknownError, 370 "cannot parse internal JSON template: " + error_msg); 371 } 372 373 if (custom_prefs) { 374 for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd(); 375 it.Advance()) { 376 prefs->Set(it.key(), it.value().DeepCopy()); 377 } 378 } 379 380 std::string prefs_str; 381 base::JSONWriter::Write(prefs, &prefs_str); 382 if (static_cast<int>(prefs_str.length()) != file_util::WriteFile( 383 path, prefs_str.c_str(), prefs_str.length())) { 384 return Status(kUnknownError, "failed to write prefs file"); 385 } 386 return Status(kOk); 387} 388 389Status PrepareUserDataDir( 390 const base::FilePath& user_data_dir, 391 const base::DictionaryValue* custom_prefs, 392 const base::DictionaryValue* custom_local_state) { 393 base::FilePath default_dir = user_data_dir.AppendASCII("Default"); 394 if (!file_util::CreateDirectory(default_dir)) 395 return Status(kUnknownError, "cannot create default profile directory"); 396 397 Status status = WritePrefsFile( 398 kPreferences, 399 custom_prefs, 400 default_dir.AppendASCII("Preferences")); 401 if (status.IsError()) 402 return status; 403 404 status = WritePrefsFile( 405 kLocalState, 406 custom_local_state, 407 user_data_dir.AppendASCII("Local State")); 408 if (status.IsError()) 409 return status; 410 411 // Write empty "First Run" file, otherwise Chrome will wipe the default 412 // profile that was written. 413 if (file_util::WriteFile( 414 user_data_dir.AppendASCII("First Run"), "", 0) != 0) { 415 return Status(kUnknownError, "failed to write first run file"); 416 } 417 return Status(kOk); 418} 419 420} // namespace internal 421