Use Optional::valueOr() instead of Optional::value_or()
[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 // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-maximum-safe-integer
38 static const double maxSafeInteger = 9007199254740991.0; // 2 ^ 53 - 1
39
40 WebDriverService::WebDriverService()
41     : m_server(*this)
42 {
43 }
44
45 static void printUsageStatement(const char* programName)
46 {
47     printf("Usage: %s options\n", programName);
48     printf("  -h,        --help         Prints this help message\n");
49     printf("  -p <port>, --port=<port>  Port number the driver will use\n");
50     printf("             --host=<host>  Host IP the driver will use, or either 'local' or 'all' (default: 'local')");
51     printf("\n");
52 }
53
54 int WebDriverService::run(int argc, char** argv)
55 {
56     String portString;
57     Optional<String> host;
58     for (int i = 1 ; i < argc; ++i) {
59         const char* arg = argv[i];
60         if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
61             printUsageStatement(argv[0]);
62             return EXIT_SUCCESS;
63         }
64
65         if (!strcmp(arg, "-p") && portString.isNull()) {
66             if (++i == argc) {
67                 printUsageStatement(argv[0]);
68                 return EXIT_FAILURE;
69             }
70             portString = argv[i];
71             continue;
72         }
73
74         static const unsigned portStrLength = strlen("--port=");
75         if (!strncmp(arg, "--port=", portStrLength) && portString.isNull()) {
76             portString = String(arg + portStrLength);
77             continue;
78         }
79
80         static const unsigned hostStrLength = strlen("--host=");
81         if (!strncmp(arg, "--host=", hostStrLength) && !host) {
82             host = String(arg + hostStrLength);
83             continue;
84         }
85     }
86
87     if (portString.isNull()) {
88         printUsageStatement(argv[0]);
89         return EXIT_FAILURE;
90     }
91
92     bool ok;
93     unsigned port = portString.toUInt(&ok);
94     if (!ok) {
95         fprintf(stderr, "Invalid port %s provided\n", portString.ascii().data());
96         return EXIT_FAILURE;
97     }
98
99     RunLoop::initializeMainRunLoop();
100
101     if (!m_server.listen(host, port))
102         return EXIT_FAILURE;
103
104     RunLoop::run();
105
106     m_server.disconnect();
107
108     return EXIT_SUCCESS;
109 }
110
111 const WebDriverService::Command WebDriverService::s_commands[] = {
112     { HTTPMethod::Post, "/session", &WebDriverService::newSession },
113     { HTTPMethod::Delete, "/session/$sessionId", &WebDriverService::deleteSession },
114     { HTTPMethod::Get, "/status", &WebDriverService::status },
115     { HTTPMethod::Get, "/session/$sessionId/timeouts", &WebDriverService::getTimeouts },
116     { HTTPMethod::Post, "/session/$sessionId/timeouts", &WebDriverService::setTimeouts },
117
118     { HTTPMethod::Post, "/session/$sessionId/url", &WebDriverService::go },
119     { HTTPMethod::Get, "/session/$sessionId/url", &WebDriverService::getCurrentURL },
120     { HTTPMethod::Post, "/session/$sessionId/back", &WebDriverService::back },
121     { HTTPMethod::Post, "/session/$sessionId/forward", &WebDriverService::forward },
122     { HTTPMethod::Post, "/session/$sessionId/refresh", &WebDriverService::refresh },
123     { HTTPMethod::Get, "/session/$sessionId/title", &WebDriverService::getTitle },
124
125     { HTTPMethod::Get, "/session/$sessionId/window", &WebDriverService::getWindowHandle },
126     { HTTPMethod::Delete, "/session/$sessionId/window", &WebDriverService::closeWindow },
127     { HTTPMethod::Post, "/session/$sessionId/window", &WebDriverService::switchToWindow },
128     { HTTPMethod::Get, "/session/$sessionId/window/handles", &WebDriverService::getWindowHandles },
129     { HTTPMethod::Post, "/session/$sessionId/frame", &WebDriverService::switchToFrame },
130     { HTTPMethod::Post, "/session/$sessionId/frame/parent", &WebDriverService::switchToParentFrame },
131     { HTTPMethod::Get, "/session/$sessionId/window/rect", &WebDriverService::getWindowRect },
132     { HTTPMethod::Post, "/session/$sessionId/window/rect", &WebDriverService::setWindowRect },
133     { HTTPMethod::Post, "/session/$sessionId/window/maximize", &WebDriverService::maximizeWindow },
134     { HTTPMethod::Post, "/session/$sessionId/window/minimize", &WebDriverService::minimizeWindow },
135     { HTTPMethod::Post, "/session/$sessionId/window/fullscreen", &WebDriverService::fullscreenWindow },
136
137     { HTTPMethod::Post, "/session/$sessionId/element", &WebDriverService::findElement },
138     { HTTPMethod::Post, "/session/$sessionId/elements", &WebDriverService::findElements },
139     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/element", &WebDriverService::findElementFromElement },
140     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/elements", &WebDriverService::findElementsFromElement },
141     { HTTPMethod::Get, "/session/$sessionId/element/active", &WebDriverService::getActiveElement },
142
143     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/selected", &WebDriverService::isElementSelected },
144     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/attribute/$name", &WebDriverService::getElementAttribute },
145     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/property/$name", &WebDriverService::getElementProperty },
146     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/css/$name", &WebDriverService::getElementCSSValue },
147     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/text", &WebDriverService::getElementText },
148     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/name", &WebDriverService::getElementTagName },
149     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/rect", &WebDriverService::getElementRect },
150     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/enabled", &WebDriverService::isElementEnabled },
151
152     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/click", &WebDriverService::elementClick },
153     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/clear", &WebDriverService::elementClear },
154     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/value", &WebDriverService::elementSendKeys },
155
156     { HTTPMethod::Post, "/session/$sessionId/execute/sync", &WebDriverService::executeScript },
157     { HTTPMethod::Post, "/session/$sessionId/execute/async", &WebDriverService::executeAsyncScript },
158
159     { HTTPMethod::Get, "/session/$sessionId/cookie", &WebDriverService::getAllCookies },
160     { HTTPMethod::Get, "/session/$sessionId/cookie/$name", &WebDriverService::getNamedCookie },
161     { HTTPMethod::Post, "/session/$sessionId/cookie", &WebDriverService::addCookie },
162     { HTTPMethod::Delete, "/session/$sessionId/cookie/$name", &WebDriverService::deleteCookie },
163     { HTTPMethod::Delete, "/session/$sessionId/cookie", &WebDriverService::deleteAllCookies },
164
165     { HTTPMethod::Post, "/session/$sessionId/actions", &WebDriverService::performActions },
166     { HTTPMethod::Delete, "/session/$sessionId/actions", &WebDriverService::releaseActions },
167
168     { HTTPMethod::Post, "/session/$sessionId/alert/dismiss", &WebDriverService::dismissAlert },
169     { HTTPMethod::Post, "/session/$sessionId/alert/accept", &WebDriverService::acceptAlert },
170     { HTTPMethod::Get, "/session/$sessionId/alert/text", &WebDriverService::getAlertText },
171     { HTTPMethod::Post, "/session/$sessionId/alert/text", &WebDriverService::sendAlertText },
172
173     { HTTPMethod::Get, "/session/$sessionId/screenshot", &WebDriverService::takeScreenshot },
174     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/screenshot", &WebDriverService::takeElementScreenshot },
175
176
177     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/displayed", &WebDriverService::isElementDisplayed },
178 };
179
180 Optional<WebDriverService::HTTPMethod> WebDriverService::toCommandHTTPMethod(const String& method)
181 {
182     auto lowerCaseMethod = method.convertToASCIILowercase();
183     if (lowerCaseMethod == "get")
184         return WebDriverService::HTTPMethod::Get;
185     if (lowerCaseMethod == "post" || lowerCaseMethod == "put")
186         return WebDriverService::HTTPMethod::Post;
187     if (lowerCaseMethod == "delete")
188         return WebDriverService::HTTPMethod::Delete;
189
190     return WTF::nullopt;
191 }
192
193 bool WebDriverService::findCommand(HTTPMethod method, const String& path, CommandHandler* handler, HashMap<String, String>& parameters)
194 {
195     size_t length = WTF_ARRAY_LENGTH(s_commands);
196     for (size_t i = 0; i < length; ++i) {
197         if (s_commands[i].method != method)
198             continue;
199
200         Vector<String> pathTokens = path.split('/');
201         Vector<String> commandTokens = String::fromUTF8(s_commands[i].uriTemplate).split('/');
202         if (pathTokens.size() != commandTokens.size())
203             continue;
204
205         bool allMatched = true;
206         for (size_t j = 0; j < pathTokens.size() && allMatched; ++j) {
207             if (commandTokens[j][0] == '$')
208                 parameters.set(commandTokens[j].substring(1), pathTokens[j]);
209             else if (commandTokens[j] != pathTokens[j])
210                 allMatched = false;
211         }
212
213         if (allMatched) {
214             *handler = s_commands[i].handler;
215             return true;
216         }
217
218         parameters.clear();
219     }
220
221     return false;
222 }
223
224 void WebDriverService::handleRequest(HTTPRequestHandler::Request&& request, Function<void (HTTPRequestHandler::Response&&)>&& replyHandler)
225 {
226     auto method = toCommandHTTPMethod(request.method);
227     if (!method) {
228         sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown method: " + request.method)));
229         return;
230     }
231     CommandHandler handler;
232     HashMap<String, String> parameters;
233     if (!findCommand(method.value(), request.path, &handler, parameters)) {
234         sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown command: " + request.path)));
235         return;
236     }
237
238     RefPtr<JSON::Object> parametersObject;
239     if (method.value() == HTTPMethod::Post && request.dataLength) {
240         RefPtr<JSON::Value> messageValue;
241         if (!JSON::Value::parseJSON(String::fromUTF8(request.data, request.dataLength), messageValue)) {
242             sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
243             return;
244         }
245
246         if (!messageValue->asObject(parametersObject)) {
247             sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
248             return;
249         }
250     } else
251         parametersObject = JSON::Object::create();
252     for (const auto& parameter : parameters)
253         parametersObject->setString(parameter.key, parameter.value);
254
255     ((*this).*handler)(WTFMove(parametersObject), [this, replyHandler = WTFMove(replyHandler)](CommandResult&& result) mutable {
256         sendResponse(WTFMove(replyHandler), WTFMove(result));
257     });
258 }
259
260 void WebDriverService::sendResponse(Function<void (HTTPRequestHandler::Response&&)>&& replyHandler, CommandResult&& result) const
261 {
262     // §6.3 Processing Model.
263     // https://w3c.github.io/webdriver/webdriver-spec.html#processing-model
264     RefPtr<JSON::Value> resultValue;
265     if (result.isError()) {
266         // When required to send an error.
267         // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-an-error
268         // Let body be a new JSON Object initialised with the following properties: "error", "message", "stacktrace".
269         auto errorObject = JSON::Object::create();
270         errorObject->setString("error"_s, result.errorString());
271         errorObject->setString("message"_s, result.errorMessage().valueOr(emptyString()));
272         errorObject->setString("stacktrace"_s, emptyString());
273         // If the error data dictionary contains any entries, set the "data" field on body to a new JSON Object populated with the dictionary.
274         if (auto& additionalData = result.additionalErrorData())
275             errorObject->setObject("data"_s, RefPtr<JSON::Object> { additionalData });
276         // Send a response with status and body as arguments.
277         resultValue = WTFMove(errorObject);
278     } else if (auto value = result.result())
279         resultValue = WTFMove(value);
280     else
281         resultValue = JSON::Value::null();
282
283     // When required to send a response.
284     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-send-a-response
285     RefPtr<JSON::Object> responseObject = JSON::Object::create();
286     responseObject->setValue("value"_s, WTFMove(resultValue));
287     replyHandler({ result.httpStatusCode(), responseObject->toJSONString().utf8(), "application/json; charset=utf-8"_s });
288 }
289
290 static Optional<double> valueAsNumberInRange(const JSON::Value& value, double minAllowed = 0, double maxAllowed = std::numeric_limits<int>::max())
291 {
292     double number;
293     if (!value.asDouble(number))
294         return WTF::nullopt;
295
296     if (std::isnan(number) || std::isinf(number))
297         return WTF::nullopt;
298
299     if (number < minAllowed || number > maxAllowed)
300         return WTF::nullopt;
301
302     return number;
303 }
304
305 static Optional<uint64_t> unsignedValue(JSON::Value& value)
306 {
307     auto number = valueAsNumberInRange(value, 0, maxSafeInteger);
308     if (!number)
309         return WTF::nullopt;
310
311     auto intValue = static_cast<uint64_t>(number.value());
312     // If the contained value is a double, bail in case it doesn't match the integer
313     // value, i.e. if the double value was not originally in integer form.
314     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-integer
315     if (number.value() != intValue)
316         return WTF::nullopt;
317
318     return intValue;
319 }
320
321 static Optional<Timeouts> deserializeTimeouts(JSON::Object& timeoutsObject)
322 {
323     // §8.5 Set Timeouts.
324     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-deserialize-as-a-timeout
325     Timeouts timeouts;
326     auto end = timeoutsObject.end();
327     for (auto it = timeoutsObject.begin(); it != end; ++it) {
328         if (it->key == "sessionId")
329             continue;
330
331         // If value is not an integer, or it is less than 0 or greater than the maximum safe integer, return error with error code invalid argument.
332         auto timeoutMS = unsignedValue(*it->value);
333         if (!timeoutMS)
334             return WTF::nullopt;
335
336         if (it->key == "script")
337             timeouts.script = Seconds::fromMilliseconds(timeoutMS.value());
338         else if (it->key == "pageLoad")
339             timeouts.pageLoad = Seconds::fromMilliseconds(timeoutMS.value());
340         else if (it->key == "implicit")
341             timeouts.implicit = Seconds::fromMilliseconds(timeoutMS.value());
342         else
343             return WTF::nullopt;
344     }
345     return timeouts;
346 }
347
348 static Optional<PageLoadStrategy> deserializePageLoadStrategy(const String& pageLoadStrategy)
349 {
350     if (pageLoadStrategy == "none")
351         return PageLoadStrategy::None;
352     if (pageLoadStrategy == "normal")
353         return PageLoadStrategy::Normal;
354     if (pageLoadStrategy == "eager")
355         return PageLoadStrategy::Eager;
356     return WTF::nullopt;
357 }
358
359 static Optional<UnhandledPromptBehavior> deserializeUnhandledPromptBehavior(const String& unhandledPromptBehavior)
360 {
361     if (unhandledPromptBehavior == "dismiss")
362         return UnhandledPromptBehavior::Dismiss;
363     if (unhandledPromptBehavior == "accept")
364         return UnhandledPromptBehavior::Accept;
365     if (unhandledPromptBehavior == "dismiss and notify")
366         return UnhandledPromptBehavior::DismissAndNotify;
367     if (unhandledPromptBehavior == "accept and notify")
368         return UnhandledPromptBehavior::AcceptAndNotify;
369     if (unhandledPromptBehavior == "ignore")
370         return UnhandledPromptBehavior::Ignore;
371     return WTF::nullopt;
372 }
373
374 void WebDriverService::parseCapabilities(const JSON::Object& matchedCapabilities, Capabilities& capabilities) const
375 {
376     // Matched capabilities have already been validated.
377     bool acceptInsecureCerts;
378     if (matchedCapabilities.getBoolean("acceptInsecureCerts"_s, acceptInsecureCerts))
379         capabilities.acceptInsecureCerts = acceptInsecureCerts;
380     bool setWindowRect;
381     if (matchedCapabilities.getBoolean("setWindowRect"_s, setWindowRect))
382         capabilities.setWindowRect = setWindowRect;
383     String browserName;
384     if (matchedCapabilities.getString("browserName"_s, browserName))
385         capabilities.browserName = browserName;
386     String browserVersion;
387     if (matchedCapabilities.getString("browserVersion"_s, browserVersion))
388         capabilities.browserVersion = browserVersion;
389     String platformName;
390     if (matchedCapabilities.getString("platformName"_s, platformName))
391         capabilities.platformName = platformName;
392     RefPtr<JSON::Object> timeouts;
393     if (matchedCapabilities.getObject("timeouts"_s, timeouts))
394         capabilities.timeouts = deserializeTimeouts(*timeouts);
395     String pageLoadStrategy;
396     if (matchedCapabilities.getString("pageLoadStrategy"_s, pageLoadStrategy))
397         capabilities.pageLoadStrategy = deserializePageLoadStrategy(pageLoadStrategy);
398     String unhandledPromptBehavior;
399     if (matchedCapabilities.getString("unhandledPromptBehavior"_s, unhandledPromptBehavior))
400         capabilities.unhandledPromptBehavior = deserializeUnhandledPromptBehavior(unhandledPromptBehavior);
401     platformParseCapabilities(matchedCapabilities, capabilities);
402 }
403
404 bool WebDriverService::findSessionOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
405 {
406     String sessionID;
407     if (!parameters.getString("sessionId"_s, sessionID)) {
408         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
409         return false;
410     }
411
412     if (!m_session || m_session->id() != sessionID) {
413         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
414         return false;
415     }
416
417     return true;
418 }
419
420 RefPtr<JSON::Object> WebDriverService::validatedCapabilities(const JSON::Object& capabilities) const
421 {
422     // §7.2 Processing Capabilities.
423     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-validate-capabilities
424     RefPtr<JSON::Object> result = JSON::Object::create();
425     auto end = capabilities.end();
426     for (auto it = capabilities.begin(); it != end; ++it) {
427         if (it->value->isNull())
428             continue;
429         if (it->key == "acceptInsecureCerts") {
430             bool acceptInsecureCerts;
431             if (!it->value->asBoolean(acceptInsecureCerts))
432                 return nullptr;
433             result->setBoolean(it->key, acceptInsecureCerts);
434         } else if (it->key == "browserName" || it->key == "browserVersion" || it->key == "platformName") {
435             String stringValue;
436             if (!it->value->asString(stringValue))
437                 return nullptr;
438             result->setString(it->key, stringValue);
439         } else if (it->key == "pageLoadStrategy") {
440             String pageLoadStrategy;
441             if (!it->value->asString(pageLoadStrategy) || !deserializePageLoadStrategy(pageLoadStrategy))
442                 return nullptr;
443             result->setString(it->key, pageLoadStrategy);
444         } else if (it->key == "proxy") {
445             // FIXME: implement proxy support.
446         } else if (it->key == "timeouts") {
447             RefPtr<JSON::Object> timeouts;
448             if (!it->value->asObject(timeouts) || !deserializeTimeouts(*timeouts))
449                 return nullptr;
450             result->setValue(it->key, RefPtr<JSON::Value>(it->value));
451         } else if (it->key == "unhandledPromptBehavior") {
452             String unhandledPromptBehavior;
453             if (!it->value->asString(unhandledPromptBehavior) || !deserializeUnhandledPromptBehavior(unhandledPromptBehavior))
454                 return nullptr;
455             result->setString(it->key, unhandledPromptBehavior);
456         } else if (it->key.find(":") != notFound) {
457             if (!platformValidateCapability(it->key, it->value))
458                 return nullptr;
459             result->setValue(it->key, RefPtr<JSON::Value>(it->value));
460         } else
461             return nullptr;
462     }
463     return result;
464 }
465
466 RefPtr<JSON::Object> WebDriverService::mergeCapabilities(const JSON::Object& requiredCapabilities, const JSON::Object& firstMatchCapabilities) const
467 {
468     // §7.2 Processing Capabilities.
469     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-merging-capabilities
470     RefPtr<JSON::Object> result = JSON::Object::create();
471     auto requiredEnd = requiredCapabilities.end();
472     for (auto it = requiredCapabilities.begin(); it != requiredEnd; ++it)
473         result->setValue(it->key, RefPtr<JSON::Value>(it->value));
474
475     auto firstMatchEnd = firstMatchCapabilities.end();
476     for (auto it = firstMatchCapabilities.begin(); it != firstMatchEnd; ++it)
477         result->setValue(it->key, RefPtr<JSON::Value>(it->value));
478
479     return result;
480 }
481
482 RefPtr<JSON::Object> WebDriverService::matchCapabilities(const JSON::Object& mergedCapabilities) const
483 {
484     // §7.2 Processing Capabilities.
485     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-matching-capabilities
486     Capabilities platformCapabilities = this->platformCapabilities();
487
488     // Some capabilities like browser name and version might need to launch the browser,
489     // so we only reject the known capabilities that don't match.
490     RefPtr<JSON::Object> matchedCapabilities = JSON::Object::create();
491     if (platformCapabilities.browserName)
492         matchedCapabilities->setString("browserName"_s, platformCapabilities.browserName.value());
493     if (platformCapabilities.browserVersion)
494         matchedCapabilities->setString("browserVersion"_s, platformCapabilities.browserVersion.value());
495     if (platformCapabilities.platformName)
496         matchedCapabilities->setString("platformName"_s, platformCapabilities.platformName.value());
497     if (platformCapabilities.acceptInsecureCerts)
498         matchedCapabilities->setBoolean("acceptInsecureCerts"_s, platformCapabilities.acceptInsecureCerts.value());
499     if (platformCapabilities.setWindowRect)
500         matchedCapabilities->setBoolean("setWindowRect"_s, platformCapabilities.setWindowRect.value());
501
502     auto end = mergedCapabilities.end();
503     for (auto it = mergedCapabilities.begin(); it != end; ++it) {
504         if (it->key == "browserName" && platformCapabilities.browserName) {
505             String browserName;
506             it->value->asString(browserName);
507             if (!equalIgnoringASCIICase(platformCapabilities.browserName.value(), browserName))
508                 return nullptr;
509         } else if (it->key == "browserVersion" && platformCapabilities.browserVersion) {
510             String browserVersion;
511             it->value->asString(browserVersion);
512             if (!platformCompareBrowserVersions(browserVersion, platformCapabilities.browserVersion.value()))
513                 return nullptr;
514         } else if (it->key == "platformName" && platformCapabilities.platformName) {
515             String platformName;
516             it->value->asString(platformName);
517             if (!equalLettersIgnoringASCIICase(platformName, "any") && platformCapabilities.platformName.value() != platformName)
518                 return nullptr;
519         } else if (it->key == "acceptInsecureCerts" && platformCapabilities.acceptInsecureCerts) {
520             bool acceptInsecureCerts;
521             it->value->asBoolean(acceptInsecureCerts);
522             if (acceptInsecureCerts && !platformCapabilities.acceptInsecureCerts.value())
523                 return nullptr;
524         } else if (it->key == "proxy") {
525             // FIXME: implement proxy support.
526         } else if (!platformMatchCapability(it->key, it->value))
527             return nullptr;
528         matchedCapabilities->setValue(it->key, RefPtr<JSON::Value>(it->value));
529     }
530
531     return matchedCapabilities;
532 }
533
534 Vector<Capabilities> WebDriverService::processCapabilities(const JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler) const
535 {
536     // §7.2 Processing Capabilities.
537     // https://w3c.github.io/webdriver/webdriver-spec.html#processing-capabilities
538
539     // 1. Let capabilities request be the result of getting the property "capabilities" from parameters.
540     RefPtr<JSON::Object> capabilitiesObject;
541     if (!parameters.getObject("capabilities"_s, capabilitiesObject)) {
542         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
543         return { };
544     }
545
546     // 2. Let required capabilities be the result of getting the property "alwaysMatch" from capabilities request.
547     RefPtr<JSON::Value> requiredCapabilitiesValue;
548     RefPtr<JSON::Object> requiredCapabilities;
549     if (!capabilitiesObject->getValue("alwaysMatch"_s, requiredCapabilitiesValue))
550         // 2.1. If required capabilities is undefined, set the value to an empty JSON Object.
551         requiredCapabilities = JSON::Object::create();
552     else if (!requiredCapabilitiesValue->asObject(requiredCapabilities)) {
553         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("alwaysMatch is invalid in capabilities")));
554         return { };
555     }
556
557     // 2.2. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
558     requiredCapabilities = validatedCapabilities(*requiredCapabilities);
559     if (!requiredCapabilities) {
560         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid alwaysMatch capabilities")));
561         return { };
562     }
563
564     // 3. Let all first match capabilities be the result of getting the property "firstMatch" from capabilities request.
565     RefPtr<JSON::Value> firstMatchCapabilitiesValue;
566     RefPtr<JSON::Array> firstMatchCapabilitiesList;
567     if (!capabilitiesObject->getValue("firstMatch"_s, firstMatchCapabilitiesValue)) {
568         // 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.
569         firstMatchCapabilitiesList = JSON::Array::create();
570         firstMatchCapabilitiesList->pushObject(JSON::Object::create());
571     } else if (!firstMatchCapabilitiesValue->asArray(firstMatchCapabilitiesList)) {
572         // 3.2. If all first match capabilities is not a JSON List, return error with error code invalid argument.
573         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("firstMatch is invalid in capabilities")));
574         return { };
575     }
576
577     // 4. Let validated first match capabilities be an empty JSON List.
578     Vector<RefPtr<JSON::Object>> validatedFirstMatchCapabilitiesList;
579     auto firstMatchCapabilitiesListLength = firstMatchCapabilitiesList->length();
580     validatedFirstMatchCapabilitiesList.reserveInitialCapacity(firstMatchCapabilitiesListLength);
581     // 5. For each first match capabilities corresponding to an indexed property in all first match capabilities.
582     for (unsigned i = 0; i < firstMatchCapabilitiesListLength; ++i) {
583         RefPtr<JSON::Value> firstMatchCapabilitiesValue = firstMatchCapabilitiesList->get(i);
584         RefPtr<JSON::Object> firstMatchCapabilities;
585         if (!firstMatchCapabilitiesValue->asObject(firstMatchCapabilities)) {
586             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid capabilities found in firstMatch")));
587             return { };
588         }
589         // 5.1. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
590         firstMatchCapabilities = validatedCapabilities(*firstMatchCapabilities);
591         if (!firstMatchCapabilities) {
592             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("Invalid firstMatch capabilities")));
593             return { };
594         }
595
596         // Validate here that firstMatchCapabilities don't shadow alwaysMatchCapabilities.
597         auto requiredEnd = requiredCapabilities->end();
598         auto firstMatchEnd = firstMatchCapabilities->end();
599         for (auto it = firstMatchCapabilities->begin(); it != firstMatchEnd; ++it) {
600             if (requiredCapabilities->find(it->key) != requiredEnd) {
601                 completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument,
602                     makeString("Invalid firstMatch capabilities: key ", it->key, " is present in alwaysMatch")));
603                 return { };
604             }
605         }
606
607         // 5.2. Append validated capabilities to validated first match capabilities.
608         validatedFirstMatchCapabilitiesList.uncheckedAppend(WTFMove(firstMatchCapabilities));
609     }
610
611     // 6. For each first match capabilities corresponding to an indexed property in validated first match capabilities.
612     Vector<Capabilities> matchedCapabilitiesList;
613     matchedCapabilitiesList.reserveInitialCapacity(validatedFirstMatchCapabilitiesList.size());
614     for (auto& validatedFirstMatchCapabilies : validatedFirstMatchCapabilitiesList) {
615         // 6.1. Let merged capabilities be the result of trying to merge capabilities with required capabilities and first match capabilities as arguments.
616         auto mergedCapabilities = mergeCapabilities(*requiredCapabilities, *validatedFirstMatchCapabilies);
617
618         // 6.2. Let matched capabilities be the result of trying to match capabilities with merged capabilities as an argument.
619         if (auto matchedCapabilities = matchCapabilities(*mergedCapabilities)) {
620             // 6.3. If matched capabilities is not null return matched capabilities.
621             Capabilities capabilities;
622             parseCapabilities(*matchedCapabilities, capabilities);
623             matchedCapabilitiesList.uncheckedAppend(WTFMove(capabilities));
624         }
625     }
626
627     if (matchedCapabilitiesList.isEmpty()) {
628         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to match capabilities")));
629         return { };
630     }
631
632     return matchedCapabilitiesList;
633 }
634
635 void WebDriverService::newSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
636 {
637     // §8.1 New Session.
638     // https://www.w3.org/TR/webdriver/#new-session
639     if (m_session) {
640         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Maximum number of active sessions")));
641         return;
642     }
643
644     auto matchedCapabilitiesList = processCapabilities(*parameters, completionHandler);
645     if (matchedCapabilitiesList.isEmpty())
646         return;
647
648     // Reverse the vector to always take last item.
649     matchedCapabilitiesList.reverse();
650     connectToBrowser(WTFMove(matchedCapabilitiesList), WTFMove(completionHandler));
651 }
652
653 void WebDriverService::connectToBrowser(Vector<Capabilities>&& capabilitiesList, Function<void (CommandResult&&)>&& completionHandler)
654 {
655     if (capabilitiesList.isEmpty()) {
656         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to match capabilities")));
657         return;
658     }
659
660     auto sessionHost = std::make_unique<SessionHost>(capabilitiesList.takeLast());
661     auto* sessionHostPtr = sessionHost.get();
662     sessionHostPtr->connectToBrowser([this, capabilitiesList = WTFMove(capabilitiesList), sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](Optional<String> error) mutable {
663         if (error) {
664             completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, makeString("Failed to connect to browser: ", error.value())));
665             return;
666         }
667
668         createSession(WTFMove(capabilitiesList), WTFMove(sessionHost), WTFMove(completionHandler));
669     });
670 }
671
672 void WebDriverService::createSession(Vector<Capabilities>&& capabilitiesList, std::unique_ptr<SessionHost>&& sessionHost, Function<void (CommandResult&&)>&& completionHandler)
673 {
674     auto* sessionHostPtr = sessionHost.get();
675     sessionHostPtr->startAutomationSession([this, capabilitiesList = WTFMove(capabilitiesList), sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](bool capabilitiesDidMatch, Optional<String> errorMessage) mutable {
676         if (errorMessage) {
677             completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError, errorMessage.value()));
678             return;
679         }
680         if (!capabilitiesDidMatch) {
681             connectToBrowser(WTFMove(capabilitiesList), WTFMove(completionHandler));
682             return;
683         }
684
685         RefPtr<Session> session = Session::create(WTFMove(sessionHost));
686         session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
687             if (result.isError()) {
688                 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorMessage()));
689                 return;
690             }
691
692             m_session = WTFMove(session);
693
694             RefPtr<JSON::Object> resultObject = JSON::Object::create();
695             resultObject->setString("sessionId"_s, m_session->id());
696             RefPtr<JSON::Object> capabilitiesObject = JSON::Object::create();
697             const auto& capabilities = m_session->capabilities();
698             capabilitiesObject->setString("browserName"_s, capabilities.browserName.valueOr(emptyString()));
699             capabilitiesObject->setString("browserVersion"_s, capabilities.browserVersion.valueOr(emptyString()));
700             capabilitiesObject->setString("platformName"_s, capabilities.platformName.valueOr(emptyString()));
701             capabilitiesObject->setBoolean("acceptInsecureCerts"_s, capabilities.acceptInsecureCerts.valueOr(false));
702             capabilitiesObject->setBoolean("setWindowRect"_s, capabilities.setWindowRect.valueOr(true));
703             switch (capabilities.unhandledPromptBehavior.valueOr(UnhandledPromptBehavior::DismissAndNotify)) {
704             case UnhandledPromptBehavior::Dismiss:
705                 capabilitiesObject->setString("unhandledPromptBehavior"_s, "dismiss");
706                 break;
707             case UnhandledPromptBehavior::Accept:
708                 capabilitiesObject->setString("unhandledPromptBehavior"_s, "accept");
709                 break;
710             case UnhandledPromptBehavior::DismissAndNotify:
711                 capabilitiesObject->setString("unhandledPromptBehavior"_s, "dismiss and notify");
712                 break;
713             case UnhandledPromptBehavior::AcceptAndNotify:
714                 capabilitiesObject->setString("unhandledPromptBehavior"_s, "accept and notify");
715                 break;
716             case UnhandledPromptBehavior::Ignore:
717                 capabilitiesObject->setString("unhandledPromptBehavior"_s, "ignore");
718                 break;
719             }
720             switch (capabilities.pageLoadStrategy.valueOr(PageLoadStrategy::Normal)) {
721             case PageLoadStrategy::None:
722                 capabilitiesObject->setString("pageLoadStrategy"_s, "none");
723                 break;
724             case PageLoadStrategy::Normal:
725                 capabilitiesObject->setString("pageLoadStrategy"_s, "normal");
726                 break;
727             case PageLoadStrategy::Eager:
728                 capabilitiesObject->setString("pageLoadStrategy"_s, "eager");
729                 break;
730             }
731             // FIXME: implement proxy support.
732             capabilitiesObject->setObject("proxy"_s, JSON::Object::create());
733             RefPtr<JSON::Object> timeoutsObject = JSON::Object::create();
734             timeoutsObject->setInteger("script"_s, m_session->scriptTimeout().millisecondsAs<int>());
735             timeoutsObject->setInteger("pageLoad"_s, m_session->pageLoadTimeout().millisecondsAs<int>());
736             timeoutsObject->setInteger("implicit"_s, m_session->implicitWaitTimeout().millisecondsAs<int>());
737             capabilitiesObject->setObject("timeouts"_s, WTFMove(timeoutsObject));
738
739             resultObject->setObject("capabilities"_s, WTFMove(capabilitiesObject));
740             completionHandler(CommandResult::success(WTFMove(resultObject)));
741         });
742     });
743 }
744
745 void WebDriverService::deleteSession(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
746 {
747     // §8.2 Delete Session.
748     // https://www.w3.org/TR/webdriver/#delete-session
749     String sessionID;
750     if (!parameters->getString("sessionId"_s, sessionID)) {
751         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
752         return;
753     }
754
755     if (!m_session || m_session->id() != sessionID) {
756         completionHandler(CommandResult::success());
757         return;
758     }
759
760     auto session = std::exchange(m_session, nullptr);
761     session->close([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
762         // Ignore unknown errors when closing the session if the browser is closed.
763         if (result.isError() && result.errorCode() == CommandResult::ErrorCode::UnknownError && !session->isConnected())
764             completionHandler(CommandResult::success());
765         else
766             completionHandler(WTFMove(result));
767     });
768 }
769
770 void WebDriverService::status(RefPtr<JSON::Object>&&, Function<void (CommandResult&&)>&& completionHandler)
771 {
772     // §8.3 Status
773     // https://w3c.github.io/webdriver/webdriver-spec.html#status
774     auto body = JSON::Object::create();
775     body->setBoolean("ready"_s, !m_session);
776     body->setString("message"_s, m_session ? "A session already exists"_s : "No sessions"_s);
777     completionHandler(CommandResult::success(WTFMove(body)));
778 }
779
780 void WebDriverService::getTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
781 {
782     // §8.4 Get Timeouts.
783     // https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts
784     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
785         return;
786
787     m_session->getTimeouts(WTFMove(completionHandler));
788 }
789
790 void WebDriverService::setTimeouts(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
791 {
792     // §8.5 Set Timeouts.
793     // https://www.w3.org/TR/webdriver/#set-timeouts
794     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
795         return;
796
797     auto timeouts = deserializeTimeouts(*parameters);
798     if (!timeouts) {
799         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
800         return;
801     }
802
803     m_session->setTimeouts(timeouts.value(), WTFMove(completionHandler));
804 }
805
806 void WebDriverService::go(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
807 {
808     // §9.1 Go.
809     // https://www.w3.org/TR/webdriver/#go
810     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
811         return;
812
813     String url;
814     if (!parameters->getString("url"_s, url)) {
815         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
816         return;
817     }
818
819     m_session->waitForNavigationToComplete([this, url = WTFMove(url), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
820         if (result.isError()) {
821             completionHandler(WTFMove(result));
822             return;
823         }
824         m_session->go(url, WTFMove(completionHandler));
825     });
826 }
827
828 void WebDriverService::getCurrentURL(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
829 {
830     // §9.2 Get Current URL.
831     // https://www.w3.org/TR/webdriver/#get-current-url
832     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
833         return;
834
835     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
836         if (result.isError()) {
837             completionHandler(WTFMove(result));
838             return;
839         }
840         m_session->getCurrentURL(WTFMove(completionHandler));
841     });
842 }
843
844 void WebDriverService::back(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
845 {
846     // §9.3 Back.
847     // https://www.w3.org/TR/webdriver/#back
848     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
849         return;
850
851     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
852         if (result.isError()) {
853             completionHandler(WTFMove(result));
854             return;
855         }
856         m_session->back(WTFMove(completionHandler));
857     });
858 }
859
860 void WebDriverService::forward(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
861 {
862     // §9.4 Forward.
863     // https://www.w3.org/TR/webdriver/#forward
864     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
865         return;
866
867     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
868         if (result.isError()) {
869             completionHandler(WTFMove(result));
870             return;
871         }
872         m_session->forward(WTFMove(completionHandler));
873     });
874 }
875
876 void WebDriverService::refresh(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
877 {
878     // §9.5 Refresh.
879     // https://www.w3.org/TR/webdriver/#refresh
880     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
881         return;
882
883     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
884         if (result.isError()) {
885             completionHandler(WTFMove(result));
886             return;
887         }
888         m_session->refresh(WTFMove(completionHandler));
889     });
890 }
891
892 void WebDriverService::getTitle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
893 {
894     // §9.6 Get Title.
895     // https://www.w3.org/TR/webdriver/#get-title
896     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
897         return;
898
899     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
900         if (result.isError()) {
901             completionHandler(WTFMove(result));
902             return;
903         }
904         m_session->getTitle(WTFMove(completionHandler));
905     });
906 }
907
908 void WebDriverService::getWindowHandle(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
909 {
910     // §10.1 Get Window Handle.
911     // https://www.w3.org/TR/webdriver/#get-window-handle
912     if (findSessionOrCompleteWithError(*parameters, completionHandler))
913         m_session->getWindowHandle(WTFMove(completionHandler));
914 }
915
916 void WebDriverService::getWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
917 {
918     // §10.7.1 Get Window Rect.
919     // https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect
920     if (findSessionOrCompleteWithError(*parameters, completionHandler))
921         m_session->getWindowRect(WTFMove(completionHandler));
922 }
923
924 void WebDriverService::setWindowRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
925 {
926     // §10.7.2 Set Window Rect.
927     // https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect
928     RefPtr<JSON::Value> value;
929     Optional<double> width;
930     if (parameters->getValue("width"_s, value)) {
931         if (auto number = valueAsNumberInRange(*value))
932             width = number;
933         else if (!value->isNull()) {
934             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
935             return;
936         }
937     }
938     Optional<double> height;
939     if (parameters->getValue("height"_s, value)) {
940         if (auto number = valueAsNumberInRange(*value))
941             height = number;
942         else if (!value->isNull()) {
943             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
944             return;
945         }
946     }
947     Optional<double> x;
948     if (parameters->getValue("x"_s, value)) {
949         if (auto number = valueAsNumberInRange(*value, INT_MIN))
950             x = number;
951         else if (!value->isNull()) {
952             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
953             return;
954         }
955     }
956     Optional<double> y;
957     if (parameters->getValue("y"_s, value)) {
958         if (auto number = valueAsNumberInRange(*value, INT_MIN))
959             y = number;
960         else if (!value->isNull()) {
961             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
962             return;
963         }
964     }
965
966     // FIXME: If the remote end does not support the Set Window Rect command for the current
967     // top-level browsing context for any reason, return error with error code unsupported operation.
968
969     if (findSessionOrCompleteWithError(*parameters, completionHandler))
970         m_session->setWindowRect(x, y, width, height, WTFMove(completionHandler));
971 }
972
973 void WebDriverService::maximizeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
974 {
975     // §10.7.3 Maximize Window
976     // https://w3c.github.io/webdriver/#maximize-window
977     if (findSessionOrCompleteWithError(*parameters, completionHandler))
978         m_session->maximizeWindow(WTFMove(completionHandler));
979 }
980
981 void WebDriverService::minimizeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
982 {
983     // §10.7.4 Minimize Window
984     // https://w3c.github.io/webdriver/#minimize-window
985     if (findSessionOrCompleteWithError(*parameters, completionHandler))
986         m_session->minimizeWindow(WTFMove(completionHandler));
987 }
988
989 void WebDriverService::fullscreenWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
990 {
991     // §10.7.5 Fullscreen Window
992     // https://w3c.github.io/webdriver/#fullscreen-window
993     if (findSessionOrCompleteWithError(*parameters, completionHandler))
994         m_session->fullscreenWindow(WTFMove(completionHandler));
995 }
996
997 void WebDriverService::closeWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
998 {
999     // §10.2 Close Window.
1000     // https://www.w3.org/TR/webdriver/#close-window
1001     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1002         return;
1003
1004     m_session->closeWindow([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1005         if (result.isError()) {
1006             completionHandler(WTFMove(result));
1007             return;
1008         }
1009
1010         RefPtr<JSON::Array> handles;
1011         if (result.result()->asArray(handles) && !handles->length())
1012             m_session = nullptr;
1013
1014         completionHandler(WTFMove(result));
1015     });
1016 }
1017
1018 void WebDriverService::switchToWindow(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1019 {
1020     // §10.3 Switch To Window.
1021     // https://www.w3.org/TR/webdriver/#switch-to-window
1022     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1023         return;
1024
1025     String handle;
1026     if (!parameters->getString("handle"_s, handle)) {
1027         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1028         return;
1029     }
1030
1031     m_session->switchToWindow(handle, WTFMove(completionHandler));
1032 }
1033
1034 void WebDriverService::getWindowHandles(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1035 {
1036     // §10.4 Get Window Handles.
1037     // https://www.w3.org/TR/webdriver/#get-window-handles
1038     if (findSessionOrCompleteWithError(*parameters, completionHandler))
1039         m_session->getWindowHandles(WTFMove(completionHandler));
1040 }
1041
1042 void WebDriverService::switchToFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1043 {
1044     // §10.5 Switch To Frame.
1045     // https://www.w3.org/TR/webdriver/#switch-to-frame
1046     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1047         return;
1048
1049     RefPtr<JSON::Value> frameID;
1050     if (!parameters->getValue("id"_s, frameID)) {
1051         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1052         return;
1053     }
1054
1055     m_session->waitForNavigationToComplete([this, frameID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1056         if (result.isError()) {
1057             completionHandler(WTFMove(result));
1058             return;
1059         }
1060         m_session->switchToFrame(WTFMove(frameID), WTFMove(completionHandler));
1061     });
1062 }
1063
1064 void WebDriverService::switchToParentFrame(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1065 {
1066     // §10.6 Switch To Parent Frame.
1067     // https://www.w3.org/TR/webdriver/#switch-to-parent-frame
1068     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1069         return;
1070
1071     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1072         if (result.isError()) {
1073             completionHandler(WTFMove(result));
1074             return;
1075         }
1076         m_session->switchToParentFrame(WTFMove(completionHandler));
1077     });
1078 }
1079
1080 static Optional<String> findElementOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler)
1081 {
1082     String elementID;
1083     if (!parameters.getString("elementId"_s, elementID) || elementID.isEmpty()) {
1084         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1085         return WTF::nullopt;
1086     }
1087     return elementID;
1088 }
1089
1090 static inline bool isValidStrategy(const String& strategy)
1091 {
1092     // §12.1 Locator Strategies.
1093     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-table-of-location-strategies
1094     return strategy == "css selector"
1095         || strategy == "link text"
1096         || strategy == "partial link text"
1097         || strategy == "tag name"
1098         || strategy == "xpath";
1099 }
1100
1101 static bool findStrategyAndSelectorOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& strategy, String& selector)
1102 {
1103     if (!parameters.getString("using"_s, strategy) || !isValidStrategy(strategy)) {
1104         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1105         return false;
1106     }
1107     if (!parameters.getString("value"_s, selector)) {
1108         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1109         return false;
1110     }
1111     return true;
1112 }
1113
1114 void WebDriverService::findElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1115 {
1116     // §12.2 Find Element.
1117     // https://www.w3.org/TR/webdriver/#find-element
1118     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1119         return;
1120
1121     String strategy, selector;
1122     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1123         return;
1124
1125     m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1126         if (result.isError()) {
1127             completionHandler(WTFMove(result));
1128             return;
1129         }
1130         m_session->findElements(strategy, selector, Session::FindElementsMode::Single, emptyString(), WTFMove(completionHandler));
1131     });
1132 }
1133
1134 void WebDriverService::findElements(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1135 {
1136     // §12.3 Find Elements.
1137     // https://www.w3.org/TR/webdriver/#find-elements
1138     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1139         return;
1140
1141     String strategy, selector;
1142     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1143         return;
1144
1145     m_session->waitForNavigationToComplete([this, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1146         if (result.isError()) {
1147             completionHandler(WTFMove(result));
1148             return;
1149         }
1150         m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, emptyString(), WTFMove(completionHandler));
1151     });
1152 }
1153
1154 void WebDriverService::findElementFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1155 {
1156     // §12.4 Find Element From Element.
1157     // https://www.w3.org/TR/webdriver/#find-element-from-element
1158     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1159         return;
1160
1161     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1162     if (!elementID)
1163         return;
1164
1165     String strategy, selector;
1166     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1167         return;
1168
1169     m_session->findElements(strategy, selector, Session::FindElementsMode::Single, elementID.value(), WTFMove(completionHandler));
1170 }
1171
1172 void WebDriverService::findElementsFromElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1173 {
1174     // §12.5 Find Elements From Element.
1175     // https://www.w3.org/TR/webdriver/#find-elements-from-element
1176     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1177         return;
1178
1179     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1180     if (!elementID)
1181         return;
1182
1183     String strategy, selector;
1184     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
1185         return;
1186
1187     m_session->findElements(strategy, selector, Session::FindElementsMode::Multiple, elementID.value(), WTFMove(completionHandler));
1188 }
1189
1190 void WebDriverService::getActiveElement(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1191 {
1192     // §12.6 Get Active Element.
1193     // https://w3c.github.io/webdriver/webdriver-spec.html#get-active-element
1194     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1195         return;
1196
1197     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1198         if (result.isError()) {
1199             completionHandler(WTFMove(result));
1200             return;
1201         }
1202         m_session->getActiveElement(WTFMove(completionHandler));
1203     });
1204 }
1205
1206 void WebDriverService::isElementSelected(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1207 {
1208     // §13.1 Is Element Selected.
1209     // https://www.w3.org/TR/webdriver/#is-element-selected
1210     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1211         return;
1212
1213     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1214     if (!elementID)
1215         return;
1216
1217     m_session->isElementSelected(elementID.value(), WTFMove(completionHandler));
1218 }
1219
1220 void WebDriverService::getElementAttribute(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1221 {
1222     // §13.2 Get Element Attribute.
1223     // https://www.w3.org/TR/webdriver/#get-element-attribute
1224     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1225         return;
1226
1227     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1228     if (!elementID)
1229         return;
1230
1231     String attribute;
1232     if (!parameters->getString("name"_s, attribute)) {
1233         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1234         return;
1235     }
1236
1237     m_session->getElementAttribute(elementID.value(), attribute, WTFMove(completionHandler));
1238 }
1239
1240 void WebDriverService::getElementProperty(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1241 {
1242     // §13.3 Get Element Property
1243     // https://w3c.github.io/webdriver/webdriver-spec.html#get-element-property
1244     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1245         return;
1246
1247     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1248     if (!elementID)
1249         return;
1250
1251     String attribute;
1252     if (!parameters->getString("name"_s, attribute)) {
1253         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1254         return;
1255     }
1256
1257     m_session->getElementProperty(elementID.value(), attribute, WTFMove(completionHandler));
1258 }
1259
1260 void WebDriverService::getElementCSSValue(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1261 {
1262     // §13.4 Get Element CSS Value
1263     // https://w3c.github.io/webdriver/webdriver-spec.html#get-element-css-value
1264     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1265         return;
1266
1267     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1268     if (!elementID)
1269         return;
1270
1271     String cssProperty;
1272     if (!parameters->getString("name"_s, cssProperty)) {
1273         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1274         return;
1275     }
1276
1277     m_session->getElementCSSValue(elementID.value(), cssProperty, WTFMove(completionHandler));
1278 }
1279
1280 void WebDriverService::getElementText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1281 {
1282     // §13.5 Get Element Text.
1283     // https://www.w3.org/TR/webdriver/#get-element-text
1284     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1285         return;
1286
1287     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1288     if (!elementID)
1289         return;
1290
1291     m_session->getElementText(elementID.value(), WTFMove(completionHandler));
1292 }
1293
1294 void WebDriverService::getElementTagName(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1295 {
1296     // §13.6 Get Element Tag Name.
1297     // https://www.w3.org/TR/webdriver/#get-element-tag-name
1298     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1299         return;
1300
1301     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1302     if (!elementID)
1303         return;
1304
1305     m_session->getElementTagName(elementID.value(), WTFMove(completionHandler));
1306 }
1307
1308 void WebDriverService::getElementRect(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1309 {
1310     // §13.7 Get Element Rect.
1311     // https://www.w3.org/TR/webdriver/#get-element-rect
1312     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1313         return;
1314
1315     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1316     if (!elementID)
1317         return;
1318
1319     m_session->getElementRect(elementID.value(), WTFMove(completionHandler));
1320 }
1321
1322 void WebDriverService::isElementEnabled(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1323 {
1324     // §13.8 Is Element Enabled.
1325     // https://www.w3.org/TR/webdriver/#is-element-enabled
1326     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1327         return;
1328
1329     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1330     if (!elementID)
1331         return;
1332
1333     m_session->isElementEnabled(elementID.value(), WTFMove(completionHandler));
1334 }
1335
1336 void WebDriverService::isElementDisplayed(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1337 {
1338     // §C. Element Displayedness.
1339     // https://www.w3.org/TR/webdriver/#element-displayedness
1340     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1341         return;
1342
1343     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1344     if (!elementID)
1345         return;
1346
1347     m_session->isElementDisplayed(elementID.value(), WTFMove(completionHandler));
1348 }
1349
1350 void WebDriverService::elementClick(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1351 {
1352     // §14.1 Element Click.
1353     // https://www.w3.org/TR/webdriver/#element-click
1354     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1355         return;
1356
1357     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1358     if (!elementID)
1359         return;
1360
1361     m_session->elementClick(elementID.value(), WTFMove(completionHandler));
1362 }
1363
1364 void WebDriverService::elementClear(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1365 {
1366     // §14.2 Element Clear.
1367     // https://www.w3.org/TR/webdriver/#element-clear
1368     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1369         return;
1370
1371     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1372     if (!elementID)
1373         return;
1374
1375     m_session->elementClear(elementID.value(), WTFMove(completionHandler));
1376 }
1377
1378 void WebDriverService::elementSendKeys(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1379 {
1380     // §14.3 Element Send Keys.
1381     // https://www.w3.org/TR/webdriver/#element-send-keys
1382     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1383         return;
1384
1385     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
1386     if (!elementID)
1387         return;
1388
1389     String text;
1390     if (!parameters->getString("text"_s, text) || text.isEmpty()) {
1391         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1392         return;
1393     }
1394
1395     m_session->elementSendKeys(elementID.value(), text, WTFMove(completionHandler));
1396 }
1397
1398 static bool findScriptAndArgumentsOrCompleteWithError(JSON::Object& parameters, Function<void (CommandResult&&)>& completionHandler, String& script, RefPtr<JSON::Array>& arguments)
1399 {
1400     if (!parameters.getString("script"_s, script)) {
1401         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1402         return false;
1403     }
1404     if (!parameters.getArray("args"_s, arguments)) {
1405         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1406         return false;
1407     }
1408     return true;
1409 }
1410
1411 void WebDriverService::executeScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1412 {
1413     // §15.2.1 Execute Script.
1414     // https://www.w3.org/TR/webdriver/#execute-script
1415     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1416         return;
1417
1418     String script;
1419     RefPtr<JSON::Array> arguments;
1420     if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
1421         return;
1422
1423     m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1424         if (result.isError()) {
1425             completionHandler(WTFMove(result));
1426             return;
1427         }
1428         m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Sync, WTFMove(completionHandler));
1429     });
1430 }
1431
1432 void WebDriverService::executeAsyncScript(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1433 {
1434     // §15.2.2 Execute Async Script.
1435     // https://www.w3.org/TR/webdriver/#execute-async-script
1436     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1437         return;
1438
1439     String script;
1440     RefPtr<JSON::Array> arguments;
1441     if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
1442         return;
1443
1444     m_session->waitForNavigationToComplete([this, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1445         if (result.isError()) {
1446             completionHandler(WTFMove(result));
1447             return;
1448         }
1449         m_session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Async, WTFMove(completionHandler));
1450     });
1451 }
1452
1453 void WebDriverService::getAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1454 {
1455     // §16.1 Get All Cookies.
1456     // https://w3c.github.io/webdriver/webdriver-spec.html#get-all-cookies
1457     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1458         return;
1459
1460     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1461         if (result.isError()) {
1462             completionHandler(WTFMove(result));
1463             return;
1464         }
1465         m_session->getAllCookies(WTFMove(completionHandler));
1466     });
1467 }
1468
1469 void WebDriverService::getNamedCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1470 {
1471     // §16.2 Get Named Cookie.
1472     // https://w3c.github.io/webdriver/webdriver-spec.html#get-named-cookie
1473     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1474         return;
1475
1476     String name;
1477     if (!parameters->getString("name"_s, name)) {
1478         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1479         return;
1480     }
1481
1482     m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1483         if (result.isError()) {
1484             completionHandler(WTFMove(result));
1485             return;
1486         }
1487         m_session->getNamedCookie(name, WTFMove(completionHandler));
1488     });
1489 }
1490
1491 static Optional<Session::Cookie> deserializeCookie(JSON::Object& cookieObject)
1492 {
1493     Session::Cookie cookie;
1494     if (!cookieObject.getString("name"_s, cookie.name) || cookie.name.isEmpty())
1495         return WTF::nullopt;
1496     if (!cookieObject.getString("value"_s, cookie.value) || cookie.value.isEmpty())
1497         return WTF::nullopt;
1498
1499     RefPtr<JSON::Value> value;
1500     if (cookieObject.getValue("path"_s, value)) {
1501         String path;
1502         if (!value->asString(path))
1503             return WTF::nullopt;
1504         cookie.path = path;
1505     }
1506     if (cookieObject.getValue("domain"_s, value)) {
1507         String domain;
1508         if (!value->asString(domain))
1509             return WTF::nullopt;
1510         cookie.domain = domain;
1511     }
1512     if (cookieObject.getValue("secure"_s, value)) {
1513         bool secure;
1514         if (!value->asBoolean(secure))
1515             return WTF::nullopt;
1516         cookie.secure = secure;
1517     }
1518     if (cookieObject.getValue("httpOnly"_s, value)) {
1519         bool httpOnly;
1520         if (!value->asBoolean(httpOnly))
1521             return WTF::nullopt;
1522         cookie.httpOnly = httpOnly;
1523     }
1524     if (cookieObject.getValue("expiry"_s, value)) {
1525         auto expiry = unsignedValue(*value);
1526         if (!expiry)
1527             return WTF::nullopt;
1528         cookie.expiry = expiry.value();
1529     }
1530
1531     return cookie;
1532 }
1533
1534 void WebDriverService::addCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1535 {
1536     // §16.3 Add Cookie.
1537     // https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
1538     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1539         return;
1540
1541     RefPtr<JSON::Object> cookieObject;
1542     if (!parameters->getObject("cookie"_s, cookieObject)) {
1543         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1544         return;
1545     }
1546
1547     auto cookie = deserializeCookie(*cookieObject);
1548     if (!cookie) {
1549         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1550         return;
1551     }
1552
1553     m_session->waitForNavigationToComplete([this, cookie = WTFMove(cookie), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1554         if (result.isError()) {
1555             completionHandler(WTFMove(result));
1556             return;
1557         }
1558         m_session->addCookie(cookie.value(), WTFMove(completionHandler));
1559     });
1560 }
1561
1562 void WebDriverService::deleteCookie(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1563 {
1564     // §16.4 Delete Cookie.
1565     // https://w3c.github.io/webdriver/webdriver-spec.html#delete-cookie
1566     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1567         return;
1568
1569     String name;
1570     if (!parameters->getString("name"_s, name)) {
1571         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
1572         return;
1573     }
1574
1575     m_session->waitForNavigationToComplete([this, name = WTFMove(name), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1576         if (result.isError()) {
1577             completionHandler(WTFMove(result));
1578             return;
1579         }
1580         m_session->deleteCookie(name, WTFMove(completionHandler));
1581     });
1582 }
1583
1584 void WebDriverService::deleteAllCookies(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1585 {
1586     // §16.5 Delete All Cookies.
1587     // https://w3c.github.io/webdriver/webdriver-spec.html#delete-all-cookies
1588     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1589         return;
1590
1591     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1592         if (result.isError()) {
1593             completionHandler(WTFMove(result));
1594             return;
1595         }
1596         m_session->deleteAllCookies(WTFMove(completionHandler));
1597     });
1598 }
1599
1600 static bool processPauseAction(JSON::Object& actionItem, Action& action, Optional<String>& errorMessage)
1601 {
1602     RefPtr<JSON::Value> durationValue;
1603     if (!actionItem.getValue("duration"_s, durationValue)) {
1604         errorMessage = String("The parameter 'duration' is missing in pause action");
1605         return false;
1606     }
1607
1608     auto duration = unsignedValue(*durationValue);
1609     if (!duration) {
1610         errorMessage = String("The parameter 'duration' is invalid in pause action");
1611         return false;
1612     }
1613
1614     action.duration = duration.value();
1615     return true;
1616 }
1617
1618 static Optional<Action> processNullAction(const String& id, JSON::Object& actionItem, Optional<String>& errorMessage)
1619 {
1620     String subtype;
1621     actionItem.getString("type"_s, subtype);
1622     if (subtype != "pause") {
1623         errorMessage = String("The parameter 'type' in null action is invalid or missing");
1624         return WTF::nullopt;
1625     }
1626
1627     Action action(id, Action::Type::None, Action::Subtype::Pause);
1628     if (!processPauseAction(actionItem, action, errorMessage))
1629         return WTF::nullopt;
1630
1631     return action;
1632 }
1633
1634 static Optional<Action> processKeyAction(const String& id, JSON::Object& actionItem, Optional<String>& errorMessage)
1635 {
1636     Action::Subtype actionSubtype;
1637     String subtype;
1638     actionItem.getString("type"_s, subtype);
1639     if (subtype == "pause")
1640         actionSubtype = Action::Subtype::Pause;
1641     else if (subtype == "keyUp")
1642         actionSubtype = Action::Subtype::KeyUp;
1643     else if (subtype == "keyDown")
1644         actionSubtype = Action::Subtype::KeyDown;
1645     else {
1646         errorMessage = String("The parameter 'type' of key action is invalid");
1647         return WTF::nullopt;
1648     }
1649
1650     Action action(id, Action::Type::Key, actionSubtype);
1651
1652     switch (actionSubtype) {
1653     case Action::Subtype::Pause:
1654         if (!processPauseAction(actionItem, action, errorMessage))
1655             return WTF::nullopt;
1656         break;
1657     case Action::Subtype::KeyUp:
1658     case Action::Subtype::KeyDown: {
1659         RefPtr<JSON::Value> keyValue;
1660         if (!actionItem.getValue("value"_s, keyValue)) {
1661             errorMessage = String("The paramater 'value' is missing for key up/down action");
1662             return WTF::nullopt;
1663         }
1664         String key;
1665         if (!keyValue->asString(key) || key.isEmpty()) {
1666             errorMessage = String("The paramater 'value' is invalid for key up/down action");
1667             return WTF::nullopt;
1668         }
1669         // FIXME: check single unicode code point.
1670         action.key = key;
1671         break;
1672     }
1673     case Action::Subtype::PointerUp:
1674     case Action::Subtype::PointerDown:
1675     case Action::Subtype::PointerMove:
1676     case Action::Subtype::PointerCancel:
1677         ASSERT_NOT_REACHED();
1678     }
1679
1680     return action;
1681 }
1682
1683 static MouseButton actionMouseButton(unsigned button)
1684 {
1685     // MouseEvent.button
1686     // https://www.w3.org/TR/uievents/#ref-for-dom-mouseevent-button-1
1687     switch (button) {
1688     case 0:
1689         return MouseButton::Left;
1690     case 1:
1691         return MouseButton::Middle;
1692     case 2:
1693         return MouseButton::Right;
1694     }
1695
1696     return MouseButton::None;
1697 }
1698
1699 static Optional<Action> processPointerAction(const String& id, PointerParameters& parameters, JSON::Object& actionItem, Optional<String>& errorMessage)
1700 {
1701     Action::Subtype actionSubtype;
1702     String subtype;
1703     actionItem.getString("type"_s, subtype);
1704     if (subtype == "pause")
1705         actionSubtype = Action::Subtype::Pause;
1706     else if (subtype == "pointerUp")
1707         actionSubtype = Action::Subtype::PointerUp;
1708     else if (subtype == "pointerDown")
1709         actionSubtype = Action::Subtype::PointerDown;
1710     else if (subtype == "pointerMove")
1711         actionSubtype = Action::Subtype::PointerMove;
1712     else if (subtype == "pointerCancel")
1713         actionSubtype = Action::Subtype::PointerCancel;
1714     else {
1715         errorMessage = String("The parameter 'type' of pointer action is invalid");
1716         return WTF::nullopt;
1717     }
1718
1719     Action action(id, Action::Type::Pointer, actionSubtype);
1720     action.pointerType = parameters.pointerType;
1721
1722     switch (actionSubtype) {
1723     case Action::Subtype::Pause:
1724         if (!processPauseAction(actionItem, action, errorMessage))
1725             return WTF::nullopt;
1726         break;
1727     case Action::Subtype::PointerUp:
1728     case Action::Subtype::PointerDown: {
1729         RefPtr<JSON::Value> buttonValue;
1730         if (!actionItem.getValue("button"_s, buttonValue)) {
1731             errorMessage = String("The paramater 'button' is missing for pointer up/down action");
1732             return WTF::nullopt;
1733         }
1734         auto button = unsignedValue(*buttonValue);
1735         if (!button) {
1736             errorMessage = String("The paramater 'button' is invalid for pointer up/down action");
1737             return WTF::nullopt;
1738         }
1739         action.button = actionMouseButton(button.value());
1740         break;
1741     }
1742     case Action::Subtype::PointerMove: {
1743         RefPtr<JSON::Value> durationValue;
1744         if (actionItem.getValue("duration"_s, durationValue)) {
1745             auto duration = unsignedValue(*durationValue);
1746             if (!duration) {
1747                 errorMessage = String("The parameter 'duration' is invalid in pointer move action");
1748                 return WTF::nullopt;
1749             }
1750             action.duration = duration.value();
1751         }
1752
1753         RefPtr<JSON::Value> originValue;
1754         if (actionItem.getValue("origin"_s, originValue)) {
1755             if (originValue->type() == JSON::Value::Type::Object) {
1756                 RefPtr<JSON::Object> originObject;
1757                 originValue->asObject(originObject);
1758                 String elementID;
1759                 if (!originObject->getString(Session::webElementIdentifier(), elementID)) {
1760                     errorMessage = String("The parameter 'origin' is not a valid web element object in pointer move action");
1761                     return WTF::nullopt;
1762                 }
1763                 action.origin = PointerOrigin { PointerOrigin::Type::Element, elementID };
1764             } else {
1765                 String origin;
1766                 originValue->asString(origin);
1767                 if (origin == "viewport")
1768                     action.origin = PointerOrigin { PointerOrigin::Type::Viewport, WTF::nullopt };
1769                 else if (origin == "pointer")
1770                     action.origin = PointerOrigin { PointerOrigin::Type::Pointer, WTF::nullopt };
1771                 else {
1772                     errorMessage = String("The parameter 'origin' is invalid in pointer move action");
1773                     return WTF::nullopt;
1774                 }
1775             }
1776         } else
1777             action.origin = PointerOrigin { PointerOrigin::Type::Viewport, WTF::nullopt };
1778
1779         RefPtr<JSON::Value> xValue;
1780         if (actionItem.getValue("x"_s, xValue)) {
1781             auto x = valueAsNumberInRange(*xValue, INT_MIN);
1782             if (!x) {
1783                 errorMessage = String("The paramater 'x' is invalid for pointer move action");
1784                 return WTF::nullopt;
1785             }
1786             action.x = x.value();
1787         }
1788
1789         RefPtr<JSON::Value> yValue;
1790         if (actionItem.getValue("y"_s, yValue)) {
1791             auto y = valueAsNumberInRange(*yValue, INT_MIN);
1792             if (!y) {
1793                 errorMessage = String("The paramater 'y' is invalid for pointer move action");
1794                 return WTF::nullopt;
1795             }
1796             action.y = y.value();
1797         }
1798         break;
1799     }
1800     case Action::Subtype::PointerCancel:
1801         break;
1802     case Action::Subtype::KeyUp:
1803     case Action::Subtype::KeyDown:
1804         ASSERT_NOT_REACHED();
1805     }
1806
1807     return action;
1808 }
1809
1810 static Optional<PointerParameters> processPointerParameters(JSON::Object& actionSequence, Optional<String>& errorMessage)
1811 {
1812     PointerParameters parameters;
1813     RefPtr<JSON::Value> parametersDataValue;
1814     if (!actionSequence.getValue("parameters"_s, parametersDataValue))
1815         return parameters;
1816
1817     RefPtr<JSON::Object> parametersData;
1818     if (!parametersDataValue->asObject(parametersData)) {
1819         errorMessage = String("Action sequence pointer parameters is not an object");
1820         return WTF::nullopt;
1821     }
1822
1823     String pointerType;
1824     if (!parametersData->getString("pointerType"_s, pointerType))
1825         return parameters;
1826
1827     if (pointerType == "mouse")
1828         parameters.pointerType = PointerType::Mouse;
1829     else if (pointerType == "pen")
1830         parameters.pointerType = PointerType::Pen;
1831     else if (pointerType == "touch")
1832         parameters.pointerType = PointerType::Touch;
1833     else {
1834         errorMessage = String("The parameter 'pointerType' in action sequence pointer parameters is invalid");
1835         return WTF::nullopt;
1836     }
1837
1838     return parameters;
1839 }
1840
1841 static Optional<Vector<Action>> processInputActionSequence(Session& session, JSON::Value& actionSequenceValue, Optional<String>& errorMessage)
1842 {
1843     RefPtr<JSON::Object> actionSequence;
1844     if (!actionSequenceValue.asObject(actionSequence)) {
1845         errorMessage = String("The action sequence is not an object");
1846         return WTF::nullopt;
1847     }
1848
1849     String type;
1850     actionSequence->getString("type"_s, type);
1851     InputSource::Type inputSourceType;
1852     if (type == "key")
1853         inputSourceType = InputSource::Type::Key;
1854     else if (type == "pointer")
1855         inputSourceType = InputSource::Type::Pointer;
1856     else if (type == "none")
1857         inputSourceType = InputSource::Type::None;
1858     else {
1859         errorMessage = String("The parameter 'type' is invalid or missing in action sequence");
1860         return WTF::nullopt;
1861     }
1862
1863     String id;
1864     if (!actionSequence->getString("id"_s, id)) {
1865         errorMessage = String("The parameter 'id' is invalid or missing in action sequence");
1866         return WTF::nullopt;
1867     }
1868
1869     Optional<PointerParameters> parameters;
1870     Optional<PointerType> pointerType;
1871     if (inputSourceType == InputSource::Type::Pointer) {
1872         parameters = processPointerParameters(*actionSequence, errorMessage);
1873         if (!parameters)
1874             return WTF::nullopt;
1875
1876         pointerType = parameters->pointerType;
1877     }
1878
1879     auto& inputSource = session.getOrCreateInputSource(id, inputSourceType, pointerType);
1880     if (inputSource.type != inputSourceType) {
1881         errorMessage = String("Action sequence type doesn't match input source type");
1882         return WTF::nullopt;
1883     }
1884
1885     if (inputSource.type ==  InputSource::Type::Pointer && inputSource.pointerType != pointerType) {
1886         errorMessage = String("Action sequence pointer type doesn't match input source pointer type");
1887         return WTF::nullopt;
1888     }
1889
1890     RefPtr<JSON::Array> actionItems;
1891     if (!actionSequence->getArray("actions"_s, actionItems)) {
1892         errorMessage = String("The parameter 'actions' is invalid or not present in action sequence");
1893         return WTF::nullopt;
1894     }
1895
1896     Vector<Action> actions;
1897     unsigned actionItemsLength = actionItems->length();
1898     for (unsigned i = 0; i < actionItemsLength; ++i) {
1899         auto actionItemValue = actionItems->get(i);
1900         RefPtr<JSON::Object> actionItem;
1901         if (!actionItemValue->asObject(actionItem)) {
1902             errorMessage = String("An action in action sequence is not an object");
1903             return WTF::nullopt;
1904         }
1905
1906         Optional<Action> action;
1907         if (inputSourceType == InputSource::Type::None)
1908             action = processNullAction(id, *actionItem, errorMessage);
1909         else if (inputSourceType == InputSource::Type::Key)
1910             action = processKeyAction(id, *actionItem, errorMessage);
1911         else if (inputSourceType == InputSource::Type::Pointer)
1912             action = processPointerAction(id, parameters.value(), *actionItem, errorMessage);
1913         if (!action)
1914             return WTF::nullopt;
1915
1916         actions.append(action.value());
1917     }
1918
1919     return actions;
1920 }
1921
1922 void WebDriverService::performActions(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1923 {
1924     // §17.5 Perform Actions.
1925     // https://w3c.github.io/webdriver/webdriver-spec.html#perform-actions
1926     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1927         return;
1928
1929     RefPtr<JSON::Array> actionsArray;
1930     if (!parameters->getArray("actions"_s, actionsArray)) {
1931         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, String("The paramater 'actions' is invalid or not present")));
1932         return;
1933     }
1934
1935     Optional<String> errorMessage;
1936     Vector<Vector<Action>> actionsByTick;
1937     unsigned actionsArrayLength = actionsArray->length();
1938     for (unsigned i = 0; i < actionsArrayLength; ++i) {
1939         auto actionSequence = actionsArray->get(i);
1940         auto inputSourceActions = processInputActionSequence(*m_session, *actionSequence, errorMessage);
1941         if (!inputSourceActions) {
1942             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument, errorMessage.value()));
1943             return;
1944         }
1945         for (unsigned i = 0; i < inputSourceActions->size(); ++i) {
1946             if (actionsByTick.size() < i + 1)
1947                 actionsByTick.append({ });
1948             actionsByTick[i].append(inputSourceActions.value()[i]);
1949         }
1950     }
1951
1952     m_session->performActions(WTFMove(actionsByTick), WTFMove(completionHandler));
1953 }
1954
1955 void WebDriverService::releaseActions(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1956 {
1957     // §17.5 Release Actions.
1958     // https://w3c.github.io/webdriver/webdriver-spec.html#release-actions
1959     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1960         return;
1961
1962     m_session->releaseActions(WTFMove(completionHandler));
1963 }
1964
1965 void WebDriverService::dismissAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1966 {
1967     // §18.1 Dismiss Alert.
1968     // https://w3c.github.io/webdriver/webdriver-spec.html#dismiss-alert
1969     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1970         return;
1971
1972     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1973         if (result.isError()) {
1974             completionHandler(WTFMove(result));
1975             return;
1976         }
1977         m_session->dismissAlert(WTFMove(completionHandler));
1978     });
1979 }
1980
1981 void WebDriverService::acceptAlert(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1982 {
1983     // §18.2 Accept Alert.
1984     // https://w3c.github.io/webdriver/webdriver-spec.html#accept-alert
1985     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
1986         return;
1987
1988     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1989         if (result.isError()) {
1990             completionHandler(WTFMove(result));
1991             return;
1992         }
1993         m_session->acceptAlert(WTFMove(completionHandler));
1994     });
1995 }
1996
1997 void WebDriverService::getAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
1998 {
1999     // §18.3 Get Alert Text.
2000     // https://w3c.github.io/webdriver/webdriver-spec.html#get-alert-text
2001     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2002         return;
2003
2004     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2005         if (result.isError()) {
2006             completionHandler(WTFMove(result));
2007             return;
2008         }
2009         m_session->getAlertText(WTFMove(completionHandler));
2010     });
2011 }
2012
2013 void WebDriverService::sendAlertText(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
2014 {
2015     // §18.4 Send Alert Text.
2016     // https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text
2017     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2018         return;
2019
2020     String text;
2021     if (!parameters->getString("text"_s, text)) {
2022         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
2023         return;
2024     }
2025
2026     m_session->waitForNavigationToComplete([this, text = WTFMove(text), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2027         if (result.isError()) {
2028             completionHandler(WTFMove(result));
2029             return;
2030         }
2031         m_session->sendAlertText(text, WTFMove(completionHandler));
2032     });
2033 }
2034
2035 void WebDriverService::takeScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
2036 {
2037     // §19.1 Take Screenshot.
2038     // https://w3c.github.io/webdriver/webdriver-spec.html#take-screenshot
2039     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2040         return;
2041
2042     m_session->waitForNavigationToComplete([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2043         if (result.isError()) {
2044             completionHandler(WTFMove(result));
2045             return;
2046         }
2047         m_session->takeScreenshot(WTF::nullopt, WTF::nullopt, WTFMove(completionHandler));
2048     });
2049 }
2050
2051 void WebDriverService::takeElementScreenshot(RefPtr<JSON::Object>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
2052 {
2053     // §19.2 Take Element Screenshot.
2054     // https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot
2055     if (!findSessionOrCompleteWithError(*parameters, completionHandler))
2056         return;
2057
2058     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
2059     if (!elementID)
2060         return;
2061
2062     bool scrollIntoView = true;
2063     parameters->getBoolean("scroll"_s, scrollIntoView);
2064
2065     m_session->waitForNavigationToComplete([this, elementID, scrollIntoView, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2066         if (result.isError()) {
2067             completionHandler(WTFMove(result));
2068             return;
2069         }
2070         m_session->takeScreenshot(elementID.value(), scrollIntoView, WTFMove(completionHandler));
2071     });
2072 }
2073
2074 } // namespace WebDriver