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