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/session_commands.h" 6 7 #include <list> 8 9 #include "base/bind.h" 10 #include "base/callback.h" 11 #include "base/file_util.h" 12 #include "base/logging.h" // For CHECK macros. 13 #include "base/memory/ref_counted.h" 14 #include "base/message_loop/message_loop_proxy.h" 15 #include "base/synchronization/lock.h" 16 #include "base/synchronization/waitable_event.h" 17 #include "base/values.h" 18 #include "chrome/test/chromedriver/basic_types.h" 19 #include "chrome/test/chromedriver/capabilities.h" 20 #include "chrome/test/chromedriver/chrome/automation_extension.h" 21 #include "chrome/test/chromedriver/chrome/chrome.h" 22 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h" 23 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h" 24 #include "chrome/test/chromedriver/chrome/device_manager.h" 25 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h" 26 #include "chrome/test/chromedriver/chrome/geoposition.h" 27 #include "chrome/test/chromedriver/chrome/status.h" 28 #include "chrome/test/chromedriver/chrome/web_view.h" 29 #include "chrome/test/chromedriver/chrome_launcher.h" 30 #include "chrome/test/chromedriver/logging.h" 31 #include "chrome/test/chromedriver/net/url_request_context_getter.h" 32 #include "chrome/test/chromedriver/session.h" 33 #include "chrome/test/chromedriver/util.h" 34 #include "chrome/test/chromedriver/version.h" 35 36 namespace { 37 38 const char kWindowHandlePrefix[] = "CDwindow-"; 39 40 std::string WebViewIdToWindowHandle(const std::string& web_view_id) { 41 return kWindowHandlePrefix + web_view_id; 42 } 43 44 bool WindowHandleToWebViewId(const std::string& window_handle, 45 std::string* web_view_id) { 46 if (window_handle.find(kWindowHandlePrefix) != 0u) 47 return false; 48 *web_view_id = window_handle.substr( 49 std::string(kWindowHandlePrefix).length()); 50 return true; 51 } 52 53 } // namespace 54 55 InitSessionParams::InitSessionParams( 56 scoped_refptr<URLRequestContextGetter> context_getter, 57 const SyncWebSocketFactory& socket_factory, 58 DeviceManager* device_manager, 59 PortServer* port_server, 60 PortManager* port_manager) 61 : context_getter(context_getter), 62 socket_factory(socket_factory), 63 device_manager(device_manager), 64 port_server(port_server), 65 port_manager(port_manager) {} 66 67 InitSessionParams::~InitSessionParams() {} 68 69 namespace { 70 71 scoped_ptr<base::DictionaryValue> CreateCapabilities(Chrome* chrome) { 72 scoped_ptr<base::DictionaryValue> caps(new base::DictionaryValue()); 73 caps->SetString("browserName", "chrome"); 74 caps->SetString("version", chrome->GetVersion()); 75 caps->SetString("chrome.chromedriverVersion", kChromeDriverVersion); 76 caps->SetString("platform", chrome->GetOperatingSystemName()); 77 caps->SetBoolean("javascriptEnabled", true); 78 caps->SetBoolean("takesScreenshot", true); 79 caps->SetBoolean("takesHeapSnapshot", true); 80 caps->SetBoolean("handlesAlerts", true); 81 caps->SetBoolean("databaseEnabled", false); 82 caps->SetBoolean("locationContextEnabled", true); 83 caps->SetBoolean("applicationCacheEnabled", false); 84 caps->SetBoolean("browserConnectionEnabled", false); 85 caps->SetBoolean("cssSelectorsEnabled", true); 86 caps->SetBoolean("webStorageEnabled", true); 87 caps->SetBoolean("rotatable", false); 88 caps->SetBoolean("acceptSslCerts", true); 89 caps->SetBoolean("nativeEvents", true); 90 scoped_ptr<base::DictionaryValue> chrome_caps(new base::DictionaryValue()); 91 if (chrome->GetAsDesktop()) { 92 chrome_caps->SetString( 93 "userDataDir", 94 chrome->GetAsDesktop()->command().GetSwitchValueNative( 95 "user-data-dir")); 96 } 97 caps->Set("chrome", chrome_caps.release()); 98 return caps.Pass(); 99 } 100 101 102 Status InitSessionHelper( 103 const InitSessionParams& bound_params, 104 Session* session, 105 const base::DictionaryValue& params, 106 scoped_ptr<base::Value>* value) { 107 session->driver_log.reset( 108 new WebDriverLog(WebDriverLog::kDriverType, Log::kAll)); 109 const base::DictionaryValue* desired_caps; 110 if (!params.GetDictionary("desiredCapabilities", &desired_caps)) 111 return Status(kUnknownError, "cannot find dict 'desiredCapabilities'"); 112 113 Capabilities capabilities; 114 Status status = capabilities.Parse(*desired_caps); 115 if (status.IsError()) 116 return status; 117 118 Log::Level driver_level = Log::kWarning; 119 if (capabilities.logging_prefs.count(WebDriverLog::kDriverType)) 120 driver_level = capabilities.logging_prefs[WebDriverLog::kDriverType]; 121 session->driver_log->set_min_level(driver_level); 122 123 // Create Log's and DevToolsEventListener's for ones that are DevTools-based. 124 // Session will own the Log's, Chrome will own the listeners. 125 ScopedVector<DevToolsEventListener> devtools_event_listeners; 126 status = CreateLogs(capabilities, 127 &session->devtools_logs, 128 &devtools_event_listeners); 129 if (status.IsError()) 130 return status; 131 132 status = LaunchChrome(bound_params.context_getter.get(), 133 bound_params.socket_factory, 134 bound_params.device_manager, 135 bound_params.port_server, 136 bound_params.port_manager, 137 capabilities, 138 devtools_event_listeners, 139 &session->chrome); 140 if (status.IsError()) 141 return status; 142 143 std::list<std::string> web_view_ids; 144 status = session->chrome->GetWebViewIds(&web_view_ids); 145 if (status.IsError() || web_view_ids.empty()) { 146 return status.IsError() ? status : 147 Status(kUnknownError, "unable to discover open window in chrome"); 148 } 149 150 session->window = web_view_ids.front(); 151 session->detach = capabilities.detach; 152 session->force_devtools_screenshot = capabilities.force_devtools_screenshot; 153 session->capabilities = CreateCapabilities(session->chrome.get()); 154 value->reset(session->capabilities->DeepCopy()); 155 return Status(kOk); 156 } 157 158 } // namespace 159 160 Status ExecuteInitSession( 161 const InitSessionParams& bound_params, 162 Session* session, 163 const base::DictionaryValue& params, 164 scoped_ptr<base::Value>* value) { 165 Status status = InitSessionHelper(bound_params, session, params, value); 166 if (status.IsError()) 167 session->quit = true; 168 return status; 169 } 170 171 Status ExecuteQuit( 172 bool allow_detach, 173 Session* session, 174 const base::DictionaryValue& params, 175 scoped_ptr<base::Value>* value) { 176 session->quit = true; 177 if (allow_detach && session->detach) 178 return Status(kOk); 179 else 180 return session->chrome->Quit(); 181 } 182 183 Status ExecuteGetSessionCapabilities( 184 Session* session, 185 const base::DictionaryValue& params, 186 scoped_ptr<base::Value>* value) { 187 value->reset(session->capabilities->DeepCopy()); 188 return Status(kOk); 189 } 190 191 Status ExecuteGetCurrentWindowHandle( 192 Session* session, 193 const base::DictionaryValue& params, 194 scoped_ptr<base::Value>* value) { 195 WebView* web_view = NULL; 196 Status status = session->GetTargetWindow(&web_view); 197 if (status.IsError()) 198 return status; 199 200 value->reset(new StringValue(WebViewIdToWindowHandle(web_view->GetId()))); 201 return Status(kOk); 202 } 203 204 Status ExecuteClose( 205 Session* session, 206 const base::DictionaryValue& params, 207 scoped_ptr<base::Value>* value) { 208 std::list<std::string> web_view_ids; 209 Status status = session->chrome->GetWebViewIds(&web_view_ids); 210 if (status.IsError()) 211 return status; 212 bool is_last_web_view = web_view_ids.size() == 1u; 213 web_view_ids.clear(); 214 215 WebView* web_view = NULL; 216 status = session->GetTargetWindow(&web_view); 217 if (status.IsError()) 218 return status; 219 220 status = session->chrome->CloseWebView(web_view->GetId()); 221 if (status.IsError()) 222 return status; 223 224 status = session->chrome->GetWebViewIds(&web_view_ids); 225 if ((status.code() == kChromeNotReachable && is_last_web_view) || 226 (status.IsOk() && web_view_ids.empty())) { 227 // If no window is open, close is the equivalent of calling "quit". 228 session->quit = true; 229 return session->chrome->Quit(); 230 } 231 232 return status; 233 } 234 235 Status ExecuteGetWindowHandles( 236 Session* session, 237 const base::DictionaryValue& params, 238 scoped_ptr<base::Value>* value) { 239 std::list<std::string> web_view_ids; 240 Status status = session->chrome->GetWebViewIds(&web_view_ids); 241 if (status.IsError()) 242 return status; 243 scoped_ptr<base::ListValue> window_ids(new base::ListValue()); 244 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 245 it != web_view_ids.end(); ++it) { 246 window_ids->AppendString(WebViewIdToWindowHandle(*it)); 247 } 248 value->reset(window_ids.release()); 249 return Status(kOk); 250 } 251 252 Status ExecuteSwitchToWindow( 253 Session* session, 254 const base::DictionaryValue& params, 255 scoped_ptr<base::Value>* value) { 256 std::string name; 257 if (!params.GetString("name", &name) || name.empty()) 258 return Status(kUnknownError, "'name' must be a nonempty string"); 259 260 std::list<std::string> web_view_ids; 261 Status status = session->chrome->GetWebViewIds(&web_view_ids); 262 if (status.IsError()) 263 return status; 264 265 std::string web_view_id; 266 bool found = false; 267 if (WindowHandleToWebViewId(name, &web_view_id)) { 268 // Check if any web_view matches |web_view_id|. 269 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 270 it != web_view_ids.end(); ++it) { 271 if (*it == web_view_id) { 272 found = true; 273 break; 274 } 275 } 276 } else { 277 // Check if any of the tab window names match |name|. 278 const char* kGetWindowNameScript = "function() { return window.name; }"; 279 base::ListValue args; 280 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 281 it != web_view_ids.end(); ++it) { 282 scoped_ptr<base::Value> result; 283 WebView* web_view; 284 status = session->chrome->GetWebViewById(*it, &web_view); 285 if (status.IsError()) 286 return status; 287 status = web_view->ConnectIfNecessary(); 288 if (status.IsError()) 289 return status; 290 status = web_view->CallFunction( 291 std::string(), kGetWindowNameScript, args, &result); 292 if (status.IsError()) 293 return status; 294 std::string window_name; 295 if (!result->GetAsString(&window_name)) 296 return Status(kUnknownError, "failed to get window name"); 297 if (window_name == name) { 298 web_view_id = *it; 299 found = true; 300 break; 301 } 302 } 303 } 304 305 if (!found) 306 return Status(kNoSuchWindow); 307 308 if (session->overridden_geoposition) { 309 WebView* web_view; 310 status = session->chrome->GetWebViewById(web_view_id, &web_view); 311 if (status.IsError()) 312 return status; 313 status = web_view->ConnectIfNecessary(); 314 if (status.IsError()) 315 return status; 316 status = web_view->OverrideGeolocation(*session->overridden_geoposition); 317 if (status.IsError()) 318 return status; 319 } 320 321 session->window = web_view_id; 322 session->SwitchToTopFrame(); 323 session->mouse_position = WebPoint(0, 0); 324 return Status(kOk); 325 } 326 327 Status ExecuteSetTimeout( 328 Session* session, 329 const base::DictionaryValue& params, 330 scoped_ptr<base::Value>* value) { 331 double ms_double; 332 if (!params.GetDouble("ms", &ms_double)) 333 return Status(kUnknownError, "'ms' must be a double"); 334 std::string type; 335 if (!params.GetString("type", &type)) 336 return Status(kUnknownError, "'type' must be a string"); 337 338 base::TimeDelta timeout = 339 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double)); 340 // TODO(frankf): implicit and script timeout should be cleared 341 // if negative timeout is specified. 342 if (type == "implicit") { 343 session->implicit_wait = timeout; 344 } else if (type == "script") { 345 session->script_timeout = timeout; 346 } else if (type == "page load") { 347 session->page_load_timeout = 348 ((timeout < base::TimeDelta()) ? Session::kDefaultPageLoadTimeout 349 : timeout); 350 } else { 351 return Status(kUnknownError, "unknown type of timeout:" + type); 352 } 353 return Status(kOk); 354 } 355 356 Status ExecuteSetScriptTimeout( 357 Session* session, 358 const base::DictionaryValue& params, 359 scoped_ptr<base::Value>* value) { 360 double ms; 361 if (!params.GetDouble("ms", &ms) || ms < 0) 362 return Status(kUnknownError, "'ms' must be a non-negative number"); 363 session->script_timeout = 364 base::TimeDelta::FromMilliseconds(static_cast<int>(ms)); 365 return Status(kOk); 366 } 367 368 Status ExecuteImplicitlyWait( 369 Session* session, 370 const base::DictionaryValue& params, 371 scoped_ptr<base::Value>* value) { 372 double ms; 373 if (!params.GetDouble("ms", &ms) || ms < 0) 374 return Status(kUnknownError, "'ms' must be a non-negative number"); 375 session->implicit_wait = 376 base::TimeDelta::FromMilliseconds(static_cast<int>(ms)); 377 return Status(kOk); 378 } 379 380 Status ExecuteIsLoading( 381 Session* session, 382 const base::DictionaryValue& params, 383 scoped_ptr<base::Value>* value) { 384 WebView* web_view = NULL; 385 Status status = session->GetTargetWindow(&web_view); 386 if (status.IsError()) 387 return status; 388 389 status = web_view->ConnectIfNecessary(); 390 if (status.IsError()) 391 return status; 392 393 bool is_pending; 394 status = web_view->IsPendingNavigation( 395 session->GetCurrentFrameId(), &is_pending); 396 if (status.IsError()) 397 return status; 398 value->reset(new base::FundamentalValue(is_pending)); 399 return Status(kOk); 400 } 401 402 Status ExecuteGetLocation( 403 Session* session, 404 const base::DictionaryValue& params, 405 scoped_ptr<base::Value>* value) { 406 if (!session->overridden_geoposition) { 407 return Status(kUnknownError, 408 "Location must be set before it can be retrieved"); 409 } 410 base::DictionaryValue location; 411 location.SetDouble("latitude", session->overridden_geoposition->latitude); 412 location.SetDouble("longitude", session->overridden_geoposition->longitude); 413 location.SetDouble("accuracy", session->overridden_geoposition->accuracy); 414 // Set a dummy altitude to make WebDriver clients happy. 415 // https://code.google.com/p/chromedriver/issues/detail?id=281 416 location.SetDouble("altitude", 0); 417 value->reset(location.DeepCopy()); 418 return Status(kOk); 419 } 420 421 Status ExecuteGetWindowPosition( 422 Session* session, 423 const base::DictionaryValue& params, 424 scoped_ptr<base::Value>* value) { 425 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 426 if (!desktop) { 427 return Status( 428 kUnknownError, 429 "command only supported for desktop Chrome without debuggerAddress"); 430 } 431 432 AutomationExtension* extension = NULL; 433 Status status = desktop->GetAutomationExtension(&extension); 434 if (status.IsError()) 435 return status; 436 437 int x, y; 438 status = extension->GetWindowPosition(&x, &y); 439 if (status.IsError()) 440 return status; 441 442 base::DictionaryValue position; 443 position.SetInteger("x", x); 444 position.SetInteger("y", y); 445 value->reset(position.DeepCopy()); 446 return Status(kOk); 447 } 448 449 Status ExecuteSetWindowPosition( 450 Session* session, 451 const base::DictionaryValue& params, 452 scoped_ptr<base::Value>* value) { 453 double x, y; 454 if (!params.GetDouble("x", &x) || !params.GetDouble("y", &y)) 455 return Status(kUnknownError, "missing or invalid 'x' or 'y'"); 456 457 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 458 if (!desktop) { 459 return Status( 460 kUnknownError, 461 "command only supported for desktop Chrome without debuggerAddress"); 462 } 463 464 AutomationExtension* extension = NULL; 465 Status status = desktop->GetAutomationExtension(&extension); 466 if (status.IsError()) 467 return status; 468 469 return extension->SetWindowPosition(static_cast<int>(x), static_cast<int>(y)); 470 } 471 472 Status ExecuteGetWindowSize( 473 Session* session, 474 const base::DictionaryValue& params, 475 scoped_ptr<base::Value>* value) { 476 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 477 if (!desktop) { 478 return Status( 479 kUnknownError, 480 "command only supported for desktop Chrome without debuggerAddress"); 481 } 482 483 AutomationExtension* extension = NULL; 484 Status status = desktop->GetAutomationExtension(&extension); 485 if (status.IsError()) 486 return status; 487 488 int width, height; 489 status = extension->GetWindowSize(&width, &height); 490 if (status.IsError()) 491 return status; 492 493 base::DictionaryValue size; 494 size.SetInteger("width", width); 495 size.SetInteger("height", height); 496 value->reset(size.DeepCopy()); 497 return Status(kOk); 498 } 499 500 Status ExecuteSetWindowSize( 501 Session* session, 502 const base::DictionaryValue& params, 503 scoped_ptr<base::Value>* value) { 504 double width, height; 505 if (!params.GetDouble("width", &width) || 506 !params.GetDouble("height", &height)) 507 return Status(kUnknownError, "missing or invalid 'width' or 'height'"); 508 509 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 510 if (!desktop) { 511 return Status( 512 kUnknownError, 513 "command only supported for desktop Chrome without debuggerAddress"); 514 } 515 516 AutomationExtension* extension = NULL; 517 Status status = desktop->GetAutomationExtension(&extension); 518 if (status.IsError()) 519 return status; 520 521 return extension->SetWindowSize( 522 static_cast<int>(width), static_cast<int>(height)); 523 } 524 525 Status ExecuteMaximizeWindow( 526 Session* session, 527 const base::DictionaryValue& params, 528 scoped_ptr<base::Value>* value) { 529 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 530 if (!desktop) { 531 return Status( 532 kUnknownError, 533 "command only supported for desktop Chrome without debuggerAddress"); 534 } 535 536 AutomationExtension* extension = NULL; 537 Status status = desktop->GetAutomationExtension(&extension); 538 if (status.IsError()) 539 return status; 540 541 return extension->MaximizeWindow(); 542 } 543 544 Status ExecuteGetAvailableLogTypes( 545 Session* session, 546 const base::DictionaryValue& params, 547 scoped_ptr<base::Value>* value) { 548 scoped_ptr<base::ListValue> types(new base::ListValue()); 549 std::vector<WebDriverLog*> logs = session->GetAllLogs(); 550 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin(); 551 log != logs.end(); 552 ++log) { 553 types->AppendString((*log)->type()); 554 } 555 *value = types.Pass(); 556 return Status(kOk); 557 } 558 559 Status ExecuteGetLog( 560 Session* session, 561 const base::DictionaryValue& params, 562 scoped_ptr<base::Value>* value) { 563 std::string log_type; 564 if (!params.GetString("type", &log_type)) { 565 return Status(kUnknownError, "missing or invalid 'type'"); 566 } 567 std::vector<WebDriverLog*> logs = session->GetAllLogs(); 568 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin(); 569 log != logs.end(); 570 ++log) { 571 if (log_type == (*log)->type()) { 572 *value = (*log)->GetAndClearEntries(); 573 return Status(kOk); 574 } 575 } 576 return Status(kUnknownError, "log type '" + log_type + "' not found"); 577 } 578 579 Status ExecuteUploadFile( 580 Session* session, 581 const base::DictionaryValue& params, 582 scoped_ptr<base::Value>* value) { 583 std::string base64_zip_data; 584 if (!params.GetString("file", &base64_zip_data)) 585 return Status(kUnknownError, "missing or invalid 'file'"); 586 std::string zip_data; 587 if (!Base64Decode(base64_zip_data, &zip_data)) 588 return Status(kUnknownError, "unable to decode 'file'"); 589 590 if (!session->temp_dir.IsValid()) { 591 if (!session->temp_dir.CreateUniqueTempDir()) 592 return Status(kUnknownError, "unable to create temp dir"); 593 } 594 base::FilePath upload_dir; 595 if (!base::CreateTemporaryDirInDir(session->temp_dir.path(), 596 FILE_PATH_LITERAL("upload"), 597 &upload_dir)) { 598 return Status(kUnknownError, "unable to create temp dir"); 599 } 600 std::string error_msg; 601 base::FilePath upload; 602 Status status = UnzipSoleFile(upload_dir, zip_data, &upload); 603 if (status.IsError()) 604 return Status(kUnknownError, "unable to unzip 'file'", status); 605 606 value->reset(new base::StringValue(upload.value())); 607 return Status(kOk); 608 } 609