2365 lines
96 KiB
C++
2365 lines
96 KiB
C++
/*
|
|
* Copyright (C) 2017 Igalia S.L.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "WebDriverService.h"
|
|
|
|
#include "Capabilities.h"
|
|
#include "CommandResult.h"
|
|
#include "SessionHost.h"
|
|
#include <wtf/RunLoop.h>
|
|
#include <wtf/text/StringToIntegerConversion.h>
|
|
#include <wtf/text/WTFString.h>
|
|
|
|
namespace WebDriver {
|
|
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-maximum-safe-integer
|
|
static const double maxSafeInteger = 9007199254740991.0; // 2 ^ 53 - 1
|
|
|
|
WebDriverService::WebDriverService()
|
|
: m_server(*this)
|
|
{
|
|
}
|
|
|
|
static void printUsageStatement(const char* programName)
|
|
{
|
|
printf("Usage: %s options\n", programName);
|
|
printf(" -h, --help Prints this help message\n");
|
|
printf(" -p <port>, --port=<port> Port number the driver will use\n");
|
|
printf(" --host=<host> Host IP the driver will use, or either 'local' or 'all' (default: 'local')\n");
|
|
#if USE(INSPECTOR_SOCKET_SERVER)
|
|
printf(" -t <ip:port> --target=<ip:port> [WinCairo] Target IP and port\n");
|
|
#endif
|
|
}
|
|
|
|
int WebDriverService::run(int argc, char** argv)
|
|
{
|
|
String portString;
|
|
std::optional<String> host;
|
|
#if USE(INSPECTOR_SOCKET_SERVER)
|
|
String targetString;
|
|
if (const char* targetEnvVar = getenv("WEBDRIVER_TARGET_ADDR"))
|
|
targetString = targetEnvVar;
|
|
#endif
|
|
for (int i = 1 ; i < argc; ++i) {
|
|
const char* arg = argv[i];
|
|
if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
|
|
printUsageStatement(argv[0]);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
if (!strcmp(arg, "-p") && portString.isNull()) {
|
|
if (++i == argc) {
|
|
printUsageStatement(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
portString = argv[i];
|
|
continue;
|
|
}
|
|
|
|
static const unsigned portStrLength = strlen("--port=");
|
|
if (!strncmp(arg, "--port=", portStrLength) && portString.isNull()) {
|
|
portString = String(arg + portStrLength);
|
|
continue;
|
|
}
|
|
|
|
static const unsigned hostStrLength = strlen("--host=");
|
|
if (!strncmp(arg, "--host=", hostStrLength) && !host) {
|
|
host = String(arg + hostStrLength);
|
|
continue;
|
|
}
|
|
|
|
#if USE(INSPECTOR_SOCKET_SERVER)
|
|
if (!strcmp(arg, "-t") && targetString.isNull()) {
|
|
if (++i == argc) {
|
|
printUsageStatement(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
targetString = argv[i];
|
|
continue;
|
|
}
|
|
|
|
static const unsigned targetStrLength = strlen("--target=");
|
|
if (!strncmp(arg, "--target=", targetStrLength) && targetString.isNull()) {
|
|
targetString = String(arg + targetStrLength);
|
|
continue;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (portString.isNull()) {
|
|
printUsageStatement(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
#if USE(INSPECTOR_SOCKET_SERVER)
|
|
if (!targetString.isEmpty()) {
|
|
auto position = targetString.reverseFind(":"_s);
|
|
if (position != notFound) {
|
|
m_targetAddress = targetString.left(position);
|
|
m_targetPort = parseIntegerAllowingTrailingJunk<uint16_t>(StringView { targetString }.substring(position + 1)).value_or(0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
auto port = parseInteger<uint16_t>(portString);
|
|
if (!port) {
|
|
fprintf(stderr, "Invalid port %s provided\n", portString.utf8().data());
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
WTF::initializeMainThread();
|
|
|
|
if (!m_server.listen(host, *port))
|
|
return EXIT_FAILURE;
|
|
|
|
RunLoop::run();
|
|
|
|
m_server.disconnect();
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
const WebDriverService::Command WebDriverService::s_commands[] = {
|
|
{ HTTPMethod::Post, "/session", &WebDriverService::newSession },
|
|
{ HTTPMethod::Delete, "/session/$sessionId", &WebDriverService::deleteSession },
|
|
{ HTTPMethod::Get, "/status", &WebDriverService::status },
|
|
{ HTTPMethod::Get, "/session/$sessionId/timeouts", &WebDriverService::getTimeouts },
|
|
{ HTTPMethod::Post, "/session/$sessionId/timeouts", &WebDriverService::setTimeouts },
|
|
|
|
{ HTTPMethod::Post, "/session/$sessionId/url", &WebDriverService::go },
|
|
{ HTTPMethod::Get, "/session/$sessionId/url", &WebDriverService::getCurrentURL },
|
|
{ HTTPMethod::Post, "/session/$sessionId/back", &WebDriverService::back },
|
|
{ HTTPMethod::Post, "/session/$sessionId/forward", &WebDriverService::forward },
|
|
{ HTTPMethod::Post, "/session/$sessionId/refresh", &WebDriverService::refresh },
|
|
{ HTTPMethod::Get, "/session/$sessionId/title", &WebDriverService::getTitle },
|
|
|
|
{ HTTPMethod::Get, "/session/$sessionId/window", &WebDriverService::getWindowHandle },
|
|
{ HTTPMethod::Delete, "/session/$sessionId/window", &WebDriverService::closeWindow },
|
|
{ HTTPMethod::Post, "/session/$sessionId/window", &WebDriverService::switchToWindow },
|
|
{ HTTPMethod::Get, "/session/$sessionId/window/handles", &WebDriverService::getWindowHandles },
|
|
{ HTTPMethod::Post, "/session/$sessionId/window/new", &WebDriverService::newWindow },
|
|
{ HTTPMethod::Post, "/session/$sessionId/frame", &WebDriverService::switchToFrame },
|
|
{ HTTPMethod::Post, "/session/$sessionId/frame/parent", &WebDriverService::switchToParentFrame },
|
|
{ HTTPMethod::Get, "/session/$sessionId/window/rect", &WebDriverService::getWindowRect },
|
|
{ HTTPMethod::Post, "/session/$sessionId/window/rect", &WebDriverService::setWindowRect },
|
|
{ HTTPMethod::Post, "/session/$sessionId/window/maximize", &WebDriverService::maximizeWindow },
|
|
{ HTTPMethod::Post, "/session/$sessionId/window/minimize", &WebDriverService::minimizeWindow },
|
|
{ HTTPMethod::Post, "/session/$sessionId/window/fullscreen", &WebDriverService::fullscreenWindow },
|
|
|
|
{ HTTPMethod::Post, "/session/$sessionId/element", &WebDriverService::findElement },
|
|
{ HTTPMethod::Post, "/session/$sessionId/elements", &WebDriverService::findElements },
|
|
{ HTTPMethod::Post, "/session/$sessionId/element/$elementId/element", &WebDriverService::findElementFromElement },
|
|
{ HTTPMethod::Post, "/session/$sessionId/element/$elementId/elements", &WebDriverService::findElementsFromElement },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/active", &WebDriverService::getActiveElement },
|
|
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/selected", &WebDriverService::isElementSelected },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/attribute/$name", &WebDriverService::getElementAttribute },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/property/$name", &WebDriverService::getElementProperty },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/css/$name", &WebDriverService::getElementCSSValue },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/text", &WebDriverService::getElementText },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/name", &WebDriverService::getElementTagName },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/rect", &WebDriverService::getElementRect },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/enabled", &WebDriverService::isElementEnabled },
|
|
|
|
{ HTTPMethod::Post, "/session/$sessionId/element/$elementId/click", &WebDriverService::elementClick },
|
|
{ HTTPMethod::Post, "/session/$sessionId/element/$elementId/clear", &WebDriverService::elementClear },
|
|
{ HTTPMethod::Post, "/session/$sessionId/element/$elementId/value", &WebDriverService::elementSendKeys },
|
|
|
|
{ HTTPMethod::Get, "/session/$sessionId/source", &WebDriverService::getPageSource },
|
|
{ HTTPMethod::Post, "/session/$sessionId/execute/sync", &WebDriverService::executeScript },
|
|
{ HTTPMethod::Post, "/session/$sessionId/execute/async", &WebDriverService::executeAsyncScript },
|
|
|
|
{ HTTPMethod::Get, "/session/$sessionId/cookie", &WebDriverService::getAllCookies },
|
|
{ HTTPMethod::Get, "/session/$sessionId/cookie/$name", &WebDriverService::getNamedCookie },
|
|
{ HTTPMethod::Post, "/session/$sessionId/cookie", &WebDriverService::addCookie },
|
|
{ HTTPMethod::Delete, "/session/$sessionId/cookie/$name", &WebDriverService::deleteCookie },
|
|
{ HTTPMethod::Delete, "/session/$sessionId/cookie", &WebDriverService::deleteAllCookies },
|
|
|
|
{ HTTPMethod::Post, "/session/$sessionId/actions", &WebDriverService::performActions },
|
|
{ HTTPMethod::Delete, "/session/$sessionId/actions", &WebDriverService::releaseActions },
|
|
|
|
{ HTTPMethod::Post, "/session/$sessionId/alert/dismiss", &WebDriverService::dismissAlert },
|
|
{ HTTPMethod::Post, "/session/$sessionId/alert/accept", &WebDriverService::acceptAlert },
|
|
{ HTTPMethod::Get, "/session/$sessionId/alert/text", &WebDriverService::getAlertText },
|
|
{ HTTPMethod::Post, "/session/$sessionId/alert/text", &WebDriverService::sendAlertText },
|
|
|
|
{ HTTPMethod::Get, "/session/$sessionId/screenshot", &WebDriverService::takeScreenshot },
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/screenshot", &WebDriverService::takeElementScreenshot },
|
|
|
|
|
|
{ HTTPMethod::Get, "/session/$sessionId/element/$elementId/displayed", &WebDriverService::isElementDisplayed },
|
|
};
|
|
|
|
std::optional<WebDriverService::HTTPMethod> WebDriverService::toCommandHTTPMethod(const String& method)
|
|
{
|
|
auto lowerCaseMethod = method.convertToASCIILowercase();
|
|
if (lowerCaseMethod == "get")
|
|
return WebDriverService::HTTPMethod::Get;
|
|
if (lowerCaseMethod == "post" || lowerCaseMethod == "put")
|
|
return WebDriverService::HTTPMethod::Post;
|
|
if (lowerCaseMethod == "delete")
|
|
return WebDriverService::HTTPMethod::Delete;
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool WebDriverService::findCommand(HTTPMethod method, const String& path, CommandHandler* handler, HashMap<String, String>& parameters)
|
|
{
|
|
size_t length = WTF_ARRAY_LENGTH(s_commands);
|
|
for (size_t i = 0; i < length; ++i) {
|
|
if (s_commands[i].method != method)
|
|
continue;
|
|
|
|
Vector<String> pathTokens = path.split('/');
|
|
Vector<String> commandTokens = String::fromUTF8(s_commands[i].uriTemplate).split('/');
|
|
if (pathTokens.size() != commandTokens.size())
|
|
continue;
|
|
|
|
bool allMatched = true;
|
|
for (size_t j = 0; j < pathTokens.size() && allMatched; ++j) {
|
|
if (commandTokens[j][0] == '$')
|
|
parameters.set(commandTokens[j].substring(1), pathTokens[j]);
|
|
else if (commandTokens[j] != pathTokens[j])
|
|
allMatched = false;
|
|
}
|
|
|
|
if (allMatched) {
|
|
*handler = s_commands[i].handler;
|
|
return true;
|
|
}
|
|
|
|
parameters.clear();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void WebDriverService::handleRequest(HTTPRequestHandler::Request&& request, Function<void (HTTPRequestHandler::Response&&)>&& replyHandler)
|
|
{
|
|
auto method = toCommandHTTPMethod(request.method);
|
|
if (!method) {
|
|
sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown method: " + request.method)));
|
|
return;
|
|
}
|
|
CommandHandler handler;
|
|
HashMap<String, String> parameters;
|
|
if (!findCommand(method.value(), request.path, &handler, parameters)) {
|
|
sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown command: " + request.path)));
|
|
return;
|
|
}
|
|
|
|
RefPtr<JSON::Object> parametersObject;
|
|
if (method.value() == HTTPMethod::Post) {
|
|
auto messageValue = JSON::Value::parseJSON(String::fromUTF8(request.data, request.dataLength));
|
|
if (!messageValue) {
|
|
sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
parametersObject = messageValue->asObject();
|
|
if (!parametersObject) {
|
|
sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
} else
|
|
parametersObject = JSON::Object::create();
|
|
for (const auto& parameter : parameters)
|
|
parametersObject->setString(parameter.key, parameter.value);
|
|
|
|
((*this).*handler)(WTFMove(parametersObject), [this, replyHandler = WTFMove(replyHandler)](CommandResult&& result) mutable {
|
|
sendResponse(WTFMove(replyHandler), WTFMove(result));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::sendResponse(Function<void (HTTPRequestHandler::Response&&)>&& replyHandler, CommandResult&& result) const
|
|
{
|
|
// §6.3 Processing Model.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#processing-model
|
|
RefPtr<JSON::Value> resultValue;
|
|
if (result.isError()) {
|
|
// When required to send an error.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-an-error
|
|
// Let body be a new JSON Object initialised with the following properties: "error", "message", "stacktrace".
|
|
auto errorObject = JSON::Object::create();
|
|
errorObject->setString("error"_s, result.errorString());
|
|
errorObject->setString("message"_s, result.errorMessage().value_or(emptyString()));
|
|
errorObject->setString("stacktrace"_s, emptyString());
|
|
// If the error data dictionary contains any entries, set the "data" field on body to a new JSON Object populated with the dictionary.
|
|
if (auto& additionalData = result.additionalErrorData())
|
|
errorObject->setObject("data"_s, *additionalData);
|
|
// Send a response with status and body as arguments.
|
|
resultValue = WTFMove(errorObject);
|
|
} else if (auto value = result.result())
|
|
resultValue = WTFMove(value);
|
|
else
|
|
resultValue = JSON::Value::null();
|
|
|
|
// When required to send a response.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-a-response
|
|
auto responseObject = JSON::Object::create();
|
|
responseObject->setValue("value"_s, resultValue.releaseNonNull());
|
|
replyHandler({ result.httpStatusCode(), responseObject->toJSONString().utf8(), "application/json; charset=utf-8"_s });
|
|
}
|
|
|
|
static std::optional<double> valueAsNumberInRange(const JSON::Value& value, double minAllowed = 0, double maxAllowed = std::numeric_limits<int>::max())
|
|
{
|
|
auto number = value.asDouble();
|
|
if (!number)
|
|
return std::nullopt;
|
|
|
|
if (std::isnan(*number) || std::isinf(*number))
|
|
return std::nullopt;
|
|
|
|
if (*number < minAllowed || *number > maxAllowed)
|
|
return std::nullopt;
|
|
|
|
return *number;
|
|
}
|
|
|
|
static std::optional<uint64_t> unsignedValue(JSON::Value& value)
|
|
{
|
|
auto number = valueAsNumberInRange(value, 0, maxSafeInteger);
|
|
if (!number)
|
|
return std::nullopt;
|
|
|
|
auto intValue = static_cast<uint64_t>(number.value());
|
|
// If the contained value is a double, bail in case it doesn't match the integer
|
|
// value, i.e. if the double value was not originally in integer form.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-integer
|
|
if (number.value() != intValue)
|
|
return std::nullopt;
|
|
|
|
return intValue;
|
|
}
|
|
|
|
enum class IgnoreUnknownTimeout { No, Yes };
|
|
|
|
static std::optional<Timeouts> deserializeTimeouts(JSON::Object& timeoutsObject, IgnoreUnknownTimeout ignoreUnknownTimeout)
|
|
{
|
|
// §8.5 Set Timeouts.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-deserialize-as-a-timeout
|
|
Timeouts timeouts;
|
|
auto end = timeoutsObject.end();
|
|
for (auto it = timeoutsObject.begin(); it != end; ++it) {
|
|
if (it->key == "sessionId")
|
|
continue;
|
|
|
|
if (it->key == "script" && it->value->isNull()) {
|
|
timeouts.script = std::numeric_limits<double>::infinity();
|
|
continue;
|
|
}
|
|
|
|
// If value is not an integer, or it is less than 0 or greater than the maximum safe integer, return error with error code invalid argument.
|
|
auto timeoutMS = unsignedValue(it->value);
|
|
if (!timeoutMS)
|
|
return std::nullopt;
|
|
|
|
if (it->key == "script")
|
|
timeouts.script = timeoutMS.value();
|
|
else if (it->key == "pageLoad")
|
|
timeouts.pageLoad = timeoutMS.value();
|
|
else if (it->key == "implicit")
|
|
timeouts.implicit = timeoutMS.value();
|
|
else if (ignoreUnknownTimeout == IgnoreUnknownTimeout::No)
|
|
return std::nullopt;
|
|
}
|
|
return timeouts;
|
|
}
|
|
|
|
static std::optional<Proxy> deserializeProxy(JSON::Object& proxyObject)
|
|
{
|
|
// §7.1 Proxy.
|
|
// https://w3c.github.io/webdriver/#proxy
|
|
Proxy proxy;
|
|
|
|
proxy.type = proxyObject.getString("proxyType"_s);
|
|
if (!proxy.type)
|
|
return std::nullopt;
|
|
|
|
if (proxy.type == "direct" || proxy.type == "autodetect" || proxy.type == "system")
|
|
return proxy;
|
|
|
|
if (proxy.type == "pac") {
|
|
proxy.autoconfigURL = proxyObject.getString("proxyAutoconfigUrl"_s);
|
|
if (!proxy.autoconfigURL)
|
|
return std::nullopt;
|
|
|
|
return proxy;
|
|
}
|
|
|
|
if (proxy.type == "manual") {
|
|
if (auto value = proxyObject.getValue("ftpProxy"_s)) {
|
|
auto ftpProxy = value->asString();
|
|
if (!ftpProxy)
|
|
return std::nullopt;
|
|
|
|
proxy.ftpURL = URL({ }, makeString("ftp://", ftpProxy));
|
|
if (!proxy.ftpURL->isValid())
|
|
return std::nullopt;
|
|
}
|
|
if (auto value = proxyObject.getValue("httpProxy"_s)) {
|
|
auto httpProxy = value->asString();
|
|
if (!httpProxy)
|
|
return std::nullopt;
|
|
|
|
proxy.httpURL = URL({ }, makeString("http://", httpProxy));
|
|
if (!proxy.httpURL->isValid())
|
|
return std::nullopt;
|
|
}
|
|
if (auto value = proxyObject.getValue("sslProxy"_s)) {
|
|
auto sslProxy = value->asString();
|
|
if (!sslProxy)
|
|
return std::nullopt;
|
|
|
|
proxy.httpsURL = URL({ }, makeString("https://", sslProxy));
|
|
if (!proxy.httpsURL->isValid())
|
|
return std::nullopt;
|
|
}
|
|
if (auto value = proxyObject.getValue("socksProxy")) {
|
|
auto socksProxy = value->asString();
|
|
if (!socksProxy)
|
|
return std::nullopt;
|
|
|
|
proxy.socksURL = URL({ }, makeString("socks://", socksProxy));
|
|
if (!proxy.socksURL->isValid())
|
|
return std::nullopt;
|
|
|
|
auto socksVersionValue = proxyObject.getValue("socksVersion");
|
|
if (!socksVersionValue)
|
|
return std::nullopt;
|
|
|
|
auto socksVersion = unsignedValue(*socksVersionValue);
|
|
if (!socksVersion || socksVersion.value() > 255)
|
|
return std::nullopt;
|
|
proxy.socksVersion = socksVersion.value();
|
|
}
|
|
if (auto value = proxyObject.getValue("noProxy"_s)) {
|
|
auto noProxy = value->asArray();
|
|
if (!noProxy)
|
|
return std::nullopt;
|
|
|
|
auto noProxyLength = noProxy->length();
|
|
for (unsigned i = 0; i < noProxyLength; ++i) {
|
|
auto address = noProxy->get(i)->asString();
|
|
if (!address)
|
|
return std::nullopt;
|
|
proxy.ignoreAddressList.append(address);
|
|
}
|
|
}
|
|
|
|
return proxy;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
static std::optional<PageLoadStrategy> deserializePageLoadStrategy(const String& pageLoadStrategy)
|
|
{
|
|
if (pageLoadStrategy == "none")
|
|
return PageLoadStrategy::None;
|
|
if (pageLoadStrategy == "normal")
|
|
return PageLoadStrategy::Normal;
|
|
if (pageLoadStrategy == "eager")
|
|
return PageLoadStrategy::Eager;
|
|
return std::nullopt;
|
|
}
|
|
|
|
static std::optional<UnhandledPromptBehavior> deserializeUnhandledPromptBehavior(const String& unhandledPromptBehavior)
|
|
{
|
|
if (unhandledPromptBehavior == "dismiss")
|
|
return UnhandledPromptBehavior::Dismiss;
|
|
if (unhandledPromptBehavior == "accept")
|
|
return UnhandledPromptBehavior::Accept;
|
|
if (unhandledPromptBehavior == "dismiss and notify")
|
|
return UnhandledPromptBehavior::DismissAndNotify;
|
|
if (unhandledPromptBehavior == "accept and notify")
|
|
return UnhandledPromptBehavior::AcceptAndNotify;
|
|
if (unhandledPromptBehavior == "ignore")
|
|
return UnhandledPromptBehavior::Ignore;
|
|
return std::nullopt;
|
|
}
|
|
|
|
void WebDriverService::parseCapabilities(const JSON::Object& matchedCapabilities, Capabilities& capabilities) const
|
|
{
|
|
// Matched capabilities have already been validated.
|
|
auto acceptInsecureCerts = matchedCapabilities.getBoolean("acceptInsecureCerts"_s);
|
|
if (acceptInsecureCerts)
|
|
capabilities.acceptInsecureCerts = *acceptInsecureCerts;
|
|
|
|
auto setWindowRect = matchedCapabilities.getBoolean("setWindowRect"_s);
|
|
if (setWindowRect)
|
|
capabilities.setWindowRect = *setWindowRect;
|
|
|
|
auto browserName = matchedCapabilities.getString("browserName"_s);
|
|
if (!!browserName)
|
|
capabilities.browserName = browserName;
|
|
|
|
auto browserVersion = matchedCapabilities.getString("browserVersion"_s);
|
|
if (!!browserVersion)
|
|
capabilities.browserVersion = browserVersion;
|
|
|
|
auto platformName = matchedCapabilities.getString("platformName"_s);
|
|
if (!!platformName)
|
|
capabilities.platformName = platformName;
|
|
|
|
auto proxy = matchedCapabilities.getObject("proxy"_s);
|
|
if (proxy)
|
|
capabilities.proxy = deserializeProxy(*proxy);
|
|
|
|
auto strictFileInteractability = matchedCapabilities.getBoolean("strictFileInteractability"_s);
|
|
if (strictFileInteractability)
|
|
capabilities.strictFileInteractability = *strictFileInteractability;
|
|
|
|
auto timeouts = matchedCapabilities.getObject("timeouts"_s);
|
|
if (timeouts)
|
|
capabilities.timeouts = deserializeTimeouts(*timeouts, IgnoreUnknownTimeout::No);
|
|
|
|
auto pageLoadStrategy = matchedCapabilities.getString("pageLoadStrategy"_s);
|
|
if (!!pageLoadStrategy)
|
|
capabilities.pageLoadStrategy = deserializePageLoadStrategy(pageLoadStrategy);
|
|
|
|
auto unhandledPromptBehavior = matchedCapabilities.getString("unhandledPromptBehavior"_s);
|
|
if (!!unhandledPromptBehavior)
|
|
capabilities.unhandledPromptBehavior = deserializeUnhandledPromptBehavior(unhandledPromptBehavior);
|
|
|
|
platformParseCapabilities(matchedCapabilities, capabilities);
|
|
}
|
|
|
|
bool WebDriverService::findSessionOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
|
|
{
|
|
auto sessionID = parameters.getString("sessionId"_s);
|
|
if (!sessionID) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return false;
|
|
}
|
|
|
|
if (!m_session || m_session->id() != sessionID) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
|
|
return false;
|
|
}
|
|
|
|
if (!m_session->isConnected()) {
|
|
m_session = nullptr;
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID, String("session deleted because of page crash or hang.")));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
RefPtr<JSON::Object> WebDriverService::validatedCapabilities(const JSON::Object& capabilities) const
|
|
{
|
|
// §7.2 Processing Capabilities.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-validate-capabilities
|
|
auto result = JSON::Object::create();
|
|
auto end = capabilities.end();
|
|
for (auto it = capabilities.begin(); it != end; ++it) {
|
|
if (it->value->isNull())
|
|
continue;
|
|
if (it->key == "acceptInsecureCerts") {
|
|
auto acceptInsecureCerts = it->value->asBoolean();
|
|
if (!acceptInsecureCerts)
|
|
return nullptr;
|
|
result->setBoolean(it->key, *acceptInsecureCerts);
|
|
} else if (it->key == "browserName" || it->key == "browserVersion" || it->key == "platformName") {
|
|
auto stringValue = it->value->asString();
|
|
if (!stringValue)
|
|
return nullptr;
|
|
result->setString(it->key, stringValue);
|
|
} else if (it->key == "pageLoadStrategy") {
|
|
auto pageLoadStrategy = it->value->asString();
|
|
if (!pageLoadStrategy || !deserializePageLoadStrategy(pageLoadStrategy))
|
|
return nullptr;
|
|
result->setString(it->key, pageLoadStrategy);
|
|
} else if (it->key == "proxy") {
|
|
auto proxy = it->value->asObject();
|
|
if (!proxy || !deserializeProxy(*proxy))
|
|
return nullptr;
|
|
result->setValue(it->key, *proxy);
|
|
} else if (it->key == "strictFileInteractability") {
|
|
auto strictFileInteractability = it->value->asBoolean();
|
|
if (!strictFileInteractability)
|
|
return nullptr;
|
|
result->setBoolean(it->key, *strictFileInteractability);
|
|
} else if (it->key == "timeouts") {
|
|
auto timeouts = it->value->asObject();
|
|
if (!timeouts || !deserializeTimeouts(*timeouts, IgnoreUnknownTimeout::No))
|
|
return nullptr;
|
|
result->setValue(it->key, *timeouts);
|
|
} else if (it->key == "unhandledPromptBehavior") {
|
|
auto unhandledPromptBehavior = it->value->asString();
|
|
if (!unhandledPromptBehavior || !deserializeUnhandledPromptBehavior(unhandledPromptBehavior))
|
|
return nullptr;
|
|
result->setString(it->key, unhandledPromptBehavior);
|
|
} else if (it->key.find(":") != notFound) {
|
|
if (!platformValidateCapability(it->key, it->value))
|
|
return nullptr;
|
|
result->setValue(it->key, it->value.copyRef());
|
|
} else
|
|
return nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
RefPtr<JSON::Object> WebDriverService::mergeCapabilities(const JSON::Object& requiredCapabilities, const JSON::Object& firstMatchCapabilities) const
|
|
{
|
|
// §7.2 Processing Capabilities.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-merging-capabilities
|
|
auto result = JSON::Object::create();
|
|
auto requiredEnd = requiredCapabilities.end();
|
|
for (auto it = requiredCapabilities.begin(); it != requiredEnd; ++it)
|
|
result->setValue(it->key, it->value.copyRef());
|
|
|
|
auto firstMatchEnd = firstMatchCapabilities.end();
|
|
for (auto it = firstMatchCapabilities.begin(); it != firstMatchEnd; ++it)
|
|
result->setValue(it->key, it->value.copyRef());
|
|
|
|
return result;
|
|
}
|
|
|
|
RefPtr<JSON::Object> WebDriverService::matchCapabilities(const JSON::Object& mergedCapabilities) const
|
|
{
|
|
// §7.2 Processing Capabilities.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-matching-capabilities
|
|
Capabilities platformCapabilities = this->platformCapabilities();
|
|
|
|
// Some capabilities like browser name and version might need to launch the browser,
|
|
// so we only reject the known capabilities that don't match.
|
|
auto matchedCapabilities = JSON::Object::create();
|
|
if (platformCapabilities.browserName)
|
|
matchedCapabilities->setString("browserName"_s, platformCapabilities.browserName.value());
|
|
if (platformCapabilities.browserVersion)
|
|
matchedCapabilities->setString("browserVersion"_s, platformCapabilities.browserVersion.value());
|
|
if (platformCapabilities.platformName)
|
|
matchedCapabilities->setString("platformName"_s, platformCapabilities.platformName.value());
|
|
if (platformCapabilities.acceptInsecureCerts)
|
|
matchedCapabilities->setBoolean("acceptInsecureCerts"_s, platformCapabilities.acceptInsecureCerts.value());
|
|
if (platformCapabilities.strictFileInteractability)
|
|
matchedCapabilities->setBoolean("strictFileInteractability"_s, platformCapabilities.strictFileInteractability.value());
|
|
if (platformCapabilities.setWindowRect)
|
|
matchedCapabilities->setBoolean("setWindowRect"_s, platformCapabilities.setWindowRect.value());
|
|
|
|
auto end = mergedCapabilities.end();
|
|
for (auto it = mergedCapabilities.begin(); it != end; ++it) {
|
|
if (it->key == "browserName" && platformCapabilities.browserName) {
|
|
auto browserName = it->value->asString();
|
|
if (!equalIgnoringASCIICase(platformCapabilities.browserName.value(), browserName))
|
|
return nullptr;
|
|
} else if (it->key == "browserVersion" && platformCapabilities.browserVersion) {
|
|
auto browserVersion = it->value->asString();
|
|
if (!platformCompareBrowserVersions(browserVersion, platformCapabilities.browserVersion.value()))
|
|
return nullptr;
|
|
} else if (it->key == "platformName" && platformCapabilities.platformName) {
|
|
auto platformName = it->value->asString();
|
|
if (!equalLettersIgnoringASCIICase(platformName, "any") && platformCapabilities.platformName.value() != platformName)
|
|
return nullptr;
|
|
} else if (it->key == "acceptInsecureCerts" && platformCapabilities.acceptInsecureCerts) {
|
|
auto acceptInsecureCerts = it->value->asBoolean();
|
|
if (acceptInsecureCerts && !platformCapabilities.acceptInsecureCerts.value())
|
|
return nullptr;
|
|
} else if (it->key == "proxy") {
|
|
auto proxyType = it->value->asObject()->getString("proxyType"_s);
|
|
if (!platformSupportProxyType(proxyType))
|
|
return nullptr;
|
|
} else if (!platformMatchCapability(it->key, it->value))
|
|
return nullptr;
|
|
matchedCapabilities->setValue(it->key, it->value.copyRef());
|
|
}
|
|
|
|
return matchedCapabilities;
|
|
}
|
|
|
|
Vector<Capabilities> WebDriverService::processCapabilities(const JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler) const
|
|
{
|
|
// §7.2 Processing Capabilities.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
|
|
|
|
// 1. Let capabilities request be the result of getting the property "capabilities" from parameters.
|
|
auto capabilitiesObject = parameters.getObject("capabilities"_s);
|
|
if (!capabilitiesObject) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return { };
|
|
}
|
|
|
|
// 2. Let required capabilities be the result of getting the property "alwaysMatch" from capabilities request.
|
|
RefPtr<JSON::Object> requiredCapabilities;
|
|
auto requiredCapabilitiesValue = capabilitiesObject->getValue("alwaysMatch"_s);
|
|
if (!requiredCapabilitiesValue) {
|
|
// 2.1. If required capabilities is undefined, set the value to an empty JSON Object.
|
|
requiredCapabilities = JSON::Object::create();
|
|
} else if (!(requiredCapabilities = requiredCapabilitiesValue->asObject())) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("alwaysMatch is invalid in capabilities")));
|
|
return { };
|
|
}
|
|
|
|
// 2.2. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
|
|
requiredCapabilities = validatedCapabilities(*requiredCapabilities);
|
|
if (!requiredCapabilities) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid alwaysMatch capabilities")));
|
|
return { };
|
|
}
|
|
|
|
// 3. Let all first match capabilities be the result of getting the property "firstMatch" from capabilities request.
|
|
RefPtr<JSON::Array> firstMatchCapabilitiesList;
|
|
auto firstMatchCapabilitiesValue = capabilitiesObject->getValue("firstMatch"_s);
|
|
if (!firstMatchCapabilitiesValue) {
|
|
// 3.1. If all first match capabilities is undefined, set the value to a JSON List with a single entry of an empty JSON Object.
|
|
firstMatchCapabilitiesList = JSON::Array::create();
|
|
firstMatchCapabilitiesList->pushObject(JSON::Object::create());
|
|
} else {
|
|
firstMatchCapabilitiesList = firstMatchCapabilitiesValue->asArray();
|
|
if (!firstMatchCapabilitiesList) {
|
|
// 3.2. If all first match capabilities is not a JSON List, return error with error code invalid argument.
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("firstMatch is invalid in capabilities")));
|
|
return { };
|
|
}
|
|
}
|
|
|
|
// 4. Let validated first match capabilities be an empty JSON List.
|
|
Vector<RefPtr<JSON::Object>> validatedFirstMatchCapabilitiesList;
|
|
auto firstMatchCapabilitiesListLength = firstMatchCapabilitiesList->length();
|
|
validatedFirstMatchCapabilitiesList.reserveInitialCapacity(firstMatchCapabilitiesListLength);
|
|
// 5. For each first match capabilities corresponding to an indexed property in all first match capabilities.
|
|
for (unsigned i = 0; i < firstMatchCapabilitiesListLength; ++i) {
|
|
auto firstMatchCapabilities = firstMatchCapabilitiesList->get(i)->asObject();
|
|
if (!firstMatchCapabilities) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid capabilities found in firstMatch")));
|
|
return { };
|
|
}
|
|
// 5.1. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
|
|
firstMatchCapabilities = validatedCapabilities(*firstMatchCapabilities);
|
|
if (!firstMatchCapabilities) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid firstMatch capabilities")));
|
|
return { };
|
|
}
|
|
|
|
// Validate here that firstMatchCapabilities don't shadow alwaysMatchCapabilities.
|
|
auto requiredEnd = requiredCapabilities->end();
|
|
auto firstMatchEnd = firstMatchCapabilities->end();
|
|
for (auto it = firstMatchCapabilities->begin(); it != firstMatchEnd; ++it) {
|
|
if (requiredCapabilities->find(it->key) != requiredEnd) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument,
|
|
makeString("Invalid firstMatch capabilities: key ", it->key, " is present in alwaysMatch")));
|
|
return { };
|
|
}
|
|
}
|
|
|
|
// 5.2. Append validated capabilities to validated first match capabilities.
|
|
validatedFirstMatchCapabilitiesList.uncheckedAppend(WTFMove(firstMatchCapabilities));
|
|
}
|
|
|
|
// 6. For each first match capabilities corresponding to an indexed property in validated first match capabilities.
|
|
Vector<Capabilities> matchedCapabilitiesList;
|
|
matchedCapabilitiesList.reserveInitialCapacity(validatedFirstMatchCapabilitiesList.size());
|
|
for (auto& validatedFirstMatchCapabilies : validatedFirstMatchCapabilitiesList) {
|
|
// 6.1. Let merged capabilities be the result of trying to merge capabilities with required capabilities and first match capabilities as arguments.
|
|
auto mergedCapabilities = mergeCapabilities(*requiredCapabilities, *validatedFirstMatchCapabilies);
|
|
|
|
// 6.2. Let matched capabilities be the result of trying to match capabilities with merged capabilities as an argument.
|
|
if (auto matchedCapabilities = matchCapabilities(*mergedCapabilities)) {
|
|
// 6.3. If matched capabilities is not null return matched capabilities.
|
|
Capabilities capabilities;
|
|
parseCapabilities(*matchedCapabilities, capabilities);
|
|
matchedCapabilitiesList.uncheckedAppend(WTFMove(capabilities));
|
|
}
|
|
}
|
|
|
|
if (matchedCapabilitiesList.isEmpty()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to match capabilities")));
|
|
return { };
|
|
}
|
|
|
|
return matchedCapabilitiesList;
|
|
}
|
|
|
|
void WebDriverService::newSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §8.1 New Session.
|
|
// https://www.w3.org/TR/webdriver/#new-session
|
|
if (m_session) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Maximum number of active sessions")));
|
|
return;
|
|
}
|
|
|
|
auto matchedCapabilitiesList = processCapabilities(*parameters, completionHandler);
|
|
if (matchedCapabilitiesList.isEmpty())
|
|
return;
|
|
|
|
// Reverse the vector to always take last item.
|
|
matchedCapabilitiesList.reverse();
|
|
connectToBrowser(WTFMove(matchedCapabilitiesList), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::connectToBrowser(Vector<Capabilities>&& capabilitiesList, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
if (capabilitiesList.isEmpty()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to match capabilities")));
|
|
return;
|
|
}
|
|
|
|
auto sessionHost = makeUnique<SessionHost>(capabilitiesList.takeLast());
|
|
auto* sessionHostPtr = sessionHost.get();
|
|
#if USE(INSPECTOR_SOCKET_SERVER)
|
|
sessionHostPtr->setHostAddress(m_targetAddress, m_targetPort);
|
|
#endif
|
|
sessionHostPtr->connectToBrowser([this, capabilitiesList = WTFMove(capabilitiesList), sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](std::optional<String> error) mutable {
|
|
if (error) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, makeString("Failed to connect to browser: ", error.value())));
|
|
return;
|
|
}
|
|
|
|
createSession(WTFMove(capabilitiesList), WTFMove(sessionHost), WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::createSession(Vector<Capabilities>&& capabilitiesList, std::unique_ptr<SessionHost>&& sessionHost, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
auto* sessionHostPtr = sessionHost.get();
|
|
sessionHostPtr->startAutomationSession([this, capabilitiesList = WTFMove(capabilitiesList), sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](bool capabilitiesDidMatch, std::optional<String> errorMessage) mutable {
|
|
if (errorMessage) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError, errorMessage.value()));
|
|
return;
|
|
}
|
|
if (!capabilitiesDidMatch) {
|
|
connectToBrowser(WTFMove(capabilitiesList), WTFMove(completionHandler));
|
|
return;
|
|
}
|
|
|
|
RefPtr<Session> session = Session::create(WTFMove(sessionHost));
|
|
session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorMessage()));
|
|
return;
|
|
}
|
|
|
|
m_session = WTFMove(session);
|
|
|
|
auto resultObject = JSON::Object::create();
|
|
resultObject->setString("sessionId"_s, m_session->id());
|
|
auto capabilitiesObject = JSON::Object::create();
|
|
const auto& capabilities = m_session->capabilities();
|
|
capabilitiesObject->setString("browserName"_s, capabilities.browserName.value_or(emptyString()));
|
|
capabilitiesObject->setString("browserVersion"_s, capabilities.browserVersion.value_or(emptyString()));
|
|
capabilitiesObject->setString("platformName"_s, capabilities.platformName.value_or(emptyString()));
|
|
capabilitiesObject->setBoolean("acceptInsecureCerts"_s, capabilities.acceptInsecureCerts.value_or(false));
|
|
capabilitiesObject->setBoolean("strictFileInteractability"_s, capabilities.strictFileInteractability.value_or(false));
|
|
capabilitiesObject->setBoolean("setWindowRect"_s, capabilities.setWindowRect.value_or(true));
|
|
switch (capabilities.unhandledPromptBehavior.value_or(UnhandledPromptBehavior::DismissAndNotify)) {
|
|
case UnhandledPromptBehavior::Dismiss:
|
|
capabilitiesObject->setString("unhandledPromptBehavior"_s, "dismiss");
|
|
break;
|
|
case UnhandledPromptBehavior::Accept:
|
|
capabilitiesObject->setString("unhandledPromptBehavior"_s, "accept");
|
|
break;
|
|
case UnhandledPromptBehavior::DismissAndNotify:
|
|
capabilitiesObject->setString("unhandledPromptBehavior"_s, "dismiss and notify");
|
|
break;
|
|
case UnhandledPromptBehavior::AcceptAndNotify:
|
|
capabilitiesObject->setString("unhandledPromptBehavior"_s, "accept and notify");
|
|
break;
|
|
case UnhandledPromptBehavior::Ignore:
|
|
capabilitiesObject->setString("unhandledPromptBehavior"_s, "ignore");
|
|
break;
|
|
}
|
|
switch (capabilities.pageLoadStrategy.value_or(PageLoadStrategy::Normal)) {
|
|
case PageLoadStrategy::None:
|
|
capabilitiesObject->setString("pageLoadStrategy"_s, "none");
|
|
break;
|
|
case PageLoadStrategy::Normal:
|
|
capabilitiesObject->setString("pageLoadStrategy"_s, "normal");
|
|
break;
|
|
case PageLoadStrategy::Eager:
|
|
capabilitiesObject->setString("pageLoadStrategy"_s, "eager");
|
|
break;
|
|
}
|
|
if (!capabilities.proxy)
|
|
capabilitiesObject->setObject("proxy"_s, JSON::Object::create());
|
|
auto timeoutsObject = JSON::Object::create();
|
|
if (m_session->scriptTimeout() == std::numeric_limits<double>::infinity())
|
|
timeoutsObject->setValue("script"_s, JSON::Value::null());
|
|
else
|
|
timeoutsObject->setDouble("script"_s, m_session->scriptTimeout());
|
|
timeoutsObject->setDouble("pageLoad"_s, m_session->pageLoadTimeout());
|
|
timeoutsObject->setDouble("implicit"_s, m_session->implicitWaitTimeout());
|
|
capabilitiesObject->setObject("timeouts"_s, WTFMove(timeoutsObject));
|
|
|
|
resultObject->setObject("capabilities"_s, WTFMove(capabilitiesObject));
|
|
completionHandler(CommandResult::success(WTFMove(resultObject)));
|
|
});
|
|
});
|
|
}
|
|
|
|
void WebDriverService::deleteSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §8.2 Delete Session.
|
|
// https://www.w3.org/TR/webdriver/#delete-session
|
|
auto sessionID = parameters->getString("sessionId"_s);
|
|
if (!sessionID) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
if (!m_session || m_session->id() != sessionID) {
|
|
completionHandler(CommandResult::success());
|
|
return;
|
|
}
|
|
|
|
auto session = std::exchange(m_session, nullptr);
|
|
session->close([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
// Ignore unknown errors when closing the session if the browser is closed.
|
|
if (result.isError() && result.errorCode() == CommandResult::ErrorCode::UnknownError && !session->isConnected())
|
|
completionHandler(CommandResult::success());
|
|
else
|
|
completionHandler(WTFMove(result));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::status(RefPtr<JSON::Object>&&, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §8.3 Status
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#status
|
|
auto body = JSON::Object::create();
|
|
body->setBoolean("ready"_s, !m_session);
|
|
body->setString("message"_s, m_session ? "A session already exists"_s : "No sessions"_s);
|
|
completionHandler(CommandResult::success(WTFMove(body)));
|
|
}
|
|
|
|
void WebDriverService::getTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §8.4 Get Timeouts.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->getTimeouts(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::setTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §8.5 Set Timeouts.
|
|
// https://www.w3.org/TR/webdriver/#set-timeouts
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto timeouts = deserializeTimeouts(*parameters, IgnoreUnknownTimeout::Yes);
|
|
if (!timeouts) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->setTimeouts(timeouts.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::go(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §9.1 Go.
|
|
// https://www.w3.org/TR/webdriver/#go
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto url = parameters->getString("url"_s);
|
|
if (!url) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->waitForNavigationToComplete([this, url = WTFMove(url), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->go(url, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::getCurrentURL(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §9.2 Get Current URL.
|
|
// https://www.w3.org/TR/webdriver/#get-current-url
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->getCurrentURL(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::back(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §9.3 Back.
|
|
// https://www.w3.org/TR/webdriver/#back
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->back(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::forward(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §9.4 Forward.
|
|
// https://www.w3.org/TR/webdriver/#forward
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->forward(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::refresh(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §9.5 Refresh.
|
|
// https://www.w3.org/TR/webdriver/#refresh
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->refresh(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::getTitle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §9.6 Get Title.
|
|
// https://www.w3.org/TR/webdriver/#get-title
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->getTitle(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::getWindowHandle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.1 Get Window Handle.
|
|
// https://www.w3.org/TR/webdriver/#get-window-handle
|
|
if (findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
m_session->getWindowHandle(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.7.1 Get Window Rect.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect
|
|
if (findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
m_session->getWindowRect(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::setWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.7.2 Set Window Rect.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect
|
|
std::optional<double> width;
|
|
if (auto value = parameters->getValue("width"_s)) {
|
|
if (auto number = valueAsNumberInRange(*value))
|
|
width = number;
|
|
else if (!value->isNull()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
}
|
|
std::optional<double> height;
|
|
if (auto value = parameters->getValue("height"_s)) {
|
|
if (auto number = valueAsNumberInRange(*value))
|
|
height = number;
|
|
else if (!value->isNull()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
}
|
|
std::optional<double> x;
|
|
if (auto value = parameters->getValue("x"_s)) {
|
|
if (auto number = valueAsNumberInRange(*value, INT_MIN))
|
|
x = number;
|
|
else if (!value->isNull()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
}
|
|
std::optional<double> y;
|
|
if (auto value = parameters->getValue("y"_s)) {
|
|
if (auto number = valueAsNumberInRange(*value, INT_MIN))
|
|
y = number;
|
|
else if (!value->isNull()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// FIXME: If the remote end does not support the Set Window Rect command for the current
|
|
// top-level browsing context for any reason, return error with error code unsupported operation.
|
|
|
|
if (findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
m_session->setWindowRect(x, y, width, height, WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::maximizeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.7.3 Maximize Window
|
|
// https://w3c.github.io/webdriver/#maximize-window
|
|
if (findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
m_session->maximizeWindow(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::minimizeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.7.4 Minimize Window
|
|
// https://w3c.github.io/webdriver/#minimize-window
|
|
if (findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
m_session->minimizeWindow(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::fullscreenWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.7.5 Fullscreen Window
|
|
// https://w3c.github.io/webdriver/#fullscreen-window
|
|
if (findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
m_session->fullscreenWindow(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::closeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.2 Close Window.
|
|
// https://www.w3.org/TR/webdriver/#close-window
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->closeWindow([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
|
|
auto handles = result.result()->asArray();
|
|
if (handles && !handles->length())
|
|
m_session = nullptr;
|
|
|
|
completionHandler(WTFMove(result));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::switchToWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.3 Switch To Window.
|
|
// https://www.w3.org/TR/webdriver/#switch-to-window
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto handle = parameters->getString("handle"_s);
|
|
if (!handle) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->switchToWindow(handle, WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getWindowHandles(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.4 Get Window Handles.
|
|
// https://www.w3.org/TR/webdriver/#get-window-handles
|
|
if (findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
m_session->getWindowHandles(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::newWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §11.5 New Window
|
|
// https://w3c.github.io/webdriver/#new-window
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
std::optional<String> typeHint;
|
|
if (auto value = parameters->getValue("type"_s)) {
|
|
auto valueString = value->asString();
|
|
if (!!valueString) {
|
|
if (valueString == "window" || valueString == "tab")
|
|
typeHint = valueString;
|
|
} else if (!value->isNull()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_session->newWindow(typeHint, WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::switchToFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.5 Switch To Frame.
|
|
// https://www.w3.org/TR/webdriver/#switch-to-frame
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto frameID = parameters->getValue("id"_s);
|
|
if (!frameID) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
switch (frameID->type()) {
|
|
case JSON::Value::Type::Null:
|
|
break;
|
|
case JSON::Value::Type::Double:
|
|
case JSON::Value::Type::Integer:
|
|
if (!valueAsNumberInRange(*frameID, 0, std::numeric_limits<unsigned short>::max())) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
break;
|
|
case JSON::Value::Type::Object: {
|
|
auto frameIDObject = frameID->asObject();
|
|
if (frameIDObject->find(Session::webElementIdentifier()) == frameIDObject->end()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case JSON::Value::Type::Boolean:
|
|
case JSON::Value::Type::String:
|
|
case JSON::Value::Type::Array:
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->waitForNavigationToComplete([this, frameID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->switchToFrame(WTFMove(frameID), WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::switchToParentFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §10.6 Switch To Parent Frame.
|
|
// https://www.w3.org/TR/webdriver/#switch-to-parent-frame
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->switchToParentFrame(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
static std::optional<String> findElementOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
|
|
{
|
|
auto elementID = parameters.getString("elementId"_s);
|
|
if (elementID.isEmpty()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return std::nullopt;
|
|
}
|
|
return elementID;
|
|
}
|
|
|
|
static inline bool isValidStrategy(const String& strategy)
|
|
{
|
|
// §12.1 Locator Strategies.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-table-of-location-strategies
|
|
return strategy == "css selector"
|
|
|| strategy == "link text"
|
|
|| strategy == "partial link text"
|
|
|| strategy == "tag name"
|
|
|| strategy == "xpath";
|
|
}
|
|
|
|
static bool findStrategyAndSelectorOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& strategy, String& selector)
|
|
{
|
|
strategy = parameters.getString("using"_s);
|
|
if (!isValidStrategy(strategy)) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return false;
|
|
}
|
|
selector = parameters.getString("value"_s);
|
|
if (!selector) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WebDriverService::findElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §12.2 Find Element.
|
|
// https://www.w3.org/TR/webdriver/#find-element
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
String strategy, selector;
|
|
if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->findElements(strategy, selector, Session::FindElementsMode::Single, emptyString(), WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::findElements(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §12.3 Find Elements.
|
|
// https://www.w3.org/TR/webdriver/#find-elements
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
String strategy, selector;
|
|
if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, emptyString(), WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::findElementFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §12.4 Find Element From Element.
|
|
// https://www.w3.org/TR/webdriver/#find-element-from-element
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
String strategy, selector;
|
|
if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
|
|
return;
|
|
|
|
m_session->findElements(strategy, selector, Session::FindElementsMode::Single, elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::findElementsFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §12.5 Find Elements From Element.
|
|
// https://www.w3.org/TR/webdriver/#find-elements-from-element
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
String strategy, selector;
|
|
if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
|
|
return;
|
|
|
|
m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getActiveElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §12.6 Get Active Element.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-active-element
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->getActiveElement(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::isElementSelected(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.1 Is Element Selected.
|
|
// https://www.w3.org/TR/webdriver/#is-element-selected
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->isElementSelected(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getElementAttribute(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.2 Get Element Attribute.
|
|
// https://www.w3.org/TR/webdriver/#get-element-attribute
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
auto attribute = parameters->getString("name"_s);
|
|
if (!attribute) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->getElementAttribute(elementID.value(), attribute, WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getElementProperty(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.3 Get Element Property
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-element-property
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
auto attribute = parameters->getString("name"_s);
|
|
if (!attribute) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->getElementProperty(elementID.value(), attribute, WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getElementCSSValue(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.4 Get Element CSS Value
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-element-css-value
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
auto cssProperty = parameters->getString("name"_s);
|
|
if (!cssProperty) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->getElementCSSValue(elementID.value(), cssProperty, WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getElementText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.5 Get Element Text.
|
|
// https://www.w3.org/TR/webdriver/#get-element-text
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->getElementText(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getElementTagName(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.6 Get Element Tag Name.
|
|
// https://www.w3.org/TR/webdriver/#get-element-tag-name
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->getElementTagName(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getElementRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.7 Get Element Rect.
|
|
// https://www.w3.org/TR/webdriver/#get-element-rect
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->getElementRect(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::isElementEnabled(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §13.8 Is Element Enabled.
|
|
// https://www.w3.org/TR/webdriver/#is-element-enabled
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->isElementEnabled(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::isElementDisplayed(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §C. Element Displayedness.
|
|
// https://www.w3.org/TR/webdriver/#element-displayedness
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->isElementDisplayed(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::elementClick(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §14.1 Element Click.
|
|
// https://www.w3.org/TR/webdriver/#element-click
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->elementClick(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::elementClear(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §14.2 Element Clear.
|
|
// https://www.w3.org/TR/webdriver/#element-clear
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->elementClear(elementID.value(), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::elementSendKeys(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §14.3 Element Send Keys.
|
|
// https://www.w3.org/TR/webdriver/#element-send-keys
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
auto text = parameters->getString("text"_s);
|
|
if (text.isEmpty()) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->elementSendKeys(elementID.value(), text, WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::getPageSource(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §15.1 Getting Page Source.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#getting-page-source
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->getPageSource(WTFMove(completionHandler));
|
|
}
|
|
|
|
static bool findScriptAndArgumentsOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& script, RefPtr<JSON::Array>& arguments)
|
|
{
|
|
script = parameters.getString("script"_s);
|
|
if (!script) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return false;
|
|
}
|
|
arguments = parameters.getArray("args"_s);
|
|
if (!arguments) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WebDriverService::executeScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §15.2.1 Execute Script.
|
|
// https://www.w3.org/TR/webdriver/#execute-script
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
String script;
|
|
RefPtr<JSON::Array> arguments;
|
|
if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Sync, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::executeAsyncScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §15.2.2 Execute Async Script.
|
|
// https://www.w3.org/TR/webdriver/#execute-async-script
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
String script;
|
|
RefPtr<JSON::Array> arguments;
|
|
if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Async, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::getAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §16.1 Get All Cookies.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-all-cookies
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->getAllCookies(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::getNamedCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §16.2 Get Named Cookie.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-named-cookie
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto name = parameters->getString("name"_s);
|
|
if (!name) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->getNamedCookie(name, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
static std::optional<Session::Cookie> deserializeCookie(JSON::Object& cookieObject)
|
|
{
|
|
Session::Cookie cookie;
|
|
|
|
cookie.name = cookieObject.getString("name"_s);
|
|
if (cookie.name.isEmpty())
|
|
return std::nullopt;
|
|
|
|
cookie.value = cookieObject.getString("value"_s);
|
|
if (cookie.value.isEmpty())
|
|
return std::nullopt;
|
|
|
|
if (auto value = cookieObject.getValue("path"_s)) {
|
|
auto path = value->asString();
|
|
if (!path)
|
|
return std::nullopt;
|
|
cookie.path = path;
|
|
}
|
|
if (auto value = cookieObject.getValue("domain"_s)) {
|
|
auto domain = value->asString();
|
|
if (!domain)
|
|
return std::nullopt;
|
|
cookie.domain = domain;
|
|
}
|
|
if (auto value = cookieObject.getValue("secure"_s)) {
|
|
auto secure = value->asBoolean();
|
|
if (!secure)
|
|
return std::nullopt;
|
|
cookie.secure = secure;
|
|
}
|
|
if (auto value = cookieObject.getValue("httpOnly"_s)) {
|
|
auto httpOnly = value->asBoolean();
|
|
if (!httpOnly)
|
|
return std::nullopt;
|
|
cookie.httpOnly = httpOnly;
|
|
}
|
|
if (auto value = cookieObject.getValue("expiry"_s)) {
|
|
auto expiry = unsignedValue(*value);
|
|
if (!expiry)
|
|
return std::nullopt;
|
|
cookie.expiry = expiry.value();
|
|
}
|
|
if (auto value = cookieObject.getValue("sameSite"_s)) {
|
|
auto sameSite = value->asString();
|
|
if (sameSite != "None"_s && sameSite != "Lax"_s && sameSite != "Strict"_s)
|
|
return std::nullopt;
|
|
cookie.sameSite = sameSite;
|
|
}
|
|
|
|
return cookie;
|
|
}
|
|
|
|
void WebDriverService::addCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §16.3 Add Cookie.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto cookieObject = parameters->getObject("cookie"_s);
|
|
if (!cookieObject) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
auto cookie = deserializeCookie(*cookieObject);
|
|
if (!cookie) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->waitForNavigationToComplete([this, cookie = WTFMove(cookie), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->addCookie(cookie.value(), WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::deleteCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §16.4 Delete Cookie.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#delete-cookie
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto name = parameters->getString("name"_s);
|
|
if (!name) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->deleteCookie(name, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::deleteAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §16.5 Delete All Cookies.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#delete-all-cookies
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->deleteAllCookies(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
static bool processPauseAction(JSON::Object& actionItem, Action& action, std::optional<String>& errorMessage)
|
|
{
|
|
auto durationValue = actionItem.getValue("duration"_s);
|
|
if (!durationValue)
|
|
return true;
|
|
|
|
auto duration = unsignedValue(*durationValue);
|
|
if (!duration) {
|
|
errorMessage = String("The parameter 'duration' is invalid in pause action");
|
|
return false;
|
|
}
|
|
|
|
action.duration = duration.value();
|
|
return true;
|
|
}
|
|
|
|
static std::optional<Action> processNullAction(const String& id, JSON::Object& actionItem, std::optional<String>& errorMessage)
|
|
{
|
|
auto subtype = actionItem.getString("type"_s);
|
|
if (subtype != "pause") {
|
|
errorMessage = String("The parameter 'type' in null action is invalid or missing");
|
|
return std::nullopt;
|
|
}
|
|
|
|
Action action(id, Action::Type::None, Action::Subtype::Pause);
|
|
if (!processPauseAction(actionItem, action, errorMessage))
|
|
return std::nullopt;
|
|
|
|
return action;
|
|
}
|
|
|
|
static std::optional<Action> processKeyAction(const String& id, JSON::Object& actionItem, std::optional<String>& errorMessage)
|
|
{
|
|
Action::Subtype actionSubtype;
|
|
auto subtype = actionItem.getString("type"_s);
|
|
if (subtype == "pause")
|
|
actionSubtype = Action::Subtype::Pause;
|
|
else if (subtype == "keyUp")
|
|
actionSubtype = Action::Subtype::KeyUp;
|
|
else if (subtype == "keyDown")
|
|
actionSubtype = Action::Subtype::KeyDown;
|
|
else {
|
|
errorMessage = String("The parameter 'type' of key action is invalid");
|
|
return std::nullopt;
|
|
}
|
|
|
|
Action action(id, Action::Type::Key, actionSubtype);
|
|
|
|
switch (actionSubtype) {
|
|
case Action::Subtype::Pause:
|
|
if (!processPauseAction(actionItem, action, errorMessage))
|
|
return std::nullopt;
|
|
break;
|
|
case Action::Subtype::KeyUp:
|
|
case Action::Subtype::KeyDown: {
|
|
auto keyValue = actionItem.getValue("value"_s);
|
|
if (!keyValue) {
|
|
errorMessage = String("The paramater 'value' is missing for key up/down action");
|
|
return std::nullopt;
|
|
}
|
|
auto key = keyValue->asString();
|
|
if (key.isEmpty()) {
|
|
errorMessage = String("The paramater 'value' is invalid for key up/down action");
|
|
return std::nullopt;
|
|
}
|
|
// FIXME: check single unicode code point.
|
|
action.key = key;
|
|
break;
|
|
}
|
|
case Action::Subtype::PointerUp:
|
|
case Action::Subtype::PointerDown:
|
|
case Action::Subtype::PointerMove:
|
|
case Action::Subtype::PointerCancel:
|
|
case Action::Subtype::Scroll:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
static MouseButton actionMouseButton(unsigned button)
|
|
{
|
|
// MouseEvent.button
|
|
// https://www.w3.org/TR/uievents/#ref-for-dom-mouseevent-button-1
|
|
switch (button) {
|
|
case 0:
|
|
return MouseButton::Left;
|
|
case 1:
|
|
return MouseButton::Middle;
|
|
case 2:
|
|
return MouseButton::Right;
|
|
}
|
|
|
|
return MouseButton::None;
|
|
}
|
|
|
|
static bool processPointerMoveAction(JSON::Object& actionItem, Action& action, std::optional<String>& errorMessage)
|
|
{
|
|
if (auto durationValue = actionItem.getValue("duration"_s)) {
|
|
auto duration = unsignedValue(*durationValue);
|
|
if (!duration) {
|
|
errorMessage = String("The parameter 'duration' is invalid in action");
|
|
return false;
|
|
}
|
|
action.duration = duration.value();
|
|
}
|
|
|
|
if (auto originValue = actionItem.getValue("origin"_s)) {
|
|
if (auto originObject = originValue->asObject()) {
|
|
auto elementID = originObject->getString(Session::webElementIdentifier());
|
|
if (!elementID) {
|
|
errorMessage = String("The parameter 'origin' is not a valid web element object in action");
|
|
return false;
|
|
}
|
|
action.origin = PointerOrigin { PointerOrigin::Type::Element, elementID };
|
|
} else {
|
|
auto origin = originValue->asString();
|
|
if (origin == "viewport")
|
|
action.origin = PointerOrigin { PointerOrigin::Type::Viewport, std::nullopt };
|
|
else if (origin == "pointer")
|
|
action.origin = PointerOrigin { PointerOrigin::Type::Pointer, std::nullopt };
|
|
else {
|
|
errorMessage = String("The parameter 'origin' is invalid in action");
|
|
return false;
|
|
}
|
|
}
|
|
} else
|
|
action.origin = PointerOrigin { PointerOrigin::Type::Viewport, std::nullopt };
|
|
|
|
if (auto xValue = actionItem.getValue("x"_s)) {
|
|
auto x = valueAsNumberInRange(*xValue, INT_MIN);
|
|
if (!x) {
|
|
errorMessage = String("The paramater 'x' is invalid for action");
|
|
return false;
|
|
}
|
|
action.x = x.value();
|
|
}
|
|
|
|
if (auto yValue = actionItem.getValue("y"_s)) {
|
|
auto y = valueAsNumberInRange(*yValue, INT_MIN);
|
|
if (!y) {
|
|
errorMessage = String("The paramater 'y' is invalid for action");
|
|
return false;
|
|
}
|
|
action.y = y.value();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::optional<Action> processPointerAction(const String& id, PointerParameters& parameters, JSON::Object& actionItem, std::optional<String>& errorMessage)
|
|
{
|
|
Action::Subtype actionSubtype;
|
|
auto subtype = actionItem.getString("type"_s);
|
|
if (subtype == "pause")
|
|
actionSubtype = Action::Subtype::Pause;
|
|
else if (subtype == "pointerUp")
|
|
actionSubtype = Action::Subtype::PointerUp;
|
|
else if (subtype == "pointerDown")
|
|
actionSubtype = Action::Subtype::PointerDown;
|
|
else if (subtype == "pointerMove")
|
|
actionSubtype = Action::Subtype::PointerMove;
|
|
else if (subtype == "pointerCancel")
|
|
actionSubtype = Action::Subtype::PointerCancel;
|
|
else {
|
|
errorMessage = String("The parameter 'type' of pointer action is invalid");
|
|
return std::nullopt;
|
|
}
|
|
|
|
Action action(id, Action::Type::Pointer, actionSubtype);
|
|
action.pointerType = parameters.pointerType;
|
|
|
|
switch (actionSubtype) {
|
|
case Action::Subtype::Pause:
|
|
if (!processPauseAction(actionItem, action, errorMessage))
|
|
return std::nullopt;
|
|
break;
|
|
case Action::Subtype::PointerUp:
|
|
case Action::Subtype::PointerDown: {
|
|
auto buttonValue = actionItem.getValue("button"_s);
|
|
if (!buttonValue) {
|
|
errorMessage = String("The paramater 'button' is missing for pointer up/down action");
|
|
return std::nullopt;
|
|
}
|
|
auto button = unsignedValue(*buttonValue);
|
|
if (!button) {
|
|
errorMessage = String("The paramater 'button' is invalid for pointer up/down action");
|
|
return std::nullopt;
|
|
}
|
|
action.button = actionMouseButton(button.value());
|
|
break;
|
|
}
|
|
case Action::Subtype::PointerMove:
|
|
if (!processPointerMoveAction(actionItem, action, errorMessage))
|
|
return std::nullopt;
|
|
break;
|
|
case Action::Subtype::PointerCancel:
|
|
break;
|
|
case Action::Subtype::KeyUp:
|
|
case Action::Subtype::KeyDown:
|
|
case Action::Subtype::Scroll:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
static std::optional<Action> processWheelAction(const String& id, JSON::Object& actionItem, std::optional<String>& errorMessage)
|
|
{
|
|
Action::Subtype actionSubtype;
|
|
auto subtype = actionItem.getString("type"_s);
|
|
if (subtype == "pause")
|
|
actionSubtype = Action::Subtype::Pause;
|
|
else if (subtype == "scroll")
|
|
actionSubtype = Action::Subtype::Scroll;
|
|
else {
|
|
errorMessage = String("The parameter 'type' of wheel action is invalid");
|
|
return std::nullopt;
|
|
}
|
|
|
|
Action action(id, Action::Type::Wheel, actionSubtype);
|
|
|
|
switch (actionSubtype) {
|
|
case Action::Subtype::Pause:
|
|
if (!processPauseAction(actionItem, action, errorMessage))
|
|
return std::nullopt;
|
|
break;
|
|
case Action::Subtype::Scroll:
|
|
if (!processPointerMoveAction(actionItem, action, errorMessage))
|
|
return std::nullopt;
|
|
|
|
if (auto deltaXValue = actionItem.getValue("deltaX"_s)) {
|
|
auto deltaX = valueAsNumberInRange(*deltaXValue, INT_MIN);
|
|
if (!deltaX) {
|
|
errorMessage = String("The paramater 'deltaX' is invalid for action");
|
|
return std::nullopt;
|
|
}
|
|
action.deltaX = deltaX.value();
|
|
}
|
|
|
|
if (auto deltaYValue = actionItem.getValue("deltaY"_s)) {
|
|
auto deltaY = valueAsNumberInRange(*deltaYValue, INT_MIN);
|
|
if (!deltaY) {
|
|
errorMessage = String("The paramater 'deltaY' is invalid for action");
|
|
return std::nullopt;
|
|
}
|
|
action.deltaY = deltaY.value();
|
|
}
|
|
break;
|
|
case Action::Subtype::KeyUp:
|
|
case Action::Subtype::KeyDown:
|
|
case Action::Subtype::PointerUp:
|
|
case Action::Subtype::PointerDown:
|
|
case Action::Subtype::PointerMove:
|
|
case Action::Subtype::PointerCancel:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
static std::optional<PointerParameters> processPointerParameters(JSON::Object& actionSequence, std::optional<String>& errorMessage)
|
|
{
|
|
PointerParameters parameters;
|
|
|
|
auto parametersDataValue = actionSequence.getValue("parameters"_s);
|
|
if (!parametersDataValue)
|
|
return parameters;
|
|
|
|
auto parametersData = parametersDataValue->asObject();
|
|
if (!parametersData) {
|
|
errorMessage = String("Action sequence pointer parameters is not an object");
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto pointerType = parametersData->getString("pointerType"_s);
|
|
if (!pointerType)
|
|
return parameters;
|
|
|
|
if (pointerType == "mouse")
|
|
parameters.pointerType = PointerType::Mouse;
|
|
else if (pointerType == "pen")
|
|
parameters.pointerType = PointerType::Pen;
|
|
else if (pointerType == "touch")
|
|
parameters.pointerType = PointerType::Touch;
|
|
else {
|
|
errorMessage = String("The parameter 'pointerType' in action sequence pointer parameters is invalid");
|
|
return std::nullopt;
|
|
}
|
|
|
|
return parameters;
|
|
}
|
|
|
|
static std::optional<Vector<Action>> processInputActionSequence(Session& session, JSON::Value& actionSequenceValue, std::optional<String>& errorMessage)
|
|
{
|
|
auto actionSequence = actionSequenceValue.asObject();
|
|
if (!actionSequence) {
|
|
errorMessage = String("The action sequence is not an object");
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto type = actionSequence->getString("type"_s);
|
|
InputSource::Type inputSourceType;
|
|
if (type == "key")
|
|
inputSourceType = InputSource::Type::Key;
|
|
else if (type == "pointer")
|
|
inputSourceType = InputSource::Type::Pointer;
|
|
else if (type == "wheel")
|
|
inputSourceType = InputSource::Type::Wheel;
|
|
else if (type == "none")
|
|
inputSourceType = InputSource::Type::None;
|
|
else {
|
|
errorMessage = String("The parameter 'type' is invalid or missing in action sequence");
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto id = actionSequence->getString("id"_s);
|
|
if (!id) {
|
|
errorMessage = String("The parameter 'id' is invalid or missing in action sequence");
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<PointerParameters> parameters;
|
|
std::optional<PointerType> pointerType;
|
|
if (inputSourceType == InputSource::Type::Pointer) {
|
|
parameters = processPointerParameters(*actionSequence, errorMessage);
|
|
if (!parameters)
|
|
return std::nullopt;
|
|
|
|
pointerType = parameters->pointerType;
|
|
}
|
|
|
|
auto& inputSource = session.getOrCreateInputSource(id, inputSourceType, pointerType);
|
|
if (inputSource.type != inputSourceType) {
|
|
errorMessage = String("Action sequence type doesn't match input source type");
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (inputSource.type == InputSource::Type::Pointer && inputSource.pointerType != pointerType) {
|
|
errorMessage = String("Action sequence pointer type doesn't match input source pointer type");
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto actionItems = actionSequence->getArray("actions"_s);
|
|
if (!actionItems) {
|
|
errorMessage = String("The parameter 'actions' is invalid or not present in action sequence");
|
|
return std::nullopt;
|
|
}
|
|
|
|
Vector<Action> actions;
|
|
unsigned actionItemsLength = actionItems->length();
|
|
for (unsigned i = 0; i < actionItemsLength; ++i) {
|
|
auto actionItem = actionItems->get(i)->asObject();
|
|
if (!actionItem) {
|
|
errorMessage = String("An action in action sequence is not an object");
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<Action> action;
|
|
if (inputSourceType == InputSource::Type::None)
|
|
action = processNullAction(id, *actionItem, errorMessage);
|
|
else if (inputSourceType == InputSource::Type::Key)
|
|
action = processKeyAction(id, *actionItem, errorMessage);
|
|
else if (inputSourceType == InputSource::Type::Pointer)
|
|
action = processPointerAction(id, parameters.value(), *actionItem, errorMessage);
|
|
else if (inputSourceType == InputSource::Type::Wheel)
|
|
action = processWheelAction(id, *actionItem, errorMessage);
|
|
if (!action)
|
|
return std::nullopt;
|
|
|
|
actions.append(action.value());
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
void WebDriverService::performActions(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §17.5 Perform Actions.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#perform-actions
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto actionsArray = parameters->getArray("actions"_s);
|
|
if (!actionsArray) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("The paramater 'actions' is invalid or not present")));
|
|
return;
|
|
}
|
|
|
|
std::optional<String> errorMessage;
|
|
Vector<Vector<Action>> actionsByTick;
|
|
unsigned actionsArrayLength = actionsArray->length();
|
|
for (unsigned i = 0; i < actionsArrayLength; ++i) {
|
|
auto actionSequence = actionsArray->get(i);
|
|
auto inputSourceActions = processInputActionSequence(*m_session, actionSequence, errorMessage);
|
|
if (!inputSourceActions) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, errorMessage.value()));
|
|
return;
|
|
}
|
|
for (unsigned i = 0; i < inputSourceActions->size(); ++i) {
|
|
if (actionsByTick.size() < i + 1)
|
|
actionsByTick.append({ });
|
|
actionsByTick[i].append(inputSourceActions.value()[i]);
|
|
}
|
|
}
|
|
|
|
m_session->performActions(WTFMove(actionsByTick), WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::releaseActions(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §17.5 Release Actions.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#release-actions
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->releaseActions(WTFMove(completionHandler));
|
|
}
|
|
|
|
void WebDriverService::dismissAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §18.1 Dismiss Alert.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#dismiss-alert
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->dismissAlert(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::acceptAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §18.2 Accept Alert.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#accept-alert
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->acceptAlert(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::getAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §18.3 Get Alert Text.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#get-alert-text
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->getAlertText(WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::sendAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §18.4 Send Alert Text.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto text = parameters->getString("text"_s);
|
|
if (!text) {
|
|
completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
|
|
return;
|
|
}
|
|
|
|
m_session->waitForNavigationToComplete([this, text = WTFMove(text), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->sendAlertText(text, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::takeScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §19.1 Take Screenshot.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#take-screenshot
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->takeScreenshot(std::nullopt, std::nullopt, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
void WebDriverService::takeElementScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
|
|
{
|
|
// §19.2 Take Element Screenshot.
|
|
// https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot
|
|
if (!findSessionOrCompleteWithError(*parameters, completionHandler))
|
|
return;
|
|
|
|
auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
|
|
if (!elementID)
|
|
return;
|
|
|
|
m_session->waitForNavigationToComplete([this, elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
|
|
if (result.isError()) {
|
|
completionHandler(WTFMove(result));
|
|
return;
|
|
}
|
|
m_session->takeScreenshot(elementID.value(), true, WTFMove(completionHandler));
|
|
});
|
|
}
|
|
|
|
} // namespace WebDriver
|