eb78c9ffb27c4aa9aaf871cfe0506999eb9f4cdb
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome / web_view_impl.cc
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/debugger_tracker.h"
17 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
18 #include "chrome/test/chromedriver/chrome/dom_tracker.h"
19 #include "chrome/test/chromedriver/chrome/frame_tracker.h"
20 #include "chrome/test/chromedriver/chrome/geolocation_override_manager.h"
21 #include "chrome/test/chromedriver/chrome/heap_snapshot_taker.h"
22 #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
23 #include "chrome/test/chromedriver/chrome/js.h"
24 #include "chrome/test/chromedriver/chrome/mobile_emulation_override_manager.h"
25 #include "chrome/test/chromedriver/chrome/navigation_tracker.h"
26 #include "chrome/test/chromedriver/chrome/status.h"
27 #include "chrome/test/chromedriver/chrome/ui_events.h"
28 #include "chrome/test/chromedriver/chrome/version.h"
29
30 namespace {
31
32 Status 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
45 const 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
58 const 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
71 const 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
84 const 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
99 const 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
116 WebViewImpl::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
136 WebViewImpl::~WebViewImpl() {}
137
138 std::string WebViewImpl::GetId() {
139   return id_;
140 }
141
142 bool WebViewImpl::WasCrashed() {
143   return client_->WasCrashed();
144 }
145
146 Status WebViewImpl::ConnectIfNecessary() {
147   return client_->ConnectIfNecessary();
148 }
149
150 Status WebViewImpl::HandleReceivedEvents() {
151   return client_->HandleReceivedEvents();
152 }
153
154 Status 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
164 Status WebViewImpl::Reload() {
165   base::DictionaryValue params;
166   params.SetBoolean("ignoreCache", false);
167   return client_->SendCommand("Page.reload", params);
168 }
169
170 Status 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
182 Status 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
202 Status 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
211 Status 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
220 Status 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
240 Status 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
270 Status 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
283 Status 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
293 Status 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
316 Status 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
330 Status 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
338 Status 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
362 Status WebViewImpl::IsPendingNavigation(const std::string& frame_id,
363                                         bool* is_pending) {
364   return navigation_tracker_->IsPendingNavigation(frame_id, is_pending);
365 }
366
367 JavaScriptDialogManager* WebViewImpl::GetJavaScriptDialogManager() {
368   return dialog_manager_.get();
369 }
370
371 Status WebViewImpl::OverrideGeolocation(const Geoposition& geoposition) {
372   return geolocation_override_manager_->OverrideGeolocation(geoposition);
373 }
374
375 Status 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
387 Status 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
426 Status WebViewImpl::TakeHeapSnapshot(scoped_ptr<base::Value>* snapshot) {
427   return heap_snapshot_taker_->TakeSnapshot(snapshot);
428 }
429
430 Status 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
452 Status 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
466 Status 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
476 Status 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
496 Status 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
560 Status 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
575 namespace internal {
576
577 Status 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
610 Status 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
630 Status 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
655 Status 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
679 Status 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