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/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"
32 Status GetContextIdForFrame(FrameTracker* tracker,
33 const std::string& frame,
39 Status status = tracker->GetContextIdForFrame(frame, context_id);
45 const char* GetAsString(MouseEventType type) {
47 case kPressedMouseEventType:
48 return "mousePressed";
49 case kReleasedMouseEventType:
50 return "mouseReleased";
51 case kMovedMouseEventType:
58 const char* GetAsString(TouchEventType type) {
71 const char* GetPointStateString(TouchEventType type) {
74 return "touchPressed";
76 return "touchReleased";
84 const char* GetAsString(MouseButton button) {
86 case kLeftMouseButton:
88 case kMiddleMouseButton:
90 case kRightMouseButton:
92 case kNoneMouseButton:
99 const char* GetAsString(KeyEventType type) {
101 case kKeyDownEventType:
103 case kKeyUpEventType:
105 case kRawKeyDownEventType:
116 WebViewImpl::WebViewImpl(const std::string& id,
117 const BrowserInfo* browser_info,
118 scoped_ptr<DevToolsClient> client,
119 const DeviceMetrics* device_metrics)
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(),
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()) {}
136 WebViewImpl::~WebViewImpl() {}
138 std::string WebViewImpl::GetId() {
142 bool WebViewImpl::WasCrashed() {
143 return client_->WasCrashed();
146 Status WebViewImpl::ConnectIfNecessary() {
147 return client_->ConnectIfNecessary();
150 Status WebViewImpl::HandleReceivedEvents() {
151 return client_->HandleReceivedEvents();
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);
164 Status WebViewImpl::Reload() {
165 base::DictionaryValue params;
166 params.SetBoolean("ignoreCache", false);
167 return client_->SendCommand("Page.reload", params);
170 Status WebViewImpl::EvaluateScript(const std::string& frame,
171 const std::string& expression,
172 scoped_ptr<base::Value>* result) {
174 Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
176 if (status.IsError())
178 return internal::EvaluateScriptAndGetValue(
179 client_.get(), context_id, expression, result);
182 Status WebViewImpl::CallFunction(const std::string& frame,
183 const std::string& function,
184 const base::ListValue& args,
185 scoped_ptr<base::Value>* result) {
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])",
194 scoped_ptr<base::Value> temp_result;
195 Status status = EvaluateScript(frame, expression, &temp_result);
196 if (status.IsError())
199 return internal::ParseCallFunctionResult(*temp_result, result);
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);
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);
220 Status WebViewImpl::GetFrameByFunction(const std::string& frame,
221 const std::string& function,
222 const base::ListValue& args,
223 std::string* out_frame) {
225 Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
227 if (status.IsError())
231 status = internal::GetNodeIdFromFunction(
232 client_.get(), context_id, function, args, &found_node, &node_id);
233 if (status.IsError())
236 return Status(kNoSuchFrame);
237 return dom_tracker_->GetFrameIdForNode(node_id, out_frame);
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())
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())
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);
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())
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);
303 params.SetInteger("modifiers", it->modifiers);
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())
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())
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());
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);
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),
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),
354 base::TimeDelta::FromSeconds(10));
355 if (new_status.IsError())
358 VLOG(0) << "Done waiting for pending navigations";
362 Status WebViewImpl::IsPendingNavigation(const std::string& frame_id,
364 return navigation_tracker_->IsPendingNavigation(frame_id, is_pending);
367 JavaScriptDialogManager* WebViewImpl::GetJavaScriptDialogManager() {
368 return dialog_manager_.get();
371 Status WebViewImpl::OverrideGeolocation(const Geoposition& geoposition) {
372 return geolocation_override_manager_->OverrideGeolocation(geoposition);
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())
382 if (!result->GetString("data", screenshot))
383 return Status(kUnknownError, "expected string 'data' in response");
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());
397 if (files[i].ReferencesParent()) {
398 return Status(kUnknownError,
399 "path is not canonical: " + files[i].AsUTF8Unsafe());
401 file_list.AppendString(files[i].value());
405 Status status = GetContextIdForFrame(frame_tracker_.get(), frame,
407 if (status.IsError())
409 base::ListValue args;
410 args.Append(element.DeepCopy());
413 status = internal::GetNodeIdFromFunction(
414 client_.get(), context_id, "function(element) { return element; }",
415 args, &found_node, &node_id);
416 if (status.IsError())
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);
426 Status WebViewImpl::TakeHeapSnapshot(scoped_ptr<base::Value>* snapshot) {
427 return heap_snapshot_taker_->TakeSnapshot(snapshot);
430 Status WebViewImpl::InitProfileInternal() {
431 base::DictionaryValue params;
433 // TODO: Remove Debugger.enable after Chrome 36 stable is released.
434 Status status_debug = client_->SendCommand("Debugger.enable", params);
436 if (status_debug.IsError())
439 Status status_profiler = client_->SendCommand("Profiler.enable", params);
441 if (status_profiler.IsError()) {
442 Status status_debugger = client_->SendCommand("Debugger.disable", params);
443 if (status_debugger.IsError())
444 return status_debugger;
446 return status_profiler;
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);
457 if (status_debug.IsError()) {
459 } else if (status_profiler.IsError()) {
460 return status_profiler;
466 Status WebViewImpl::StartProfile() {
467 Status status_init = InitProfileInternal();
469 if (status_init.IsError())
472 base::DictionaryValue params;
473 return client_->SendCommand("Profiler.start", params);
476 Status WebViewImpl::EndProfile(scoped_ptr<base::Value>* profile_data) {
477 base::DictionaryValue params;
478 scoped_ptr<base::DictionaryValue> profile_result;
480 Status status = client_->SendCommandAndGetResult(
481 "Profiler.stop", params, &profile_result);
483 if (status.IsError()) {
484 Status disable_profile_status = StopProfileInternal();
485 if (disable_profile_status.IsError()) {
486 return disable_profile_status;
492 *profile_data = profile_result.Pass();
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())
513 const char kDocUnloadError[] = "document unloaded while waiting for result";
514 std::string kQueryResult = base::StringPrintf(
516 " var info = document.$chrome_asyncScriptInfo;"
518 " return {status: %d, value: '%s'};"
519 " var result = info.result;"
521 " return {status: 0};"
522 " delete info.result;"
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);
538 base::DictionaryValue* result_info = NULL;
539 if (!query_value->GetAsDictionary(&result_info))
540 return Status(kUnknownError, "async result info is not a dictionary");
542 if (!result_info->GetInteger("status", &status_code))
543 return Status(kUnknownError, "async result info has no int 'status'");
544 if (status_code != kOk) {
546 result_info->GetString("value", &message);
547 return Status(static_cast<StatusCode>(status_code), message);
550 base::Value* value = NULL;
551 if (result_info->Get("value", &value)) {
552 result->reset(value->DeepCopy());
556 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
560 Status WebViewImpl::IsNotPendingNavigation(const std::string& frame_id,
561 bool* is_not_pending) {
564 navigation_tracker_->IsPendingNavigation(frame_id, &is_pending);
565 if (status.IsError())
567 // An alert may block the pending navigation.
568 if (is_pending && dialog_manager_->IsDialogOpen())
569 return Status(kUnexpectedAlertOpen);
571 *is_not_pending = !is_pending;
577 Status EvaluateScript(DevToolsClient* client,
579 const std::string& expression,
580 EvaluateScriptReturnType return_type,
581 scoped_ptr<base::DictionaryValue>* result) {
582 base::DictionaryValue params;
583 params.SetString("expression", expression);
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())
594 if (!cmd_result->GetBoolean("wasThrown", &was_thrown))
595 return Status(kUnknownError, "Runtime.evaluate missing 'wasThrown'");
597 std::string description = "unknown";
598 cmd_result->GetString("result.description", &description);
599 return Status(kUnknownError,
600 "Runtime.evaluate threw exception: " + description);
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());
610 Status EvaluateScriptAndGetObject(DevToolsClient* client,
612 const std::string& expression,
614 std::string* object_id) {
615 scoped_ptr<base::DictionaryValue> result;
616 Status status = EvaluateScript(client, context_id, expression, ReturnByObject,
618 if (status.IsError())
620 if (!result->HasKey("objectId")) {
624 if (!result->GetString("objectId", object_id))
625 return Status(kUnknownError, "evaluate has invalid 'objectId'");
630 Status EvaluateScriptAndGetValue(DevToolsClient* client,
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,
637 if (status.IsError())
641 if (!temp_result->GetString("type", &type))
642 return Status(kUnknownError, "Runtime.evaluate missing string 'type'");
644 if (type == "undefined") {
645 result->reset(base::Value::CreateNullValue());
648 if (!temp_result->Get("value", &value))
649 return Status(kUnknownError, "Runtime.evaluate missing 'value'");
650 result->reset(value->DeepCopy());
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");
661 if (!dict->GetInteger("status", &status_code)) {
662 return Status(kUnknownError,
663 "call function result missing int 'status'");
665 if (status_code != kOk) {
667 dict->GetString("value", &message);
668 return Status(static_cast<StatusCode>(status_code), message);
670 const base::Value* unscoped_value;
671 if (!dict->Get("value", &unscoped_value)) {
672 return Status(kUnknownError,
673 "call function result missing 'value'");
675 result->reset(unscoped_value->DeepCopy());
679 Status GetNodeIdFromFunction(DevToolsClient* client,
681 const std::string& function,
682 const base::ListValue& args,
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])",
695 std::string element_id;
696 Status status = internal::EvaluateScriptAndGetObject(
697 client, context_id, expression, &got_object, &element_id);
698 if (status.IsError())
705 scoped_ptr<base::DictionaryValue> cmd_result;
707 base::DictionaryValue params;
708 params.SetString("objectId", element_id);
709 status = client->SendCommandAndGetResult(
710 "DOM.requestNode", params, &cmd_result);
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();
723 if (status.IsError())
726 if (!cmd_result->GetInteger("nodeId", node_id))
727 return Status(kUnknownError, "DOM.requestNode missing int 'nodeId'");
732 } // namespace internal