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/chrome/automation_extension.h" 20 #include "chrome/test/chromedriver/chrome/chrome.h" 21 #include "chrome/test/chromedriver/chrome/geoposition.h" 22 #include "chrome/test/chromedriver/chrome/status.h" 23 #include "chrome/test/chromedriver/chrome/web_view.h" 24 #include "chrome/test/chromedriver/logging.h" 25 #include "chrome/test/chromedriver/session.h" 26 #include "chrome/test/chromedriver/util.h" 27 28 namespace { 29 30 const char kWindowHandlePrefix[] = "CDwindow-"; 31 32 std::string WebViewIdToWindowHandle(const std::string& web_view_id) { 33 return kWindowHandlePrefix + web_view_id; 34 } 35 36 bool WindowHandleToWebViewId(const std::string& window_handle, 37 std::string* web_view_id) { 38 if (window_handle.find(kWindowHandlePrefix) != 0u) 39 return false; 40 *web_view_id = window_handle.substr( 41 std::string(kWindowHandlePrefix).length()); 42 return true; 43 } 44 45 } // namespace 46 47 Status ExecuteQuit( 48 bool allow_detach, 49 Session* session, 50 const base::DictionaryValue& params, 51 scoped_ptr<base::Value>* value) { 52 if (allow_detach && session->detach) { 53 return Status(kOk); 54 } else { 55 session->quit = true; 56 return session->chrome->Quit(); 57 } 58 } 59 60 Status ExecuteGetSessionCapabilities( 61 Session* session, 62 const base::DictionaryValue& params, 63 scoped_ptr<base::Value>* value) { 64 value->reset(session->capabilities->DeepCopy()); 65 return Status(kOk); 66 } 67 68 Status ExecuteGetCurrentWindowHandle( 69 Session* session, 70 const base::DictionaryValue& params, 71 scoped_ptr<base::Value>* value) { 72 WebView* web_view = NULL; 73 Status status = session->GetTargetWindow(&web_view); 74 if (status.IsError()) 75 return status; 76 77 value->reset(new StringValue(WebViewIdToWindowHandle(web_view->GetId()))); 78 return Status(kOk); 79 } 80 81 Status ExecuteClose( 82 Session* session, 83 const base::DictionaryValue& params, 84 scoped_ptr<base::Value>* value) { 85 std::list<std::string> web_view_ids; 86 Status status = session->chrome->GetWebViewIds(&web_view_ids); 87 if (status.IsError()) 88 return status; 89 bool is_last_web_view = web_view_ids.size() == 1u; 90 web_view_ids.clear(); 91 92 WebView* web_view = NULL; 93 status = session->GetTargetWindow(&web_view); 94 if (status.IsError()) 95 return status; 96 97 status = session->chrome->CloseWebView(web_view->GetId()); 98 if (status.IsError()) 99 return status; 100 101 status = session->chrome->GetWebViewIds(&web_view_ids); 102 if ((status.code() == kChromeNotReachable && is_last_web_view) || 103 (status.IsOk() && web_view_ids.empty())) { 104 // If no window is open, close is the equivalent of calling "quit". 105 session->quit = true; 106 return session->chrome->Quit(); 107 } 108 109 return status; 110 } 111 112 Status ExecuteGetWindowHandles( 113 Session* session, 114 const base::DictionaryValue& params, 115 scoped_ptr<base::Value>* value) { 116 std::list<std::string> web_view_ids; 117 Status status = session->chrome->GetWebViewIds(&web_view_ids); 118 if (status.IsError()) 119 return status; 120 scoped_ptr<base::ListValue> window_ids(new base::ListValue()); 121 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 122 it != web_view_ids.end(); ++it) { 123 window_ids->AppendString(WebViewIdToWindowHandle(*it)); 124 } 125 value->reset(window_ids.release()); 126 return Status(kOk); 127 } 128 129 Status ExecuteSwitchToWindow( 130 Session* session, 131 const base::DictionaryValue& params, 132 scoped_ptr<base::Value>* value) { 133 std::string name; 134 if (!params.GetString("name", &name) || name.empty()) 135 return Status(kUnknownError, "'name' must be a nonempty string"); 136 137 std::list<std::string> web_view_ids; 138 Status status = session->chrome->GetWebViewIds(&web_view_ids); 139 if (status.IsError()) 140 return status; 141 142 std::string web_view_id; 143 bool found = false; 144 if (WindowHandleToWebViewId(name, &web_view_id)) { 145 // Check if any web_view matches |web_view_id|. 146 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 147 it != web_view_ids.end(); ++it) { 148 if (*it == web_view_id) { 149 found = true; 150 break; 151 } 152 } 153 } else { 154 // Check if any of the tab window names match |name|. 155 const char* kGetWindowNameScript = "function() { return window.name; }"; 156 base::ListValue args; 157 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 158 it != web_view_ids.end(); ++it) { 159 scoped_ptr<base::Value> result; 160 WebView* web_view; 161 status = session->chrome->GetWebViewById(*it, &web_view); 162 if (status.IsError()) 163 return status; 164 status = web_view->ConnectIfNecessary(); 165 if (status.IsError()) 166 return status; 167 status = web_view->CallFunction( 168 std::string(), kGetWindowNameScript, args, &result); 169 if (status.IsError()) 170 return status; 171 std::string window_name; 172 if (!result->GetAsString(&window_name)) 173 return Status(kUnknownError, "failed to get window name"); 174 if (window_name == name) { 175 web_view_id = *it; 176 found = true; 177 break; 178 } 179 } 180 } 181 182 if (!found) 183 return Status(kNoSuchWindow); 184 185 if (session->overridden_geoposition) { 186 WebView* web_view; 187 status = session->chrome->GetWebViewById(web_view_id, &web_view); 188 if (status.IsError()) 189 return status; 190 status = web_view->ConnectIfNecessary(); 191 if (status.IsError()) 192 return status; 193 status = web_view->OverrideGeolocation(*session->overridden_geoposition); 194 if (status.IsError()) 195 return status; 196 } 197 198 session->window = web_view_id; 199 session->SwitchToTopFrame(); 200 session->mouse_position = WebPoint(0, 0); 201 return Status(kOk); 202 } 203 204 Status ExecuteSetTimeout( 205 Session* session, 206 const base::DictionaryValue& params, 207 scoped_ptr<base::Value>* value) { 208 double ms_double; 209 if (!params.GetDouble("ms", &ms_double)) 210 return Status(kUnknownError, "'ms' must be a double"); 211 std::string type; 212 if (!params.GetString("type", &type)) 213 return Status(kUnknownError, "'type' must be a string"); 214 215 int ms = static_cast<int>(ms_double); 216 // TODO(frankf): implicit and script timeout should be cleared 217 // if negative timeout is specified. 218 if (type == "implicit") 219 session->implicit_wait = ms; 220 else if (type == "script") 221 session->script_timeout = ms; 222 else if (type == "page load") 223 session->page_load_timeout = 224 ((ms < 0) ? Session::kDefaultPageLoadTimeoutMs : ms); 225 else 226 return Status(kUnknownError, "unknown type of timeout:" + type); 227 return Status(kOk); 228 } 229 230 Status ExecuteSetScriptTimeout( 231 Session* session, 232 const base::DictionaryValue& params, 233 scoped_ptr<base::Value>* value) { 234 double ms; 235 if (!params.GetDouble("ms", &ms) || ms < 0) 236 return Status(kUnknownError, "'ms' must be a non-negative number"); 237 session->script_timeout = static_cast<int>(ms); 238 return Status(kOk); 239 } 240 241 Status ExecuteImplicitlyWait( 242 Session* session, 243 const base::DictionaryValue& params, 244 scoped_ptr<base::Value>* value) { 245 double ms; 246 if (!params.GetDouble("ms", &ms) || ms < 0) 247 return Status(kUnknownError, "'ms' must be a non-negative number"); 248 session->implicit_wait = static_cast<int>(ms); 249 return Status(kOk); 250 } 251 252 Status ExecuteIsLoading( 253 Session* session, 254 const base::DictionaryValue& params, 255 scoped_ptr<base::Value>* value) { 256 WebView* web_view = NULL; 257 Status status = session->GetTargetWindow(&web_view); 258 if (status.IsError()) 259 return status; 260 261 status = web_view->ConnectIfNecessary(); 262 if (status.IsError()) 263 return status; 264 265 bool is_pending; 266 status = web_view->IsPendingNavigation( 267 session->GetCurrentFrameId(), &is_pending); 268 if (status.IsError()) 269 return status; 270 value->reset(new base::FundamentalValue(is_pending)); 271 return Status(kOk); 272 } 273 274 Status ExecuteGetLocation( 275 Session* session, 276 const base::DictionaryValue& params, 277 scoped_ptr<base::Value>* value) { 278 if (!session->overridden_geoposition) { 279 return Status(kUnknownError, 280 "Location must be set before it can be retrieved"); 281 } 282 base::DictionaryValue location; 283 location.SetDouble("latitude", session->overridden_geoposition->latitude); 284 location.SetDouble("longitude", session->overridden_geoposition->longitude); 285 location.SetDouble("accuracy", session->overridden_geoposition->accuracy); 286 // Set a dummy altitude to make WebDriver clients happy. 287 // https://code.google.com/p/chromedriver/issues/detail?id=281 288 location.SetDouble("altitude", 0); 289 value->reset(location.DeepCopy()); 290 return Status(kOk); 291 } 292 293 Status ExecuteGetWindowPosition( 294 Session* session, 295 const base::DictionaryValue& params, 296 scoped_ptr<base::Value>* value) { 297 AutomationExtension* extension = NULL; 298 Status status = session->chrome->GetAutomationExtension(&extension); 299 if (status.IsError()) 300 return status; 301 302 int x, y; 303 status = extension->GetWindowPosition(&x, &y); 304 if (status.IsError()) 305 return status; 306 307 base::DictionaryValue position; 308 position.SetInteger("x", x); 309 position.SetInteger("y", y); 310 value->reset(position.DeepCopy()); 311 return Status(kOk); 312 } 313 314 Status ExecuteSetWindowPosition( 315 Session* session, 316 const base::DictionaryValue& params, 317 scoped_ptr<base::Value>* value) { 318 double x, y; 319 if (!params.GetDouble("x", &x) || !params.GetDouble("y", &y)) 320 return Status(kUnknownError, "missing or invalid 'x' or 'y'"); 321 AutomationExtension* extension = NULL; 322 Status status = session->chrome->GetAutomationExtension(&extension); 323 if (status.IsError()) 324 return status; 325 326 return extension->SetWindowPosition(static_cast<int>(x), static_cast<int>(y)); 327 } 328 329 Status ExecuteGetWindowSize( 330 Session* session, 331 const base::DictionaryValue& params, 332 scoped_ptr<base::Value>* value) { 333 AutomationExtension* extension = NULL; 334 Status status = session->chrome->GetAutomationExtension(&extension); 335 if (status.IsError()) 336 return status; 337 338 int width, height; 339 status = extension->GetWindowSize(&width, &height); 340 if (status.IsError()) 341 return status; 342 343 base::DictionaryValue size; 344 size.SetInteger("width", width); 345 size.SetInteger("height", height); 346 value->reset(size.DeepCopy()); 347 return Status(kOk); 348 } 349 350 Status ExecuteSetWindowSize( 351 Session* session, 352 const base::DictionaryValue& params, 353 scoped_ptr<base::Value>* value) { 354 double width, height; 355 if (!params.GetDouble("width", &width) || 356 !params.GetDouble("height", &height)) 357 return Status(kUnknownError, "missing or invalid 'width' or 'height'"); 358 AutomationExtension* extension = NULL; 359 Status status = session->chrome->GetAutomationExtension(&extension); 360 if (status.IsError()) 361 return status; 362 363 return extension->SetWindowSize( 364 static_cast<int>(width), static_cast<int>(height)); 365 } 366 367 Status ExecuteMaximizeWindow( 368 Session* session, 369 const base::DictionaryValue& params, 370 scoped_ptr<base::Value>* value) { 371 AutomationExtension* extension = NULL; 372 Status status = session->chrome->GetAutomationExtension(&extension); 373 if (status.IsError()) 374 return status; 375 376 return extension->MaximizeWindow(); 377 } 378 379 Status ExecuteGetAvailableLogTypes( 380 Session* session, 381 const base::DictionaryValue& params, 382 scoped_ptr<base::Value>* value) { 383 scoped_ptr<base::ListValue> types(new base::ListValue()); 384 for (ScopedVector<WebDriverLog>::const_iterator log 385 = session->devtools_logs.begin(); 386 log != session->devtools_logs.end(); ++log) { 387 types->AppendString((*log)->GetType()); 388 } 389 value->reset(types.release()); 390 return Status(kOk); 391 } 392 393 Status ExecuteGetLog( 394 Session* session, 395 const base::DictionaryValue& params, 396 scoped_ptr<base::Value>* value) { 397 std::string log_type; 398 if (!params.GetString("type", &log_type)) { 399 return Status(kUnknownError, "missing or invalid 'type'"); 400 } 401 for (ScopedVector<WebDriverLog>::const_iterator log 402 = session->devtools_logs.begin(); 403 log != session->devtools_logs.end(); ++log) { 404 if (log_type == (*log)->GetType()) { 405 scoped_ptr<base::ListValue> log_entries = (*log)->GetAndClearEntries(); 406 value->reset(log_entries.release()); 407 return Status(kOk); 408 } 409 } 410 return Status(kUnknownError, "log type '" + log_type + "' not found"); 411 } 412 413 Status ExecuteUploadFile( 414 Session* session, 415 const base::DictionaryValue& params, 416 scoped_ptr<base::Value>* value) { 417 std::string base64_zip_data; 418 if (!params.GetString("file", &base64_zip_data)) 419 return Status(kUnknownError, "missing or invalid 'file'"); 420 std::string zip_data; 421 if (!Base64Decode(base64_zip_data, &zip_data)) 422 return Status(kUnknownError, "unable to decode 'file'"); 423 424 if (!session->temp_dir.IsValid()) { 425 if (!session->temp_dir.CreateUniqueTempDir()) 426 return Status(kUnknownError, "unable to create temp dir"); 427 } 428 base::FilePath upload_dir; 429 if (!file_util::CreateTemporaryDirInDir( 430 session->temp_dir.path(), FILE_PATH_LITERAL("upload"), &upload_dir)) { 431 return Status(kUnknownError, "unable to create temp dir"); 432 } 433 std::string error_msg; 434 base::FilePath upload; 435 Status status = UnzipSoleFile(upload_dir, zip_data, &upload); 436 if (status.IsError()) 437 return Status(kUnknownError, "unable to unzip 'file'", status); 438 439 value->reset(new base::StringValue(upload.value())); 440 return Status(kOk); 441 } 442