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(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 | |
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(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 | |
353 | void 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 | |
391 | void 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 | |
429 | void 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 | |
455 | void 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 | |
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(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 | |
518 | static 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 | |
530 | void 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 | |
638 | void 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 | |
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(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 | |
754 | void 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 | |
779 | void 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 | |