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