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 "Session.h"
28
29#include "CommandResult.h"
30#include "SessionHost.h"
31#include "WebDriverAtoms.h"
32#include <wtf/CryptographicallyRandomNumber.h>
33#include <wtf/HashSet.h>
34#include <wtf/HexNumber.h>
35#include <wtf/NeverDestroyed.h>
36
37namespace WebDriver {
38
39// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-session-script-timeout
40static const Seconds defaultScriptTimeout = 30_s;
41// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-session-page-load-timeout
42static const Seconds defaultPageLoadTimeout = 300_s;
43// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-session-implicit-wait-timeout
44static const Seconds defaultImplicitWaitTimeout = 0_s;
45
46const String& Session::webElementIdentifier()
47{
48 // The web element identifier is a constant defined by the spec in Section 11 Elements.
49 // https://www.w3.org/TR/webdriver/#elements
50 static NeverDestroyed<String> webElementID { "element-6066-11e4-a52e-4f735466cecf"_s };
51 return webElementID;
52}
53
54Session::Session(std::unique_ptr<SessionHost>&& host)
55 : m_host(WTFMove(host))
56 , m_scriptTimeout(defaultScriptTimeout)
57 , m_pageLoadTimeout(defaultPageLoadTimeout)
58 , m_implicitWaitTimeout(defaultImplicitWaitTimeout)
59{
60 if (capabilities().timeouts)
61 setTimeouts(capabilities().timeouts.value(), [](CommandResult&&) { });
62}
63
64Session::~Session()
65{
66}
67
68const String& Session::id() const
69{
70 return m_host->sessionID();
71}
72
73const Capabilities& Session::capabilities() const
74{
75 return m_host->capabilities();
76}
77
78bool Session::isConnected() const
79{
80 return m_host->isConnected();
81}
82
83static Optional<String> firstWindowHandleInResult(JSON::Value& result)
84{
85 RefPtr<JSON::Array> handles;
86 if (result.asArray(handles) && handles->length()) {
87 auto handleValue = handles->get(0);
88 String handle;
89 if (handleValue->asString(handle))
90 return handle;
91 }
92 return WTF::nullopt;
93}
94
95void Session::closeAllToplevelBrowsingContexts(const String& toplevelBrowsingContext, Function<void (CommandResult&&)>&& completionHandler)
96{
97 closeTopLevelBrowsingContext(toplevelBrowsingContext, [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
98 if (result.isError()) {
99 completionHandler(WTFMove(result));
100 return;
101 }
102 if (auto handle = firstWindowHandleInResult(*result.result())) {
103 closeAllToplevelBrowsingContexts(handle.value(), WTFMove(completionHandler));
104 return;
105 }
106 completionHandler(CommandResult::success());
107 });
108}
109
110void Session::close(Function<void (CommandResult&&)>&& completionHandler)
111{
112 m_toplevelBrowsingContext = WTF::nullopt;
113 getWindowHandles([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
114 if (result.isError()) {
115 completionHandler(WTFMove(result));
116 return;
117 }
118 if (auto handle = firstWindowHandleInResult(*result.result())) {
119 closeAllToplevelBrowsingContexts(handle.value(), WTFMove(completionHandler));
120 return;
121 }
122 completionHandler(CommandResult::success());
123 });
124}
125
126void Session::getTimeouts(Function<void (CommandResult&&)>&& completionHandler)
127{
128 RefPtr<JSON::Object> parameters = JSON::Object::create();
129 parameters->setInteger("script"_s, m_scriptTimeout.millisecondsAs<int>());
130 parameters->setInteger("pageLoad"_s, m_pageLoadTimeout.millisecondsAs<int>());
131 parameters->setInteger("implicit"_s, m_implicitWaitTimeout.millisecondsAs<int>());
132 completionHandler(CommandResult::success(WTFMove(parameters)));
133}
134
135void Session::setTimeouts(const Timeouts& timeouts, Function<void (CommandResult&&)>&& completionHandler)
136{
137 if (timeouts.script)
138 m_scriptTimeout = timeouts.script.value();
139 if (timeouts.pageLoad)
140 m_pageLoadTimeout = timeouts.pageLoad.value();
141 if (timeouts.implicit)
142 m_implicitWaitTimeout = timeouts.implicit.value();
143 completionHandler(CommandResult::success());
144}
145
146void Session::switchToTopLevelBrowsingContext(Optional<String> toplevelBrowsingContext)
147{
148 m_toplevelBrowsingContext = toplevelBrowsingContext;
149 m_currentBrowsingContext = WTF::nullopt;
150}
151
152void Session::switchToBrowsingContext(Optional<String> browsingContext)
153{
154 // Automation sends empty strings for main frame.
155 if (!browsingContext || browsingContext.value().isEmpty())
156 m_currentBrowsingContext = WTF::nullopt;
157 else
158 m_currentBrowsingContext = browsingContext;
159}
160
161Optional<String> Session::pageLoadStrategyString() const
162{
163 if (!capabilities().pageLoadStrategy)
164 return WTF::nullopt;
165
166 switch (capabilities().pageLoadStrategy.value()) {
167 case PageLoadStrategy::None:
168 return String("None");
169 case PageLoadStrategy::Normal:
170 return String("Normal");
171 case PageLoadStrategy::Eager:
172 return String("Eager");
173 }
174
175 return WTF::nullopt;
176}
177
178void Session::createTopLevelBrowsingContext(Function<void (CommandResult&&)>&& completionHandler)
179{
180 ASSERT(!m_toplevelBrowsingContext);
181 m_host->sendCommandToBackend("createBrowsingContext"_s, nullptr, [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
182 if (response.isError || !response.responseObject) {
183 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
184 return;
185 }
186 String handle;
187 if (!response.responseObject->getString("handle"_s, handle)) {
188 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
189 return;
190 }
191 switchToTopLevelBrowsingContext(handle);
192 completionHandler(CommandResult::success());
193 });
194}
195
196void Session::handleUserPrompts(Function<void (CommandResult&&)>&& completionHandler)
197{
198 RefPtr<JSON::Object> parameters = JSON::Object::create();
199 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
200 m_host->sendCommandToBackend("isShowingJavaScriptDialog"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
201 if (response.isError || !response.responseObject) {
202 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
203 return;
204 }
205 bool isShowingJavaScriptDialog;
206 if (!response.responseObject->getBoolean("result", isShowingJavaScriptDialog)) {
207 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
208 return;
209 }
210
211 if (!isShowingJavaScriptDialog) {
212 completionHandler(CommandResult::success());
213 return;
214 }
215
216 handleUnexpectedAlertOpen(WTFMove(completionHandler));
217 });
218}
219
220void Session::handleUnexpectedAlertOpen(Function<void (CommandResult&&)>&& completionHandler)
221{
222 switch (capabilities().unhandledPromptBehavior.valueOr(UnhandledPromptBehavior::DismissAndNotify)) {
223 case UnhandledPromptBehavior::Dismiss:
224 dismissAlert(WTFMove(completionHandler));
225 break;
226 case UnhandledPromptBehavior::Accept:
227 acceptAlert(WTFMove(completionHandler));
228 break;
229 case UnhandledPromptBehavior::DismissAndNotify:
230 dismissAndNotifyAlert(WTFMove(completionHandler));
231 break;
232 case UnhandledPromptBehavior::AcceptAndNotify:
233 acceptAndNotifyAlert(WTFMove(completionHandler));
234 break;
235 case UnhandledPromptBehavior::Ignore:
236 reportUnexpectedAlertOpen(WTFMove(completionHandler));
237 break;
238 }
239}
240
241void Session::dismissAndNotifyAlert(Function<void (CommandResult&&)>&& completionHandler)
242{
243 reportUnexpectedAlertOpen([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
244 dismissAlert([errorResult = WTFMove(result), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
245 if (result.isError()) {
246 completionHandler(WTFMove(result));
247 return;
248 }
249 completionHandler(WTFMove(errorResult));
250 });
251 });
252}
253
254void Session::acceptAndNotifyAlert(Function<void (CommandResult&&)>&& completionHandler)
255{
256 reportUnexpectedAlertOpen([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
257 acceptAlert([errorResult = WTFMove(result), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
258 if (result.isError()) {
259 completionHandler(WTFMove(result));
260 return;
261 }
262 completionHandler(WTFMove(errorResult));
263 });
264 });
265}
266
267void Session::reportUnexpectedAlertOpen(Function<void (CommandResult&&)>&& completionHandler)
268{
269 getAlertText([completionHandler = WTFMove(completionHandler)](CommandResult&& result) {
270 Optional<String> alertText;
271 if (!result.isError()) {
272 String valueString;
273 if (result.result()->asString(valueString))
274 alertText = valueString;
275 }
276 auto errorResult = CommandResult::fail(CommandResult::ErrorCode::UnexpectedAlertOpen);
277 if (alertText) {
278 RefPtr<JSON::Object> additonalData = JSON::Object::create();
279 additonalData->setString("text"_s, alertText.value());
280 errorResult.setAdditionalErrorData(WTFMove(additonalData));
281 }
282 completionHandler(WTFMove(errorResult));
283 });
284}
285
286void Session::go(const String& url, Function<void (CommandResult&&)>&& completionHandler)
287{
288 if (!m_toplevelBrowsingContext) {
289 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
290 return;
291 }
292
293 handleUserPrompts([this, protectedThis = makeRef(*this), url, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
294 if (result.isError()) {
295 completionHandler(WTFMove(result));
296 return;
297 }
298
299 RefPtr<JSON::Object> parameters = JSON::Object::create();
300 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
301 parameters->setString("url"_s, url);
302 parameters->setInteger("pageLoadTimeout"_s, m_pageLoadTimeout.millisecondsAs<int>());
303 if (auto pageLoadStrategy = pageLoadStrategyString())
304 parameters->setString("pageLoadStrategy"_s, pageLoadStrategy.value());
305 m_host->sendCommandToBackend("navigateBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
306 if (response.isError) {
307 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
308 return;
309 }
310 switchToBrowsingContext(WTF::nullopt);
311 completionHandler(CommandResult::success());
312 });
313 });
314}
315
316void Session::getCurrentURL(Function<void (CommandResult&&)>&& completionHandler)
317{
318 if (!m_toplevelBrowsingContext) {
319 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
320 return;
321 }
322
323 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
324 if (result.isError()) {
325 completionHandler(WTFMove(result));
326 return;
327 }
328
329 RefPtr<JSON::Object> parameters = JSON::Object::create();
330 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
331 m_host->sendCommandToBackend("getBrowsingContext"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
332 if (response.isError || !response.responseObject) {
333 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
334 return;
335 }
336 RefPtr<JSON::Object> browsingContext;
337 if (!response.responseObject->getObject("context", browsingContext)) {
338 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
339 return;
340 }
341 String url;
342 if (!browsingContext->getString("url", url)) {
343 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
344 return;
345 }
346 completionHandler(CommandResult::success(JSON::Value::create(url)));
347 });
348 });
349}
350
351void Session::back(Function<void (CommandResult&&)>&& completionHandler)
352{
353 if (!m_toplevelBrowsingContext) {
354 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
355 return;
356 }
357
358 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
359 if (result.isError()) {
360 completionHandler(WTFMove(result));
361 return;
362 }
363 RefPtr<JSON::Object> parameters = JSON::Object::create();
364 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
365 parameters->setInteger("pageLoadTimeout"_s, m_pageLoadTimeout.millisecondsAs<int>());
366 if (auto pageLoadStrategy = pageLoadStrategyString())
367 parameters->setString("pageLoadStrategy"_s, pageLoadStrategy.value());
368 m_host->sendCommandToBackend("goBackInBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
369 if (response.isError) {
370 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
371 return;
372 }
373 switchToBrowsingContext(WTF::nullopt);
374 completionHandler(CommandResult::success());
375 });
376 });
377}
378
379void Session::forward(Function<void (CommandResult&&)>&& completionHandler)
380{
381 if (!m_toplevelBrowsingContext) {
382 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
383 return;
384 }
385
386 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
387 if (result.isError()) {
388 completionHandler(WTFMove(result));
389 return;
390 }
391 RefPtr<JSON::Object> parameters = JSON::Object::create();
392 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
393 parameters->setInteger("pageLoadTimeout"_s, m_pageLoadTimeout.millisecondsAs<int>());
394 if (auto pageLoadStrategy = pageLoadStrategyString())
395 parameters->setString("pageLoadStrategy"_s, pageLoadStrategy.value());
396 m_host->sendCommandToBackend("goForwardInBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
397 if (response.isError) {
398 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
399 return;
400 }
401 switchToBrowsingContext(WTF::nullopt);
402 completionHandler(CommandResult::success());
403 });
404 });
405}
406
407void Session::refresh(Function<void (CommandResult&&)>&& completionHandler)
408{
409 if (!m_toplevelBrowsingContext) {
410 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
411 return;
412 }
413
414 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
415 if (result.isError()) {
416 completionHandler(WTFMove(result));
417 return;
418 }
419 RefPtr<JSON::Object> parameters = JSON::Object::create();
420 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
421 parameters->setInteger("pageLoadTimeout"_s, m_pageLoadTimeout.millisecondsAs<int>());
422 if (auto pageLoadStrategy = pageLoadStrategyString())
423 parameters->setString("pageLoadStrategy"_s, pageLoadStrategy.value());
424 m_host->sendCommandToBackend("reloadBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
425 if (response.isError) {
426 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
427 return;
428 }
429 switchToBrowsingContext(WTF::nullopt);
430 completionHandler(CommandResult::success());
431 });
432 });
433}
434
435void Session::getTitle(Function<void (CommandResult&&)>&& completionHandler)
436{
437 if (!m_toplevelBrowsingContext) {
438 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
439 return;
440 }
441
442 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
443 if (result.isError()) {
444 completionHandler(WTFMove(result));
445 return;
446 }
447 RefPtr<JSON::Object> parameters = JSON::Object::create();
448 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
449 parameters->setString("function"_s, "function() { return document.title; }"_s);
450 parameters->setArray("arguments"_s, JSON::Array::create());
451 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
452 if (response.isError || !response.responseObject) {
453 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
454 return;
455 }
456 String valueString;
457 if (!response.responseObject->getString("result"_s, valueString)) {
458 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
459 return;
460 }
461 RefPtr<JSON::Value> resultValue;
462 if (!JSON::Value::parseJSON(valueString, resultValue)) {
463 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
464 return;
465 }
466 completionHandler(CommandResult::success(WTFMove(resultValue)));
467 });
468 });
469}
470
471void Session::getWindowHandle(Function<void (CommandResult&&)>&& completionHandler)
472{
473 if (!m_toplevelBrowsingContext) {
474 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
475 return;
476 }
477
478 RefPtr<JSON::Object> parameters = JSON::Object::create();
479 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
480 m_host->sendCommandToBackend("getBrowsingContext"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
481 if (response.isError || !response.responseObject) {
482 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
483 return;
484 }
485 RefPtr<JSON::Object> browsingContext;
486 if (!response.responseObject->getObject("context"_s, browsingContext)) {
487 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
488 return;
489 }
490 String handle;
491 if (!browsingContext->getString("handle"_s, handle)) {
492 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
493 return;
494 }
495 completionHandler(CommandResult::success(JSON::Value::create(handle)));
496 });
497}
498
499void Session::closeTopLevelBrowsingContext(const String& toplevelBrowsingContext, Function<void (CommandResult&&)>&& completionHandler)
500{
501 RefPtr<JSON::Object> parameters = JSON::Object::create();
502 parameters->setString("handle"_s, toplevelBrowsingContext);
503 m_host->sendCommandToBackend("closeBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
504 if (!m_host->isConnected()) {
505 // Closing the browsing context made the browser quit.
506 completionHandler(CommandResult::success(JSON::Array::create()));
507 return;
508 }
509 if (response.isError) {
510 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
511 return;
512 }
513
514 getWindowHandles([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) {
515 if (!m_host->isConnected()) {
516 // Closing the browsing context made the browser quit.
517 completionHandler(CommandResult::success(JSON::Array::create()));
518 return;
519 }
520 completionHandler(WTFMove(result));
521 });
522 });
523}
524
525void Session::closeWindow(Function<void (CommandResult&&)>&& completionHandler)
526{
527 if (!m_toplevelBrowsingContext) {
528 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
529 return;
530 }
531
532 handleUserPrompts([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
533 if (result.isError()) {
534 completionHandler(WTFMove(result));
535 return;
536 }
537 auto toplevelBrowsingContext = std::exchange(m_toplevelBrowsingContext, WTF::nullopt);
538 closeTopLevelBrowsingContext(toplevelBrowsingContext.value(), WTFMove(completionHandler));
539 });
540}
541
542void Session::switchToWindow(const String& windowHandle, Function<void (CommandResult&&)>&& completionHandler)
543{
544 RefPtr<JSON::Object> parameters = JSON::Object::create();
545 parameters->setString("browsingContextHandle"_s, windowHandle);
546 m_host->sendCommandToBackend("switchToBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), windowHandle, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
547 if (response.isError) {
548 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
549 return;
550 }
551 switchToTopLevelBrowsingContext(windowHandle);
552 completionHandler(CommandResult::success());
553 });
554}
555
556void Session::getWindowHandles(Function<void (CommandResult&&)>&& completionHandler)
557{
558 m_host->sendCommandToBackend("getBrowsingContexts"_s, JSON::Object::create(), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
559 if (response.isError || !response.responseObject) {
560 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
561 return;
562 }
563 RefPtr<JSON::Array> browsingContextArray;
564 if (!response.responseObject->getArray("contexts"_s, browsingContextArray)) {
565 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
566 return;
567 }
568 RefPtr<JSON::Array> windowHandles = JSON::Array::create();
569 for (unsigned i = 0; i < browsingContextArray->length(); ++i) {
570 RefPtr<JSON::Value> browsingContextValue = browsingContextArray->get(i);
571 RefPtr<JSON::Object> browsingContext;
572 if (!browsingContextValue->asObject(browsingContext)) {
573 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
574 return;
575 }
576
577 String handle;
578 if (!browsingContext->getString("handle"_s, handle)) {
579 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
580 return;
581 }
582
583 windowHandles->pushString(handle);
584 }
585 completionHandler(CommandResult::success(WTFMove(windowHandles)));
586 });
587}
588
589void Session::switchToFrame(RefPtr<JSON::Value>&& frameID, Function<void (CommandResult&&)>&& completionHandler)
590{
591 if (!m_toplevelBrowsingContext) {
592 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
593 return;
594 }
595
596 if (frameID->isNull()) {
597 switchToBrowsingContext(WTF::nullopt);
598 completionHandler(CommandResult::success());
599 return;
600 }
601
602 handleUserPrompts([this, protectedThis = makeRef(*this), frameID = WTFMove(frameID), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
603 if (result.isError()) {
604 completionHandler(WTFMove(result));
605 return;
606 }
607 RefPtr<JSON::Object> parameters = JSON::Object::create();
608 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
609 if (m_currentBrowsingContext)
610 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
611
612 int frameIndex;
613 if (frameID->asInteger(frameIndex)) {
614 if (frameIndex < 0 || frameIndex > USHRT_MAX) {
615 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchFrame));
616 return;
617 }
618 parameters->setInteger("ordinal"_s, frameIndex);
619 } else {
620 String frameElementID = extractElementID(*frameID);
621 if (!frameElementID.isEmpty())
622 parameters->setString("nodeHandle"_s, frameElementID);
623 else {
624 String frameName;
625 if (!frameID->asString(frameName)) {
626 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchFrame));
627 return;
628 }
629 parameters->setString("name"_s, frameName);
630 }
631 }
632
633 m_host->sendCommandToBackend("resolveChildFrameHandle"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
634 if (response.isError || !response.responseObject) {
635 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
636 return;
637 }
638 String frameHandle;
639 if (!response.responseObject->getString("result"_s, frameHandle)) {
640 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
641 return;
642 }
643 switchToBrowsingContext(frameHandle);
644 completionHandler(CommandResult::success());
645 });
646 });
647}
648
649void Session::switchToParentFrame(Function<void (CommandResult&&)>&& completionHandler)
650{
651 if (!m_toplevelBrowsingContext) {
652 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
653 return;
654 }
655
656 if (!m_currentBrowsingContext) {
657 completionHandler(CommandResult::success());
658 return;
659 }
660
661 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
662 if (result.isError()) {
663 completionHandler(WTFMove(result));
664 return;
665 }
666 RefPtr<JSON::Object> parameters = JSON::Object::create();
667 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
668 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
669 m_host->sendCommandToBackend("resolveParentFrameHandle"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
670 if (response.isError || !response.responseObject) {
671 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
672 return;
673 }
674 String frameHandle;
675 if (!response.responseObject->getString("result"_s, frameHandle)) {
676 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
677 return;
678 }
679 switchToBrowsingContext(frameHandle);
680 completionHandler(CommandResult::success());
681 });
682 });
683}
684
685void Session::getToplevelBrowsingContextRect(Function<void (CommandResult&&)>&& completionHandler)
686{
687 RefPtr<JSON::Object> parameters = JSON::Object::create();
688 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
689 m_host->sendCommandToBackend("getBrowsingContext"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
690 if (response.isError || !response.responseObject) {
691 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
692 return;
693 }
694 RefPtr<JSON::Object> browsingContext;
695 if (!response.responseObject->getObject("context"_s, browsingContext)) {
696 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
697 return;
698 }
699 RefPtr<JSON::Object> windowOrigin;
700 double x, y;
701 if (!browsingContext->getObject("windowOrigin"_s, windowOrigin)
702 || !windowOrigin->getDouble("x"_s, x)
703 || !windowOrigin->getDouble("y"_s, y)) {
704 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
705 return;
706 }
707 RefPtr<JSON::Object> windowSize;
708 double width, height;
709 if (!browsingContext->getObject("windowSize"_s, windowSize)
710 || !windowSize->getDouble("width"_s, width)
711 || !windowSize->getDouble("height"_s, height)) {
712 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
713 return;
714 }
715 auto windowRect = JSON::Object::create();
716 windowRect->setDouble("x"_s, x);
717 windowRect->setDouble("y"_s, y);
718 windowRect->setDouble("width"_s, width);
719 windowRect->setDouble("height"_s, height);
720 completionHandler(CommandResult::success(WTFMove(windowRect)));
721 });
722}
723
724void Session::getWindowRect(Function<void (CommandResult&&)>&& completionHandler)
725{
726 if (!m_toplevelBrowsingContext) {
727 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
728 return;
729 }
730
731 handleUserPrompts([this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
732 if (result.isError()) {
733 completionHandler(WTFMove(result));
734 return;
735 }
736 getToplevelBrowsingContextRect(WTFMove(completionHandler));
737 });
738}
739
740void Session::setWindowRect(Optional<double> x, Optional<double> y, Optional<double> width, Optional<double> height, Function<void (CommandResult&&)>&& completionHandler)
741{
742 if (!m_toplevelBrowsingContext) {
743 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
744 return;
745 }
746
747 handleUserPrompts([this, protectedThis = makeRef(*this), x, y, width, height, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
748 if (result.isError()) {
749 completionHandler(WTFMove(result));
750 return;
751 }
752
753 RefPtr<JSON::Object> parameters = JSON::Object::create();
754 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
755 if (x && y) {
756 RefPtr<JSON::Object> windowOrigin = JSON::Object::create();
757 windowOrigin->setDouble("x", x.value());
758 windowOrigin->setDouble("y", y.value());
759 parameters->setObject("origin"_s, WTFMove(windowOrigin));
760 }
761 if (width && height) {
762 RefPtr<JSON::Object> windowSize = JSON::Object::create();
763 windowSize->setDouble("width", width.value());
764 windowSize->setDouble("height", height.value());
765 parameters->setObject("size"_s, WTFMove(windowSize));
766 }
767 m_host->sendCommandToBackend("setWindowFrameOfBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)] (SessionHost::CommandResponse&& response) mutable {
768 if (response.isError) {
769 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
770 return;
771 }
772 getToplevelBrowsingContextRect(WTFMove(completionHandler));
773 });
774 });
775}
776
777void Session::maximizeWindow(Function<void (CommandResult&&)>&& completionHandler)
778{
779 if (!m_toplevelBrowsingContext) {
780 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
781 return;
782 }
783
784 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
785 if (result.isError()) {
786 completionHandler(WTFMove(result));
787 return;
788 }
789
790 RefPtr<JSON::Object> parameters = JSON::Object::create();
791 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
792 m_host->sendCommandToBackend("maximizeWindowOfBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)] (SessionHost::CommandResponse&& response) mutable {
793 if (response.isError) {
794 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
795 return;
796 }
797 getToplevelBrowsingContextRect(WTFMove(completionHandler));
798 });
799 });
800}
801
802void Session::minimizeWindow(Function<void (CommandResult&&)>&& completionHandler)
803{
804 if (!m_toplevelBrowsingContext) {
805 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
806 return;
807 }
808
809 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
810 if (result.isError()) {
811 completionHandler(WTFMove(result));
812 return;
813 }
814
815 RefPtr<JSON::Object> parameters = JSON::Object::create();
816 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
817 m_host->sendCommandToBackend("hideWindowOfBrowsingContext"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)] (SessionHost::CommandResponse&& response) mutable {
818 if (response.isError) {
819 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
820 return;
821 }
822 getToplevelBrowsingContextRect(WTFMove(completionHandler));
823 });
824 });
825}
826
827void Session::fullscreenWindow(Function<void (CommandResult&&)>&& completionHandler)
828{
829 if (!m_toplevelBrowsingContext) {
830 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
831 return;
832 }
833
834 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
835 if (result.isError()) {
836 completionHandler(WTFMove(result));
837 return;
838 }
839
840 RefPtr<JSON::Object> parameters = JSON::Object::create();
841 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
842 parameters->setString("function"_s, String(EnterFullscreenJavaScript, sizeof(EnterFullscreenJavaScript)));
843 parameters->setArray("arguments"_s, JSON::Array::create());
844 parameters->setBoolean("expectsImplicitCallbackArgument"_s, true);
845 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
846 if (response.isError || !response.responseObject) {
847 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
848 return;
849 }
850
851 String valueString;
852 if (!response.responseObject->getString("result"_s, valueString)) {
853 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
854 return;
855 }
856 RefPtr<JSON::Value> resultValue;
857 if (!JSON::Value::parseJSON(valueString, resultValue)) {
858 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
859 return;
860 }
861
862 getToplevelBrowsingContextRect(WTFMove(completionHandler));
863 });
864 });
865}
866
867RefPtr<JSON::Object> Session::createElement(RefPtr<JSON::Value>&& value)
868{
869 RefPtr<JSON::Object> valueObject;
870 if (!value->asObject(valueObject))
871 return nullptr;
872
873 String elementID;
874 if (!valueObject->getString("session-node-" + id(), elementID))
875 return nullptr;
876
877 RefPtr<JSON::Object> elementObject = JSON::Object::create();
878 elementObject->setString(webElementIdentifier(), elementID);
879 return elementObject;
880}
881
882RefPtr<JSON::Object> Session::createElement(const String& elementID)
883{
884 RefPtr<JSON::Object> elementObject = JSON::Object::create();
885 elementObject->setString("session-node-" + id(), elementID);
886 return elementObject;
887}
888
889RefPtr<JSON::Object> Session::extractElement(JSON::Value& value)
890{
891 String elementID = extractElementID(value);
892 return !elementID.isEmpty() ? createElement(elementID) : nullptr;
893}
894
895String Session::extractElementID(JSON::Value& value)
896{
897 RefPtr<JSON::Object> valueObject;
898 if (!value.asObject(valueObject))
899 return emptyString();
900
901 String elementID;
902 if (!valueObject->getString(webElementIdentifier(), elementID))
903 return emptyString();
904
905 return elementID;
906}
907
908void Session::computeElementLayout(const String& elementID, OptionSet<ElementLayoutOption> options, Function<void (Optional<Rect>&&, Optional<Point>&&, bool, RefPtr<JSON::Object>&&)>&& completionHandler)
909{
910 ASSERT(m_toplevelBrowsingContext.value());
911
912 RefPtr<JSON::Object> parameters = JSON::Object::create();
913 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
914 parameters->setString("frameHandle"_s, m_currentBrowsingContext.valueOr(emptyString()));
915 parameters->setString("nodeHandle"_s, elementID);
916 parameters->setBoolean("scrollIntoViewIfNeeded"_s, options.contains(ElementLayoutOption::ScrollIntoViewIfNeeded));
917 parameters->setString("coordinateSystem"_s, options.contains(ElementLayoutOption::UseViewportCoordinates) ? "LayoutViewport"_s : "Page"_s);
918 m_host->sendCommandToBackend("computeElementLayout"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
919 if (response.isError || !response.responseObject) {
920 completionHandler(WTF::nullopt, WTF::nullopt, false, WTFMove(response.responseObject));
921 return;
922 }
923 RefPtr<JSON::Object> rectObject;
924 if (!response.responseObject->getObject("rect"_s, rectObject)) {
925 completionHandler(WTF::nullopt, WTF::nullopt, false, nullptr);
926 return;
927 }
928 Optional<int> elementX;
929 Optional<int> elementY;
930 RefPtr<JSON::Object> elementPosition;
931 if (rectObject->getObject("origin"_s, elementPosition)) {
932 int x, y;
933 if (elementPosition->getInteger("x"_s, x) && elementPosition->getInteger("y"_s, y)) {
934 elementX = x;
935 elementY = y;
936 }
937 }
938 if (!elementX || !elementY) {
939 completionHandler(WTF::nullopt, WTF::nullopt, false, nullptr);
940 return;
941 }
942 Optional<int> elementWidth;
943 Optional<int> elementHeight;
944 RefPtr<JSON::Object> elementSize;
945 if (rectObject->getObject("size"_s, elementSize)) {
946 int width, height;
947 if (elementSize->getInteger("width"_s, width) && elementSize->getInteger("height"_s, height)) {
948 elementWidth = width;
949 elementHeight = height;
950 }
951 }
952 if (!elementWidth || !elementHeight) {
953 completionHandler(WTF::nullopt, WTF::nullopt, false, nullptr);
954 return;
955 }
956 Rect rect = { { elementX.value(), elementY.value() }, { elementWidth.value(), elementHeight.value() } };
957
958 bool isObscured;
959 if (!response.responseObject->getBoolean("isObscured"_s, isObscured)) {
960 completionHandler(WTF::nullopt, WTF::nullopt, false, nullptr);
961 return;
962 }
963 RefPtr<JSON::Object> inViewCenterPointObject;
964 if (!response.responseObject->getObject("inViewCenterPoint"_s, inViewCenterPointObject)) {
965 completionHandler(rect, WTF::nullopt, isObscured, nullptr);
966 return;
967 }
968 int inViewCenterPointX, inViewCenterPointY;
969 if (!inViewCenterPointObject->getInteger("x"_s, inViewCenterPointX)
970 || !inViewCenterPointObject->getInteger("y"_s, inViewCenterPointY)) {
971 completionHandler(WTF::nullopt, WTF::nullopt, isObscured, nullptr);
972 return;
973 }
974 Point inViewCenterPoint = { inViewCenterPointX, inViewCenterPointY };
975 completionHandler(rect, inViewCenterPoint, isObscured, nullptr);
976 });
977}
978
979void Session::findElements(const String& strategy, const String& selector, FindElementsMode mode, const String& rootElementID, Function<void (CommandResult&&)>&& completionHandler)
980{
981 if (!m_toplevelBrowsingContext) {
982 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
983 return;
984 }
985
986 handleUserPrompts([this, protectedThis = makeRef(*this), strategy, selector, mode, rootElementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
987 if (result.isError()) {
988 completionHandler(WTFMove(result));
989 return;
990 }
991 RefPtr<JSON::Array> arguments = JSON::Array::create();
992 arguments->pushString(JSON::Value::create(strategy)->toJSONString());
993 if (rootElementID.isEmpty())
994 arguments->pushString(JSON::Value::null()->toJSONString());
995 else
996 arguments->pushString(createElement(rootElementID)->toJSONString());
997 arguments->pushString(JSON::Value::create(selector)->toJSONString());
998 arguments->pushString(JSON::Value::create(mode == FindElementsMode::Single)->toJSONString());
999 arguments->pushString(JSON::Value::create(m_implicitWaitTimeout.milliseconds())->toJSONString());
1000
1001 RefPtr<JSON::Object> parameters = JSON::Object::create();
1002 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1003 if (m_currentBrowsingContext)
1004 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1005 parameters->setString("function"_s, String(FindNodesJavaScript, sizeof(FindNodesJavaScript)));
1006 parameters->setArray("arguments"_s, WTFMove(arguments));
1007 parameters->setBoolean("expectsImplicitCallbackArgument"_s, true);
1008 // If there's an implicit wait, use one second more as callback timeout.
1009 if (m_implicitWaitTimeout)
1010 parameters->setInteger("callbackTimeout"_s, Seconds(m_implicitWaitTimeout + 1_s).millisecondsAs<int>());
1011
1012 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), mode, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1013 if (response.isError || !response.responseObject) {
1014 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1015 return;
1016 }
1017 String valueString;
1018 if (!response.responseObject->getString("result"_s, valueString)) {
1019 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1020 return;
1021 }
1022 RefPtr<JSON::Value> resultValue;
1023 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1024 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1025 return;
1026 }
1027
1028 switch (mode) {
1029 case FindElementsMode::Single: {
1030 RefPtr<JSON::Object> elementObject = createElement(WTFMove(resultValue));
1031 if (!elementObject) {
1032 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchElement));
1033 return;
1034 }
1035 completionHandler(CommandResult::success(WTFMove(elementObject)));
1036 break;
1037 }
1038 case FindElementsMode::Multiple: {
1039 RefPtr<JSON::Array> elementsArray;
1040 if (!resultValue->asArray(elementsArray)) {
1041 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchElement));
1042 return;
1043 }
1044 RefPtr<JSON::Array> elementObjectsArray = JSON::Array::create();
1045 unsigned elementsArrayLength = elementsArray->length();
1046 for (unsigned i = 0; i < elementsArrayLength; ++i) {
1047 if (auto elementObject = createElement(elementsArray->get(i)))
1048 elementObjectsArray->pushObject(WTFMove(elementObject));
1049 }
1050 completionHandler(CommandResult::success(WTFMove(elementObjectsArray)));
1051 break;
1052 }
1053 }
1054 });
1055 });
1056}
1057
1058void Session::getActiveElement(Function<void (CommandResult&&)>&& completionHandler)
1059{
1060 if (!m_toplevelBrowsingContext) {
1061 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1062 return;
1063 }
1064
1065 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1066 if (result.isError()) {
1067 completionHandler(WTFMove(result));
1068 return;
1069 }
1070 RefPtr<JSON::Object> parameters = JSON::Object::create();
1071 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1072 parameters->setString("function"_s, "function() { return document.activeElement; }"_s);
1073 parameters->setArray("arguments"_s, JSON::Array::create());
1074 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1075 if (response.isError || !response.responseObject) {
1076 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1077 return;
1078 }
1079 String valueString;
1080 if (!response.responseObject->getString("result"_s, valueString)) {
1081 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1082 return;
1083 }
1084 RefPtr<JSON::Value> resultValue;
1085 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1086 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1087 return;
1088 }
1089 RefPtr<JSON::Object> elementObject = createElement(WTFMove(resultValue));
1090 if (!elementObject) {
1091 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchElement));
1092 return;
1093 }
1094 completionHandler(CommandResult::success(WTFMove(elementObject)));
1095 });
1096 });
1097}
1098
1099void Session::isElementSelected(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1100{
1101 if (!m_toplevelBrowsingContext) {
1102 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1103 return;
1104 }
1105
1106 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1107 if (result.isError()) {
1108 completionHandler(WTFMove(result));
1109 return;
1110 }
1111 RefPtr<JSON::Array> arguments = JSON::Array::create();
1112 arguments->pushString(createElement(elementID)->toJSONString());
1113 arguments->pushString(JSON::Value::create("selected")->toJSONString());
1114
1115 RefPtr<JSON::Object> parameters = JSON::Object::create();
1116 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1117 if (m_currentBrowsingContext)
1118 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1119 parameters->setString("function"_s, String(ElementAttributeJavaScript, sizeof(ElementAttributeJavaScript)));
1120 parameters->setArray("arguments"_s, WTFMove(arguments));
1121 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1122 if (response.isError || !response.responseObject) {
1123 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1124 return;
1125 }
1126 String valueString;
1127 if (!response.responseObject->getString("result"_s, valueString)) {
1128 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1129 return;
1130 }
1131 RefPtr<JSON::Value> resultValue;
1132 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1133 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1134 return;
1135 }
1136 if (resultValue->isNull()) {
1137 completionHandler(CommandResult::success(JSON::Value::create(false)));
1138 return;
1139 }
1140 String booleanResult;
1141 if (!resultValue->asString(booleanResult) || booleanResult != "true") {
1142 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1143 return;
1144 }
1145 completionHandler(CommandResult::success(JSON::Value::create(true)));
1146 });
1147 });
1148}
1149
1150void Session::getElementText(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1151{
1152 if (!m_toplevelBrowsingContext) {
1153 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1154 return;
1155 }
1156
1157 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1158 if (result.isError()) {
1159 completionHandler(WTFMove(result));
1160 return;
1161 }
1162 RefPtr<JSON::Array> arguments = JSON::Array::create();
1163 arguments->pushString(createElement(elementID)->toJSONString());
1164
1165 RefPtr<JSON::Object> parameters = JSON::Object::create();
1166 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1167 if (m_currentBrowsingContext)
1168 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1169 // FIXME: Add an atom to properly implement this instead of just using innerText.
1170 parameters->setString("function"_s, "function(element) { return element.innerText.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g, '') }"_s);
1171 parameters->setArray("arguments"_s, WTFMove(arguments));
1172 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1173 if (response.isError || !response.responseObject) {
1174 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1175 return;
1176 }
1177 String valueString;
1178 if (!response.responseObject->getString("result"_s, valueString)) {
1179 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1180 return;
1181 }
1182 RefPtr<JSON::Value> resultValue;
1183 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1184 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1185 return;
1186 }
1187 completionHandler(CommandResult::success(WTFMove(resultValue)));
1188 });
1189 });
1190}
1191
1192void Session::getElementTagName(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1193{
1194 if (!m_toplevelBrowsingContext) {
1195 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1196 return;
1197 }
1198
1199 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1200 if (result.isError()) {
1201 completionHandler(WTFMove(result));
1202 return;
1203 }
1204 RefPtr<JSON::Array> arguments = JSON::Array::create();
1205 arguments->pushString(createElement(elementID)->toJSONString());
1206
1207 RefPtr<JSON::Object> parameters = JSON::Object::create();
1208 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1209 if (m_currentBrowsingContext)
1210 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1211 parameters->setString("function"_s, "function(element) { return element.tagName.toLowerCase() }"_s);
1212 parameters->setArray("arguments"_s, WTFMove(arguments));
1213 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1214 if (response.isError || !response.responseObject) {
1215 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1216 return;
1217 }
1218 String valueString;
1219 if (!response.responseObject->getString("result"_s, valueString)) {
1220 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1221 return;
1222 }
1223 RefPtr<JSON::Value> resultValue;
1224 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1225 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1226 return;
1227 }
1228 completionHandler(CommandResult::success(WTFMove(resultValue)));
1229 });
1230 });
1231}
1232
1233void Session::getElementRect(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1234{
1235 if (!m_toplevelBrowsingContext) {
1236 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1237 return;
1238 }
1239
1240 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1241 if (result.isError()) {
1242 completionHandler(WTFMove(result));
1243 return;
1244 }
1245 computeElementLayout(elementID, { }, [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](Optional<Rect>&& rect, Optional<Point>&&, bool, RefPtr<JSON::Object>&& error) {
1246 if (!rect || error) {
1247 completionHandler(CommandResult::fail(WTFMove(error)));
1248 return;
1249 }
1250 RefPtr<JSON::Object> rectObject = JSON::Object::create();
1251 rectObject->setInteger("x"_s, rect.value().origin.x);
1252 rectObject->setInteger("y"_s, rect.value().origin.y);
1253 rectObject->setInteger("width"_s, rect.value().size.width);
1254 rectObject->setInteger("height"_s, rect.value().size.height);
1255 completionHandler(CommandResult::success(WTFMove(rectObject)));
1256 });
1257 });
1258}
1259
1260void Session::isElementEnabled(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1261{
1262 if (!m_toplevelBrowsingContext) {
1263 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1264 return;
1265 }
1266
1267 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1268 if (result.isError()) {
1269 completionHandler(WTFMove(result));
1270 return;
1271 }
1272 RefPtr<JSON::Array> arguments = JSON::Array::create();
1273 arguments->pushString(createElement(elementID)->toJSONString());
1274
1275 RefPtr<JSON::Object> parameters = JSON::Object::create();
1276 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1277 if (m_currentBrowsingContext)
1278 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1279 parameters->setString("function"_s, "function(element) { return element.disabled === undefined ? true : !element.disabled }"_s);
1280 parameters->setArray("arguments"_s, WTFMove(arguments));
1281 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1282 if (response.isError || !response.responseObject) {
1283 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1284 return;
1285 }
1286 String valueString;
1287 if (!response.responseObject->getString("result"_s, valueString)) {
1288 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1289 return;
1290 }
1291 RefPtr<JSON::Value> resultValue;
1292 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1293 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1294 return;
1295 }
1296 completionHandler(CommandResult::success(WTFMove(resultValue)));
1297 });
1298 });
1299}
1300
1301void Session::isElementDisplayed(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1302{
1303 if (!m_toplevelBrowsingContext) {
1304 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1305 return;
1306 }
1307
1308 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1309 if (result.isError()) {
1310 completionHandler(WTFMove(result));
1311 return;
1312 }
1313 RefPtr<JSON::Array> arguments = JSON::Array::create();
1314 arguments->pushString(createElement(elementID)->toJSONString());
1315
1316 RefPtr<JSON::Object> parameters = JSON::Object::create();
1317 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1318 if (m_currentBrowsingContext)
1319 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1320 parameters->setString("function"_s, String(ElementDisplayedJavaScript, sizeof(ElementDisplayedJavaScript)));
1321 parameters->setArray("arguments"_s, WTFMove(arguments));
1322 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1323 if (response.isError || !response.responseObject) {
1324 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1325 return;
1326 }
1327 String valueString;
1328 if (!response.responseObject->getString("result"_s, valueString)) {
1329 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1330 return;
1331 }
1332 RefPtr<JSON::Value> resultValue;
1333 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1334 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1335 return;
1336 }
1337 completionHandler(CommandResult::success(WTFMove(resultValue)));
1338 });
1339 });
1340}
1341
1342void Session::getElementAttribute(const String& elementID, const String& attribute, Function<void (CommandResult&&)>&& completionHandler)
1343{
1344 if (!m_toplevelBrowsingContext) {
1345 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1346 return;
1347 }
1348
1349 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, attribute, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1350 if (result.isError()) {
1351 completionHandler(WTFMove(result));
1352 return;
1353 }
1354 RefPtr<JSON::Array> arguments = JSON::Array::create();
1355 arguments->pushString(createElement(elementID)->toJSONString());
1356 arguments->pushString(JSON::Value::create(attribute)->toJSONString());
1357
1358 RefPtr<JSON::Object> parameters = JSON::Object::create();
1359 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1360 if (m_currentBrowsingContext)
1361 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1362 parameters->setString("function"_s, String(ElementAttributeJavaScript, sizeof(ElementAttributeJavaScript)));
1363 parameters->setArray("arguments"_s, WTFMove(arguments));
1364 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1365 if (response.isError || !response.responseObject) {
1366 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1367 return;
1368 }
1369 String valueString;
1370 if (!response.responseObject->getString("result"_s, valueString)) {
1371 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1372 return;
1373 }
1374 RefPtr<JSON::Value> resultValue;
1375 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1376 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1377 return;
1378 }
1379 completionHandler(CommandResult::success(WTFMove(resultValue)));
1380 });
1381 });
1382}
1383
1384void Session::getElementProperty(const String& elementID, const String& property, Function<void (CommandResult&&)>&& completionHandler)
1385{
1386 if (!m_toplevelBrowsingContext) {
1387 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1388 return;
1389 }
1390
1391 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, property, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1392 if (result.isError()) {
1393 completionHandler(WTFMove(result));
1394 return;
1395 }
1396 RefPtr<JSON::Array> arguments = JSON::Array::create();
1397 arguments->pushString(createElement(elementID)->toJSONString());
1398
1399 RefPtr<JSON::Object> parameters = JSON::Object::create();
1400 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1401 if (m_currentBrowsingContext)
1402 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1403 parameters->setString("function"_s, makeString("function(element) { return element.", property, "; }"));
1404 parameters->setArray("arguments"_s, WTFMove(arguments));
1405 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1406 if (response.isError || !response.responseObject) {
1407 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1408 return;
1409 }
1410 String valueString;
1411 if (!response.responseObject->getString("result"_s, valueString)) {
1412 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1413 return;
1414 }
1415 RefPtr<JSON::Value> resultValue;
1416 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1417 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1418 return;
1419 }
1420 completionHandler(CommandResult::success(WTFMove(resultValue)));
1421 });
1422 });
1423}
1424
1425void Session::getElementCSSValue(const String& elementID, const String& cssProperty, Function<void (CommandResult&&)>&& completionHandler)
1426{
1427 if (!m_toplevelBrowsingContext) {
1428 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1429 return;
1430 }
1431
1432 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, cssProperty, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1433 if (result.isError()) {
1434 completionHandler(WTFMove(result));
1435 return;
1436 }
1437 RefPtr<JSON::Array> arguments = JSON::Array::create();
1438 arguments->pushString(createElement(elementID)->toJSONString());
1439
1440 RefPtr<JSON::Object> parameters = JSON::Object::create();
1441 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1442 if (m_currentBrowsingContext)
1443 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1444 parameters->setString("function"_s, makeString("function(element) { return document.defaultView.getComputedStyle(element).getPropertyValue('", cssProperty, "'); }"));
1445 parameters->setArray("arguments"_s, WTFMove(arguments));
1446 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1447 if (response.isError || !response.responseObject) {
1448 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1449 return;
1450 }
1451 String valueString;
1452 if (!response.responseObject->getString("result"_s, valueString)) {
1453 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1454 return;
1455 }
1456 RefPtr<JSON::Value> resultValue;
1457 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1458 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1459 return;
1460 }
1461 completionHandler(CommandResult::success(WTFMove(resultValue)));
1462 });
1463 });
1464}
1465
1466void Session::waitForNavigationToComplete(Function<void (CommandResult&&)>&& completionHandler)
1467{
1468 if (!m_toplevelBrowsingContext) {
1469 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1470 return;
1471 }
1472
1473 RefPtr<JSON::Object> parameters = JSON::Object::create();
1474 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1475 if (m_currentBrowsingContext)
1476 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1477 parameters->setInteger("pageLoadTimeout"_s, m_pageLoadTimeout.millisecondsAs<int>());
1478 if (auto pageLoadStrategy = pageLoadStrategyString())
1479 parameters->setString("pageLoadStrategy"_s, pageLoadStrategy.value());
1480 m_host->sendCommandToBackend("waitForNavigationToComplete"_s, WTFMove(parameters), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1481 if (response.isError) {
1482 auto result = CommandResult::fail(WTFMove(response.responseObject));
1483 switch (result.errorCode()) {
1484 case CommandResult::ErrorCode::NoSuchWindow:
1485 // Window was closed, reset the top level browsing context and ignore the error.
1486 m_toplevelBrowsingContext = WTF::nullopt;
1487 break;
1488 case CommandResult::ErrorCode::NoSuchFrame:
1489 // Navigation destroyed the current frame, switch to top level browsing context and ignore the error.
1490 switchToBrowsingContext(WTF::nullopt);
1491 break;
1492 default:
1493 completionHandler(WTFMove(result));
1494 return;
1495 }
1496 }
1497 completionHandler(CommandResult::success());
1498 });
1499}
1500
1501void Session::selectOptionElement(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1502{
1503 RefPtr<JSON::Object> parameters = JSON::Object::create();
1504 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1505 parameters->setString("frameHandle"_s, m_currentBrowsingContext.valueOr(emptyString()));
1506 parameters->setString("nodeHandle"_s, elementID);
1507 m_host->sendCommandToBackend("selectOptionElement"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1508 if (response.isError) {
1509 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1510 return;
1511 }
1512 completionHandler(CommandResult::success());
1513 });
1514}
1515
1516void Session::elementClick(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1517{
1518 if (!m_toplevelBrowsingContext) {
1519 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1520 return;
1521 }
1522
1523 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1524 if (result.isError()) {
1525 completionHandler(WTFMove(result));
1526 return;
1527 }
1528 OptionSet<ElementLayoutOption> options = { ElementLayoutOption::ScrollIntoViewIfNeeded, ElementLayoutOption::UseViewportCoordinates };
1529 computeElementLayout(elementID, options, [this, protectedThis = protectedThis.copyRef(), elementID, completionHandler = WTFMove(completionHandler)](Optional<Rect>&& rect, Optional<Point>&& inViewCenter, bool isObscured, RefPtr<JSON::Object>&& error) mutable {
1530 if (!rect || error) {
1531 completionHandler(CommandResult::fail(WTFMove(error)));
1532 return;
1533 }
1534 if (isObscured) {
1535 completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementClickIntercepted));
1536 return;
1537 }
1538 if (!inViewCenter) {
1539 completionHandler(CommandResult::fail(CommandResult::ErrorCode::ElementNotInteractable));
1540 return;
1541 }
1542
1543 getElementTagName(elementID, [this, elementID, inViewCenter = WTFMove(inViewCenter), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1544 bool isOptionElement = false;
1545 if (!result.isError()) {
1546 String tagName;
1547 if (result.result()->asString(tagName))
1548 isOptionElement = tagName == "option";
1549 }
1550
1551 Function<void (CommandResult&&)> continueAfterClickFunction = [this, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1552 if (result.isError()) {
1553 completionHandler(WTFMove(result));
1554 return;
1555 }
1556
1557 waitForNavigationToComplete(WTFMove(completionHandler));
1558 };
1559 if (isOptionElement)
1560 selectOptionElement(elementID, WTFMove(continueAfterClickFunction));
1561 else
1562 performMouseInteraction(inViewCenter.value().x, inViewCenter.value().y, MouseButton::Left, MouseInteraction::SingleClick, WTFMove(continueAfterClickFunction));
1563 });
1564 });
1565 });
1566}
1567
1568void Session::elementClear(const String& elementID, Function<void (CommandResult&&)>&& completionHandler)
1569{
1570 if (!m_toplevelBrowsingContext) {
1571 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1572 return;
1573 }
1574
1575 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1576 if (result.isError()) {
1577 completionHandler(WTFMove(result));
1578 return;
1579 }
1580
1581 RefPtr<JSON::Array> arguments = JSON::Array::create();
1582 arguments->pushString(createElement(elementID)->toJSONString());
1583
1584 RefPtr<JSON::Object> parameters = JSON::Object::create();
1585 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1586 if (m_currentBrowsingContext)
1587 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1588 parameters->setString("function"_s, String(FormElementClearJavaScript, sizeof(FormElementClearJavaScript)));
1589 parameters->setArray("arguments"_s, WTFMove(arguments));
1590 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1591 if (response.isError) {
1592 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1593 return;
1594 }
1595 completionHandler(CommandResult::success());
1596 });
1597 });
1598}
1599
1600String Session::virtualKeyForKey(UChar key, KeyModifier& modifier)
1601{
1602 // §17.4.2 Keyboard Actions.
1603 // https://www.w3.org/TR/webdriver/#keyboard-actions
1604 modifier = KeyModifier::None;
1605 switch (key) {
1606 case 0xE001U:
1607 return "Cancel"_s;
1608 case 0xE002U:
1609 return "Help"_s;
1610 case 0xE003U:
1611 return "Backspace"_s;
1612 case 0xE004U:
1613 return "Tab"_s;
1614 case 0xE005U:
1615 return "Clear"_s;
1616 case 0xE006U:
1617 return "Return"_s;
1618 case 0xE007U:
1619 return "Enter"_s;
1620 case 0xE008U:
1621 case 0xE050U:
1622 modifier = KeyModifier::Shift;
1623 return "Shift"_s;
1624 case 0xE009U:
1625 case 0xE051U:
1626 modifier = KeyModifier::Control;
1627 return "Control"_s;
1628 case 0xE00AU:
1629 case 0xE052U:
1630 modifier = KeyModifier::Alternate;
1631 return "Alternate"_s;
1632 case 0xE00BU:
1633 return "Pause"_s;
1634 case 0xE00CU:
1635 return "Escape"_s;
1636 case 0xE00DU:
1637 return "Space"_s;
1638 case 0xE00EU:
1639 case 0xE054U:
1640 return "PageUp"_s;
1641 case 0xE00FU:
1642 case 0xE055U:
1643 return "PageDown"_s;
1644 case 0xE010U:
1645 case 0xE056U:
1646 return "End"_s;
1647 case 0xE011U:
1648 case 0xE057U:
1649 return "Home"_s;
1650 case 0xE012U:
1651 case 0xE058U:
1652 return "LeftArrow"_s;
1653 case 0xE013U:
1654 case 0xE059U:
1655 return "UpArrow"_s;
1656 case 0xE014U:
1657 case 0xE05AU:
1658 return "RightArrow"_s;
1659 case 0xE015U:
1660 case 0xE05BU:
1661 return "DownArrow"_s;
1662 case 0xE016U:
1663 case 0xE05CU:
1664 return "Insert"_s;
1665 case 0xE017U:
1666 case 0xE05DU:
1667 return "Delete"_s;
1668 case 0xE018U:
1669 return "Semicolon"_s;
1670 case 0xE019U:
1671 return "Equals"_s;
1672 case 0xE01AU:
1673 return "NumberPad0"_s;
1674 case 0xE01BU:
1675 return "NumberPad1"_s;
1676 case 0xE01CU:
1677 return "NumberPad2"_s;
1678 case 0xE01DU:
1679 return "NumberPad3"_s;
1680 case 0xE01EU:
1681 return "NumberPad4"_s;
1682 case 0xE01FU:
1683 return "NumberPad5"_s;
1684 case 0xE020U:
1685 return "NumberPad6"_s;
1686 case 0xE021U:
1687 return "NumberPad7"_s;
1688 case 0xE022U:
1689 return "NumberPad8"_s;
1690 case 0xE023U:
1691 return "NumberPad9"_s;
1692 case 0xE024U:
1693 return "NumberPadMultiply"_s;
1694 case 0xE025U:
1695 return "NumberPadAdd"_s;
1696 case 0xE026U:
1697 return "NumberPadSeparator"_s;
1698 case 0xE027U:
1699 return "NumberPadSubtract"_s;
1700 case 0xE028U:
1701 return "NumberPadDecimal"_s;
1702 case 0xE029U:
1703 return "NumberPadDivide"_s;
1704 case 0xE031U:
1705 return "Function1"_s;
1706 case 0xE032U:
1707 return "Function2"_s;
1708 case 0xE033U:
1709 return "Function3"_s;
1710 case 0xE034U:
1711 return "Function4"_s;
1712 case 0xE035U:
1713 return "Function5"_s;
1714 case 0xE036U:
1715 return "Function6"_s;
1716 case 0xE037U:
1717 return "Function7"_s;
1718 case 0xE038U:
1719 return "Function8"_s;
1720 case 0xE039U:
1721 return "Function9"_s;
1722 case 0xE03AU:
1723 return "Function10"_s;
1724 case 0xE03BU:
1725 return "Function11"_s;
1726 case 0xE03CU:
1727 return "Function12"_s;
1728 case 0xE03DU:
1729 case 0xE053U:
1730 modifier = KeyModifier::Meta;
1731 return "Meta"_s;
1732 default:
1733 break;
1734 }
1735 return String();
1736}
1737
1738void Session::elementSendKeys(const String& elementID, const String& text, Function<void (CommandResult&&)>&& completionHandler)
1739{
1740 if (!m_toplevelBrowsingContext) {
1741 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1742 return;
1743 }
1744
1745 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, text, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1746 if (result.isError()) {
1747 completionHandler(WTFMove(result));
1748 return;
1749 }
1750 // FIXME: move this to an atom.
1751 static const char focusScript[] =
1752 "function focus(element) {"
1753 " let doc = element.ownerDocument || element;"
1754 " let prevActiveElement = doc.activeElement;"
1755 " if (element != prevActiveElement && prevActiveElement)"
1756 " prevActiveElement.blur();"
1757 " element.focus();"
1758 " let tagName = element.tagName.toUpperCase();"
1759 " let isTextElement = tagName === 'TEXTAREA' || (tagName === 'INPUT' && element.type === 'text');"
1760 " if (isTextElement && element.selectionEnd == 0)"
1761 " element.setSelectionRange(element.value.length, element.value.length);"
1762 " if (element != doc.activeElement)"
1763 " throw new Error('cannot focus element');"
1764 "}";
1765
1766 RefPtr<JSON::Array> arguments = JSON::Array::create();
1767 arguments->pushString(createElement(elementID)->toJSONString());
1768 RefPtr<JSON::Object> parameters = JSON::Object::create();
1769 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1770 if (m_currentBrowsingContext)
1771 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1772 parameters->setString("function"_s, focusScript);
1773 parameters->setArray("arguments"_s, WTFMove(arguments));
1774 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), text, completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
1775 if (response.isError || !response.responseObject) {
1776 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1777 return;
1778 }
1779
1780 unsigned stickyModifiers = 0;
1781 auto textLength = text.length();
1782 Vector<KeyboardInteraction> interactions;
1783 interactions.reserveInitialCapacity(textLength);
1784 for (unsigned i = 0; i < textLength; ++i) {
1785 auto key = text[i];
1786 KeyboardInteraction interaction;
1787 KeyModifier modifier;
1788 auto virtualKey = virtualKeyForKey(key, modifier);
1789 if (!virtualKey.isNull()) {
1790 interaction.key = virtualKey;
1791 if (modifier != KeyModifier::None) {
1792 stickyModifiers ^= modifier;
1793 if (stickyModifiers & modifier)
1794 interaction.type = KeyboardInteractionType::KeyPress;
1795 else
1796 interaction.type = KeyboardInteractionType::KeyRelease;
1797 }
1798 } else
1799 interaction.text = String(&key, 1);
1800 interactions.uncheckedAppend(WTFMove(interaction));
1801 }
1802
1803 // Reset sticky modifiers if needed.
1804 if (stickyModifiers) {
1805 if (stickyModifiers & KeyModifier::Shift)
1806 interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Shift"_s) });
1807 if (stickyModifiers & KeyModifier::Control)
1808 interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Control"_s) });
1809 if (stickyModifiers & KeyModifier::Alternate)
1810 interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Alternate"_s) });
1811 if (stickyModifiers & KeyModifier::Meta)
1812 interactions.append({ KeyboardInteractionType::KeyRelease, WTF::nullopt, Optional<String>("Meta"_s) });
1813 }
1814
1815 performKeyboardInteractions(WTFMove(interactions), WTFMove(completionHandler));
1816 });
1817 });
1818}
1819
1820RefPtr<JSON::Value> Session::handleScriptResult(RefPtr<JSON::Value>&& resultValue)
1821{
1822 RefPtr<JSON::Array> resultArray;
1823 if (resultValue->asArray(resultArray)) {
1824 RefPtr<JSON::Array> returnValueArray = JSON::Array::create();
1825 unsigned resultArrayLength = resultArray->length();
1826 for (unsigned i = 0; i < resultArrayLength; ++i)
1827 returnValueArray->pushValue(handleScriptResult(resultArray->get(i)));
1828 return returnValueArray;
1829 }
1830
1831 if (auto element = createElement(RefPtr<JSON::Value>(resultValue)))
1832 return element;
1833
1834 RefPtr<JSON::Object> resultObject;
1835 if (resultValue->asObject(resultObject)) {
1836 RefPtr<JSON::Object> returnValueObject = JSON::Object::create();
1837 auto end = resultObject->end();
1838 for (auto it = resultObject->begin(); it != end; ++it)
1839 returnValueObject->setValue(it->key, handleScriptResult(WTFMove(it->value)));
1840 return returnValueObject;
1841 }
1842
1843 return WTFMove(resultValue);
1844}
1845
1846void Session::executeScript(const String& script, RefPtr<JSON::Array>&& argumentsArray, ExecuteScriptMode mode, Function<void (CommandResult&&)>&& completionHandler)
1847{
1848 if (!m_toplevelBrowsingContext) {
1849 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
1850 return;
1851 }
1852
1853 handleUserPrompts([this, protectedThis = makeRef(*this), script, argumentsArray = WTFMove(argumentsArray), mode, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
1854 if (result.isError()) {
1855 completionHandler(WTFMove(result));
1856 return;
1857 }
1858 RefPtr<JSON::Array> arguments = JSON::Array::create();
1859 unsigned argumentsLength = argumentsArray->length();
1860 for (unsigned i = 0; i < argumentsLength; ++i) {
1861 if (auto argument = argumentsArray->get(i)) {
1862 if (auto element = extractElement(*argument))
1863 arguments->pushString(element->toJSONString());
1864 else
1865 arguments->pushString(argument->toJSONString());
1866 }
1867 }
1868
1869 RefPtr<JSON::Object> parameters = JSON::Object::create();
1870 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
1871 if (m_currentBrowsingContext)
1872 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
1873 parameters->setString("function"_s, "function(){" + script + '}');
1874 parameters->setArray("arguments"_s, WTFMove(arguments));
1875 if (mode == ExecuteScriptMode::Async) {
1876 parameters->setBoolean("expectsImplicitCallbackArgument"_s, true);
1877 if (m_scriptTimeout)
1878 parameters->setInteger("callbackTimeout"_s, m_scriptTimeout.millisecondsAs<int>());
1879 }
1880 m_host->sendCommandToBackend("evaluateJavaScriptFunction"_s, WTFMove(parameters), [this, protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
1881 if (response.isError || !response.responseObject) {
1882 auto result = CommandResult::fail(WTFMove(response.responseObject));
1883 if (result.errorCode() == CommandResult::ErrorCode::UnexpectedAlertOpen)
1884 completionHandler(CommandResult::success());
1885 else
1886 completionHandler(WTFMove(result));
1887 return;
1888 }
1889 String valueString;
1890 if (!response.responseObject->getString("result"_s, valueString)) {
1891 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1892 return;
1893 }
1894 RefPtr<JSON::Value> resultValue;
1895 if (!JSON::Value::parseJSON(valueString, resultValue)) {
1896 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
1897 return;
1898 }
1899 completionHandler(CommandResult::success(handleScriptResult(WTFMove(resultValue))));
1900 });
1901 });
1902}
1903
1904static String mouseButtonForAutomation(MouseButton button)
1905{
1906 switch (button) {
1907 case MouseButton::None:
1908 return "None"_s;
1909 case MouseButton::Left:
1910 return "Left"_s;
1911 case MouseButton::Middle:
1912 return "Middle"_s;
1913 case MouseButton::Right:
1914 return "Right"_s;
1915 }
1916
1917 RELEASE_ASSERT_NOT_REACHED();
1918}
1919
1920void Session::performMouseInteraction(int x, int y, MouseButton button, MouseInteraction interaction, Function<void (CommandResult&&)>&& completionHandler)
1921{
1922 RefPtr<JSON::Object> parameters = JSON::Object::create();
1923 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
1924 RefPtr<JSON::Object> position = JSON::Object::create();
1925 position->setInteger("x"_s, x);
1926 position->setInteger("y"_s, y);
1927 parameters->setObject("position"_s, WTFMove(position));
1928 parameters->setString("button"_s, mouseButtonForAutomation(button));
1929 switch (interaction) {
1930 case MouseInteraction::Move:
1931 parameters->setString("interaction"_s, "Move"_s);
1932 break;
1933 case MouseInteraction::Down:
1934 parameters->setString("interaction"_s, "Down"_s);
1935 break;
1936 case MouseInteraction::Up:
1937 parameters->setString("interaction"_s, "Up"_s);
1938 break;
1939 case MouseInteraction::SingleClick:
1940 parameters->setString("interaction"_s, "SingleClick"_s);
1941 break;
1942 case MouseInteraction::DoubleClick:
1943 parameters->setString("interaction"_s, "DoubleClick"_s);
1944 break;
1945 }
1946 parameters->setArray("modifiers"_s, JSON::Array::create());
1947 m_host->sendCommandToBackend("performMouseInteraction"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1948 if (response.isError) {
1949 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1950 return;
1951 }
1952 completionHandler(CommandResult::success());
1953 });
1954}
1955
1956void Session::performKeyboardInteractions(Vector<KeyboardInteraction>&& interactions, Function<void (CommandResult&&)>&& completionHandler)
1957{
1958 RefPtr<JSON::Object> parameters = JSON::Object::create();
1959 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
1960 RefPtr<JSON::Array> interactionsArray = JSON::Array::create();
1961 for (const auto& interaction : interactions) {
1962 RefPtr<JSON::Object> interactionObject = JSON::Object::create();
1963 switch (interaction.type) {
1964 case KeyboardInteractionType::KeyPress:
1965 interactionObject->setString("type"_s, "KeyPress"_s);
1966 break;
1967 case KeyboardInteractionType::KeyRelease:
1968 interactionObject->setString("type"_s, "KeyRelease"_s);
1969 break;
1970 case KeyboardInteractionType::InsertByKey:
1971 interactionObject->setString("type"_s, "InsertByKey"_s);
1972 break;
1973 }
1974 if (interaction.key)
1975 interactionObject->setString("key"_s, interaction.key.value());
1976 if (interaction.text)
1977 interactionObject->setString("text"_s, interaction.text.value());
1978 interactionsArray->pushObject(WTFMove(interactionObject));
1979 }
1980 parameters->setArray("interactions"_s, WTFMove(interactionsArray));
1981 m_host->sendCommandToBackend("performKeyboardInteractions"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
1982 if (response.isError) {
1983 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
1984 return;
1985 }
1986 completionHandler(CommandResult::success());
1987 });
1988}
1989
1990static Optional<Session::Cookie> parseAutomationCookie(const JSON::Object& cookieObject)
1991{
1992 Session::Cookie cookie;
1993 if (!cookieObject.getString("name"_s, cookie.name))
1994 return WTF::nullopt;
1995 if (!cookieObject.getString("value"_s, cookie.value))
1996 return WTF::nullopt;
1997
1998 String path;
1999 if (cookieObject.getString("path"_s, path))
2000 cookie.path = path;
2001 String domain;
2002 if (cookieObject.getString("domain"_s, domain))
2003 cookie.domain = domain;
2004 bool secure;
2005 if (cookieObject.getBoolean("secure"_s, secure))
2006 cookie.secure = secure;
2007 bool httpOnly;
2008 if (cookieObject.getBoolean("httpOnly"_s, httpOnly))
2009 cookie.httpOnly = httpOnly;
2010 bool session = false;
2011 cookieObject.getBoolean("session"_s, session);
2012 if (!session) {
2013 double expiry;
2014 if (cookieObject.getDouble("expires"_s, expiry))
2015 cookie.expiry = expiry;
2016 }
2017
2018 return cookie;
2019}
2020
2021static RefPtr<JSON::Object> builtAutomationCookie(const Session::Cookie& cookie)
2022{
2023 RefPtr<JSON::Object> cookieObject = JSON::Object::create();
2024 cookieObject->setString("name"_s, cookie.name);
2025 cookieObject->setString("value"_s, cookie.value);
2026 cookieObject->setString("path"_s, cookie.path.valueOr("/"));
2027 cookieObject->setString("domain"_s, cookie.domain.valueOr(emptyString()));
2028 cookieObject->setBoolean("secure"_s, cookie.secure.valueOr(false));
2029 cookieObject->setBoolean("httpOnly"_s, cookie.httpOnly.valueOr(false));
2030 cookieObject->setBoolean("session"_s, !cookie.expiry);
2031 cookieObject->setDouble("expires"_s, cookie.expiry.valueOr(0));
2032 return cookieObject;
2033}
2034
2035static RefPtr<JSON::Object> serializeCookie(const Session::Cookie& cookie)
2036{
2037 RefPtr<JSON::Object> cookieObject = JSON::Object::create();
2038 cookieObject->setString("name"_s, cookie.name);
2039 cookieObject->setString("value"_s, cookie.value);
2040 if (cookie.path)
2041 cookieObject->setString("path"_s, cookie.path.value());
2042 if (cookie.domain)
2043 cookieObject->setString("domain"_s, cookie.domain.value());
2044 if (cookie.secure)
2045 cookieObject->setBoolean("secure"_s, cookie.secure.value());
2046 if (cookie.httpOnly)
2047 cookieObject->setBoolean("httpOnly"_s, cookie.httpOnly.value());
2048 if (cookie.expiry)
2049 cookieObject->setInteger("expiry"_s, cookie.expiry.value());
2050 return cookieObject;
2051}
2052
2053void Session::getAllCookies(Function<void (CommandResult&&)>&& completionHandler)
2054{
2055 if (!m_toplevelBrowsingContext) {
2056 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2057 return;
2058 }
2059
2060 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2061 if (result.isError()) {
2062 completionHandler(WTFMove(result));
2063 return;
2064 }
2065
2066 RefPtr<JSON::Object> parameters = JSON::Object::create();
2067 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2068 m_host->sendCommandToBackend("getAllCookies"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
2069 if (response.isError || !response.responseObject) {
2070 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2071 return;
2072 }
2073 RefPtr<JSON::Array> cookiesArray;
2074 if (!response.responseObject->getArray("cookies"_s, cookiesArray)) {
2075 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
2076 return;
2077 }
2078 RefPtr<JSON::Array> cookies = JSON::Array::create();
2079 for (unsigned i = 0; i < cookiesArray->length(); ++i) {
2080 RefPtr<JSON::Value> cookieValue = cookiesArray->get(i);
2081 RefPtr<JSON::Object> cookieObject;
2082 if (!cookieValue->asObject(cookieObject)) {
2083 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
2084 return;
2085 }
2086
2087 auto cookie = parseAutomationCookie(*cookieObject);
2088 if (!cookie) {
2089 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
2090 return;
2091 }
2092 cookies->pushObject(serializeCookie(cookie.value()));
2093 }
2094 completionHandler(CommandResult::success(WTFMove(cookies)));
2095 });
2096 });
2097}
2098
2099void Session::getNamedCookie(const String& name, Function<void (CommandResult&&)>&& completionHandler)
2100{
2101 getAllCookies([name, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2102 if (result.isError()) {
2103 completionHandler(WTFMove(result));
2104 return;
2105 }
2106 RefPtr<JSON::Array> cookiesArray;
2107 result.result()->asArray(cookiesArray);
2108 for (unsigned i = 0; i < cookiesArray->length(); ++i) {
2109 RefPtr<JSON::Value> cookieValue = cookiesArray->get(i);
2110 RefPtr<JSON::Object> cookieObject;
2111 cookieValue->asObject(cookieObject);
2112 String cookieName;
2113 cookieObject->getString("name"_s, cookieName);
2114 if (cookieName == name) {
2115 completionHandler(CommandResult::success(WTFMove(cookieObject)));
2116 return;
2117 }
2118 }
2119 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchCookie));
2120 });
2121}
2122
2123void Session::addCookie(const Cookie& cookie, Function<void (CommandResult&&)>&& completionHandler)
2124{
2125 if (!m_toplevelBrowsingContext) {
2126 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2127 return;
2128 }
2129
2130 handleUserPrompts([this, protectedThis = makeRef(*this), cookie = builtAutomationCookie(cookie), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2131 if (result.isError()) {
2132 completionHandler(WTFMove(result));
2133 return;
2134 }
2135 RefPtr<JSON::Object> parameters = JSON::Object::create();
2136 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2137 parameters->setObject("cookie"_s, WTFMove(cookie));
2138 m_host->sendCommandToBackend("addSingleCookie"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2139 if (response.isError) {
2140 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2141 return;
2142 }
2143 completionHandler(CommandResult::success());
2144 });
2145 });
2146}
2147
2148void Session::deleteCookie(const String& name, Function<void (CommandResult&&)>&& completionHandler)
2149{
2150 if (!m_toplevelBrowsingContext) {
2151 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2152 return;
2153 }
2154
2155 handleUserPrompts([this, protectedThis = makeRef(*this), name, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2156 if (result.isError()) {
2157 completionHandler(WTFMove(result));
2158 return;
2159 }
2160 RefPtr<JSON::Object> parameters = JSON::Object::create();
2161 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2162 parameters->setString("cookieName"_s, name);
2163 m_host->sendCommandToBackend("deleteSingleCookie"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2164 if (response.isError) {
2165 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2166 return;
2167 }
2168 completionHandler(CommandResult::success());
2169 });
2170 });
2171}
2172
2173void Session::deleteAllCookies(Function<void (CommandResult&&)>&& completionHandler)
2174{
2175 if (!m_toplevelBrowsingContext) {
2176 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2177 return;
2178 }
2179
2180 handleUserPrompts([this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2181 if (result.isError()) {
2182 completionHandler(WTFMove(result));
2183 return;
2184 }
2185 RefPtr<JSON::Object> parameters = JSON::Object::create();
2186 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2187 m_host->sendCommandToBackend("deleteAllCookies"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2188 if (response.isError) {
2189 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2190 return;
2191 }
2192 completionHandler(CommandResult::success());
2193 });
2194 });
2195}
2196
2197InputSource& Session::getOrCreateInputSource(const String& id, InputSource::Type type, Optional<PointerType> pointerType)
2198{
2199 auto addResult = m_activeInputSources.add(id, InputSource());
2200 if (addResult.isNewEntry)
2201 addResult.iterator->value = { type, pointerType };
2202 return addResult.iterator->value;
2203}
2204
2205Session::InputSourceState& Session::inputSourceState(const String& id)
2206{
2207 return m_inputStateTable.ensure(id, [] { return InputSourceState(); }).iterator->value;
2208}
2209
2210static const char* automationSourceType(InputSource::Type type)
2211{
2212 switch (type) {
2213 case InputSource::Type::None:
2214 return "Null";
2215 case InputSource::Type::Pointer:
2216 return "Mouse";
2217 case InputSource::Type::Key:
2218 return "Keyboard";
2219 }
2220 RELEASE_ASSERT_NOT_REACHED();
2221}
2222
2223static const char* automationOriginType(PointerOrigin::Type type)
2224{
2225 switch (type) {
2226 case PointerOrigin::Type::Viewport:
2227 return "Viewport";
2228 case PointerOrigin::Type::Pointer:
2229 return "Pointer";
2230 case PointerOrigin::Type::Element:
2231 return "Element";
2232 }
2233 RELEASE_ASSERT_NOT_REACHED();
2234}
2235
2236void Session::performActions(Vector<Vector<Action>>&& actionsByTick, Function<void (CommandResult&&)>&& completionHandler)
2237{
2238 if (!m_toplevelBrowsingContext) {
2239 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2240 return;
2241 }
2242
2243 handleUserPrompts([this, protectedThis = makeRef(*this), actionsByTick = WTFMove(actionsByTick), completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2244 if (result.isError()) {
2245 completionHandler(WTFMove(result));
2246 return;
2247 }
2248
2249 // First check if we have actions and whether we need to resolve any pointer move element origin.
2250 unsigned actionsCount = 0;
2251 for (const auto& tick : actionsByTick)
2252 actionsCount += tick.size();
2253 if (!actionsCount) {
2254 completionHandler(CommandResult::success());
2255 return;
2256 }
2257
2258 RefPtr<JSON::Object> parameters = JSON::Object::create();
2259 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
2260 if (m_currentBrowsingContext)
2261 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
2262 RefPtr<JSON::Array> inputSources = JSON::Array::create();
2263 for (const auto& inputSource : m_activeInputSources) {
2264 RefPtr<JSON::Object> inputSourceObject = JSON::Object::create();
2265 inputSourceObject->setString("sourceId"_s, inputSource.key);
2266 inputSourceObject->setString("sourceType"_s, automationSourceType(inputSource.value.type));
2267 inputSources->pushObject(WTFMove(inputSourceObject));
2268 }
2269 parameters->setArray("inputSources"_s, WTFMove(inputSources));
2270 RefPtr<JSON::Array> steps = JSON::Array::create();
2271 for (const auto& tick : actionsByTick) {
2272 RefPtr<JSON::Array> states = JSON::Array::create();
2273 for (const auto& action : tick) {
2274 RefPtr<JSON::Object> state = JSON::Object::create();
2275 auto& currentState = inputSourceState(action.id);
2276 state->setString("sourceId"_s, action.id);
2277 switch (action.type) {
2278 case Action::Type::None:
2279 state->setDouble("duration"_s, action.duration.value());
2280 break;
2281 case Action::Type::Pointer: {
2282 switch (action.subtype) {
2283 case Action::Subtype::PointerUp:
2284 currentState.pressedButton = WTF::nullopt;
2285 break;
2286 case Action::Subtype::PointerDown:
2287 currentState.pressedButton = action.button.value();
2288 break;
2289 case Action::Subtype::PointerMove: {
2290 state->setString("origin"_s, automationOriginType(action.origin->type));
2291 RefPtr<JSON::Object> location = JSON::Object::create();
2292 location->setInteger("x"_s, action.x.value());
2293 location->setInteger("y"_s, action.y.value());
2294 state->setObject("location"_s, WTFMove(location));
2295 if (action.origin->type == PointerOrigin::Type::Element)
2296 state->setString("nodeHandle"_s, action.origin->elementID.value());
2297 FALLTHROUGH;
2298 }
2299 case Action::Subtype::Pause:
2300 if (action.duration)
2301 state->setDouble("duration"_s, action.duration.value());
2302 break;
2303 case Action::Subtype::PointerCancel:
2304 currentState.pressedButton = WTF::nullopt;
2305 break;
2306 case Action::Subtype::KeyUp:
2307 case Action::Subtype::KeyDown:
2308 ASSERT_NOT_REACHED();
2309 }
2310 if (currentState.pressedButton)
2311 state->setString("pressedButton"_s, mouseButtonForAutomation(currentState.pressedButton.value()));
2312 break;
2313 }
2314 case Action::Type::Key:
2315 switch (action.subtype) {
2316 case Action::Subtype::KeyUp:
2317 if (currentState.pressedVirtualKey)
2318 currentState.pressedVirtualKey = WTF::nullopt;
2319 else
2320 currentState.pressedKey = WTF::nullopt;
2321 break;
2322 case Action::Subtype::KeyDown: {
2323 KeyModifier modifier;
2324 auto virtualKey = virtualKeyForKey(action.key.value()[0], modifier);
2325 if (!virtualKey.isNull())
2326 currentState.pressedVirtualKey = virtualKey;
2327 else
2328 currentState.pressedKey = action.key.value();
2329 break;
2330 }
2331 case Action::Subtype::Pause:
2332 if (action.duration)
2333 state->setDouble("duration"_s, action.duration.value());
2334 break;
2335 case Action::Subtype::PointerUp:
2336 case Action::Subtype::PointerDown:
2337 case Action::Subtype::PointerMove:
2338 case Action::Subtype::PointerCancel:
2339 ASSERT_NOT_REACHED();
2340 }
2341 if (currentState.pressedKey)
2342 state->setString("pressedCharKey"_s, currentState.pressedKey.value());
2343 if (currentState.pressedVirtualKey) {
2344 // FIXME: support parsing and tracking multiple virtual keys.
2345 Ref<JSON::Array> virtualKeys = JSON::Array::create();
2346 virtualKeys->pushString(currentState.pressedVirtualKey.value());
2347 state->setArray("pressedVirtualKeys"_s, WTFMove(virtualKeys));
2348 }
2349 break;
2350 }
2351 states->pushObject(WTFMove(state));
2352 }
2353 RefPtr<JSON::Object> stepStates = JSON::Object::create();
2354 stepStates->setArray("states"_s, WTFMove(states));
2355 steps->pushObject(WTFMove(stepStates));
2356 }
2357
2358 parameters->setArray("steps"_s, WTFMove(steps));
2359 m_host->sendCommandToBackend("performInteractionSequence"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)] (SessionHost::CommandResponse&& response) {
2360 if (response.isError) {
2361 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2362 return;
2363 }
2364 completionHandler(CommandResult::success());
2365 });
2366 });
2367}
2368
2369void Session::releaseActions(Function<void (CommandResult&&)>&& completionHandler)
2370{
2371 if (!m_toplevelBrowsingContext) {
2372 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2373 return;
2374 }
2375
2376 m_activeInputSources.clear();
2377 m_inputStateTable.clear();
2378
2379 RefPtr<JSON::Object> parameters = JSON::Object::create();
2380 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
2381 m_host->sendCommandToBackend("cancelInteractionSequence"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2382 if (response.isError) {
2383 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2384 return;
2385 }
2386 completionHandler(CommandResult::success());
2387 });
2388}
2389
2390void Session::dismissAlert(Function<void (CommandResult&&)>&& completionHandler)
2391{
2392 if (!m_toplevelBrowsingContext) {
2393 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2394 return;
2395 }
2396
2397 RefPtr<JSON::Object> parameters = JSON::Object::create();
2398 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2399 m_host->sendCommandToBackend("dismissCurrentJavaScriptDialog"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2400 if (response.isError) {
2401 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2402 return;
2403 }
2404 completionHandler(CommandResult::success());
2405 });
2406}
2407
2408void Session::acceptAlert(Function<void (CommandResult&&)>&& completionHandler)
2409{
2410 if (!m_toplevelBrowsingContext) {
2411 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2412 return;
2413 }
2414
2415 RefPtr<JSON::Object> parameters = JSON::Object::create();
2416 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2417 m_host->sendCommandToBackend("acceptCurrentJavaScriptDialog"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2418 if (response.isError) {
2419 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2420 return;
2421 }
2422 completionHandler(CommandResult::success());
2423 });
2424}
2425
2426void Session::getAlertText(Function<void (CommandResult&&)>&& completionHandler)
2427{
2428 if (!m_toplevelBrowsingContext) {
2429 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2430 return;
2431 }
2432
2433 RefPtr<JSON::Object> parameters = JSON::Object::create();
2434 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2435 m_host->sendCommandToBackend("messageOfCurrentJavaScriptDialog"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2436 if (response.isError || !response.responseObject) {
2437 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2438 return;
2439 }
2440 String valueString;
2441 if (!response.responseObject->getString("message"_s, valueString)) {
2442 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
2443 return;
2444 }
2445 completionHandler(CommandResult::success(JSON::Value::create(valueString)));
2446 });
2447}
2448
2449void Session::sendAlertText(const String& text, Function<void (CommandResult&&)>&& completionHandler)
2450{
2451 if (!m_toplevelBrowsingContext) {
2452 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2453 return;
2454 }
2455
2456 RefPtr<JSON::Object> parameters = JSON::Object::create();
2457 parameters->setString("browsingContextHandle"_s, m_toplevelBrowsingContext.value());
2458 parameters->setString("userInput"_s, text);
2459 m_host->sendCommandToBackend("setUserInputForCurrentJavaScriptPrompt"_s, WTFMove(parameters), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) {
2460 if (response.isError) {
2461 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2462 return;
2463 }
2464 completionHandler(CommandResult::success());
2465 });
2466}
2467
2468void Session::takeScreenshot(Optional<String> elementID, Optional<bool> scrollIntoView, Function<void (CommandResult&&)>&& completionHandler)
2469{
2470 if (!m_toplevelBrowsingContext) {
2471 completionHandler(CommandResult::fail(CommandResult::ErrorCode::NoSuchWindow));
2472 return;
2473 }
2474
2475 handleUserPrompts([this, protectedThis = makeRef(*this), elementID, scrollIntoView, completionHandler = WTFMove(completionHandler)](CommandResult&& result) mutable {
2476 if (result.isError()) {
2477 completionHandler(WTFMove(result));
2478 return;
2479 }
2480 RefPtr<JSON::Object> parameters = JSON::Object::create();
2481 parameters->setString("handle"_s, m_toplevelBrowsingContext.value());
2482 if (m_currentBrowsingContext)
2483 parameters->setString("frameHandle"_s, m_currentBrowsingContext.value());
2484 if (elementID)
2485 parameters->setString("nodeHandle"_s, elementID.value());
2486 else
2487 parameters->setBoolean("clipToViewport"_s, true);
2488 if (scrollIntoView.valueOr(false))
2489 parameters->setBoolean("scrollIntoViewIfNeeded"_s, true);
2490 m_host->sendCommandToBackend("takeScreenshot"_s, WTFMove(parameters), [protectedThis = protectedThis.copyRef(), completionHandler = WTFMove(completionHandler)](SessionHost::CommandResponse&& response) mutable {
2491 if (response.isError || !response.responseObject) {
2492 completionHandler(CommandResult::fail(WTFMove(response.responseObject)));
2493 return;
2494 }
2495 String data;
2496 if (!response.responseObject->getString("data"_s, data)) {
2497 completionHandler(CommandResult::fail(CommandResult::ErrorCode::UnknownError));
2498 return;
2499 }
2500 completionHandler(CommandResult::success(JSON::Value::create(data)));
2501 });
2502 });
2503}
2504
2505} // namespace WebDriver
2506