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