Changeset 198737 in webkit
- Timestamp:
- Mar 28, 2016 8:57:06 AM (8 years ago)
- Location:
- trunk/Source/WebKit2
- Files:
-
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebKit2/ChangeLog
r198736 r198737 1 2016-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 1 40 2016-03-14 Timothy Hatcher <timothy@apple.com> 2 41 -
trunk/Source/WebKit2/UIProcess/Automation/Automation.json
r197773 r198737 14 14 "enum": [ 15 15 "InternalError", 16 "JavaScriptError", 16 17 "WindowNotFound", 18 "NodeNotFound", 17 19 "NotImplemented" 18 20 ] … … 96 98 { "name": "handle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context that should be reloaded." } 97 99 ] 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 98 114 } 99 115 ] -
trunk/Source/WebKit2/UIProcess/Automation/WebAutomationSession.cpp
r198736 r198737 256 256 } 257 257 258 void 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 280 void 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 258 293 } // namespace WebKit -
trunk/Source/WebKit2/UIProcess/Automation/WebAutomationSession.h
r198736 r198737 90 90 void goForwardInBrowsingContext(Inspector::ErrorString&, const String&) override; 91 91 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; 92 93 93 94 private: … … 99 100 100 101 // 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); 103 103 104 104 WebKit::WebProcessPool* m_processPool { nullptr }; … … 113 113 String m_activeBrowsingContextHandle; 114 114 115 uint64_t m_nextEvaluateJavaScriptCallbackID { 1 }; 116 HashMap<uint64_t, RefPtr<Inspector::AutomationBackendDispatcherHandler::EvaluateJavaScriptFunctionCallback>> m_evaluateJavaScriptFunctionCallbacks; 117 115 118 #if ENABLE(REMOTE_INSPECTOR) 116 119 Inspector::FrontendChannel* m_remoteChannel { nullptr }; -
trunk/Source/WebKit2/UIProcess/Automation/WebAutomationSession.messages.in
r198736 r198737 22 22 23 23 messages -> WebAutomationSession { 24 // FIXME: Add messages here. 25 Test() 24 DidEvaluateJavaScriptFunction(uint64_t callbackID, String result, String errorType) 26 25 } -
trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.cpp
r198736 r198737 27 27 #include "WebAutomationSessionProxy.h" 28 28 29 #include "InspectorProtocolObjects.h" 29 30 #include "WebAutomationSessionMessages.h" 30 31 #include "WebAutomationSessionProxyMessages.h" … … 40 41 namespace WebKit { 41 42 43 template <typename T> 44 static 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 42 68 static inline JSRetainPtr<JSStringRef> toJSString(const String& string) 43 69 { … … 48 74 { 49 75 return JSValueMakeString(context, toJSString(string).get()); 76 } 77 78 static 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); 50 87 } 51 88 … … 76 113 { 77 114 return toJSValue(context, WebCore::createCanonicalUUIDString().convertToASCIIUppercase()); 115 } 116 117 static 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); 78 135 } 79 136 … … 107 164 void WebAutomationSessionProxy::didClearWindowObjectForFrame(WebFrame& frame) 108 165 { 109 if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.take(frame.frameID())) 166 uint64_t frameID = frame.frameID(); 167 if (JSObjectRef scriptObject = m_webFrameScriptObjectMap.take(frameID)) 110 168 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 178 void 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 229 void 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); 111 240 } 112 241 -
trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.h
r198736 r198737 44 44 void didClearWindowObjectForFrame(WebFrame&); 45 45 46 void didEvaluateJavaScriptFunction(uint64_t frameID, uint64_t callbackID, const String& result, const String& errorType); 47 46 48 private: 47 49 JSObjectRef scriptObjectForFrame(WebFrame&); … … 51 53 52 54 // 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); 55 56 56 57 String m_sessionIdentifier; 57 58 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; 60 61 }; 61 62 -
trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.js
r198736 r198737 28 28 (function (sessionIdentifier, evaluate, createUUID) { 29 29 30 // Protect against Object overwritten by the page. 31 let Object = {}.constructor; 30 const sessionNodePropertyName = "session-node-" + sessionIdentifier; 32 31 33 32 let AutomationSessionProxy = class AutomationSessionProxy 34 33 { 34 constructor() 35 { 36 this._nodeToIdMap = new Map; 37 this._idToNodeMap = new Map; 38 } 39 35 40 // Public 36 41 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 } 38 115 }; 39 116 -
trunk/Source/WebKit2/WebProcess/Automation/WebAutomationSessionProxy.messages.in
r198736 r198737 22 22 23 23 messages -> WebAutomationSessionProxy { 24 // FIXME: Add messages here. 25 Test() 24 EvaluateJavaScriptFunction(uint64_t frame, String function, Vector<String> arguments, bool expectsImplicitCallbackArgument, uint64_t callbackID) 26 25 }
Note: See TracChangeset
for help on using the changeset viewer.