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