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