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