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