96501fedc5c4151238f1c1b387a104cef8b026c6
[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 WebDriverService::WebDriverService()
38     : m_server(*this)
39 {
40 }
41
42 static void printUsageStatement(const char* programName)
43 {
44     printf("Usage: %s options\n", programName);
45     printf("  -h, --help                Prints this help message\n");
46     printf("  -p <port>, --port=<port>  Port number the driver will use\n");
47     printf("\n");
48 }
49
50 int WebDriverService::run(int argc, char** argv)
51 {
52     String portString;
53     for (int i = 1 ; i < argc; ++i) {
54         const char* arg = argv[i];
55         if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
56             printUsageStatement(argv[0]);
57             return EXIT_SUCCESS;
58         }
59
60         if (!strcmp(arg, "-p") && portString.isNull()) {
61             if (++i == argc) {
62                 printUsageStatement(argv[0]);
63                 return EXIT_FAILURE;
64             }
65             portString = argv[i];
66             continue;
67         }
68
69         static const unsigned portStrLength = strlen("--port=");
70         if (!strncmp(arg, "--port=", portStrLength) && portString.isNull()) {
71             portString = String(arg + portStrLength);
72             continue;
73         }
74     }
75
76     if (portString.isNull()) {
77         printUsageStatement(argv[0]);
78         return EXIT_FAILURE;
79     }
80
81     bool ok;
82     unsigned port = portString.toUInt(&ok);
83     if (!ok) {
84         fprintf(stderr, "Invalid port %s provided\n", portString.ascii().data());
85         return EXIT_FAILURE;
86     }
87
88     RunLoop::initializeMainRunLoop();
89
90     if (!m_server.listen(port))
91         return EXIT_FAILURE;
92
93     RunLoop::run();
94
95     m_server.disconnect();
96
97     return EXIT_SUCCESS;
98 }
99
100 const WebDriverService::Command WebDriverService::s_commands[] = {
101     { HTTPMethod::Post, "/session", &WebDriverService::newSession },
102     { HTTPMethod::Delete, "/session/$sessionId", &WebDriverService::deleteSession },
103     { HTTPMethod::Get, "/status", &WebDriverService::status },
104     { HTTPMethod::Get, "/session/$sessionId/timeouts", &WebDriverService::getTimeouts },
105     { HTTPMethod::Post, "/session/$sessionId/timeouts", &WebDriverService::setTimeouts },
106
107     { HTTPMethod::Post, "/session/$sessionId/url", &WebDriverService::go },
108     { HTTPMethod::Get, "/session/$sessionId/url", &WebDriverService::getCurrentURL },
109     { HTTPMethod::Post, "/session/$sessionId/back", &WebDriverService::back },
110     { HTTPMethod::Post, "/session/$sessionId/forward", &WebDriverService::forward },
111     { HTTPMethod::Post, "/session/$sessionId/refresh", &WebDriverService::refresh },
112     { HTTPMethod::Get, "/session/$sessionId/title", &WebDriverService::getTitle },
113
114     { HTTPMethod::Get, "/session/$sessionId/window", &WebDriverService::getWindowHandle },
115     { HTTPMethod::Delete, "/session/$sessionId/window", &WebDriverService::closeWindow },
116     { HTTPMethod::Post, "/session/$sessionId/window", &WebDriverService::switchToWindow },
117     { HTTPMethod::Get, "/session/$sessionId/window/handles", &WebDriverService::getWindowHandles },
118     { HTTPMethod::Post, "/session/$sessionId/frame", &WebDriverService::switchToFrame },
119     { HTTPMethod::Post, "/session/$sessionId/frame/parent", &WebDriverService::switchToParentFrame },
120     { HTTPMethod::Get, "/session/$sessionId/window/rect", &WebDriverService::getWindowRect },
121     { HTTPMethod::Post, "/session/$sessionId/window/rect", &WebDriverService::setWindowRect },
122
123     { HTTPMethod::Post, "/session/$sessionId/element", &WebDriverService::findElement },
124     { HTTPMethod::Post, "/session/$sessionId/elements", &WebDriverService::findElements },
125     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/element", &WebDriverService::findElementFromElement },
126     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/elements", &WebDriverService::findElementsFromElement },
127     { HTTPMethod::Get, "/session/$sessionId/element/active", &WebDriverService::getActiveElement },
128
129     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/selected", &WebDriverService::isElementSelected },
130     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/attribute/$name", &WebDriverService::getElementAttribute },
131     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/property/$name", &WebDriverService::getElementProperty },
132     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/text", &WebDriverService::getElementText },
133     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/name", &WebDriverService::getElementTagName },
134     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/rect", &WebDriverService::getElementRect },
135     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/enabled", &WebDriverService::isElementEnabled },
136
137     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/click", &WebDriverService::elementClick },
138     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/clear", &WebDriverService::elementClear },
139     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/value", &WebDriverService::elementSendKeys },
140
141     { HTTPMethod::Post, "/session/$sessionId/execute/sync", &WebDriverService::executeScript },
142     { HTTPMethod::Post, "/session/$sessionId/execute/async", &WebDriverService::executeAsyncScript },
143
144     { HTTPMethod::Get, "/session/$sessionId/cookie", &WebDriverService::getAllCookies },
145     { HTTPMethod::Get, "/session/$sessionId/cookie/$name", &WebDriverService::getNamedCookie },
146     { HTTPMethod::Post, "/session/$sessionId/cookie", &WebDriverService::addCookie },
147     { HTTPMethod::Delete, "/session/$sessionId/cookie/$name", &WebDriverService::deleteCookie },
148     { HTTPMethod::Delete, "/session/$sessionId/cookie", &WebDriverService::deleteAllCookies },
149
150     { HTTPMethod::Post, "/session/$sessionId/alert/dismiss", &WebDriverService::dismissAlert },
151     { HTTPMethod::Post, "/session/$sessionId/alert/accept", &WebDriverService::acceptAlert },
152     { HTTPMethod::Get, "/session/$sessionId/alert/text", &WebDriverService::getAlertText },
153     { HTTPMethod::Post, "/session/$sessionId/alert/text", &WebDriverService::sendAlertText },
154
155     { HTTPMethod::Get, "/session/$sessionId/screenshot", &WebDriverService::takeScreenshot },
156     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/screenshot", &WebDriverService::takeElementScreenshot },
157
158
159     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/displayed", &WebDriverService::isElementDisplayed },
160 };
161
162 std::optional<WebDriverService::HTTPMethod> WebDriverService::toCommandHTTPMethod(const String& method)
163 {
164     auto lowerCaseMethod = method.convertToASCIILowercase();
165     if (lowerCaseMethod == "get")
166         return WebDriverService::HTTPMethod::Get;
167     if (lowerCaseMethod == "post" || lowerCaseMethod == "put")
168         return WebDriverService::HTTPMethod::Post;
169     if (lowerCaseMethod == "delete")
170         return WebDriverService::HTTPMethod::Delete;
171
172     return std::nullopt;
173 }
174
175 bool WebDriverService::findCommand(HTTPMethod method, const String& path, CommandHandler* handler, HashMap<String, String>& parameters)
176 {
177     size_t length = WTF_ARRAY_LENGTH(s_commands);
178     for (size_t i = 0; i < length; ++i) {
179         if (s_commands[i].method != method)
180             continue;
181
182         Vector<String> pathTokens;
183         path.split("/", pathTokens);
184         Vector<String> commandTokens;
185         String::fromUTF8(s_commands[i].uriTemplate).split("/", commandTokens);
186         if (pathTokens.size() != commandTokens.size())
187             continue;
188
189         bool allMatched = true;
190         for (size_t j = 0; j < pathTokens.size() && allMatched; ++j) {
191             if (commandTokens[j][0] == '$')
192                 parameters.set(commandTokens[j].substring(1), pathTokens[j]);
193             else if (commandTokens[j] != pathTokens[j])
194                 allMatched = false;
195         }
196
197         if (allMatched) {
198             *handler = s_commands[i].handler;
199             return true;
200         }
201
202         parameters.clear();
203     }
204
205     return false;
206 }
207
208 void WebDriverService::handleRequest(HTTPRequestHandler::Request&& request, Function<void (HTTPRequestHandler::Response&&)>&& replyHandler)
209 {
210     auto method = toCommandHTTPMethod(request.method);
211     if (!method) {
212         sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown method: " + request.method)));
213         return;
214     }
215     CommandHandler handler;
216     HashMap<String, String> parameters;
217     if (!findCommand(method.value(), request.path, &handler, parameters)) {
218         sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown command: " + request.path)));
219         return;
220     }
221
222     RefPtr<JSON::Object> parametersObject;
223     if (method.value() == HTTPMethod::Post && request.dataLength) {
224         RefPtr<JSON::Value> messageValue;
225         if (!JSON::Value::parseJSON(String::fromUTF8(request.data, request.dataLength), messageValue)) {
226             sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
227             return;
228         }
229
230         if (!messageValue->asObject(parametersObject)) {
231             sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
232             return;
233         }
234     } else
235         parametersObject = JSON::Object::create();
236     for (const auto& parameter : parameters)
237         parametersObject->setString(parameter.key, parameter.value);
238
239     ((*this).*handler)(WTFMove(parametersObject), [this, replyHandler = WTFMove(replyHandler)](CommandResult&& result) mutable {
240         sendResponse(WTFMove(replyHandler), WTFMove(result));
241     });
242 }
243
244 void WebDriverService::sendResponse(Function<void (HTTPRequestHandler::Response&&)>&& replyHandler, CommandResult&& result) const
245 {
246     // §6.3 Processing Model.
247     // https://w3c.github.io/webdriver/webdriver-spec.html#processing-model
248     RefPtr<JSON::Value> resultValue;
249     if (result.isError()) {
250         // When required to send an error.
251         // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-an-error
252         // Let body be a new JSON Object initialised with the following properties: "error", "message", "stacktrace".
253         auto errorObject = JSON::Object::create();
254         errorObject->setString(ASCIILiteral("error"), result.errorString());
255         errorObject->setString(ASCIILiteral("message"), result.errorMessage().value_or(emptyString()));
256         errorObject->setString(ASCIILiteral("stacktrace"), emptyString());
257         // If the error data dictionary contains any entries, set the "data" field on body to a new JSON Object populated with the dictionary.
258         if (auto& additionalData = result.additionalErrorData())
259             errorObject->setObject(ASCIILiteral("data"), RefPtr<JSON::Object> { additionalData });
260         // Send a response with status and body as arguments.
261         resultValue = WTFMove(errorObject);
262     } else if (auto value = result.result())
263         resultValue = WTFMove(value);
264     else
265         resultValue = JSON::Value::null();
266
267     // When required to send a response.
268     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-a-response
269     RefPtr<JSON::Object> responseObject = JSON::Object::create();
270     responseObject->setValue(ASCIILiteral("value"), WTFMove(resultValue));
271     replyHandler({ result.httpStatusCode(), responseObject->toJSONString().utf8(), ASCIILiteral("application/json; charset=utf-8") });
272 }
273
274 static bool integerValue(JSON::Value& value, int& output)
275 {
276     // Bail if an integer value cannot be retrieved.
277     if (!value.asInteger(output))
278         return false;
279
280     // If the contained value is a double, bail in case it doesn't match the integer
281     // value, i.e. if the double value was not originally in integer form.
282     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-integer
283     double doubleValue;
284     if (value.asDouble(doubleValue) && doubleValue != output)
285         return false;
286
287     return true;
288 }
289
290 static std::optional<Timeouts> deserializeTimeouts(JSON::Object& timeoutsObject)
291 {
292     // §8.5 Set Timeouts.
293     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-deserialize-as-a-timeout
294     Timeouts timeouts;
295     auto end = timeoutsObject.end();
296     for (auto it = timeoutsObject.begin(); it != end; ++it) {
297         if (it->key == "sessionId")
298             continue;
299
300         int timeoutMS;
301         if (!integerValue(*it->value, timeoutMS) || timeoutMS < 0 || timeoutMS > INT_MAX)
302             return std::nullopt;
303
304         if (it->key == "script")
305             timeouts.script = Seconds::fromMilliseconds(timeoutMS);
306         else if (it->key == "pageLoad")
307             timeouts.pageLoad = Seconds::fromMilliseconds(timeoutMS);
308         else if (it->key == "implicit")
309             timeouts.implicit = Seconds::fromMilliseconds(timeoutMS);
310         else
311             return std::nullopt;
312     }
313     return timeouts;
314 }
315
316 static std::optional<PageLoadStrategy> deserializePageLoadStrategy(const String& pageLoadStrategy)
317 {
318     if (pageLoadStrategy == "none")
319         return PageLoadStrategy::None;
320     if (pageLoadStrategy == "normal")
321         return PageLoadStrategy::Normal;
322     if (pageLoadStrategy == "eager")
323         return PageLoadStrategy::Eager;
324     return std::nullopt;
325 }
326
327 static std::optional<UnhandledPromptBehavior> deserializeUnhandledPromptBehavior(const String& unhandledPromptBehavior)
328 {
329     if (unhandledPromptBehavior == "dismiss")
330         return UnhandledPromptBehavior::Dismiss;
331     if (unhandledPromptBehavior == "accept")
332         return UnhandledPromptBehavior::Accept;
333     if (unhandledPromptBehavior == "dismiss and notify")
334         return UnhandledPromptBehavior::DismissAndNotify;
335     if (unhandledPromptBehavior == "accept and notify")
336         return UnhandledPromptBehavior::AcceptAndNotify;
337     if (unhandledPromptBehavior == "ignore")
338         return UnhandledPromptBehavior::Ignore;
339     return std::nullopt;
340 }
341
342 void WebDriverService::parseCapabilities(const JSON::Object& matchedCapabilities, Capabilities& capabilities) const
343 {
344     // Matched capabilities have already been validated.
345     bool acceptInsecureCerts;
346     if (matchedCapabilities.getBoolean(ASCIILiteral("acceptInsecureCerts"), acceptInsecureCerts))
347         capabilities.acceptInsecureCerts = acceptInsecureCerts;
348     String browserName;
349     if (matchedCapabilities.getString(ASCIILiteral("browserName"), browserName))
350         capabilities.browserName = browserName;
351     String browserVersion;
352     if (matchedCapabilities.getString(ASCIILiteral("browserVersion"), browserVersion))
353         capabilities.browserVersion = browserVersion;
354     String platformName;
355     if (matchedCapabilities.getString(ASCIILiteral("platformName"), platformName))
356         capabilities.platformName = platformName;
357     RefPtr<JSON::Object> timeouts;
358     if (matchedCapabilities.getObject(ASCIILiteral("timeouts"), timeouts))
359         capabilities.timeouts = deserializeTimeouts(*timeouts);
360     String pageLoadStrategy;
361     if (matchedCapabilities.getString(ASCIILiteral("pageLoadStrategy"), pageLoadStrategy))
362         capabilities.pageLoadStrategy = deserializePageLoadStrategy(pageLoadStrategy);
363     String unhandledPromptBehavior;
364     if (matchedCapabilities.getString(ASCIILiteral("unhandledPromptBehavior"), unhandledPromptBehavior))
365         capabilities.unhandledPromptBehavior = deserializeUnhandledPromptBehavior(unhandledPromptBehavior);
366     platformParseCapabilities(matchedCapabilities, capabilities);
367 }
368
369 bool WebDriverService::findSessionOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
370 {
371     String sessionID;
372     if (!parameters.getString(ASCIILiteral("sessionId"), sessionID)) {
373         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
374         return false;
375     }
376
377     if (!m_session || m_session->id() != sessionID) {
378         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
379         return false;
380     }
381
382     return true;
383 }
384
385 RefPtr<JSON::Object> WebDriverService::validatedCapabilities(const JSON::Object& capabilities) const
386 {
387     // §7.2 Processing Capabilities.
388     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-validate-capabilities
389     RefPtr<JSON::Object> result = JSON::Object::create();
390     auto end = capabilities.end();
391     for (auto it = capabilities.begin(); it != end; ++it) {
392         if (it->value->isNull())
393             continue;
394         if (it->key == "acceptInsecureCerts") {
395             bool acceptInsecureCerts;
396             if (!it->value->asBoolean(acceptInsecureCerts))
397                 return nullptr;
398             result->setBoolean(it->key, acceptInsecureCerts);
399         } else if (it->key == "browserName" || it->key == "browserVersion" || it->key == "platformName") {
400             String stringValue;
401             if (!it->value->asString(stringValue))
402                 return nullptr;
403             result->setString(it->key, stringValue);
404         } else if (it->key == "pageLoadStrategy") {
405             String pageLoadStrategy;
406             if (!it->value->asString(pageLoadStrategy) || !deserializePageLoadStrategy(pageLoadStrategy))
407                 return nullptr;
408             result->setString(it->key, pageLoadStrategy);
409         } else if (it->key == "proxy") {
410             // FIXME: implement proxy support.
411         } else if (it->key == "timeouts") {
412             RefPtr<JSON::Object> timeouts;
413             if (!it->value->asObject(timeouts) || !deserializeTimeouts(*timeouts))
414                 return nullptr;
415             result->setValue(it->key, RefPtr<JSON::Value>(it->value));
416         } else if (it->key == "unhandledPromptBehavior") {
417             String unhandledPromptBehavior;
418             if (!it->value->asString(unhandledPromptBehavior) || !deserializeUnhandledPromptBehavior(unhandledPromptBehavior))
419                 return nullptr;
420             result->setString(it->key, unhandledPromptBehavior);
421         } else if (it->key.find(":") != notFound) {
422             if (!platformValidateCapability(it->key, it->value))
423                 return nullptr;
424             result->setValue(it->key, RefPtr<JSON::Value>(it->value));
425         } else
426             return nullptr;
427     }
428     return result;
429 }
430
431 RefPtr<JSON::Object> WebDriverService::mergeCapabilities(const JSON::Object& requiredCapabilities, const JSON::Object& firstMatchCapabilities) const
432 {
433     // §7.2 Processing Capabilities.
434     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-merging-capabilities
435     RefPtr<JSON::Object> result = JSON::Object::create();
436     auto requiredEnd = requiredCapabilities.end();
437     for (auto it = requiredCapabilities.begin(); it != requiredEnd; ++it)
438         result->setValue(it->key, RefPtr<JSON::Value>(it->value));
439
440     auto firstMatchEnd = firstMatchCapabilities.end();
441     for (auto it = firstMatchCapabilities.begin(); it != firstMatchEnd; ++it) {
442         if (requiredCapabilities.find(it->key) != requiredEnd)
443             return nullptr;
444
445         result->setValue(it->key, RefPtr<JSON::Value>(it->value));
446     }
447
448     return result;
449 }
450
451 RefPtr<JSON::Object> WebDriverService::matchCapabilities(const JSON::Object& mergedCapabilities, std::optional<String>& errorString) const
452 {
453     // §7.2 Processing Capabilities.
454     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-matching-capabilities
455     Capabilities platformCapabilities = this->platformCapabilities();
456
457     // Some capabilities like browser name and version might need to launch the browser,
458     // so we only reject the known capabilities that don't match.
459     RefPtr<JSON::Object> matchedCapabilities = JSON::Object::create();
460     if (platformCapabilities.browserName)
461         matchedCapabilities->setString(ASCIILiteral("browserName"), platformCapabilities.browserName.value());
462     if (platformCapabilities.browserVersion)
463         matchedCapabilities->setString(ASCIILiteral("browserVersion"), platformCapabilities.browserVersion.value());
464     if (platformCapabilities.platformName)
465         matchedCapabilities->setString(ASCIILiteral("platformName"), platformCapabilities.platformName.value());
466     if (platformCapabilities.acceptInsecureCerts)
467         matchedCapabilities->setBoolean(ASCIILiteral("acceptInsecureCerts"), platformCapabilities.acceptInsecureCerts.value());
468
469     auto end = mergedCapabilities.end();
470     for (auto it = mergedCapabilities.begin(); it != end; ++it) {
471         if (it->key == "browserName" && platformCapabilities.browserName) {
472             String browserName;
473             it->value->asString(browserName);
474             if (!equalIgnoringASCIICase(platformCapabilities.browserName.value(), browserName)) {
475                 errorString = makeString("expected browserName ", platformCapabilities.browserName.value(), " but got ", browserName);
476                 return nullptr;
477             }
478         } else if (it->key == "browserVersion" && platformCapabilities.browserVersion) {
479             String browserVersion;
480             it->value->asString(browserVersion);
481             if (!platformCompareBrowserVersions(browserVersion, platformCapabilities.browserVersion.value())) {
482                 errorString = makeString("requested browserVersion is ", browserVersion, " but actual version is ", platformCapabilities.browserVersion.value());
483                 return nullptr;
484             }
485         } else if (it->key == "platformName" && platformCapabilities.platformName) {
486             String platformName;
487             it->value->asString(platformName);
488             if (!equalLettersIgnoringASCIICase(platformName, "any") && !equalIgnoringASCIICase(platformCapabilities.platformName.value(), platformName)) {
489                 errorString = makeString("expected platformName ", platformCapabilities.platformName.value(), " but got ", platformName);
490                 return nullptr;
491             }
492         } else if (it->key == "acceptInsecureCerts" && platformCapabilities.acceptInsecureCerts) {
493             bool acceptInsecureCerts;
494             it->value->asBoolean(acceptInsecureCerts);
495             if (acceptInsecureCerts && !platformCapabilities.acceptInsecureCerts.value()) {
496                 errorString = String("browser doesn't accept insecure TLS certificates");
497                 return nullptr;
498             }
499         } else if (it->key == "proxy") {
500             // FIXME: implement proxy support.
501         } else if (auto platformErrorString = platformMatchCapability(it->key, it->value)) {
502             errorString = platformErrorString;
503             return nullptr;
504         }
505         matchedCapabilities->setValue(it->key, RefPtr<JSON::Value>(it->value));
506     }
507
508     return matchedCapabilities;
509 }
510
511 RefPtr<JSON::Object> WebDriverService::processCapabilities(const JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler) const
512 {
513     // §7.2 Processing Capabilities.
514     // https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
515
516     // 1. Let capabilities request be the result of getting the property "capabilities" from parameters.
517     RefPtr<JSON::Object> capabilitiesObject;
518     if (!parameters.getObject(ASCIILiteral("capabilities"), capabilitiesObject)) {
519         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
520         return nullptr;
521     }
522
523     // 2. Let required capabilities be the result of getting the property "alwaysMatch" from capabilities request.
524     RefPtr<JSON::Value> requiredCapabilitiesValue;
525     RefPtr<JSON::Object> requiredCapabilities;
526     if (!capabilitiesObject->getValue(ASCIILiteral("alwaysMatch"), requiredCapabilitiesValue))
527         // 2.1. If required capabilities is undefined, set the value to an empty JSON Object.
528         requiredCapabilities = JSON::Object::create();
529     else if (!requiredCapabilitiesValue->asObject(requiredCapabilities)) {
530         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("alwaysMatch is invalid in capabilities")));
531         return nullptr;
532     }
533
534     // 2.2. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
535     requiredCapabilities = validatedCapabilities(*requiredCapabilities);
536     if (!requiredCapabilities) {
537         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid alwaysMatch capabilities")));
538         return nullptr;
539     }
540
541     // 3. Let all first match capabilities be the result of getting the property "firstMatch" from capabilities request.
542     RefPtr<JSON::Value> firstMatchCapabilitiesValue;
543     RefPtr<JSON::Array> firstMatchCapabilitiesList;
544     if (!capabilitiesObject->getValue(ASCIILiteral("firstMatch"), firstMatchCapabilitiesValue)) {
545         // 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.
546         firstMatchCapabilitiesList = JSON::Array::create();
547         firstMatchCapabilitiesList->pushObject(JSON::Object::create());
548     } else if (!firstMatchCapabilitiesValue->asArray(firstMatchCapabilitiesList)) {
549         // 3.2. If all first match capabilities is not a JSON List, return error with error code invalid argument.
550         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("firstMatch is invalid in capabilities")));
551         return nullptr;
552     }
553
554     // 4. Let validated first match capabilities be an empty JSON List.
555     Vector<RefPtr<JSON::Object>> validatedFirstMatchCapabilitiesList;
556     auto firstMatchCapabilitiesListLength = firstMatchCapabilitiesList->length();
557     validatedFirstMatchCapabilitiesList.reserveInitialCapacity(firstMatchCapabilitiesListLength);
558     // 5. For each first match capabilities corresponding to an indexed property in all first match capabilities.
559     for (unsigned i = 0; i < firstMatchCapabilitiesListLength; ++i) {
560         RefPtr<JSON::Value> firstMatchCapabilitiesValue = firstMatchCapabilitiesList->get(i);
561         RefPtr<JSON::Object> firstMatchCapabilities;
562         if (!firstMatchCapabilitiesValue->asObject(firstMatchCapabilities)) {
563             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid capabilities found in firstMatch")));
564             return nullptr;
565         }
566         // 5.1. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
567         firstMatchCapabilities = validatedCapabilities(*firstMatchCapabilities);
568         if (!firstMatchCapabilities) {
569             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid firstMatch capabilities")));
570             return nullptr;
571         }
572         // 5.2. Append validated capabilities to validated first match capabilities.
573         validatedFirstMatchCapabilitiesList.uncheckedAppend(WTFMove(firstMatchCapabilities));
574     }
575
576     // 6. For each first match capabilities corresponding to an indexed property in validated first match capabilities.
577     std::optional<String> errorString;
578     for (auto& validatedFirstMatchCapabilies : validatedFirstMatchCapabilitiesList) {
579         // 6.1. Let merged capabilities be the result of trying to merge capabilities with required capabilities and first match capabilities as arguments.
580         auto mergedCapabilities = mergeCapabilities(*requiredCapabilities, *validatedFirstMatchCapabilies);
581         if (!mergedCapabilities) {
582             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Same capability found in firstMatch and alwaysMatch")));
583             return nullptr;
584         }
585         // 6.2. Let matched capabilities be the result of trying to match capabilities with merged capabilities as an argument.
586         auto matchedCapabilities = matchCapabilities(*mergedCapabilities, errorString);
587         if (matchedCapabilities) {
588             // 6.3. If matched capabilities is not null return matched capabilities.
589             return matchedCapabilities;
590         }
591     }
592
593     completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, errorString ? errorString.value() : String("Invalid capabilities")));
594     return nullptr;
595 }
596
597 void WebDriverService::newSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
598 {
599     // §8.1 New Session.
600     // https://www.w3.org/TR/webdriver/#new-session
601     if (m_session) {
602         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Maximum number of active sessions")));
603         return;
604     }
605
606     auto matchedCapabilities = processCapabilities(*parameters, completionHandler);
607     if (!matchedCapabilities)
608         return;
609
610     Capabilities capabilities;
611     parseCapabilities(*matchedCapabilities, capabilities);
612     auto sessionHost = std::make_unique<SessionHost>(WTFMove(capabilities));
613     auto* sessionHostPtr = sessionHost.get();
614     sessionHostPtr->connectToBrowser([this, sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](std::optional<String> error) mutable {
615         if (error) {
616             completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, makeString("Failed to connect to browser: ", error.value())));
617             return;
618         }
619
620         RefPtr<Session> session = Session::create(WTFMove(sessionHost));
621         session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
622             if (result.isError()) {
623                 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorMessage()));
624                 return;
625             }
626
627             m_session = WTFMove(session);
628
629             const auto& capabilities = m_session->capabilities();
630             if (capabilities.timeouts)
631                 m_session->setTimeouts(capabilities.timeouts.value(), [](CommandResult&&) { });
632
633             RefPtr<JSON::Object> resultObject = JSON::Object::create();
634             resultObject->setString(ASCIILiteral("sessionId"), m_session->id());
635             RefPtr<JSON::Object> capabilitiesObject = JSON::Object::create();
636             if (capabilities.browserName)
637                 capabilitiesObject->setString(ASCIILiteral("browserName"), capabilities.browserName.value());
638             if (capabilities.browserVersion)
639                 capabilitiesObject->setString(ASCIILiteral("browserVersion"), capabilities.browserVersion.value());
640             if (capabilities.platformName)
641                 capabilitiesObject->setString(ASCIILiteral("platformName"), capabilities.platformName.value());
642             if (capabilities.acceptInsecureCerts)
643                 capabilitiesObject->setBoolean(ASCIILiteral("acceptInsecureCerts"), capabilities.acceptInsecureCerts.value());
644             if (capabilities.timeouts) {
645                 RefPtr<JSON::Object> timeoutsObject = JSON::Object::create();
646                 if (capabilities.timeouts.value().script)
647                     timeoutsObject->setInteger(ASCIILiteral("script"), capabilities.timeouts.value().script.value().millisecondsAs<int>());
648                 if (capabilities.timeouts.value().pageLoad)
649                     timeoutsObject->setInteger(ASCIILiteral("pageLoad"), capabilities.timeouts.value().pageLoad.value().millisecondsAs<int>());
650                 if (capabilities.timeouts.value().implicit)
651                     timeoutsObject->setInteger(ASCIILiteral("implicit"), capabilities.timeouts.value().implicit.value().millisecondsAs<int>());
652                 capabilitiesObject->setObject(ASCIILiteral("timeouts"), WTFMove(timeoutsObject));
653             }
654             if (capabilities.pageLoadStrategy) {
655                 switch (capabilities.pageLoadStrategy.value()) {
656                 case PageLoadStrategy::None:
657                     capabilitiesObject->setString(ASCIILiteral("pageLoadStrategy"), "none");
658                     break;
659                 case PageLoadStrategy::Normal:
660                     capabilitiesObject->setString(ASCIILiteral("pageLoadStrategy"), "normal");
661                     break;
662                 case PageLoadStrategy::Eager:
663                     capabilitiesObject->setString(ASCIILiteral("pageLoadStrategy"), "eager");
664                     break;
665                 }
666             }
667             if (capabilities.unhandledPromptBehavior) {
668                 switch (capabilities.unhandledPromptBehavior.value()) {
669                 case UnhandledPromptBehavior::Dismiss:
670                     capabilitiesObject->setString(ASCIILiteral("unhandledPromptBehavior"), "dismiss");
671                     break;
672                 case UnhandledPromptBehavior::Accept:
673                     capabilitiesObject->setString(ASCIILiteral("unhandledPromptBehavior"), "accept");
674                     break;
675                 case UnhandledPromptBehavior::DismissAndNotify:
676                     capabilitiesObject->setString(ASCIILiteral("unhandledPromptBehavior"), "dismiss and notify");
677                     break;
678                 case UnhandledPromptBehavior::AcceptAndNotify:
679                     capabilitiesObject->setString(ASCIILiteral("unhandledPromptBehavior"), "accept and notify");
680                     break;
681                 case UnhandledPromptBehavior::Ignore:
682                     capabilitiesObject->setString(ASCIILiteral("unhandledPromptBehavior"), "ignore");
683                     break;
684                 }
685             }
686             resultObject->setObject(ASCIILiteral("capabilities"), WTFMove(capabilitiesObject));
687             completionHandler(CommandResult::success(WTFMove(resultObject)));
688         });
689     });
690 }
691
692 void WebDriverService::deleteSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
693 {
694     // §8.2 Delete Session.
695     // https://www.w3.org/TR/webdriver/#delete-session
696     String sessionID;
697     if (!parameters->getString(ASCIILiteral("sessionId"), sessionID)) {
698         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
699         return;
700     }
701
702     if (!m_session || m_session->id() != sessionID) {
703         completionHandler(CommandResult::success());
704         return;
705     }
706
707     auto session = std::exchange(m_session, nullptr);
708     session->close([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
709         completionHandler(WTFMove(result));
710     });
711 }
712
713 void WebDriverService::status(RefPtr<JSON::Object>&&, Function<void (CommandResult&&)>&& completionHandler)
714 {
715     // §8.3 Status
716     // https://w3c.github.io/webdriver/webdriver-spec.html#status
717     auto body = JSON::Object::create();
718     body->setBoolean(ASCIILiteral("ready"), !m_session);
719     body->setString(ASCIILiteral("message"), m_session ? ASCIILiteral("A session already exists") : ASCIILiteral("No sessions"));
720     completionHandler(CommandResult::success(WTFMove(body)));
721 }
722
723 void WebDriverService::getTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
724 {
725     // §8.4 Get Timeouts.
726     // https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts
727     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
728         return;
729
730     m_session->getTimeouts(WTFMove(completionHandler));
731 }
732
733 void WebDriverService::setTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
734 {
735     // §8.5 Set Timeouts.
736     // https://www.w3.org/TR/webdriver/#set-timeouts
737     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
738         return;
739
740     auto timeouts = deserializeTimeouts(*parameters);
741     if (!timeouts) {
742         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
743         return;
744     }
745
746     m_session->setTimeouts(timeouts.value(), WTFMove(completionHandler));
747 }
748
749 void WebDriverService::go(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
750 {
751     // §9.1 Go.
752     // https://www.w3.org/TR/webdriver/#go
753     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
754         return;
755
756     String url;
757     if (!parameters->getString(ASCIILiteral("url"), url)) {
758         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
759         return;
760     }
761
762     m_session->waitForNavigationToComplete([this, url = WTFMove(url), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
763         if (result.isError()) {
764             completionHandler(WTFMove(result));
765             return;
766         }
767         m_session->go(url, WTFMove(completionHandler));
768     });
769 }
770
771 void WebDriverService::getCurrentURL(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
772 {
773     // §9.2 Get Current URL.
774     // https://www.w3.org/TR/webdriver/#get-current-url
775     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
776         return;
777
778     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
779         if (result.isError()) {
780             completionHandler(WTFMove(result));
781             return;
782         }
783         m_session->getCurrentURL(WTFMove(completionHandler));
784     });
785 }
786
787 void WebDriverService::back(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
788 {
789     // §9.3 Back.
790     // https://www.w3.org/TR/webdriver/#back
791     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
792         return;
793
794     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
795         if (result.isError()) {
796             completionHandler(WTFMove(result));
797             return;
798         }
799         m_session->back(WTFMove(completionHandler));
800     });
801 }
802
803 void WebDriverService::forward(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
804 {
805     // §9.4 Forward.
806     // https://www.w3.org/TR/webdriver/#forward
807     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
808         return;
809
810     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
811         if (result.isError()) {
812             completionHandler(WTFMove(result));
813             return;
814         }
815         m_session->forward(WTFMove(completionHandler));
816     });
817 }
818
819 void WebDriverService::refresh(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
820 {
821     // §9.5 Refresh.
822     // https://www.w3.org/TR/webdriver/#refresh
823     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
824         return;
825
826     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
827         if (result.isError()) {
828             completionHandler(WTFMove(result));
829             return;
830         }
831         m_session->refresh(WTFMove(completionHandler));
832     });
833 }
834
835 void WebDriverService::getTitle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
836 {
837     // §9.6 Get Title.
838     // https://www.w3.org/TR/webdriver/#get-title
839     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
840         return;
841
842     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
843         if (result.isError()) {
844             completionHandler(WTFMove(result));
845             return;
846         }
847         m_session->getTitle(WTFMove(completionHandler));
848     });
849 }
850
851 void WebDriverService::getWindowHandle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
852 {
853     // §10.1 Get Window Handle.
854     // https://www.w3.org/TR/webdriver/#get-window-handle
855     if (findSessionOrCompleteWithError(*parameters, completionHandler))
856         m_session->getWindowHandle(WTFMove(completionHandler));
857 }
858
859 void WebDriverService::getWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
860 {
861     // §10.7.1 Get Window Rect.
862     // https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect
863     if (findSessionOrCompleteWithError(*parameters, completionHandler))
864         m_session->getWindowRect(WTFMove(completionHandler));
865 }
866
867 static std::optional<double> valueAsNumberInRange(const JSON::Value& value, double minAllowed = 0, double maxAllowed = INT_MAX)
868 {
869     double number;
870     if (!value.asDouble(number))
871         return std::nullopt;
872
873     if (std::isnan(number) || std::isinf(number))
874         return std::nullopt;
875
876     if (number < minAllowed || number > maxAllowed)
877         return std::nullopt;
878
879     return number;
880 }
881
882 void WebDriverService::setWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
883 {
884     // §10.7.2 Set Window Rect.
885     // https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect
886     RefPtr<JSON::Value> value;
887     std::optional<double> width;
888     if (parameters->getValue(ASCIILiteral("width"), value)) {
889         if (auto number = valueAsNumberInRange(*value))
890             width = number;
891         else if (!value->isNull()) {
892             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
893             return;
894         }
895     }
896     std::optional<double> height;
897     if (parameters->getValue(ASCIILiteral("height"), value)) {
898         if (auto number = valueAsNumberInRange(*value))
899             height = number;
900         else if (!value->isNull()) {
901             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
902             return;
903         }
904     }
905     std::optional<double> x;
906     if (parameters->getValue(ASCIILiteral("x"), value)) {
907         if (auto number = valueAsNumberInRange(*value, INT_MIN))
908             x = number;
909         else if (!value->isNull()) {
910             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
911             return;
912         }
913     }
914     std::optional<double> y;
915     if (parameters->getValue(ASCIILiteral("y"), value)) {
916         if (auto number = valueAsNumberInRange(*value, INT_MIN))
917             y = number;
918         else if (!value->isNull()) {
919             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
920             return;
921         }
922     }
923
924     // FIXME: If the remote end does not support the Set Window Rect command for the current
925     // top-level browsing context for any reason, return error with error code unsupported operation.
926
927     if (findSessionOrCompleteWithError(*parameters, completionHandler))
928         m_session->setWindowRect(x, y, width, height, WTFMove(completionHandler));
929 }
930
931 void WebDriverService::closeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
932 {
933     // §10.2 Close Window.
934     // https://www.w3.org/TR/webdriver/#close-window
935     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
936         return;
937
938     m_session->closeWindow([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
939         if (result.isError()) {
940             completionHandler(WTFMove(result));
941             return;
942         }
943
944         RefPtr<JSON::Array> handles;
945         if (result.result()->asArray(handles) && !handles->length())
946             m_session = nullptr;
947
948         completionHandler(WTFMove(result));
949     });
950 }
951
952 void WebDriverService::switchToWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
953 {
954     // §10.3 Switch To Window.
955     // https://www.w3.org/TR/webdriver/#switch-to-window
956     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
957         return;
958
959     String handle;
960     if (!parameters->getString(ASCIILiteral("handle"), handle)) {
961         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
962         return;
963     }
964
965     m_session->switchToWindow(handle, WTFMove(completionHandler));
966 }
967
968 void WebDriverService::getWindowHandles(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
969 {
970     // §10.4 Get Window Handles.
971     // https://www.w3.org/TR/webdriver/#get-window-handles
972     if (findSessionOrCompleteWithError(*parameters, completionHandler))
973         m_session->getWindowHandles(WTFMove(completionHandler));
974 }
975
976 void WebDriverService::switchToFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
977 {
978     // §10.5 Switch To Frame.
979     // https://www.w3.org/TR/webdriver/#switch-to-frame
980     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
981         return;
982
983     RefPtr<JSON::Value> frameID;
984     if (!parameters->getValue(ASCIILiteral("id"), frameID)) {
985         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
986         return;
987     }
988
989     m_session->waitForNavigationToComplete([this, frameID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
990         if (result.isError()) {
991             completionHandler(WTFMove(result));
992             return;
993         }
994         m_session->switchToFrame(WTFMove(frameID), WTFMove(completionHandler));
995     });
996 }
997
998 void WebDriverService::switchToParentFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
999 {
1000     // §10.6 Switch To Parent Frame.
1001     // https://www.w3.org/TR/webdriver/#switch-to-parent-frame
1002     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1003         return;
1004
1005     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1006         if (result.isError()) {
1007             completionHandler(WTFMove(result));
1008             return;
1009         }
1010         m_session->switchToParentFrame(WTFMove(completionHandler));
1011     });
1012 }
1013
1014 static std::optional<String> findElementOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
1015 {
1016     String elementID;
1017     if (!parameters.getString(ASCIILiteral("elementId"), elementID) || elementID.isEmpty()) {
1018         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1019         return std::nullopt;
1020     }
1021     return elementID;
1022 }
1023
1024 static inline bool isValidStrategy(const String& strategy)
1025 {
1026     // §12.1 Locator Strategies.
1027     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-table-of-location-strategies
1028     return strategy == "css selector"
1029         || strategy == "link text"
1030         || strategy == "partial link text"
1031         || strategy == "tag name"
1032         || strategy == "xpath";
1033 }
1034
1035 static bool findStrategyAndSelectorOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& strategy, String& selector)
1036 {
1037     if (!parameters.getString(ASCIILiteral("using"), strategy) || !isValidStrategy(strategy)) {
1038         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1039         return false;
1040     }
1041     if (!parameters.getString(ASCIILiteral("value"), selector)) {
1042         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1043         return false;
1044     }
1045     return true;
1046 }
1047
1048 void WebDriverService::findElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1049 {
1050     // §12.2 Find Element.
1051     // https://www.w3.org/TR/webdriver/#find-element
1052     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1053         return;
1054
1055     String strategy, selector;
1056     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1057         return;
1058
1059     m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1060         if (result.isError()) {
1061             completionHandler(WTFMove(result));
1062             return;
1063         }
1064         m_session->findElements(strategy, selector, Session::FindElementsMode::Single, emptyString(), WTFMove(completionHandler));
1065     });
1066 }
1067
1068 void WebDriverService::findElements(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1069 {
1070     // §12.3 Find Elements.
1071     // https://www.w3.org/TR/webdriver/#find-elements
1072     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1073         return;
1074
1075     String strategy, selector;
1076     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1077         return;
1078
1079     m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1080         if (result.isError()) {
1081             completionHandler(WTFMove(result));
1082             return;
1083         }
1084         m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, emptyString(), WTFMove(completionHandler));
1085     });
1086 }
1087
1088 void WebDriverService::findElementFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1089 {
1090     // §12.4 Find Element From Element.
1091     // https://www.w3.org/TR/webdriver/#find-element-from-element
1092     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1093         return;
1094
1095     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1096     if (!elementID)
1097         return;
1098
1099     String strategy, selector;
1100     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1101         return;
1102
1103     m_session->findElements(strategy, selector, Session::FindElementsMode::Single, elementID.value(), WTFMove(completionHandler));
1104 }
1105
1106 void WebDriverService::findElementsFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1107 {
1108     // §12.5 Find Elements From Element.
1109     // https://www.w3.org/TR/webdriver/#find-elements-from-element
1110     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1111         return;
1112
1113     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1114     if (!elementID)
1115         return;
1116
1117     String strategy, selector;
1118     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1119         return;
1120
1121     m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, elementID.value(), WTFMove(completionHandler));
1122 }
1123
1124 void WebDriverService::getActiveElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1125 {
1126     // §12.6 Get Active Element.
1127     // https://w3c.github.io/webdriver/webdriver-spec.html#get-active-element
1128     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1129         return;
1130
1131     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1132         if (result.isError()) {
1133             completionHandler(WTFMove(result));
1134             return;
1135         }
1136         m_session->getActiveElement(WTFMove(completionHandler));
1137     });
1138 }
1139
1140 void WebDriverService::isElementSelected(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1141 {
1142     // §13.1 Is Element Selected.
1143     // https://www.w3.org/TR/webdriver/#is-element-selected
1144     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1145         return;
1146
1147     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1148     if (!elementID)
1149         return;
1150
1151     m_session->isElementSelected(elementID.value(), WTFMove(completionHandler));
1152 }
1153
1154 void WebDriverService::getElementAttribute(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1155 {
1156     // §13.2 Get Element Attribute.
1157     // https://www.w3.org/TR/webdriver/#get-element-attribute
1158     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1159         return;
1160
1161     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1162     if (!elementID)
1163         return;
1164
1165     String attribute;
1166     if (!parameters->getString(ASCIILiteral("name"), attribute)) {
1167         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1168         return;
1169     }
1170
1171     m_session->getElementAttribute(elementID.value(), attribute, WTFMove(completionHandler));
1172 }
1173
1174 void WebDriverService::getElementProperty(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1175 {
1176     // §13.3 Get Element Property
1177     // https://w3c.github.io/webdriver/webdriver-spec.html#get-element-property
1178     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1179         return;
1180
1181     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1182     if (!elementID)
1183         return;
1184
1185     String attribute;
1186     if (!parameters->getString(ASCIILiteral("name"), attribute)) {
1187         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1188         return;
1189     }
1190
1191     m_session->getElementProperty(elementID.value(), attribute, WTFMove(completionHandler));
1192 }
1193
1194 void WebDriverService::getElementText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1195 {
1196     // §13.5 Get Element Text.
1197     // https://www.w3.org/TR/webdriver/#get-element-text
1198     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1199         return;
1200
1201     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1202     if (!elementID)
1203         return;
1204
1205     m_session->getElementText(elementID.value(), WTFMove(completionHandler));
1206 }
1207
1208 void WebDriverService::getElementTagName(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1209 {
1210     // §13.6 Get Element Tag Name.
1211     // https://www.w3.org/TR/webdriver/#get-element-tag-name
1212     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1213         return;
1214
1215     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1216     if (!elementID)
1217         return;
1218
1219     m_session->getElementTagName(elementID.value(), WTFMove(completionHandler));
1220 }
1221
1222 void WebDriverService::getElementRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1223 {
1224     // §13.7 Get Element Rect.
1225     // https://www.w3.org/TR/webdriver/#get-element-rect
1226     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1227         return;
1228
1229     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1230     if (!elementID)
1231         return;
1232
1233     m_session->getElementRect(elementID.value(), WTFMove(completionHandler));
1234 }
1235
1236 void WebDriverService::isElementEnabled(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1237 {
1238     // §13.8 Is Element Enabled.
1239     // https://www.w3.org/TR/webdriver/#is-element-enabled
1240     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1241         return;
1242
1243     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1244     if (!elementID)
1245         return;
1246
1247     m_session->isElementEnabled(elementID.value(), WTFMove(completionHandler));
1248 }
1249
1250 void WebDriverService::isElementDisplayed(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1251 {
1252     // §C. Element Displayedness.
1253     // https://www.w3.org/TR/webdriver/#element-displayedness
1254     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1255         return;
1256
1257     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1258     if (!elementID)
1259         return;
1260
1261     m_session->isElementDisplayed(elementID.value(), WTFMove(completionHandler));
1262 }
1263
1264 void WebDriverService::elementClick(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1265 {
1266     // §14.1 Element Click.
1267     // https://www.w3.org/TR/webdriver/#element-click
1268     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1269         return;
1270
1271     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1272     if (!elementID)
1273         return;
1274
1275     m_session->elementClick(elementID.value(), WTFMove(completionHandler));
1276 }
1277
1278 void WebDriverService::elementClear(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1279 {
1280     // §14.2 Element Clear.
1281     // https://www.w3.org/TR/webdriver/#element-clear
1282     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1283         return;
1284
1285     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1286     if (!elementID)
1287         return;
1288
1289     m_session->elementClear(elementID.value(), WTFMove(completionHandler));
1290 }
1291
1292 void WebDriverService::elementSendKeys(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1293 {
1294     // §14.3 Element Send Keys.
1295     // https://www.w3.org/TR/webdriver/#element-send-keys
1296     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1297         return;
1298
1299     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1300     if (!elementID)
1301         return;
1302
1303     RefPtr<JSON::Array> valueArray;
1304     if (!parameters->getArray(ASCIILiteral("value"), valueArray)) {
1305         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1306         return;
1307     }
1308
1309     unsigned valueArrayLength = valueArray->length();
1310     if (!valueArrayLength) {
1311         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1312         return;
1313     }
1314     Vector<String> value;
1315     value.reserveInitialCapacity(valueArrayLength);
1316     for (unsigned i = 0; i < valueArrayLength; ++i) {
1317         if (auto keyValue = valueArray->get(i)) {
1318             String key;
1319             if (keyValue->asString(key))
1320                 value.uncheckedAppend(WTFMove(key));
1321         }
1322     }
1323     if (!value.size()) {
1324         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1325         return;
1326     }
1327
1328     m_session->elementSendKeys(elementID.value(), WTFMove(value), WTFMove(completionHandler));
1329 }
1330
1331 static bool findScriptAndArgumentsOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& script, RefPtr<JSON::Array>& arguments)
1332 {
1333     if (!parameters.getString(ASCIILiteral("script"), script)) {
1334         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1335         return false;
1336     }
1337     if (!parameters.getArray(ASCIILiteral("args"), arguments)) {
1338         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1339         return false;
1340     }
1341     return true;
1342 }
1343
1344 void WebDriverService::executeScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1345 {
1346     // §15.2.1 Execute Script.
1347     // https://www.w3.org/TR/webdriver/#execute-script
1348     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1349         return;
1350
1351     String script;
1352     RefPtr<JSON::Array> arguments;
1353     if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
1354         return;
1355
1356     m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1357         if (result.isError()) {
1358             completionHandler(WTFMove(result));
1359             return;
1360         }
1361         m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Sync, WTFMove(completionHandler));
1362     });
1363 }
1364
1365 void WebDriverService::executeAsyncScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1366 {
1367     // §15.2.2 Execute Async Script.
1368     // https://www.w3.org/TR/webdriver/#execute-async-script
1369     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1370         return;
1371
1372     String script;
1373     RefPtr<JSON::Array> arguments;
1374     if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
1375         return;
1376
1377     m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1378         if (result.isError()) {
1379             completionHandler(WTFMove(result));
1380             return;
1381         }
1382         m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Async, WTFMove(completionHandler));
1383     });
1384 }
1385
1386 void WebDriverService::getAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1387 {
1388     // §16.1 Get All Cookies.
1389     // https://w3c.github.io/webdriver/webdriver-spec.html#get-all-cookies
1390     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1391         return;
1392
1393     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1394         if (result.isError()) {
1395             completionHandler(WTFMove(result));
1396             return;
1397         }
1398         m_session->getAllCookies(WTFMove(completionHandler));
1399     });
1400 }
1401
1402 void WebDriverService::getNamedCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1403 {
1404     // §16.2 Get Named Cookie.
1405     // https://w3c.github.io/webdriver/webdriver-spec.html#get-named-cookie
1406     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1407         return;
1408
1409     String name;
1410     if (!parameters->getString(ASCIILiteral("name"), name)) {
1411         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1412         return;
1413     }
1414
1415     m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1416         if (result.isError()) {
1417             completionHandler(WTFMove(result));
1418             return;
1419         }
1420         m_session->getNamedCookie(name, WTFMove(completionHandler));
1421     });
1422 }
1423
1424 static std::optional<Session::Cookie> deserializeCookie(JSON::Object& cookieObject)
1425 {
1426     Session::Cookie cookie;
1427     if (!cookieObject.getString(ASCIILiteral("name"), cookie.name) || cookie.name.isEmpty())
1428         return std::nullopt;
1429     if (!cookieObject.getString(ASCIILiteral("value"), cookie.value) || cookie.value.isEmpty())
1430         return std::nullopt;
1431
1432     RefPtr<JSON::Value> value;
1433     if (cookieObject.getValue(ASCIILiteral("path"), value)) {
1434         String path;
1435         if (!value->asString(path))
1436             return std::nullopt;
1437         cookie.path = path;
1438     }
1439     if (cookieObject.getValue(ASCIILiteral("domain"), value)) {
1440         String domain;
1441         if (!value->asString(domain))
1442             return std::nullopt;
1443         cookie.domain = domain;
1444     }
1445     if (cookieObject.getValue(ASCIILiteral("secure"), value)) {
1446         bool secure;
1447         if (!value->asBoolean(secure))
1448             return std::nullopt;
1449         cookie.secure = secure;
1450     }
1451     if (cookieObject.getValue(ASCIILiteral("httpOnly"), value)) {
1452         bool httpOnly;
1453         if (!value->asBoolean(httpOnly))
1454             return std::nullopt;
1455         cookie.httpOnly = httpOnly;
1456     }
1457     if (cookieObject.getValue(ASCIILiteral("expiry"), value)) {
1458         int expiry;
1459         if (!value->asInteger(expiry) || expiry < 0 || expiry > INT_MAX)
1460             return std::nullopt;
1461
1462         cookie.expiry = static_cast<unsigned>(expiry);
1463     }
1464
1465     return cookie;
1466 }
1467
1468 void WebDriverService::addCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1469 {
1470     // §16.3 Add Cookie.
1471     // https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
1472     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1473         return;
1474
1475     RefPtr<JSON::Object> cookieObject;
1476     if (!parameters->getObject(ASCIILiteral("cookie"), cookieObject)) {
1477         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1478         return;
1479     }
1480
1481     auto cookie = deserializeCookie(*cookieObject);
1482     if (!cookie) {
1483         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1484         return;
1485     }
1486
1487     m_session->waitForNavigationToComplete([this, cookie = WTFMove(cookie), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1488         if (result.isError()) {
1489             completionHandler(WTFMove(result));
1490             return;
1491         }
1492         m_session->addCookie(cookie.value(), WTFMove(completionHandler));
1493     });
1494 }
1495
1496 void WebDriverService::deleteCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1497 {
1498     // §16.4 Delete Cookie.
1499     // https://w3c.github.io/webdriver/webdriver-spec.html#delete-cookie
1500     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1501         return;
1502
1503     String name;
1504     if (!parameters->getString(ASCIILiteral("name"), name)) {
1505         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1506         return;
1507     }
1508
1509     m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1510         if (result.isError()) {
1511             completionHandler(WTFMove(result));
1512             return;
1513         }
1514         m_session->deleteCookie(name, WTFMove(completionHandler));
1515     });
1516 }
1517
1518 void WebDriverService::deleteAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1519 {
1520     // §16.5 Delete All Cookies.
1521     // https://w3c.github.io/webdriver/webdriver-spec.html#delete-all-cookies
1522     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1523         return;
1524
1525     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1526         if (result.isError()) {
1527             completionHandler(WTFMove(result));
1528             return;
1529         }
1530         m_session->deleteAllCookies(WTFMove(completionHandler));
1531     });
1532 }
1533
1534 void WebDriverService::dismissAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1535 {
1536     // §18.1 Dismiss Alert.
1537     // https://w3c.github.io/webdriver/webdriver-spec.html#dismiss-alert
1538     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1539         return;
1540
1541     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1542         if (result.isError()) {
1543             completionHandler(WTFMove(result));
1544             return;
1545         }
1546         m_session->dismissAlert(WTFMove(completionHandler));
1547     });
1548 }
1549
1550 void WebDriverService::acceptAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1551 {
1552     // §18.2 Accept Alert.
1553     // https://w3c.github.io/webdriver/webdriver-spec.html#accept-alert
1554     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1555         return;
1556
1557     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1558         if (result.isError()) {
1559             completionHandler(WTFMove(result));
1560             return;
1561         }
1562         m_session->acceptAlert(WTFMove(completionHandler));
1563     });
1564 }
1565
1566 void WebDriverService::getAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1567 {
1568     // §18.3 Get Alert Text.
1569     // https://w3c.github.io/webdriver/webdriver-spec.html#get-alert-text
1570     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1571         return;
1572
1573     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1574         if (result.isError()) {
1575             completionHandler(WTFMove(result));
1576             return;
1577         }
1578         m_session->getAlertText(WTFMove(completionHandler));
1579     });
1580 }
1581
1582 void WebDriverService::sendAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1583 {
1584     // §18.4 Send Alert Text.
1585     // https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text
1586     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1587         return;
1588
1589     String text;
1590     if (!parameters->getString(ASCIILiteral("text"), text)) {
1591         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1592         return;
1593     }
1594
1595     m_session->waitForNavigationToComplete([this, text = WTFMove(text), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1596         if (result.isError()) {
1597             completionHandler(WTFMove(result));
1598             return;
1599         }
1600         m_session->sendAlertText(text, WTFMove(completionHandler));
1601     });
1602 }
1603
1604 void WebDriverService::takeScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1605 {
1606     // §19.1 Take Screenshot.
1607     // https://w3c.github.io/webdriver/webdriver-spec.html#take-screenshot
1608     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1609         return;
1610
1611     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1612         if (result.isError()) {
1613             completionHandler(WTFMove(result));
1614             return;
1615         }
1616         m_session->takeScreenshot(std::nullopt, std::nullopt, WTFMove(completionHandler));
1617     });
1618 }
1619
1620 void WebDriverService::takeElementScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1621 {
1622     // §19.2 Take Element Screenshot.
1623     // https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot
1624     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1625         return;
1626
1627     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1628     if (!elementID)
1629         return;
1630
1631     bool scrollIntoView = true;
1632     parameters->getBoolean(ASCIILiteral("scroll"), scrollIntoView);
1633
1634     m_session->waitForNavigationToComplete([this, elementID, scrollIntoView, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1635         if (result.isError()) {
1636             completionHandler(WTFMove(result));
1637             return;
1638         }
1639         m_session->takeScreenshot(elementID.value(), scrollIntoView, WTFMove(completionHandler));
1640     });
1641 }
1642
1643 } // namespace WebDriver