1/*
2 * Copyright (C) 2017 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "WebDriverService.h"
28
29#include "Capabilities.h"
30#include "CommandResult.h"
31#include "SessionHost.h"
32#include <wtf/RunLoop.h>
33#include <wtf/text/WTFString.h>
34
35namespace WebDriver {
36
37// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-maximum-safe-integer
38static const double maxSafeInteger = 9007199254740991.0; // 2 ^ 53 - 1
39
40WebDriverService::WebDriverService()
41 : m_server(*this)
42{
43}
44
45static void printUsageStatement(const char* programName)
46{
47 printf("Usage: %s options\n", programName);
48 printf(" -h, --help Prints this help message\n");
49 printf(" -p <port>, --port=<port> Port number the driver will use\n");
50 printf(" --host=<host> Host IP the driver will use, or either 'local' or 'all' (default: 'local')");
51 printf("\n");
52}
53
54int WebDriverService::run(int argc, char** argv)
55{
56 String portString;
57 Optional<String> host;
58 for (int i = 1 ; i < argc; ++i) {
59 const char* arg = argv[i];
60 if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
61 printUsageStatement(argv[0]);
62 return EXIT_SUCCESS;
63 }
64
65 if (!strcmp(arg, "-p") && portString.isNull()) {
66 if (++i == argc) {
67 printUsageStatement(argv[0]);
68 return EXIT_FAILURE;
69 }
70 portString = argv[i];
71 continue;
72 }
73
74 static const unsigned portStrLength = strlen("--port=");
75 if (!strncmp(arg, "--port=", portStrLength) && portString.isNull()) {
76 portString = String(arg + portStrLength);
77 continue;
78 }
79
80 static const unsigned hostStrLength = strlen("--host=");
81 if (!strncmp(arg, "--host=", hostStrLength) && !host) {
82 host = String(arg + hostStrLength);
83 continue;
84 }
85 }
86
87 if (portString.isNull()) {
88 printUsageStatement(argv[0]);
89 return EXIT_FAILURE;
90 }
91
92 bool ok;
93 unsigned port = portString.toUInt(&ok);
94 if (!ok) {
95 fprintf(stderr, "Invalid port %s provided\n", portString.ascii().data());
96 return EXIT_FAILURE;
97 }
98
99 RunLoop::initializeMainRunLoop();
100
101 if (!m_server.listen(host, port))
102 return EXIT_FAILURE;
103
104 RunLoop::run();
105
106 m_server.disconnect();
107
108 return EXIT_SUCCESS;
109}
110
111const WebDriverService::Command WebDriverService::s_commands[] = {
112 { HTTPMethod::Post, "/session", &WebDriverService::newSession },
113 { HTTPMethod::Delete, "/session/$sessionId", &WebDriverService::deleteSession },
114 { HTTPMethod::Get, "/status", &WebDriverService::status },
115 { HTTPMethod::Get, "/session/$sessionId/timeouts", &WebDriverService::getTimeouts },
116 { HTTPMethod::Post, "/session/$sessionId/timeouts", &WebDriverService::setTimeouts },
117
118 { HTTPMethod::Post, "/session/$sessionId/url", &WebDriverService::go },
119 { HTTPMethod::Get, "/session/$sessionId/url", &WebDriverService::getCurrentURL },
120 { HTTPMethod::Post, "/session/$sessionId/back", &WebDriverService::back },
121 { HTTPMethod::Post, "/session/$sessionId/forward", &WebDriverService::forward },
122 { HTTPMethod::Post, "/session/$sessionId/refresh", &WebDriverService::refresh },
123 { HTTPMethod::Get, "/session/$sessionId/title", &WebDriverService::getTitle },
124
125 { HTTPMethod::Get, "/session/$sessionId/window", &WebDriverService::getWindowHandle },
126 { HTTPMethod::Delete, "/session/$sessionId/window", &WebDriverService::closeWindow },
127 { HTTPMethod::Post, "/session/$sessionId/window", &WebDriverService::switchToWindow },
128 { HTTPMethod::Get, "/session/$sessionId/window/handles", &WebDriverService::getWindowHandles },
129 { HTTPMethod::Post, "/session/$sessionId/frame", &WebDriverService::switchToFrame },
130 { HTTPMethod::Post, "/session/$sessionId/frame/parent", &WebDriverService::switchToParentFrame },
131 { HTTPMethod::Get, "/session/$sessionId/window/rect", &WebDriverService::getWindowRect },
132 { HTTPMethod::Post, "/session/$sessionId/window/rect", &WebDriverService::setWindowRect },
133 { HTTPMethod::Post, "/session/$sessionId/window/maximize", &WebDriverService::maximizeWindow },
134 { HTTPMethod::Post, "/session/$sessionId/window/minimize", &WebDriverService::minimizeWindow },
135 { HTTPMethod::Post, "/session/$sessionId/window/fullscreen", &WebDriverService::fullscreenWindow },
136
137 { HTTPMethod::Post, "/session/$sessionId/element", &WebDriverService::findElement },
138 { HTTPMethod::Post, "/session/$sessionId/elements", &WebDriverService::findElements },
139 { HTTPMethod::Post, "/session/$sessionId/element/$elementId/element", &WebDriverService::findElementFromElement },
140 { HTTPMethod::Post, "/session/$sessionId/element/$elementId/elements", &WebDriverService::findElementsFromElement },
141 { HTTPMethod::Get, "/session/$sessionId/element/active", &WebDriverService::getActiveElement },
142
143 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/selected", &WebDriverService::isElementSelected },
144 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/attribute/$name", &WebDriverService::getElementAttribute },
145 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/property/$name", &WebDriverService::getElementProperty },
146 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/css/$name", &WebDriverService::getElementCSSValue },
147 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/text", &WebDriverService::getElementText },
148 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/name", &WebDriverService::getElementTagName },
149 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/rect", &WebDriverService::getElementRect },
150 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/enabled", &WebDriverService::isElementEnabled },
151
152 { HTTPMethod::Post, "/session/$sessionId/element/$elementId/click", &WebDriverService::elementClick },
153 { HTTPMethod::Post, "/session/$sessionId/element/$elementId/clear", &WebDriverService::elementClear },
154 { HTTPMethod::Post, "/session/$sessionId/element/$elementId/value", &WebDriverService::elementSendKeys },
155
156 { HTTPMethod::Post, "/session/$sessionId/execute/sync", &WebDriverService::executeScript },
157 { HTTPMethod::Post, "/session/$sessionId/execute/async", &WebDriverService::executeAsyncScript },
158
159 { HTTPMethod::Get, "/session/$sessionId/cookie", &WebDriverService::getAllCookies },
160 { HTTPMethod::Get, "/session/$sessionId/cookie/$name", &WebDriverService::getNamedCookie },
161 { HTTPMethod::Post, "/session/$sessionId/cookie", &WebDriverService::addCookie },
162 { HTTPMethod::Delete, "/session/$sessionId/cookie/$name", &WebDriverService::deleteCookie },
163 { HTTPMethod::Delete, "/session/$sessionId/cookie", &WebDriverService::deleteAllCookies },
164
165 { HTTPMethod::Post, "/session/$sessionId/actions", &WebDriverService::performActions },
166 { HTTPMethod::Delete, "/session/$sessionId/actions", &WebDriverService::releaseActions },
167
168 { HTTPMethod::Post, "/session/$sessionId/alert/dismiss", &WebDriverService::dismissAlert },
169 { HTTPMethod::Post, "/session/$sessionId/alert/accept", &WebDriverService::acceptAlert },
170 { HTTPMethod::Get, "/session/$sessionId/alert/text", &WebDriverService::getAlertText },
171 { HTTPMethod::Post, "/session/$sessionId/alert/text", &WebDriverService::sendAlertText },
172
173 { HTTPMethod::Get, "/session/$sessionId/screenshot", &WebDriverService::takeScreenshot },
174 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/screenshot", &WebDriverService::takeElementScreenshot },
175
176
177 { HTTPMethod::Get, "/session/$sessionId/element/$elementId/displayed", &WebDriverService::isElementDisplayed },
178};
179
180Optional<WebDriverService::HTTPMethod> WebDriverService::toCommandHTTPMethod(const String& method)
181{
182 auto lowerCaseMethod = method.convertToASCIILowercase();
183 if (lowerCaseMethod == "get")
184 return WebDriverService::HTTPMethod::Get;
185 if (lowerCaseMethod == "post" || lowerCaseMethod == "put")
186 return WebDriverService::HTTPMethod::Post;
187 if (lowerCaseMethod == "delete")
188 return WebDriverService::HTTPMethod::Delete;
189
190 return WTF::nullopt;
191}
192
193bool WebDriverService::findCommand(HTTPMethod method, const String& path, CommandHandler* handler, HashMap<String, String>& parameters)
194{
195 size_t length = WTF_ARRAY_LENGTH(s_commands);
196 for (size_t i = 0; i < length; ++i) {
197 if (s_commands[i].method != method)
198 continue;
199
200 Vector<String> pathTokens = path.split('/');
201 Vector<String> commandTokens = String::fromUTF8(s_commands[i].uriTemplate).split('/');
202 if (pathTokens.size() != commandTokens.size())
203 continue;
204
205 bool allMatched = true;
206 for (size_t j = 0; j < pathTokens.size() && allMatched; ++j) {
207 if (commandTokens[j][0] == '$')
208 parameters.set(commandTokens[j].substring(1), pathTokens[j]);
209 else if (commandTokens[j] != pathTokens[j])
210 allMatched = false;
211 }
212
213 if (allMatched) {
214 *handler = s_commands[i].handler;
215 return true;
216 }
217
218 parameters.clear();
219 }
220
221 return false;
222}
223
224void WebDriverService::handleRequest(HTTPRequestHandler::Request&& request, Function<void (HTTPRequestHandler::Response&&)>&& replyHandler)
225{
226 auto method = toCommandHTTPMethod(request.method);
227 if (!method) {
228 sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown method: " + request.method)));
229 return;
230 }
231 CommandHandler handler;
232 HashMap<String, String> parameters;
233 if (!findCommand(method.value(), request.path, &handler, parameters)) {
234 sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown command: " + request.path)));
235 return;
236 }
237
238 RefPtr<JSON::Object> parametersObject;
239 if (method.value() == HTTPMethod::Post) {
240 RefPtr<JSON::Value> messageValue;
241 if (!JSON::Value::parseJSON(String::fromUTF8(request.data, request.dataLength), messageValue)) {
242 sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
243 return;
244 }
245
246 if (!messageValue->asObject(parametersObject)) {
247 sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
248 return;
249 }
250 } else
251 parametersObject = JSON::Object::create();
252 for (const auto& parameter : parameters)
253 parametersObject->setString(parameter.key, parameter.value);
254
255 ((*this).*handler)(WTFMove(parametersObject), [this, replyHandler = WTFMove(replyHandler)](CommandResult&& result) mutable {
256 sendResponse(WTFMove(replyHandler), WTFMove(result));
257 });
258}
259
260void WebDriverService::sendResponse(Function<void (HTTPRequestHandler::Response&&)>&& replyHandler, CommandResult&& result) const
261{
262 // §6.3 Processing Model.
263 // https://w3c.github.io/webdriver/webdriver-spec.html#processing-model
264 RefPtr<JSON::Value> resultValue;
265 if (result.isError()) {
266 // When required to send an error.
267 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-an-error
268 // Let body be a new JSON Object initialised with the following properties: "error", "message", "stacktrace".
269 auto errorObject = JSON::Object::create();
270 errorObject->setString("error"_s, result.errorString());
271 errorObject->setString("message"_s, result.errorMessage().valueOr(emptyString()));
272 errorObject->setString("stacktrace"_s, emptyString());
273 // If the error data dictionary contains any entries, set the "data" field on body to a new JSON Object populated with the dictionary.
274 if (auto& additionalData = result.additionalErrorData())
275 errorObject->setObject("data"_s, RefPtr<JSON::Object> { additionalData });
276 // Send a response with status and body as arguments.
277 resultValue = WTFMove(errorObject);
278 } else if (auto value = result.result())
279 resultValue = WTFMove(value);
280 else
281 resultValue = JSON::Value::null();
282
283 // When required to send a response.
284 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-a-response
285 RefPtr<JSON::Object> responseObject = JSON::Object::create();
286 responseObject->setValue("value"_s, WTFMove(resultValue));
287 replyHandler({ result.httpStatusCode(), responseObject->toJSONString().utf8(), "application/json; charset=utf-8"_s });
288}
289
290static Optional<double> valueAsNumberInRange(const JSON::Value& value, double minAllowed = 0, double maxAllowed = std::numeric_limits<int>::max())
291{
292 double number;
293 if (!value.asDouble(number))
294 return WTF::nullopt;
295
296 if (std::isnan(number) || std::isinf(number))
297 return WTF::nullopt;
298
299 if (number < minAllowed || number > maxAllowed)
300 return WTF::nullopt;
301
302 return number;
303}
304
305static Optional<uint64_t> unsignedValue(JSON::Value& value)
306{
307 auto number = valueAsNumberInRange(value, 0, maxSafeInteger);
308 if (!number)
309 return WTF::nullopt;
310
311 auto intValue = static_cast<uint64_t>(number.value());
312 // If the contained value is a double, bail in case it doesn't match the integer
313 // value, i.e. if the double value was not originally in integer form.
314 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-integer
315 if (number.value() != intValue)
316 return WTF::nullopt;
317
318 return intValue;
319}
320
321static Optional<Timeouts> deserializeTimeouts(JSON::Object& timeoutsObject)
322{
323 // §8.5 Set Timeouts.
324 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-deserialize-as-a-timeout
325 Timeouts timeouts;
326 auto end = timeoutsObject.end();
327 for (auto it = timeoutsObject.begin(); it != end; ++it) {
328 if (it->key == "sessionId")
329 continue;
330
331 // 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.
332 auto timeoutMS = unsignedValue(*it->value);
333 if (!timeoutMS)
334 return WTF::nullopt;
335
336 if (it->key == "script")
337 timeouts.script = Seconds::fromMilliseconds(timeoutMS.value());
338 else if (it->key == "pageLoad")
339 timeouts.pageLoad = Seconds::fromMilliseconds(timeoutMS.value());
340 else if (it->key == "implicit")
341 timeouts.implicit = Seconds::fromMilliseconds(timeoutMS.value());
342 else
343 return WTF::nullopt;
344 }
345 return timeouts;
346}
347
348static Optional<PageLoadStrategy> deserializePageLoadStrategy(const String& pageLoadStrategy)
349{
350 if (pageLoadStrategy == "none")
351 return PageLoadStrategy::None;
352 if (pageLoadStrategy == "normal")
353 return PageLoadStrategy::Normal;
354 if (pageLoadStrategy == "eager")
355 return PageLoadStrategy::Eager;
356 return WTF::nullopt;
357}
358
359static Optional<UnhandledPromptBehavior> deserializeUnhandledPromptBehavior(const String& unhandledPromptBehavior)
360{
361 if (unhandledPromptBehavior == "dismiss")
362 return UnhandledPromptBehavior::Dismiss;
363 if (unhandledPromptBehavior == "accept")
364 return UnhandledPromptBehavior::Accept;
365 if (unhandledPromptBehavior == "dismiss and notify")
366 return UnhandledPromptBehavior::DismissAndNotify;
367 if (unhandledPromptBehavior == "accept and notify")
368 return UnhandledPromptBehavior::AcceptAndNotify;
369 if (unhandledPromptBehavior == "ignore")
370 return UnhandledPromptBehavior::Ignore;
371 return WTF::nullopt;
372}
373
374void WebDriverService::parseCapabilities(const JSON::Object& matchedCapabilities, Capabilities& capabilities) const
375{
376 // Matched capabilities have already been validated.
377 bool acceptInsecureCerts;
378 if (matchedCapabilities.getBoolean("acceptInsecureCerts"_s, acceptInsecureCerts))
379 capabilities.acceptInsecureCerts = acceptInsecureCerts;
380 bool setWindowRect;
381 if (matchedCapabilities.getBoolean("setWindowRect"_s, setWindowRect))
382 capabilities.setWindowRect = setWindowRect;
383 String browserName;
384 if (matchedCapabilities.getString("browserName"_s, browserName))
385 capabilities.browserName = browserName;
386 String browserVersion;
387 if (matchedCapabilities.getString("browserVersion"_s, browserVersion))
388 capabilities.browserVersion = browserVersion;
389 String platformName;
390 if (matchedCapabilities.getString("platformName"_s, platformName))
391 capabilities.platformName = platformName;
392 RefPtr<JSON::Object> timeouts;
393 if (matchedCapabilities.getObject("timeouts"_s, timeouts))
394 capabilities.timeouts = deserializeTimeouts(*timeouts);
395 String pageLoadStrategy;
396 if (matchedCapabilities.getString("pageLoadStrategy"_s, pageLoadStrategy))
397 capabilities.pageLoadStrategy = deserializePageLoadStrategy(pageLoadStrategy);
398 String unhandledPromptBehavior;
399 if (matchedCapabilities.getString("unhandledPromptBehavior"_s, unhandledPromptBehavior))
400 capabilities.unhandledPromptBehavior = deserializeUnhandledPromptBehavior(unhandledPromptBehavior);
401 platformParseCapabilities(matchedCapabilities, capabilities);
402}
403
404bool WebDriverService::findSessionOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
405{
406 String sessionID;
407 if (!parameters.getString("sessionId"_s, sessionID)) {
408 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
409 return false;
410 }
411
412 if (!m_session || m_session->id() != sessionID) {
413 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
414 return false;
415 }
416
417 return true;
418}
419
420RefPtr<JSON::Object> WebDriverService::validatedCapabilities(const JSON::Object& capabilities) const
421{
422 // §7.2 Processing Capabilities.
423 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-validate-capabilities
424 RefPtr<JSON::Object> result = JSON::Object::create();
425 auto end = capabilities.end();
426 for (auto it = capabilities.begin(); it != end; ++it) {
427 if (it->value->isNull())
428 continue;
429 if (it->key == "acceptInsecureCerts") {
430 bool acceptInsecureCerts;
431 if (!it->value->asBoolean(acceptInsecureCerts))
432 return nullptr;
433 result->setBoolean(it->key, acceptInsecureCerts);
434 } else if (it->key == "browserName" || it->key == "browserVersion" || it->key == "platformName") {
435 String stringValue;
436 if (!it->value->asString(stringValue))
437 return nullptr;
438 result->setString(it->key, stringValue);
439 } else if (it->key == "pageLoadStrategy") {
440 String pageLoadStrategy;
441 if (!it->value->asString(pageLoadStrategy) || !deserializePageLoadStrategy(pageLoadStrategy))
442 return nullptr;
443 result->setString(it->key, pageLoadStrategy);
444 } else if (it->key == "proxy") {
445 // FIXME: implement proxy support.
446 } else if (it->key == "timeouts") {
447 RefPtr<JSON::Object> timeouts;
448 if (!it->value->asObject(timeouts) || !deserializeTimeouts(*timeouts))
449 return nullptr;
450 result->setValue(it->key, RefPtr<JSON::Value>(it->value));
451 } else if (it->key == "unhandledPromptBehavior") {
452 String unhandledPromptBehavior;
453 if (!it->value->asString(unhandledPromptBehavior) || !deserializeUnhandledPromptBehavior(unhandledPromptBehavior))
454 return nullptr;
455 result->setString(it->key, unhandledPromptBehavior);
456 } else if (it->key.find(":") != notFound) {
457 if (!platformValidateCapability(it->key, it->value))
458 return nullptr;
459 result->setValue(it->key, RefPtr<JSON::Value>(it->value));
460 } else
461 return nullptr;
462 }
463 return result;
464}
465
466RefPtr<JSON::Object> WebDriverService::mergeCapabilities(const JSON::Object& requiredCapabilities, const JSON::Object& firstMatchCapabilities) const
467{
468 // §7.2 Processing Capabilities.
469 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-merging-capabilities
470 RefPtr<JSON::Object> result = JSON::Object::create();
471 auto requiredEnd = requiredCapabilities.end();
472 for (auto it = requiredCapabilities.begin(); it != requiredEnd; ++it)
473 result->setValue(it->key, RefPtr<JSON::Value>(it->value));
474
475 auto firstMatchEnd = firstMatchCapabilities.end();
476 for (auto it = firstMatchCapabilities.begin(); it != firstMatchEnd; ++it)
477 result->setValue(it->key, RefPtr<JSON::Value>(it->value));
478
479 return result;
480}
481
482RefPtr<JSON::Object> WebDriverService::matchCapabilities(const JSON::Object& mergedCapabilities) const
483{
484 // §7.2 Processing Capabilities.
485 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-matching-capabilities
486 Capabilities platformCapabilities = this->platformCapabilities();
487
488 // Some capabilities like browser name and version might need to launch the browser,
489 // so we only reject the known capabilities that don't match.
490 RefPtr<JSON::Object> matchedCapabilities = JSON::Object::create();
491 if (platformCapabilities.browserName)
492 matchedCapabilities->setString("browserName"_s, platformCapabilities.browserName.value());
493 if (platformCapabilities.browserVersion)
494 matchedCapabilities->setString("browserVersion"_s, platformCapabilities.browserVersion.value());
495 if (platformCapabilities.platformName)
496 matchedCapabilities->setString("platformName"_s, platformCapabilities.platformName.value());
497 if (platformCapabilities.acceptInsecureCerts)
498 matchedCapabilities->setBoolean("acceptInsecureCerts"_s, platformCapabilities.acceptInsecureCerts.value());
499 if (platformCapabilities.setWindowRect)
500 matchedCapabilities->setBoolean("setWindowRect"_s, platformCapabilities.setWindowRect.value());
501
502 auto end = mergedCapabilities.end();
503 for (auto it = mergedCapabilities.begin(); it != end; ++it) {
504 if (it->key == "browserName" && platformCapabilities.browserName) {
505 String browserName;
506 it->value->asString(browserName);
507 if (!equalIgnoringASCIICase(platformCapabilities.browserName.value(), browserName))
508 return nullptr;
509 } else if (it->key == "browserVersion" && platformCapabilities.browserVersion) {
510 String browserVersion;
511 it->value->asString(browserVersion);
512 if (!platformCompareBrowserVersions(browserVersion, platformCapabilities.browserVersion.value()))
513 return nullptr;
514 } else if (it->key == "platformName" && platformCapabilities.platformName) {
515 String platformName;
516 it->value->asString(platformName);
517 if (!equalLettersIgnoringASCIICase(platformName, "any") && platformCapabilities.platformName.value() != platformName)
518 return nullptr;
519 } else if (it->key == "acceptInsecureCerts" && platformCapabilities.acceptInsecureCerts) {
520 bool acceptInsecureCerts;
521 it->value->asBoolean(acceptInsecureCerts);
522 if (acceptInsecureCerts && !platformCapabilities.acceptInsecureCerts.value())
523 return nullptr;
524 } else if (it->key == "proxy") {
525 // FIXME: implement proxy support.
526 } else if (!platformMatchCapability(it->key, it->value))
527 return nullptr;
528 matchedCapabilities->setValue(it->key, RefPtr<JSON::Value>(it->value));
529 }
530
531 return matchedCapabilities;
532}
533
534Vector<Capabilities> WebDriverService::processCapabilities(const JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler) const
535{
536 // §7.2 Processing Capabilities.
537 // https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
538
539 // 1. Let capabilities request be the result of getting the property "capabilities" from parameters.
540 RefPtr<JSON::Object> capabilitiesObject;
541 if (!parameters.getObject("capabilities"_s, capabilitiesObject)) {
542 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
543 return { };
544 }
545
546 // 2. Let required capabilities be the result of getting the property "alwaysMatch" from capabilities request.
547 RefPtr<JSON::Value> requiredCapabilitiesValue;
548 RefPtr<JSON::Object> requiredCapabilities;
549 if (!capabilitiesObject->getValue("alwaysMatch"_s, requiredCapabilitiesValue))
550 // 2.1. If required capabilities is undefined, set the value to an empty JSON Object.
551 requiredCapabilities = JSON::Object::create();
552 else if (!requiredCapabilitiesValue->asObject(requiredCapabilities)) {
553 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("alwaysMatch is invalid in capabilities")));
554 return { };
555 }
556
557 // 2.2. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
558 requiredCapabilities = validatedCapabilities(*requiredCapabilities);
559 if (!requiredCapabilities) {
560 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid alwaysMatch capabilities")));
561 return { };
562 }
563
564 // 3. Let all first match capabilities be the result of getting the property "firstMatch" from capabilities request.
565 RefPtr<JSON::Value> firstMatchCapabilitiesValue;
566 RefPtr<JSON::Array> firstMatchCapabilitiesList;
567 if (!capabilitiesObject->getValue("firstMatch"_s, firstMatchCapabilitiesValue)) {
568 // 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.
569 firstMatchCapabilitiesList = JSON::Array::create();
570 firstMatchCapabilitiesList->pushObject(JSON::Object::create());
571 } else if (!firstMatchCapabilitiesValue->asArray(firstMatchCapabilitiesList)) {
572 // 3.2. If all first match capabilities is not a JSON List, return error with error code invalid argument.
573 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("firstMatch is invalid in capabilities")));
574 return { };
575 }
576
577 // 4. Let validated first match capabilities be an empty JSON List.
578 Vector<RefPtr<JSON::Object>> validatedFirstMatchCapabilitiesList;
579 auto firstMatchCapabilitiesListLength = firstMatchCapabilitiesList->length();
580 validatedFirstMatchCapabilitiesList.reserveInitialCapacity(firstMatchCapabilitiesListLength);
581 // 5. For each first match capabilities corresponding to an indexed property in all first match capabilities.
582 for (unsigned i = 0; i < firstMatchCapabilitiesListLength; ++i) {
583 RefPtr<JSON::Value> firstMatchCapabilitiesValue = firstMatchCapabilitiesList->get(i);
584 RefPtr<JSON::Object> firstMatchCapabilities;
585 if (!firstMatchCapabilitiesValue->asObject(firstMatchCapabilities)) {
586 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid capabilities found in firstMatch")));
587 return { };
588 }
589 // 5.1. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
590 firstMatchCapabilities = validatedCapabilities(*firstMatchCapabilities);
591 if (!firstMatchCapabilities) {
592 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid firstMatch capabilities")));
593 return { };
594 }
595
596 // Validate here that firstMatchCapabilities don't shadow alwaysMatchCapabilities.
597 auto requiredEnd = requiredCapabilities->end();
598 auto firstMatchEnd = firstMatchCapabilities->end();
599 for (auto it = firstMatchCapabilities->begin(); it != firstMatchEnd; ++it) {
600 if (requiredCapabilities->find(it->key) != requiredEnd) {
601 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument,
602 makeString("Invalid firstMatch capabilities: key ", it->key, " is present in alwaysMatch")));
603 return { };
604 }
605 }
606
607 // 5.2. Append validated capabilities to validated first match capabilities.
608 validatedFirstMatchCapabilitiesList.uncheckedAppend(WTFMove(firstMatchCapabilities));
609 }
610
611 // 6. For each first match capabilities corresponding to an indexed property in validated first match capabilities.
612 Vector<Capabilities> matchedCapabilitiesList;
613 matchedCapabilitiesList.reserveInitialCapacity(validatedFirstMatchCapabilitiesList.size());
614 for (auto& validatedFirstMatchCapabilies : validatedFirstMatchCapabilitiesList) {
615 // 6.1. Let merged capabilities be the result of trying to merge capabilities with required capabilities and first match capabilities as arguments.
616 auto mergedCapabilities = mergeCapabilities(*requiredCapabilities, *validatedFirstMatchCapabilies);
617
618 // 6.2. Let matched capabilities be the result of trying to match capabilities with merged capabilities as an argument.
619 if (auto matchedCapabilities = matchCapabilities(*mergedCapabilities)) {
620 // 6.3. If matched capabilities is not null return matched capabilities.
621 Capabilities capabilities;
622 parseCapabilities(*matchedCapabilities, capabilities);
623 matchedCapabilitiesList.uncheckedAppend(WTFMove(capabilities));
624 }
625 }
626
627 if (matchedCapabilitiesList.isEmpty()) {
628 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to match capabilities")));
629 return { };
630 }
631
632 return matchedCapabilitiesList;
633}
634
635void WebDriverService::newSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
636{
637 // §8.1 New Session.
638 // https://www.w3.org/TR/webdriver/#new-session
639 if (m_session) {
640 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Maximum number of active sessions")));
641 return;
642 }
643
644 auto matchedCapabilitiesList = processCapabilities(*parameters, completionHandler);
645 if (matchedCapabilitiesList.isEmpty())
646 return;
647
648 // Reverse the vector to always take last item.
649 matchedCapabilitiesList.reverse();
650 connectToBrowser(WTFMove(matchedCapabilitiesList), WTFMove(completionHandler));
651}
652
653void WebDriverService::connectToBrowser(Vector<Capabilities>&& capabilitiesList, Function<void (CommandResult&&)>&& completionHandler)
654{
655 if (capabilitiesList.isEmpty()) {
656 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to match capabilities")));
657 return;
658 }
659
660 auto sessionHost = std::make_unique<SessionHost>(capabilitiesList.takeLast());
661 auto* sessionHostPtr = sessionHost.get();
662 sessionHostPtr->connectToBrowser([this, capabilitiesList = WTFMove(capabilitiesList), sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](Optional<String> error) mutable {
663 if (error) {
664 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, makeString("Failed to connect to browser: ", error.value())));
665 return;
666 }
667
668 createSession(WTFMove(capabilitiesList), WTFMove(sessionHost), WTFMove(completionHandler));
669 });
670}
671
672void WebDriverService::createSession(Vector<Capabilities>&& capabilitiesList, std::unique_ptr<SessionHost>&& sessionHost, Function<void (CommandResult&&)>&& completionHandler)
673{
674 auto* sessionHostPtr = sessionHost.get();
675 sessionHostPtr->startAutomationSession([this, capabilitiesList = WTFMove(capabilitiesList), sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](bool capabilitiesDidMatch, Optional<String> errorMessage) mutable {
676 if (errorMessage) {
677 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError, errorMessage.value()));
678 return;
679 }
680 if (!capabilitiesDidMatch) {
681 connectToBrowser(WTFMove(capabilitiesList), WTFMove(completionHandler));
682 return;
683 }
684
685 RefPtr<Session> session = Session::create(WTFMove(sessionHost));
686 session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
687 if (result.isError()) {
688 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorMessage()));
689 return;
690 }
691
692 m_session = WTFMove(session);
693
694 RefPtr<JSON::Object> resultObject = JSON::Object::create();
695 resultObject->setString("sessionId"_s, m_session->id());
696 RefPtr<JSON::Object> capabilitiesObject = JSON::Object::create();
697 const auto& capabilities = m_session->capabilities();
698 capabilitiesObject->setString("browserName"_s, capabilities.browserName.valueOr(emptyString()));
699 capabilitiesObject->setString("browserVersion"_s, capabilities.browserVersion.valueOr(emptyString()));
700 capabilitiesObject->setString("platformName"_s, capabilities.platformName.valueOr(emptyString()));
701 capabilitiesObject->setBoolean("acceptInsecureCerts"_s, capabilities.acceptInsecureCerts.valueOr(false));
702 capabilitiesObject->setBoolean("setWindowRect"_s, capabilities.setWindowRect.valueOr(true));
703 switch (capabilities.unhandledPromptBehavior.valueOr(UnhandledPromptBehavior::DismissAndNotify)) {
704 case UnhandledPromptBehavior::Dismiss:
705 capabilitiesObject->setString("unhandledPromptBehavior"_s, "dismiss");
706 break;
707 case UnhandledPromptBehavior::Accept:
708 capabilitiesObject->setString("unhandledPromptBehavior"_s, "accept");
709 break;
710 case UnhandledPromptBehavior::DismissAndNotify:
711 capabilitiesObject->setString("unhandledPromptBehavior"_s, "dismiss and notify");
712 break;
713 case UnhandledPromptBehavior::AcceptAndNotify:
714 capabilitiesObject->setString("unhandledPromptBehavior"_s, "accept and notify");
715 break;
716 case UnhandledPromptBehavior::Ignore:
717 capabilitiesObject->setString("unhandledPromptBehavior"_s, "ignore");
718 break;
719 }
720 switch (capabilities.pageLoadStrategy.valueOr(PageLoadStrategy::Normal)) {
721 case PageLoadStrategy::None:
722 capabilitiesObject->setString("pageLoadStrategy"_s, "none");
723 break;
724 case PageLoadStrategy::Normal:
725 capabilitiesObject->setString("pageLoadStrategy"_s, "normal");
726 break;
727 case PageLoadStrategy::Eager:
728 capabilitiesObject->setString("pageLoadStrategy"_s, "eager");
729 break;
730 }
731 // FIXME: implement proxy support.
732 capabilitiesObject->setObject("proxy"_s, JSON::Object::create());
733 RefPtr<JSON::Object> timeoutsObject = JSON::Object::create();
734 timeoutsObject->setInteger("script"_s, m_session->scriptTimeout().millisecondsAs<int>());
735 timeoutsObject->setInteger("pageLoad"_s, m_session->pageLoadTimeout().millisecondsAs<int>());
736 timeoutsObject->setInteger("implicit"_s, m_session->implicitWaitTimeout().millisecondsAs<int>());
737 capabilitiesObject->setObject("timeouts"_s, WTFMove(timeoutsObject));
738
739 resultObject->setObject("capabilities"_s, WTFMove(capabilitiesObject));
740 completionHandler(CommandResult::success(WTFMove(resultObject)));
741 });
742 });
743}
744
745void WebDriverService::deleteSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
746{
747 // §8.2 Delete Session.
748 // https://www.w3.org/TR/webdriver/#delete-session
749 String sessionID;
750 if (!parameters->getString("sessionId"_s, sessionID)) {
751 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
752 return;
753 }
754
755 if (!m_session || m_session->id() != sessionID) {
756 completionHandler(CommandResult::success());
757 return;
758 }
759
760 auto session = std::exchange(m_session, nullptr);
761 session->close([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
762 // Ignore unknown errors when closing the session if the browser is closed.
763 if (result.isError() && result.errorCode() == CommandResult::ErrorCode::UnknownError && !session->isConnected())
764 completionHandler(CommandResult::success());
765 else
766 completionHandler(WTFMove(result));
767 });
768}
769
770void WebDriverService::status(RefPtr<JSON::Object>&&, Function<void (CommandResult&&)>&& completionHandler)
771{
772 // §8.3 Status
773 // https://w3c.github.io/webdriver/webdriver-spec.html#status
774 auto body = JSON::Object::create();
775 body->setBoolean("ready"_s, !m_session);
776 body->setString("message"_s, m_session ? "A session already exists"_s : "No sessions"_s);
777 completionHandler(CommandResult::success(WTFMove(body)));
778}
779
780void WebDriverService::getTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
781{
782 // §8.4 Get Timeouts.
783 // https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts
784 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
785 return;
786
787 m_session->getTimeouts(WTFMove(completionHandler));
788}
789
790void WebDriverService::setTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
791{
792 // §8.5 Set Timeouts.
793 // https://www.w3.org/TR/webdriver/#set-timeouts
794 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
795 return;
796
797 auto timeouts = deserializeTimeouts(*parameters);
798 if (!timeouts) {
799 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
800 return;
801 }
802
803 m_session->setTimeouts(timeouts.value(), WTFMove(completionHandler));
804}
805
806void WebDriverService::go(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
807{
808 // §9.1 Go.
809 // https://www.w3.org/TR/webdriver/#go
810 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
811 return;
812
813 String url;
814 if (!parameters->getString("url"_s, url)) {
815 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
816 return;
817 }
818
819 m_session->waitForNavigationToComplete([this, url = WTFMove(url), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
820 if (result.isError()) {
821 completionHandler(WTFMove(result));
822 return;
823 }
824 m_session->go(url, WTFMove(completionHandler));
825 });
826}
827
828void WebDriverService::getCurrentURL(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
829{
830 // §9.2 Get Current URL.
831 // https://www.w3.org/TR/webdriver/#get-current-url
832 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
833 return;
834
835 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
836 if (result.isError()) {
837 completionHandler(WTFMove(result));
838 return;
839 }
840 m_session->getCurrentURL(WTFMove(completionHandler));
841 });
842}
843
844void WebDriverService::back(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
845{
846 // §9.3 Back.
847 // https://www.w3.org/TR/webdriver/#back
848 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
849 return;
850
851 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
852 if (result.isError()) {
853 completionHandler(WTFMove(result));
854 return;
855 }
856 m_session->back(WTFMove(completionHandler));
857 });
858}
859
860void WebDriverService::forward(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
861{
862 // §9.4 Forward.
863 // https://www.w3.org/TR/webdriver/#forward
864 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
865 return;
866
867 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
868 if (result.isError()) {
869 completionHandler(WTFMove(result));
870 return;
871 }
872 m_session->forward(WTFMove(completionHandler));
873 });
874}
875
876void WebDriverService::refresh(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
877{
878 // §9.5 Refresh.
879 // https://www.w3.org/TR/webdriver/#refresh
880 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
881 return;
882
883 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
884 if (result.isError()) {
885 completionHandler(WTFMove(result));
886 return;
887 }
888 m_session->refresh(WTFMove(completionHandler));
889 });
890}
891
892void WebDriverService::getTitle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
893{
894 // §9.6 Get Title.
895 // https://www.w3.org/TR/webdriver/#get-title
896 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
897 return;
898
899 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
900 if (result.isError()) {
901 completionHandler(WTFMove(result));
902 return;
903 }
904 m_session->getTitle(WTFMove(completionHandler));
905 });
906}
907
908void WebDriverService::getWindowHandle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
909{
910 // §10.1 Get Window Handle.
911 // https://www.w3.org/TR/webdriver/#get-window-handle
912 if (findSessionOrCompleteWithError(*parameters, completionHandler))
913 m_session->getWindowHandle(WTFMove(completionHandler));
914}
915
916void WebDriverService::getWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
917{
918 // §10.7.1 Get Window Rect.
919 // https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect
920 if (findSessionOrCompleteWithError(*parameters, completionHandler))
921 m_session->getWindowRect(WTFMove(completionHandler));
922}
923
924void WebDriverService::setWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
925{
926 // §10.7.2 Set Window Rect.
927 // https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect
928 RefPtr<JSON::Value> value;
929 Optional<double> width;
930 if (parameters->getValue("width"_s, value)) {
931 if (auto number = valueAsNumberInRange(*value))
932 width = number;
933 else if (!value->isNull()) {
934 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
935 return;
936 }
937 }
938 Optional<double> height;
939 if (parameters->getValue("height"_s, value)) {
940 if (auto number = valueAsNumberInRange(*value))
941 height = number;
942 else if (!value->isNull()) {
943 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
944 return;
945 }
946 }
947 Optional<double> x;
948 if (parameters->getValue("x"_s, value)) {
949 if (auto number = valueAsNumberInRange(*value, INT_MIN))
950 x = number;
951 else if (!value->isNull()) {
952 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
953 return;
954 }
955 }
956 Optional<double> y;
957 if (parameters->getValue("y"_s, value)) {
958 if (auto number = valueAsNumberInRange(*value, INT_MIN))
959 y = number;
960 else if (!value->isNull()) {
961 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
962 return;
963 }
964 }
965
966 // FIXME: If the remote end does not support the Set Window Rect command for the current
967 // top-level browsing context for any reason, return error with error code unsupported operation.
968
969 if (findSessionOrCompleteWithError(*parameters, completionHandler))
970 m_session->setWindowRect(x, y, width, height, WTFMove(completionHandler));
971}
972
973void WebDriverService::maximizeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
974{
975 // §10.7.3 Maximize Window
976 // https://w3c.github.io/webdriver/#maximize-window
977 if (findSessionOrCompleteWithError(*parameters, completionHandler))
978 m_session->maximizeWindow(WTFMove(completionHandler));
979}
980
981void WebDriverService::minimizeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
982{
983 // §10.7.4 Minimize Window
984 // https://w3c.github.io/webdriver/#minimize-window
985 if (findSessionOrCompleteWithError(*parameters, completionHandler))
986 m_session->minimizeWindow(WTFMove(completionHandler));
987}
988
989void WebDriverService::fullscreenWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
990{
991 // §10.7.5 Fullscreen Window
992 // https://w3c.github.io/webdriver/#fullscreen-window
993 if (findSessionOrCompleteWithError(*parameters, completionHandler))
994 m_session->fullscreenWindow(WTFMove(completionHandler));
995}
996
997void WebDriverService::closeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
998{
999 // §10.2 Close Window.
1000 // https://www.w3.org/TR/webdriver/#close-window
1001 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1002 return;
1003
1004 m_session->closeWindow([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1005 if (result.isError()) {
1006 completionHandler(WTFMove(result));
1007 return;
1008 }
1009
1010 RefPtr<JSON::Array> handles;
1011 if (result.result()->asArray(handles) && !handles->length())
1012 m_session = nullptr;
1013
1014 completionHandler(WTFMove(result));
1015 });
1016}
1017
1018void WebDriverService::switchToWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1019{
1020 // §10.3 Switch To Window.
1021 // https://www.w3.org/TR/webdriver/#switch-to-window
1022 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1023 return;
1024
1025 String handle;
1026 if (!parameters->getString("handle"_s, handle)) {
1027 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1028 return;
1029 }
1030
1031 m_session->switchToWindow(handle, WTFMove(completionHandler));
1032}
1033
1034void WebDriverService::getWindowHandles(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1035{
1036 // §10.4 Get Window Handles.
1037 // https://www.w3.org/TR/webdriver/#get-window-handles
1038 if (findSessionOrCompleteWithError(*parameters, completionHandler))
1039 m_session->getWindowHandles(WTFMove(completionHandler));
1040}
1041
1042void WebDriverService::switchToFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1043{
1044 // §10.5 Switch To Frame.
1045 // https://www.w3.org/TR/webdriver/#switch-to-frame
1046 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1047 return;
1048
1049 RefPtr<JSON::Value> frameID;
1050 if (!parameters->getValue("id"_s, frameID)) {
1051 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1052 return;
1053 }
1054
1055 m_session->waitForNavigationToComplete([this, frameID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1056 if (result.isError()) {
1057 completionHandler(WTFMove(result));
1058 return;
1059 }
1060 m_session->switchToFrame(WTFMove(frameID), WTFMove(completionHandler));
1061 });
1062}
1063
1064void WebDriverService::switchToParentFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1065{
1066 // §10.6 Switch To Parent Frame.
1067 // https://www.w3.org/TR/webdriver/#switch-to-parent-frame
1068 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1069 return;
1070
1071 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1072 if (result.isError()) {
1073 completionHandler(WTFMove(result));
1074 return;
1075 }
1076 m_session->switchToParentFrame(WTFMove(completionHandler));
1077 });
1078}
1079
1080static Optional<String> findElementOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
1081{
1082 String elementID;
1083 if (!parameters.getString("elementId"_s, elementID) || elementID.isEmpty()) {
1084 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1085 return WTF::nullopt;
1086 }
1087 return elementID;
1088}
1089
1090static inline bool isValidStrategy(const String& strategy)
1091{
1092 // §12.1 Locator Strategies.
1093 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-table-of-location-strategies
1094 return strategy == "css selector"
1095 || strategy == "link text"
1096 || strategy == "partial link text"
1097 || strategy == "tag name"
1098 || strategy == "xpath";
1099}
1100
1101static bool findStrategyAndSelectorOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& strategy, String& selector)
1102{
1103 if (!parameters.getString("using"_s, strategy) || !isValidStrategy(strategy)) {
1104 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1105 return false;
1106 }
1107 if (!parameters.getString("value"_s, selector)) {
1108 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1109 return false;
1110 }
1111 return true;
1112}
1113
1114void WebDriverService::findElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1115{
1116 // §12.2 Find Element.
1117 // https://www.w3.org/TR/webdriver/#find-element
1118 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1119 return;
1120
1121 String strategy, selector;
1122 if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1123 return;
1124
1125 m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1126 if (result.isError()) {
1127 completionHandler(WTFMove(result));
1128 return;
1129 }
1130 m_session->findElements(strategy, selector, Session::FindElementsMode::Single, emptyString(), WTFMove(completionHandler));
1131 });
1132}
1133
1134void WebDriverService::findElements(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1135{
1136 // §12.3 Find Elements.
1137 // https://www.w3.org/TR/webdriver/#find-elements
1138 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1139 return;
1140
1141 String strategy, selector;
1142 if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1143 return;
1144
1145 m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1146 if (result.isError()) {
1147 completionHandler(WTFMove(result));
1148 return;
1149 }
1150 m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, emptyString(), WTFMove(completionHandler));
1151 });
1152}
1153
1154void WebDriverService::findElementFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1155{
1156 // §12.4 Find Element From Element.
1157 // https://www.w3.org/TR/webdriver/#find-element-from-element
1158 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1159 return;
1160
1161 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1162 if (!elementID)
1163 return;
1164
1165 String strategy, selector;
1166 if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1167 return;
1168
1169 m_session->findElements(strategy, selector, Session::FindElementsMode::Single, elementID.value(), WTFMove(completionHandler));
1170}
1171
1172void WebDriverService::findElementsFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1173{
1174 // §12.5 Find Elements From Element.
1175 // https://www.w3.org/TR/webdriver/#find-elements-from-element
1176 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1177 return;
1178
1179 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1180 if (!elementID)
1181 return;
1182
1183 String strategy, selector;
1184 if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1185 return;
1186
1187 m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, elementID.value(), WTFMove(completionHandler));
1188}
1189
1190void WebDriverService::getActiveElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1191{
1192 // §12.6 Get Active Element.
1193 // https://w3c.github.io/webdriver/webdriver-spec.html#get-active-element
1194 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1195 return;
1196
1197 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1198 if (result.isError()) {
1199 completionHandler(WTFMove(result));
1200 return;
1201 }
1202 m_session->getActiveElement(WTFMove(completionHandler));
1203 });
1204}
1205
1206void WebDriverService::isElementSelected(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1207{
1208 // §13.1 Is Element Selected.
1209 // https://www.w3.org/TR/webdriver/#is-element-selected
1210 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1211 return;
1212
1213 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1214 if (!elementID)
1215 return;
1216
1217 m_session->isElementSelected(elementID.value(), WTFMove(completionHandler));
1218}
1219
1220void WebDriverService::getElementAttribute(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1221{
1222 // §13.2 Get Element Attribute.
1223 // https://www.w3.org/TR/webdriver/#get-element-attribute
1224 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1225 return;
1226
1227 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1228 if (!elementID)
1229 return;
1230
1231 String attribute;
1232 if (!parameters->getString("name"_s, attribute)) {
1233 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1234 return;
1235 }
1236
1237 m_session->getElementAttribute(elementID.value(), attribute, WTFMove(completionHandler));
1238}
1239
1240void WebDriverService::getElementProperty(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1241{
1242 // §13.3 Get Element Property
1243 // https://w3c.github.io/webdriver/webdriver-spec.html#get-element-property
1244 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1245 return;
1246
1247 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1248 if (!elementID)
1249 return;
1250
1251 String attribute;
1252 if (!parameters->getString("name"_s, attribute)) {
1253 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1254 return;
1255 }
1256
1257 m_session->getElementProperty(elementID.value(), attribute, WTFMove(completionHandler));
1258}
1259
1260void WebDriverService::getElementCSSValue(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1261{
1262 // §13.4 Get Element CSS Value
1263 // https://w3c.github.io/webdriver/webdriver-spec.html#get-element-css-value
1264 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1265 return;
1266
1267 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1268 if (!elementID)
1269 return;
1270
1271 String cssProperty;
1272 if (!parameters->getString("name"_s, cssProperty)) {
1273 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1274 return;
1275 }
1276
1277 m_session->getElementCSSValue(elementID.value(), cssProperty, WTFMove(completionHandler));
1278}
1279
1280void WebDriverService::getElementText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1281{
1282 // §13.5 Get Element Text.
1283 // https://www.w3.org/TR/webdriver/#get-element-text
1284 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1285 return;
1286
1287 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1288 if (!elementID)
1289 return;
1290
1291 m_session->getElementText(elementID.value(), WTFMove(completionHandler));
1292}
1293
1294void WebDriverService::getElementTagName(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1295{
1296 // §13.6 Get Element Tag Name.
1297 // https://www.w3.org/TR/webdriver/#get-element-tag-name
1298 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1299 return;
1300
1301 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1302 if (!elementID)
1303 return;
1304
1305 m_session->getElementTagName(elementID.value(), WTFMove(completionHandler));
1306}
1307
1308void WebDriverService::getElementRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1309{
1310 // §13.7 Get Element Rect.
1311 // https://www.w3.org/TR/webdriver/#get-element-rect
1312 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1313 return;
1314
1315 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1316 if (!elementID)
1317 return;
1318
1319 m_session->getElementRect(elementID.value(), WTFMove(completionHandler));
1320}
1321
1322void WebDriverService::isElementEnabled(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1323{
1324 // §13.8 Is Element Enabled.
1325 // https://www.w3.org/TR/webdriver/#is-element-enabled
1326 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1327 return;
1328
1329 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1330 if (!elementID)
1331 return;
1332
1333 m_session->isElementEnabled(elementID.value(), WTFMove(completionHandler));
1334}
1335
1336void WebDriverService::isElementDisplayed(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1337{
1338 // §C. Element Displayedness.
1339 // https://www.w3.org/TR/webdriver/#element-displayedness
1340 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1341 return;
1342
1343 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1344 if (!elementID)
1345 return;
1346
1347 m_session->isElementDisplayed(elementID.value(), WTFMove(completionHandler));
1348}
1349
1350void WebDriverService::elementClick(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1351{
1352 // §14.1 Element Click.
1353 // https://www.w3.org/TR/webdriver/#element-click
1354 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1355 return;
1356
1357 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1358 if (!elementID)
1359 return;
1360
1361 m_session->elementClick(elementID.value(), WTFMove(completionHandler));
1362}
1363
1364void WebDriverService::elementClear(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1365{
1366 // §14.2 Element Clear.
1367 // https://www.w3.org/TR/webdriver/#element-clear
1368 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1369 return;
1370
1371 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1372 if (!elementID)
1373 return;
1374
1375 m_session->elementClear(elementID.value(), WTFMove(completionHandler));
1376}
1377
1378void WebDriverService::elementSendKeys(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1379{
1380 // §14.3 Element Send Keys.
1381 // https://www.w3.org/TR/webdriver/#element-send-keys
1382 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1383 return;
1384
1385 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1386 if (!elementID)
1387 return;
1388
1389 String text;
1390 if (!parameters->getString("text"_s, text) || text.isEmpty()) {
1391 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1392 return;
1393 }
1394
1395 m_session->elementSendKeys(elementID.value(), text, WTFMove(completionHandler));
1396}
1397
1398static bool findScriptAndArgumentsOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& script, RefPtr<JSON::Array>& arguments)
1399{
1400 if (!parameters.getString("script"_s, script)) {
1401 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1402 return false;
1403 }
1404 if (!parameters.getArray("args"_s, arguments)) {
1405 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1406 return false;
1407 }
1408 return true;
1409}
1410
1411void WebDriverService::executeScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1412{
1413 // §15.2.1 Execute Script.
1414 // https://www.w3.org/TR/webdriver/#execute-script
1415 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1416 return;
1417
1418 String script;
1419 RefPtr<JSON::Array> arguments;
1420 if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
1421 return;
1422
1423 m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1424 if (result.isError()) {
1425 completionHandler(WTFMove(result));
1426 return;
1427 }
1428 m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Sync, WTFMove(completionHandler));
1429 });
1430}
1431
1432void WebDriverService::executeAsyncScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1433{
1434 // §15.2.2 Execute Async Script.
1435 // https://www.w3.org/TR/webdriver/#execute-async-script
1436 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1437 return;
1438
1439 String script;
1440 RefPtr<JSON::Array> arguments;
1441 if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
1442 return;
1443
1444 m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1445 if (result.isError()) {
1446 completionHandler(WTFMove(result));
1447 return;
1448 }
1449 m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Async, WTFMove(completionHandler));
1450 });
1451}
1452
1453void WebDriverService::getAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1454{
1455 // §16.1 Get All Cookies.
1456 // https://w3c.github.io/webdriver/webdriver-spec.html#get-all-cookies
1457 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1458 return;
1459
1460 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1461 if (result.isError()) {
1462 completionHandler(WTFMove(result));
1463 return;
1464 }
1465 m_session->getAllCookies(WTFMove(completionHandler));
1466 });
1467}
1468
1469void WebDriverService::getNamedCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1470{
1471 // §16.2 Get Named Cookie.
1472 // https://w3c.github.io/webdriver/webdriver-spec.html#get-named-cookie
1473 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1474 return;
1475
1476 String name;
1477 if (!parameters->getString("name"_s, name)) {
1478 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1479 return;
1480 }
1481
1482 m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1483 if (result.isError()) {
1484 completionHandler(WTFMove(result));
1485 return;
1486 }
1487 m_session->getNamedCookie(name, WTFMove(completionHandler));
1488 });
1489}
1490
1491static Optional<Session::Cookie> deserializeCookie(JSON::Object& cookieObject)
1492{
1493 Session::Cookie cookie;
1494 if (!cookieObject.getString("name"_s, cookie.name) || cookie.name.isEmpty())
1495 return WTF::nullopt;
1496 if (!cookieObject.getString("value"_s, cookie.value) || cookie.value.isEmpty())
1497 return WTF::nullopt;
1498
1499 RefPtr<JSON::Value> value;
1500 if (cookieObject.getValue("path"_s, value)) {
1501 String path;
1502 if (!value->asString(path))
1503 return WTF::nullopt;
1504 cookie.path = path;
1505 }
1506 if (cookieObject.getValue("domain"_s, value)) {
1507 String domain;
1508 if (!value->asString(domain))
1509 return WTF::nullopt;
1510 cookie.domain = domain;
1511 }
1512 if (cookieObject.getValue("secure"_s, value)) {
1513 bool secure;
1514 if (!value->asBoolean(secure))
1515 return WTF::nullopt;
1516 cookie.secure = secure;
1517 }
1518 if (cookieObject.getValue("httpOnly"_s, value)) {
1519 bool httpOnly;
1520 if (!value->asBoolean(httpOnly))
1521 return WTF::nullopt;
1522 cookie.httpOnly = httpOnly;
1523 }
1524 if (cookieObject.getValue("expiry"_s, value)) {
1525 auto expiry = unsignedValue(*value);
1526 if (!expiry)
1527 return WTF::nullopt;
1528 cookie.expiry = expiry.value();
1529 }
1530
1531 return cookie;
1532}
1533
1534void WebDriverService::addCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1535{
1536 // §16.3 Add Cookie.
1537 // https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
1538 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1539 return;
1540
1541 RefPtr<JSON::Object> cookieObject;
1542 if (!parameters->getObject("cookie"_s, cookieObject)) {
1543 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1544 return;
1545 }
1546
1547 auto cookie = deserializeCookie(*cookieObject);
1548 if (!cookie) {
1549 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1550 return;
1551 }
1552
1553 m_session->waitForNavigationToComplete([this, cookie = WTFMove(cookie), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1554 if (result.isError()) {
1555 completionHandler(WTFMove(result));
1556 return;
1557 }
1558 m_session->addCookie(cookie.value(), WTFMove(completionHandler));
1559 });
1560}
1561
1562void WebDriverService::deleteCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1563{
1564 // §16.4 Delete Cookie.
1565 // https://w3c.github.io/webdriver/webdriver-spec.html#delete-cookie
1566 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1567 return;
1568
1569 String name;
1570 if (!parameters->getString("name"_s, name)) {
1571 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1572 return;
1573 }
1574
1575 m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1576 if (result.isError()) {
1577 completionHandler(WTFMove(result));
1578 return;
1579 }
1580 m_session->deleteCookie(name, WTFMove(completionHandler));
1581 });
1582}
1583
1584void WebDriverService::deleteAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1585{
1586 // §16.5 Delete All Cookies.
1587 // https://w3c.github.io/webdriver/webdriver-spec.html#delete-all-cookies
1588 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1589 return;
1590
1591 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1592 if (result.isError()) {
1593 completionHandler(WTFMove(result));
1594 return;
1595 }
1596 m_session->deleteAllCookies(WTFMove(completionHandler));
1597 });
1598}
1599
1600static bool processPauseAction(JSON::Object& actionItem, Action& action, Optional<String>& errorMessage)
1601{
1602 RefPtr<JSON::Value> durationValue;
1603 if (!actionItem.getValue("duration"_s, durationValue)) {
1604 errorMessage = String("The parameter 'duration' is missing in pause action");
1605 return false;
1606 }
1607
1608 auto duration = unsignedValue(*durationValue);
1609 if (!duration) {
1610 errorMessage = String("The parameter 'duration' is invalid in pause action");
1611 return false;
1612 }
1613
1614 action.duration = duration.value();
1615 return true;
1616}
1617
1618static Optional<Action> processNullAction(const String& id, JSON::Object& actionItem, Optional<String>& errorMessage)
1619{
1620 String subtype;
1621 actionItem.getString("type"_s, subtype);
1622 if (subtype != "pause") {
1623 errorMessage = String("The parameter 'type' in null action is invalid or missing");
1624 return WTF::nullopt;
1625 }
1626
1627 Action action(id, Action::Type::None, Action::Subtype::Pause);
1628 if (!processPauseAction(actionItem, action, errorMessage))
1629 return WTF::nullopt;
1630
1631 return action;
1632}
1633
1634static Optional<Action> processKeyAction(const String& id, JSON::Object& actionItem, Optional<String>& errorMessage)
1635{
1636 Action::Subtype actionSubtype;
1637 String subtype;
1638 actionItem.getString("type"_s, subtype);
1639 if (subtype == "pause")
1640 actionSubtype = Action::Subtype::Pause;
1641 else if (subtype == "keyUp")
1642 actionSubtype = Action::Subtype::KeyUp;
1643 else if (subtype == "keyDown")
1644 actionSubtype = Action::Subtype::KeyDown;
1645 else {
1646 errorMessage = String("The parameter 'type' of key action is invalid");
1647 return WTF::nullopt;
1648 }
1649
1650 Action action(id, Action::Type::Key, actionSubtype);
1651
1652 switch (actionSubtype) {
1653 case Action::Subtype::Pause:
1654 if (!processPauseAction(actionItem, action, errorMessage))
1655 return WTF::nullopt;
1656 break;
1657 case Action::Subtype::KeyUp:
1658 case Action::Subtype::KeyDown: {
1659 RefPtr<JSON::Value> keyValue;
1660 if (!actionItem.getValue("value"_s, keyValue)) {
1661 errorMessage = String("The paramater 'value' is missing for key up/down action");
1662 return WTF::nullopt;
1663 }
1664 String key;
1665 if (!keyValue->asString(key) || key.isEmpty()) {
1666 errorMessage = String("The paramater 'value' is invalid for key up/down action");
1667 return WTF::nullopt;
1668 }
1669 // FIXME: check single unicode code point.
1670 action.key = key;
1671 break;
1672 }
1673 case Action::Subtype::PointerUp:
1674 case Action::Subtype::PointerDown:
1675 case Action::Subtype::PointerMove:
1676 case Action::Subtype::PointerCancel:
1677 ASSERT_NOT_REACHED();
1678 }
1679
1680 return action;
1681}
1682
1683static MouseButton actionMouseButton(unsigned button)
1684{
1685 // MouseEvent.button
1686 // https://www.w3.org/TR/uievents/#ref-for-dom-mouseevent-button-1
1687 switch (button) {
1688 case 0:
1689 return MouseButton::Left;
1690 case 1:
1691 return MouseButton::Middle;
1692 case 2:
1693 return MouseButton::Right;
1694 }
1695
1696 return MouseButton::None;
1697}
1698
1699static Optional<Action> processPointerAction(const String& id, PointerParameters& parameters, JSON::Object& actionItem, Optional<String>& errorMessage)
1700{
1701 Action::Subtype actionSubtype;
1702 String subtype;
1703 actionItem.getString("type"_s, subtype);
1704 if (subtype == "pause")
1705 actionSubtype = Action::Subtype::Pause;
1706 else if (subtype == "pointerUp")
1707 actionSubtype = Action::Subtype::PointerUp;
1708 else if (subtype == "pointerDown")
1709 actionSubtype = Action::Subtype::PointerDown;
1710 else if (subtype == "pointerMove")
1711 actionSubtype = Action::Subtype::PointerMove;
1712 else if (subtype == "pointerCancel")
1713 actionSubtype = Action::Subtype::PointerCancel;
1714 else {
1715 errorMessage = String("The parameter 'type' of pointer action is invalid");
1716 return WTF::nullopt;
1717 }
1718
1719 Action action(id, Action::Type::Pointer, actionSubtype);
1720 action.pointerType = parameters.pointerType;
1721
1722 switch (actionSubtype) {
1723 case Action::Subtype::Pause:
1724 if (!processPauseAction(actionItem, action, errorMessage))
1725 return WTF::nullopt;
1726 break;
1727 case Action::Subtype::PointerUp:
1728 case Action::Subtype::PointerDown: {
1729 RefPtr<JSON::Value> buttonValue;
1730 if (!actionItem.getValue("button"_s, buttonValue)) {
1731 errorMessage = String("The paramater 'button' is missing for pointer up/down action");
1732 return WTF::nullopt;
1733 }
1734 auto button = unsignedValue(*buttonValue);
1735 if (!button) {
1736 errorMessage = String("The paramater 'button' is invalid for pointer up/down action");
1737 return WTF::nullopt;
1738 }
1739 action.button = actionMouseButton(button.value());
1740 break;
1741 }
1742 case Action::Subtype::PointerMove: {
1743 RefPtr<JSON::Value> durationValue;
1744 if (actionItem.getValue("duration"_s, durationValue)) {
1745 auto duration = unsignedValue(*durationValue);
1746 if (!duration) {
1747 errorMessage = String("The parameter 'duration' is invalid in pointer move action");
1748 return WTF::nullopt;
1749 }
1750 action.duration = duration.value();
1751 }
1752
1753 RefPtr<JSON::Value> originValue;
1754 if (actionItem.getValue("origin"_s, originValue)) {
1755 if (originValue->type() == JSON::Value::Type::Object) {
1756 RefPtr<JSON::Object> originObject;
1757 originValue->asObject(originObject);
1758 String elementID;
1759 if (!originObject->getString(Session::webElementIdentifier(), elementID)) {
1760 errorMessage = String("The parameter 'origin' is not a valid web element object in pointer move action");
1761 return WTF::nullopt;
1762 }
1763 action.origin = PointerOrigin { PointerOrigin::Type::Element, elementID };
1764 } else {
1765 String origin;
1766 originValue->asString(origin);
1767 if (origin == "viewport")
1768 action.origin = PointerOrigin { PointerOrigin::Type::Viewport, WTF::nullopt };
1769 else if (origin == "pointer")
1770 action.origin = PointerOrigin { PointerOrigin::Type::Pointer, WTF::nullopt };
1771 else {
1772 errorMessage = String("The parameter 'origin' is invalid in pointer move action");
1773 return WTF::nullopt;
1774 }
1775 }
1776 } else
1777 action.origin = PointerOrigin { PointerOrigin::Type::Viewport, WTF::nullopt };
1778
1779 RefPtr<JSON::Value> xValue;
1780 if (actionItem.getValue("x"_s, xValue)) {
1781 auto x = valueAsNumberInRange(*xValue, INT_MIN);
1782 if (!x) {
1783 errorMessage = String("The paramater 'x' is invalid for pointer move action");
1784 return WTF::nullopt;
1785 }
1786 action.x = x.value();
1787 }
1788
1789 RefPtr<JSON::Value> yValue;
1790 if (actionItem.getValue("y"_s, yValue)) {
1791 auto y = valueAsNumberInRange(*yValue, INT_MIN);
1792 if (!y) {
1793 errorMessage = String("The paramater 'y' is invalid for pointer move action");
1794 return WTF::nullopt;
1795 }
1796 action.y = y.value();
1797 }
1798 break;
1799 }
1800 case Action::Subtype::PointerCancel:
1801 break;
1802 case Action::Subtype::KeyUp:
1803 case Action::Subtype::KeyDown:
1804 ASSERT_NOT_REACHED();
1805 }
1806
1807 return action;
1808}
1809
1810static Optional<PointerParameters> processPointerParameters(JSON::Object& actionSequence, Optional<String>& errorMessage)
1811{
1812 PointerParameters parameters;
1813 RefPtr<JSON::Value> parametersDataValue;
1814 if (!actionSequence.getValue("parameters"_s, parametersDataValue))
1815 return parameters;
1816
1817 RefPtr<JSON::Object> parametersData;
1818 if (!parametersDataValue->asObject(parametersData)) {
1819 errorMessage = String("Action sequence pointer parameters is not an object");
1820 return WTF::nullopt;
1821 }
1822
1823 String pointerType;
1824 if (!parametersData->getString("pointerType"_s, pointerType))
1825 return parameters;
1826
1827 if (pointerType == "mouse")
1828 parameters.pointerType = PointerType::Mouse;
1829 else if (pointerType == "pen")
1830 parameters.pointerType = PointerType::Pen;
1831 else if (pointerType == "touch")
1832 parameters.pointerType = PointerType::Touch;
1833 else {
1834 errorMessage = String("The parameter 'pointerType' in action sequence pointer parameters is invalid");
1835 return WTF::nullopt;
1836 }
1837
1838 return parameters;
1839}
1840
1841static Optional<Vector<Action>> processInputActionSequence(Session& session, JSON::Value& actionSequenceValue, Optional<String>& errorMessage)
1842{
1843 RefPtr<JSON::Object> actionSequence;
1844 if (!actionSequenceValue.asObject(actionSequence)) {
1845 errorMessage = String("The action sequence is not an object");
1846 return WTF::nullopt;
1847 }
1848
1849 String type;
1850 actionSequence->getString("type"_s, type);
1851 InputSource::Type inputSourceType;
1852 if (type == "key")
1853 inputSourceType = InputSource::Type::Key;
1854 else if (type == "pointer")
1855 inputSourceType = InputSource::Type::Pointer;
1856 else if (type == "none")
1857 inputSourceType = InputSource::Type::None;
1858 else {
1859 errorMessage = String("The parameter 'type' is invalid or missing in action sequence");
1860 return WTF::nullopt;
1861 }
1862
1863 String id;
1864 if (!actionSequence->getString("id"_s, id)) {
1865 errorMessage = String("The parameter 'id' is invalid or missing in action sequence");
1866 return WTF::nullopt;
1867 }
1868
1869 Optional<PointerParameters> parameters;
1870 Optional<PointerType> pointerType;
1871 if (inputSourceType == InputSource::Type::Pointer) {
1872 parameters = processPointerParameters(*actionSequence, errorMessage);
1873 if (!parameters)
1874 return WTF::nullopt;
1875
1876 pointerType = parameters->pointerType;
1877 }
1878
1879 auto& inputSource = session.getOrCreateInputSource(id, inputSourceType, pointerType);
1880 if (inputSource.type != inputSourceType) {
1881 errorMessage = String("Action sequence type doesn't match input source type");
1882 return WTF::nullopt;
1883 }
1884
1885 if (inputSource.type == InputSource::Type::Pointer && inputSource.pointerType != pointerType) {
1886 errorMessage = String("Action sequence pointer type doesn't match input source pointer type");
1887 return WTF::nullopt;
1888 }
1889
1890 RefPtr<JSON::Array> actionItems;
1891 if (!actionSequence->getArray("actions"_s, actionItems)) {
1892 errorMessage = String("The parameter 'actions' is invalid or not present in action sequence");
1893 return WTF::nullopt;
1894 }
1895
1896 Vector<Action> actions;
1897 unsigned actionItemsLength = actionItems->length();
1898 for (unsigned i = 0; i < actionItemsLength; ++i) {
1899 auto actionItemValue = actionItems->get(i);
1900 RefPtr<JSON::Object> actionItem;
1901 if (!actionItemValue->asObject(actionItem)) {
1902 errorMessage = String("An action in action sequence is not an object");
1903 return WTF::nullopt;
1904 }
1905
1906 Optional<Action> action;
1907 if (inputSourceType == InputSource::Type::None)
1908 action = processNullAction(id, *actionItem, errorMessage);
1909 else if (inputSourceType == InputSource::Type::Key)
1910 action = processKeyAction(id, *actionItem, errorMessage);
1911 else if (inputSourceType == InputSource::Type::Pointer)
1912 action = processPointerAction(id, parameters.value(), *actionItem, errorMessage);
1913 if (!action)
1914 return WTF::nullopt;
1915
1916 actions.append(action.value());
1917 }
1918
1919 return actions;
1920}
1921
1922void WebDriverService::performActions(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1923{
1924 // §17.5 Perform Actions.
1925 // https://w3c.github.io/webdriver/webdriver-spec.html#perform-actions
1926 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1927 return;
1928
1929 RefPtr<JSON::Array> actionsArray;
1930 if (!parameters->getArray("actions"_s, actionsArray)) {
1931 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("The paramater 'actions' is invalid or not present")));
1932 return;
1933 }
1934
1935 Optional<String> errorMessage;
1936 Vector<Vector<Action>> actionsByTick;
1937 unsigned actionsArrayLength = actionsArray->length();
1938 for (unsigned i = 0; i < actionsArrayLength; ++i) {
1939 auto actionSequence = actionsArray->get(i);
1940 auto inputSourceActions = processInputActionSequence(*m_session, *actionSequence, errorMessage);
1941 if (!inputSourceActions) {
1942 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, errorMessage.value()));
1943 return;
1944 }
1945 for (unsigned i = 0; i < inputSourceActions->size(); ++i) {
1946 if (actionsByTick.size() < i + 1)
1947 actionsByTick.append({ });
1948 actionsByTick[i].append(inputSourceActions.value()[i]);
1949 }
1950 }
1951
1952 m_session->performActions(WTFMove(actionsByTick), WTFMove(completionHandler));
1953}
1954
1955void WebDriverService::releaseActions(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1956{
1957 // §17.5 Release Actions.
1958 // https://w3c.github.io/webdriver/webdriver-spec.html#release-actions
1959 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1960 return;
1961
1962 m_session->releaseActions(WTFMove(completionHandler));
1963}
1964
1965void WebDriverService::dismissAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1966{
1967 // §18.1 Dismiss Alert.
1968 // https://w3c.github.io/webdriver/webdriver-spec.html#dismiss-alert
1969 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1970 return;
1971
1972 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1973 if (result.isError()) {
1974 completionHandler(WTFMove(result));
1975 return;
1976 }
1977 m_session->dismissAlert(WTFMove(completionHandler));
1978 });
1979}
1980
1981void WebDriverService::acceptAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1982{
1983 // §18.2 Accept Alert.
1984 // https://w3c.github.io/webdriver/webdriver-spec.html#accept-alert
1985 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1986 return;
1987
1988 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1989 if (result.isError()) {
1990 completionHandler(WTFMove(result));
1991 return;
1992 }
1993 m_session->acceptAlert(WTFMove(completionHandler));
1994 });
1995}
1996
1997void WebDriverService::getAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1998{
1999 // §18.3 Get Alert Text.
2000 // https://w3c.github.io/webdriver/webdriver-spec.html#get-alert-text
2001 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2002 return;
2003
2004 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2005 if (result.isError()) {
2006 completionHandler(WTFMove(result));
2007 return;
2008 }
2009 m_session->getAlertText(WTFMove(completionHandler));
2010 });
2011}
2012
2013void WebDriverService::sendAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
2014{
2015 // §18.4 Send Alert Text.
2016 // https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text
2017 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2018 return;
2019
2020 String text;
2021 if (!parameters->getString("text"_s, text)) {
2022 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
2023 return;
2024 }
2025
2026 m_session->waitForNavigationToComplete([this, text = WTFMove(text), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2027 if (result.isError()) {
2028 completionHandler(WTFMove(result));
2029 return;
2030 }
2031 m_session->sendAlertText(text, WTFMove(completionHandler));
2032 });
2033}
2034
2035void WebDriverService::takeScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
2036{
2037 // §19.1 Take Screenshot.
2038 // https://w3c.github.io/webdriver/webdriver-spec.html#take-screenshot
2039 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2040 return;
2041
2042 m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2043 if (result.isError()) {
2044 completionHandler(WTFMove(result));
2045 return;
2046 }
2047 m_session->takeScreenshot(WTF::nullopt, WTF::nullopt, WTFMove(completionHandler));
2048 });
2049}
2050
2051void WebDriverService::takeElementScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
2052{
2053 // §19.2 Take Element Screenshot.
2054 // https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot
2055 if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2056 return;
2057
2058 auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
2059 if (!elementID)
2060 return;
2061
2062 bool scrollIntoView = true;
2063 parameters->getBoolean("scroll"_s, scrollIntoView);
2064
2065 m_session->waitForNavigationToComplete([this, elementID, scrollIntoView, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2066 if (result.isError()) {
2067 completionHandler(WTFMove(result));
2068 return;
2069 }
2070 m_session->takeScreenshot(elementID.value(), scrollIntoView, WTFMove(completionHandler));
2071 });
2072}
2073
2074} // namespace WebDriver
2075