// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/test/chromedriver/window_commands.h" #include <list> #include <string> #include "base/callback.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/values.h" #include "chrome/test/chromedriver/basic_types.h" #include "chrome/test/chromedriver/chrome/automation_extension.h" #include "chrome/test/chromedriver/chrome/chrome.h" #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h" #include "chrome/test/chromedriver/chrome/devtools_client.h" #include "chrome/test/chromedriver/chrome/geoposition.h" #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h" #include "chrome/test/chromedriver/chrome/js.h" #include "chrome/test/chromedriver/chrome/status.h" #include "chrome/test/chromedriver/chrome/ui_events.h" #include "chrome/test/chromedriver/chrome/web_view.h" #include "chrome/test/chromedriver/element_util.h" #include "chrome/test/chromedriver/session.h" #include "chrome/test/chromedriver/util.h" namespace { Status GetMouseButton(const base::DictionaryValue& params, MouseButton* button) { int button_num; if (!params.GetInteger("button", &button_num)) { button_num = 0; // Default to left mouse button. } else if (button_num < 0 || button_num > 2) { return Status(kUnknownError, base::StringPrintf("invalid button: %d", button_num)); } *button = static_cast<MouseButton>(button_num); return Status(kOk); } Status GetUrl(WebView* web_view, const std::string& frame, std::string* url) { scoped_ptr<base::Value> value; base::ListValue args; Status status = web_view->CallFunction( frame, "function() { return document.URL; }", args, &value); if (status.IsError()) return status; if (!value->GetAsString(url)) return Status(kUnknownError, "javascript failed to return the url"); return Status(kOk); } struct Cookie { Cookie(const std::string& name, const std::string& value, const std::string& domain, const std::string& path, double expiry, bool secure, bool session) : name(name), value(value), domain(domain), path(path), expiry(expiry), secure(secure), session(session) {} std::string name; std::string value; std::string domain; std::string path; double expiry; bool secure; bool session; }; base::DictionaryValue* CreateDictionaryFrom(const Cookie& cookie) { base::DictionaryValue* dict = new base::DictionaryValue(); dict->SetString("name", cookie.name); dict->SetString("value", cookie.value); if (!cookie.domain.empty()) dict->SetString("domain", cookie.domain); if (!cookie.path.empty()) dict->SetString("path", cookie.path); if (!cookie.session) dict->SetDouble("expiry", cookie.expiry); dict->SetBoolean("secure", cookie.secure); return dict; } Status GetVisibleCookies(WebView* web_view, std::list<Cookie>* cookies) { scoped_ptr<base::ListValue> internal_cookies; Status status = web_view->GetCookies(&internal_cookies); if (status.IsError()) return status; std::list<Cookie> cookies_tmp; for (size_t i = 0; i < internal_cookies->GetSize(); ++i) { base::DictionaryValue* cookie_dict; if (!internal_cookies->GetDictionary(i, &cookie_dict)) return Status(kUnknownError, "DevTools returns a non-dictionary cookie"); std::string name; cookie_dict->GetString("name", &name); std::string value; cookie_dict->GetString("value", &value); std::string domain; cookie_dict->GetString("domain", &domain); std::string path; cookie_dict->GetString("path", &path); double expiry = 0; cookie_dict->GetDouble("expires", &expiry); expiry /= 1000; // Convert from millisecond to second. bool session = false; cookie_dict->GetBoolean("session", &session); bool secure = false; cookie_dict->GetBoolean("secure", &secure); cookies_tmp.push_back( Cookie(name, value, domain, path, expiry, secure, session)); } cookies->swap(cookies_tmp); return Status(kOk); } Status ScrollCoordinateInToView( Session* session, WebView* web_view, int x, int y, int* offset_x, int* offset_y) { scoped_ptr<base::Value> value; base::ListValue args; args.AppendInteger(x); args.AppendInteger(y); Status status = web_view->CallFunction( std::string(), "function(x, y) {" " if (x < window.pageXOffset ||" " x >= window.pageXOffset + window.innerWidth ||" " y < window.pageYOffset ||" " y >= window.pageYOffset + window.innerHeight) {" " window.scrollTo(x - window.innerWidth/2, y - window.innerHeight/2);" " }" " return {" " view_x: Math.floor(window.pageXOffset)," " view_y: Math.floor(window.pageYOffset)," " view_width: Math.floor(window.innerWidth)," " view_height: Math.floor(window.innerHeight)};" "}", args, &value); if (!status.IsOk()) return status; base::DictionaryValue* view_attrib; value->GetAsDictionary(&view_attrib); int view_x, view_y, view_width, view_height; view_attrib->GetInteger("view_x", &view_x); view_attrib->GetInteger("view_y", &view_y); view_attrib->GetInteger("view_width", &view_width); view_attrib->GetInteger("view_height", &view_height); *offset_x = x - view_x; *offset_y = y - view_y; if (*offset_x < 0 || *offset_x >= view_width || *offset_y < 0 || *offset_y >= view_height) return Status(kUnknownError, "Failed to scroll coordinate into view"); return Status(kOk); } Status ExecuteTouchEvent( Session* session, WebView* web_view, TouchEventType type, const base::DictionaryValue& params) { int x, y; if (!params.GetInteger("x", &x)) return Status(kUnknownError, "'x' must be an integer"); if (!params.GetInteger("y", &y)) return Status(kUnknownError, "'y' must be an integer"); int relative_x = x; int relative_y = y; Status status = ScrollCoordinateInToView( session, web_view, x, y, &relative_x, &relative_y); if (!status.IsOk()) return status; std::list<TouchEvent> events; events.push_back( TouchEvent(type, relative_x, relative_y)); return web_view->DispatchTouchEvents(events); } } // namespace Status ExecuteWindowCommand( const WindowCommand& command, Session* session, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { WebView* web_view = NULL; Status status = session->GetTargetWindow(&web_view); if (status.IsError()) return status; status = web_view->ConnectIfNecessary(); if (status.IsError()) return status; status = web_view->HandleReceivedEvents(); if (status.IsError()) return status; if (web_view->GetJavaScriptDialogManager()->IsDialogOpen()) return Status(kUnexpectedAlertOpen); Status nav_status(kOk); for (int attempt = 0; attempt < 2; attempt++) { if (attempt == 1) { if (status.code() == kNoSuchExecutionContext) // Switch to main frame and retry command if subframe no longer exists. session->SwitchToTopFrame(); else break; } nav_status = web_view->WaitForPendingNavigations( session->GetCurrentFrameId(), session->page_load_timeout, true); if (nav_status.IsError()) return nav_status; status = command.Run(session, web_view, params, value); } nav_status = web_view->WaitForPendingNavigations( session->GetCurrentFrameId(), session->page_load_timeout, true); if (status.IsOk() && nav_status.IsError() && nav_status.code() != kUnexpectedAlertOpen) return nav_status; if (status.code() == kUnexpectedAlertOpen) return Status(kOk); return status; } Status ExecuteGet( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string url; if (!params.GetString("url", &url)) return Status(kUnknownError, "'url' must be a string"); return web_view->Load(url); } Status ExecuteExecuteScript( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string script; if (!params.GetString("script", &script)) return Status(kUnknownError, "'script' must be a string"); if (script == ":takeHeapSnapshot") { return web_view->TakeHeapSnapshot(value); } else if (script == ":startProfile") { return web_view->StartProfile(); } else if (script == ":endProfile") { return web_view->EndProfile(value); } else { const base::ListValue* args; if (!params.GetList("args", &args)) return Status(kUnknownError, "'args' must be a list"); return web_view->CallFunction(session->GetCurrentFrameId(), "function(){" + script + "}", *args, value); } } Status ExecuteExecuteAsyncScript( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string script; if (!params.GetString("script", &script)) return Status(kUnknownError, "'script' must be a string"); const base::ListValue* args; if (!params.GetList("args", &args)) return Status(kUnknownError, "'args' must be a list"); return web_view->CallUserAsyncFunction( session->GetCurrentFrameId(), "function(){" + script + "}", *args, session->script_timeout, value); } Status ExecuteSwitchToFrame( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { const base::Value* id; if (!params.Get("id", &id)) return Status(kUnknownError, "missing 'id'"); if (id->IsType(base::Value::TYPE_NULL)) { session->SwitchToTopFrame(); return Status(kOk); } std::string script; base::ListValue args; const base::DictionaryValue* id_dict; if (id->GetAsDictionary(&id_dict)) { script = "function(elem) { return elem; }"; args.Append(id_dict->DeepCopy()); } else { script = "function(xpath) {" " return document.evaluate(xpath, document, null, " " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" "}"; std::string xpath = "(/html/body//iframe|/html/frameset/frame)"; std::string id_string; int id_int; if (id->GetAsString(&id_string)) { xpath += base::StringPrintf( "[@name=\"%s\" or @id=\"%s\"]", id_string.c_str(), id_string.c_str()); } else if (id->GetAsInteger(&id_int)) { xpath += base::StringPrintf("[%d]", id_int + 1); } else { return Status(kUnknownError, "invalid 'id'"); } args.Append(new base::StringValue(xpath)); } std::string frame; Status status = web_view->GetFrameByFunction( session->GetCurrentFrameId(), script, args, &frame); if (status.IsError()) return status; scoped_ptr<base::Value> result; status = web_view->CallFunction( session->GetCurrentFrameId(), script, args, &result); if (status.IsError()) return status; const base::DictionaryValue* element; if (!result->GetAsDictionary(&element)) return Status(kUnknownError, "fail to locate the sub frame element"); std::string chrome_driver_id = GenerateId(); const char* kSetFrameIdentifier = "function(frame, id) {" " frame.setAttribute('cd_frame_id_', id);" "}"; base::ListValue new_args; new_args.Append(element->DeepCopy()); new_args.AppendString(chrome_driver_id); result.reset(NULL); status = web_view->CallFunction( session->GetCurrentFrameId(), kSetFrameIdentifier, new_args, &result); if (status.IsError()) return status; session->SwitchToSubFrame(frame, chrome_driver_id); return Status(kOk); } Status ExecuteSwitchToParentFrame( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { session->SwitchToParentFrame(); return Status(kOk); } Status ExecuteGetTitle( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { const char* kGetTitleScript = "function() {" " if (document.title)" " return document.title;" " else" " return document.URL;" "}"; base::ListValue args; return web_view->CallFunction(std::string(), kGetTitleScript, args, value); } Status ExecuteGetPageSource( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { const char* kGetPageSource = "function() {" " return new XMLSerializer().serializeToString(document);" "}"; base::ListValue args; return web_view->CallFunction( session->GetCurrentFrameId(), kGetPageSource, args, value); } Status ExecuteFindElement( int interval_ms, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return FindElement(interval_ms, true, NULL, session, web_view, params, value); } Status ExecuteFindElements( int interval_ms, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return FindElement( interval_ms, false, NULL, session, web_view, params, value); } Status ExecuteGetCurrentUrl( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string url; Status status = GetUrl(web_view, session->GetCurrentFrameId(), &url); if (status.IsError()) return status; value->reset(new base::StringValue(url)); return Status(kOk); } Status ExecuteGoBack( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->EvaluateScript( std::string(), "window.history.back();", value); } Status ExecuteGoForward( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->EvaluateScript( std::string(), "window.history.forward();", value); } Status ExecuteRefresh( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->Reload(); } Status ExecuteMouseMoveTo( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string element_id; bool has_element = params.GetString("element", &element_id); int x_offset = 0; int y_offset = 0; bool has_offset = params.GetInteger("xoffset", &x_offset) && params.GetInteger("yoffset", &y_offset); if (!has_element && !has_offset) return Status(kUnknownError, "at least an element or offset should be set"); WebPoint location; if (has_element) { Status status = ScrollElementIntoView( session, web_view, element_id, &location); if (status.IsError()) return status; } else { location = session->mouse_position; } if (has_offset) { location.Offset(x_offset, y_offset); } else { WebSize size; Status status = GetElementSize(session, web_view, element_id, &size); if (status.IsError()) return status; location.Offset(size.width / 2, size.height / 2); } std::list<MouseEvent> events; events.push_back( MouseEvent(kMovedMouseEventType, kNoneMouseButton, location.x, location.y, session->sticky_modifiers, 0)); Status status = web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); if (status.IsOk()) session->mouse_position = location; return status; } Status ExecuteMouseClick( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { MouseButton button; Status status = GetMouseButton(params, &button); if (status.IsError()) return status; std::list<MouseEvent> events; events.push_back( MouseEvent(kPressedMouseEventType, button, session->mouse_position.x, session->mouse_position.y, session->sticky_modifiers, 1)); events.push_back( MouseEvent(kReleasedMouseEventType, button, session->mouse_position.x, session->mouse_position.y, session->sticky_modifiers, 1)); return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); } Status ExecuteMouseButtonDown( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { MouseButton button; Status status = GetMouseButton(params, &button); if (status.IsError()) return status; std::list<MouseEvent> events; events.push_back( MouseEvent(kPressedMouseEventType, button, session->mouse_position.x, session->mouse_position.y, session->sticky_modifiers, 1)); return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); } Status ExecuteMouseButtonUp( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { MouseButton button; Status status = GetMouseButton(params, &button); if (status.IsError()) return status; std::list<MouseEvent> events; events.push_back( MouseEvent(kReleasedMouseEventType, button, session->mouse_position.x, session->mouse_position.y, session->sticky_modifiers, 1)); return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); } Status ExecuteMouseDoubleClick( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { MouseButton button; Status status = GetMouseButton(params, &button); if (status.IsError()) return status; std::list<MouseEvent> events; events.push_back( MouseEvent(kPressedMouseEventType, button, session->mouse_position.x, session->mouse_position.y, session->sticky_modifiers, 2)); events.push_back( MouseEvent(kReleasedMouseEventType, button, session->mouse_position.x, session->mouse_position.y, session->sticky_modifiers, 2)); return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId()); } Status ExecuteTouchDown( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return ExecuteTouchEvent(session, web_view, kTouchStart, params); } Status ExecuteTouchUp( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return ExecuteTouchEvent(session, web_view, kTouchEnd, params); } Status ExecuteTouchMove( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return ExecuteTouchEvent(session, web_view, kTouchMove, params); } Status ExecuteGetActiveElement( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return GetActiveElement(session, web_view, value); } Status ExecuteSendKeysToActiveElement( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { const base::ListValue* key_list; if (!params.GetList("value", &key_list)) return Status(kUnknownError, "'value' must be a list"); return SendKeysOnWindow( web_view, key_list, false, &session->sticky_modifiers); } Status ExecuteGetAppCacheStatus( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->EvaluateScript( session->GetCurrentFrameId(), "applicationCache.status", value); } Status ExecuteIsBrowserOnline( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->EvaluateScript( session->GetCurrentFrameId(), "navigator.onLine", value); } Status ExecuteGetStorageItem( const char* storage, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string key; if (!params.GetString("key", &key)) return Status(kUnknownError, "'key' must be a string"); base::ListValue args; args.Append(new base::StringValue(key)); return web_view->CallFunction( session->GetCurrentFrameId(), base::StringPrintf("function(key) { return %s[key]; }", storage), args, value); } Status ExecuteGetStorageKeys( const char* storage, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { const char script[] = "var keys = [];" "for (var key in %s) {" " keys.push(key);" "}" "keys"; return web_view->EvaluateScript( session->GetCurrentFrameId(), base::StringPrintf(script, storage), value); } Status ExecuteSetStorageItem( const char* storage, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string key; if (!params.GetString("key", &key)) return Status(kUnknownError, "'key' must be a string"); std::string storage_value; if (!params.GetString("value", &storage_value)) return Status(kUnknownError, "'value' must be a string"); base::ListValue args; args.Append(new base::StringValue(key)); args.Append(new base::StringValue(storage_value)); return web_view->CallFunction( session->GetCurrentFrameId(), base::StringPrintf("function(key, value) { %s[key] = value; }", storage), args, value); } Status ExecuteRemoveStorageItem( const char* storage, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string key; if (!params.GetString("key", &key)) return Status(kUnknownError, "'key' must be a string"); base::ListValue args; args.Append(new base::StringValue(key)); return web_view->CallFunction( session->GetCurrentFrameId(), base::StringPrintf("function(key) { %s.removeItem(key) }", storage), args, value); } Status ExecuteClearStorage( const char* storage, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->EvaluateScript( session->GetCurrentFrameId(), base::StringPrintf("%s.clear()", storage), value); } Status ExecuteGetStorageSize( const char* storage, Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->EvaluateScript( session->GetCurrentFrameId(), base::StringPrintf("%s.length", storage), value); } Status ExecuteScreenshot( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { Status status = session->chrome->ActivateWebView(web_view->GetId()); if (status.IsError()) return status; std::string screenshot; if (session->chrome->GetAsDesktop() && !session->force_devtools_screenshot) { AutomationExtension* extension = NULL; status = session->chrome->GetAsDesktop()->GetAutomationExtension(&extension); if (status.IsError()) return status; status = extension->CaptureScreenshot(&screenshot); // If the screenshot was forbidden, fallback to DevTools. if (status.code() == kForbidden) status = web_view->CaptureScreenshot(&screenshot); } else { status = web_view->CaptureScreenshot(&screenshot); } if (status.IsError()) return status; value->reset(new base::StringValue(screenshot)); return Status(kOk); } Status ExecuteGetCookies( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::list<Cookie> cookies; Status status = GetVisibleCookies(web_view, &cookies); if (status.IsError()) return status; scoped_ptr<base::ListValue> cookie_list(new base::ListValue()); for (std::list<Cookie>::const_iterator it = cookies.begin(); it != cookies.end(); ++it) { cookie_list->Append(CreateDictionaryFrom(*it)); } value->reset(cookie_list.release()); return Status(kOk); } Status ExecuteAddCookie( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { const base::DictionaryValue* cookie; if (!params.GetDictionary("cookie", &cookie)) return Status(kUnknownError, "missing 'cookie'"); base::ListValue args; args.Append(cookie->DeepCopy()); scoped_ptr<base::Value> result; return web_view->CallFunction( session->GetCurrentFrameId(), kAddCookieScript, args, &result); } Status ExecuteDeleteCookie( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::string name; if (!params.GetString("name", &name)) return Status(kUnknownError, "missing 'name'"); base::DictionaryValue params_url; scoped_ptr<base::Value> value_url; std::string url; Status status = GetUrl(web_view, session->GetCurrentFrameId(), &url); if (status.IsError()) return status; return web_view->DeleteCookie(name, url); } Status ExecuteDeleteAllCookies( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { std::list<Cookie> cookies; Status status = GetVisibleCookies(web_view, &cookies); if (status.IsError()) return status; if (!cookies.empty()) { base::DictionaryValue params_url; scoped_ptr<base::Value> value_url; std::string url; status = GetUrl(web_view, session->GetCurrentFrameId(), &url); if (status.IsError()) return status; for (std::list<Cookie>::const_iterator it = cookies.begin(); it != cookies.end(); ++it) { status = web_view->DeleteCookie(it->name, url); if (status.IsError()) return status; } } return Status(kOk); } Status ExecuteSetLocation( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { const base::DictionaryValue* location = NULL; Geoposition geoposition; if (!params.GetDictionary("location", &location) || !location->GetDouble("latitude", &geoposition.latitude) || !location->GetDouble("longitude", &geoposition.longitude)) return Status(kUnknownError, "missing or invalid 'location'"); if (location->HasKey("accuracy") && !location->GetDouble("accuracy", &geoposition.accuracy)) { return Status(kUnknownError, "invalid 'accuracy'"); } else { // |accuracy| is not part of the WebDriver spec yet, so if it is not given // default to 100 meters accuracy. geoposition.accuracy = 100; } Status status = web_view->OverrideGeolocation(geoposition); if (status.IsOk()) session->overridden_geoposition.reset(new Geoposition(geoposition)); return status; } Status ExecuteTakeHeapSnapshot( Session* session, WebView* web_view, const base::DictionaryValue& params, scoped_ptr<base::Value>* value) { return web_view->TakeHeapSnapshot(value); }