1// Copyright (c) 2012 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/webdriver/webdriver_session.h" 6 7#include <sstream> 8#include <vector> 9 10#include "base/bind.h" 11#include "base/callback.h" 12#include "base/command_line.h" 13#include "base/file_util.h" 14#include "base/files/file_path.h" 15#include "base/json/json_reader.h" 16#include "base/json/json_writer.h" 17#include "base/memory/scoped_ptr.h" 18#include "base/message_loop/message_loop_proxy.h" 19#include "base/process/process.h" 20#include "base/strings/string_number_conversions.h" 21#include "base/strings/string_split.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/synchronization/waitable_event.h" 26#include "base/test/test_timeouts.h" 27#include "base/threading/platform_thread.h" 28#include "base/time/time.h" 29#include "base/values.h" 30#include "chrome/app/chrome_command_ids.h" 31#include "chrome/common/chrome_constants.h" 32#include "chrome/common/chrome_switches.h" 33#include "chrome/test/automation/automation_json_requests.h" 34#include "chrome/test/automation/value_conversion_util.h" 35#include "chrome/test/webdriver/webdriver_capabilities_parser.h" 36#include "chrome/test/webdriver/webdriver_error.h" 37#include "chrome/test/webdriver/webdriver_key_converter.h" 38#include "chrome/test/webdriver/webdriver_logging.h" 39#include "chrome/test/webdriver/webdriver_session_manager.h" 40#include "chrome/test/webdriver/webdriver_util.h" 41#include "third_party/webdriver/atoms.h" 42 43using automation::kLeftButton; 44using automation::kMouseDown; 45using automation::kMouseMove; 46using automation::kMouseUp; 47using automation::kNoButton; 48 49namespace webdriver { 50 51namespace { 52// This is the minimum version of chrome that supports the new mouse API. 53const int kNewMouseAPIMinVersion = 1002; 54} 55 56FrameId::FrameId() {} 57 58FrameId::FrameId(const WebViewId& view_id, const FramePath& frame_path) 59 : view_id(view_id), 60 frame_path(frame_path) { 61} 62 63Session::Session() 64 : session_log_(new InMemoryLog()), 65 logger_(kAllLogLevel), 66 id_(GenerateRandomID()), 67 current_target_(FrameId(WebViewId(), FramePath())), 68 thread_(id_.c_str()), 69 async_script_timeout_(0), 70 implicit_wait_(0), 71 has_alert_prompt_text_(false), 72 sticky_modifiers_(0), 73 build_no_(0) { 74 SessionManager::GetInstance()->Add(this); 75 logger_.AddHandler(session_log_.get()); 76 if (FileLog::Get()) 77 logger_.AddHandler(FileLog::Get()); 78} 79 80Session::~Session() { 81 SessionManager::GetInstance()->Remove(id_); 82} 83 84Error* Session::Init(const base::DictionaryValue* capabilities_dict) { 85 if (!thread_.Start()) { 86 delete this; 87 return new Error(kUnknownError, "Cannot start session thread"); 88 } 89 if (!temp_dir_.CreateUniqueTempDir()) { 90 delete this; 91 return new Error( 92 kUnknownError, "Unable to create temp directory for unpacking"); 93 } 94 logger_.Log(kFineLogLevel, 95 "Initializing session with capabilities " + 96 JsonStringifyForDisplay(capabilities_dict)); 97 CapabilitiesParser parser( 98 capabilities_dict, temp_dir_.path(), logger_, &capabilities_); 99 Error* error = parser.Parse(); 100 if (error) { 101 delete this; 102 return error; 103 } 104 logger_.set_min_log_level(capabilities_.log_levels[LogType::kDriver]); 105 106 Automation::BrowserOptions browser_options; 107 browser_options.command = capabilities_.command; 108 browser_options.channel_id = capabilities_.channel; 109 browser_options.detach_process = capabilities_.detach; 110 browser_options.user_data_dir = capabilities_.profile; 111 browser_options.exclude_switches = capabilities_.exclude_switches; 112 if (!capabilities_.no_website_testing_defaults) { 113 browser_options.ignore_certificate_errors = true; 114 } 115 RunSessionTask(base::Bind( 116 &Session::InitOnSessionThread, 117 base::Unretained(this), 118 browser_options, 119 &build_no_, 120 &error)); 121 if (!error) 122 error = PostBrowserStartInit(); 123 124 if (error) 125 Terminate(); 126 return error; 127} 128 129Error* Session::BeforeExecuteCommand() { 130 Error* error = AfterExecuteCommand(); 131 if (!error) { 132 scoped_ptr<Error> switch_error(SwitchToTopFrameIfCurrentFrameInvalid()); 133 if (switch_error.get()) { 134 std::string text; 135 scoped_ptr<Error> alert_error(GetAlertMessage(&text)); 136 if (alert_error.get()) { 137 // Only return a frame checking error if a modal dialog is not present. 138 // TODO(kkania): This is ugly. Fix. 139 return switch_error.release(); 140 } 141 } 142 } 143 return error; 144} 145 146Error* Session::AfterExecuteCommand() { 147 Error* error = NULL; 148 if (!capabilities_.load_async) { 149 error = WaitForAllViewsToStopLoading(); 150 } 151 return error; 152} 153 154void Session::Terminate() { 155 RunSessionTask(base::Bind( 156 &Session::TerminateOnSessionThread, 157 base::Unretained(this))); 158 delete this; 159} 160 161Error* Session::ExecuteScript(const FrameId& frame_id, 162 const std::string& script, 163 const base::ListValue* const args, 164 base::Value** value) { 165 std::string args_as_json; 166 base::JSONWriter::Write(static_cast<const base::Value* const>(args), 167 &args_as_json); 168 169 // Every injected script is fed through the executeScript atom. This atom 170 // will catch any errors that are thrown and convert them to the 171 // appropriate JSON structure. 172 std::string jscript = base::StringPrintf( 173 "window.domAutomationController.send((%s).apply(null," 174 "[function(){%s\n},%s,true]));", 175 atoms::asString(atoms::EXECUTE_SCRIPT).c_str(), script.c_str(), 176 args_as_json.c_str()); 177 178 return ExecuteScriptAndParseValue(frame_id, jscript, value); 179} 180 181Error* Session::ExecuteScript(const std::string& script, 182 const base::ListValue* const args, 183 base::Value** value) { 184 return ExecuteScript(current_target_, script, args, value); 185} 186 187Error* Session::ExecuteScriptAndParse(const FrameId& frame_id, 188 const std::string& anonymous_func_script, 189 const std::string& script_name, 190 const base::ListValue* args, 191 const ValueParser* parser) { 192 scoped_ptr<const base::ListValue> scoped_args(args); 193 scoped_ptr<const ValueParser> scoped_parser(parser); 194 std::string called_script = base::StringPrintf( 195 "return (%s).apply(null, arguments);", anonymous_func_script.c_str()); 196 base::Value* unscoped_value = NULL; 197 Error* error = ExecuteScript(frame_id, called_script, args, &unscoped_value); 198 if (error) { 199 error->AddDetails(script_name + " execution failed"); 200 return error; 201 } 202 203 scoped_ptr<base::Value> value(unscoped_value); 204 std::string error_msg; 205 if (!parser->Parse(value.get())) { 206 error_msg = base::StringPrintf("%s returned invalid value: %s", 207 script_name.c_str(), JsonStringify(value.get()).c_str()); 208 return new Error(kUnknownError, error_msg); 209 } 210 return NULL; 211} 212 213Error* Session::ExecuteAsyncScript(const FrameId& frame_id, 214 const std::string& script, 215 const base::ListValue* const args, 216 base::Value** value) { 217 std::string args_as_json; 218 base::JSONWriter::Write(static_cast<const base::Value* const>(args), 219 &args_as_json); 220 221 int timeout_ms = async_script_timeout(); 222 223 // Every injected script is fed through the executeScript atom. This atom 224 // will catch any errors that are thrown and convert them to the 225 // appropriate JSON structure. 226 std::string jscript = base::StringPrintf( 227 "(%s).apply(null, [function(){%s},%s,%d,%s,true]);", 228 atoms::asString(atoms::EXECUTE_ASYNC_SCRIPT).c_str(), 229 script.c_str(), 230 args_as_json.c_str(), 231 timeout_ms, 232 "function(result) {window.domAutomationController.send(result);}"); 233 234 return ExecuteScriptAndParseValue(frame_id, jscript, value); 235} 236 237Error* Session::SendKeys(const ElementId& element, const string16& keys) { 238 bool is_displayed = false; 239 Error* error = IsElementDisplayed( 240 current_target_, element, true /* ignore_opacity */, &is_displayed); 241 if (error) 242 return error; 243 if (!is_displayed) 244 return new Error(kElementNotVisible); 245 246 bool is_enabled = false; 247 error = IsElementEnabled(current_target_, element, &is_enabled); 248 if (error) 249 return error; 250 if (!is_enabled) 251 return new Error(kInvalidElementState); 252 253 // Focus the target element in order to send keys to it. 254 // First, the currently active element is blurred, if it is different from 255 // the target element. We do not want to blur an element unnecessarily, 256 // because this may cause us to lose the current cursor position in the 257 // element. 258 // Secondly, we focus the target element. 259 // Thirdly, if the target element is newly focused and is a text input, we 260 // set the cursor position at the end. 261 // Fourthly, we check if the new active element is the target element. If not, 262 // we throw an error. 263 // Additional notes: 264 // - |document.activeElement| is the currently focused element, or body if 265 // no element is focused 266 // - Even if |document.hasFocus()| returns true and the active element is 267 // the body, sometimes we still need to focus the body element for send 268 // keys to work. Not sure why 269 // - You cannot focus a descendant of a content editable node 270 // - V8 throws a TypeError when calling setSelectionRange for a non-text 271 // input, which still have setSelectionRange defined. 272 // TODO(jleyba): Update this to use the correct atom. 273 const char* kFocusScript = 274 "function(elem) {" 275 " var doc = elem.ownerDocument || elem;" 276 " var prevActiveElem = doc.activeElement;" 277 " if (elem != prevActiveElem && prevActiveElem)" 278 " prevActiveElem.blur();" 279 " elem.focus();" 280 " if (elem != prevActiveElem && elem.value && elem.value.length &&" 281 " elem.setSelectionRange) {" 282 " try {" 283 " elem.setSelectionRange(elem.value.length, elem.value.length);" 284 " } catch (error) {" 285 " if (!(error instanceof TypeError)) {" 286 " throw error;" 287 " }" 288 " }" 289 " }" 290 " if (elem != doc.activeElement)" 291 " throw new Error('Failed to send keys because cannot focus element');" 292 "}"; 293 error = ExecuteScriptAndParse(current_target_, 294 kFocusScript, 295 "focusElement", 296 CreateListValueFrom(element), 297 CreateDirectValueParser(kSkipParsing)); 298 if (error) 299 return error; 300 301 RunSessionTask(base::Bind( 302 &Session::SendKeysOnSessionThread, 303 base::Unretained(this), 304 keys, 305 true /* release_modifiers */, 306 &error)); 307 return error; 308} 309 310Error* Session::SendKeys(const string16& keys) { 311 Error* error = NULL; 312 RunSessionTask(base::Bind( 313 &Session::SendKeysOnSessionThread, 314 base::Unretained(this), 315 keys, 316 false /* release_modifiers */, 317 &error)); 318 return error; 319} 320 321Error* Session::DragAndDropFilePaths( 322 const Point& location, 323 const std::vector<base::FilePath::StringType>& paths) { 324 Error* error = NULL; 325 RunSessionTask(base::Bind( 326 &Automation::DragAndDropFilePaths, 327 base::Unretained(automation_.get()), 328 current_target_.view_id, 329 location, 330 paths, 331 &error)); 332 return error; 333} 334 335Error* Session::NavigateToURL(const std::string& url) { 336 if (!current_target_.view_id.IsTab()) { 337 return new Error(kUnknownError, 338 "The current target does not support navigation"); 339 } 340 Error* error = NULL; 341 if (capabilities_.load_async) { 342 RunSessionTask(base::Bind( 343 &Automation::NavigateToURLAsync, 344 base::Unretained(automation_.get()), 345 current_target_.view_id, 346 url, 347 &error)); 348 } else { 349 RunSessionTask(base::Bind( 350 &Automation::NavigateToURL, 351 base::Unretained(automation_.get()), 352 current_target_.view_id, 353 url, 354 &error)); 355 } 356 return error; 357} 358 359Error* Session::GoForward() { 360 if (!current_target_.view_id.IsTab()) { 361 return new Error(kUnknownError, 362 "The current target does not support navigation"); 363 } 364 Error* error = NULL; 365 RunSessionTask(base::Bind( 366 &Automation::GoForward, 367 base::Unretained(automation_.get()), 368 current_target_.view_id, 369 &error)); 370 return error; 371} 372 373Error* Session::GoBack() { 374 if (!current_target_.view_id.IsTab()) { 375 return new Error(kUnknownError, 376 "The current target does not support navigation"); 377 } 378 Error* error = NULL; 379 RunSessionTask(base::Bind( 380 &Automation::GoBack, 381 base::Unretained(automation_.get()), 382 current_target_.view_id, 383 &error)); 384 return error; 385} 386 387Error* Session::Reload() { 388 if (!current_target_.view_id.IsTab()) { 389 return new Error(kUnknownError, 390 "The current target does not support navigation"); 391 } 392 Error* error = NULL; 393 RunSessionTask(base::Bind( 394 &Automation::Reload, 395 base::Unretained(automation_.get()), 396 current_target_.view_id, 397 &error)); 398 return error; 399} 400 401Error* Session::GetURL(std::string* url) { 402 return ExecuteScriptAndParse(current_target_, 403 "function() { return document.URL }", 404 "getUrl", 405 new base::ListValue(), 406 CreateDirectValueParser(url)); 407} 408 409Error* Session::GetTitle(std::string* tab_title) { 410 const char* kGetTitleScript = 411 "function() {" 412 " if (document.title)" 413 " return document.title;" 414 " else" 415 " return document.URL;" 416 "}"; 417 return ExecuteScriptAndParse(FrameId(current_target_.view_id, FramePath()), 418 kGetTitleScript, 419 "getTitle", 420 new base::ListValue(), 421 CreateDirectValueParser(tab_title)); 422} 423 424Error* Session::MouseMoveAndClick(const Point& location, 425 automation::MouseButton button) { 426 Error* error = NULL; 427 if (build_no_ >= kNewMouseAPIMinVersion) { 428 std::vector<WebMouseEvent> events; 429 events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, location, 0)); 430 events.push_back(CreateWebMouseEvent(kMouseDown, button, location, 1)); 431 events.push_back(CreateWebMouseEvent(kMouseUp, button, location, 1)); 432 error = ProcessWebMouseEvents(events); 433 } else { 434 RunSessionTask(base::Bind( 435 &Automation::MouseClickDeprecated, 436 base::Unretained(automation_.get()), 437 current_target_.view_id, 438 location, 439 button, 440 &error)); 441 } 442 if (!error) 443 mouse_position_ = location; 444 return error; 445} 446 447Error* Session::MouseMove(const Point& location) { 448 Error* error = NULL; 449 if (build_no_ >= kNewMouseAPIMinVersion) { 450 std::vector<WebMouseEvent> events; 451 events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, location, 0)); 452 error = ProcessWebMouseEvents(events); 453 } else { 454 RunSessionTask(base::Bind( 455 &Automation::MouseMoveDeprecated, 456 base::Unretained(automation_.get()), 457 current_target_.view_id, 458 location, 459 &error)); 460 } 461 if (!error) 462 mouse_position_ = location; 463 return error; 464} 465 466Error* Session::MouseDrag(const Point& start, 467 const Point& end) { 468 Error* error = NULL; 469 if (build_no_ >= kNewMouseAPIMinVersion) { 470 std::vector<WebMouseEvent> events; 471 events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, start, 0)); 472 events.push_back(CreateWebMouseEvent(kMouseDown, kLeftButton, start, 1)); 473 events.push_back(CreateWebMouseEvent(kMouseMove, kLeftButton, end, 0)); 474 events.push_back(CreateWebMouseEvent(kMouseUp, kLeftButton, end, 1)); 475 error = ProcessWebMouseEvents(events); 476 } else { 477 RunSessionTask(base::Bind( 478 &Automation::MouseDragDeprecated, 479 base::Unretained(automation_.get()), 480 current_target_.view_id, 481 start, 482 end, 483 &error)); 484 } 485 if (!error) 486 mouse_position_ = end; 487 return error; 488} 489 490Error* Session::MouseClick(automation::MouseButton button) { 491 if (build_no_ >= kNewMouseAPIMinVersion) { 492 std::vector<WebMouseEvent> events; 493 events.push_back(CreateWebMouseEvent( 494 kMouseDown, button, mouse_position_, 1)); 495 events.push_back(CreateWebMouseEvent( 496 kMouseUp, button, mouse_position_, 1)); 497 return ProcessWebMouseEvents(events); 498 } else { 499 return MouseMoveAndClick(mouse_position_, button); 500 } 501} 502 503Error* Session::MouseButtonDown() { 504 Error* error = NULL; 505 if (build_no_ >= kNewMouseAPIMinVersion) { 506 std::vector<WebMouseEvent> events; 507 events.push_back(CreateWebMouseEvent( 508 kMouseDown, kLeftButton, mouse_position_, 1)); 509 error = ProcessWebMouseEvents(events); 510 } else { 511 RunSessionTask(base::Bind( 512 &Automation::MouseButtonDownDeprecated, 513 base::Unretained(automation_.get()), 514 current_target_.view_id, 515 mouse_position_, 516 &error)); 517 } 518 return error; 519} 520 521Error* Session::MouseButtonUp() { 522 Error* error = NULL; 523 if (build_no_ >= kNewMouseAPIMinVersion) { 524 std::vector<WebMouseEvent> events; 525 events.push_back(CreateWebMouseEvent( 526 kMouseUp, kLeftButton, mouse_position_, 1)); 527 error = ProcessWebMouseEvents(events); 528 } else { 529 RunSessionTask(base::Bind( 530 &Automation::MouseButtonUpDeprecated, 531 base::Unretained(automation_.get()), 532 current_target_.view_id, 533 mouse_position_, 534 &error)); 535 } 536 return error; 537} 538 539Error* Session::MouseDoubleClick() { 540 Error* error = NULL; 541 if (build_no_ >= kNewMouseAPIMinVersion) { 542 std::vector<WebMouseEvent> events; 543 events.push_back(CreateWebMouseEvent( 544 kMouseDown, kLeftButton, mouse_position_, 1)); 545 events.push_back(CreateWebMouseEvent( 546 kMouseUp, kLeftButton, mouse_position_, 1)); 547 events.push_back(CreateWebMouseEvent( 548 kMouseDown, kLeftButton, mouse_position_, 2)); 549 events.push_back(CreateWebMouseEvent( 550 kMouseUp, kLeftButton, mouse_position_, 2)); 551 error = ProcessWebMouseEvents(events); 552 } else { 553 RunSessionTask(base::Bind( 554 &Automation::MouseDoubleClickDeprecated, 555 base::Unretained(automation_.get()), 556 current_target_.view_id, 557 mouse_position_, 558 &error)); 559 } 560 return error; 561} 562 563Error* Session::GetCookies(const std::string& url, 564 scoped_ptr<base::ListValue>* cookies) { 565 Error* error = NULL; 566 RunSessionTask(base::Bind( 567 &Automation::GetCookies, 568 base::Unretained(automation_.get()), 569 url, 570 cookies, 571 &error)); 572 return error; 573} 574 575Error* Session::DeleteCookie(const std::string& url, 576 const std::string& cookie_name) { 577 Error* error = NULL; 578 RunSessionTask(base::Bind( 579 &Automation::DeleteCookie, 580 base::Unretained(automation_.get()), 581 url, 582 cookie_name, 583 &error)); 584 return error; 585} 586 587// Note that when this is called from CookieCommand::ExecutePost then 588// |cookie_dict| is destroyed as soon as the caller finishes. Therefore 589// it is essential that RunSessionTask executes synchronously. 590Error* Session::SetCookie(const std::string& url, 591 base::DictionaryValue* cookie_dict) { 592 Error* error = NULL; 593 RunSessionTask(base::Bind( 594 &Automation::SetCookie, 595 base::Unretained(automation_.get()), 596 url, 597 cookie_dict, 598 &error)); 599 return error; 600} 601 602Error* Session::GetViews(std::vector<WebViewInfo>* views) { 603 Error* error = NULL; 604 RunSessionTask(base::Bind( 605 &Automation::GetViews, 606 base::Unretained(automation_.get()), 607 views, 608 &error)); 609 return error; 610} 611 612Error* Session::SwitchToView(const std::string& id_or_name) { 613 Error* error = NULL; 614 bool does_exist = false; 615 616 WebViewId new_view; 617 StringToWebViewId(id_or_name, &new_view); 618 if (new_view.IsValid()) { 619 RunSessionTask(base::Bind( 620 &Automation::DoesViewExist, 621 base::Unretained(automation_.get()), 622 new_view, 623 &does_exist, 624 &error)); 625 if (error) 626 return error; 627 } 628 629 if (!does_exist) { 630 // See if any of the tab window names match |name|. 631 std::vector<WebViewInfo> views; 632 Error* error = GetViews(&views); 633 if (error) 634 return error; 635 for (size_t i = 0; i < views.size(); ++i) { 636 if (!views[i].view_id.IsTab()) 637 continue; 638 std::string window_name; 639 Error* error = ExecuteScriptAndParse( 640 FrameId(views[i].view_id, FramePath()), 641 "function() { return window.name; }", 642 "getWindowName", 643 new base::ListValue(), 644 CreateDirectValueParser(&window_name)); 645 if (error) 646 return error; 647 if (id_or_name == window_name) { 648 new_view = views[i].view_id; 649 does_exist = true; 650 break; 651 } 652 } 653 } 654 655 if (!does_exist) 656 return new Error(kNoSuchWindow); 657 frame_elements_.clear(); 658 current_target_ = FrameId(new_view, FramePath()); 659 return NULL; 660} 661 662Error* Session::SwitchToFrameWithNameOrId(const std::string& name_or_id) { 663 std::string script = 664 "function(arg) {" 665 " var xpath = '(/html/body//iframe|/html/frameset/frame)';" 666 " var sub = function(s) { return s.replace(/\\$/g, arg); };" 667 " xpath += sub('[@name=\"$\" or @id=\"$\"]');" 668 " return document.evaluate(xpath, document, null, " 669 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" 670 "}"; 671 return SwitchToFrameWithJavaScriptLocatedFrame( 672 script, CreateListValueFrom(name_or_id)); 673} 674 675Error* Session::SwitchToFrameWithIndex(int index) { 676 // We cannot simply index into window.frames because we need to know the 677 // tagName of the frameElement. If child frame N is from another domain, then 678 // the following will run afoul of the same origin policy: 679 // window.frames[N].frameElement; 680 // Instead of indexing window.frames, we use an XPath expression to index 681 // into the list of all IFRAME and FRAME elements on the page - if we find 682 // something, then that XPath expression can be used as the new frame's XPath. 683 std::string script = 684 "function(index) {" 685 " var xpathIndex = '[' + (index + 1) + ']';" 686 " var xpath = '(/html/body//iframe|/html/frameset/frame)' + " 687 " xpathIndex;" 688 " return document.evaluate(xpath, document, null, " 689 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" 690 "}"; 691 return SwitchToFrameWithJavaScriptLocatedFrame( 692 script, CreateListValueFrom(index)); 693} 694 695Error* Session::SwitchToFrameWithElement(const ElementId& element) { 696 // TODO(jleyba): Extract this, and the other frame switch methods to an atom. 697 std::string script = 698 "function(elem) {" 699 " if (elem.nodeType != 1 || !/^i?frame$/i.test(elem.tagName)) {" 700 " console.error('Element is not a frame');" 701 " return null;" 702 " }" 703 " for (var i = 0; i < window.frames.length; i++) {" 704 " if (elem.contentWindow == window.frames[i]) {" 705 " return elem;" 706 " }" 707 " }" 708 " console.info('Frame is not connected to this DOM tree');" 709 " return null;" 710 "}"; 711 return SwitchToFrameWithJavaScriptLocatedFrame( 712 script, CreateListValueFrom(element)); 713} 714 715void Session::SwitchToTopFrame() { 716 frame_elements_.clear(); 717 current_target_.frame_path = FramePath(); 718} 719 720Error* Session::SwitchToTopFrameIfCurrentFrameInvalid() { 721 std::vector<std::string> components; 722 current_target_.frame_path.GetComponents(&components); 723 if (frame_elements_.size() != components.size()) { 724 return new Error(kUnknownError, 725 "Frame element vector out of sync with frame path"); 726 } 727 FramePath frame_path; 728 // Start from the root path and check that each frame element that makes 729 // up the current frame target is valid by executing an empty script. 730 // This code should not execute script in any frame before making sure the 731 // frame element is valid, otherwise the automation hangs until a timeout. 732 for (size_t i = 0; i < frame_elements_.size(); ++i) { 733 FrameId frame_id(current_target_.view_id, frame_path); 734 scoped_ptr<Error> error(ExecuteScriptAndParse( 735 frame_id, 736 "function(){ }", 737 "emptyScript", 738 CreateListValueFrom(frame_elements_[i]), 739 CreateDirectValueParser(kSkipParsing))); 740 if (error.get() && error->code() == kStaleElementReference) { 741 SwitchToTopFrame(); 742 } else if (error.get()) { 743 return error.release(); 744 } 745 frame_path = frame_path.Append(components[i]); 746 } 747 return NULL; 748} 749 750Error* Session::CloseWindow() { 751 Error* error = NULL; 752 RunSessionTask(base::Bind( 753 &Automation::CloseView, 754 base::Unretained(automation_.get()), 755 current_target_.view_id, 756 &error)); 757 758 if (!error) { 759 std::vector<WebViewInfo> views; 760 scoped_ptr<Error> error(GetViews(&views)); 761 if (error.get() || views.empty()) { 762 // The automation connection will soon be closed, if not already, 763 // because we supposedly just closed the last window. Terminate the 764 // session. 765 // TODO(kkania): This will cause us problems if GetWindowIds fails for a 766 // reason other than the channel is disconnected. Look into having 767 // |GetWindowIds| tell us if it just closed the last window. 768 Terminate(); 769 } 770 } 771 return error; 772} 773 774Error* Session::GetWindowBounds(const WebViewId& window, Rect* bounds) { 775 const char* kGetWindowBoundsScript = 776 "function() {" 777 " return {" 778 " 'left': window.screenX," 779 " 'top': window.screenY," 780 " 'width': window.outerWidth," 781 " 'height': window.outerHeight" 782 " }" 783 "}"; 784 return ExecuteScriptAndParse( 785 FrameId(window, FramePath()), 786 kGetWindowBoundsScript, 787 "getWindowBoundsScript", 788 new base::ListValue(), 789 CreateDirectValueParser(bounds)); 790} 791 792Error* Session::SetWindowBounds( 793 const WebViewId& window, 794 const Rect& bounds) { 795 Error* error = NULL; 796 RunSessionTask(base::Bind( 797 &Automation::SetViewBounds, 798 base::Unretained(automation_.get()), 799 window, 800 bounds, 801 &error)); 802 return error; 803} 804 805Error* Session::MaximizeWindow(const WebViewId& window) { 806 Error* error = NULL; 807 RunSessionTask(base::Bind( 808 &Automation::MaximizeView, 809 base::Unretained(automation_.get()), 810 window, 811 &error)); 812 return error; 813} 814 815Error* Session::GetAlertMessage(std::string* text) { 816 Error* error = NULL; 817 RunSessionTask(base::Bind( 818 &Automation::GetAppModalDialogMessage, 819 base::Unretained(automation_.get()), 820 text, 821 &error)); 822 return error; 823} 824 825Error* Session::SetAlertPromptText(const std::string& alert_prompt_text) { 826 std::string message_text; 827 // Only set the alert prompt text if an alert is actually active. 828 Error* error = GetAlertMessage(&message_text); 829 if (!error) { 830 has_alert_prompt_text_ = true; 831 alert_prompt_text_ = alert_prompt_text; 832 } 833 return error; 834} 835 836Error* Session::AcceptOrDismissAlert(bool accept) { 837 Error* error = NULL; 838 if (accept && has_alert_prompt_text_) { 839 RunSessionTask(base::Bind( 840 &Automation::AcceptPromptAppModalDialog, 841 base::Unretained(automation_.get()), 842 alert_prompt_text_, 843 &error)); 844 } else { 845 RunSessionTask(base::Bind( 846 &Automation::AcceptOrDismissAppModalDialog, 847 base::Unretained(automation_.get()), 848 accept, 849 &error)); 850 } 851 has_alert_prompt_text_ = false; 852 return error; 853} 854 855std::string Session::GetBrowserVersion() { 856 std::string version; 857 RunSessionTask(base::Bind( 858 &Automation::GetBrowserVersion, 859 base::Unretained(automation_.get()), 860 &version)); 861 return version; 862} 863 864Error* Session::CompareBrowserVersion(int client_build_no, 865 int client_patch_no, 866 bool* is_newer_or_equal) { 867 std::string version = GetBrowserVersion(); 868 std::vector<std::string> split_version; 869 base::SplitString(version, '.', &split_version); 870 if (split_version.size() != 4) { 871 return new Error( 872 kUnknownError, "Browser version has unrecognized format: " + version); 873 } 874 int build_no, patch_no; 875 if (!base::StringToInt(split_version[2], &build_no) || 876 !base::StringToInt(split_version[3], &patch_no)) { 877 return new Error( 878 kUnknownError, "Browser version has unrecognized format: " + version); 879 } 880 if (build_no < client_build_no) 881 *is_newer_or_equal = false; 882 else if (build_no > client_build_no) 883 *is_newer_or_equal = true; 884 else 885 *is_newer_or_equal = patch_no >= client_patch_no; 886 return NULL; 887} 888 889Error* Session::FindElement(const FrameId& frame_id, 890 const ElementId& root_element, 891 const std::string& locator, 892 const std::string& query, 893 ElementId* element) { 894 std::vector<ElementId> elements; 895 Error* error = FindElementsHelper( 896 frame_id, root_element, locator, query, true, &elements); 897 if (!error) 898 *element = elements[0]; 899 return error; 900} 901 902Error* Session::FindElements(const FrameId& frame_id, 903 const ElementId& root_element, 904 const std::string& locator, 905 const std::string& query, 906 std::vector<ElementId>* elements) { 907 return FindElementsHelper( 908 frame_id, root_element, locator, query, false, elements); 909} 910 911Error* Session::GetElementLocationInView( 912 const ElementId& element, 913 Point* location) { 914 Size size; 915 Error* error = GetElementSize(current_target_, element, &size); 916 if (error) 917 return error; 918 return GetElementRegionInView( 919 element, Rect(Point(0, 0), size), 920 false /* center */, false /* verify_clickable_at_middle */, location); 921} 922 923Error* Session::GetElementRegionInView( 924 const ElementId& element, 925 const Rect& region, 926 bool center, 927 bool verify_clickable_at_middle, 928 Point* location) { 929 CHECK(element.is_valid()); 930 931 Point region_offset = region.origin(); 932 Size region_size = region.size(); 933 Error* error = GetElementRegionInViewHelper( 934 current_target_, element, region, center, verify_clickable_at_middle, 935 ®ion_offset); 936 if (error) 937 return error; 938 939 for (FramePath frame_path = current_target_.frame_path; 940 frame_path.IsSubframe(); 941 frame_path = frame_path.Parent()) { 942 // Find the frame element for the current frame path. 943 FrameId frame_id(current_target_.view_id, frame_path.Parent()); 944 ElementId frame_element; 945 error = FindElement(frame_id, 946 ElementId(std::string()), 947 LocatorType::kXpath, 948 frame_path.BaseName().value(), 949 &frame_element); 950 if (error) { 951 std::string context = base::StringPrintf( 952 "Could not find frame element (%s) in frame (%s)", 953 frame_path.BaseName().value().c_str(), 954 frame_path.Parent().value().c_str()); 955 error->AddDetails(context); 956 return error; 957 } 958 // Modify |region_offset| by the frame's border. 959 int border_left, border_top; 960 error = GetElementBorder( 961 frame_id, frame_element, &border_left, &border_top); 962 if (error) 963 return error; 964 region_offset.Offset(border_left, border_top); 965 966 error = GetElementRegionInViewHelper( 967 frame_id, frame_element, Rect(region_offset, region_size), 968 center, verify_clickable_at_middle, ®ion_offset); 969 if (error) 970 return error; 971 } 972 *location = region_offset; 973 return NULL; 974} 975 976Error* Session::GetElementSize(const FrameId& frame_id, 977 const ElementId& element, 978 Size* size) { 979 return ExecuteScriptAndParse( 980 frame_id, 981 atoms::asString(atoms::GET_SIZE), 982 "getSize", 983 CreateListValueFrom(element), 984 CreateDirectValueParser(size)); 985} 986 987Error* Session::GetElementFirstClientRect(const FrameId& frame_id, 988 const ElementId& element, 989 Rect* rect) { 990 return ExecuteScriptAndParse( 991 frame_id, 992 atoms::asString(atoms::GET_FIRST_CLIENT_RECT), 993 "getFirstClientRect", 994 CreateListValueFrom(element), 995 CreateDirectValueParser(rect)); 996} 997 998Error* Session::GetElementEffectiveStyle( 999 const FrameId& frame_id, 1000 const ElementId& element, 1001 const std::string& prop, 1002 std::string* value) { 1003 return ExecuteScriptAndParse( 1004 frame_id, 1005 atoms::asString(atoms::GET_EFFECTIVE_STYLE), 1006 "getEffectiveStyle", 1007 CreateListValueFrom(element, prop), 1008 CreateDirectValueParser(value)); 1009} 1010 1011Error* Session::GetElementBorder(const FrameId& frame_id, 1012 const ElementId& element, 1013 int* border_left, 1014 int* border_top) { 1015 std::string border_left_str, border_top_str; 1016 Error* error = GetElementEffectiveStyle( 1017 frame_id, element, "border-left-width", &border_left_str); 1018 if (error) 1019 return error; 1020 error = GetElementEffectiveStyle( 1021 frame_id, element, "border-top-width", &border_top_str); 1022 if (error) 1023 return error; 1024 1025 base::StringToInt(border_left_str, border_left); 1026 base::StringToInt(border_top_str, border_top); 1027 return NULL; 1028} 1029 1030Error* Session::IsElementDisplayed(const FrameId& frame_id, 1031 const ElementId& element, 1032 bool ignore_opacity, 1033 bool* is_displayed) { 1034 return ExecuteScriptAndParse( 1035 frame_id, 1036 atoms::asString(atoms::IS_DISPLAYED), 1037 "isDisplayed", 1038 CreateListValueFrom(element, ignore_opacity), 1039 CreateDirectValueParser(is_displayed)); 1040} 1041 1042Error* Session::IsElementEnabled(const FrameId& frame_id, 1043 const ElementId& element, 1044 bool* is_enabled) { 1045 return ExecuteScriptAndParse( 1046 frame_id, 1047 atoms::asString(atoms::IS_ENABLED), 1048 "isEnabled", 1049 CreateListValueFrom(element), 1050 CreateDirectValueParser(is_enabled)); 1051} 1052 1053Error* Session::IsOptionElementSelected(const FrameId& frame_id, 1054 const ElementId& element, 1055 bool* is_selected) { 1056 return ExecuteScriptAndParse( 1057 frame_id, 1058 atoms::asString(atoms::IS_SELECTED), 1059 "isSelected", 1060 CreateListValueFrom(element), 1061 CreateDirectValueParser(is_selected)); 1062} 1063 1064Error* Session::SetOptionElementSelected(const FrameId& frame_id, 1065 const ElementId& element, 1066 bool selected) { 1067 // This wrapper ensures the script is started successfully and 1068 // allows for an alert to happen when the option selection occurs. 1069 // See selenium bug 2671. 1070 const char kSetSelectedWrapper[] = 1071 "var args = [].slice.apply(arguments);" 1072 "args[args.length - 1]();" 1073 "return (%s).apply(null, args.slice(0, args.length - 1));"; 1074 base::Value* value = NULL; 1075 Error* error = ExecuteAsyncScript( 1076 frame_id, 1077 base::StringPrintf(kSetSelectedWrapper, 1078 atoms::asString(atoms::CLICK).c_str()), 1079 CreateListValueFrom(element, selected), 1080 &value); 1081 scoped_ptr<base::Value> scoped_value(value); 1082 return error; 1083} 1084 1085Error* Session::ToggleOptionElement(const FrameId& frame_id, 1086 const ElementId& element) { 1087 bool is_selected; 1088 Error* error = IsOptionElementSelected(frame_id, element, &is_selected); 1089 if (error) 1090 return error; 1091 1092 return SetOptionElementSelected(frame_id, element, !is_selected); 1093} 1094 1095Error* Session::GetElementTagName(const FrameId& frame_id, 1096 const ElementId& element, 1097 std::string* tag_name) { 1098 return ExecuteScriptAndParse( 1099 frame_id, 1100 "function(elem) { return elem.tagName.toLowerCase() }", 1101 "getElementTagName", 1102 CreateListValueFrom(element), 1103 CreateDirectValueParser(tag_name)); 1104} 1105 1106Error* Session::GetClickableLocation(const ElementId& element, 1107 Point* location) { 1108 bool is_displayed = false; 1109 Error* error = IsElementDisplayed( 1110 current_target_, element, true /* ignore_opacity */, &is_displayed); 1111 if (error) 1112 return error; 1113 if (!is_displayed) 1114 return new Error(kElementNotVisible, "Element must be displayed to click"); 1115 1116 // We try 3 methods to determine clickable location. This mostly follows 1117 // what FirefoxDriver does. Try the first client rect, then the bounding 1118 // client rect, and lastly the size of the element (via closure). 1119 // SVG is one case that doesn't have a first client rect. 1120 Rect rect; 1121 scoped_ptr<Error> ignore_error( 1122 GetElementFirstClientRect(current_target_, element, &rect)); 1123 if (ignore_error.get()) { 1124 Rect client_rect; 1125 ignore_error.reset(ExecuteScriptAndParse( 1126 current_target_, 1127 "function(elem) { return elem.getBoundingClientRect() }", 1128 "getBoundingClientRect", 1129 CreateListValueFrom(element), 1130 CreateDirectValueParser(&client_rect))); 1131 rect = Rect(0, 0, client_rect.width(), client_rect.height()); 1132 } 1133 if (ignore_error.get()) { 1134 Size size; 1135 ignore_error.reset(GetElementSize(current_target_, element, &size)); 1136 rect = Rect(0, 0, size.width(), size.height()); 1137 } 1138 if (ignore_error.get()) { 1139 return new Error(kUnknownError, 1140 "Unable to determine clickable location of element"); 1141 } 1142 1143 error = GetElementRegionInView( 1144 element, rect, true /* center */, true /* verify_clickable_at_middle */, 1145 location); 1146 if (error) 1147 return error; 1148 location->Offset(rect.width() / 2, rect.height() / 2); 1149 return NULL; 1150} 1151 1152Error* Session::GetAttribute(const ElementId& element, 1153 const std::string& key, 1154 base::Value** value) { 1155 return ExecuteScriptAndParse( 1156 current_target_, 1157 atoms::asString(atoms::GET_ATTRIBUTE), 1158 "getAttribute", 1159 CreateListValueFrom(element, key), 1160 CreateDirectValueParser(value)); 1161} 1162 1163Error* Session::WaitForAllViewsToStopLoading() { 1164 if (!automation_.get()) 1165 return NULL; 1166 1167 logger_.Log(kFinerLogLevel, "Waiting for all views to stop loading..."); 1168 Error* error = NULL; 1169 RunSessionTask(base::Bind( 1170 &Automation::WaitForAllViewsToStopLoading, 1171 base::Unretained(automation_.get()), 1172 &error)); 1173 logger_.Log(kFinerLogLevel, "Done waiting for all views to stop loading"); 1174 return error; 1175} 1176 1177Error* Session::InstallExtension( 1178 const base::FilePath& path, std::string* extension_id) { 1179 Error* error = NULL; 1180 RunSessionTask(base::Bind( 1181 &Automation::InstallExtension, 1182 base::Unretained(automation_.get()), 1183 path, 1184 extension_id, 1185 &error)); 1186 return error; 1187} 1188 1189Error* Session::GetExtensionsInfo(base::ListValue* extensions_list) { 1190 Error* error = NULL; 1191 RunSessionTask(base::Bind( 1192 &Automation::GetExtensionsInfo, 1193 base::Unretained(automation_.get()), 1194 extensions_list, 1195 &error)); 1196 return error; 1197} 1198 1199Error* Session::IsPageActionVisible( 1200 const WebViewId& tab_id, 1201 const std::string& extension_id, 1202 bool* is_visible) { 1203 if (!tab_id.IsTab()) { 1204 return new Error( 1205 kUnknownError, 1206 "The current target does not support page actions. Switch to a tab."); 1207 } 1208 Error* error = NULL; 1209 RunSessionTask(base::Bind( 1210 &Automation::IsPageActionVisible, 1211 base::Unretained(automation_.get()), 1212 tab_id, 1213 extension_id, 1214 is_visible, 1215 &error)); 1216 return error; 1217} 1218 1219Error* Session::SetExtensionState( 1220 const std::string& extension_id, bool enable) { 1221 Error* error = NULL; 1222 RunSessionTask(base::Bind( 1223 &Automation::SetExtensionState, 1224 base::Unretained(automation_.get()), 1225 extension_id, 1226 enable, 1227 &error)); 1228 return error; 1229} 1230 1231Error* Session::ClickExtensionButton( 1232 const std::string& extension_id, bool browser_action) { 1233 Error* error = NULL; 1234 RunSessionTask(base::Bind( 1235 &Automation::ClickExtensionButton, 1236 base::Unretained(automation_.get()), 1237 extension_id, 1238 browser_action, 1239 &error)); 1240 return error; 1241} 1242 1243Error* Session::UninstallExtension(const std::string& extension_id) { 1244 Error* error = NULL; 1245 RunSessionTask(base::Bind( 1246 &Automation::UninstallExtension, 1247 base::Unretained(automation_.get()), 1248 extension_id, 1249 &error)); 1250 return error; 1251} 1252 1253Error* Session::SetPreference( 1254 const std::string& pref, 1255 bool is_user_pref, 1256 base::Value* value) { 1257 Error* error = NULL; 1258 if (is_user_pref) { 1259 RunSessionTask(base::Bind( 1260 &Automation::SetPreference, 1261 base::Unretained(automation_.get()), 1262 pref, 1263 value, 1264 &error)); 1265 if (error) 1266 error->AddDetails("Failed to set user pref '" + pref + "'"); 1267 } else { 1268 RunSessionTask(base::Bind( 1269 &Automation::SetLocalStatePreference, 1270 base::Unretained(automation_.get()), 1271 pref, 1272 value, 1273 &error)); 1274 if (error) 1275 error->AddDetails("Failed to set local state pref '" + pref + "'"); 1276 } 1277 return error; 1278} 1279 1280base::ListValue* Session::GetLog() const { 1281 return session_log_->entries_list()->DeepCopy(); 1282} 1283 1284Error* Session::GetBrowserConnectionState(bool* online) { 1285 return ExecuteScriptAndParse( 1286 current_target_, 1287 atoms::asString(atoms::IS_ONLINE), 1288 "isOnline", 1289 new base::ListValue(), 1290 CreateDirectValueParser(online)); 1291} 1292 1293Error* Session::GetAppCacheStatus(int* status) { 1294 return ExecuteScriptAndParse( 1295 current_target_, 1296 atoms::asString(atoms::GET_APPCACHE_STATUS), 1297 "getAppcacheStatus", 1298 new base::ListValue(), 1299 CreateDirectValueParser(status)); 1300} 1301 1302Error* Session::GetStorageSize(StorageType type, int* size) { 1303 std::string js = atoms::asString( 1304 type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_SIZE 1305 : atoms::GET_SESSION_STORAGE_SIZE); 1306 return ExecuteScriptAndParse( 1307 current_target_, 1308 js, 1309 "getStorageSize", 1310 new base::ListValue(), 1311 CreateDirectValueParser(size)); 1312} 1313 1314Error* Session::SetStorageItem(StorageType type, 1315 const std::string& key, 1316 const std::string& value) { 1317 std::string js = atoms::asString( 1318 type == kLocalStorageType ? atoms::SET_LOCAL_STORAGE_ITEM 1319 : atoms::SET_SESSION_STORAGE_ITEM); 1320 return ExecuteScriptAndParse( 1321 current_target_, 1322 js, 1323 "setStorageItem", 1324 CreateListValueFrom(key, value), 1325 CreateDirectValueParser(kSkipParsing)); 1326} 1327 1328Error* Session::ClearStorage(StorageType type) { 1329 std::string js = atoms::asString( 1330 type == kLocalStorageType ? atoms::CLEAR_LOCAL_STORAGE 1331 : atoms::CLEAR_SESSION_STORAGE); 1332 return ExecuteScriptAndParse( 1333 current_target_, 1334 js, 1335 "clearStorage", 1336 new base::ListValue(), 1337 CreateDirectValueParser(kSkipParsing)); 1338} 1339 1340Error* Session::GetStorageKeys(StorageType type, base::ListValue** keys) { 1341 std::string js = atoms::asString( 1342 type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_KEYS 1343 : atoms::GET_SESSION_STORAGE_KEYS); 1344 return ExecuteScriptAndParse( 1345 current_target_, 1346 js, 1347 "getStorageKeys", 1348 new base::ListValue(), 1349 CreateDirectValueParser(keys)); 1350} 1351 1352Error* Session::GetStorageItem(StorageType type, 1353 const std::string& key, 1354 std::string* value) { 1355 std::string js = atoms::asString( 1356 type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_ITEM 1357 : atoms::GET_SESSION_STORAGE_ITEM); 1358 return ExecuteScriptAndParse( 1359 current_target_, 1360 js, 1361 "getStorageItem", 1362 CreateListValueFrom(key), 1363 CreateDirectValueParser(value)); 1364} 1365 1366Error* Session::RemoveStorageItem(StorageType type, 1367 const std::string& key, 1368 std::string* value) { 1369 std::string js = atoms::asString( 1370 type == kLocalStorageType ? atoms::REMOVE_LOCAL_STORAGE_ITEM 1371 : atoms::REMOVE_SESSION_STORAGE_ITEM); 1372 return ExecuteScriptAndParse( 1373 current_target_, 1374 js, 1375 "removeStorageItem", 1376 CreateListValueFrom(key), 1377 CreateDirectValueParser(value)); 1378} 1379 1380Error* Session::GetGeolocation( 1381 scoped_ptr<base::DictionaryValue>* geolocation) { 1382 Error* error = NULL; 1383 RunSessionTask(base::Bind( 1384 &Automation::GetGeolocation, 1385 base::Unretained(automation_.get()), 1386 geolocation, 1387 &error)); 1388 return error; 1389} 1390 1391Error* Session::OverrideGeolocation(const base::DictionaryValue* geolocation) { 1392 Error* error = NULL; 1393 RunSessionTask(base::Bind( 1394 &Automation::OverrideGeolocation, 1395 base::Unretained(automation_.get()), 1396 geolocation, 1397 &error)); 1398 return error; 1399} 1400 1401const std::string& Session::id() const { 1402 return id_; 1403} 1404 1405const FrameId& Session::current_target() const { 1406 return current_target_; 1407} 1408 1409void Session::set_async_script_timeout(int timeout_ms) { 1410 async_script_timeout_ = timeout_ms; 1411} 1412 1413int Session::async_script_timeout() const { 1414 return async_script_timeout_; 1415} 1416 1417void Session::set_implicit_wait(int timeout_ms) { 1418 implicit_wait_ = timeout_ms; 1419} 1420 1421int Session::implicit_wait() const { 1422 return implicit_wait_; 1423} 1424 1425const Point& Session::get_mouse_position() const { 1426 return mouse_position_; 1427} 1428 1429const Logger& Session::logger() const { 1430 return logger_; 1431} 1432 1433const base::FilePath& Session::temp_dir() const { 1434 return temp_dir_.path(); 1435} 1436 1437const Capabilities& Session::capabilities() const { 1438 return capabilities_; 1439} 1440 1441void Session::RunSessionTask(const base::Closure& task) { 1442 base::WaitableEvent done_event(false, false); 1443 thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( 1444 &Session::RunClosureOnSessionThread, 1445 base::Unretained(this), 1446 task, 1447 &done_event)); 1448 // See SetCookie for why it is essential that we wait here. 1449 done_event.Wait(); 1450} 1451 1452void Session::RunClosureOnSessionThread(const base::Closure& task, 1453 base::WaitableEvent* done_event) { 1454 task.Run(); 1455 done_event->Signal(); 1456} 1457 1458void Session::InitOnSessionThread(const Automation::BrowserOptions& options, 1459 int* build_no, 1460 Error** error) { 1461 automation_.reset(new Automation(logger_)); 1462 automation_->Init(options, build_no, error); 1463 if (*error) 1464 return; 1465 1466 std::vector<WebViewInfo> views; 1467 automation_->GetViews(&views, error); 1468 if (*error) 1469 return; 1470 if (views.empty()) { 1471 *error = new Error(kUnknownError, "No view ids after initialization"); 1472 return; 1473 } 1474 current_target_ = FrameId(views[0].view_id, FramePath()); 1475} 1476 1477void Session::TerminateOnSessionThread() { 1478 if (automation_.get()) 1479 automation_->Terminate(); 1480 automation_.reset(); 1481} 1482 1483Error* Session::ExecuteScriptAndParseValue(const FrameId& frame_id, 1484 const std::string& script, 1485 base::Value** script_result) { 1486 std::string response_json; 1487 Error* error = NULL; 1488 RunSessionTask(base::Bind( 1489 &Automation::ExecuteScript, 1490 base::Unretained(automation_.get()), 1491 frame_id.view_id, 1492 frame_id.frame_path, 1493 script, 1494 &response_json, 1495 &error)); 1496 if (error) 1497 return error; 1498 1499 scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError( 1500 response_json, base::JSON_ALLOW_TRAILING_COMMAS, NULL, NULL)); 1501 if (!value.get()) 1502 return new Error(kUnknownError, "Failed to parse script result"); 1503 if (value->GetType() != base::Value::TYPE_DICTIONARY) 1504 return new Error(kUnknownError, "Execute script returned non-dict: " + 1505 JsonStringify(value.get())); 1506 base::DictionaryValue* result_dict = 1507 static_cast<base::DictionaryValue*>(value.get()); 1508 1509 int status; 1510 if (!result_dict->GetInteger("status", &status)) 1511 return new Error(kUnknownError, "Execute script did not return status: " + 1512 JsonStringify(result_dict)); 1513 ErrorCode code = static_cast<ErrorCode>(status); 1514 if (code != kSuccess) { 1515 base::DictionaryValue* error_dict; 1516 std::string error_msg; 1517 if (result_dict->GetDictionary("value", &error_dict)) 1518 error_dict->GetString("message", &error_msg); 1519 if (error_msg.empty()) 1520 error_msg = "Script failed with error code: " + base::IntToString(code); 1521 return new Error(code, error_msg); 1522 } 1523 1524 base::Value* tmp; 1525 if (result_dict->Get("value", &tmp)) { 1526 *script_result= tmp->DeepCopy(); 1527 } else { 1528 // "value" was not defined in the returned dictionary; set to null. 1529 *script_result= base::Value::CreateNullValue(); 1530 } 1531 return NULL; 1532} 1533 1534void Session::SendKeysOnSessionThread(const string16& keys, 1535 bool release_modifiers, Error** error) { 1536 std::vector<WebKeyEvent> key_events; 1537 std::string error_msg; 1538 if (!ConvertKeysToWebKeyEvents(keys, logger_, release_modifiers, 1539 &sticky_modifiers_, &key_events, &error_msg)) { 1540 *error = new Error(kUnknownError, error_msg); 1541 return; 1542 } 1543 for (size_t i = 0; i < key_events.size(); ++i) { 1544 automation_->SendWebKeyEvent( 1545 current_target_.view_id, 1546 key_events[i], error); 1547 if (*error) { 1548 std::string details = base::StringPrintf( 1549 "Failed to send key event. Event details:\n" 1550 "Type: %d, KeyCode: %d, UnmodifiedText: %s, ModifiedText: %s, " 1551 "Modifiers: %d", 1552 key_events[i].type, 1553 key_events[i].key_code, 1554 key_events[i].unmodified_text.c_str(), 1555 key_events[i].modified_text.c_str(), 1556 key_events[i].modifiers); 1557 (*error)->AddDetails(details); 1558 return; 1559 } 1560 } 1561} 1562 1563Error* Session::ProcessWebMouseEvents( 1564 const std::vector<WebMouseEvent>& events) { 1565 for (size_t i = 0; i < events.size(); ++i) { 1566 Error* error = NULL; 1567 RunSessionTask(base::Bind( 1568 &Automation::SendWebMouseEvent, 1569 base::Unretained(automation_.get()), 1570 current_target_.view_id, 1571 events[i], 1572 &error)); 1573 if (error) 1574 return error; 1575 mouse_position_ = Point(events[i].x, events[i].y); 1576 } 1577 return NULL; 1578} 1579 1580WebMouseEvent Session::CreateWebMouseEvent( 1581 automation::MouseEventType type, 1582 automation::MouseButton button, 1583 const Point& point, 1584 int click_count) { 1585 return WebMouseEvent(type, button, point.rounded_x(), point.rounded_y(), 1586 click_count, sticky_modifiers_); 1587} 1588 1589Error* Session::SwitchToFrameWithJavaScriptLocatedFrame( 1590 const std::string& script, base::ListValue* args) { 1591 class SwitchFrameValueParser : public ValueParser { 1592 public: 1593 SwitchFrameValueParser( 1594 bool* found_frame, ElementId* frame) 1595 : found_frame_(found_frame), frame_(frame) { } 1596 1597 virtual ~SwitchFrameValueParser() { } 1598 1599 virtual bool Parse(base::Value* value) const OVERRIDE { 1600 if (value->IsType(base::Value::TYPE_NULL)) { 1601 *found_frame_ = false; 1602 return true; 1603 } 1604 ElementId id(value); 1605 if (!id.is_valid()) { 1606 return false; 1607 } 1608 *frame_ = id; 1609 *found_frame_ = true; 1610 return true; 1611 } 1612 1613 private: 1614 bool* found_frame_; 1615 ElementId* frame_; 1616 }; 1617 1618 bool found_frame; 1619 ElementId new_frame_element; 1620 Error* error = ExecuteScriptAndParse( 1621 current_target_, script, "switchFrame", args, 1622 new SwitchFrameValueParser(&found_frame, &new_frame_element)); 1623 if (error) 1624 return error; 1625 1626 if (!found_frame) 1627 return new Error(kNoSuchFrame); 1628 1629 std::string frame_id = GenerateRandomID(); 1630 error = ExecuteScriptAndParse( 1631 current_target_, 1632 "function(elem, id) { elem.setAttribute('wd_frame_id_', id); }", 1633 "setFrameId", 1634 CreateListValueFrom(new_frame_element, frame_id), 1635 CreateDirectValueParser(kSkipParsing)); 1636 if (error) 1637 return error; 1638 1639 frame_elements_.push_back(new_frame_element); 1640 current_target_.frame_path = current_target_.frame_path.Append( 1641 base::StringPrintf("//*[@wd_frame_id_ = '%s']", frame_id.c_str())); 1642 return NULL; 1643} 1644 1645Error* Session::FindElementsHelper(const FrameId& frame_id, 1646 const ElementId& root_element, 1647 const std::string& locator, 1648 const std::string& query, 1649 bool find_one, 1650 std::vector<ElementId>* elements) { 1651 CHECK(root_element.is_valid()); 1652 base::Time start_time = base::Time::Now(); 1653 while (true) { 1654 std::vector<ElementId> temp_elements; 1655 Error* error = ExecuteFindElementScriptAndParse( 1656 frame_id, root_element, locator, query, find_one, &temp_elements); 1657 if (error) 1658 return error; 1659 1660 if (temp_elements.size() > 0u) { 1661 elements->swap(temp_elements); 1662 break; 1663 } 1664 1665 if ((base::Time::Now() - start_time).InMilliseconds() > implicit_wait_) { 1666 if (find_one) 1667 return new Error(kNoSuchElement); 1668 break; 1669 } 1670 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); 1671 } 1672 return NULL; 1673} 1674 1675Error* Session::ExecuteFindElementScriptAndParse( 1676 const FrameId& frame_id, 1677 const ElementId& root_element, 1678 const std::string& locator, 1679 const std::string& query, 1680 bool find_one, 1681 std::vector<ElementId>* elements) { 1682 CHECK(root_element.is_valid()); 1683 1684 class FindElementsParser : public ValueParser { 1685 public: 1686 explicit FindElementsParser(std::vector<ElementId>* elements) 1687 : elements_(elements) { } 1688 1689 virtual ~FindElementsParser() { } 1690 1691 virtual bool Parse(base::Value* value) const OVERRIDE { 1692 if (!value->IsType(base::Value::TYPE_LIST)) 1693 return false; 1694 base::ListValue* list = static_cast<base::ListValue*>(value); 1695 for (size_t i = 0; i < list->GetSize(); ++i) { 1696 ElementId element; 1697 base::Value* element_value = NULL; 1698 if (!list->Get(i, &element_value)) 1699 return false; 1700 if (!SetFromValue(element_value, &element)) 1701 return false; 1702 elements_->push_back(element); 1703 } 1704 return true; 1705 } 1706 private: 1707 std::vector<ElementId>* elements_; 1708 }; 1709 1710 class FindElementParser : public ValueParser { 1711 public: 1712 explicit FindElementParser(std::vector<ElementId>* elements) 1713 : elements_(elements) { } 1714 1715 virtual ~FindElementParser() { } 1716 1717 virtual bool Parse(base::Value* value) const OVERRIDE { 1718 if (value->IsType(base::Value::TYPE_NULL)) 1719 return true; 1720 ElementId element; 1721 bool set = SetFromValue(value, &element); 1722 if (set) 1723 elements_->push_back(element); 1724 return set; 1725 } 1726 private: 1727 std::vector<ElementId>* elements_; 1728 }; 1729 1730 base::DictionaryValue locator_dict; 1731 locator_dict.SetString(locator, query); 1732 std::vector<ElementId> temp_elements; 1733 Error* error = NULL; 1734 if (find_one) { 1735 error = ExecuteScriptAndParse( 1736 frame_id, 1737 atoms::asString(atoms::FIND_ELEMENT), 1738 "findElement", 1739 CreateListValueFrom(&locator_dict, root_element), 1740 new FindElementParser(&temp_elements)); 1741 } else { 1742 error = ExecuteScriptAndParse( 1743 frame_id, 1744 atoms::asString(atoms::FIND_ELEMENTS), 1745 "findElements", 1746 CreateListValueFrom(&locator_dict, root_element), 1747 new FindElementsParser(&temp_elements)); 1748 } 1749 if (!error) 1750 elements->swap(temp_elements); 1751 return error; 1752} 1753 1754Error* Session::VerifyElementIsClickable( 1755 const FrameId& frame_id, 1756 const ElementId& element, 1757 const Point& location) { 1758 class IsElementClickableParser : public ValueParser { 1759 public: 1760 IsElementClickableParser(bool* clickable, std::string* message) 1761 : clickable_(clickable), message_(message) { } 1762 1763 virtual ~IsElementClickableParser() { } 1764 1765 virtual bool Parse(base::Value* value) const OVERRIDE { 1766 if (!value->IsType(base::Value::TYPE_DICTIONARY)) 1767 return false; 1768 base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(value); 1769 dict->GetString("message", message_); 1770 return dict->GetBoolean("clickable", clickable_); 1771 } 1772 1773 private: 1774 bool* clickable_; 1775 std::string* message_; 1776 }; 1777 1778 bool clickable; 1779 std::string message; 1780 Error* error = ExecuteScriptAndParse( 1781 frame_id, 1782 atoms::asString(atoms::IS_ELEMENT_CLICKABLE), 1783 "isElementClickable", 1784 CreateListValueFrom(element, location), 1785 new IsElementClickableParser(&clickable, &message)); 1786 if (error) 1787 return error; 1788 1789 if (!clickable) { 1790 if (message.empty()) 1791 message = "element is not clickable"; 1792 return new Error(kUnknownError, message); 1793 } 1794 if (message.length()) { 1795 logger_.Log(kWarningLogLevel, message); 1796 } 1797 return NULL; 1798} 1799 1800Error* Session::GetElementRegionInViewHelper( 1801 const FrameId& frame_id, 1802 const ElementId& element, 1803 const Rect& region, 1804 bool center, 1805 bool verify_clickable_at_middle, 1806 Point* location) { 1807 Point temp_location; 1808 Error* error = ExecuteScriptAndParse( 1809 frame_id, 1810 atoms::asString(atoms::GET_LOCATION_IN_VIEW), 1811 "getLocationInView", 1812 CreateListValueFrom(element, center, region), 1813 CreateDirectValueParser(&temp_location)); 1814 1815 if (verify_clickable_at_middle) { 1816 Point middle_point = temp_location; 1817 middle_point.Offset(region.width() / 2, region.height() / 2); 1818 error = VerifyElementIsClickable(frame_id, element, middle_point); 1819 if (error) 1820 return error; 1821 } 1822 *location = temp_location; 1823 return NULL; 1824} 1825 1826Error* Session::GetScreenShot(std::string* png) { 1827 if (!current_target_.view_id.IsTab()) { 1828 return new Error(kUnknownError, 1829 "The current target does not support screenshot"); 1830 } 1831 Error* error = NULL; 1832 base::ScopedTempDir screenshots_dir; 1833 if (!screenshots_dir.CreateUniqueTempDir()) { 1834 return new Error(kUnknownError, 1835 "Could not create temp directory for screenshot"); 1836 } 1837 1838 base::FilePath path = screenshots_dir.path().AppendASCII("screen"); 1839 RunSessionTask(base::Bind( 1840 &Automation::CaptureEntirePageAsPNG, 1841 base::Unretained(automation_.get()), 1842 current_target_.view_id, 1843 path, 1844 &error)); 1845 if (error) 1846 return error; 1847 if (!file_util::ReadFileToString(path, png)) 1848 return new Error(kUnknownError, "Could not read screenshot file"); 1849 return NULL; 1850} 1851 1852#if !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS)) 1853Error* Session::HeapProfilerDump(const std::string& reason) { 1854 // TODO(dmikurube): Support browser processes. 1855 Error* error = NULL; 1856 RunSessionTask(base::Bind( 1857 &Automation::HeapProfilerDump, 1858 base::Unretained(automation_.get()), 1859 current_target_.view_id, 1860 reason, 1861 &error)); 1862 return error; 1863} 1864#endif // !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS)) 1865 1866Error* Session::PostBrowserStartInit() { 1867 Error* error = NULL; 1868 if (!capabilities_.no_website_testing_defaults) 1869 error = InitForWebsiteTesting(); 1870 if (!error) 1871 error = SetPrefs(); 1872 if (error) 1873 return error; 1874 1875 // Install extensions. 1876 for (size_t i = 0; i < capabilities_.extensions.size(); ++i) { 1877 std::string extension_id; 1878 error = InstallExtension(capabilities_.extensions[i], &extension_id); 1879 if (error) 1880 return error; 1881 } 1882 return NULL; 1883} 1884 1885Error* Session::InitForWebsiteTesting() { 1886 bool has_prefs_api = false; 1887 // Don't set these prefs for Chrome 14 and below. 1888 // TODO(kkania): Remove this when Chrome 14 is unsupported. 1889 Error* error = CompareBrowserVersion(874, 0, &has_prefs_api); 1890 if (error || !has_prefs_api) 1891 return error; 1892 1893 // Disable checking for SSL certificate revocation. 1894 error = SetPreference( 1895 "ssl.rev_checking.enabled", 1896 false /* is_user_pref */, 1897 new base::FundamentalValue(false)); 1898 if (error) 1899 return error; 1900 1901 // Allow content by default. 1902 // Media-stream cannot be enabled by default; we must specify 1903 // particular host patterns and devices. 1904 base::DictionaryValue* devices = new base::DictionaryValue(); 1905 devices->SetString("audio", "Default"); 1906 devices->SetString("video", "Default"); 1907 base::DictionaryValue* content_settings = new base::DictionaryValue(); 1908 content_settings->Set("media-stream", devices); 1909 base::DictionaryValue* pattern_pairs = new base::DictionaryValue(); 1910 pattern_pairs->Set("https://*,*", content_settings); 1911 error = SetPreference( 1912 "profile.content_settings.pattern_pairs", 1913 true /* is_user_pref */, 1914 pattern_pairs); 1915 if (error) 1916 return error; 1917 const int kAllowContent = 1; 1918 base::DictionaryValue* default_content_settings = new base::DictionaryValue(); 1919 default_content_settings->SetInteger("geolocation", kAllowContent); 1920 default_content_settings->SetInteger("mouselock", kAllowContent); 1921 default_content_settings->SetInteger("notifications", kAllowContent); 1922 default_content_settings->SetInteger("popups", kAllowContent); 1923 return SetPreference( 1924 "profile.default_content_settings", 1925 true /* is_user_pref */, 1926 default_content_settings); 1927} 1928 1929Error* Session::SetPrefs() { 1930 for (base::DictionaryValue::Iterator iter(*capabilities_.prefs); 1931 !iter.IsAtEnd(); iter.Advance()) { 1932 Error* error = SetPreference(iter.key(), true /* is_user_pref */, 1933 iter.value().DeepCopy()); 1934 if (error) 1935 return error; 1936 } 1937 for (base::DictionaryValue::Iterator iter(*capabilities_.local_state); 1938 !iter.IsAtEnd(); iter.Advance()) { 1939 Error* error = SetPreference(iter.key(), false /* is_user_pref */, 1940 iter.value().DeepCopy()); 1941 if (error) 1942 return error; 1943 } 1944 return NULL; 1945} 1946 1947} // namespace webdriver 1948