blob: 329646a877c2d4091a9d13580f56ead88cae33af [file] [log] [blame]
// 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 <stddef.h>
#include <list>
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.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/browser_info.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/network_conditions.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_commands.h"
#include "chrome/test/chromedriver/element_util.h"
#include "chrome/test/chromedriver/key_converter.h"
#include "chrome/test/chromedriver/keycode_text_conversion.h"
#include "chrome/test/chromedriver/net/timeout.h"
#include "chrome/test/chromedriver/session.h"
#include "chrome/test/chromedriver/util.h"
#include "ui/gfx/geometry/point.h"
namespace {
// The error page URL was renamed in
// https://chromium-review.googlesource.com/c/580169, but because ChromeDriver
// needs to be backward-compatible with older versions of Chrome, it is
// necessary to compare against both the old and new error URL.
static const char kUnreachableWebDataURL[] = "chrome-error://chromewebdata/";
const char kDeprecatedUnreachableWebDataURL[] = "data:text/html,chromewebdata";
// TODO(johnchen@chromium.org): Remove when we stop supporting legacy protocol.
// Defaults to 20 years into the future when adding a cookie.
const double kDefaultCookieExpiryTime = 20*365*24*60*60;
// for pointer actions
enum class PointerActionType { NOT_INITIALIZED, PRESS, MOVE, RELEASE, IDLE };
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(kInvalidArgument,
base::StringPrintf("invalid button: %d", button_num));
}
*button = static_cast<MouseButton>(button_num);
return Status(kOk);
}
Status IntToStringButton(int button, std::string& out) {
if (button == 0) {
out = "left";
} else if (button == 1) {
out = "middle";
} else if (button == 2) {
out = "right";
} else if (button == 3) {
out = "back";
} else if (button == 4) {
out = "forward";
} else {
return Status(kInvalidArgument,
"'button' must be an integer between 0 and 4 inclusive");
}
return Status(kOk);
}
Status GetUrl(WebView* web_view, const std::string& frame, std::string* url) {
std::unique_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);
}
MouseEventType StringToMouseEventType(std::string action_type) {
if (action_type == "pointerDown")
return kPressedMouseEventType;
else if (action_type == "pointerUp")
return kReleasedMouseEventType;
else if (action_type == "pointerMove")
return kMovedMouseEventType;
else if (action_type == "pause")
return kPauseMouseEventType;
else
return kPressedMouseEventType;
}
MouseButton StringToMouseButton(std::string button_type) {
if (button_type == "left")
return kLeftMouseButton;
else if (button_type == "middle")
return kMiddleMouseButton;
else if (button_type == "right")
return kRightMouseButton;
else if (button_type == "back")
return kBackMouseButton;
else if (button_type == "forward")
return kForwardMouseButton;
else
return kNoneMouseButton;
}
TouchEventType StringToTouchEventType(std::string action_type) {
if (action_type == "pointerDown")
return kTouchStart;
else if (action_type == "pointerUp")
return kTouchEnd;
else if (action_type == "pointerMove")
return kTouchMove;
else if (action_type == "pause")
return kPause;
else
return kTouchStart;
}
int StringToModifierMouseButton(std::string button_type) {
if (button_type == "left")
return 1;
else if (button_type == "right")
return 2;
else if (button_type == "middle")
return 4;
else if (button_type == "back")
return 8;
else if (button_type == "forward")
return 16;
else
return 0;
}
int MouseButtonToButtons(MouseButton button) {
switch (button) {
case kLeftMouseButton:
return 1;
case kRightMouseButton:
return 2;
case kMiddleMouseButton:
return 4;
case kBackMouseButton:
return 8;
case kForwardMouseButton:
return 16;
default:
return 0;
}
}
struct Cookie {
Cookie(const std::string& name,
const std::string& value,
const std::string& domain,
const std::string& path,
double expiry,
bool http_only,
bool secure,
bool session)
: name(name), value(value), domain(domain), path(path), expiry(expiry),
http_only(http_only), secure(secure), session(session) {}
std::string name;
std::string value;
std::string domain;
std::string path;
double expiry;
bool http_only;
bool secure;
bool session;
};
std::unique_ptr<base::DictionaryValue> CreateDictionaryFrom(
const Cookie& cookie) {
std::unique_ptr<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("httpOnly", cookie.http_only);
dict->SetBoolean("secure", cookie.secure);
return dict;
}
Status GetVisibleCookies(WebView* web_view,
std::list<Cookie>* cookies) {
std::string current_page_url;
Status status = GetUrl(web_view, std::string(), &current_page_url);
if (status.IsError())
return status;
std::unique_ptr<base::ListValue> internal_cookies;
status = web_view->GetCookies(&internal_cookies, current_page_url);
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);
if (expiry > 1e12)
expiry /= 1000; // Backwards compatibility ms -> sec.
bool http_only = false;
cookie_dict->GetBoolean("httpOnly", &http_only);
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, http_only, 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) {
std::unique_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(kInvalidArgument, "'x' must be an integer");
if (!params.GetInteger("y", &y))
return Status(kInvalidArgument, "'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);
}
Status WindowViewportSize(Session* session,
WebView* web_view,
int* innerWidth,
int* innerHeight) {
std::unique_ptr<base::Value> value;
base::ListValue args;
Status status =
web_view->CallFunction(std::string(),
"function() {"
" return {"
" 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);
view_attrib->GetInteger("view_width", innerWidth);
view_attrib->GetInteger("view_height", innerHeight);
return Status(kOk);
}
Status ProcessPauseAction(const base::DictionaryValue* action_item,
base::DictionaryValue* action) {
if (action_item->HasKey("duration")) {
int duration;
if (!action_item->GetInteger("duration", &duration) || duration < 0)
return Status(kInvalidArgument, "'duration' must be a non-negative int");
action->SetInteger("duration", duration);
}
return Status(kOk);
}
Status ElementInViewCenter(Session* session,
WebView* web_view,
std::string element_id,
int* center_x,
int* center_y) {
WebRect region;
Status status = GetElementRegion(session, web_view, element_id, &region);
if (status.IsError())
return status;
WebPoint region_offset;
status = ScrollElementRegionIntoView(session, web_view, element_id, region,
true /* center */, std::string(),
&region_offset);
if (status.IsError())
return status;
int innerWidth, innerHeight;
status = WindowViewportSize(session, web_view, &innerWidth, &innerHeight);
if (status.IsError())
return status;
int left =
std::max(0, std::min(region_offset.x, region_offset.x + region.Width()));
int right = std::min(
innerWidth, std::max(region_offset.x, region_offset.x + region.Width()));
int top =
std::max(0, std::min(region_offset.y, region_offset.y + region.Height()));
int bottom =
std::min(innerHeight,
std::max(region_offset.y, region_offset.y + region.Height()));
*center_x = static_cast<int>(std::floor((left + right) / 2));
*center_y = static_cast<int>(std::floor((top + bottom) / 2));
return Status(kOk);
}
} // namespace
Status ExecuteWindowCommand(const WindowCommand& command,
Session* session,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
Timeout timeout;
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;
JavaScriptDialogManager* dialog_manager =
web_view->GetJavaScriptDialogManager();
if (dialog_manager->IsDialogOpen()) {
std::string alert_text;
status = dialog_manager->GetDialogMessage(&alert_text);
if (status.IsError())
return status;
// Close the dialog depending on the unexpectedalert behaviour set by user
// before returning an error, so that subsequent commands do not fail.
const std::string& prompt_behavior = session->unhandled_prompt_behavior;
if (prompt_behavior == kAccept || prompt_behavior == kAcceptAndNotify)
status = dialog_manager->HandleDialog(true, session->prompt_text.get());
else if (prompt_behavior == kDismiss ||
prompt_behavior == kDismissAndNotify)
status = dialog_manager->HandleDialog(false, session->prompt_text.get());
if (status.IsError())
return status;
// For backward compatibility, in legacy mode we always notify.
if (!session->w3c_compliant || prompt_behavior == kAcceptAndNotify ||
prompt_behavior == kDismissAndNotify || prompt_behavior == kIgnore)
return Status(kUnexpectedAlertOpen, "{Alert text : " + alert_text + "}");
}
Status nav_status(kOk);
for (int attempt = 0; attempt < 3; attempt++) {
if (attempt == 2) {
// Switch to main frame and retry command if subframe no longer exists.
session->SwitchToTopFrame();
}
nav_status = web_view->WaitForPendingNavigations(
session->GetCurrentFrameId(),
Timeout(session->page_load_timeout, &timeout), true);
if (nav_status.IsError())
return nav_status;
status = command.Run(session, web_view, params, value, &timeout);
if (status.code() == kNoSuchExecutionContext || status.code() == kTimeout) {
// If the command timed out, let WaitForPendingNavigations cancel
// the navigation if there is one.
continue;
} else if (status.IsError()) {
// If the command failed while a new page or frame started loading, retry
// the command after the pending navigation has completed.
bool is_pending = false;
nav_status = web_view->IsPendingNavigation(session->GetCurrentFrameId(),
&timeout, &is_pending);
if (nav_status.IsError())
return nav_status;
else if (is_pending)
continue;
}
break;
}
nav_status = web_view->WaitForPendingNavigations(
session->GetCurrentFrameId(),
Timeout(session->page_load_timeout, &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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
timeout->SetDuration(session->page_load_timeout);
std::string url;
if (!params.GetString("url", &url))
return Status(kInvalidArgument, "'url' must be a string");
Status status = web_view->Load(url, timeout);
if (status.IsError())
return status;
session->SwitchToTopFrame();
return Status(kOk);
}
Status ExecuteExecuteScript(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string script;
if (!params.GetString("script", &script))
return Status(kInvalidArgument, "'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(kInvalidArgument, "'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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string script;
if (!params.GetString("script", &script))
return Status(kInvalidArgument, "'script' must be a string");
const base::ListValue* args;
if (!params.GetList("args", &args))
return Status(kInvalidArgument, "'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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
const base::Value* id;
if (!params.Get("id", &id))
return Status(kInvalidArgument, "missing 'id'");
if (id->is_none()) {
session->SwitchToTopFrame();
return Status(kOk);
}
std::string script;
base::ListValue args;
const base::DictionaryValue* id_dict;
if (id->GetAsDictionary(&id_dict)) {
std::string element_id;
if (!id_dict->GetString(GetElementKey(), &element_id))
return Status(kInvalidArgument, "missing 'ELEMENT'");
bool is_displayed = false;
Status status = IsElementDisplayed(
session, web_view, element_id, true, &is_displayed);
if (status.IsError())
return status;
script = "function(elem) { return elem; }";
args.Append(id_dict->CreateDeepCopy());
} 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(kInvalidArgument, "invalid 'id'");
}
args.AppendString(xpath);
}
std::string frame;
Status status = web_view->GetFrameByFunction(
session->GetCurrentFrameId(), script, args, &frame);
if (status.IsError())
return status;
std::unique_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->CreateDeepCopy());
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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
session->SwitchToParentFrame();
return Status(kOk);
}
Status ExecuteGetTitle(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
const char kGetTitleScript[] = "function() { return document.title;}";
base::ListValue args;
return web_view->CallFunction(std::string(), kGetTitleScript, args, value);
}
Status ExecuteGetPageSource(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return FindElement(
interval_ms, false, NULL, session, web_view, params, value);
}
Status ExecuteGetCurrentUrl(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string url;
Status status = GetUrl(web_view, std::string(), &url);
if (status.IsError())
return status;
if (url == kUnreachableWebDataURL ||
url == kDeprecatedUnreachableWebDataURL) {
status = web_view->GetUrl(&url);
if (status.IsError())
return status;
}
value->reset(new base::Value(url));
return Status(kOk);
}
Status ExecuteGoBack(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
timeout->SetDuration(session->page_load_timeout);
Status status = web_view->TraverseHistory(-1, timeout);
if (status.IsError())
return status;
session->SwitchToTopFrame();
return Status(kOk);
}
Status ExecuteGoForward(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
timeout->SetDuration(session->page_load_timeout);
Status status = web_view->TraverseHistory(1, timeout);
if (status.IsError())
return status;
session->SwitchToTopFrame();
return Status(kOk);
}
Status ExecuteRefresh(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
timeout->SetDuration(session->page_load_timeout);
Status status = web_view->Reload(timeout);
if (status.IsError())
return status;
session->SwitchToTopFrame();
return Status(kOk);
}
Status ExecuteFreeze(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
timeout->SetDuration(session->page_load_timeout);
Status status = web_view->Freeze(timeout);
return status;
}
Status ExecuteResume(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
timeout->SetDuration(session->page_load_timeout);
Status status = web_view->Resume(timeout);
if (status.IsError())
return status;
return Status(kOk);
}
Status ExecuteMouseMoveTo(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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(kInvalidArgument,
"at least an element or offset should be set");
WebPoint location;
if (has_element) {
WebPoint offset(x_offset, y_offset);
Status status = ScrollElementIntoView(session, web_view, element_id,
has_offset ? &offset : nullptr, &location);
if (status.IsError())
return status;
} else {
location = session->mouse_position;
if (has_offset)
location.Offset(x_offset, y_offset);
}
std::list<MouseEvent> events;
events.push_back(MouseEvent(kMovedMouseEventType,
session->pressed_mouse_button, location.x,
location.y, session->sticky_modifiers, 0, 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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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, 0, 1));
events.push_back(
MouseEvent(kReleasedMouseEventType, button, session->mouse_position.x,
session->mouse_position.y, session->sticky_modifiers,
MouseButtonToButtons(button), 1));
session->pressed_mouse_button = kNoneMouseButton;
return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
}
Status ExecuteMouseButtonDown(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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, 0, 1));
session->pressed_mouse_button = button;
return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
}
Status ExecuteMouseButtonUp(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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,
MouseButtonToButtons(button), 1));
session->pressed_mouse_button = kNoneMouseButton;
return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
}
Status ExecuteMouseDoubleClick(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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, 0, 2));
events.push_back(
MouseEvent(kReleasedMouseEventType, button, session->mouse_position.x,
session->mouse_position.y, session->sticky_modifiers,
MouseButtonToButtons(button), 2));
session->pressed_mouse_button = kNoneMouseButton;
return web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
}
Status ExecuteTouchDown(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return ExecuteTouchEvent(session, web_view, kTouchStart, params);
}
Status ExecuteTouchUp(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return ExecuteTouchEvent(session, web_view, kTouchEnd, params);
}
Status ExecuteTouchMove(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return ExecuteTouchEvent(session, web_view, kTouchMove, params);
}
Status ExecuteTouchScroll(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
WebPoint location = session->mouse_position;
std::string element;
if (params.GetString("element", &element)) {
Status status = GetElementClickableLocation(
session, web_view, element, &location);
if (status.IsError())
return status;
}
int xoffset;
if (!params.GetInteger("xoffset", &xoffset))
return Status(kInvalidArgument, "'xoffset' must be an integer");
int yoffset;
if (!params.GetInteger("yoffset", &yoffset))
return Status(kInvalidArgument, "'yoffset' must be an integer");
return web_view->SynthesizeScrollGesture(
location.x, location.y, xoffset, yoffset);
}
Status ExecuteTouchPinch(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
WebPoint location;
if (!params.GetInteger("x", &location.x))
return Status(kInvalidArgument, "'x' must be an integer");
if (!params.GetInteger("y", &location.y))
return Status(kInvalidArgument, "'y' must be an integer");
double scale_factor;
if (!params.GetDouble("scale", &scale_factor))
return Status(kInvalidArgument, "'scale' must be an integer");
return web_view->SynthesizePinchGesture(location.x, location.y, scale_factor);
}
Status ProcessInputActionSequence(
Session* session,
const base::DictionaryValue* action_sequence,
std::unique_ptr<base::DictionaryValue>* action_sequence_result) {
std::string id;
std::string type;
const base::DictionaryValue* source;
const base::DictionaryValue* parameters;
std::string pointer_type;
if (!action_sequence->GetString("type", &type) ||
((type != "key") && (type != "pointer") && (type != "none"))) {
return Status(
kInvalidArgument,
"'type' must be one of the strings 'key', 'pointer' or 'none'");
}
if (!action_sequence->GetString("id", &id))
return Status(kInvalidArgument, "'id' must be a string");
if (type == "pointer") {
if (action_sequence->GetDictionary("parameters", &parameters)) {
// error check arguments
if (!parameters->GetString("pointerType", &pointer_type) ||
(pointer_type != "mouse" && pointer_type != "pen" &&
pointer_type != "touch"))
return Status(
kInvalidArgument,
"'pointerType' must be a string and one of mouse, pen or touch");
} else {
pointer_type = "mouse";
}
}
(*action_sequence_result)->SetString("sourceType", type);
(*action_sequence_result)->SetString("pointerType", pointer_type);
(*action_sequence_result)->SetString("id", id);
bool found = false;
for (size_t i = 0; i < session->active_input_sources.GetSize(); i++) {
session->active_input_sources.GetDictionary(i, &source);
DCHECK(source);
std::string source_id;
source->GetString("id", &source_id);
if (source_id == id) {
found = true;
if (type == "pointer") {
std::string source_pointer_type;
if (!source->GetString("pointerType", &source_pointer_type) ||
pointer_type != source_pointer_type) {
return Status(kInvalidArgument,
"'pointerType' must be a string that matches sources "
"pointer type");
}
}
std::string source_type;
source->GetString("type", &source_type);
if (source_type != type) {
return Status(kInvalidArgument,
"input state with same id has a different type");
}
break;
}
}
// if we found no matching active input source
std::unique_ptr<base::DictionaryValue> tmp_source(new base::DictionaryValue);
if (!found) {
// create input source
tmp_source->SetString("id", id);
tmp_source->SetString("type", type);
if (type == "pointer") {
tmp_source->SetString("pointerType", pointer_type);
}
session->active_input_sources.Append(std::move(tmp_source));
base::DictionaryValue tmp_state;
tmp_state.SetString("id", id);
if (type == "key") {
std::unique_ptr<base::ListValue> pressed(new base::ListValue);
bool alt = false;
bool shift = false;
bool ctrl = false;
bool meta = false;
tmp_state.SetList("pressed", std::move(pressed));
tmp_state.SetBoolean("alt", alt);
tmp_state.SetBoolean("shift", shift);
tmp_state.SetBoolean("ctrl", ctrl);
tmp_state.SetBoolean("meta", meta);
} else if (type == "pointer") {
std::unique_ptr<base::ListValue> pressed(new base::ListValue);
int x = 0;
int y = 0;
tmp_state.SetList("pressed", std::move(pressed));
tmp_state.SetString("subtype", pointer_type);
tmp_state.SetInteger("x", x);
tmp_state.SetInteger("y", y);
}
session->input_state_table.SetDictionary(
id, std::make_unique<base::DictionaryValue>(std::move(tmp_state)));
}
const base::ListValue* actions;
if (!action_sequence->GetList("actions", &actions))
return Status(kInvalidArgument, "'actions' must be an array");
std::unique_ptr<base::ListValue> actions_result(new base::ListValue);
for (size_t i = 0; i < actions->GetSize(); i++) {
std::unique_ptr<base::DictionaryValue> action(new base::DictionaryValue());
const base::DictionaryValue* action_item;
if (!actions->GetDictionary(i, &action_item))
return Status(
kInvalidArgument,
"each argument in the action sequence must be a dictionary");
action->SetString("id", id);
action->SetString("type", type);
if (type == "none") {
// process none action
std::string subtype;
if (!action_item->GetString("type", &subtype) || subtype != "pause")
return Status(kInvalidArgument,
"type of action must be the string 'pause'");
action->SetString("subtype", subtype);
Status status = ProcessPauseAction(action_item, action.get());
if (status.IsError())
return status;
} else if (type == "key") {
// process key action
std::string subtype;
if (!action_item->GetString("type", &subtype) ||
(subtype != "keyUp" && subtype != "keyDown" && subtype != "pause"))
return Status(
kInvalidArgument,
"type of action must be the string 'keyUp', 'keyDown' or 'pause'");
action->SetString("subtype", subtype);
if (subtype == "pause") {
Status status = ProcessPauseAction(action_item, action.get());
if (status.IsError())
return status;
} else {
std::string key;
// TODO: check if key is a single unicode code point
if (!action_item->GetString("value", &key)) {
return Status(kInvalidArgument,
"'value' must be a single unicode point");
}
action->SetString("value", key);
}
} else if (type == "pointer") {
std::string subtype;
if (!action_item->GetString("type", &subtype) ||
(subtype != "pointerUp" && subtype != "pointerDown" &&
subtype != "pointerMove" && subtype != "pointerCancel" &&
subtype != "pause"))
return Status(kInvalidArgument,
"type of action must be the string 'pointerUp', "
"'pointerDown', 'pointerMove' or 'pause'");
action->SetString("subtype", subtype);
if (subtype == "pointerDown" || subtype == "pointerUp") {
if (pointer_type == "mouse") {
int button;
if (!action_item->GetInteger("button", &button) || button < 0 ||
button > 4) {
return Status(
kInvalidArgument,
"'button' must be a non-negative int and between 0 and 4");
}
std::string button_str;
Status status = IntToStringButton(button, button_str);
if (status.IsError())
return status;
action->SetString("button", button_str);
}
} else if (subtype == "pointerMove") {
int x;
if (!action_item->GetInteger("x", &x))
return Status(kInvalidArgument, "'x' must be an int");
int y;
if (!action_item->GetInteger("y", &y))
return Status(kInvalidArgument, "'y' must be an int");
action->SetInteger("x", x);
action->SetInteger("y", y);
std::string origin;
if (action_item->HasKey("origin")) {
if (!action_item->GetString("origin", &origin)) {
const base::DictionaryValue* origin_dict;
if (!action_item->GetDictionary("origin", &origin_dict))
return Status(kInvalidArgument,
"'origin' must be either a string or a dictionary");
std::string element_id;
if (!origin_dict->GetString(GetElementKey(), &element_id))
return Status(kInvalidArgument, "'element' is missing");
std::unique_ptr<base::DictionaryValue> origin_result =
std::make_unique<base::DictionaryValue>();
origin_result->SetString(GetElementKey(), element_id);
action->SetDictionary("origin", std::move(origin_result));
} else {
if (origin != "viewport" && origin != "pointer")
return Status(kInvalidArgument,
"if 'origin' is a string, it must be either "
"'viewport' or 'pointer'");
action->SetString("origin", origin);
}
} else {
action->SetString("origin", "viewport");
}
Status status = ProcessPauseAction(action_item, action.get());
if (status.IsError())
return status;
} else if (subtype == "pause") {
Status status = ProcessPauseAction(action_item, action.get());
if (status.IsError())
return status;
}
}
actions_result->Append(std::move(action));
}
(*action_sequence_result)->SetList("actions", std::move(actions_result));
return Status(kOk);
}
Status ExecutePerformActions(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
// extract action sequence
const base::DictionaryValue* actions_dict;
const base::ListValue* actions_input;
// TODO(lanwei): The below line will be removed after this pull request is
// merged, https://github.com/web-platform-tests/wpt/pull/14345.
if (!params.GetDictionary("actions", &actions_dict))
actions_dict = &params;
if (!actions_dict->GetList("actions", &actions_input))
return Status(kInvalidArgument, "'actions' must be an array");
// the processed actions
base::ListValue actions_list;
for (size_t i = 0; i < actions_input->GetSize(); i++) {
std::unique_ptr<base::DictionaryValue> input_source_actions(
new base::DictionaryValue());
// proccess input action sequence
const base::DictionaryValue* action_sequence;
if (!actions_input->GetDictionary(i, &action_sequence))
return Status(kInvalidArgument, "each argument must be a dictionary");
Status status = ProcessInputActionSequence(session, action_sequence,
&input_source_actions);
if (status.IsError())
return Status(kInvalidArgument, status);
actions_list.Append(std::move(input_source_actions));
}
std::string input_pointer_type;
std::set<std::string> pointer_id_set;
std::string type;
std::vector<std::vector<MouseEvent>> mouse_events_list;
std::vector<std::vector<TouchEvent>> touch_events_list;
std::vector<std::vector<KeyEvent>> key_events_list;
size_t longest_mouse_list_size = 0;
size_t longest_touch_list_size = 0;
size_t longest_key_list_size = 0;
for (size_t i = 0; i < actions_list.GetSize(); i++) {
base::DictionaryValue* action_sequence;
actions_list.GetDictionary(i, &action_sequence);
const base::ListValue* actions;
action_sequence->GetList("actions", &actions);
DCHECK(actions);
action_sequence->GetString("sourceType", &type);
// key actions
if (type == "key") {
KeyEventBuilder builder;
std::vector<KeyEvent> key_events;
for (size_t j = 0; j < actions->GetSize(); j++) {
const base::DictionaryValue* action;
actions->GetDictionary(j, &action);
std::string subtype;
action->GetString("subtype", &subtype);
std::string id;
action->GetString("id", &id);
if (subtype == "pause") {
key_events.push_back(builder.SetType(kPauseEventType)->Build());
} else {
base::DictionaryValue dispatch_params;
base::string16 raw_key;
action->GetString("value", &raw_key);
base::char16 key = raw_key[0];
// TODO: understand necessary_modifiers
int necessary_modifiers = 0;
ui::KeyboardCode key_code = ui::VKEY_UNKNOWN;
std::string error_msg;
ConvertCharToKeyCode(key, &key_code, &necessary_modifiers,
&error_msg);
if (!error_msg.empty())
return Status(kUnknownError, error_msg);
if (subtype == "keyDown")
key_events.push_back(builder.SetType(kKeyDownEventType)
->SetText(base::UTF16ToUTF8(raw_key),
base::UTF16ToUTF8(raw_key))
->SetKeyCode(key_code)
->SetModifiers(0)
->Build());
else if (subtype == "keyUp")
key_events.push_back(builder.SetType(kKeyUpEventType)
->SetText(base::UTF16ToUTF8(raw_key),
base::UTF16ToUTF8(raw_key))
->SetKeyCode(key_code)
->SetModifiers(0)
->Build());
}
}
longest_key_list_size =
std::max(key_events.size(), longest_key_list_size);
key_events_list.push_back(key_events);
} else if (type == "pointer") {
std::string pointer_type;
action_sequence->GetString("pointerType", &pointer_type);
if (input_pointer_type.empty())
input_pointer_type = pointer_type;
if (input_pointer_type != pointer_type) {
return Status(kInvalidArgument,
"multiple input pointer types are not supported now");
}
std::string pointer_id;
action_sequence->GetString("id", &pointer_id);
if (pointer_id_set.find(pointer_id) != pointer_id_set.end())
return Status(kInvalidArgument, "'id' already exists");
pointer_id_set.insert(pointer_id);
std::vector<MouseEvent> mouse_events;
std::vector<TouchEvent> touch_events;
double x = 0;
double y = 0;
bool has_touch_start = false;
int buttons = 0;
std::string button_type;
std::string element_id;
for (size_t j = 0; j < actions->GetSize(); j++) {
const base::DictionaryValue* pointer_action;
actions->GetDictionary(j, &pointer_action);
std::string action_type;
pointer_action->GetString("subtype", &action_type);
if (action_type == "pointerMove") {
pointer_action->GetDouble("x", &x);
pointer_action->GetDouble("y", &y);
const base::DictionaryValue* origin_dict;
element_id = "";
if (pointer_action->HasKey("origin") &&
pointer_action->GetDictionary("origin", &origin_dict)) {
origin_dict->GetString(GetElementKey(), &element_id);
}
}
if (pointer_type == "mouse") {
int click_count = 0;
if (action_type == "pointerDown" || action_type == "pointerUp") {
pointer_action->GetString("button", &button_type);
click_count = 1;
}
MouseEvent event(StringToMouseEventType(action_type),
StringToMouseButton(button_type), x, y, 0, buttons,
click_count);
event.element_id = element_id;
mouse_events.push_back(event);
if (action_type == "pointerDown")
buttons |= StringToModifierMouseButton(button_type);
else if (action_type == "pointerUp")
buttons &= ~StringToModifierMouseButton(button_type);
} else if (pointer_type == "touch") {
if (action_type == "pointerDown")
has_touch_start = true;
else if (action_type == "pointerUp")
has_touch_start = false;
if (action_type != "pointerMove" || has_touch_start) {
TouchEvent event(StringToTouchEventType(action_type), x, y);
event.element_id = element_id;
touch_events.push_back(event);
}
}
}
if (pointer_type == "mouse") {
longest_mouse_list_size =
std::max(mouse_events.size(), longest_mouse_list_size);
mouse_events_list.push_back(mouse_events);
} else if (pointer_type == "touch") {
longest_touch_list_size =
std::max(touch_events.size(), longest_touch_list_size);
touch_events_list.push_back(touch_events);
}
}
}
size_t max_list_length =
std::max(std::max(longest_mouse_list_size, longest_touch_list_size),
longest_key_list_size);
std::map<std::string, gfx::Point> element_center_point;
for (size_t i = 0; i < max_list_length; i++) {
std::list<MouseEvent> dispatch_mouse_events;
for (size_t j = 0; j < mouse_events_list.size(); j++) {
if (i < mouse_events_list[j].size() &&
mouse_events_list[j][i].type != kPauseMouseEventType) {
MouseEvent event = mouse_events_list[j][i];
if (!event.element_id.empty()) {
if (event.type == kMovedMouseEventType ||
element_center_point.find(event.element_id) ==
element_center_point.end()) {
int center_x = 0, center_y = 0;
ElementInViewCenter(session, web_view, event.element_id, &center_x,
&center_y);
element_center_point[event.element_id] =
gfx::Point(center_x, center_y);
}
event.x += element_center_point[event.element_id].x();
event.y += element_center_point[event.element_id].y();
}
dispatch_mouse_events.push_back(event);
}
}
if (dispatch_mouse_events.size() > 0) {
Status status = web_view->DispatchMouseEvents(
dispatch_mouse_events, session->GetCurrentFrameId());
if (status.IsError())
return status;
}
std::list<TouchEvent> dispatch_touch_events;
for (size_t j = 0; j < touch_events_list.size(); j++) {
if (i < touch_events_list[j].size() &&
touch_events_list[j][i].type != kPause) {
TouchEvent event = touch_events_list[j][i];
if (!event.element_id.empty()) {
if (event.type == kTouchMove ||
element_center_point.find(event.element_id) ==
element_center_point.end()) {
int center_x = 0, center_y = 0;
ElementInViewCenter(session, web_view, event.element_id, &center_x,
&center_y);
element_center_point[event.element_id] =
gfx::Point(center_x, center_y);
}
event.x += element_center_point[event.element_id].x();
event.y += element_center_point[event.element_id].y();
}
dispatch_touch_events.push_back(event);
}
}
if (dispatch_touch_events.size() > 0) {
Status status = web_view->DispatchTouchEvents(dispatch_touch_events);
if (status.IsError())
return status;
}
std::list<KeyEvent> dispatch_key_events;
for (size_t j = 0; j < key_events_list.size(); j++) {
if (i < key_events_list[j].size() &&
key_events_list[j][i].type != kPauseEventType) {
dispatch_key_events.push_back(key_events_list[j][i]);
}
}
if (dispatch_key_events.size() > 0) {
Status status = web_view->DispatchKeyEvents(dispatch_key_events);
if (status.IsError())
return status;
}
}
return Status(kOk);
}
Status ExecuteReleaseActions(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
// TODO(https://crbug.com/chromedriver/1897): Process "input cancel list".
session->input_state_table.Clear();
session->active_input_sources.Clear();
return Status(kOk);
}
Status ExecuteSendCommand(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string cmd;
if (!params.GetString("cmd", &cmd)) {
return Status(kInvalidArgument, "command not passed");
}
const base::DictionaryValue* cmdParams;
if (!params.GetDictionary("params", &cmdParams)) {
return Status(kInvalidArgument, "params not passed");
}
return web_view->SendCommand(cmd, *cmdParams);
}
Status ExecuteSendCommandAndGetResult(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string cmd;
if (!params.GetString("cmd", &cmd)) {
return Status(kInvalidArgument, "command not passed");
}
const base::DictionaryValue* cmdParams;
if (!params.GetDictionary("params", &cmdParams)) {
return Status(kInvalidArgument, "params not passed");
}
return web_view->SendCommandAndGetResult(cmd, *cmdParams, value);
}
Status ExecuteGetActiveElement(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return GetActiveElement(session, web_view, value);
}
Status ExecuteSendKeysToActiveElement(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
const base::ListValue* key_list;
if (!params.GetList("value", &key_list))
return Status(kInvalidArgument, "'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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return web_view->EvaluateScript(
session->GetCurrentFrameId(),
"applicationCache.status",
value);
}
Status ExecuteIsBrowserOnline(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return web_view->EvaluateScript(
session->GetCurrentFrameId(),
"navigator.onLine",
value);
}
Status ExecuteGetStorageItem(const char* storage,
Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string key;
if (!params.GetString("key", &key))
return Status(kInvalidArgument, "'key' must be a string");
base::ListValue args;
args.AppendString(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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
const char script[] =
"var keys = [];"
"var storage = %s;"
"for (var i = 0; i < storage.length; i++) {"
" keys.push(storage.key(i));"
"}"
"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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string key;
if (!params.GetString("key", &key))
return Status(kInvalidArgument, "'key' must be a string");
std::string storage_value;
if (!params.GetString("value", &storage_value))
return Status(kInvalidArgument, "'value' must be a string");
base::ListValue args;
args.AppendString(key);
args.AppendString(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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string key;
if (!params.GetString("key", &key))
return Status(kInvalidArgument, "'key' must be a string");
base::ListValue args;
args.AppendString(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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
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,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return web_view->EvaluateScript(
session->GetCurrentFrameId(),
base::StringPrintf("%s.length", storage),
value);
}
Status ExecuteScreenshot(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
Status status = session->chrome->ActivateWebView(web_view->GetId());
if (status.IsError())
return status;
std::string screenshot;
ChromeDesktopImpl* desktop = NULL;
status = session->chrome->GetAsDesktop(&desktop);
if (status.IsOk() && !session->force_devtools_screenshot) {
AutomationExtension* extension = NULL;
status = desktop->GetAutomationExtension(&extension,
session->w3c_compliant);
if (status.IsError())
return status;
status = extension->CaptureScreenshot(&screenshot);
} else {
std::unique_ptr<base::DictionaryValue> screenshot_params(
const base::DictionaryValue&);
status = web_view->CaptureScreenshot(&screenshot, base::DictionaryValue());
}
if (status.IsError()) {
LOG(WARNING) << "screenshot failed, retrying";
std::unique_ptr<base::DictionaryValue> screenshot_params(
new base::DictionaryValue);
status = web_view->CaptureScreenshot(&screenshot, base::DictionaryValue());
}
if (status.IsError())
return status;
value->reset(new base::Value(screenshot));
return Status(kOk);
}
Status ExecuteGetCookies(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::list<Cookie> cookies;
Status status = GetVisibleCookies(web_view, &cookies);
if (status.IsError())
return status;
std::unique_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 = std::move(cookie_list);
return Status(kOk);
}
Status ExecuteGetNamedCookie(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string name;
if (!params.GetString("name", &name))
return Status(kInvalidArgument, "missing 'cookie name'");
std::list<Cookie> cookies;
Status status = GetVisibleCookies(web_view, &cookies);
if (status.IsError())
return status;
for (std::list<Cookie>::const_iterator it = cookies.begin();
it != cookies.end(); ++it) {
if (name == it->name) {
value->reset(CreateDictionaryFrom(*it)->DeepCopy());
return Status(kOk);
}
}
return Status(kNoSuchCookie);
}
Status ExecuteAddCookie(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
const base::DictionaryValue* cookie;
if (!params.GetDictionary("cookie", &cookie))
return Status(kInvalidArgument, "missing 'cookie'");
std::string name;
std::string cookie_value;
if (!cookie->GetString("name", &name))
return Status(kInvalidArgument, "missing 'name'");
if (!cookie->GetString("value", &cookie_value))
return Status(kInvalidArgument, "missing 'value'");
std::string url;
Status status = GetUrl(web_view, session->GetCurrentFrameId(), &url);
if (status.IsError())
return status;
std::string domain;
if (!GetOptionalString(cookie, "domain", &domain))
return Status(kInvalidArgument, "invalid 'domain'");
std::string path("/");
if (!GetOptionalString(cookie, "path", &path))
return Status(kInvalidArgument, "invalid 'path'");
bool secure = false;
if (!GetOptionalBool(cookie, "secure", &secure))
return Status(kInvalidArgument, "invalid 'secure'");
bool httpOnly = false;
if (!GetOptionalBool(cookie, "httpOnly", &httpOnly))
return Status(kInvalidArgument, "invalid 'httpOnly'");
double expiry;
bool has_value;
if (session->w3c_compliant) {
// W3C spec says expiry is a safe integer.
int64_t expiry_int64;
if (!GetOptionalSafeInt(cookie, "expiry", &expiry_int64, &has_value) ||
(has_value && expiry_int64 < 0))
return Status(kInvalidArgument, "invalid 'expiry'");
// Use negative value to indicate expiry not specified.
expiry = has_value ? static_cast<double>(expiry_int64) : -1.0;
} else {
// JSON wire protocol didn't specify the type of expiry, but ChromeDriver
// has always accepted double, so we keep that in legacy mode.
if (!GetOptionalDouble(cookie, "expiry", &expiry, &has_value) ||
(has_value && expiry < 0))
return Status(kInvalidArgument, "invalid 'expiry'");
if (!has_value)
expiry = (base::Time::Now() - base::Time::UnixEpoch()).InSeconds() +
kDefaultCookieExpiryTime;
}
return web_view->AddCookie(name, url, cookie_value, domain, path,
secure, httpOnly, expiry);
}
Status ExecuteDeleteCookie(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string name;
if (!params.GetString("name", &name))
return Status(kInvalidArgument, "missing 'name'");
base::DictionaryValue params_url;
std::unique_ptr<base::Value> value_url;
std::string url;
Status status = GetUrl(web_view, session->GetCurrentFrameId(), &url);
if (status.IsError())
return status;
std::list<Cookie> cookies;
status = GetVisibleCookies(web_view, &cookies);
if (status.IsError())
return status;
for (std::list<Cookie>::const_iterator it = cookies.begin();
it != cookies.end(); ++it) {
if (name == it->name) {
status = web_view->DeleteCookie(it->name, url, it->domain, it->path);
if (status.IsError())
return status;
}
}
return Status(kOk);
}
Status ExecuteDeleteAllCookies(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::list<Cookie> cookies;
Status status = GetVisibleCookies(web_view, &cookies);
if (status.IsError())
return status;
if (!cookies.empty()) {
base::DictionaryValue params_url;
std::unique_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, it->domain, it->path);
if (status.IsError())
return status;
}
}
return Status(kOk);
}
Status ExecuteSetLocation(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
const base::DictionaryValue* location = NULL;
Geoposition geoposition;
if (!params.GetDictionary("location", &location) ||
!location->GetDouble("latitude", &geoposition.latitude) ||
!location->GetDouble("longitude", &geoposition.longitude))
return Status(kInvalidArgument, "missing or invalid 'location'");
if (location->HasKey("accuracy") &&
!location->GetDouble("accuracy", &geoposition.accuracy)) {
return Status(kInvalidArgument, "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 ExecuteSetNetworkConditions(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string network_name;
const base::DictionaryValue* conditions = NULL;
std::unique_ptr<NetworkConditions> network_conditions(
new NetworkConditions());
if (params.GetString("network_name", &network_name)) {
// Get conditions from preset list.
Status status = FindPresetNetwork(network_name, network_conditions.get());
if (status.IsError())
return status;
} else if (params.GetDictionary("network_conditions", &conditions)) {
// |latency| is required.
if (!conditions->GetDouble("latency", &network_conditions->latency))
return Status(kInvalidArgument,
"invalid 'network_conditions' is missing 'latency'");
// Either |throughput| or the pair |download_throughput| and
// |upload_throughput| is required.
if (conditions->HasKey("throughput")) {
if (!conditions->GetDouble("throughput",
&network_conditions->download_throughput))
return Status(kInvalidArgument, "invalid 'throughput'");
conditions->GetDouble("throughput",
&network_conditions->upload_throughput);
} else if (conditions->HasKey("download_throughput") &&
conditions->HasKey("upload_throughput")) {
if (!conditions->GetDouble("download_throughput",
&network_conditions->download_throughput) ||
!conditions->GetDouble("upload_throughput",
&network_conditions->upload_throughput))
return Status(kInvalidArgument,
"invalid 'download_throughput' or 'upload_throughput'");
} else {
return Status(kInvalidArgument,
"invalid 'network_conditions' is missing 'throughput' or "
"'download_throughput'/'upload_throughput' pair");
}
// |offline| is optional.
if (conditions->HasKey("offline")) {
if (!conditions->GetBoolean("offline", &network_conditions->offline))
return Status(kInvalidArgument, "invalid 'offline'");
} else {
network_conditions->offline = false;
}
} else {
return Status(kInvalidArgument,
"either 'network_conditions' or 'network_name' must be "
"supplied");
}
session->overridden_network_conditions.reset(
network_conditions.release());
return web_view->OverrideNetworkConditions(
*session->overridden_network_conditions);
}
Status ExecuteDeleteNetworkConditions(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
// Chrome does not have any command to stop overriding network conditions, so
// we just override the network conditions with the "No throttling" preset.
NetworkConditions network_conditions;
// Get conditions from preset list.
Status status = FindPresetNetwork("No throttling", &network_conditions);
if (status.IsError())
return status;
status = web_view->OverrideNetworkConditions(network_conditions);
if (status.IsError())
return status;
// After we've successfully overridden the network conditions with
// "No throttling", we can delete them from |session|.
session->overridden_network_conditions.reset();
return status;
}
Status ExecuteTakeHeapSnapshot(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
return web_view->TakeHeapSnapshot(value);
}
// TODO(johnchen): There is no public method in Chrome or ChromeDesktopImpl to
// get both size and position in one call. What we're doing now is kind of
// wasteful, since both GetWindowPosition and GetWindowSize end up getting both
// position and size, and then discard one of the two pieces.
Status ExecuteGetWindowRect(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
int x, y;
int width, height;
Status status = session->chrome->GetWindowPosition(session->window, &x, &y);
if (status.IsError())
return status;
status = session->chrome->GetWindowSize(session->window, &width, &height);
if (status.IsError())
return status;
base::DictionaryValue rect;
rect.SetInteger("x", x);
rect.SetInteger("y", y);
rect.SetInteger("width", width);
rect.SetInteger("height", height);
value->reset(rect.DeepCopy());
return Status(kOk);
}
Status ExecuteSetWindowRect(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
const double max_range = 2147483647; // 2^31 - 1
const double min_range = -2147483648; // -2^31
const base::Value* temp;
double width = 0;
double height = 0;
double x = 0;
double y = 0;
bool has_x = params.Get("x", &temp) && !temp->is_none();
if (has_x) {
if (!temp->GetAsDouble(&x))
return Status(kInvalidArgument, "'x' must be a number");
if (x > max_range || x < min_range)
return Status(kInvalidArgument, "'x' out of range");
}
bool has_y = params.Get("y", &temp) && !temp->is_none();
if (has_y) {
if (!temp->GetAsDouble(&y))
return Status(kInvalidArgument, "'y' must be a number");
if (y > max_range || y < min_range )
return Status(kInvalidArgument, "'y' out of range");
}
bool has_width = params.Get("width", &temp) && !temp->is_none();
if (has_width) {
if (!temp->GetAsDouble(&width))
return Status(kInvalidArgument, "'width' must be a number");
if (width > max_range || width < 0 )
return Status(kInvalidArgument, "'width' out of range");
}
bool has_height = params.Get("height", &temp) && !temp->is_none();
if (has_height) {
if (!temp->GetAsDouble(&height))
return Status(kInvalidArgument, "'height' must be a number");
if (height > max_range || height < 0 )
return Status(kInvalidArgument, "'height' out of range");
}
// to pass to the set window rect command
base::DictionaryValue rect_params;
// only set position if both x and y are given
if (has_x && has_y) {
rect_params.SetInteger("x", static_cast<int>(x));
rect_params.SetInteger("y", static_cast<int>(y));
} // only set size if both height and width are given
if (has_width && has_height) {
rect_params.SetInteger("width", static_cast<int>(width));
rect_params.SetInteger("height", static_cast<int>(height));
}
Status status = session->chrome->SetWindowRect(session->window, rect_params);
if (status.IsError())
return status;
// return the current window rect
return ExecuteGetWindowRect(session, web_view, params, value, timeout);
}
Status ExecuteMaximizeWindow(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
Status status = session->chrome->MaximizeWindow(session->window);
if (status.IsError())
return status;
return ExecuteGetWindowRect(session, web_view, params, value, timeout);
}
Status ExecuteMinimizeWindow(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
Status status = session->chrome->MinimizeWindow(session->window);
if (status.IsError())
return status;
return ExecuteGetWindowRect(session, web_view, params, value, timeout);
}
Status ExecuteFullScreenWindow(Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
Status status = session->chrome->FullScreenWindow(session->window);
if (status.IsError())
return status;
return ExecuteGetWindowRect(session, web_view, params, value, timeout);
}