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