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