diff options
Diffstat (limited to 'Source/WebKit2/WebProcess/Automation')
4 files changed, 843 insertions, 0 deletions
diff --git a/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.cpp b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.cpp new file mode 100644 index 000000000..c4922d858 --- /dev/null +++ b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.cpp @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "WebAutomationSessionProxy.h" + +#include "AutomationProtocolObjects.h" +#include "WebAutomationSessionMessages.h" +#include "WebAutomationSessionProxyMessages.h" +#include "WebAutomationSessionProxyScriptSource.h" +#include "WebFrame.h" +#include "WebImage.h" +#include "WebPage.h" +#include "WebProcess.h" +#include <JavaScriptCore/APICast.h> +#include <JavaScriptCore/JSObject.h> +#include <JavaScriptCore/JSRetainPtr.h> +#include <JavaScriptCore/JSStringRefPrivate.h> +#include <JavaScriptCore/OpaqueJSString.h> +#include <WebCore/CookieJar.h> +#include <WebCore/DOMWindow.h> +#include <WebCore/Frame.h> +#include <WebCore/FrameTree.h> +#include <WebCore/FrameView.h> +#include <WebCore/HTMLFrameElementBase.h> +#include <WebCore/JSElement.h> +#include <WebCore/MainFrame.h> +#include <WebCore/UUID.h> + +namespace WebKit { + +template <typename T> +static JSObjectRef toJSArray(JSContextRef context, const Vector<T>& data, JSValueRef (*converter)(JSContextRef, const T&), JSValueRef* exception) +{ + ASSERT_ARG(converter, converter); + + if (data.isEmpty()) + return JSObjectMakeArray(context, 0, nullptr, exception); + + Vector<JSValueRef, 8> convertedData; + convertedData.reserveCapacity(data.size()); + + for (auto& originalValue : data) { + JSValueRef convertedValue = converter(context, originalValue); + JSValueProtect(context, convertedValue); + convertedData.uncheckedAppend(convertedValue); + } + + JSObjectRef array = JSObjectMakeArray(context, convertedData.size(), convertedData.data(), exception); + + for (auto& convertedValue : convertedData) + JSValueUnprotect(context, convertedValue); + + return array; +} + +static inline JSRetainPtr<JSStringRef> toJSString(const String& string) +{ + return JSRetainPtr<JSStringRef>(Adopt, OpaqueJSString::create(string).leakRef()); +} + +static inline JSValueRef toJSValue(JSContextRef context, const String& string) +{ + return JSValueMakeString(context, toJSString(string).get()); +} + +static inline JSValueRef callPropertyFunction(JSContextRef context, JSObjectRef object, const String& propertyName, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + ASSERT_ARG(object, object); + ASSERT_ARG(object, JSValueIsObject(context, object)); + + JSObjectRef function = const_cast<JSObjectRef>(JSObjectGetProperty(context, object, toJSString(propertyName).get(), exception)); + ASSERT(JSObjectIsFunction(context, function)); + + return JSObjectCallAsFunction(context, function, object, argumentCount, arguments, exception); +} + +WebAutomationSessionProxy::WebAutomationSessionProxy(const String& sessionIdentifier) + : m_sessionIdentifier(sessionIdentifier) +{ + WebProcess::singleton().addMessageReceiver(Messages::WebAutomationSessionProxy::messageReceiverName(), *this); +} + +WebAutomationSessionProxy::~WebAutomationSessionProxy() +{ + WebProcess::singleton().removeMessageReceiver(Messages::WebAutomationSessionProxy::messageReceiverName()); +} + +static JSValueRef evaluate(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + ASSERT_ARG(argumentCount, argumentCount == 1); + ASSERT_ARG(arguments, JSValueIsString(context, arguments[0])); + + if (argumentCount != 1) + return JSValueMakeUndefined(context); + + JSRetainPtr<JSStringRef> script(Adopt, JSValueToStringCopy(context, arguments[0], exception)); + return JSEvaluateScript(context, script.get(), nullptr, nullptr, 0, exception); +} + +static JSValueRef createUUID(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + return toJSValue(context, WebCore::createCanonicalUUIDString().convertToASCIIUppercase()); +} + +static JSValueRef evaluateJavaScriptCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + ASSERT_ARG(argumentCount, argumentCount == 4); + ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[0])); + ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[1])); + ASSERT_ARG(arguments, JSValueIsString(context, arguments[2])); + ASSERT_ARG(arguments, JSValueIsBoolean(context, arguments[3])); + + auto automationSessionProxy = WebProcess::singleton().automationSessionProxy(); + if (!automationSessionProxy) + return JSValueMakeUndefined(context); + + uint64_t frameID = JSValueToNumber(context, arguments[0], exception); + uint64_t callbackID = JSValueToNumber(context, arguments[1], exception); + JSRetainPtr<JSStringRef> result(Adopt, JSValueToStringCopy(context, arguments[2], exception)); + + bool resultIsErrorName = JSValueToBoolean(context, arguments[3]); + + if (resultIsErrorName) { + if (result->string() == "JavaScriptTimeout") { + String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptTimeout); + automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, String(), errorType); + } else { + ASSERT_NOT_REACHED(); + String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InternalError); + automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, String(), errorType); + } + } else + automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, result->string(), String()); + + return JSValueMakeUndefined(context); +} + +JSObjectRef WebAutomationSessionProxy::scriptObjectForFrame(WebFrame& frame) +{ + if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.get(frame.frameID())) + return scriptObject; + + JSValueRef exception = nullptr; + JSGlobalContextRef context = frame.jsContext(); + + JSValueRef sessionIdentifier = toJSValue(context, m_sessionIdentifier); + JSObjectRef evaluateFunction = JSObjectMakeFunctionWithCallback(context, nullptr, evaluate); + JSObjectRef createUUIDFunction = JSObjectMakeFunctionWithCallback(context, nullptr, createUUID); + + String script = StringImpl::createWithoutCopying(WebAutomationSessionProxyScriptSource, sizeof(WebAutomationSessionProxyScriptSource)); + + JSObjectRef scriptObjectFunction = const_cast<JSObjectRef>(JSEvaluateScript(context, toJSString(script).get(), nullptr, nullptr, 0, &exception)); + ASSERT(JSValueIsObject(context, scriptObjectFunction)); + + JSValueRef arguments[] = { sessionIdentifier, evaluateFunction, createUUIDFunction }; + JSObjectRef scriptObject = const_cast<JSObjectRef>(JSObjectCallAsFunction(context, scriptObjectFunction, nullptr, WTF_ARRAY_LENGTH(arguments), arguments, &exception)); + ASSERT(JSValueIsObject(context, scriptObject)); + + JSValueProtect(context, scriptObject); + m_webFrameScriptObjectMap.add(frame.frameID(), scriptObject); + + return scriptObject; +} + +WebCore::Element* WebAutomationSessionProxy::elementForNodeHandle(WebFrame& frame, const String& nodeHandle) +{ + // Don't use scriptObjectForFrame() since we can assume if the script object + // does not exist, there are no nodes mapped to handles. Using scriptObjectForFrame() + // will make a new script object if it can't find one, preventing us from returning fast. + JSObjectRef scriptObject = m_webFrameScriptObjectMap.get(frame.frameID()); + if (!scriptObject) + return nullptr; + + JSGlobalContextRef context = frame.jsContext(); + + JSValueRef functionArguments[] = { + toJSValue(context, nodeHandle) + }; + + JSValueRef result = callPropertyFunction(context, scriptObject, ASCIILiteral("nodeForIdentifier"), WTF_ARRAY_LENGTH(functionArguments), functionArguments, nullptr); + JSObjectRef element = JSValueToObject(context, result, nullptr); + if (!element) + return nullptr; + + auto elementWrapper = WebCore::jsDynamicDowncast<WebCore::JSElement*>(toJS(context)->vm(), toJS(element)); + if (!elementWrapper) + return nullptr; + + return &elementWrapper->wrapped(); +} + +void WebAutomationSessionProxy::didClearWindowObjectForFrame(WebFrame& frame) +{ + uint64_t frameID = frame.frameID(); + if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.take(frameID)) + JSValueUnprotect(frame.jsContext(), scriptObject); + + String errorMessage = ASCIILiteral("Callback was not called before the unload event."); + String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError); + + auto pendingFrameCallbacks = m_webFramePendingEvaluateJavaScriptCallbacksMap.take(frameID); + for (uint64_t callbackID : pendingFrameCallbacks) + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, String(), errorType), 0); +} + +void WebAutomationSessionProxy::evaluateJavaScriptFunction(uint64_t pageID, uint64_t frameID, const String& function, Vector<String> arguments, bool expectsImplicitCallbackArgument, int callbackTimeout, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) + return; + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame) + return; + + JSObjectRef scriptObject = scriptObjectForFrame(*frame); + if (!scriptObject) + return; + + JSValueRef exception = nullptr; + JSGlobalContextRef context = frame->jsContext(); + + if (expectsImplicitCallbackArgument) { + auto result = m_webFramePendingEvaluateJavaScriptCallbacksMap.add(frameID, Vector<uint64_t>()); + result.iterator->value.append(callbackID); + } + + JSValueRef functionArguments[] = { + toJSValue(context, function), + toJSArray(context, arguments, toJSValue, &exception), + JSValueMakeBoolean(context, expectsImplicitCallbackArgument), + JSValueMakeNumber(context, frameID), + JSValueMakeNumber(context, callbackID), + JSObjectMakeFunctionWithCallback(context, nullptr, evaluateJavaScriptCallback), + JSValueMakeNumber(context, callbackTimeout) + }; + + callPropertyFunction(context, scriptObject, ASCIILiteral("evaluateJavaScriptFunction"), WTF_ARRAY_LENGTH(functionArguments), functionArguments, &exception); + + if (!exception) + return; + + String errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError); + + JSRetainPtr<JSStringRef> exceptionMessage; + if (JSValueIsObject(context, exception)) { + JSValueRef nameValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), toJSString(ASCIILiteral("name")).get(), nullptr); + JSRetainPtr<JSStringRef> exceptionName(Adopt, JSValueToStringCopy(context, nameValue, nullptr)); + if (exceptionName->string() == "NodeNotFound") + errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound); + else if (exceptionName->string() == "InvalidElementState") + errorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::InvalidElementState); + + JSValueRef messageValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), toJSString(ASCIILiteral("message")).get(), nullptr); + exceptionMessage.adopt(JSValueToStringCopy(context, messageValue, nullptr)); + } else + exceptionMessage.adopt(JSValueToStringCopy(context, exception, nullptr)); + + didEvaluateJavaScriptFunction(frameID, callbackID, exceptionMessage->string(), errorType); +} + +void WebAutomationSessionProxy::didEvaluateJavaScriptFunction(uint64_t frameID, uint64_t callbackID, const String& result, const String& errorType) +{ + auto findResult = m_webFramePendingEvaluateJavaScriptCallbacksMap.find(frameID); + if (findResult != m_webFramePendingEvaluateJavaScriptCallbacksMap.end()) { + findResult->value.removeFirst(callbackID); + ASSERT(!findResult->value.contains(callbackID)); + if (findResult->value.isEmpty()) + m_webFramePendingEvaluateJavaScriptCallbacksMap.remove(findResult); + } + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, result, errorType), 0); +} + +void WebAutomationSessionProxy::resolveChildFrameWithOrdinal(uint64_t pageID, uint64_t frameID, uint32_t ordinal, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, windowNotFoundErrorType), 0); + return; + } + + String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound); + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebCore::Frame* coreFrame = frame->coreFrame(); + if (!coreFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebCore::Frame* coreChildFrame = coreFrame->tree().scopedChild(ordinal); + if (!coreChildFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebFrame* childFrame = WebFrame::fromCoreFrame(*coreChildFrame); + if (!childFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, childFrame->frameID(), String()), 0); +} + +void WebAutomationSessionProxy::resolveChildFrameWithNodeHandle(uint64_t pageID, uint64_t frameID, const String& nodeHandle, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, windowNotFoundErrorType), 0); + return; + } + + String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound); + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle); + if (!coreElement || !coreElement->isFrameElementBase()) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebCore::Frame* coreFrameFromElement = static_cast<WebCore::HTMLFrameElementBase*>(coreElement)->contentFrame(); + if (!coreFrameFromElement) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebFrame* frameFromElement = WebFrame::fromCoreFrame(*coreFrameFromElement); + if (!frameFromElement) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, frameFromElement->frameID(), String()), 0); +} + +void WebAutomationSessionProxy::resolveChildFrameWithName(uint64_t pageID, uint64_t frameID, const String& name, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, windowNotFoundErrorType), 0); + return; + } + + String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound); + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebCore::Frame* coreFrame = frame->coreFrame(); + if (!coreFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebCore::Frame* coreChildFrame = coreFrame->tree().scopedChild(name); + if (!coreChildFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebFrame* childFrame = WebFrame::fromCoreFrame(*coreChildFrame); + if (!childFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveChildFrame(callbackID, childFrame->frameID(), String()), 0); +} + +void WebAutomationSessionProxy::resolveParentFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, 0, windowNotFoundErrorType), 0); + return; + } + + String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound); + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebFrame* parentFrame = frame->parentFrame(); + if (!parentFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, 0, frameNotFoundErrorType), 0); + return; + } + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidResolveParentFrame(callbackID, parentFrame->frameID(), String()), 0); +} + +void WebAutomationSessionProxy::focusFrame(uint64_t pageID, uint64_t frameID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) + return; + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame) + return; + + WebCore::Frame* coreFrame = frame->coreFrame(); + if (!coreFrame) + return; + + WebCore::Document* coreDocument = coreFrame->document(); + if (!coreDocument) + return; + + WebCore::DOMWindow* coreDOMWindow = coreDocument->domWindow(); + if (!coreDOMWindow) + return; + + coreDOMWindow->focus(true); +} + +void WebAutomationSessionProxy::computeElementLayout(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool useViewportCoordinates, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, WebCore::IntRect(), windowNotFoundErrorType), 0); + return; + } + + String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound); + String nodeNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound); + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, WebCore::IntRect(), frameNotFoundErrorType), 0); + return; + } + + WebCore::Element* coreElement = elementForNodeHandle(*frame, nodeHandle); + if (!coreElement) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, WebCore::IntRect(), nodeNotFoundErrorType), 0); + return; + } + + if (scrollIntoViewIfNeeded) + coreElement->scrollIntoViewIfNeeded(false); + + WebCore::IntRect rect = coreElement->clientRect(); + + WebCore::Frame* coreFrame = frame->coreFrame(); + if (!coreFrame) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, WebCore::IntRect(), frameNotFoundErrorType), 0); + return; + } + + WebCore::FrameView *coreFrameView = coreFrame->view(); + if (!coreFrameView) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, WebCore::IntRect(), frameNotFoundErrorType), 0); + return; + } + + if (useViewportCoordinates) + rect.moveBy(WebCore::IntPoint(0, -coreFrameView->topContentInset())); + else + rect = coreFrameView->rootViewToContents(rect); + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidComputeElementLayout(callbackID, rect, String()), 0); +} + +void WebAutomationSessionProxy::takeScreenshot(uint64_t pageID, uint64_t callbackID) +{ + ShareableBitmap::Handle handle; + + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, windowNotFoundErrorType), 0); + return; + } + + WebCore::FrameView* frameView = page->mainFrameView(); + if (!frameView) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, String()), 0); + return; + } + + WebCore::IntRect snapshotRect = WebCore::IntRect(WebCore::IntPoint(0, 0), frameView->contentsSize()); + if (snapshotRect.isEmpty()) { + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, String()), 0); + return; + } + + RefPtr<WebImage> image = page->scaledSnapshotWithOptions(snapshotRect, 1, SnapshotOptionsShareable); + if (image) + image->bitmap().createHandle(handle, SharedMemory::Protection::ReadOnly); + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidTakeScreenshot(callbackID, handle, String()), 0); +} + +void WebAutomationSessionProxy::getCookiesForFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidGetCookiesForFrame(callbackID, Vector<WebCore::Cookie>(), windowNotFoundErrorType), 0); + return; + } + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame || !frame->coreFrame() || !frame->coreFrame()->document()) { + String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidGetCookiesForFrame(callbackID, Vector<WebCore::Cookie>(), frameNotFoundErrorType), 0); + return; + } + + // This returns the same list of cookies as when evaluating `document.cookies` in JavaScript. + auto& document = *frame->coreFrame()->document(); + Vector<WebCore::Cookie> foundCookies; + WebCore::getRawCookies(document, document.cookieURL(), foundCookies); + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidGetCookiesForFrame(callbackID, foundCookies, String()), 0); +} + +void WebAutomationSessionProxy::deleteCookie(uint64_t pageID, uint64_t frameID, String cookieName, uint64_t callbackID) +{ + WebPage* page = WebProcess::singleton().webPage(pageID); + if (!page) { + String windowNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::WindowNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidDeleteCookie(callbackID, windowNotFoundErrorType), 0); + return; + } + + WebFrame* frame = frameID ? WebProcess::singleton().webFrame(frameID) : page->mainWebFrame(); + if (!frame || !frame->coreFrame() || !frame->coreFrame()->document()) { + String frameNotFoundErrorType = Inspector::Protocol::AutomationHelpers::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::FrameNotFound); + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidDeleteCookie(callbackID, frameNotFoundErrorType), 0); + return; + } + + auto& document = *frame->coreFrame()->document(); + WebCore::deleteCookie(document, document.cookieURL(), cookieName); + + WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidDeleteCookie(callbackID, String()), 0); +} + +} // namespace WebKit diff --git a/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.h b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.h new file mode 100644 index 000000000..18d403816 --- /dev/null +++ b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WebAutomationSessionProxy_h +#define WebAutomationSessionProxy_h + +#include "Connection.h" +#include <JavaScriptCore/JSBase.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { +class Element; +} + +namespace WebKit { + +class WebFrame; +class WebPage; + +class WebAutomationSessionProxy : public IPC::MessageReceiver { +public: + WebAutomationSessionProxy(const String& sessionIdentifier); + ~WebAutomationSessionProxy(); + + String sessionIdentifier() const { return m_sessionIdentifier; } + + void didClearWindowObjectForFrame(WebFrame&); + + void didEvaluateJavaScriptFunction(uint64_t frameID, uint64_t callbackID, const String& result, const String& errorType); + +private: + JSObjectRef scriptObjectForFrame(WebFrame&); + WebCore::Element* elementForNodeHandle(WebFrame&, const String&); + + // Implemented in generated WebAutomationSessionProxyMessageReceiver.cpp + void didReceiveMessage(IPC::Connection&, IPC::Decoder&) override; + + // Called by WebAutomationSessionProxy messages + void evaluateJavaScriptFunction(uint64_t pageID, uint64_t frameID, const String& function, Vector<String> arguments, bool expectsImplicitCallbackArgument, int callbackTimeout, uint64_t callbackID); + void resolveChildFrameWithOrdinal(uint64_t pageID, uint64_t frameID, uint32_t ordinal, uint64_t callbackID); + void resolveChildFrameWithNodeHandle(uint64_t pageID, uint64_t frameID, const String& nodeHandle, uint64_t callbackID); + void resolveChildFrameWithName(uint64_t pageID, uint64_t frameID, const String& name, uint64_t callbackID); + void resolveParentFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID); + void focusFrame(uint64_t pageID, uint64_t frameID); + void computeElementLayout(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool useViewportCoordinates, uint64_t callbackID); + void takeScreenshot(uint64_t pageID, uint64_t callbackID); + void getCookiesForFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID); + void deleteCookie(uint64_t pageID, uint64_t frameID, String cookieName, uint64_t callbackID); + + String m_sessionIdentifier; + + HashMap<uint64_t, JSObjectRef, DefaultHash<uint64_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<uint64_t>> m_webFrameScriptObjectMap; + HashMap<uint64_t, Vector<uint64_t>, DefaultHash<uint64_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<uint64_t>> m_webFramePendingEvaluateJavaScriptCallbacksMap; +}; + +} // namespace WebKit + +#endif // WebAutomationSessionProxy_h diff --git a/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.js b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.js new file mode 100644 index 000000000..b1ea97299 --- /dev/null +++ b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.js @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +//# sourceURL=__InjectedScript_WebAutomationSessionProxy.js + +(function (sessionIdentifier, evaluate, createUUID) { + +const sessionNodePropertyName = "session-node-" + sessionIdentifier; + +let AutomationSessionProxy = class AutomationSessionProxy +{ + constructor() + { + this._nodeToIdMap = new Map; + this._idToNodeMap = new Map; + } + + // Public + + evaluateJavaScriptFunction(functionString, argumentStrings, expectsImplicitCallbackArgument, frameID, callbackID, resultCallback, callbackTimeout) + { + // The script is expected to be a function declaration. Evaluate it inside parenthesis to get the function value. + let functionValue = evaluate("(" + functionString + ")"); + if (typeof functionValue !== "function") + throw new TypeError("Script did not evaluate to a function."); + + let argumentValues = argumentStrings.map(this._jsonParse, this); + + let timeoutIdentifier = 0; + + let reportResult = (result) => { clearTimeout(timeoutIdentifier); resultCallback(frameID, callbackID, this._jsonStringify(result), false); }; + let reportTimeoutError = () => { resultCallback(frameID, callbackID, "JavaScriptTimeout", true); }; + + if (expectsImplicitCallbackArgument) { + timeoutIdentifier = setTimeout(reportTimeoutError, callbackTimeout); + + argumentValues.push(reportResult); + functionValue.apply(null, argumentValues); + } else + reportResult(functionValue.apply(null, argumentValues)); + } + + nodeForIdentifier(identifier) + { + try { + return this._nodeForIdentifier(identifier); + } catch (error) { + return null; + } + } + + // Private + + _jsonParse(string) + { + if (!string) + return undefined; + return JSON.parse(string, (key, value) => this._reviveJSONValue(key, value)); + } + + _jsonStringify(original) + { + return JSON.stringify(original, (key, value) => this._replaceJSONValue(key, value)) || ""; + } + + _reviveJSONValue(key, value) + { + if (value && typeof value === "object" && value[sessionNodePropertyName]) + return this._nodeForIdentifier(value[sessionNodePropertyName]); + return value; + } + + _replaceJSONValue(key, value) + { + if (value instanceof Node) + return this._createNodeHandle(value); + + if (value instanceof NodeList || value instanceof HTMLCollection) + value = Array.from(value).map(this._createNodeHandle, this); + + return value; + } + + _createNodeHandle(node) + { + return {[sessionNodePropertyName]: this._identifierForNode(node)}; + } + + _nodeForIdentifier(identifier) + { + let node = this._idToNodeMap.get(identifier); + if (node) + return node; + throw {name: "NodeNotFound", message: "Node with identifier '" + identifier + "' was not found"}; + } + + _identifierForNode(node) + { + let identifier = this._nodeToIdMap.get(node); + if (identifier) + return identifier; + + identifier = "node-" + createUUID(); + + this._nodeToIdMap.set(node, identifier); + this._idToNodeMap.set(identifier, node); + + return identifier; + } +}; + +return new AutomationSessionProxy; + +}) diff --git a/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.messages.in b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.messages.in new file mode 100644 index 000000000..2daf2a5d5 --- /dev/null +++ b/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.messages.in @@ -0,0 +1,39 @@ +# Copyright (C) 2016 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +messages -> WebAutomationSessionProxy { + EvaluateJavaScriptFunction(uint64_t pageID, uint64_t frameID, String function, Vector<String> arguments, bool expectsImplicitCallbackArgument, int callbackTimeout, uint64_t callbackID) + + ResolveChildFrameWithOrdinal(uint64_t pageID, uint64_t frameID, uint32_t ordinal, uint64_t callbackID) + ResolveChildFrameWithNodeHandle(uint64_t pageID, uint64_t frameID, String nodeHandle, uint64_t callbackID) + ResolveChildFrameWithName(uint64_t pageID, uint64_t frameID, String name, uint64_t callbackID) + ResolveParentFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID) + + FocusFrame(uint64_t pageID, uint64_t frameID) + + ComputeElementLayout(uint64_t pageID, uint64_t frameID, String nodeHandle, bool scrollIntoViewIfNeeded, bool useViewportCoordinates, uint64_t callbackID) + + TakeScreenshot(uint64_t pageID, uint64_t callbackID) + + GetCookiesForFrame(uint64_t pageID, uint64_t frameID, uint64_t callbackID) + DeleteCookie(uint64_t pageID, uint64_t frameID, String cookieName, uint64_t callbackID) +} |