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