dd978b07e79deb959e93304474aa0585c9592e8c
[WebKit-https.git] / Source / WebKit / WebProcess / Automation / WebAutomationSessionProxy.cpp
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
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 "WebAutomationSessionProxy.h"
28
29 #include "AutomationProtocolObjects.h"
30 #include "CoordinateSystem.h"
31 #include "WebAutomationSessionMessages.h"
32 #include "WebAutomationSessionProxyMessages.h"
33 #include "WebAutomationSessionProxyScriptSource.h"
34 #include "WebCoreArgumentCoders.h"
35 #include "WebFrame.h"
36 #include "WebImage.h"
37 #include "WebPage.h"
38 #include "WebProcess.h"
39 #include <JavaScriptCore/APICast.h>
40 #include <JavaScriptCore/JSObject.h>
41 #include <JavaScriptCore/JSRetainPtr.h>
42 #include <JavaScriptCore/JSStringRefPrivate.h>
43 #include <JavaScriptCore/OpaqueJSString.h>
44 #include <WebCore/CookieJar.h>
45 #include <WebCore/DOMRect.h>
46 #include <WebCore/DOMRectList.h>
47 #include <WebCore/DOMWindow.h>
48 #include <WebCore/Frame.h>
49 #include <WebCore/FrameTree.h>
50 #include <WebCore/FrameView.h>
51 #include <WebCore/HTMLFrameElementBase.h>
52 #include <WebCore/HTMLOptGroupElement.h>
53 #include <WebCore/HTMLOptionElement.h>
54 #include <WebCore/HTMLSelectElement.h>
55 #include <WebCore/JSElement.h>
56 #include <WebCore/RenderElement.h>
57 #include <wtf/UUID.h>
58
59 #if ENABLE(DATALIST_ELEMENT)
60 #include <WebCore/HTMLDataListElement.h>
61 #endif
62
63 namespace WebKit {
64
65 template <typename T>
66 static JSObjectRef toJSArray(JSContextRef context, const Vector<T>& data, JSValueRef (*converter)(JSContextRef, const T&), JSValueRef* exception)
67 {
68     ASSERT_ARG(converter, converter);
69
70     if (data.isEmpty())
71         return JSObjectMakeArray(context, 0, nullptr, exception);
72
73     Vector<JSValueRef, 8> convertedData;
74     convertedData.reserveCapacity(data.size());
75
76     for (auto& originalValue : data) {
77         JSValueRef convertedValue = converter(context, originalValue);
78         JSValueProtect(context, convertedValue);
79         convertedData.uncheckedAppend(convertedValue);
80     }
81
82     JSObjectRef array = JSObjectMakeArray(context, convertedData.size(), convertedData.data(), exception);
83
84     for (auto& convertedValue : convertedData)
85         JSValueUnprotect(context, convertedValue);
86
87     return array;
88 }
89
90 static inline JSRetainPtr<JSStringRef> toJSString(const String& string)
91 {
92     return JSRetainPtr<JSStringRef>(Adopt, OpaqueJSString::create(string).leakRef());
93 }
94
95 static inline JSValueRef toJSValue(JSContextRef context, const String& string)
96 {
97     return JSValueMakeString(context, toJSString(string).get());
98 }
99
100 static inline JSValueRef callPropertyFunction(JSContextRef context, JSObjectRef object, const String& propertyName, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
101 {
102     ASSERT_ARG(object, object);
103     ASSERT_ARG(object, JSValueIsObject(context, object));
104
105     JSObjectRef function = const_cast<JSObjectRef>(JSObjectGetProperty(context, object, toJSString(propertyName).get(), exception));
106     ASSERT(JSObjectIsFunction(context, function));
107
108     return JSObjectCallAsFunction(context, function, object, argumentCount, arguments, exception);
109 }
110
111 WebAutomationSessionProxy::WebAutomationSessionProxy(const String& sessionIdentifier)
112     : m_sessionIdentifier(sessionIdentifier)
113 {
114     WebProcess::singleton().addMessageReceiver(Messages::WebAutomationSessionProxy::messageReceiverName(), *this);
115 }
116
117 WebAutomationSessionProxy::~WebAutomationSessionProxy()
118 {
119     WebProcess::singleton().removeMessageReceiver(Messages::WebAutomationSessionProxy::messageReceiverName());
120 }
121
122 static JSValueRef evaluate(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
123 {
124     ASSERT_ARG(argumentCount, argumentCount == 1);
125     ASSERT_ARG(arguments, JSValueIsString(context, arguments[0]));
126
127     if (argumentCount != 1)
128         return JSValueMakeUndefined(context);
129
130     JSRetainPtr<JSStringRef> script(Adopt, JSValueToStringCopy(context, arguments[0], exception));
131     return JSEvaluateScript(context, script.get(), nullptr, nullptr, 0, exception);
132 }
133
134 static JSValueRef createUUID(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
135 {
136     return toJSValue(context, createCanonicalUUIDString().convertToASCIIUppercase());
137 }
138
139 static JSValueRef evaluateJavaScriptCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
140 {
141     ASSERT_ARG(argumentCount, argumentCount == 4);
142     ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[0]));
143     ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[1]));
144     ASSERT_ARG(arguments, JSValueIsString(context, arguments[2]));
145     ASSERT_ARG(arguments, JSValueIsBoolean(context, arguments[3]));
146
147     auto automationSessionProxy = WebProcess::singleton().automationSessionProxy();
148     if (!automationSessionProxy)
149         return JSValueMakeUndefined(context);
150
151     uint64_t frameID = JSValueToNumber(context, arguments[0], exception);
152     uint64_t callbackID = JSValueToNumber(context, arguments[1], exception);
153     JSRetainPtr<JSStringRef> result(Adopt, JSValueToStringCopy(context, arguments[2], exception));
154
155     bool resultIsErrorName = JSValueToBoolean(context, arguments[3]);
156
157     if (resultIsErrorName) {
158         if (result->string() == "JavaScriptTimeout") {
159             String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptTimeout);
160             automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, String(), errorType);
161         } else {
162             ASSERT_NOT_REACHED();
163             String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InternalError);
164             automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, String(), errorType);
165         }
166     } else
167         automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, result->string(), String());
168
169     return JSValueMakeUndefined(context);
170 }
171
172 JSObjectRef WebAutomationSessionProxy::scriptObjectForFrame(WebFrame& frame)
173 {
174     if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.get(frame.frameID()))
175         return scriptObject;
176
177     JSValueRef exception = nullptr;
178     JSGlobalContextRef context = frame.jsContext();
179
180     JSValueRef sessionIdentifier = toJSValue(context, m_sessionIdentifier);
181     JSObjectRef evaluateFunction = JSObjectMakeFunctionWithCallback(context, nullptr, evaluate);
182     JSObjectRef createUUIDFunction = JSObjectMakeFunctionWithCallback(context, nullptr, createUUID);
183
184     String script = StringImpl::createWithoutCopying(WebAutomationSessionProxyScriptSource, sizeof(WebAutomationSessionProxyScriptSource));
185
186     JSObjectRef scriptObjectFunction = const_cast<JSObjectRef>(JSEvaluateScript(context, toJSString(script).get(), nullptr, nullptr, 0, &exception));
187     ASSERT(JSValueIsObject(context, scriptObjectFunction));
188
189     JSValueRef arguments[] = { sessionIdentifier, evaluateFunction, createUUIDFunction };
190     JSObjectRef scriptObject = const_cast<JSObjectRef>(JSObjectCallAsFunction(context, scriptObjectFunction, nullptr, WTF_ARRAY_LENGTH(arguments), arguments, &exception));
191     ASSERT(JSValueIsObject(context, scriptObject));
192
193     JSValueProtect(context, scriptObject);
194     m_webFrameScriptObjectMap.add(frame.frameID(), scriptObject);
195
196     return scriptObject;
197 }
198
199 WebCore::Element* WebAutomationSessionProxy::elementForNodeHandle(WebFrame& frame, const String& nodeHandle)
200 {
201     // Don't use scriptObjectForFrame() since we can assume if the script object
202     // does not exist, there are no nodes mapped to handles. Using scriptObjectForFrame()
203     // will make a new script object if it can't find one, preventing us from returning fast.
204     JSObjectRef scriptObject = m_webFrameScriptObjectMap.get(frame.frameID());
205     if (!scriptObject)
206         return nullptr;
207
208     JSGlobalContextRef context = frame.jsContext();
209
210     JSValueRef functionArguments[] = {
211         toJSValue(context, nodeHandle)
212     };
213
214     JSValueRef result = callPropertyFunction(context, scriptObject, "nodeForIdentifier"_s, WTF_ARRAY_LENGTH(functionArguments), functionArguments, nullptr);
215     JSObjectRef element = JSValueToObject(context, result, nullptr);
216     if (!element)
217         return nullptr;
218
219     auto elementWrapper = JSC::jsDynamicCast<WebCore::JSElement*>(toJS(context)->vm(), toJS(element));
220     if (!elementWrapper)
221         return nullptr;
222
223     return &elementWrapper->wrapped();
224 }
225
226 void WebAutomationSessionProxy::didClearWindowObjectForFrame(WebFrame& frame)
227 {
228     uint64_t frameID = frame.frameID();
229     if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.take(frameID))
230         JSValueUnprotect(frame.jsContext(), scriptObject);
231
232     String errorMessage = "Callback was not called before the unload event."_s;
233     String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
234
235     auto pendingFrameCallbacks = m_webFramePendingEvaluateJavaScriptCallbacksMap.take(frameID);
236     for (uint64_t callbackID : pendingFrameCallbacks)
237         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, errorMessage, errorType), 0);
238 }
239
240 void WebAutomationSessionProxy::evaluateJavaScriptFunction(uint64_t pageID, uint64_t frameID, const String& function, Vector<String> arguments, bool expectsImplicitCallbackArgument, int callbackTimeout, uint64_t callbackID)
241 {
242     WebPage* page = WebProcess::singleton().webPage(pageID);
243     if (!page) {
244         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, { },
245             Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound)), 0);
246         return;
247     }
248     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
249     if (!frame) {
250         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, { },
251             Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound)), 0);
252         return;
253     }
254
255     JSObjectRef scriptObject = scriptObjectForFrame(*frame);
256     ASSERT(scriptObject);
257
258     frameID = frame->frameID();
259     JSValueRef exception = nullptr;
260     JSGlobalContextRef context = frame->jsContext();
261
262     if (expectsImplicitCallbackArgument) {
263         auto result = m_webFramePendingEvaluateJavaScriptCallbacksMap.add(frameID, Vector<uint64_t>());
264         result.iterator->value.append(callbackID);
265     }
266
267     JSValueRef functionArguments[] = {
268         toJSValue(context, function),
269         toJSArray(context, arguments, toJSValue, &exception),
270         JSValueMakeBoolean(context, expectsImplicitCallbackArgument),
271         JSValueMakeNumber(context, frameID),
272         JSValueMakeNumber(context, callbackID),
273         JSObjectMakeFunctionWithCallback(context, nullptr, evaluateJavaScriptCallback),
274         JSValueMakeNumber(context, callbackTimeout)
275     };
276
277     {
278         WebCore::UserGestureIndicator gestureIndicator(WebCore::ProcessingUserGesture, frame->coreFrame()->document());
279         callPropertyFunction(context, scriptObject, "evaluateJavaScriptFunction"_s, WTF_ARRAY_LENGTH(functionArguments), functionArguments, &exception);
280     }
281
282     if (!exception)
283         return;
284
285     String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
286
287     JSRetainPtr<JSStringRef> exceptionMessage;
288     if (JSValueIsObject(context, exception)) {
289         JSValueRef nameValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), toJSString("name"_s).get(), nullptr);
290         JSRetainPtr<JSStringRef> exceptionName(Adopt, JSValueToStringCopy(context, nameValue, nullptr));
291         if (exceptionName->string() == "NodeNotFound")
292             errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
293         else if (exceptionName->string() == "InvalidElementState")
294             errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InvalidElementState);
295         else if (exceptionName->string() == "InvalidParameter")
296             errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InvalidParameter);
297         else if (exceptionName->string() == "InvalidSelector")
298             errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InvalidSelector);
299
300         JSValueRef messageValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), toJSString("message"_s).get(), nullptr);
301         exceptionMessage.adopt(JSValueToStringCopy(context, messageValue, nullptr));
302     } else
303         exceptionMessage.adopt(JSValueToStringCopy(context, exception, nullptr));
304
305     didEvaluateJavaScriptFunction(frameID, callbackID, exceptionMessage->string(), errorType);
306 }
307
308 void WebAutomationSessionProxy::didEvaluateJavaScriptFunction(uint64_t frameID, uint64_t callbackID, const String& result, const String& errorType)
309 {
310     auto findResult = m_webFramePendingEvaluateJavaScriptCallbacksMap.find(frameID);
311     if (findResult != m_webFramePendingEvaluateJavaScriptCallbacksMap.end()) {
312         findResult->value.removeFirst(callbackID);
313         ASSERT(!findResult->value.contains(callbackID));
314         if (findResult->value.isEmpty())
315             m_webFramePendingEvaluateJavaScriptCallbacksMap.remove(findResult);
316     }
317
318     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, result, errorType), 0);
319 }
320
321 void WebAutomationSessionProxy::resolveChildFrameWithOrdinal(uint64_t pageID, uint64_t frameID, uint32_t ordinal, uint64_t callbackID)
322 {
323     WebPage* page = WebProcess::singleton().webPage(pageID);
324     if (!page) {
325         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
326         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, windowNotFoundErrorType), 0);
327         return;
328     }
329
330     String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
331
332     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
333     if (!frame) {
334         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
335         return;
336     }
337
338     WebCore::Frame* coreFrame = frame->coreFrame();
339     if (!coreFrame) {
340         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
341         return;
342     }
343
344     WebCore::Frame* coreChildFrame = coreFrame->tree().scopedChild(ordinal);
345     if (!coreChildFrame) {
346         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
347         return;
348     }
349
350     WebFrame* childFrame = WebFrame::fromCoreFrame(*coreChildFrame);
351     if (!childFrame) {
352         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
353         return;
354     }
355
356     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, childFrame->frameID(), String()), 0);
357 }
358
359 void WebAutomationSessionProxy::resolveChildFrameWithNodeHandle(uint64_t pageID, uint64_t frameID, const String& nodeHandle, uint64_t callbackID)
360 {
361     WebPage* page = WebProcess::singleton().webPage(pageID);
362     if (!page) {
363         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
364         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, windowNotFoundErrorType), 0);
365         return;
366     }
367
368     String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
369
370     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
371     if (!frame) {
372         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
373         return;
374     }
375
376     WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle);
377     if (!coreElement || !coreElement->isFrameElementBase()) {
378         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
379         return;
380     }
381
382     WebCore::Frame* coreFrameFromElement = static_cast<WebCore::HTMLFrameElementBase*>(coreElement)->contentFrame();
383     if (!coreFrameFromElement) {
384         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
385         return;
386     }
387
388     WebFrame* frameFromElement = WebFrame::fromCoreFrame(*coreFrameFromElement);
389     if (!frameFromElement) {
390         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
391         return;
392     }
393
394     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, frameFromElement->frameID(), String()), 0);
395 }
396
397 void WebAutomationSessionProxy::resolveChildFrameWithName(uint64_t pageID, uint64_t frameID, const String& name, uint64_t callbackID)
398 {
399     WebPage* page = WebProcess::singleton().webPage(pageID);
400     if (!page) {
401         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
402         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, windowNotFoundErrorType), 0);
403         return;
404     }
405
406     String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
407
408     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
409     if (!frame) {
410         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
411         return;
412     }
413
414     WebCore::Frame* coreFrame = frame->coreFrame();
415     if (!coreFrame) {
416         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
417         return;
418     }
419
420     WebCore::Frame* coreChildFrame = coreFrame->tree().scopedChild(name);
421     if (!coreChildFrame) {
422         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
423         return;
424     }
425
426     WebFrame* childFrame = WebFrame::fromCoreFrame(*coreChildFrame);
427     if (!childFrame) {
428         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0);
429         return;
430     }
431
432     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, childFrame->frameID(), String()), 0);
433 }
434
435 void WebAutomationSessionProxy::resolveParentFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID)
436 {
437     WebPage* page = WebProcess::singleton().webPage(pageID);
438     if (!page) {
439         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
440         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, 0, windowNotFoundErrorType), 0);
441         return;
442     }
443
444     String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
445
446     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
447     if (!frame) {
448         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, 0, frameNotFoundErrorType), 0);
449         return;
450     }
451
452     WebFrame* parentFrame = frame->parentFrame();
453     if (!parentFrame) {
454         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, 0, frameNotFoundErrorType), 0);
455         return;
456     }
457
458     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, parentFrame->frameID(), String()), 0);
459 }
460
461 void WebAutomationSessionProxy::focusFrame(uint64_t pageID, uint64_t frameID)
462 {
463     WebPage* page = WebProcess::singleton().webPage(pageID);
464     if (!page)
465         return;
466
467     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
468     if (!frame)
469         return;
470
471     WebCore::Frame* coreFrame = frame->coreFrame();
472     if (!coreFrame)
473         return;
474
475     WebCore::Document* coreDocument = coreFrame->document();
476     if (!coreDocument)
477         return;
478
479     WebCore::DOMWindow* coreDOMWindow = coreDocument->domWindow();
480     if (!coreDOMWindow)
481         return;
482
483     coreDOMWindow->focus(true);
484 }
485
486 static std::optional<WebCore::FloatPoint> elementInViewClientCenterPoint(WebCore::Element& element, bool& isObscured)
487 {
488     // §11.1 Element Interactability.
489     // https://www.w3.org/TR/webdriver/#dfn-in-view-center-point
490     auto* clientRect = element.getClientRects()->item(0);
491     if (!clientRect)
492         return std::nullopt;
493
494     auto clientCenterPoint = WebCore::FloatPoint::narrowPrecision(0.5 * (clientRect->left() + clientRect->right()), 0.5 * (clientRect->top() + clientRect->bottom()));
495     auto elementList = element.treeScope().elementsFromPoint(clientCenterPoint.x(), clientCenterPoint.y());
496     if (elementList.isEmpty()) {
497         // An element is obscured if the pointer-interactable paint tree at its center point is empty,
498         // or the first element in this tree is not an inclusive descendant of itself.
499         // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-obscured
500         isObscured = true;
501         return clientCenterPoint;
502     }
503
504     auto index = elementList.findMatching([&element] (auto& item) { return item.get() == &element; });
505     if (index == notFound)
506         return std::nullopt;
507
508     if (index) {
509         // Element is not the first one in the list.
510         auto firstElement = elementList[0];
511         isObscured = !firstElement->isDescendantOf(element);
512     }
513
514     return clientCenterPoint;
515 }
516
517 static WebCore::Element* containerElementForElement(WebCore::Element& element)
518 {
519     // §13. Element State.
520     // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-container.
521     if (is<WebCore::HTMLOptionElement>(element)) {
522         auto& optionElement = downcast<WebCore::HTMLOptionElement>(element);
523 #if ENABLE(DATALIST_ELEMENT)
524         if (auto* parentElement = optionElement.ownerDataListElement())
525             return parentElement;
526 #endif
527         if (auto* parentElement = optionElement.ownerSelectElement())
528             return parentElement;
529
530         return nullptr;
531     }
532
533     if (is<WebCore::HTMLOptGroupElement>(element)) {
534         if (auto* parentElement = downcast<WebCore::HTMLOptGroupElement>(element).ownerSelectElement())
535             return parentElement;
536
537         return nullptr;
538     }
539
540     return &element;
541 }
542
543 void WebAutomationSessionProxy::computeElementLayout(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, CoordinateSystem coordinateSystem, uint64_t callbackID)
544 {
545     WebPage* page = WebProcess::singleton().webPage(pageID);
546     if (!page) {
547         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
548         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, { }, std::nullopt, false, windowNotFoundErrorType), 0);
549         return;
550     }
551
552     String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
553     String nodeNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
554     String notImplementedErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NotImplemented);
555
556     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
557     if (!frame || !frame->coreFrame() || !frame->coreFrame()->view()) {
558         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, { }, std::nullopt, false, frameNotFoundErrorType), 0);
559         return;
560     }
561
562     WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle);
563     if (!coreElement) {
564         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, { }, std::nullopt, false, nodeNotFoundErrorType), 0);
565         return;
566     }
567
568     auto* containerElement = containerElementForElement(*coreElement);
569     if (scrollIntoViewIfNeeded && containerElement) {
570         // §14.1 Element Click. Step 4. Scroll into view the element’s container.
571         // https://w3c.github.io/webdriver/webdriver-spec.html#element-click
572         containerElement->scrollIntoViewIfNeeded(false);
573         // FIXME: Wait in an implementation-specific way up to the session implicit wait timeout for the element to become in view.
574     }
575
576     if (coordinateSystem == CoordinateSystem::VisualViewport) {
577         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, { }, std::nullopt, false, notImplementedErrorType), 0);
578         return;
579     }
580
581     WebCore::FrameView* frameView = frame->coreFrame()->view();
582     WebCore::FrameView* mainView = frame->coreFrame()->mainFrame().view();
583     WebCore::IntRect frameElementBounds = roundedIntRect(coreElement->boundingClientRect());
584     WebCore::IntRect rootElementBounds = mainView->rootViewToContents(frameView->contentsToRootView(frameElementBounds));
585     WebCore::IntRect resultElementBounds;
586     switch (coordinateSystem) {
587     case CoordinateSystem::Page:
588         resultElementBounds = WebCore::IntRect(mainView->clientToDocumentRect(WebCore::FloatRect(rootElementBounds)));
589         break;
590     case CoordinateSystem::LayoutViewport:
591         // The element bounds are already in client coordinates.
592         resultElementBounds = rootElementBounds;
593         break;
594     case CoordinateSystem::VisualViewport:
595         ASSERT_NOT_REACHED();
596         break;
597     }
598
599     std::optional<WebCore::IntPoint> resultInViewCenterPoint;
600     bool isObscured = false;
601     if (containerElement) {
602         std::optional<WebCore::FloatPoint> frameInViewCenterPoint = elementInViewClientCenterPoint(*containerElement, isObscured);
603         if (frameInViewCenterPoint.has_value()) {
604             WebCore::IntPoint rootInViewCenterPoint = mainView->rootViewToContents(frameView->contentsToRootView(WebCore::IntPoint(frameInViewCenterPoint.value())));
605             switch (coordinateSystem) {
606             case CoordinateSystem::Page:
607                 resultInViewCenterPoint = WebCore::IntPoint(mainView->clientToDocumentPoint(rootInViewCenterPoint));
608                 break;
609             case CoordinateSystem::LayoutViewport:
610                 // The point is already in client coordinates.
611                 resultInViewCenterPoint = rootInViewCenterPoint;
612                 break;
613             case CoordinateSystem::VisualViewport:
614                 ASSERT_NOT_REACHED();
615                 break;
616             }
617         }
618     }
619
620     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, resultElementBounds, resultInViewCenterPoint, isObscured, String()), 0);
621 }
622
623 void WebAutomationSessionProxy::selectOptionElement(uint64_t pageID, uint64_t frameID, String nodeHandle, uint64_t callbackID)
624 {
625     WebPage* page = WebProcess::singleton().webPage(pageID);
626     if (!page) {
627         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
628         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, windowNotFoundErrorType), 0);
629         return;
630     }
631
632     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
633     if (!frame || !frame->coreFrame() || !frame->coreFrame()->view()) {
634         String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
635         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, frameNotFoundErrorType), 0);
636         return;
637     }
638
639     WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle);
640     if (!coreElement || (!is<WebCore::HTMLOptionElement>(coreElement) && !is<WebCore::HTMLOptGroupElement>(coreElement))) {
641         String nodeNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
642         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, nodeNotFoundErrorType), 0);
643         return;
644     }
645
646     String elementNotInteractableErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::ElementNotInteractable);
647     if (is<WebCore::HTMLOptGroupElement>(coreElement)) {
648         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, elementNotInteractableErrorType), 0);
649         return;
650     }
651
652     auto& optionElement = downcast<WebCore::HTMLOptionElement>(*coreElement);
653     auto* selectElement = optionElement.ownerSelectElement();
654     if (!selectElement) {
655         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, elementNotInteractableErrorType), 0);
656         return;
657     }
658
659     if (!selectElement->isDisabledFormControl() && !optionElement.isDisabledFormControl()) {
660         // FIXME: According to the spec we should fire mouse over, move and down events, then input and change, and finally mouse up and click.
661         // optionSelectedByUser() will fire input and change events if needed, but all other events should be fired manually here.
662         selectElement->optionSelectedByUser(optionElement.index(), true, selectElement->multiple());
663     }
664     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidSelectOptionElement(callbackID, { }), 0);
665 }
666
667 static WebCore::IntRect snapshotRectForScreenshot(WebPage& page, WebCore::Element* element, bool clipToViewport)
668 {
669     auto* frameView = page.mainFrameView();
670     if (!frameView)
671         return { };
672
673     if (element) {
674         if (!element->renderer())
675             return { };
676
677         WebCore::LayoutRect topLevelRect;
678         WebCore::IntRect elementRect = WebCore::snappedIntRect(element->renderer()->paintingRootRect(topLevelRect));
679         if (clipToViewport)
680             elementRect.intersect(frameView->visibleContentRect());
681
682         return elementRect;
683     }
684
685     if (auto* frameView = page.mainFrameView())
686         return clipToViewport ? frameView->visibleContentRect() : WebCore::IntRect(WebCore::IntPoint(0, 0), frameView->contentsSize());
687
688     return { };
689 }
690
691 void WebAutomationSessionProxy::takeScreenshot(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool clipToViewport, uint64_t callbackID)
692 {
693     ShareableBitmap::Handle handle;
694
695     WebPage* page = WebProcess::singleton().webPage(pageID);
696     if (!page) {
697         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
698         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, windowNotFoundErrorType), 0);
699         return;
700     }
701
702     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
703     if (!frame || !frame->coreFrame()) {
704         String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
705         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, frameNotFoundErrorType), 0);
706         return;
707     }
708
709     WebCore::Element* coreElement = nullptr;
710     if (!nodeHandle.isEmpty()) {
711         coreElement = elementForNodeHandle(*frame, nodeHandle);
712         if (!coreElement) {
713             String nodeNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
714             WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, nodeNotFoundErrorType), 0);
715             return;
716         }
717     }
718
719     if (coreElement && scrollIntoViewIfNeeded)
720         coreElement->scrollIntoViewIfNeeded(false);
721
722     String screenshotErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::ScreenshotError);
723     WebCore::IntRect snapshotRect = snapshotRectForScreenshot(*page, coreElement, clipToViewport);
724     if (snapshotRect.isEmpty()) {
725         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, screenshotErrorType), 0);
726         return;
727     }
728
729     RefPtr<WebImage> image = page->scaledSnapshotWithOptions(snapshotRect, 1, SnapshotOptionsShareable);
730     if (!image) {
731         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, screenshotErrorType), 0);
732         return;
733     }
734
735     image->bitmap().createHandle(handle, SharedMemory::Protection::ReadOnly);
736     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, { }), 0);
737 }
738
739 void WebAutomationSessionProxy::getCookiesForFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID)
740 {
741     WebPage* page = WebProcess::singleton().webPage(pageID);
742     if (!page) {
743         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
744         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidGetCookiesForFrame(callbackID, Vector<WebCore::Cookie>(), windowNotFoundErrorType), 0);
745         return;
746     }
747
748     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
749     if (!frame || !frame->coreFrame() || !frame->coreFrame()->document()) {
750         String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
751         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidGetCookiesForFrame(callbackID, Vector<WebCore::Cookie>(), frameNotFoundErrorType), 0);
752         return;
753     }
754
755     // This returns the same list of cookies as when evaluating `document.cookies` in JavaScript.
756     auto& document = *frame->coreFrame()->document();
757     Vector<WebCore::Cookie> foundCookies;
758     if (!document.cookieURL().isEmpty())
759         WebCore::getRawCookies(document, document.cookieURL(), foundCookies);
760
761     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidGetCookiesForFrame(callbackID, foundCookies, String()), 0);
762 }
763
764 void WebAutomationSessionProxy::deleteCookie(uint64_t pageID, uint64_t frameID, String cookieName, uint64_t callbackID)
765 {
766     WebPage* page = WebProcess::singleton().webPage(pageID);
767     if (!page) {
768         String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound);
769         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidDeleteCookie(callbackID, windowNotFoundErrorType), 0);
770         return;
771     }
772
773     WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame();
774     if (!frame || !frame->coreFrame() || !frame->coreFrame()->document()) {
775         String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound);
776         WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidDeleteCookie(callbackID, frameNotFoundErrorType), 0);
777         return;
778     }
779
780     auto& document = *frame->coreFrame()->document();
781     WebCore::deleteCookie(document, document.cookieURL(), cookieName);
782
783     WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidDeleteCookie(callbackID, String()), 0);
784 }
785
786 } // namespace WebKit