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.
5 #include "chrome/test/chromedriver/chrome/web_view_impl.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"
30 Status GetContextIdForFrame(FrameTracker* tracker,
31 const std::string& frame,
37 Status status = tracker->GetContextIdForFrame(frame, context_id);
43 const char* GetAsString(MouseEventType type) {
45 case kPressedMouseEventType:
46 return "mousePressed";
47 case kReleasedMouseEventType:
48 return "mouseReleased";
49 case kMovedMouseEventType:
56 const char* GetAsString(TouchEventType type) {
69 const char* GetPointStateString(TouchEventType type) {
72 return "touchPressed";
74 return "touchReleased";
82 const char* GetAsString(MouseButton button) {
84 case kLeftMouseButton:
86 case kMiddleMouseButton:
88 case kRightMouseButton:
90 case kNoneMouseButton:
97 const char* GetAsString(KeyEventType type) {
99 case kKeyDownEventType:
101 case kKeyUpEventType:
103 case kRawKeyDownEventType:
114 WebViewImpl::WebViewImpl(const std::string& id,
116 scoped_ptr<DevToolsClient> client)
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()) {}
129 WebViewImpl::~WebViewImpl() {}
131 std::string WebViewImpl::GetId() {
135 bool WebViewImpl::WasCrashed() {
136 return client_->WasCrashed();
139 Status WebViewImpl::ConnectIfNecessary() {
140 return client_->ConnectIfNecessary();
143 Status WebViewImpl::HandleReceivedEvents() {
144 return client_->HandleReceivedEvents();
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);
157 Status WebViewImpl::Reload() {
158 base::DictionaryValue params;
159 params.SetBoolean("ignoreCache", false);
160 return client_->SendCommand("Page.reload", params);
163 Status WebViewImpl::EvaluateScript(const std::string& frame,
164 const std::string& expression,
165 scoped_ptr<base::Value>* result) {
167 Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
169 if (status.IsError())
171 return internal::EvaluateScriptAndGetValue(
172 client_.get(), context_id, expression, 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) {
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])",
187 scoped_ptr<base::Value> temp_result;
188 Status status = EvaluateScript(frame, expression, &temp_result);
189 if (status.IsError())
192 return internal::ParseCallFunctionResult(*temp_result, 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);
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);
213 Status WebViewImpl::GetFrameByFunction(const std::string& frame,
214 const std::string& function,
215 const base::ListValue& args,
216 std::string* out_frame) {
218 Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
220 if (status.IsError())
224 status = internal::GetNodeIdFromFunction(
225 client_.get(), context_id, function, args, &found_node, &node_id);
226 if (status.IsError())
229 return Status(kNoSuchFrame);
230 return dom_tracker_->GetFrameIdForNode(node_id, out_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())
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())
263 Status WebViewImpl::DispatchTouchEvent(const TouchEvent& event) {
264 base::DictionaryValue params;
265 params.SetString("type", GetAsString(event.type));
266 scoped_ptr<base::ListValue> point_list(new base::ListValue);
267 scoped_ptr<base::DictionaryValue> point(new base::DictionaryValue);
268 point->SetString("state", GetPointStateString(event.type));
269 point->SetInteger("x", event.x);
270 point->SetInteger("y", event.y);
271 point_list->Set(0, point.release());
272 params.Set("touchPoints", point_list.release());
273 return client_->SendCommand("Input.dispatchTouchEvent", params);
276 Status WebViewImpl::DispatchTouchEvents(const std::list<TouchEvent>& events) {
277 for (std::list<TouchEvent>::const_iterator it = events.begin();
278 it != events.end(); ++it) {
279 Status status = DispatchTouchEvent(*it);
280 if (status.IsError())
286 Status WebViewImpl::DispatchKeyEvents(const std::list<KeyEvent>& events) {
287 for (std::list<KeyEvent>::const_iterator it = events.begin();
288 it != events.end(); ++it) {
289 base::DictionaryValue params;
290 params.SetString("type", GetAsString(it->type));
291 if (it->modifiers & kNumLockKeyModifierMask) {
292 params.SetBoolean("isKeypad", true);
293 params.SetInteger("modifiers",
294 it->modifiers & ~kNumLockKeyModifierMask);
296 params.SetInteger("modifiers", it->modifiers);
298 params.SetString("text", it->modified_text);
299 params.SetString("unmodifiedText", it->unmodified_text);
300 params.SetInteger("nativeVirtualKeyCode", it->key_code);
301 params.SetInteger("windowsVirtualKeyCode", it->key_code);
302 Status status = client_->SendCommand("Input.dispatchKeyEvent", params);
303 if (status.IsError())
309 Status WebViewImpl::GetCookies(scoped_ptr<base::ListValue>* cookies) {
310 base::DictionaryValue params;
311 scoped_ptr<base::DictionaryValue> result;
312 Status status = client_->SendCommandAndGetResult(
313 "Page.getCookies", params, &result);
314 if (status.IsError())
316 base::ListValue* cookies_tmp;
317 if (!result->GetList("cookies", &cookies_tmp))
318 return Status(kUnknownError, "DevTools didn't return cookies");
319 cookies->reset(cookies_tmp->DeepCopy());
323 Status WebViewImpl::DeleteCookie(const std::string& name,
324 const std::string& url) {
325 base::DictionaryValue params;
326 params.SetString("cookieName", name);
327 params.SetString("url", url);
328 return client_->SendCommand("Page.deleteCookie", params);
331 Status WebViewImpl::WaitForPendingNavigations(const std::string& frame_id,
332 const base::TimeDelta& timeout,
333 bool stop_load_on_timeout) {
334 VLOG(0) << "Waiting for pending navigations...";
335 Status status = client_->HandleEventsUntil(
336 base::Bind(&WebViewImpl::IsNotPendingNavigation,
337 base::Unretained(this),
340 if (status.code() == kTimeout && stop_load_on_timeout) {
341 VLOG(0) << "Timed out. Stopping navigation...";
342 scoped_ptr<base::Value> unused_value;
343 EvaluateScript(std::string(), "window.stop();", &unused_value);
344 Status new_status = client_->HandleEventsUntil(
345 base::Bind(&WebViewImpl::IsNotPendingNavigation, base::Unretained(this),
347 base::TimeDelta::FromSeconds(10));
348 if (new_status.IsError())
351 VLOG(0) << "Done waiting for pending navigations";
355 Status WebViewImpl::IsPendingNavigation(const std::string& frame_id,
357 return navigation_tracker_->IsPendingNavigation(frame_id, is_pending);
360 JavaScriptDialogManager* WebViewImpl::GetJavaScriptDialogManager() {
361 return dialog_manager_.get();
364 Status WebViewImpl::OverrideGeolocation(const Geoposition& geoposition) {
365 return geolocation_override_manager_->OverrideGeolocation(geoposition);
368 Status WebViewImpl::CaptureScreenshot(std::string* screenshot) {
369 base::DictionaryValue params;
370 scoped_ptr<base::DictionaryValue> result;
371 Status status = client_->SendCommandAndGetResult(
372 "Page.captureScreenshot", params, &result);
373 if (status.IsError())
375 if (!result->GetString("data", screenshot))
376 return Status(kUnknownError, "expected string 'data' in response");
380 Status WebViewImpl::SetFileInputFiles(
381 const std::string& frame,
382 const base::DictionaryValue& element,
383 const std::vector<base::FilePath>& files) {
384 base::ListValue file_list;
385 for (size_t i = 0; i < files.size(); ++i) {
386 if (!files[i].IsAbsolute()) {
387 return Status(kUnknownError,
388 "path is not absolute: " + files[i].AsUTF8Unsafe());
390 if (files[i].ReferencesParent()) {
391 return Status(kUnknownError,
392 "path is not canonical: " + files[i].AsUTF8Unsafe());
394 file_list.AppendString(files[i].value());
398 Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
400 if (status.IsError())
402 base::ListValue args;
403 args.Append(element.DeepCopy());
406 status = internal::GetNodeIdFromFunction(
407 client_.get(), context_id, "function(element) { return element; }",
408 args, &found_node, &node_id);
409 if (status.IsError())
412 return Status(kUnknownError, "no node ID for file input");
413 base::DictionaryValue params;
414 params.SetInteger("nodeId", node_id);
415 params.Set("files", file_list.DeepCopy());
416 return client_->SendCommand("DOM.setFileInputFiles", params);
419 Status WebViewImpl::TakeHeapSnapshot(scoped_ptr<base::Value>* snapshot) {
420 return heap_snapshot_taker_->TakeSnapshot(snapshot);
423 Status WebViewImpl::CallAsyncFunctionInternal(const std::string& frame,
424 const std::string& function,
425 const base::ListValue& args,
426 bool is_user_supplied,
427 const base::TimeDelta& timeout,
428 scoped_ptr<base::Value>* result) {
429 base::ListValue async_args;
430 async_args.AppendString("return (" + function + ").apply(null, arguments);");
431 async_args.Append(args.DeepCopy());
432 async_args.AppendBoolean(is_user_supplied);
433 async_args.AppendInteger(timeout.InMilliseconds());
434 scoped_ptr<base::Value> tmp;
435 Status status = CallFunction(
436 frame, kExecuteAsyncScriptScript, async_args, &tmp);
437 if (status.IsError())
440 const char* kDocUnloadError = "document unloaded while waiting for result";
441 std::string kQueryResult = base::StringPrintf(
443 " var info = document.$chrome_asyncScriptInfo;"
445 " return {status: %d, value: '%s'};"
446 " var result = info.result;"
448 " return {status: 0};"
449 " delete info.result;"
456 base::ListValue no_args;
457 scoped_ptr<base::Value> query_value;
458 Status status = CallFunction(frame, kQueryResult, no_args, &query_value);
459 if (status.IsError()) {
460 if (status.code() == kNoSuchFrame)
461 return Status(kJavaScriptError, kDocUnloadError);
465 base::DictionaryValue* result_info = NULL;
466 if (!query_value->GetAsDictionary(&result_info))
467 return Status(kUnknownError, "async result info is not a dictionary");
469 if (!result_info->GetInteger("status", &status_code))
470 return Status(kUnknownError, "async result info has no int 'status'");
471 if (status_code != kOk) {
473 result_info->GetString("value", &message);
474 return Status(static_cast<StatusCode>(status_code), message);
477 base::Value* value = NULL;
478 if (result_info->Get("value", &value)) {
479 result->reset(value->DeepCopy());
483 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
487 Status WebViewImpl::IsNotPendingNavigation(const std::string& frame_id,
488 bool* is_not_pending) {
491 navigation_tracker_->IsPendingNavigation(frame_id, &is_pending);
492 if (status.IsError())
494 // An alert may block the pending navigation.
495 if (is_pending && dialog_manager_->IsDialogOpen())
496 return Status(kUnexpectedAlertOpen);
498 *is_not_pending = !is_pending;
504 Status EvaluateScript(DevToolsClient* client,
506 const std::string& expression,
507 EvaluateScriptReturnType return_type,
508 scoped_ptr<base::DictionaryValue>* result) {
509 base::DictionaryValue params;
510 params.SetString("expression", expression);
512 params.SetInteger("contextId", context_id);
513 params.SetBoolean("returnByValue", return_type == ReturnByValue);
514 scoped_ptr<base::DictionaryValue> cmd_result;
515 Status status = client->SendCommandAndGetResult(
516 "Runtime.evaluate", params, &cmd_result);
517 if (status.IsError())
521 if (!cmd_result->GetBoolean("wasThrown", &was_thrown))
522 return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'");
524 std::string description = "unknown";
525 cmd_result->GetString("result.description", &description);
526 return Status(kUnknownError,
527 "Runtime.evaluate threw exception: " + description);
530 base::DictionaryValue* unscoped_result;
531 if (!cmd_result->GetDictionary("result", &unscoped_result))
532 return Status(kUnknownError, "evaluate missing dictionary 'result'");
533 result->reset(unscoped_result->DeepCopy());
537 Status EvaluateScriptAndGetObject(DevToolsClient* client,
539 const std::string& expression,
541 std::string* object_id) {
542 scoped_ptr<base::DictionaryValue> result;
543 Status status = EvaluateScript(client, context_id, expression, ReturnByObject,
545 if (status.IsError())
547 if (!result->HasKey("objectId")) {
551 if (!result->GetString("objectId", object_id))
552 return Status(kUnknownError, "evaluate has invalid 'objectId'");
557 Status EvaluateScriptAndGetValue(DevToolsClient* client,
559 const std::string& expression,
560 scoped_ptr<base::Value>* result) {
561 scoped_ptr<base::DictionaryValue> temp_result;
562 Status status = EvaluateScript(client, context_id, expression, ReturnByValue,
564 if (status.IsError())
568 if (!temp_result->GetString("type", &type))
569 return Status(kUnknownError, "Runtime.evaluate missing string 'type'");
571 if (type == "undefined") {
572 result->reset(base::Value::CreateNullValue());
575 if (!temp_result->Get("value", &value))
576 return Status(kUnknownError, "Runtime.evaluate missing 'value'");
577 result->reset(value->DeepCopy());
582 Status ParseCallFunctionResult(const base::Value& temp_result,
583 scoped_ptr<base::Value>* result) {
584 const base::DictionaryValue* dict;
585 if (!temp_result.GetAsDictionary(&dict))
586 return Status(kUnknownError, "call function result must be a dictionary");
588 if (!dict->GetInteger("status", &status_code)) {
589 return Status(kUnknownError,
590 "call function result missing int 'status'");
592 if (status_code != kOk) {
594 dict->GetString("value", &message);
595 return Status(static_cast<StatusCode>(status_code), message);
597 const base::Value* unscoped_value;
598 if (!dict->Get("value", &unscoped_value)) {
599 return Status(kUnknownError,
600 "call function result missing 'value'");
602 result->reset(unscoped_value->DeepCopy());
606 Status GetNodeIdFromFunction(DevToolsClient* client,
608 const std::string& function,
609 const base::ListValue& args,
613 base::JSONWriter::Write(&args, &json);
614 // TODO(zachconrad): Second null should be array of shadow host ids.
615 std::string expression = base::StringPrintf(
616 "(%s).apply(null, [null, %s, %s, true])",
622 std::string element_id;
623 Status status = internal::EvaluateScriptAndGetObject(
624 client, context_id, expression, &got_object, &element_id);
625 if (status.IsError())
632 scoped_ptr<base::DictionaryValue> cmd_result;
634 base::DictionaryValue params;
635 params.SetString("objectId", element_id);
636 status = client->SendCommandAndGetResult(
637 "DOM.requestNode", params, &cmd_result);
640 // Release the remote object before doing anything else.
641 base::DictionaryValue params;
642 params.SetString("objectId", element_id);
643 Status release_status =
644 client->SendCommand("Runtime.releaseObject", params);
645 if (release_status.IsError()) {
646 LOG(ERROR) << "Failed to release remote object: "
647 << release_status.message();
650 if (status.IsError())
653 if (!cmd_result->GetInteger("nodeId", node_id))
654 return Status(kUnknownError, "DOM.requestNode missing int 'nodeId'");
659 } // namespace internal