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
62namespace WebKit {
63
64template <typename T>
65static 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
89static inline JSValueRef toJSValue(JSContextRef context, const String& string)
90{
91 return JSValueMakeString(context, OpaqueJSString::tryCreate(string).get());
92}
93
94static 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
105WebAutomationSessionProxy::WebAutomationSessionProxy(const String& sessionIdentifier)
106 : m_sessionIdentifier(sessionIdentifier)
107{
108 WebProcess::singleton().addMessageReceiver(Messages::WebAutomationSessionProxy::messageReceiverName(), *this);
109}
110
111WebAutomationSessionProxy::~WebAutomationSessionProxy()
112{
113 WebProcess::singleton().removeMessageReceiver(Messages::WebAutomationSessionProxy::messageReceiverName());
114}
115
116static 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
128static 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
133static 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
166JSObjectRef 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
193WebCore::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
220void 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
234void WebAutomationSessionProxy::evaluateJavaScriptFunction(WebCore::PageIdentifier 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
302void 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
315void WebAutomationSessionProxy::resolveChildFrameWithOrdinal(WebCore::PageIdentifier 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
353void WebAutomationSessionProxy::resolveChildFrameWithNodeHandle(WebCore::PageIdentifier 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
391void WebAutomationSessionProxy::resolveChildFrameWithName(WebCore::PageIdentifier 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
429void WebAutomationSessionProxy::resolveParentFrame(WebCore::PageIdentifier 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
455void WebAutomationSessionProxy::focusFrame(WebCore::PageIdentifier 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
480static 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
506static WebCore::FloatRect convertRectFromFrameClientToRootView(WebCore::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
518static WebCore::FloatPoint convertPointFromFrameClientToRootView(WebCore::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
530void WebAutomationSessionProxy::computeElementLayout(WebCore::PageIdentifier 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 = WebCore::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
638void WebAutomationSessionProxy::selectOptionElement(WebCore::PageIdentifier 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
682static 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
706void WebAutomationSessionProxy::takeScreenshot(WebCore::PageIdentifier 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
754void WebAutomationSessionProxy::getCookiesForFrame(WebCore::PageIdentifier 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
779void WebAutomationSessionProxy::deleteCookie(WebCore::PageIdentifier 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
802