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