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