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