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