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