Changeset 198737 in webkit


Ignore:
Timestamp:
Mar 28, 2016 8:57:06 AM (8 years ago)
Author:
timothy@apple.com
Message:

Web Automation: Add Automation.evaluateJavaScriptFunction

https://bugs.webkit.org/show_bug.cgi?id=155524
rdar://problem/25181888

Reviewed by Joseph Pecoraro.

  • UIProcess/Automation/Automation.json: Added evaluateJavaScriptFunction command.
  • UIProcess/Automation/WebAutomationSession.cpp:

(WebKit::WebAutomationSession::evaluateJavaScriptFunction): Added.
(WebKit::WebAutomationSession::didEvaluateJavaScriptFunction): Added.

  • UIProcess/Automation/WebAutomationSession.h:
  • UIProcess/Automation/WebAutomationSession.messages.in: Added didEvaluateJavaScriptFunction.
  • WebProcess/Automation/WebAutomationSessionProxy.cpp:

(WebKit::toJSArray): Added.
(WebKit::callPropertyFunction): Added.
(WebKit::evaluateJavaScriptCallback): Added.
(WebKit::WebAutomationSessionProxy::didClearWindowObjectForFrame): Dispatch pending callbacks as errors.
(WebKit::WebAutomationSessionProxy::evaluateJavaScriptFunction): Added.
(WebKit::WebAutomationSessionProxy::didEvaluateJavaScriptFunction): Added.

  • WebProcess/Automation/WebAutomationSessionProxy.h:
  • WebProcess/Automation/WebAutomationSessionProxy.js:

(AutomationSessionProxy): Added maps for node handles.
(AutomationSessionProxy.prototype.evaluateJavaScriptFunction): Added.
(AutomationSessionProxy.prototype._jsonParse): Added.
(AutomationSessionProxy.prototype._jsonStringify): Added.
(AutomationSessionProxy.prototype._reviveJSONValue): Added.
(AutomationSessionProxy.prototype._replaceJSONValue): Added.
(AutomationSessionProxy.prototype._createNodeHandle): Added.
(AutomationSessionProxy.prototype._nodeForIdentifier): Added.
(AutomationSessionProxy.prototype._identifierForNode): Added.

  • WebProcess/Automation/WebAutomationSessionProxy.messages.in: Added evaluateJavaScriptFunction.
Location:
trunk/Source/WebKit2
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit2/ChangeLog

    r198736 r198737  
     12016-03-17  Timothy Hatcher  <timothy@apple.com>
     2
     3        Web Automation: Add Automation.evaluateJavaScriptFunction
     4
     5        https://bugs.webkit.org/show_bug.cgi?id=155524
     6        rdar://problem/25181888
     7
     8        Reviewed by Joseph Pecoraro.
     9
     10        * UIProcess/Automation/Automation.json: Added evaluateJavaScriptFunction command.
     11
     12        * UIProcess/Automation/WebAutomationSession.cpp:
     13        (WebKit::WebAutomationSession::evaluateJavaScriptFunction): Added.
     14        (WebKit::WebAutomationSession::didEvaluateJavaScriptFunction): Added.
     15        * UIProcess/Automation/WebAutomationSession.h:
     16        * UIProcess/Automation/WebAutomationSession.messages.in: Added didEvaluateJavaScriptFunction.
     17
     18        * WebProcess/Automation/WebAutomationSessionProxy.cpp:
     19        (WebKit::toJSArray): Added.
     20        (WebKit::callPropertyFunction): Added.
     21        (WebKit::evaluateJavaScriptCallback): Added.
     22        (WebKit::WebAutomationSessionProxy::didClearWindowObjectForFrame): Dispatch pending callbacks as errors.
     23        (WebKit::WebAutomationSessionProxy::evaluateJavaScriptFunction): Added.
     24        (WebKit::WebAutomationSessionProxy::didEvaluateJavaScriptFunction): Added.
     25        * WebProcess/Automation/WebAutomationSessionProxy.h:
     26
     27        * WebProcess/Automation/WebAutomationSessionProxy.js:
     28        (AutomationSessionProxy): Added maps for node handles.
     29        (AutomationSessionProxy.prototype.evaluateJavaScriptFunction): Added.
     30        (AutomationSessionProxy.prototype._jsonParse): Added.
     31        (AutomationSessionProxy.prototype._jsonStringify): Added.
     32        (AutomationSessionProxy.prototype._reviveJSONValue): Added.
     33        (AutomationSessionProxy.prototype._replaceJSONValue): Added.
     34        (AutomationSessionProxy.prototype._createNodeHandle): Added.
     35        (AutomationSessionProxy.prototype._nodeForIdentifier): Added.
     36        (AutomationSessionProxy.prototype._identifierForNode): Added.
     37
     38        * WebProcess/Automation/WebAutomationSessionProxy.messages.in: Added evaluateJavaScriptFunction.
     39
    1402016-03-14  Timothy Hatcher  <timothy@apple.com>
    241
  • trunk/Source/WebKit2/UIProcess/Automation/Automation.json

    r197773 r198737  
    1414            "enum": [
    1515                "InternalError",
     16                "JavaScriptError",
    1617                "WindowNotFound",
     18                "NodeNotFound",
    1719                "NotImplemented"
    1820            ]
     
    9698                { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be reloaded." }
    9799            ]
     100        },
     101        {
     102            "name": "evaluateJavaScriptFunction",
     103            "description": "Evaluates a script function in a browsing context and calls it with the supplied arguments.",
     104            "parameters": [
     105                { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context the script should be evaluated." },
     106                { "name": "function", "type": "string", "description": "The script to evaluate in the browsing context. The script is expected to be a function declaration, or otherwise evaluate to a function result." },
     107                { "name": "arguments", "type": "array", "items": { "type": "string" }, "description": "The arguments to pass to the function when called. They will be parsed as JSON before calling the function." },
     108                { "name": "expectsImplicitCallbackArgument", "type": "boolean", "description": "The function expects a callback function as the last argument. It is expected to call this callback with a result." }
     109            ],
     110            "returns": [
     111                { "name": "result", "type": "string", "description": "The result returned by the called function. It is JSON encoded after the function returns or calls the callback." }
     112            ],
     113            "async": true
    98114        }
    99115    ]
  • trunk/Source/WebKit2/UIProcess/Automation/WebAutomationSession.cpp

    r198736 r198737  
    256256}
    257257
     258void WebAutomationSession::evaluateJavaScriptFunction(Inspector::ErrorString& errorString, const String& handle, const String& function, const Inspector::InspectorArray& arguments, bool expectsImplicitCallbackArgument, Ref<EvaluateJavaScriptFunctionCallback>&& callback)
     259{
     260    // FIXME 24172439: This should be a frame handle, not a page handle. Change this once we have frame support.
     261    WebPageProxy* page = webPageProxyForHandle(handle);
     262    if (!page)
     263        FAIL_WITH_PREDEFINED_ERROR_MESSAGE(WindowNotFound);
     264
     265    Vector<String> argumentsVector;
     266    argumentsVector.reserveCapacity(arguments.length());
     267
     268    for (auto& argument : arguments) {
     269        String argumentString;
     270        argument->asString(argumentString);
     271        argumentsVector.uncheckedAppend(argumentString);
     272    }
     273
     274    uint64_t callbackID = m_nextEvaluateJavaScriptCallbackID++;
     275    m_evaluateJavaScriptFunctionCallbacks.set(callbackID, WTFMove(callback));
     276
     277    page->process().send(Messages::WebAutomationSessionProxy::EvaluateJavaScriptFunction(page->mainFrame()->frameID(), function, argumentsVector, expectsImplicitCallbackArgument, callbackID), 0);
     278}
     279
     280void WebAutomationSession::didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType)
     281{
     282    auto callback = m_evaluateJavaScriptFunctionCallbacks.take(callbackID);
     283    if (!callback)
     284        return;
     285
     286    if (!errorType.isEmpty()) {
     287        // FIXME: We should send both the errorType and result, since result has the specific exception message.
     288        callback->sendFailure(errorType);
     289    } else
     290        callback->sendSuccess(result);
     291}
     292
    258293} // namespace WebKit
  • trunk/Source/WebKit2/UIProcess/Automation/WebAutomationSession.h

    r198736 r198737  
    9090    void goForwardInBrowsingContext(Inspector::ErrorString&, const String&) override;
    9191    void reloadBrowsingContext(Inspector::ErrorString&, const String&) override;
     92    void evaluateJavaScriptFunction(Inspector::ErrorString&, const String& handle, const String& function, const Inspector::InspectorArray& arguments, bool expectsImplicitCallbackArgument, Ref<Inspector::AutomationBackendDispatcherHandler::EvaluateJavaScriptFunctionCallback>&&) override;
    9293
    9394private:
     
    99100
    100101    // Called by WebAutomationSession messages
    101     // FIXME: Add message functions here.
    102     void test() { };
     102    void didEvaluateJavaScriptFunction(uint64_t callbackID, const String& result, const String& errorType);
    103103
    104104    WebKit::WebProcessPool* m_processPool { nullptr };
     
    113113    String m_activeBrowsingContextHandle;
    114114
     115    uint64_t m_nextEvaluateJavaScriptCallbackID { 1 };
     116    HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::EvaluateJavaScriptFunctionCallback>> m_evaluateJavaScriptFunctionCallbacks;
     117
    115118#if ENABLE(REMOTE_INSPECTOR)
    116119    Inspector::FrontendChannel* m_remoteChannel { nullptr };
  • trunk/Source/WebKit2/UIProcess/Automation/WebAutomationSession.messages.in

    r198736 r198737  
    2222
    2323messages -> WebAutomationSession {
    24     // FIXME: Add messages here.
    25     Test()
     24    DidEvaluateJavaScriptFunction(uint64_t callbackID, String result, String errorType)
    2625}
  • trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.cpp

    r198736 r198737  
    2727#include "WebAutomationSessionProxy.h"
    2828
     29#include "InspectorProtocolObjects.h"
    2930#include "WebAutomationSessionMessages.h"
    3031#include "WebAutomationSessionProxyMessages.h"
     
    4041namespace WebKit {
    4142
     43template <typename T>
     44static JSObjectRef toJSArray(JSContextRef context, const Vector<T>& data, JSValueRef (*converter)(JSContextRef, const T&), JSValueRef* exception)
     45{
     46    ASSERT_ARG(converter, converter);
     47
     48    if (data.isEmpty())
     49        return JSObjectMakeArray(context, 0, nullptr, exception);
     50
     51    Vector<JSValueRef, 8> convertedData;
     52    convertedData.reserveCapacity(data.size());
     53
     54    for (auto& originalValue : data) {
     55        JSValueRef convertedValue = converter(context, originalValue);
     56        JSValueProtect(context, convertedValue);
     57        convertedData.uncheckedAppend(convertedValue);
     58    }
     59
     60    JSObjectRef array = JSObjectMakeArray(context, convertedData.size(), convertedData.data(), exception);
     61
     62    for (auto& convertedValue : convertedData)
     63        JSValueUnprotect(context, convertedValue);
     64
     65    return array;
     66}
     67
    4268static inline JSRetainPtr<JSStringRef> toJSString(const String& string)
    4369{
     
    4874{
    4975    return JSValueMakeString(context, toJSString(string).get());
     76}
     77
     78static inline JSValueRef callPropertyFunction(JSContextRef context, JSObjectRef object, const String& propertyName, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
     79{
     80    ASSERT_ARG(object, object);
     81    ASSERT_ARG(object, JSValueIsObject(context, object));
     82
     83    JSObjectRef function = const_cast<JSObjectRef>(JSObjectGetProperty(context, object, toJSString(propertyName).get(), exception));
     84    ASSERT(JSObjectIsFunction(context, function));
     85
     86    return JSObjectCallAsFunction(context, function, object, argumentCount, arguments, exception);
    5087}
    5188
     
    76113{
    77114    return toJSValue(context, WebCore::createCanonicalUUIDString().convertToASCIIUppercase());
     115}
     116
     117static JSValueRef evaluateJavaScriptCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
     118{
     119    ASSERT_ARG(argumentCount, argumentCount == 3);
     120    ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[0]));
     121    ASSERT_ARG(arguments, JSValueIsNumber(context, arguments[1]));
     122    ASSERT_ARG(arguments, JSValueIsString(context, arguments[2]));
     123
     124    auto automationSessionProxy = WebProcess::singleton().automationSessionProxy();
     125    if (!automationSessionProxy)
     126        return JSValueMakeUndefined(context);
     127
     128    uint64_t frameID = JSValueToNumber(context, arguments[0], exception);
     129    uint64_t callbackID = JSValueToNumber(context, arguments[1], exception);
     130    JSRetainPtr<JSStringRef> result(Adopt, JSValueToStringCopy(context, arguments[2], exception));
     131
     132    automationSessionProxy->didEvaluateJavaScriptFunction(frameID, callbackID, result->string(), emptyString());
     133
     134    return JSValueMakeUndefined(context);
    78135}
    79136
     
    107164void WebAutomationSessionProxy::didClearWindowObjectForFrame(WebFrame& frame)
    108165{
    109     if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.take(frame.frameID()))
     166    uint64_t frameID = frame.frameID();
     167    if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.take(frameID))
    110168        JSValueUnprotect(frame.jsContext(), scriptObject);
     169
     170    String errorMessage = ASCIILiteral("Callback was not called before the unload event.");
     171    String errorType = Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
     172
     173    auto pendingFrameCallbacks = m_webFramePendingEvaluateJavaScriptCallbacksMap.take(frameID);
     174    for (uint64_t callbackID : pendingFrameCallbacks)
     175        WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, emptyString(), errorType), 0);
     176}
     177
     178void WebAutomationSessionProxy::evaluateJavaScriptFunction(uint64_t frameID, const String& function, Vector<String> arguments, bool expectsImplicitCallbackArgument, uint64_t callbackID)
     179{
     180    WebFrame* frame = WebProcess::singleton().webFrame(frameID);
     181    if (!frame)
     182        return;
     183
     184    JSObjectRef scriptObject = scriptObjectForFrame(*frame);
     185    if (!scriptObject)
     186        return;
     187
     188    JSValueRef exception = nullptr;
     189    JSGlobalContextRef context = frame->jsContext();
     190
     191    JSObjectRef callbackFunction = JSObjectMakeFunctionWithCallback(context, nullptr, evaluateJavaScriptCallback);
     192
     193    if (expectsImplicitCallbackArgument) {
     194        auto result = m_webFramePendingEvaluateJavaScriptCallbacksMap.add(frameID, Vector<uint64_t>());
     195        result.iterator->value.append(callbackID);
     196    }
     197
     198    JSValueRef functionArguments[] = {
     199        toJSValue(context, function),
     200        toJSArray(context, arguments, toJSValue, &exception),
     201        JSValueMakeBoolean(context, expectsImplicitCallbackArgument),
     202        JSValueMakeNumber(context, frameID),
     203        JSValueMakeNumber(context, callbackID),
     204        callbackFunction
     205    };
     206
     207    callPropertyFunction(context, scriptObject, ASCIILiteral("evaluateJavaScriptFunction"), WTF_ARRAY_LENGTH(functionArguments), functionArguments, &exception);
     208
     209    if (!exception)
     210        return;
     211
     212    String errorType = Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::JavaScriptError);
     213
     214    JSRetainPtr<JSStringRef> exceptionMessage;
     215    if (JSValueIsObject(context, exception)) {
     216        JSValueRef nameValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), toJSString(ASCIILiteral("name")).get(), nullptr);
     217        JSRetainPtr<JSStringRef> exceptionName(Adopt, JSValueToStringCopy(context, nameValue, nullptr));
     218        if (exceptionName->string() == "NodeNotFound")
     219            errorType = Inspector::Protocol::getEnumConstantValue(Inspector::Protocol::Automation::ErrorMessage::NodeNotFound);
     220
     221        JSValueRef messageValue = JSObjectGetProperty(context, const_cast<JSObjectRef>(exception), toJSString(ASCIILiteral("message")).get(), nullptr);
     222        exceptionMessage.adopt(JSValueToStringCopy(context, messageValue, nullptr));
     223    } else
     224        exceptionMessage.adopt(JSValueToStringCopy(context, exception, nullptr));
     225
     226    didEvaluateJavaScriptFunction(frameID, callbackID, exceptionMessage->string(), errorType);
     227}
     228
     229void WebAutomationSessionProxy::didEvaluateJavaScriptFunction(uint64_t frameID, uint64_t callbackID, const String& result, const String& errorType)
     230{
     231    auto findResult = m_webFramePendingEvaluateJavaScriptCallbacksMap.find(frameID);
     232    if (findResult != m_webFramePendingEvaluateJavaScriptCallbacksMap.end()) {
     233        findResult->value.removeFirst(callbackID);
     234        ASSERT(!findResult->value.contains(callbackID));
     235        if (findResult->value.isEmpty())
     236            m_webFramePendingEvaluateJavaScriptCallbacksMap.remove(findResult);
     237    }
     238
     239    WebProcess::singleton().parentProcessConnection()->send(Messages::WebAutomationSession::DidEvaluateJavaScriptFunction(callbackID, result, errorType), 0);
    111240}
    112241
  • trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.h

    r198736 r198737  
    4444    void didClearWindowObjectForFrame(WebFrame&);
    4545
     46    void didEvaluateJavaScriptFunction(uint64_t frameID, uint64_t callbackID, const String& result, const String& errorType);
     47
    4648private:
    4749    JSObjectRef scriptObjectForFrame(WebFrame&);
     
    5153
    5254    // Called by WebAutomationSessionProxy messages
    53     // FIXME: Add message functions here.
    54     void test() { };
     55    void evaluateJavaScriptFunction(uint64_t frameID, const String& function, Vector<String> arguments, bool expectsImplicitCallbackArgument, uint64_t callbackID);
    5556
    5657    String m_sessionIdentifier;
    5758
    58     typedef HashMap<uint64_t, JSObjectRef> WebFrameScriptObjectMap;
    59     WebFrameScriptObjectMap m_webFrameScriptObjectMap;
     59    HashMap<uint64_t, JSObjectRef> m_webFrameScriptObjectMap;
     60    HashMap<uint64_t, Vector<uint64_t>> m_webFramePendingEvaluateJavaScriptCallbacksMap;
    6061};
    6162
  • trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.js

    r198736 r198737  
    2828(function (sessionIdentifier, evaluate, createUUID) {
    2929
    30 // Protect against Object overwritten by the page.
    31 let Object = {}.constructor;
     30const sessionNodePropertyName = "session-node-" + sessionIdentifier;
    3231
    3332let AutomationSessionProxy = class AutomationSessionProxy
    3433{
     34    constructor()
     35    {
     36        this._nodeToIdMap = new Map;
     37        this._idToNodeMap = new Map;
     38    }
     39
    3540    // Public
    3641
    37     // FIXME: Add functions here.
     42    evaluateJavaScriptFunction(functionString, argumentStrings, expectsImplicitCallbackArgument, frameID, callbackID, resultCallback)
     43    {
     44        // The script is expected to be a function declaration. Evaluate it inside parenthesis to get the function value.
     45        let functionValue = evaluate("(" + functionString + ")");
     46        if (typeof functionValue !== "function")
     47            throw new TypeError("Script did not evaluate to a function.");
     48
     49        let argumentValues = argumentStrings.map(this._jsonParse, this);
     50        let callback = (result) => resultCallback(frameID, callbackID, this._jsonStringify(result));
     51
     52        if (expectsImplicitCallbackArgument) {
     53            argumentValues.push(callback);
     54            functionValue.apply(null, argumentValues);
     55        } else
     56            callback(functionValue.apply(null, argumentValues));
     57    }
     58
     59    // Private
     60
     61    _jsonParse(string)
     62    {
     63        return JSON.parse(string, (key, value) => this._reviveJSONValue(key, value));
     64    }
     65
     66    _jsonStringify(original)
     67    {
     68        return JSON.stringify(original, (key, value) => this._replaceJSONValue(key, value));
     69    }
     70
     71    _reviveJSONValue(key, value)
     72    {
     73        if (value && typeof value === "object" && value[sessionNodePropertyName])
     74            return this._nodeForIdentifier(value[sessionNodePropertyName]);
     75        return value;
     76    }
     77
     78    _replaceJSONValue(key, value)
     79    {
     80        if (value instanceof Node)
     81            return this._createNodeHandle(value);
     82
     83        if (value instanceof NodeList || value instanceof HTMLCollection)
     84            value = Array.from(value).map(this._createNodeHandle, this);
     85
     86        return value;
     87    }
     88
     89    _createNodeHandle(node)
     90    {
     91        return {[sessionNodePropertyName]: this._identifierForNode(node)};
     92    }
     93
     94    _nodeForIdentifier(identifier)
     95    {
     96        let node = this._idToNodeMap.get(identifier);
     97        if (node)
     98            return node;
     99        throw {name: "NodeNotFound", message: "Node with identifier '" + identifier + "' was not found"};
     100    }
     101
     102    _identifierForNode(node)
     103    {
     104        let identifier = this._nodeToIdMap.get(node);
     105        if (identifier)
     106            return identifier;
     107
     108        identifier = "node-" + createUUID();
     109
     110        this._nodeToIdMap.set(node, identifier);
     111        this._idToNodeMap.set(identifier, node);
     112
     113        return identifier;
     114    }
    38115};
    39116
  • trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.messages.in

    r198736 r198737  
    2222
    2323messages -> WebAutomationSessionProxy {
    24     // FIXME: Add messages here.
    25     Test()
     24    EvaluateJavaScriptFunction(uint64_t frame, String function, Vector<String> arguments, bool expectsImplicitCallbackArgument, uint64_t callbackID)
    2625}
Note: See TracChangeset for help on using the changeset viewer.