web_view_impl.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
1// Copyright (c) 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/test/chromedriver/chrome/web_view_impl.h" 6 7#include "base/bind.h" 8#include "base/files/file_path.h" 9#include "base/json/json_writer.h" 10#include "base/logging.h" 11#include "base/strings/string_util.h" 12#include "base/strings/stringprintf.h" 13#include "base/threading/platform_thread.h" 14#include "base/time/time.h" 15#include "base/values.h" 16#include "chrome/test/chromedriver/chrome/devtools_client_impl.h" 17#include "chrome/test/chromedriver/chrome/dom_tracker.h" 18#include "chrome/test/chromedriver/chrome/frame_tracker.h" 19#include "chrome/test/chromedriver/chrome/geolocation_override_manager.h" 20#include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h" 21#include "chrome/test/chromedriver/chrome/js.h" 22#include "chrome/test/chromedriver/chrome/log.h" 23#include "chrome/test/chromedriver/chrome/navigation_tracker.h" 24#include "chrome/test/chromedriver/chrome/status.h" 25#include "chrome/test/chromedriver/chrome/ui_events.h" 26 27namespace { 28 29Status GetContextIdForFrame(FrameTracker* tracker, 30 const std::string& frame, 31 int* context_id) { 32 if (frame.empty()) { 33 *context_id = 0; 34 return Status(kOk); 35 } 36 Status status = tracker->GetContextIdForFrame(frame, context_id); 37 if (status.IsError()) 38 return status; 39 return Status(kOk); 40} 41 42const char* GetAsString(MouseEventType type) { 43 switch (type) { 44 case kPressedMouseEventType: 45 return "mousePressed"; 46 case kReleasedMouseEventType: 47 return "mouseReleased"; 48 case kMovedMouseEventType: 49 return "mouseMoved"; 50 default: 51 return ""; 52 } 53} 54 55const char* GetAsString(MouseButton button) { 56 switch (button) { 57 case kLeftMouseButton: 58 return "left"; 59 case kMiddleMouseButton: 60 return "middle"; 61 case kRightMouseButton: 62 return "right"; 63 case kNoneMouseButton: 64 return "none"; 65 default: 66 return ""; 67 } 68} 69 70const char* GetAsString(KeyEventType type) { 71 switch (type) { 72 case kKeyDownEventType: 73 return "keyDown"; 74 case kKeyUpEventType: 75 return "keyUp"; 76 case kRawKeyDownEventType: 77 return "rawKeyDown"; 78 case kCharEventType: 79 return "char"; 80 default: 81 return ""; 82 } 83} 84 85} // namespace 86 87WebViewImpl::WebViewImpl(const std::string& id, 88 int build_no, 89 scoped_ptr<DevToolsClient> client, 90 Log* log) 91 : id_(id), 92 build_no_(build_no), 93 dom_tracker_(new DomTracker(client.get())), 94 frame_tracker_(new FrameTracker(client.get())), 95 navigation_tracker_(new NavigationTracker(client.get())), 96 dialog_manager_(new JavaScriptDialogManager(client.get())), 97 geolocation_override_manager_( 98 new GeolocationOverrideManager(client.get())), 99 client_(client.release()), 100 log_(log) {} 101 102WebViewImpl::~WebViewImpl() {} 103 104std::string WebViewImpl::GetId() { 105 return id_; 106} 107 108Status WebViewImpl::ConnectIfNecessary() { 109 return client_->ConnectIfNecessary(); 110} 111 112Status WebViewImpl::HandleReceivedEvents() { 113 return client_->HandleReceivedEvents(); 114} 115 116Status WebViewImpl::Load(const std::string& url) { 117 // Javascript URLs will cause a hang while waiting for the page to stop 118 // loading, so just disallow. 119 if (StartsWithASCII(url, "javascript:", false)) 120 return Status(kUnknownError, "unsupported protocol"); 121 base::DictionaryValue params; 122 params.SetString("url", url); 123 return client_->SendCommand("Page.navigate", params); 124} 125 126Status WebViewImpl::Reload() { 127 base::DictionaryValue params; 128 params.SetBoolean("ignoreCache", false); 129 return client_->SendCommand("Page.reload", params); 130} 131 132Status WebViewImpl::EvaluateScript(const std::string& frame, 133 const std::string& expression, 134 scoped_ptr<base::Value>* result) { 135 int context_id; 136 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, 137 &context_id); 138 if (status.IsError()) 139 return status; 140 return internal::EvaluateScriptAndGetValue( 141 client_.get(), context_id, expression, result); 142} 143 144Status WebViewImpl::CallFunction(const std::string& frame, 145 const std::string& function, 146 const base::ListValue& args, 147 scoped_ptr<base::Value>* result) { 148 std::string json; 149 base::JSONWriter::Write(&args, &json); 150 std::string expression = base::StringPrintf( 151 "(%s).apply(null, [%s, %s])", 152 kCallFunctionScript, 153 function.c_str(), 154 json.c_str()); 155 scoped_ptr<base::Value> temp_result; 156 Status status = EvaluateScript(frame, expression, &temp_result); 157 if (status.IsError()) 158 return status; 159 160 return internal::ParseCallFunctionResult(*temp_result, result); 161} 162 163Status WebViewImpl::CallAsyncFunction(const std::string& frame, 164 const std::string& function, 165 const base::ListValue& args, 166 const base::TimeDelta& timeout, 167 scoped_ptr<base::Value>* result) { 168 return CallAsyncFunctionInternal( 169 frame, function, args, false, timeout, result); 170} 171 172Status WebViewImpl::CallUserAsyncFunction(const std::string& frame, 173 const std::string& function, 174 const base::ListValue& args, 175 const base::TimeDelta& timeout, 176 scoped_ptr<base::Value>* result) { 177 return CallAsyncFunctionInternal( 178 frame, function, args, true, timeout, result); 179} 180 181Status WebViewImpl::GetFrameByFunction(const std::string& frame, 182 const std::string& function, 183 const base::ListValue& args, 184 std::string* out_frame) { 185 int context_id; 186 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, 187 &context_id); 188 if (status.IsError()) 189 return status; 190 bool found_node; 191 int node_id; 192 status = internal::GetNodeIdFromFunction( 193 client_.get(), context_id, function, args, &found_node, &node_id); 194 if (status.IsError()) 195 return status; 196 if (!found_node) 197 return Status(kNoSuchFrame); 198 return dom_tracker_->GetFrameIdForNode(node_id, out_frame); 199} 200 201Status WebViewImpl::DispatchMouseEvents(const std::list<MouseEvent>& events, 202 const std::string& frame) { 203 for (std::list<MouseEvent>::const_iterator it = events.begin(); 204 it != events.end(); ++it) { 205 base::DictionaryValue params; 206 params.SetString("type", GetAsString(it->type)); 207 params.SetInteger("x", it->x); 208 params.SetInteger("y", it->y); 209 params.SetInteger("modifiers", it->modifiers); 210 params.SetString("button", GetAsString(it->button)); 211 params.SetInteger("clickCount", it->click_count); 212 Status status = client_->SendCommand("Input.dispatchMouseEvent", params); 213 if (status.IsError()) 214 return status; 215 if (build_no_ < 1569 && it->button == kRightMouseButton && 216 it->type == kReleasedMouseEventType) { 217 base::ListValue args; 218 args.AppendInteger(it->x); 219 args.AppendInteger(it->y); 220 args.AppendInteger(it->modifiers); 221 scoped_ptr<base::Value> result; 222 status = CallFunction( 223 frame, kDispatchContextMenuEventScript, args, &result); 224 if (status.IsError()) 225 return status; 226 } 227 } 228 return Status(kOk); 229} 230 231Status WebViewImpl::DispatchKeyEvents(const std::list<KeyEvent>& events) { 232 for (std::list<KeyEvent>::const_iterator it = events.begin(); 233 it != events.end(); ++it) { 234 base::DictionaryValue params; 235 params.SetString("type", GetAsString(it->type)); 236 if (it->modifiers & kNumLockKeyModifierMask) { 237 params.SetBoolean("isKeypad", true); 238 params.SetInteger("modifiers", 239 it->modifiers & ~kNumLockKeyModifierMask); 240 } else { 241 params.SetInteger("modifiers", it->modifiers); 242 } 243 params.SetString("text", it->modified_text); 244 params.SetString("unmodifiedText", it->unmodified_text); 245 params.SetInteger("nativeVirtualKeyCode", it->key_code); 246 params.SetInteger("windowsVirtualKeyCode", it->key_code); 247 Status status = client_->SendCommand("Input.dispatchKeyEvent", params); 248 if (status.IsError()) 249 return status; 250 } 251 return Status(kOk); 252} 253 254Status WebViewImpl::GetCookies(scoped_ptr<base::ListValue>* cookies) { 255 base::DictionaryValue params; 256 scoped_ptr<base::DictionaryValue> result; 257 Status status = client_->SendCommandAndGetResult( 258 "Page.getCookies", params, &result); 259 if (status.IsError()) 260 return status; 261 base::ListValue* cookies_tmp; 262 if (!result->GetList("cookies", &cookies_tmp)) 263 return Status(kUnknownError, "DevTools didn't return cookies"); 264 cookies->reset(cookies_tmp->DeepCopy()); 265 return Status(kOk); 266} 267 268Status WebViewImpl::DeleteCookie(const std::string& name, 269 const std::string& url) { 270 base::DictionaryValue params; 271 params.SetString("cookieName", name); 272 params.SetString("url", url); 273 return client_->SendCommand("Page.deleteCookie", params); 274} 275 276Status WebViewImpl::WaitForPendingNavigations(const std::string& frame_id, 277 int timeout) { 278 log_->AddEntry(Log::kLog, "waiting for pending navigations..."); 279 Status status = client_->HandleEventsUntil( 280 base::Bind(&WebViewImpl::IsNotPendingNavigation, base::Unretained(this), 281 frame_id), 282 base::TimeDelta::FromMilliseconds(timeout)); 283 if (status.code() == kTimeout) { 284 log_->AddEntry(Log::kLog, "timed out. stopping navigations..."); 285 scoped_ptr<base::Value> unused_value; 286 EvaluateScript(std::string(), "window.stop();", &unused_value); 287 Status new_status = client_->HandleEventsUntil( 288 base::Bind(&WebViewImpl::IsNotPendingNavigation, base::Unretained(this), 289 frame_id), 290 base::TimeDelta::FromSeconds(10)); 291 if (new_status.IsError()) 292 status = new_status; 293 } 294 log_->AddEntry(Log::kLog, "done waiting for pending navigations"); 295 return status; 296} 297 298Status WebViewImpl::IsPendingNavigation(const std::string& frame_id, 299 bool* is_pending) { 300 return navigation_tracker_->IsPendingNavigation(frame_id, is_pending); 301} 302 303JavaScriptDialogManager* WebViewImpl::GetJavaScriptDialogManager() { 304 return dialog_manager_.get(); 305} 306 307Status WebViewImpl::OverrideGeolocation(const Geoposition& geoposition) { 308 return geolocation_override_manager_->OverrideGeolocation(geoposition); 309} 310 311Status WebViewImpl::CaptureScreenshot(std::string* screenshot) { 312 base::DictionaryValue params; 313 scoped_ptr<base::DictionaryValue> result; 314 Status status = client_->SendCommandAndGetResult( 315 "Page.captureScreenshot", params, &result); 316 if (status.IsError()) 317 return status; 318 if (!result->GetString("data", screenshot)) 319 return Status(kUnknownError, "expected string 'data' in response"); 320 return Status(kOk); 321} 322 323Status WebViewImpl::SetFileInputFiles( 324 const std::string& frame, 325 const base::DictionaryValue& element, 326 const std::vector<base::FilePath>& files) { 327 base::ListValue file_list; 328 for (size_t i = 0; i < files.size(); ++i) { 329 if (!files[i].IsAbsolute()) { 330 return Status(kUnknownError, 331 "path is not absolute: " + files[i].AsUTF8Unsafe()); 332 } 333 if (files[i].ReferencesParent()) { 334 return Status(kUnknownError, 335 "path is not canonical: " + files[i].AsUTF8Unsafe()); 336 } 337 file_list.AppendString(files[i].value()); 338 } 339 340 int context_id; 341 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, 342 &context_id); 343 if (status.IsError()) 344 return status; 345 base::ListValue args; 346 args.Append(element.DeepCopy()); 347 bool found_node; 348 int node_id; 349 status = internal::GetNodeIdFromFunction( 350 client_.get(), context_id, "function(element) { return element; }", 351 args, &found_node, &node_id); 352 if (status.IsError()) 353 return status; 354 if (!found_node) 355 return Status(kUnknownError, "no node ID for file input"); 356 base::DictionaryValue params; 357 params.SetInteger("nodeId", node_id); 358 params.Set("files", file_list.DeepCopy()); 359 return client_->SendCommand("DOM.setFileInputFiles", params); 360} 361 362Status WebViewImpl::CallAsyncFunctionInternal(const std::string& frame, 363 const std::string& function, 364 const base::ListValue& args, 365 bool is_user_supplied, 366 const base::TimeDelta& timeout, 367 scoped_ptr<base::Value>* result) { 368 base::ListValue async_args; 369 async_args.AppendString("return (" + function + ").apply(null, arguments);"); 370 async_args.Append(args.DeepCopy()); 371 async_args.AppendBoolean(is_user_supplied); 372 async_args.AppendInteger(timeout.InMilliseconds()); 373 scoped_ptr<base::Value> tmp; 374 Status status = CallFunction( 375 frame, kExecuteAsyncScriptScript, async_args, &tmp); 376 if (status.IsError()) 377 return status; 378 379 const char* kDocUnloadError = "document unloaded while waiting for result"; 380 std::string kQueryResult = base::StringPrintf( 381 "function() {" 382 " var info = document.$chrome_asyncScriptInfo;" 383 " if (!info)" 384 " return {status: %d, value: '%s'};" 385 " var result = info.result;" 386 " if (!result)" 387 " return {status: 0};" 388 " delete info.result;" 389 " return result;" 390 "}", 391 kJavaScriptError, 392 kDocUnloadError); 393 394 while (true) { 395 base::ListValue no_args; 396 scoped_ptr<base::Value> query_value; 397 Status status = CallFunction(frame, kQueryResult, no_args, &query_value); 398 if (status.IsError()) { 399 if (status.code() == kNoSuchFrame) 400 return Status(kJavaScriptError, kDocUnloadError); 401 return status; 402 } 403 404 base::DictionaryValue* result_info = NULL; 405 if (!query_value->GetAsDictionary(&result_info)) 406 return Status(kUnknownError, "async result info is not a dictionary"); 407 int status_code; 408 if (!result_info->GetInteger("status", &status_code)) 409 return Status(kUnknownError, "async result info has no int 'status'"); 410 if (status_code != kOk) { 411 std::string message; 412 result_info->GetString("value", &message); 413 return Status(static_cast<StatusCode>(status_code), message); 414 } 415 416 base::Value* value = NULL; 417 if (result_info->Get("value", &value)) { 418 result->reset(value->DeepCopy()); 419 return Status(kOk); 420 } 421 422 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); 423 } 424} 425 426Status WebViewImpl::IsNotPendingNavigation(const std::string& frame_id, 427 bool* is_not_pending) { 428 bool is_pending; 429 Status status = 430 navigation_tracker_->IsPendingNavigation(frame_id, &is_pending); 431 if (status.IsError()) 432 return status; 433 // An alert may block the pending navigation. 434 if (is_pending && dialog_manager_->IsDialogOpen()) 435 return Status(kUnexpectedAlertOpen); 436 437 *is_not_pending = !is_pending; 438 return Status(kOk); 439} 440 441namespace internal { 442 443Status EvaluateScript(DevToolsClient* client, 444 int context_id, 445 const std::string& expression, 446 EvaluateScriptReturnType return_type, 447 scoped_ptr<base::DictionaryValue>* result) { 448 base::DictionaryValue params; 449 params.SetString("expression", expression); 450 if (context_id) 451 params.SetInteger("contextId", context_id); 452 params.SetBoolean("returnByValue", return_type == ReturnByValue); 453 scoped_ptr<base::DictionaryValue> cmd_result; 454 Status status = client->SendCommandAndGetResult( 455 "Runtime.evaluate", params, &cmd_result); 456 if (status.IsError()) 457 return status; 458 459 bool was_thrown; 460 if (!cmd_result->GetBoolean("wasThrown", &was_thrown)) 461 return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'"); 462 if (was_thrown) { 463 std::string description = "unknown"; 464 cmd_result->GetString("result.description", &description); 465 return Status(kUnknownError, 466 "Runtime.evaluate threw exception: " + description); 467 } 468 469 base::DictionaryValue* unscoped_result; 470 if (!cmd_result->GetDictionary("result", &unscoped_result)) 471 return Status(kUnknownError, "evaluate missing dictionary 'result'"); 472 result->reset(unscoped_result->DeepCopy()); 473 return Status(kOk); 474} 475 476Status EvaluateScriptAndGetObject(DevToolsClient* client, 477 int context_id, 478 const std::string& expression, 479 bool* got_object, 480 std::string* object_id) { 481 scoped_ptr<base::DictionaryValue> result; 482 Status status = EvaluateScript(client, context_id, expression, ReturnByObject, 483 &result); 484 if (status.IsError()) 485 return status; 486 if (!result->HasKey("objectId")) { 487 *got_object = false; 488 return Status(kOk); 489 } 490 if (!result->GetString("objectId", object_id)) 491 return Status(kUnknownError, "evaluate has invalid 'objectId'"); 492 *got_object = true; 493 return Status(kOk); 494} 495 496Status EvaluateScriptAndGetValue(DevToolsClient* client, 497 int context_id, 498 const std::string& expression, 499 scoped_ptr<base::Value>* result) { 500 scoped_ptr<base::DictionaryValue> temp_result; 501 Status status = EvaluateScript(client, context_id, expression, ReturnByValue, 502 &temp_result); 503 if (status.IsError()) 504 return status; 505 506 std::string type; 507 if (!temp_result->GetString("type", &type)) 508 return Status(kUnknownError, "Runtime.evaluate missing string 'type'"); 509 510 if (type == "undefined") { 511 result->reset(base::Value::CreateNullValue()); 512 } else { 513 base::Value* value; 514 if (!temp_result->Get("value", &value)) 515 return Status(kUnknownError, "Runtime.evaluate missing 'value'"); 516 result->reset(value->DeepCopy()); 517 } 518 return Status(kOk); 519} 520 521Status ParseCallFunctionResult(const base::Value& temp_result, 522 scoped_ptr<base::Value>* result) { 523 const base::DictionaryValue* dict; 524 if (!temp_result.GetAsDictionary(&dict)) 525 return Status(kUnknownError, "call function result must be a dictionary"); 526 int status_code; 527 if (!dict->GetInteger("status", &status_code)) { 528 return Status(kUnknownError, 529 "call function result missing int 'status'"); 530 } 531 if (status_code != kOk) { 532 std::string message; 533 dict->GetString("value", &message); 534 return Status(static_cast<StatusCode>(status_code), message); 535 } 536 const base::Value* unscoped_value; 537 if (!dict->Get("value", &unscoped_value)) { 538 return Status(kUnknownError, 539 "call function result missing 'value'"); 540 } 541 result->reset(unscoped_value->DeepCopy()); 542 return Status(kOk); 543} 544 545Status GetNodeIdFromFunction(DevToolsClient* client, 546 int context_id, 547 const std::string& function, 548 const base::ListValue& args, 549 bool* found_node, 550 int* node_id) { 551 std::string json; 552 base::JSONWriter::Write(&args, &json); 553 std::string expression = base::StringPrintf( 554 "(%s).apply(null, [%s, %s, true])", 555 kCallFunctionScript, 556 function.c_str(), 557 json.c_str()); 558 559 bool got_object; 560 std::string element_id; 561 Status status = internal::EvaluateScriptAndGetObject( 562 client, context_id, expression, &got_object, &element_id); 563 if (status.IsError()) 564 return status; 565 if (!got_object) { 566 *found_node = false; 567 return Status(kOk); 568 } 569 570 scoped_ptr<base::DictionaryValue> cmd_result; 571 { 572 base::DictionaryValue params; 573 params.SetString("objectId", element_id); 574 status = client->SendCommandAndGetResult( 575 "DOM.requestNode", params, &cmd_result); 576 } 577 { 578 // Release the remote object before doing anything else. 579 base::DictionaryValue params; 580 params.SetString("objectId", element_id); 581 Status release_status = 582 client->SendCommand("Runtime.releaseObject", params); 583 if (release_status.IsError()) { 584 LOG(ERROR) << "Failed to release remote object: " 585 << release_status.message(); 586 } 587 } 588 if (status.IsError()) 589 return status; 590 591 if (!cmd_result->GetInteger("nodeId", node_id)) 592 return Status(kUnknownError, "DOM.requestNode missing int 'nodeId'"); 593 *found_node = true; 594 return Status(kOk); 595} 596 597} // namespace internal 598