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/window_commands.h"
10 #include "base/callback.h"
11 #include "base/strings/string_number_conversions.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/basic_types.h"
17 #include "chrome/test/chromedriver/chrome/automation_extension.h"
18 #include "chrome/test/chromedriver/chrome/chrome.h"
19 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
20 #include "chrome/test/chromedriver/chrome/devtools_client.h"
21 #include "chrome/test/chromedriver/chrome/geoposition.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/status.h"
25 #include "chrome/test/chromedriver/chrome/ui_events.h"
26 #include "chrome/test/chromedriver/chrome/web_view.h"
27 #include "chrome/test/chromedriver/element_util.h"
28 #include "chrome/test/chromedriver/session.h"
29 #include "chrome/test/chromedriver/util.h"
33 Status GetMouseButton(const base::DictionaryValue& params,
34 MouseButton* button) {
36 if (!params.GetInteger("button", &button_num)) {
37 button_num = 0; // Default to left mouse button.
38 } else if (button_num < 0 || button_num > 2) {
39 return Status(kUnknownError,
40 base::StringPrintf("invalid button: %d", button_num));
42 *button = static_cast<MouseButton>(button_num);
46 Status GetUrl(WebView* web_view, const std::string& frame, std::string* url) {
47 scoped_ptr<base::Value> value;
49 Status status = web_view->CallFunction(
50 frame, "function() { return document.URL; }", args, &value);
53 if (!value->GetAsString(url))
54 return Status(kUnknownError, "javascript failed to return the url");
59 Cookie(const std::string& name,
60 const std::string& value,
61 const std::string& domain,
62 const std::string& path,
66 : name(name), value(value), domain(domain), path(path), expiry(expiry),
67 secure(secure), session(session) {}
78 base::DictionaryValue* CreateDictionaryFrom(const Cookie& cookie) {
79 base::DictionaryValue* dict = new base::DictionaryValue();
80 dict->SetString("name", cookie.name);
81 dict->SetString("value", cookie.value);
82 if (!cookie.domain.empty())
83 dict->SetString("domain", cookie.domain);
84 if (!cookie.path.empty())
85 dict->SetString("path", cookie.path);
87 dict->SetDouble("expiry", cookie.expiry);
88 dict->SetBoolean("secure", cookie.secure);
92 Status GetVisibleCookies(WebView* web_view,
93 std::list<Cookie>* cookies) {
94 scoped_ptr<base::ListValue> internal_cookies;
95 Status status = web_view->GetCookies(&internal_cookies);
98 std::list<Cookie> cookies_tmp;
99 for (size_t i = 0; i < internal_cookies->GetSize(); ++i) {
100 base::DictionaryValue* cookie_dict;
101 if (!internal_cookies->GetDictionary(i, &cookie_dict))
102 return Status(kUnknownError, "DevTools returns a non-dictionary cookie");
105 cookie_dict->GetString("name", &name);
107 cookie_dict->GetString("value", &value);
109 cookie_dict->GetString("domain", &domain);
111 cookie_dict->GetString("path", &path);
113 cookie_dict->GetDouble("expires", &expiry);
114 expiry /= 1000; // Convert from millisecond to second.
115 bool session = false;
116 cookie_dict->GetBoolean("session", &session);
118 cookie_dict->GetBoolean("secure", &secure);
120 cookies_tmp.push_back(
121 Cookie(name, value, domain, path, expiry, secure, session));
123 cookies->swap(cookies_tmp);
127 Status ScrollCoordinateInToView(
128 Session* session, WebView* web_view, int x, int y, int* offset_x,
130 scoped_ptr<base::Value> value;
131 base::ListValue args;
132 args.AppendInteger(x);
133 args.AppendInteger(y);
134 Status status = web_view->CallFunction(
137 " if (x < window.pageXOffset ||"
138 " x >= window.pageXOffset + window.innerWidth ||"
139 " y < window.pageYOffset ||"
140 " y >= window.pageYOffset + window.innerHeight) {"
141 " window.scrollTo(x - window.innerWidth/2, y - window.innerHeight/2);"
144 " view_x: Math.floor(window.pageXOffset),"
145 " view_y: Math.floor(window.pageYOffset),"
146 " view_width: Math.floor(window.innerWidth),"
147 " view_height: Math.floor(window.innerHeight)};"
153 base::DictionaryValue* view_attrib;
154 value->GetAsDictionary(&view_attrib);
155 int view_x, view_y, view_width, view_height;
156 view_attrib->GetInteger("view_x", &view_x);
157 view_attrib->GetInteger("view_y", &view_y);
158 view_attrib->GetInteger("view_width", &view_width);
159 view_attrib->GetInteger("view_height", &view_height);
160 *offset_x = x - view_x;
161 *offset_y = y - view_y;
162 if (*offset_x < 0 || *offset_x >= view_width || *offset_y < 0 ||
163 *offset_y >= view_height)
164 return Status(kUnknownError, "Failed to scroll coordinate into view");
168 Status ExecuteTouchEvent(
169 Session* session, WebView* web_view, TouchEventType type,
170 const base::DictionaryValue& params) {
172 if (!params.GetInteger("x", &x))
173 return Status(kUnknownError, "'x' must be an integer");
174 if (!params.GetInteger("y", &y))
175 return Status(kUnknownError, "'y' must be an integer");
178 Status status = ScrollCoordinateInToView(
179 session, web_view, x, y, &relative_x, &relative_y);
182 std::list<TouchEvent> events;
184 TouchEvent(type, relative_x, relative_y));
185 return web_view->DispatchTouchEvents(events);
190 Status ExecuteWindowCommand(
191 const WindowCommand& command,
193 const base::DictionaryValue& params,
194 scoped_ptr<base::Value>* value) {
195 WebView* web_view = NULL;
196 Status status = session->GetTargetWindow(&web_view);
197 if (status.IsError())
200 status = web_view->ConnectIfNecessary();
201 if (status.IsError())
204 status = web_view->HandleReceivedEvents();
205 if (status.IsError())
208 if (web_view->GetJavaScriptDialogManager()->IsDialogOpen())
209 return Status(kUnexpectedAlertOpen);
211 Status nav_status(kOk);
212 for (int attempt = 0; attempt < 2; attempt++) {
214 if (status.code() == kNoSuchExecutionContext)
215 // Switch to main frame and retry command if subframe no longer exists.
216 session->SwitchToTopFrame();
220 nav_status = web_view->WaitForPendingNavigations(
221 session->GetCurrentFrameId(), session->page_load_timeout, true);
222 if (nav_status.IsError())
225 status = command.Run(session, web_view, params, value);
228 nav_status = web_view->WaitForPendingNavigations(
229 session->GetCurrentFrameId(), session->page_load_timeout, true);
231 if (status.IsOk() && nav_status.IsError() &&
232 nav_status.code() != kUnexpectedAlertOpen)
234 if (status.code() == kUnexpectedAlertOpen)
242 const base::DictionaryValue& params,
243 scoped_ptr<base::Value>* value) {
245 if (!params.GetString("url", &url))
246 return Status(kUnknownError, "'url' must be a string");
247 return web_view->Load(url);
250 Status ExecuteExecuteScript(
253 const base::DictionaryValue& params,
254 scoped_ptr<base::Value>* value) {
256 if (!params.GetString("script", &script))
257 return Status(kUnknownError, "'script' must be a string");
258 if (script == ":takeHeapSnapshot") {
259 return web_view->TakeHeapSnapshot(value);
261 const base::ListValue* args;
262 if (!params.GetList("args", &args))
263 return Status(kUnknownError, "'args' must be a list");
265 return web_view->CallFunction(session->GetCurrentFrameId(),
266 "function(){" + script + "}", *args, value);
270 Status ExecuteExecuteAsyncScript(
273 const base::DictionaryValue& params,
274 scoped_ptr<base::Value>* value) {
276 if (!params.GetString("script", &script))
277 return Status(kUnknownError, "'script' must be a string");
278 const base::ListValue* args;
279 if (!params.GetList("args", &args))
280 return Status(kUnknownError, "'args' must be a list");
282 return web_view->CallUserAsyncFunction(
283 session->GetCurrentFrameId(), "function(){" + script + "}", *args,
284 session->script_timeout, value);
287 Status ExecuteSwitchToFrame(
290 const base::DictionaryValue& params,
291 scoped_ptr<base::Value>* value) {
292 const base::Value* id;
293 if (!params.Get("id", &id))
294 return Status(kUnknownError, "missing 'id'");
296 if (id->IsType(base::Value::TYPE_NULL)) {
297 session->SwitchToTopFrame();
302 base::ListValue args;
303 const base::DictionaryValue* id_dict;
304 if (id->GetAsDictionary(&id_dict)) {
305 script = "function(elem) { return elem; }";
306 args.Append(id_dict->DeepCopy());
310 " return document.evaluate(xpath, document, null, "
311 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
313 std::string xpath = "(/html/body//iframe|/html/frameset/frame)";
314 std::string id_string;
316 if (id->GetAsString(&id_string)) {
317 xpath += base::StringPrintf(
318 "[@name=\"%s\" or @id=\"%s\"]", id_string.c_str(), id_string.c_str());
319 } else if (id->GetAsInteger(&id_int)) {
320 xpath += base::StringPrintf("[%d]", id_int + 1);
322 return Status(kUnknownError, "invalid 'id'");
324 args.Append(new base::StringValue(xpath));
327 Status status = web_view->GetFrameByFunction(
328 session->GetCurrentFrameId(), script, args, &frame);
329 if (status.IsError())
332 scoped_ptr<base::Value> result;
333 status = web_view->CallFunction(
334 session->GetCurrentFrameId(), script, args, &result);
335 if (status.IsError())
337 const base::DictionaryValue* element;
338 if (!result->GetAsDictionary(&element))
339 return Status(kUnknownError, "fail to locate the sub frame element");
341 std::string chrome_driver_id = GenerateId();
342 const char* kSetFrameIdentifier =
343 "function(frame, id) {"
344 " frame.setAttribute('cd_frame_id_', id);"
346 base::ListValue new_args;
347 new_args.Append(element->DeepCopy());
348 new_args.AppendString(chrome_driver_id);
350 status = web_view->CallFunction(
351 session->GetCurrentFrameId(), kSetFrameIdentifier, new_args, &result);
352 if (status.IsError())
354 session->SwitchToSubFrame(frame, chrome_driver_id);
358 Status ExecuteSwitchToParentFrame(
361 const base::DictionaryValue& params,
362 scoped_ptr<base::Value>* value) {
363 session->SwitchToParentFrame();
367 Status ExecuteGetTitle(
370 const base::DictionaryValue& params,
371 scoped_ptr<base::Value>* value) {
372 const char* kGetTitleScript =
374 " if (document.title)"
375 " return document.title;"
377 " return document.URL;"
379 base::ListValue args;
380 return web_view->CallFunction(std::string(), kGetTitleScript, args, value);
383 Status ExecuteGetPageSource(
386 const base::DictionaryValue& params,
387 scoped_ptr<base::Value>* value) {
388 const char* kGetPageSource =
390 " return new XMLSerializer().serializeToString(document);"
392 base::ListValue args;
393 return web_view->CallFunction(
394 session->GetCurrentFrameId(), kGetPageSource, args, value);
397 Status ExecuteFindElement(
401 const base::DictionaryValue& params,
402 scoped_ptr<base::Value>* value) {
403 return FindElement(interval_ms, true, NULL, session, web_view, params, value);
406 Status ExecuteFindElements(
410 const base::DictionaryValue& params,
411 scoped_ptr<base::Value>* value) {
413 interval_ms, false, NULL, session, web_view, params, value);
416 Status ExecuteGetCurrentUrl(
419 const base::DictionaryValue& params,
420 scoped_ptr<base::Value>* value) {
422 Status status = GetUrl(web_view, session->GetCurrentFrameId(), &url);
423 if (status.IsError())
425 value->reset(new base::StringValue(url));
429 Status ExecuteGoBack(
432 const base::DictionaryValue& params,
433 scoped_ptr<base::Value>* value) {
434 return web_view->EvaluateScript(
435 std::string(), "window.history.back();", value);
438 Status ExecuteGoForward(
441 const base::DictionaryValue& params,
442 scoped_ptr<base::Value>* value) {
443 return web_view->EvaluateScript(
444 std::string(), "window.history.forward();", value);
447 Status ExecuteRefresh(
450 const base::DictionaryValue& params,
451 scoped_ptr<base::Value>* value) {
452 return web_view->Reload();
455 Status ExecuteMouseMoveTo(
458 const base::DictionaryValue& params,
459 scoped_ptr<base::Value>* value) {
460 std::string element_id;
461 bool has_element = params.GetString("element", &element_id);
464 bool has_offset = params.GetInteger("xoffset", &x_offset) &&
465 params.GetInteger("yoffset", &y_offset);
466 if (!has_element && !has_offset)
467 return Status(kUnknownError, "at least an element or offset should be set");
471 Status status = ScrollElementIntoView(
472 session, web_view, element_id, &location);
473 if (status.IsError())
476 location = session->mouse_position;
480 location.Offset(x_offset, y_offset);
483 Status status = GetElementSize(session, web_view, element_id, &size);
484 if (status.IsError())
486 location.Offset(size.width / 2, size.height / 2);
489 std::list<MouseEvent> events;
491 MouseEvent(kMovedMouseEventType, kNoneMouseButton,
492 location.x, location.y, session->sticky_modifiers, 0));
494 web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
496 session->mouse_position = location;
500 Status ExecuteMouseClick(
503 const base::DictionaryValue& params,
504 scoped_ptr<base::Value>* value) {
506 Status status = GetMouseButton(params, &button);
507 if (status.IsError())
509 std::list<MouseEvent> events;
511 MouseEvent(kPressedMouseEventType, button,
512 session->mouse_position.x, session->mouse_position.y,
513 session->sticky_modifiers, 1));
515 MouseEvent(kReleasedMouseEventType, button,
516 session->mouse_position.x, session->mouse_position.y,
517 session->sticky_modifiers, 1));
518 return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
521 Status ExecuteMouseButtonDown(
524 const base::DictionaryValue& params,
525 scoped_ptr<base::Value>* value) {
527 Status status = GetMouseButton(params, &button);
528 if (status.IsError())
530 std::list<MouseEvent> events;
532 MouseEvent(kPressedMouseEventType, button,
533 session->mouse_position.x, session->mouse_position.y,
534 session->sticky_modifiers, 1));
535 return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
538 Status ExecuteMouseButtonUp(
541 const base::DictionaryValue& params,
542 scoped_ptr<base::Value>* value) {
544 Status status = GetMouseButton(params, &button);
545 if (status.IsError())
547 std::list<MouseEvent> events;
549 MouseEvent(kReleasedMouseEventType, button,
550 session->mouse_position.x, session->mouse_position.y,
551 session->sticky_modifiers, 1));
552 return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
555 Status ExecuteMouseDoubleClick(
558 const base::DictionaryValue& params,
559 scoped_ptr<base::Value>* value) {
561 Status status = GetMouseButton(params, &button);
562 if (status.IsError())
564 std::list<MouseEvent> events;
566 MouseEvent(kPressedMouseEventType, button,
567 session->mouse_position.x, session->mouse_position.y,
568 session->sticky_modifiers, 2));
570 MouseEvent(kReleasedMouseEventType, button,
571 session->mouse_position.x, session->mouse_position.y,
572 session->sticky_modifiers, 2));
573 return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
576 Status ExecuteTouchDown(
579 const base::DictionaryValue& params,
580 scoped_ptr<base::Value>* value) {
581 return ExecuteTouchEvent(session, web_view, kTouchStart, params);
584 Status ExecuteTouchUp(
587 const base::DictionaryValue& params,
588 scoped_ptr<base::Value>* value) {
589 return ExecuteTouchEvent(session, web_view, kTouchEnd, params);
592 Status ExecuteTouchMove(
595 const base::DictionaryValue& params,
596 scoped_ptr<base::Value>* value) {
597 return ExecuteTouchEvent(session, web_view, kTouchMove, params);
600 Status ExecuteGetActiveElement(
603 const base::DictionaryValue& params,
604 scoped_ptr<base::Value>* value) {
605 return GetActiveElement(session, web_view, value);
608 Status ExecuteSendKeysToActiveElement(
611 const base::DictionaryValue& params,
612 scoped_ptr<base::Value>* value) {
613 const base::ListValue* key_list;
614 if (!params.GetList("value", &key_list))
615 return Status(kUnknownError, "'value' must be a list");
616 return SendKeysOnWindow(
617 web_view, key_list, false, &session->sticky_modifiers);
620 Status ExecuteGetAppCacheStatus(
623 const base::DictionaryValue& params,
624 scoped_ptr<base::Value>* value) {
625 return web_view->EvaluateScript(
626 session->GetCurrentFrameId(),
627 "applicationCache.status",
631 Status ExecuteIsBrowserOnline(
634 const base::DictionaryValue& params,
635 scoped_ptr<base::Value>* value) {
636 return web_view->EvaluateScript(
637 session->GetCurrentFrameId(),
642 Status ExecuteGetStorageItem(
646 const base::DictionaryValue& params,
647 scoped_ptr<base::Value>* value) {
649 if (!params.GetString("key", &key))
650 return Status(kUnknownError, "'key' must be a string");
651 base::ListValue args;
652 args.Append(new base::StringValue(key));
653 return web_view->CallFunction(
654 session->GetCurrentFrameId(),
655 base::StringPrintf("function(key) { return %s[key]; }", storage),
660 Status ExecuteGetStorageKeys(
664 const base::DictionaryValue& params,
665 scoped_ptr<base::Value>* value) {
666 const char script[] =
668 "for (var key in %s) {"
672 return web_view->EvaluateScript(
673 session->GetCurrentFrameId(),
674 base::StringPrintf(script, storage),
678 Status ExecuteSetStorageItem(
682 const base::DictionaryValue& params,
683 scoped_ptr<base::Value>* value) {
685 if (!params.GetString("key", &key))
686 return Status(kUnknownError, "'key' must be a string");
687 std::string storage_value;
688 if (!params.GetString("value", &storage_value))
689 return Status(kUnknownError, "'value' must be a string");
690 base::ListValue args;
691 args.Append(new base::StringValue(key));
692 args.Append(new base::StringValue(storage_value));
693 return web_view->CallFunction(
694 session->GetCurrentFrameId(),
695 base::StringPrintf("function(key, value) { %s[key] = value; }", storage),
700 Status ExecuteRemoveStorageItem(
704 const base::DictionaryValue& params,
705 scoped_ptr<base::Value>* value) {
707 if (!params.GetString("key", &key))
708 return Status(kUnknownError, "'key' must be a string");
709 base::ListValue args;
710 args.Append(new base::StringValue(key));
711 return web_view->CallFunction(
712 session->GetCurrentFrameId(),
713 base::StringPrintf("function(key) { %s.removeItem(key) }", storage),
718 Status ExecuteClearStorage(
722 const base::DictionaryValue& params,
723 scoped_ptr<base::Value>* value) {
724 return web_view->EvaluateScript(
725 session->GetCurrentFrameId(),
726 base::StringPrintf("%s.clear()", storage),
730 Status ExecuteGetStorageSize(
734 const base::DictionaryValue& params,
735 scoped_ptr<base::Value>* value) {
736 return web_view->EvaluateScript(
737 session->GetCurrentFrameId(),
738 base::StringPrintf("%s.length", storage),
742 Status ExecuteScreenshot(
745 const base::DictionaryValue& params,
746 scoped_ptr<base::Value>* value) {
747 Status status = session->chrome->ActivateWebView(web_view->GetId());
748 if (status.IsError())
751 std::string screenshot;
752 if (session->chrome->GetAsDesktop() && !session->force_devtools_screenshot) {
753 AutomationExtension* extension = NULL;
755 session->chrome->GetAsDesktop()->GetAutomationExtension(&extension);
756 if (status.IsError())
758 status = extension->CaptureScreenshot(&screenshot);
759 // If the screenshot was forbidden, fallback to DevTools.
760 if (status.code() == kForbidden)
761 status = web_view->CaptureScreenshot(&screenshot);
763 status = web_view->CaptureScreenshot(&screenshot);
765 if (status.IsError())
768 value->reset(new base::StringValue(screenshot));
772 Status ExecuteGetCookies(
775 const base::DictionaryValue& params,
776 scoped_ptr<base::Value>* value) {
777 std::list<Cookie> cookies;
778 Status status = GetVisibleCookies(web_view, &cookies);
779 if (status.IsError())
781 scoped_ptr<base::ListValue> cookie_list(new base::ListValue());
782 for (std::list<Cookie>::const_iterator it = cookies.begin();
783 it != cookies.end(); ++it) {
784 cookie_list->Append(CreateDictionaryFrom(*it));
786 value->reset(cookie_list.release());
790 Status ExecuteAddCookie(
793 const base::DictionaryValue& params,
794 scoped_ptr<base::Value>* value) {
795 const base::DictionaryValue* cookie;
796 if (!params.GetDictionary("cookie", &cookie))
797 return Status(kUnknownError, "missing 'cookie'");
798 base::ListValue args;
799 args.Append(cookie->DeepCopy());
800 scoped_ptr<base::Value> result;
801 return web_view->CallFunction(
802 session->GetCurrentFrameId(), kAddCookieScript, args, &result);
805 Status ExecuteDeleteCookie(
808 const base::DictionaryValue& params,
809 scoped_ptr<base::Value>* value) {
811 if (!params.GetString("name", &name))
812 return Status(kUnknownError, "missing 'name'");
813 base::DictionaryValue params_url;
814 scoped_ptr<base::Value> value_url;
816 Status status = GetUrl(web_view, session->GetCurrentFrameId(), &url);
817 if (status.IsError())
819 return web_view->DeleteCookie(name, url);
822 Status ExecuteDeleteAllCookies(
825 const base::DictionaryValue& params,
826 scoped_ptr<base::Value>* value) {
827 std::list<Cookie> cookies;
828 Status status = GetVisibleCookies(web_view, &cookies);
829 if (status.IsError())
832 if (!cookies.empty()) {
833 base::DictionaryValue params_url;
834 scoped_ptr<base::Value> value_url;
836 status = GetUrl(web_view, session->GetCurrentFrameId(), &url);
837 if (status.IsError())
839 for (std::list<Cookie>::const_iterator it = cookies.begin();
840 it != cookies.end(); ++it) {
841 status = web_view->DeleteCookie(it->name, url);
842 if (status.IsError())
850 Status ExecuteSetLocation(
853 const base::DictionaryValue& params,
854 scoped_ptr<base::Value>* value) {
855 const base::DictionaryValue* location = NULL;
856 Geoposition geoposition;
857 if (!params.GetDictionary("location", &location) ||
858 !location->GetDouble("latitude", &geoposition.latitude) ||
859 !location->GetDouble("longitude", &geoposition.longitude))
860 return Status(kUnknownError, "missing or invalid 'location'");
861 if (location->HasKey("accuracy") &&
862 !location->GetDouble("accuracy", &geoposition.accuracy)) {
863 return Status(kUnknownError, "invalid 'accuracy'");
865 // |accuracy| is not part of the WebDriver spec yet, so if it is not given
866 // default to 100 meters accuracy.
867 geoposition.accuracy = 100;
870 Status status = web_view->OverrideGeolocation(geoposition);
872 session->overridden_geoposition.reset(new Geoposition(geoposition));
876 Status ExecuteTakeHeapSnapshot(
879 const base::DictionaryValue& params,
880 scoped_ptr<base::Value>* value) {
881 return web_view->TakeHeapSnapshot(value);