WebDriver: wait until navigation is complete before running new commands and after...
[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 (unsigned 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     return EXIT_SUCCESS;
99 }
100
101 void WebDriverService::quit()
102 {
103     m_server.disconnect();
104     RunLoop::main().stop();
105 }
106
107 const WebDriverService::Command WebDriverService::s_commands[] = {
108     { HTTPMethod::Post, "/session", &WebDriverService::newSession },
109     { HTTPMethod::Delete, "/session/$sessionId", &WebDriverService::deleteSession },
110     { HTTPMethod::Post, "/session/$sessionId/timeouts", &WebDriverService::setTimeouts },
111
112     { HTTPMethod::Post, "/session/$sessionId/url", &WebDriverService::go },
113     { HTTPMethod::Get, "/session/$sessionId/url", &WebDriverService::getCurrentURL },
114     { HTTPMethod::Post, "/session/$sessionId/back", &WebDriverService::back },
115     { HTTPMethod::Post, "/session/$sessionId/forward", &WebDriverService::forward },
116     { HTTPMethod::Post, "/session/$sessionId/refresh", &WebDriverService::refresh },
117     { HTTPMethod::Get, "/session/$sessionId/title", &WebDriverService::getTitle },
118
119     { HTTPMethod::Get, "/session/$sessionId/window", &WebDriverService::getWindowHandle },
120     { HTTPMethod::Delete, "/session/$sessionId/window", &WebDriverService::closeWindow },
121     { HTTPMethod::Post, "/session/$sessionId/window", &WebDriverService::switchToWindow },
122     { HTTPMethod::Get, "/session/$sessionId/window/handles", &WebDriverService::getWindowHandles },
123     { HTTPMethod::Post, "/session/$sessionId/frame", &WebDriverService::switchToFrame },
124     { HTTPMethod::Post, "/session/$sessionId/frame/parent", &WebDriverService::switchToParentFrame },
125
126     // FIXME: Not in the spec, but still used by Selenium.
127     { HTTPMethod::Get, "/session/$sessionId/window/position", &WebDriverService::getWindowPosition },
128     { HTTPMethod::Post, "/session/$sessionId/window/position", &WebDriverService::setWindowPosition },
129     { HTTPMethod::Get, "/session/$sessionId/window/size", &WebDriverService::getWindowSize },
130     { HTTPMethod::Post, "/session/$sessionId/window/size", &WebDriverService::setWindowSize },
131
132     { HTTPMethod::Post, "/session/$sessionId/element", &WebDriverService::findElement },
133     { HTTPMethod::Post, "/session/$sessionId/elements", &WebDriverService::findElements },
134     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/element", &WebDriverService::findElementFromElement },
135     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/elements", &WebDriverService::findElementsFromElement },
136
137     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/selected", &WebDriverService::isElementSelected },
138     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/attribute/$name", &WebDriverService::getElementAttribute },
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     // FIXME: Not in the spec, but still used by Selenium.
149     { HTTPMethod::Post, "/session/$sessionId/element/$elementId/submit", &WebDriverService::elementSubmit },
150
151     { HTTPMethod::Post, "/session/$sessionId/execute/sync", &WebDriverService::executeScript },
152     { HTTPMethod::Post, "/session/$sessionId/execute/async", &WebDriverService::executeAsyncScript },
153
154     { HTTPMethod::Get, "/session/$sessionId/element/$elementId/displayed", &WebDriverService::isElementDisplayed },
155 };
156
157 std::optional<WebDriverService::HTTPMethod> WebDriverService::toCommandHTTPMethod(const String& method)
158 {
159     auto lowerCaseMethod = method.convertToASCIILowercase();
160     if (lowerCaseMethod == "get")
161         return WebDriverService::HTTPMethod::Get;
162     if (lowerCaseMethod == "post" || lowerCaseMethod == "put")
163         return WebDriverService::HTTPMethod::Post;
164     if (lowerCaseMethod == "delete")
165         return WebDriverService::HTTPMethod::Delete;
166
167     return std::nullopt;
168 }
169
170 bool WebDriverService::findCommand(const String& method, const String& path, CommandHandler* handler, HashMap<String, String>& parameters)
171 {
172     auto commandMethod = toCommandHTTPMethod(method);
173     if (!commandMethod)
174         return false;
175
176     size_t length = WTF_ARRAY_LENGTH(s_commands);
177     for (size_t i = 0; i < length; ++i) {
178         if (s_commands[i].method != *commandMethod)
179             continue;
180
181         Vector<String> pathTokens;
182         path.split("/", pathTokens);
183         Vector<String> commandTokens;
184         String::fromUTF8(s_commands[i].uriTemplate).split("/", commandTokens);
185         if (pathTokens.size() != commandTokens.size())
186             continue;
187
188         bool allMatched = true;
189         for (size_t j = 0; j < pathTokens.size() && allMatched; ++j) {
190             if (commandTokens[j][0] == '$')
191                 parameters.set(commandTokens[j].substring(1), pathTokens[j]);
192             else if (commandTokens[j] != pathTokens[j])
193                 allMatched = false;
194         }
195
196         if (allMatched) {
197             *handler = s_commands[i].handler;
198             return true;
199         }
200
201         parameters.clear();
202     }
203
204     return false;
205 }
206
207 void WebDriverService::handleRequest(HTTPRequestHandler::Request&& request, Function<void (HTTPRequestHandler::Response&&)>&& replyHandler)
208 {
209     CommandHandler handler;
210     HashMap<String, String> parameters;
211     if (!findCommand(request.method, request.path, &handler, parameters)) {
212         sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::UnknownCommand, String("Unknown command: " + request.path)));
213         return;
214     }
215
216     RefPtr<InspectorObject> parametersObject;
217     if (request.dataLength) {
218         RefPtr<InspectorValue> messageValue;
219         if (!InspectorValue::parseJSON(String::fromUTF8(request.data, request.dataLength), messageValue)) {
220             sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
221             return;
222         }
223
224         if (!messageValue->asObject(parametersObject)) {
225             sendResponse(WTFMove(replyHandler), CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
226             return;
227         }
228     } else
229         parametersObject = InspectorObject::create();
230     for (const auto& parameter : parameters)
231         parametersObject->setString(parameter.key, parameter.value);
232
233     ((*this).*handler)(WTFMove(parametersObject), [this, replyHandler = WTFMove(replyHandler)](CommandResult&& result) mutable {
234         sendResponse(WTFMove(replyHandler), WTFMove(result));
235     });
236 }
237
238 void WebDriverService::sendResponse(Function<void (HTTPRequestHandler::Response&&)>&& replyHandler, CommandResult&& result) const
239 {
240     RefPtr<InspectorObject> responseObject;
241     if (result.isError()) {
242         responseObject = InspectorObject::create();
243         responseObject->setString(ASCIILiteral("error"), result.errorString());
244         responseObject->setString(ASCIILiteral("message"), result.errorMessage().value_or(emptyString()));
245         responseObject->setString(ASCIILiteral("stacktrace"), emptyString());
246     } else {
247         responseObject = InspectorObject::create();
248         auto resultValue = result.result();
249         responseObject->setValue(ASCIILiteral("value"), resultValue ? WTFMove(resultValue) : InspectorValue::null());
250     }
251     replyHandler({ result.httpStatusCode(), responseObject->toJSONString().utf8(), ASCIILiteral("application/json; charset=utf-8") });
252 }
253
254 bool WebDriverService::parseCapabilities(InspectorObject& desiredCapabilities, Capabilities& capabilities, Function<void (CommandResult&&)>& completionHandler)
255 {
256     RefPtr<InspectorValue> value;
257     if (desiredCapabilities.getValue(ASCIILiteral("browserName"), value) && !value->asString(capabilities.browserName)) {
258         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("browserName parameter is invalid in capabilities")));
259         return false;
260     }
261     if (desiredCapabilities.getValue(ASCIILiteral("version"), value) && !value->asString(capabilities.browserVersion)) {
262         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("version parameter is invalid in capabilities")));
263         return false;
264     }
265     if (desiredCapabilities.getValue(ASCIILiteral("platform"), value) && !value->asString(capabilities.platform)) {
266         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("platform parameter is invalid in capabilities")));
267         return false;
268     }
269     // FIXME: parse all other well-known capabilities: acceptInsecureCerts, pageLoadStrategy, proxy, setWindowRect, timeouts, unhandledPromptBehavior.
270     return platformParseCapabilities(desiredCapabilities, capabilities, completionHandler);
271 }
272
273 RefPtr<Session> WebDriverService::findSessionOrCompleteWithError(InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler)
274 {
275     String sessionID;
276     if (!parameters.getString(ASCIILiteral("sessionId"), sessionID)) {
277         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
278         return nullptr;
279     }
280
281     auto session = m_sessions.get(sessionID);
282     if (!session) {
283         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
284         return nullptr;
285     }
286
287     return session;
288 }
289
290 void WebDriverService::newSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
291 {
292     // §8.1 New Session.
293     // https://www.w3.org/TR/webdriver/#new-session
294     RefPtr<InspectorObject> capabilitiesObject;
295     if (!parameters->getObject(ASCIILiteral("capabilities"), capabilitiesObject)) {
296         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated));
297         return;
298     }
299     RefPtr<InspectorValue> requiredCapabilitiesValue;
300     RefPtr<InspectorObject> requiredCapabilities;
301     if (!capabilitiesObject->getValue(ASCIILiteral("alwaysMatch"), requiredCapabilitiesValue))
302         requiredCapabilities = InspectorObject::create();
303     else if (!requiredCapabilitiesValue->asObject(requiredCapabilities)) {
304         completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("alwaysMatch is invalid in capabilities")));
305         return;
306     }
307     // FIXME: process firstMatch capabilities.
308
309     Capabilities capabilities;
310     if (!parseCapabilities(*requiredCapabilities, capabilities, completionHandler))
311         return;
312
313     auto sessionHost = std::make_unique<SessionHost>(WTFMove(capabilities));
314     auto* sessionHostPtr = sessionHost.get();
315     sessionHostPtr->connectToBrowser([this, sessionHost = WTFMove(sessionHost), completionHandler = WTFMove(completionHandler)](SessionHost::Succeeded succeeded) mutable {
316         if (succeeded == SessionHost::Succeeded::No) {
317             completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, String("Failed to connect to browser")));
318             return;
319         }
320
321         RefPtr<Session> session = Session::create(WTFMove(sessionHost));
322         session->createTopLevelBrowsingContext([this, session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
323             if (result.isError()) {
324                 completionHandler(CommandResult::fail(CommandResult::ErrorCode::SessionNotCreated, result.errorString()));
325                 return;
326             }
327
328             m_activeSession = session.get();
329             m_sessions.add(session->id(), session);
330             RefPtr<InspectorObject> resultObject = InspectorObject::create();
331             resultObject->setString(ASCIILiteral("sessionId"), session->id());
332             RefPtr<InspectorObject> capabilities = InspectorObject::create();
333             capabilities->setString(ASCIILiteral("browserName"), session->capabilities().browserName);
334             capabilities->setString(ASCIILiteral("version"), session->capabilities().browserVersion);
335             capabilities->setString(ASCIILiteral("platform"), session->capabilities().platform);
336             resultObject->setObject(ASCIILiteral("value"), WTFMove(capabilities));
337             completionHandler(CommandResult::success(WTFMove(resultObject)));
338         });
339     });
340 }
341
342 void WebDriverService::deleteSession(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
343 {
344     // §8.2 Delete Session.
345     // https://www.w3.org/TR/webdriver/#delete-session
346     String sessionID;
347     if (!parameters->getString(ASCIILiteral("sessionId"), sessionID)) {
348         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
349         return;
350     }
351
352     auto session = m_sessions.take(sessionID);
353     if (!session) {
354         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidSessionID));
355         return;
356     }
357
358     if (m_activeSession == session.get())
359         m_activeSession = nullptr;
360
361     session->close(WTFMove(completionHandler));
362 }
363
364 void WebDriverService::setTimeouts(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
365 {
366     // §8.5 Set Timeouts.
367     // https://www.w3.org/TR/webdriver/#set-timeouts
368     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
369     if (!session)
370         return;
371
372     Session::Timeouts timeouts;
373     auto end = parameters->end();
374     for (auto it = parameters->begin(); it != end; ++it) {
375         if (it->key == "sessionId")
376             continue;
377
378         int timeoutMS;
379         if (!it->value->asInteger(timeoutMS) || timeoutMS < 0 || timeoutMS > INT_MAX) {
380             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
381             return;
382         }
383
384         if (it->key == "script")
385             timeouts.script = Seconds::fromMilliseconds(timeoutMS);
386         else if (it->key == "page load")
387             timeouts.pageLoad = Seconds::fromMilliseconds(timeoutMS);
388         else if (it->key == "implicit")
389             timeouts.implicit = Seconds::fromMilliseconds(timeoutMS);
390         else {
391             completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
392             return;
393         }
394     }
395
396     session->setTimeouts(timeouts, WTFMove(completionHandler));
397 }
398
399 void WebDriverService::go(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
400 {
401     // §9.1 Go.
402     // https://www.w3.org/TR/webdriver/#go
403     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
404     if (!session)
405         return;
406
407     String url;
408     if (!parameters->getString(ASCIILiteral("url"), url)) {
409         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
410         return;
411     }
412
413     session->waitForNavigationToComplete([session, url = WTFMove(url), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
414         if (result.isError()) {
415             completionHandler(WTFMove(result));
416             return;
417         }
418         session->go(url, WTFMove(completionHandler));
419     });
420 }
421
422 void WebDriverService::getCurrentURL(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
423 {
424     // §9.2 Get Current URL.
425     // https://www.w3.org/TR/webdriver/#get-current-url
426     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
427     if (!session)
428         return;
429
430     session->waitForNavigationToComplete([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
431         if (result.isError()) {
432             completionHandler(WTFMove(result));
433             return;
434         }
435         session->getCurrentURL(WTFMove(completionHandler));
436     });
437 }
438
439 void WebDriverService::back(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
440 {
441     // §9.3 Back.
442     // https://www.w3.org/TR/webdriver/#back
443     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
444     if (!session)
445         return;
446
447     session->waitForNavigationToComplete([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
448         if (result.isError()) {
449             completionHandler(WTFMove(result));
450             return;
451         }
452         session->back(WTFMove(completionHandler));
453     });
454 }
455
456 void WebDriverService::forward(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
457 {
458     // §9.4 Forward.
459     // https://www.w3.org/TR/webdriver/#forward
460     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
461     if (!session)
462         return;
463
464     session->waitForNavigationToComplete([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
465         if (result.isError()) {
466             completionHandler(WTFMove(result));
467             return;
468         }
469         session->forward(WTFMove(completionHandler));
470     });
471 }
472
473 void WebDriverService::refresh(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
474 {
475     // §9.5 Refresh.
476     // https://www.w3.org/TR/webdriver/#refresh
477     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
478     if (!session)
479         return;
480
481     session->waitForNavigationToComplete([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
482         if (result.isError()) {
483             completionHandler(WTFMove(result));
484             return;
485         }
486         session->refresh(WTFMove(completionHandler));
487     });
488 }
489
490 void WebDriverService::getTitle(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
491 {
492     // §9.6 Get Title.
493     // https://www.w3.org/TR/webdriver/#get-title
494     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
495     if (!session)
496         return;
497
498     session->waitForNavigationToComplete([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
499         if (result.isError()) {
500             completionHandler(WTFMove(result));
501             return;
502         }
503         session->getTitle(WTFMove(completionHandler));
504     });
505 }
506
507 void WebDriverService::getWindowHandle(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
508 {
509     // §10.1 Get Window Handle.
510     // https://www.w3.org/TR/webdriver/#get-window-handle
511     if (auto session = findSessionOrCompleteWithError(*parameters, completionHandler))
512         session->getWindowHandle(WTFMove(completionHandler));
513 }
514
515 void WebDriverService::getWindowPosition(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
516 {
517     if (auto session = findSessionOrCompleteWithError(*parameters, completionHandler))
518         session->getWindowPosition(WTFMove(completionHandler));
519 }
520
521 void WebDriverService::setWindowPosition(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
522 {
523     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
524     if (!session)
525         return;
526
527     int windowX;
528     if (!parameters->getInteger(ASCIILiteral("x"), windowX)) {
529         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
530         return;
531     }
532
533     int windowY;
534     if (!parameters->getInteger(ASCIILiteral("y"), windowY)) {
535         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
536         return;
537     }
538
539     session->setWindowPosition(windowX, windowY, WTFMove(completionHandler));
540 }
541
542 void WebDriverService::getWindowSize(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
543 {
544     if (auto session = findSessionOrCompleteWithError(*parameters, completionHandler))
545         session->getWindowSize(WTFMove(completionHandler));
546 }
547
548 void WebDriverService::setWindowSize(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
549 {
550     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
551     if (!session)
552         return;
553
554     int windowWidth;
555     if (!parameters->getInteger(ASCIILiteral("width"), windowWidth)) {
556         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
557         return;
558     }
559
560     int windowHeight;
561     if (!parameters->getInteger(ASCIILiteral("height"), windowHeight)) {
562         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
563         return;
564     }
565
566     session->setWindowSize(windowWidth, windowHeight, WTFMove(completionHandler));
567 }
568
569 void WebDriverService::closeWindow(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
570 {
571     // §10.2 Close Window.
572     // https://www.w3.org/TR/webdriver/#close-window
573     if (auto session = findSessionOrCompleteWithError(*parameters, completionHandler))
574         session->closeWindow(WTFMove(completionHandler));
575 }
576
577 void WebDriverService::switchToWindow(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
578 {
579     // §10.3 Switch To Window.
580     // https://www.w3.org/TR/webdriver/#switch-to-window
581     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
582     if (!session)
583         return;
584
585     String handle;
586     if (!parameters->getString(ASCIILiteral("handle"), handle)) {
587         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
588         return;
589     }
590
591     session->switchToWindow(handle, WTFMove(completionHandler));
592 }
593
594 void WebDriverService::getWindowHandles(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
595 {
596     // §10.4 Get Window Handles.
597     // https://www.w3.org/TR/webdriver/#get-window-handles
598     if (auto session = findSessionOrCompleteWithError(*parameters, completionHandler))
599         session->getWindowHandles(WTFMove(completionHandler));
600 }
601
602 void WebDriverService::switchToFrame(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
603 {
604     // §10.5 Switch To Frame.
605     // https://www.w3.org/TR/webdriver/#switch-to-frame
606     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
607     if (!session)
608         return;
609
610     RefPtr<InspectorValue> frameID;
611     if (!parameters->getValue(ASCIILiteral("id"), frameID)) {
612         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
613         return;
614     }
615
616     session->waitForNavigationToComplete([session, frameID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
617         if (result.isError()) {
618             completionHandler(WTFMove(result));
619             return;
620         }
621         session->switchToFrame(WTFMove(frameID), WTFMove(completionHandler));
622     });
623 }
624
625 void WebDriverService::switchToParentFrame(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
626 {
627     // §10.6 Switch To Parent Frame.
628     // https://www.w3.org/TR/webdriver/#switch-to-parent-frame
629     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
630     if (!session)
631         return;
632
633     session->waitForNavigationToComplete([session, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
634         if (result.isError()) {
635             completionHandler(WTFMove(result));
636             return;
637         }
638         session->switchToParentFrame(WTFMove(completionHandler));
639     });
640 }
641
642 static std::optional<String> findElementOrCompleteWithError(InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler)
643 {
644     String elementID;
645     if (!parameters.getString(ASCIILiteral("elementId"), elementID) || elementID.isEmpty()) {
646         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
647         return std::nullopt;
648     }
649     return elementID;
650 }
651
652 static bool findStrategyAndSelectorOrCompleteWithError(InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler, String& strategy, String& selector)
653 {
654     if (!parameters.getString(ASCIILiteral("using"), strategy)) {
655         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
656         return false;
657     }
658     if (!parameters.getString(ASCIILiteral("value"), selector)) {
659         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
660         return false;
661     }
662     return true;
663 }
664
665 void WebDriverService::findElement(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
666 {
667     // §12.2 Find Element.
668     // https://www.w3.org/TR/webdriver/#find-element
669     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
670     if (!session)
671         return;
672
673     String strategy, selector;
674     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
675         return;
676
677     session->waitForNavigationToComplete([session, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
678         if (result.isError()) {
679             completionHandler(WTFMove(result));
680             return;
681         }
682         session->findElements(strategy, selector, Session::FindElementsMode::Single, emptyString(), WTFMove(completionHandler));
683     });
684 }
685
686 void WebDriverService::findElements(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
687 {
688     // §12.3 Find Elements.
689     // https://www.w3.org/TR/webdriver/#find-elements
690     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
691     if (!session)
692         return;
693
694     String strategy, selector;
695     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
696         return;
697
698     session->waitForNavigationToComplete([session, strategy = WTFMove(strategy), selector = WTFMove(selector), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
699         if (result.isError()) {
700             completionHandler(WTFMove(result));
701             return;
702         }
703         session->findElements(strategy, selector, Session::FindElementsMode::Multiple, emptyString(), WTFMove(completionHandler));
704     });
705 }
706
707 void WebDriverService::findElementFromElement(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
708 {
709     // §12.4 Find Element From Element.
710     // https://www.w3.org/TR/webdriver/#find-element-from-element
711     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
712     if (!session)
713         return;
714
715     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
716     if (!elementID)
717         return;
718
719     String strategy, selector;
720     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
721         return;
722
723     session->findElements(strategy, selector, Session::FindElementsMode::Single, elementID.value(), WTFMove(completionHandler));
724 }
725
726 void WebDriverService::findElementsFromElement(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
727 {
728     // §12.5 Find Elements From Element.
729     // https://www.w3.org/TR/webdriver/#find-elements-from-element
730     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
731     if (!session)
732         return;
733
734     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
735     if (!elementID)
736         return;
737
738     String strategy, selector;
739     if (!findStrategyAndSelectorOrCompleteWithError(*parameters, completionHandler, strategy, selector))
740         return;
741
742     session->findElements(strategy, selector, Session::FindElementsMode::Multiple, elementID.value(), WTFMove(completionHandler));
743 }
744
745 void WebDriverService::isElementSelected(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
746 {
747     // §13.1 Is Element Selected.
748     // https://www.w3.org/TR/webdriver/#is-element-selected
749     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
750     if (!session)
751         return;
752
753     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
754     if (!elementID)
755         return;
756
757     session->isElementSelected(elementID.value(), WTFMove(completionHandler));
758 }
759
760 void WebDriverService::getElementAttribute(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
761 {
762     // §13.2 Get Element Attribute.
763     // https://www.w3.org/TR/webdriver/#get-element-attribute
764     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
765     if (!session)
766         return;
767
768     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
769     if (!elementID)
770         return;
771
772     String attribute;
773     if (!parameters->getString(ASCIILiteral("name"), attribute)) {
774         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
775         return;
776     }
777
778     session->getElementAttribute(elementID.value(), attribute, WTFMove(completionHandler));
779 }
780
781 void WebDriverService::getElementText(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
782 {
783     // §13.5 Get Element Text.
784     // https://www.w3.org/TR/webdriver/#get-element-text
785     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
786     if (!session)
787         return;
788
789     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
790     if (!elementID)
791         return;
792
793     session->getElementText(elementID.value(), WTFMove(completionHandler));
794 }
795
796 void WebDriverService::getElementTagName(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
797 {
798     // §13.6 Get Element Tag Name.
799     // https://www.w3.org/TR/webdriver/#get-element-tag-name
800     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
801     if (!session)
802         return;
803
804     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
805     if (!elementID)
806         return;
807
808     session->getElementTagName(elementID.value(), WTFMove(completionHandler));
809 }
810
811 void WebDriverService::getElementRect(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
812 {
813     // §13.7 Get Element Rect.
814     // https://www.w3.org/TR/webdriver/#get-element-rect
815     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
816     if (!session)
817         return;
818
819     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
820     if (!elementID)
821         return;
822
823     session->getElementRect(elementID.value(), WTFMove(completionHandler));
824 }
825
826 void WebDriverService::isElementEnabled(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
827 {
828     // §13.8 Is Element Enabled.
829     // https://www.w3.org/TR/webdriver/#is-element-enabled
830     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
831     if (!session)
832         return;
833
834     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
835     if (!elementID)
836         return;
837
838     session->isElementEnabled(elementID.value(), WTFMove(completionHandler));
839 }
840
841 void WebDriverService::isElementDisplayed(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
842 {
843     // §C. Element Displayedness.
844     // https://www.w3.org/TR/webdriver/#element-displayedness
845     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
846     if (!session)
847         return;
848
849     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
850     if (!elementID)
851         return;
852
853     session->isElementDisplayed(elementID.value(), WTFMove(completionHandler));
854 }
855
856 void WebDriverService::elementClick(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
857 {
858     // §14.1 Element Click.
859     // https://www.w3.org/TR/webdriver/#element-click
860     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
861     if (!session)
862         return;
863
864     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
865     if (!elementID)
866         return;
867
868     session->elementClick(elementID.value(), WTFMove(completionHandler));
869 }
870
871 void WebDriverService::elementClear(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
872 {
873     // §14.2 Element Clear.
874     // https://www.w3.org/TR/webdriver/#element-clear
875     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
876     if (!session)
877         return;
878
879     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
880     if (!elementID)
881         return;
882
883     session->elementClear(elementID.value(), WTFMove(completionHandler));
884 }
885
886 void WebDriverService::elementSendKeys(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
887 {
888     // §14.3 Element Send Keys.
889     // https://www.w3.org/TR/webdriver/#element-send-keys
890     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
891     if (!session)
892         return;
893
894     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
895     if (!elementID)
896         return;
897
898     RefPtr<InspectorArray> valueArray;
899     if (!parameters->getArray(ASCIILiteral("value"), valueArray)) {
900         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
901         return;
902     }
903
904     unsigned valueArrayLength = valueArray->length();
905     if (!valueArrayLength) {
906         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
907         return;
908     }
909     Vector<String> value;
910     value.reserveInitialCapacity(valueArrayLength);
911     for (unsigned i = 0; i < valueArrayLength; ++i) {
912         if (auto keyValue = valueArray->get(i)) {
913             String key;
914             if (keyValue->asString(key))
915                 value.uncheckedAppend(WTFMove(key));
916         }
917     }
918     if (!value.size()) {
919         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
920         return;
921     }
922
923     session->elementSendKeys(elementID.value(), WTFMove(value), WTFMove(completionHandler));
924 }
925
926 void WebDriverService::elementSubmit(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
927 {
928     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
929     if (!session)
930         return;
931
932     auto elementID = findElementOrCompleteWithError(*parameters, completionHandler);
933     if (!elementID)
934         return;
935
936     session->elementSubmit(elementID.value(), WTFMove(completionHandler));
937 }
938
939 static bool findScriptAndArgumentsOrCompleteWithError(InspectorObject& parameters, Function<void (CommandResult&&)>& completionHandler, String& script, RefPtr<InspectorArray>& arguments)
940 {
941     if (!parameters.getString(ASCIILiteral("script"), script)) {
942         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
943         return false;
944     }
945     if (!parameters.getArray(ASCIILiteral("args"), arguments)) {
946         completionHandler(CommandResult::fail(CommandResult::ErrorCode::InvalidArgument));
947         return false;
948     }
949     return true;
950 }
951
952 void WebDriverService::executeScript(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
953 {
954     // §15.2.1 Execute Script.
955     // https://www.w3.org/TR/webdriver/#execute-script
956     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
957     if (!session)
958         return;
959
960     String script;
961     RefPtr<InspectorArray> arguments;
962     if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
963         return;
964
965     session->waitForNavigationToComplete([session, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
966         if (result.isError()) {
967             completionHandler(WTFMove(result));
968             return;
969         }
970         session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Sync, WTFMove(completionHandler));
971     });
972 }
973
974 void WebDriverService::executeAsyncScript(RefPtr<InspectorObject>&& parameters, Function<void (CommandResult&&)>&& completionHandler)
975 {
976     // §15.2.2 Execute Async Script.
977     // https://www.w3.org/TR/webdriver/#execute-async-script
978     auto session = findSessionOrCompleteWithError(*parameters, completionHandler);
979     if (!session)
980         return;
981
982     String script;
983     RefPtr<InspectorArray> arguments;
984     if (!findScriptAndArgumentsOrCompleteWithError(*parameters, completionHandler, script, arguments))
985         return;
986
987     session->waitForNavigationToComplete([session, script = WTFMove(script), arguments = WTFMove(arguments), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
988         if (result.isError()) {
989             completionHandler(WTFMove(result));
990             return;
991         }
992         session->executeScript(script, WTFMove(arguments), Session::ExecuteScriptMode::Async, WTFMove(completionHandler));
993     });
994 }
995
996 } // namespace WebDriver