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/browser_info.h" 17#include "chrome/test/chromedriver/chrome/debugger_tracker.h" 18#include "chrome/test/chromedriver/chrome/devtools_client_impl.h" 19#include "chrome/test/chromedriver/chrome/dom_tracker.h" 20#include "chrome/test/chromedriver/chrome/frame_tracker.h" 21#include "chrome/test/chromedriver/chrome/geolocation_override_manager.h" 22#include "chrome/test/chromedriver/chrome/heap_snapshot_taker.h" 23#include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h" 24#include "chrome/test/chromedriver/chrome/js.h" 25#include "chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h" 26#include "chrome/test/chromedriver/chrome/navigation_tracker.h" 27#include "chrome/test/chromedriver/chrome/status.h" 28#include "chrome/test/chromedriver/chrome/ui_events.h" 29 30namespace { 31 32Status GetContextIdForFrame(FrameTracker* tracker, 33 const std::string& frame, 34 int* context_id) { 35 if (frame.empty()) { 36 *context_id = 0; 37 return Status(kOk); 38 } 39 Status status = tracker->GetContextIdForFrame(frame, context_id); 40 if (status.IsError()) 41 return status; 42 return Status(kOk); 43} 44 45const char* GetAsString(MouseEventType type) { 46 switch (type) { 47 case kPressedMouseEventType: 48 return "mousePressed"; 49 case kReleasedMouseEventType: 50 return "mouseReleased"; 51 case kMovedMouseEventType: 52 return "mouseMoved"; 53 default: 54 return ""; 55 } 56} 57 58const char* GetAsString(TouchEventType type) { 59 switch (type) { 60 case kTouchStart: 61 return "touchStart"; 62 case kTouchEnd: 63 return "touchEnd"; 64 case kTouchMove: 65 return "touchMove"; 66 default: 67 return ""; 68 } 69} 70 71const char* GetPointStateString(TouchEventType type) { 72 switch (type) { 73 case kTouchStart: 74 return "touchPressed"; 75 case kTouchEnd: 76 return "touchReleased"; 77 case kTouchMove: 78 return "touchMoved"; 79 default: 80 return ""; 81 } 82} 83 84const char* GetAsString(MouseButton button) { 85 switch (button) { 86 case kLeftMouseButton: 87 return "left"; 88 case kMiddleMouseButton: 89 return "middle"; 90 case kRightMouseButton: 91 return "right"; 92 case kNoneMouseButton: 93 return "none"; 94 default: 95 return ""; 96 } 97} 98 99const char* GetAsString(KeyEventType type) { 100 switch (type) { 101 case kKeyDownEventType: 102 return "keyDown"; 103 case kKeyUpEventType: 104 return "keyUp"; 105 case kRawKeyDownEventType: 106 return "rawKeyDown"; 107 case kCharEventType: 108 return "char"; 109 default: 110 return ""; 111 } 112} 113 114} // namespace 115 116WebViewImpl::WebViewImpl(const std::string& id, 117 const BrowserInfo* browser_info, 118 scoped_ptr<DevToolsClient> client, 119 const DeviceMetrics* device_metrics) 120 : id_(id), 121 browser_info_(browser_info), 122 dom_tracker_(new DomTracker(client.get())), 123 frame_tracker_(new FrameTracker(client.get())), 124 navigation_tracker_(new NavigationTracker(client.get(), browser_info)), 125 dialog_manager_(new JavaScriptDialogManager(client.get())), 126 mobile_emulation_override_manager_( 127 new MobileEmulationOverrideManager(client.get(), 128 device_metrics, 129 browser_info)), 130 geolocation_override_manager_( 131 new GeolocationOverrideManager(client.get())), 132 heap_snapshot_taker_(new HeapSnapshotTaker(client.get())), 133 debugger_(new DebuggerTracker(client.get())), 134 client_(client.release()) {} 135 136WebViewImpl::~WebViewImpl() {} 137 138std::string WebViewImpl::GetId() { 139 return id_; 140} 141 142bool WebViewImpl::WasCrashed() { 143 return client_->WasCrashed(); 144} 145 146Status WebViewImpl::ConnectIfNecessary() { 147 return client_->ConnectIfNecessary(); 148} 149 150Status WebViewImpl::HandleReceivedEvents() { 151 return client_->HandleReceivedEvents(); 152} 153 154Status WebViewImpl::Load(const std::string& url) { 155 // Javascript URLs will cause a hang while waiting for the page to stop 156 // loading, so just disallow. 157 if (StartsWithASCII(url, "javascript:", false)) 158 return Status(kUnknownError, "unsupported protocol"); 159 base::DictionaryValue params; 160 params.SetString("url", url); 161 return client_->SendCommand("Page.navigate", params); 162} 163 164Status WebViewImpl::Reload() { 165 base::DictionaryValue params; 166 params.SetBoolean("ignoreCache", false); 167 return client_->SendCommand("Page.reload", params); 168} 169 170Status WebViewImpl::EvaluateScript(const std::string& frame, 171 const std::string& expression, 172 scoped_ptr<base::Value>* result) { 173 int context_id; 174 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, 175 &context_id); 176 if (status.IsError()) 177 return status; 178 return internal::EvaluateScriptAndGetValue( 179 client_.get(), context_id, expression, result); 180} 181 182Status WebViewImpl::CallFunction(const std::string& frame, 183 const std::string& function, 184 const base::ListValue& args, 185 scoped_ptr<base::Value>* result) { 186 std::string json; 187 base::JSONWriter::Write(&args, &json); 188 // TODO(zachconrad): Second null should be array of shadow host ids. 189 std::string expression = base::StringPrintf( 190 "(%s).apply(null, [null, %s, %s])", 191 kCallFunctionScript, 192 function.c_str(), 193 json.c_str()); 194 scoped_ptr<base::Value> temp_result; 195 Status status = EvaluateScript(frame, expression, &temp_result); 196 if (status.IsError()) 197 return status; 198 199 return internal::ParseCallFunctionResult(*temp_result, result); 200} 201 202Status WebViewImpl::CallAsyncFunction(const std::string& frame, 203 const std::string& function, 204 const base::ListValue& args, 205 const base::TimeDelta& timeout, 206 scoped_ptr<base::Value>* result) { 207 return CallAsyncFunctionInternal( 208 frame, function, args, false, timeout, result); 209} 210 211Status WebViewImpl::CallUserAsyncFunction(const std::string& frame, 212 const std::string& function, 213 const base::ListValue& args, 214 const base::TimeDelta& timeout, 215 scoped_ptr<base::Value>* result) { 216 return CallAsyncFunctionInternal( 217 frame, function, args, true, timeout, result); 218} 219 220Status WebViewImpl::GetFrameByFunction(const std::string& frame, 221 const std::string& function, 222 const base::ListValue& args, 223 std::string* out_frame) { 224 int context_id; 225 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, 226 &context_id); 227 if (status.IsError()) 228 return status; 229 bool found_node; 230 int node_id; 231 status = internal::GetNodeIdFromFunction( 232 client_.get(), context_id, function, args, &found_node, &node_id); 233 if (status.IsError()) 234 return status; 235 if (!found_node) 236 return Status(kNoSuchFrame); 237 return dom_tracker_->GetFrameIdForNode(node_id, out_frame); 238} 239 240Status WebViewImpl::DispatchMouseEvents(const std::list<MouseEvent>& events, 241 const std::string& frame) { 242 for (std::list<MouseEvent>::const_iterator it = events.begin(); 243 it != events.end(); ++it) { 244 base::DictionaryValue params; 245 params.SetString("type", GetAsString(it->type)); 246 params.SetInteger("x", it->x); 247 params.SetInteger("y", it->y); 248 params.SetInteger("modifiers", it->modifiers); 249 params.SetString("button", GetAsString(it->button)); 250 params.SetInteger("clickCount", it->click_count); 251 Status status = client_->SendCommand("Input.dispatchMouseEvent", params); 252 if (status.IsError()) 253 return status; 254 if (browser_info_->build_no < 1569 && it->button == kRightMouseButton && 255 it->type == kReleasedMouseEventType) { 256 base::ListValue args; 257 args.AppendInteger(it->x); 258 args.AppendInteger(it->y); 259 args.AppendInteger(it->modifiers); 260 scoped_ptr<base::Value> result; 261 status = CallFunction( 262 frame, kDispatchContextMenuEventScript, args, &result); 263 if (status.IsError()) 264 return status; 265 } 266 } 267 return Status(kOk); 268} 269 270Status WebViewImpl::DispatchTouchEvent(const TouchEvent& event) { 271 base::DictionaryValue params; 272 params.SetString("type", GetAsString(event.type)); 273 scoped_ptr<base::ListValue> point_list(new base::ListValue); 274 scoped_ptr<base::DictionaryValue> point(new base::DictionaryValue); 275 point->SetString("state", GetPointStateString(event.type)); 276 point->SetInteger("x", event.x); 277 point->SetInteger("y", event.y); 278 point_list->Set(0, point.release()); 279 params.Set("touchPoints", point_list.release()); 280 return client_->SendCommand("Input.dispatchTouchEvent", params); 281} 282 283Status WebViewImpl::DispatchTouchEvents(const std::list<TouchEvent>& events) { 284 for (std::list<TouchEvent>::const_iterator it = events.begin(); 285 it != events.end(); ++it) { 286 Status status = DispatchTouchEvent(*it); 287 if (status.IsError()) 288 return status; 289 } 290 return Status(kOk); 291} 292 293Status WebViewImpl::DispatchKeyEvents(const std::list<KeyEvent>& events) { 294 for (std::list<KeyEvent>::const_iterator it = events.begin(); 295 it != events.end(); ++it) { 296 base::DictionaryValue params; 297 params.SetString("type", GetAsString(it->type)); 298 if (it->modifiers & kNumLockKeyModifierMask) { 299 params.SetBoolean("isKeypad", true); 300 params.SetInteger("modifiers", 301 it->modifiers & ~kNumLockKeyModifierMask); 302 } else { 303 params.SetInteger("modifiers", it->modifiers); 304 } 305 params.SetString("text", it->modified_text); 306 params.SetString("unmodifiedText", it->unmodified_text); 307 params.SetInteger("nativeVirtualKeyCode", it->key_code); 308 params.SetInteger("windowsVirtualKeyCode", it->key_code); 309 Status status = client_->SendCommand("Input.dispatchKeyEvent", params); 310 if (status.IsError()) 311 return status; 312 } 313 return Status(kOk); 314} 315 316Status WebViewImpl::GetCookies(scoped_ptr<base::ListValue>* cookies) { 317 base::DictionaryValue params; 318 scoped_ptr<base::DictionaryValue> result; 319 Status status = client_->SendCommandAndGetResult( 320 "Page.getCookies", params, &result); 321 if (status.IsError()) 322 return status; 323 base::ListValue* cookies_tmp; 324 if (!result->GetList("cookies", &cookies_tmp)) 325 return Status(kUnknownError, "DevTools didn't return cookies"); 326 cookies->reset(cookies_tmp->DeepCopy()); 327 return Status(kOk); 328} 329 330Status WebViewImpl::DeleteCookie(const std::string& name, 331 const std::string& url) { 332 base::DictionaryValue params; 333 params.SetString("cookieName", name); 334 params.SetString("url", url); 335 return client_->SendCommand("Page.deleteCookie", params); 336} 337 338Status WebViewImpl::WaitForPendingNavigations(const std::string& frame_id, 339 const base::TimeDelta& timeout, 340 bool stop_load_on_timeout) { 341 VLOG(0) << "Waiting for pending navigations..."; 342 Status status = client_->HandleEventsUntil( 343 base::Bind(&WebViewImpl::IsNotPendingNavigation, 344 base::Unretained(this), 345 frame_id), 346 timeout); 347 if (status.code() == kTimeout && stop_load_on_timeout) { 348 VLOG(0) << "Timed out. Stopping navigation..."; 349 scoped_ptr<base::Value> unused_value; 350 EvaluateScript(std::string(), "window.stop();", &unused_value); 351 Status new_status = client_->HandleEventsUntil( 352 base::Bind(&WebViewImpl::IsNotPendingNavigation, base::Unretained(this), 353 frame_id), 354 base::TimeDelta::FromSeconds(10)); 355 if (new_status.IsError()) 356 status = new_status; 357 } 358 VLOG(0) << "Done waiting for pending navigations"; 359 return status; 360} 361 362Status WebViewImpl::IsPendingNavigation(const std::string& frame_id, 363 bool* is_pending) { 364 return navigation_tracker_->IsPendingNavigation(frame_id, is_pending); 365} 366 367JavaScriptDialogManager* WebViewImpl::GetJavaScriptDialogManager() { 368 return dialog_manager_.get(); 369} 370 371Status WebViewImpl::OverrideGeolocation(const Geoposition& geoposition) { 372 return geolocation_override_manager_->OverrideGeolocation(geoposition); 373} 374 375Status WebViewImpl::CaptureScreenshot(std::string* screenshot) { 376 base::DictionaryValue params; 377 scoped_ptr<base::DictionaryValue> result; 378 Status status = client_->SendCommandAndGetResult( 379 "Page.captureScreenshot", params, &result); 380 if (status.IsError()) 381 return status; 382 if (!result->GetString("data", screenshot)) 383 return Status(kUnknownError, "expected string 'data' in response"); 384 return Status(kOk); 385} 386 387Status WebViewImpl::SetFileInputFiles( 388 const std::string& frame, 389 const base::DictionaryValue& element, 390 const std::vector<base::FilePath>& files) { 391 base::ListValue file_list; 392 for (size_t i = 0; i < files.size(); ++i) { 393 if (!files[i].IsAbsolute()) { 394 return Status(kUnknownError, 395 "path is not absolute: " + files[i].AsUTF8Unsafe()); 396 } 397 if (files[i].ReferencesParent()) { 398 return Status(kUnknownError, 399 "path is not canonical: " + files[i].AsUTF8Unsafe()); 400 } 401 file_list.AppendString(files[i].value()); 402 } 403 404 int context_id; 405 Status status = GetContextIdForFrame(frame_tracker_.get(), frame, 406 &context_id); 407 if (status.IsError()) 408 return status; 409 base::ListValue args; 410 args.Append(element.DeepCopy()); 411 bool found_node; 412 int node_id; 413 status = internal::GetNodeIdFromFunction( 414 client_.get(), context_id, "function(element) { return element; }", 415 args, &found_node, &node_id); 416 if (status.IsError()) 417 return status; 418 if (!found_node) 419 return Status(kUnknownError, "no node ID for file input"); 420 base::DictionaryValue params; 421 params.SetInteger("nodeId", node_id); 422 params.Set("files", file_list.DeepCopy()); 423 return client_->SendCommand("DOM.setFileInputFiles", params); 424} 425 426Status WebViewImpl::TakeHeapSnapshot(scoped_ptr<base::Value>* snapshot) { 427 return heap_snapshot_taker_->TakeSnapshot(snapshot); 428} 429 430Status WebViewImpl::InitProfileInternal() { 431 base::DictionaryValue params; 432 433 // TODO: Remove Debugger.enable after Chrome 36 stable is released. 434 Status status_debug = client_->SendCommand("Debugger.enable", params); 435 436 if (status_debug.IsError()) 437 return status_debug; 438 439 Status status_profiler = client_->SendCommand("Profiler.enable", params); 440 441 if (status_profiler.IsError()) { 442 Status status_debugger = client_->SendCommand("Debugger.disable", params); 443 if (status_debugger.IsError()) 444 return status_debugger; 445 446 return status_profiler; 447 } 448 449 return Status(kOk); 450} 451 452Status WebViewImpl::StopProfileInternal() { 453 base::DictionaryValue params; 454 Status status_debug = client_->SendCommand("Debugger.disable", params); 455 Status status_profiler = client_->SendCommand("Profiler.disable", params); 456 457 if (status_debug.IsError()) { 458 return status_debug; 459 } else if (status_profiler.IsError()) { 460 return status_profiler; 461 } 462 463 return Status(kOk); 464} 465 466Status WebViewImpl::StartProfile() { 467 Status status_init = InitProfileInternal(); 468 469 if (status_init.IsError()) 470 return status_init; 471 472 base::DictionaryValue params; 473 return client_->SendCommand("Profiler.start", params); 474} 475 476Status WebViewImpl::EndProfile(scoped_ptr<base::Value>* profile_data) { 477 base::DictionaryValue params; 478 scoped_ptr<base::DictionaryValue> profile_result; 479 480 Status status = client_->SendCommandAndGetResult( 481 "Profiler.stop", params, &profile_result); 482 483 if (status.IsError()) { 484 Status disable_profile_status = StopProfileInternal(); 485 if (disable_profile_status.IsError()) { 486 return disable_profile_status; 487 } else { 488 return status; 489 } 490 } 491 492 *profile_data = profile_result.PassAs<base::Value>(); 493 return status; 494} 495 496Status WebViewImpl::CallAsyncFunctionInternal(const std::string& frame, 497 const std::string& function, 498 const base::ListValue& args, 499 bool is_user_supplied, 500 const base::TimeDelta& timeout, 501 scoped_ptr<base::Value>* result) { 502 base::ListValue async_args; 503 async_args.AppendString("return (" + function + ").apply(null, arguments);"); 504 async_args.Append(args.DeepCopy()); 505 async_args.AppendBoolean(is_user_supplied); 506 async_args.AppendInteger(timeout.InMilliseconds()); 507 scoped_ptr<base::Value> tmp; 508 Status status = CallFunction( 509 frame, kExecuteAsyncScriptScript, async_args, &tmp); 510 if (status.IsError()) 511 return status; 512 513 const char* kDocUnloadError = "document unloaded while waiting for result"; 514 std::string kQueryResult = base::StringPrintf( 515 "function() {" 516 " var info = document.$chrome_asyncScriptInfo;" 517 " if (!info)" 518 " return {status: %d, value: '%s'};" 519 " var result = info.result;" 520 " if (!result)" 521 " return {status: 0};" 522 " delete info.result;" 523 " return result;" 524 "}", 525 kJavaScriptError, 526 kDocUnloadError); 527 528 while (true) { 529 base::ListValue no_args; 530 scoped_ptr<base::Value> query_value; 531 Status status = CallFunction(frame, kQueryResult, no_args, &query_value); 532 if (status.IsError()) { 533 if (status.code() == kNoSuchFrame) 534 return Status(kJavaScriptError, kDocUnloadError); 535 return status; 536 } 537 538 base::DictionaryValue* result_info = NULL; 539 if (!query_value->GetAsDictionary(&result_info)) 540 return Status(kUnknownError, "async result info is not a dictionary"); 541 int status_code; 542 if (!result_info->GetInteger("status", &status_code)) 543 return Status(kUnknownError, "async result info has no int 'status'"); 544 if (status_code != kOk) { 545 std::string message; 546 result_info->GetString("value", &message); 547 return Status(static_cast<StatusCode>(status_code), message); 548 } 549 550 base::Value* value = NULL; 551 if (result_info->Get("value", &value)) { 552 result->reset(value->DeepCopy()); 553 return Status(kOk); 554 } 555 556 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); 557 } 558} 559 560Status WebViewImpl::IsNotPendingNavigation(const std::string& frame_id, 561 bool* is_not_pending) { 562 bool is_pending; 563 Status status = 564 navigation_tracker_->IsPendingNavigation(frame_id, &is_pending); 565 if (status.IsError()) 566 return status; 567 // An alert may block the pending navigation. 568 if (is_pending && dialog_manager_->IsDialogOpen()) 569 return Status(kUnexpectedAlertOpen); 570 571 *is_not_pending = !is_pending; 572 return Status(kOk); 573} 574 575namespace internal { 576 577Status EvaluateScript(DevToolsClient* client, 578 int context_id, 579 const std::string& expression, 580 EvaluateScriptReturnType return_type, 581 scoped_ptr<base::DictionaryValue>* result) { 582 base::DictionaryValue params; 583 params.SetString("expression", expression); 584 if (context_id) 585 params.SetInteger("contextId", context_id); 586 params.SetBoolean("returnByValue", return_type == ReturnByValue); 587 scoped_ptr<base::DictionaryValue> cmd_result; 588 Status status = client->SendCommandAndGetResult( 589 "Runtime.evaluate", params, &cmd_result); 590 if (status.IsError()) 591 return status; 592 593 bool was_thrown; 594 if (!cmd_result->GetBoolean("wasThrown", &was_thrown)) 595 return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'"); 596 if (was_thrown) { 597 std::string description = "unknown"; 598 cmd_result->GetString("result.description", &description); 599 return Status(kUnknownError, 600 "Runtime.evaluate threw exception: " + description); 601 } 602 603 base::DictionaryValue* unscoped_result; 604 if (!cmd_result->GetDictionary("result", &unscoped_result)) 605 return Status(kUnknownError, "evaluate missing dictionary 'result'"); 606 result->reset(unscoped_result->DeepCopy()); 607 return Status(kOk); 608} 609 610Status EvaluateScriptAndGetObject(DevToolsClient* client, 611 int context_id, 612 const std::string& expression, 613 bool* got_object, 614 std::string* object_id) { 615 scoped_ptr<base::DictionaryValue> result; 616 Status status = EvaluateScript(client, context_id, expression, ReturnByObject, 617 &result); 618 if (status.IsError()) 619 return status; 620 if (!result->HasKey("objectId")) { 621 *got_object = false; 622 return Status(kOk); 623 } 624 if (!result->GetString("objectId", object_id)) 625 return Status(kUnknownError, "evaluate has invalid 'objectId'"); 626 *got_object = true; 627 return Status(kOk); 628} 629 630Status EvaluateScriptAndGetValue(DevToolsClient* client, 631 int context_id, 632 const std::string& expression, 633 scoped_ptr<base::Value>* result) { 634 scoped_ptr<base::DictionaryValue> temp_result; 635 Status status = EvaluateScript(client, context_id, expression, ReturnByValue, 636 &temp_result); 637 if (status.IsError()) 638 return status; 639 640 std::string type; 641 if (!temp_result->GetString("type", &type)) 642 return Status(kUnknownError, "Runtime.evaluate missing string 'type'"); 643 644 if (type == "undefined") { 645 result->reset(base::Value::CreateNullValue()); 646 } else { 647 base::Value* value; 648 if (!temp_result->Get("value", &value)) 649 return Status(kUnknownError, "Runtime.evaluate missing 'value'"); 650 result->reset(value->DeepCopy()); 651 } 652 return Status(kOk); 653} 654 655Status ParseCallFunctionResult(const base::Value& temp_result, 656 scoped_ptr<base::Value>* result) { 657 const base::DictionaryValue* dict; 658 if (!temp_result.GetAsDictionary(&dict)) 659 return Status(kUnknownError, "call function result must be a dictionary"); 660 int status_code; 661 if (!dict->GetInteger("status", &status_code)) { 662 return Status(kUnknownError, 663 "call function result missing int 'status'"); 664 } 665 if (status_code != kOk) { 666 std::string message; 667 dict->GetString("value", &message); 668 return Status(static_cast<StatusCode>(status_code), message); 669 } 670 const base::Value* unscoped_value; 671 if (!dict->Get("value", &unscoped_value)) { 672 return Status(kUnknownError, 673 "call function result missing 'value'"); 674 } 675 result->reset(unscoped_value->DeepCopy()); 676 return Status(kOk); 677} 678 679Status GetNodeIdFromFunction(DevToolsClient* client, 680 int context_id, 681 const std::string& function, 682 const base::ListValue& args, 683 bool* found_node, 684 int* node_id) { 685 std::string json; 686 base::JSONWriter::Write(&args, &json); 687 // TODO(zachconrad): Second null should be array of shadow host ids. 688 std::string expression = base::StringPrintf( 689 "(%s).apply(null, [null, %s, %s, true])", 690 kCallFunctionScript, 691 function.c_str(), 692 json.c_str()); 693 694 bool got_object; 695 std::string element_id; 696 Status status = internal::EvaluateScriptAndGetObject( 697 client, context_id, expression, &got_object, &element_id); 698 if (status.IsError()) 699 return status; 700 if (!got_object) { 701 *found_node = false; 702 return Status(kOk); 703 } 704 705 scoped_ptr<base::DictionaryValue> cmd_result; 706 { 707 base::DictionaryValue params; 708 params.SetString("objectId", element_id); 709 status = client->SendCommandAndGetResult( 710 "DOM.requestNode", params, &cmd_result); 711 } 712 { 713 // Release the remote object before doing anything else. 714 base::DictionaryValue params; 715 params.SetString("objectId", element_id); 716 Status release_status = 717 client->SendCommand("Runtime.releaseObject", params); 718 if (release_status.IsError()) { 719 LOG(ERROR) << "Failed to release remote object: " 720 << release_status.message(); 721 } 722 } 723 if (status.IsError()) 724 return status; 725 726 if (!cmd_result->GetInteger("nodeId", node_id)) 727 return Status(kUnknownError, "DOM.requestNode missing int 'nodeId'"); 728 *found_node = true; 729 return Status(kOk); 730} 731 732} // namespace internal 733