Title: [253950] trunk
Revision
253950
Author
beid...@apple.com
Date
2019-12-30 12:23:20 -0800 (Mon, 30 Dec 2019)

Log Message

Add WKWebView SPI to evaluate a function with arguments
https://bugs.webkit.org/show_bug.cgi?id=205239

Reviewed by Alex Christensen.

Source/WebCore:

Covered by new API tests.

* Headers.cmake:
* WebCore.xcodeproj/project.pbxproj:

* bindings/js/ExceptionDetails.h:

* bindings/js/RunJavaScriptParameters.h: Added.
(WebCore::RunJavaScriptParameters::RunJavaScriptParameters):
(WebCore::RunJavaScriptParameters::encode const):
(WebCore::RunJavaScriptParameters::decode):

* bindings/js/ScriptController.cpp:
(WebCore::ScriptController::executeScriptInWorldIgnoringException):
(WebCore::ScriptController::executeScriptInWorld):
(WebCore::ScriptController::callInWorld):
(WebCore::ScriptController::executeUserAgentScriptInWorld):
(WebCore::ScriptController::executeUserAgentScriptInWorldInternal):
(WebCore::ScriptController::executeAsynchronousUserAgentScriptInWorld):
* bindings/js/ScriptController.h:

XPathGrammar changes completely unrelated to the functionality of this patch,
but because of our poor #include hygiene these were necessary to keep linuxes building.
* xml/XPathGrammar.cpp:
* xml/XPathGrammar.y:

Source/WebKit:

* Shared/API/APISerializedScriptValue.h:

* UIProcess/API/C/WKPage.cpp:
(WKPageRunJavaScriptInMainFrame):

* UIProcess/API/Cocoa/APISerializedScriptValueCocoa.mm:
(API::sharedContext):
(API::SerializedScriptValue::deserialize):
(API::SerializedScriptValue::wireBytesFromNSObject):

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView evaluateJavaScript:completionHandler:]):
(validateArgument):
(-[WKWebView _evaluateJavaScript:asAsyncFunction:withArguments:forceUserGesture:completionHandler:]):
(-[WKWebView _callAsyncFunction:withArguments:completionHandler:]):
(-[WKWebView _evaluateJavaScriptWithoutUserGesture:completionHandler:]):
(-[WKWebView _evaluateJavaScript:forceUserGesture:completionHandler:]): Deleted.
* UIProcess/API/Cocoa/WKWebViewPrivate.h:

* UIProcess/API/glib/WebKitWebView.cpp:
(webkit_web_view_run_javascript):
(webkit_web_view_run_javascript_in_world):
(resourcesStreamReadCallback):

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::runJavaScriptInMainFrame):
(WebKit::WebPageProxy::runJavaScriptInMainFrameScriptWorld):
* UIProcess/WebPageProxy.h:

* UIProcess/socket/RemoteInspectorProtocolHandler.cpp:
(WebKit::RemoteInspectorProtocolHandler::runScript):

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::runJavaScript):
(WebKit::WebPage::runJavaScriptInMainFrameScriptWorld):
(WebKit::WebPage::runJavaScriptInFrame):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm: Added.
(TestWebKitAPI::TEST):
* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[WKWebView objectByCallingAsyncFunction:withArguments:error:]):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (253949 => 253950)


--- trunk/Source/WebCore/ChangeLog	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/ChangeLog	2019-12-30 20:23:20 UTC (rev 253950)
@@ -1,3 +1,36 @@
+2019-12-30  Brady Eidson  <beid...@apple.com>
+
+        Add WKWebView SPI to evaluate a function with arguments
+        https://bugs.webkit.org/show_bug.cgi?id=205239
+
+        Reviewed by Alex Christensen.
+
+        Covered by new API tests.
+
+        * Headers.cmake:
+        * WebCore.xcodeproj/project.pbxproj:
+        
+        * bindings/js/ExceptionDetails.h:
+        
+        * bindings/js/RunJavaScriptParameters.h: Added.
+        (WebCore::RunJavaScriptParameters::RunJavaScriptParameters):
+        (WebCore::RunJavaScriptParameters::encode const):
+        (WebCore::RunJavaScriptParameters::decode):
+        
+        * bindings/js/ScriptController.cpp:
+        (WebCore::ScriptController::executeScriptInWorldIgnoringException):
+        (WebCore::ScriptController::executeScriptInWorld):
+        (WebCore::ScriptController::callInWorld):
+        (WebCore::ScriptController::executeUserAgentScriptInWorld):
+        (WebCore::ScriptController::executeUserAgentScriptInWorldInternal):
+        (WebCore::ScriptController::executeAsynchronousUserAgentScriptInWorld):
+        * bindings/js/ScriptController.h:
+        
+        XPathGrammar changes completely unrelated to the functionality of this patch,
+        but because of our poor #include hygiene these were necessary to keep linuxes building.
+        * xml/XPathGrammar.cpp:
+        * xml/XPathGrammar.y:
+
 2019-12-30  Antti Koivisto  <an...@apple.com>
 
         Style invalidation cleanups

Modified: trunk/Source/WebCore/Headers.cmake (253949 => 253950)


--- trunk/Source/WebCore/Headers.cmake	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/Headers.cmake	2019-12-30 20:23:20 UTC (rev 253950)
@@ -240,6 +240,7 @@
     bindings/js/JSValueInWrappedObject.h
     bindings/js/JSWindowProxy.h
     bindings/js/ReadableStreamDefaultController.h
+    bindings/js/RunJavaScriptParameters.h
     bindings/js/ScriptCachedFrameData.h
     bindings/js/ScriptController.h
     bindings/js/ScriptState.h

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (253949 => 253950)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-12-30 20:23:20 UTC (rev 253950)
@@ -1533,6 +1533,7 @@
 		51BA4AC41BBB5CD800DF3D6D /* IDBDatabaseInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 51BA4AC21BBB5CBF00DF3D6D /* IDBDatabaseInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		51BA4ACB1BBC5BD900DF3D6D /* MemoryIDBBackingStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 51BA4AC91BBC5B9E00DF3D6D /* MemoryIDBBackingStore.h */; };
 		51BA4ACC1BBC5BDD00DF3D6D /* IDBBackingStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 51BA4AC71BBC5AD600DF3D6D /* IDBBackingStore.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		51BA947123AC305300444846 /* RunJavaScriptParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 51BA946F23AC305000444846 /* RunJavaScriptParameters.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		51BCCE301F8F179E006BA0ED /* ServiceWorkerThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 517C87101F8EE72E00EB8076 /* ServiceWorkerThread.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		51BE37E00DAEE00E001085FC /* StorageArea.h in Headers */ = {isa = PBXBuildFile; fileRef = 51BE37DE0DAEE00E001085FC /* StorageArea.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		51C0AA390F2AA10A001648C2 /* CachedFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C0AA380F2AA10A001648C2 /* CachedFrame.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -8336,6 +8337,7 @@
 		51BA4AC71BBC5AD600DF3D6D /* IDBBackingStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IDBBackingStore.h; sourceTree = "<group>"; };
 		51BA4AC81BBC5B9E00DF3D6D /* MemoryIDBBackingStore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryIDBBackingStore.cpp; sourceTree = "<group>"; };
 		51BA4AC91BBC5B9E00DF3D6D /* MemoryIDBBackingStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MemoryIDBBackingStore.h; sourceTree = "<group>"; };
+		51BA946F23AC305000444846 /* RunJavaScriptParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RunJavaScriptParameters.h; sourceTree = "<group>"; };
 		51BE37DE0DAEE00E001085FC /* StorageArea.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StorageArea.h; sourceTree = "<group>"; };
 		51C0AA380F2AA10A001648C2 /* CachedFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CachedFrame.h; sourceTree = "<group>"; };
 		51C0AA400F2AA15E001648C2 /* CachedFrame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CachedFrame.cpp; sourceTree = "<group>"; };
@@ -25405,6 +25407,7 @@
 				41B459DA1F4CADB90000F6FD /* ReadableStream.h */,
 				418C395E1C8F0AAB0051C8A3 /* ReadableStreamDefaultController.cpp */,
 				418C395F1C8F0AAB0051C8A3 /* ReadableStreamDefaultController.h */,
+				51BA946F23AC305000444846 /* RunJavaScriptParameters.h */,
 				41F1D21E0EF35C2A00DA8753 /* ScriptCachedFrameData.cpp */,
 				41F1D21D0EF35C2A00DA8753 /* ScriptCachedFrameData.h */,
 				93B70D5309EB0C7C009D8468 /* ScriptController.cpp */,
@@ -32131,6 +32134,7 @@
 				E4863CFE23842E9E00972158 /* RuleData.h in Headers */,
 				A79BADA2161E7F3F00C2E652 /* RuleFeature.h in Headers */,
 				A79BADA4161E7F3F00C2E652 /* RuleSet.h in Headers */,
+				51BA947123AC305300444846 /* RunJavaScriptParameters.h in Headers */,
 				2D76BB821945632400CFD29A /* RunLoopObserver.h in Headers */,
 				1A569D1F0D7E2B82007C3983 /* runtime_array.h in Headers */,
 				1A569D210D7E2B82007C3983 /* runtime_method.h in Headers */,

Modified: trunk/Source/WebCore/bindings/js/ExceptionDetails.h (253949 => 253950)


--- trunk/Source/WebCore/bindings/js/ExceptionDetails.h	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/bindings/js/ExceptionDetails.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -33,7 +33,10 @@
     String message;
     int lineNumber { 0 };
     int columnNumber { 0 };
-    String sourceURL;
+    // This bizarre explicit initialization of String is because older compilers (like on High Sierra)
+    // don't properly handle partial initialization lists unless every struct member has an explicit default value.
+    // Once we stop building on those platforms we can remove this.
+    String sourceURL { };
 };
 
 } // namespace WebCore

Added: trunk/Source/WebCore/bindings/js/RunJavaScriptParameters.h (0 => 253950)


--- trunk/Source/WebCore/bindings/js/RunJavaScriptParameters.h	                        (rev 0)
+++ trunk/Source/WebCore/bindings/js/RunJavaScriptParameters.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <wtf/HashMap.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+enum class RunAsAsyncFunction : bool { No, Yes };
+enum class ForceUserGesture : bool { No, Yes };
+
+using ArgumentWireBytesMap = HashMap<String, Vector<uint8_t>>;
+
+struct RunJavaScriptParameters {
+    RunJavaScriptParameters(String&& source, RunAsAsyncFunction runAsAsyncFunction, Optional<ArgumentWireBytesMap>&& arguments, ForceUserGesture forceUserGesture)
+        : source(WTFMove(source))
+        , runAsAsyncFunction(runAsAsyncFunction)
+        , arguments(WTFMove(arguments))
+        , forceUserGesture(forceUserGesture)
+    {
+    }
+
+    RunJavaScriptParameters(const String& source, bool runAsAsyncFunction, Optional<ArgumentWireBytesMap>&& arguments, bool forceUserGesture)
+        : source(source)
+        , runAsAsyncFunction(runAsAsyncFunction ? RunAsAsyncFunction::Yes : RunAsAsyncFunction::No)
+        , arguments(WTFMove(arguments))
+        , forceUserGesture(forceUserGesture ? ForceUserGesture::Yes : ForceUserGesture::No)
+    {
+    }
+
+    RunJavaScriptParameters(String&& source, bool runAsAsyncFunction, Optional<ArgumentWireBytesMap>&& arguments, bool forceUserGesture)
+        : source(WTFMove(source))
+        , runAsAsyncFunction(runAsAsyncFunction ? RunAsAsyncFunction::Yes : RunAsAsyncFunction::No)
+        , arguments(WTFMove(arguments))
+        , forceUserGesture(forceUserGesture ? ForceUserGesture::Yes : ForceUserGesture::No)
+    {
+    }
+
+    String source;
+    RunAsAsyncFunction runAsAsyncFunction;
+    Optional<ArgumentWireBytesMap> arguments;
+    ForceUserGesture forceUserGesture;
+
+    template<typename Encoder> void encode(Encoder& encoder) const
+    {
+        encoder << source << runAsAsyncFunction << arguments << forceUserGesture;
+    }
+
+    template<typename Decoder> static Optional<RunJavaScriptParameters> decode(Decoder& decoder)
+    {
+        String source;
+        if (!decoder.decode(source))
+            return WTF::nullopt;
+
+        RunAsAsyncFunction runAsAsyncFunction;
+        if (!decoder.decode(runAsAsyncFunction))
+            return WTF::nullopt;
+
+        Optional<ArgumentWireBytesMap> arguments;
+        if (!decoder.decode(arguments))
+            return WTF::nullopt;
+
+        ForceUserGesture forceUserGesture;
+        if (!decoder.decode(forceUserGesture))
+            return WTF::nullopt;
+
+        return { RunJavaScriptParameters { WTFMove(source), runAsAsyncFunction, WTFMove(arguments), forceUserGesture } };
+    }
+};
+
+} // namespace WebCore

Modified: trunk/Source/WebCore/bindings/js/ScriptController.cpp (253949 => 253950)


--- trunk/Source/WebCore/bindings/js/ScriptController.cpp	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/bindings/js/ScriptController.cpp	2019-12-30 20:23:20 UTC (rev 253950)
@@ -47,6 +47,7 @@
 #include "PageGroup.h"
 #include "PaymentCoordinator.h"
 #include "PluginViewBase.h"
+#include "RunJavaScriptParameters.h"
 #include "RuntimeApplicationChecks.h"
 #include "ScriptDisallowedScope.h"
 #include "ScriptSourceCode.h"
@@ -565,22 +566,118 @@
 
 JSC::JSValue ScriptController::executeScriptInWorldIgnoringException(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
 {
-    auto result = executeScriptInWorld(world, script, forceUserGesture);
+    auto result = executeScriptInWorld(world, RunJavaScriptParameters { script, false, WTF::nullopt, forceUserGesture });
     return result ? result.value() : JSC::JSValue { };
 }
 
-ValueOrException ScriptController::executeScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
+ValueOrException ScriptController::executeScriptInWorld(DOMWrapperWorld& world, RunJavaScriptParameters&& parameters)
 {
-    UserGestureIndicator gestureIndicator(forceUserGesture ? Optional<ProcessingUserGestureState>(ProcessingUserGesture) : WTF::nullopt);
-    ScriptSourceCode sourceCode(script, URL(m_frame.document()->url()), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset()));
+    UserGestureIndicator gestureIndicator(parameters.forceUserGesture == ForceUserGesture::Yes ? Optional<ProcessingUserGestureState>(ProcessingUserGesture) : WTF::nullopt);
 
     // FIXME: Instead of returning an empty JSValue, should return an ExceptionDetails.
     if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
         return { };
 
-    return evaluateInWorld(sourceCode, world);
+    switch (parameters.runAsAsyncFunction) {
+    case RunAsAsyncFunction::No: {
+        ScriptSourceCode sourceCode(parameters.source, URL(m_frame.document()->url()), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset()));
+        return evaluateInWorld(sourceCode, world);
+    }
+    case RunAsAsyncFunction::Yes:
+        return callInWorld(WTFMove(parameters), world);
+    default:
+        RELEASE_ASSERT_NOT_REACHED();
+    }
 }
 
+ValueOrException ScriptController::callInWorld(RunJavaScriptParameters&& parameters, DOMWrapperWorld& world)
+{
+    ASSERT(parameters.runAsAsyncFunction == RunAsAsyncFunction::Yes);
+    ASSERT(parameters.arguments);
+
+    auto& proxy = jsWindowProxy(world);
+    auto& globalObject = *proxy.window();
+    MarkedArgumentBuffer markedArguments;
+    StringBuilder functionStringBuilder;
+    String errorMessage;
+
+    // Build up a new script string that is an async function with arguments, and deserialize those arguments.
+    functionStringBuilder.append("(function(");
+    for (auto argument = parameters.arguments->begin(); argument != parameters.arguments->end();) {
+        functionStringBuilder.append(argument->key);
+        auto serializedArgument = SerializedScriptValue::createFromWireBytes(WTFMove(argument->value));
+
+        auto scope = DECLARE_CATCH_SCOPE(globalObject.vm());
+        auto jsArgument = serializedArgument->deserialize(globalObject, &globalObject);
+        if (UNLIKELY(scope.exception())) {
+            errorMessage = "Unable to deserialize argument to execute asynchronous _javascript_ function";
+            break;
+        }
+
+        markedArguments.append(jsArgument);
+
+        ++argument;
+        if (argument != parameters.arguments->end())
+            functionStringBuilder.append(',');
+    }
+
+    if (!errorMessage.isEmpty())
+        return makeUnexpected(ExceptionDetails { errorMessage });
+
+    functionStringBuilder.append("){", parameters.source, "})");
+
+    auto sourceCode = ScriptSourceCode { functionStringBuilder.toString(), URL(m_frame.document()->url()), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset()) };
+    const auto& jsSourceCode = sourceCode.jsSourceCode();
+
+    String sourceURL = jsSourceCode.provider()->url();
+    const String* savedSourceURL = m_sourceURL;
+    m_sourceURL = &sourceURL;
+
+    Ref<Frame> protector(m_frame);
+
+    InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine(), sourceCode.startColumn());
+
+    NakedPtr<JSC::Exception> evaluationException;
+    Optional<ExceptionDetails> optionalDetails;
+    JSValue returnValue;
+    do {
+        JSValue functionObject = JSExecState::profiledEvaluate(&globalObject, JSC::ProfilingReason::Other, jsSourceCode, &proxy, evaluationException);
+
+        if (evaluationException)
+            break;
+
+        if (!functionObject || !functionObject.isFunction(world.vm())) {
+            optionalDetails = { { "Unable to create _javascript_ async function to call"_s } };
+            break;
+        }
+
+        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=205562
+        // Getting CallData/CallType shouldn't be required to call into JS.
+        CallData callData;
+        CallType callType = getCallData(world.vm(), functionObject, callData);
+        if (callType == CallType::None) {
+            optionalDetails = { { "Unable to prepare _javascript_ async function to be called"_s } };
+            break;
+        }
+
+        returnValue = JSExecState::profiledCall(&globalObject, JSC::ProfilingReason::Other, functionObject, callType, callData, &proxy, markedArguments, evaluationException);
+    } while (false);
+
+    InspectorInstrumentation::didEvaluateScript(m_frame);
+
+    if (evaluationException && !optionalDetails) {
+        ExceptionDetails details;
+        reportException(&globalObject, evaluationException, sourceCode.cachedScript(), &details);
+        optionalDetails = WTFMove(details);
+    }
+
+    m_sourceURL = savedSourceURL;
+
+    if (optionalDetails)
+        return makeUnexpected(*optionalDetails);
+    return returnValue;
+}
+
 JSC::JSValue ScriptController::executeUserAgentScriptInWorldIgnoringException(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
 {
     auto result = executeUserAgentScriptInWorld(world, script, forceUserGesture);
@@ -588,14 +685,28 @@
 }
 ValueOrException ScriptController::executeUserAgentScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture)
 {
+    return executeUserAgentScriptInWorldInternal(world, { script, false, WTF::nullopt, forceUserGesture });
+}
+
+ValueOrException ScriptController::executeUserAgentScriptInWorldInternal(DOMWrapperWorld& world, RunJavaScriptParameters&& parameters)
+{
     auto& document = *m_frame.document();
     if (!shouldAllowUserAgentScripts(document))
-        return makeUnexpected(ExceptionDetails { "Unable to run user agent scripts in this document for security reasons"_s, 0, 0, { } });
+        return makeUnexpected(ExceptionDetails { "Unable to run user agent scripts in this document for security reasons"_s });
 
     document.setHasEvaluatedUserAgentScripts();
-    return executeScriptInWorld(world, script, forceUserGesture);
+    return executeScriptInWorld(world, WTFMove(parameters));
 }
 
+void ScriptController::executeAsynchronousUserAgentScriptInWorld(DOMWrapperWorld& world, RunJavaScriptParameters&& parameters, ResolveFunction&& resolveFunction)
+{
+    auto result = executeUserAgentScriptInWorldInternal(world, WTFMove(parameters));
+
+    // FIXME: If the result is a thenable, install the fulfill/reject handlers instead of resolving now.
+
+    resolveFunction(result);
+}
+
 bool ScriptController::shouldAllowUserAgentScripts(Document& document) const
 {
 #if ENABLE(APPLE_PAY)

Modified: trunk/Source/WebCore/bindings/js/ScriptController.h (253949 => 253950)


--- trunk/Source/WebCore/bindings/js/ScriptController.h	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/bindings/js/ScriptController.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -23,6 +23,7 @@
 
 #include "FrameLoaderTypes.h"
 #include "JSWindowProxy.h"
+#include "SerializedScriptValue.h"
 #include "WindowProxy.h"
 #include <_javascript_Core/JSBase.h>
 #include <_javascript_Core/Strong.h>
@@ -63,7 +64,11 @@
 class ScriptSourceCode;
 class SecurityOrigin;
 class Widget;
+
+enum class RunAsAsyncFunction : bool;
+
 struct ExceptionDetails;
+struct RunJavaScriptParameters;
 
 enum ReasonForCallingCanExecuteScripts {
     AboutToCreateEventListener,
@@ -91,11 +96,15 @@
 
     static void getAllWorlds(Vector<Ref<DOMWrapperWorld>>&);
 
+    using ResolveFunction = CompletionHandler<void(ValueOrException)>;
+
     WEBCORE_EXPORT JSC::JSValue executeScriptIgnoringException(const String& script, bool forceUserGesture = false);
     JSC::JSValue executeScriptInWorldIgnoringException(DOMWrapperWorld&, const String& script, bool forceUserGesture = false);
-    ValueOrException executeScriptInWorld(DOMWrapperWorld&, const String& script, bool forceUserGesture = false);
     WEBCORE_EXPORT JSC::JSValue executeUserAgentScriptInWorldIgnoringException(DOMWrapperWorld&, const String& script, bool forceUserGesture);
     WEBCORE_EXPORT ValueOrException executeUserAgentScriptInWorld(DOMWrapperWorld&, const String& script, bool forceUserGesture);
+    WEBCORE_EXPORT void executeAsynchronousUserAgentScriptInWorld(DOMWrapperWorld&, RunJavaScriptParameters&&, ResolveFunction&&);
+    JSC::JSValue evaluateIgnoringException(const ScriptSourceCode&);
+    JSC::JSValue evaluateInWorldIgnoringException(const ScriptSourceCode&, DOMWrapperWorld&);
 
     bool shouldAllowUserAgentScripts(Document&) const;
 
@@ -106,10 +115,6 @@
     // Darwin is an exception to this rule: it is OK to call this function from any thread, even reentrantly.
     static void initializeThreading();
 
-    JSC::JSValue evaluateIgnoringException(const ScriptSourceCode&);
-    JSC::JSValue evaluateInWorldIgnoringException(const ScriptSourceCode&, DOMWrapperWorld&);
-    ValueOrException evaluateInWorld(const ScriptSourceCode&, DOMWrapperWorld&);
-
     void loadModuleScriptInWorld(LoadableModuleScript&, const String& moduleName, Ref<ModuleFetchParameters>&&, DOMWrapperWorld&);
     void loadModuleScript(LoadableModuleScript&, const String& moduleName, Ref<ModuleFetchParameters>&&);
     void loadModuleScriptInWorld(LoadableModuleScript&, const ScriptSourceCode&, DOMWrapperWorld&);
@@ -171,6 +176,11 @@
     bool willReplaceWithResultOfExecutingJavascriptURL() const { return m_willReplaceWithResultOfExecutingJavascriptURL; }
 
 private:
+    ValueOrException executeUserAgentScriptInWorldInternal(DOMWrapperWorld&, RunJavaScriptParameters&&);
+    ValueOrException executeScriptInWorld(DOMWrapperWorld&, RunJavaScriptParameters&&);
+    ValueOrException evaluateInWorld(const ScriptSourceCode&, DOMWrapperWorld&);
+    ValueOrException callInWorld(RunJavaScriptParameters&&, DOMWrapperWorld&);
+    
     void setupModuleScriptHandlers(LoadableModuleScript&, JSC::JSInternalPromise&, DOMWrapperWorld&);
 
     void disconnectPlatformScriptObjects();

Modified: trunk/Source/WebCore/xml/XPathGrammar.cpp (253949 => 253950)


--- trunk/Source/WebCore/xml/XPathGrammar.cpp	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/xml/XPathGrammar.cpp	2019-12-30 20:23:20 UTC (rev 253950)
@@ -1969,7 +1969,7 @@
 #line 402 "WebCore/xml/XPathGrammar.y"
     {
         (yyvsp[(3) - (3)].locationPath)->setAbsolute();
-        (yyval._expression_) = new Path(std::unique_ptr<_expression_>((yyvsp[(1) - (3)]._expression_)), std::unique_ptr<LocationPath>((yyvsp[(3) - (3)].locationPath)));
+        (yyval._expression_) = new XPath::Path(std::unique_ptr<_expression_>((yyvsp[(1) - (3)]._expression_)), std::unique_ptr<LocationPath>((yyvsp[(3) - (3)].locationPath)));
     ;}
     break;
 
@@ -1978,7 +1978,7 @@
     {
         (yyvsp[(3) - (3)].locationPath)->prependStep(std::unique_ptr<Step>((yyvsp[(2) - (3)].step)));
         (yyvsp[(3) - (3)].locationPath)->setAbsolute();
-        (yyval._expression_) = new Path(std::unique_ptr<_expression_>((yyvsp[(1) - (3)]._expression_)), std::unique_ptr<LocationPath>((yyvsp[(3) - (3)].locationPath)));
+        (yyval._expression_) = new XPath::Path(std::unique_ptr<_expression_>((yyvsp[(1) - (3)]._expression_)), std::unique_ptr<LocationPath>((yyvsp[(3) - (3)].locationPath)));
     ;}
     break;
 

Modified: trunk/Source/WebCore/xml/XPathGrammar.y (253949 => 253950)


--- trunk/Source/WebCore/xml/XPathGrammar.y	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebCore/xml/XPathGrammar.y	2019-12-30 20:23:20 UTC (rev 253950)
@@ -401,7 +401,7 @@
     FilterExpr '/' RelativeLocationPath
     {
         $3->setAbsolute();
-        $$ = new Path(std::unique_ptr<_expression_>($1), std::unique_ptr<LocationPath>($3));
+        $$ = new XPath::Path(std::unique_ptr<_expression_>($1), std::unique_ptr<LocationPath>($3));
     }
     |
     FilterExpr DescendantOrSelf RelativeLocationPath
@@ -408,7 +408,7 @@
     {
         $3->prependStep(std::unique_ptr<Step>($2));
         $3->setAbsolute();
-        $$ = new Path(std::unique_ptr<_expression_>($1), std::unique_ptr<LocationPath>($3));
+        $$ = new XPath::Path(std::unique_ptr<_expression_>($1), std::unique_ptr<LocationPath>($3));
     }
     ;
 

Modified: trunk/Source/WebKit/ChangeLog (253949 => 253950)


--- trunk/Source/WebKit/ChangeLog	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/ChangeLog	2019-12-30 20:23:20 UTC (rev 253950)
@@ -1,3 +1,49 @@
+2019-12-30  Brady Eidson  <beid...@apple.com>
+
+        Add WKWebView SPI to evaluate a function with arguments
+        https://bugs.webkit.org/show_bug.cgi?id=205239
+
+        Reviewed by Alex Christensen.
+
+        * Shared/API/APISerializedScriptValue.h:
+
+        * UIProcess/API/C/WKPage.cpp:
+        (WKPageRunJavaScriptInMainFrame):
+
+        * UIProcess/API/Cocoa/APISerializedScriptValueCocoa.mm:
+        (API::sharedContext):
+        (API::SerializedScriptValue::deserialize):
+        (API::SerializedScriptValue::wireBytesFromNSObject):
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView evaluateJavaScript:completionHandler:]):
+        (validateArgument):
+        (-[WKWebView _evaluateJavaScript:asAsyncFunction:withArguments:forceUserGesture:completionHandler:]):
+        (-[WKWebView _callAsyncFunction:withArguments:completionHandler:]):
+        (-[WKWebView _evaluateJavaScriptWithoutUserGesture:completionHandler:]):
+        (-[WKWebView _evaluateJavaScript:forceUserGesture:completionHandler:]): Deleted.
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+
+        * UIProcess/API/glib/WebKitWebView.cpp:
+        (webkit_web_view_run_javascript):
+        (webkit_web_view_run_javascript_in_world):
+        (resourcesStreamReadCallback):
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::runJavaScriptInMainFrame):
+        (WebKit::WebPageProxy::runJavaScriptInMainFrameScriptWorld):
+        * UIProcess/WebPageProxy.h:
+
+        * UIProcess/socket/RemoteInspectorProtocolHandler.cpp:
+        (WebKit::RemoteInspectorProtocolHandler::runScript):
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::runJavaScript):
+        (WebKit::WebPage::runJavaScriptInMainFrameScriptWorld):
+        (WebKit::WebPage::runJavaScriptInFrame):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
 2019-12-29  Peng Liu  <peng.l...@apple.com>
 
         Tweak the format and comment in the code to support media in GPU process

Modified: trunk/Source/WebKit/Shared/API/APISerializedScriptValue.h (253949 => 253950)


--- trunk/Source/WebKit/Shared/API/APISerializedScriptValue.h	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/Shared/API/APISerializedScriptValue.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -61,6 +61,7 @@
     
 #if PLATFORM(COCOA) && defined(__OBJC__)
     static id deserialize(WebCore::SerializedScriptValue&, JSValueRef* exception);
+    static Optional<Vector<uint8_t>> wireBytesFromNSObject(id);
 #endif
 
     IPC::DataReference dataReference() const { return m_serializedScriptValue->data(); }

Modified: trunk/Source/WebKit/UIProcess/API/C/WKPage.cpp (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/API/C/WKPage.cpp	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/API/C/WKPage.cpp	2019-12-30 20:23:20 UTC (rev 253950)
@@ -2472,7 +2472,7 @@
 
 void WKPageRunJavaScriptInMainFrame(WKPageRef pageRef, WKStringRef scriptRef, void* context, WKPageRunJavaScriptFunction callback)
 {
-    toImpl(pageRef)->runJavaScriptInMainFrame(toImpl(scriptRef)->string(), true, [context, callback](API::SerializedScriptValue* returnValue, Optional<WebCore::ExceptionDetails>, CallbackBase::Error error) {
+    toImpl(pageRef)->runJavaScriptInMainFrame({ toImpl(scriptRef)->string(), false, WTF::nullopt, true }, [context, callback](API::SerializedScriptValue* returnValue, Optional<WebCore::ExceptionDetails>, CallbackBase::Error error) {
         callback(toAPI(returnValue), (error != CallbackBase::Error::None) ? toAPI(API::Error::create().ptr()) : 0, context);
     });
 }

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/APISerializedScriptValueCocoa.mm (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/APISerializedScriptValueCocoa.mm	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/APISerializedScriptValueCocoa.mm	2019-12-30 20:23:20 UTC (rev 253950)
@@ -26,7 +26,9 @@
 #include "config.h"
 #include "APISerializedScriptValue.h"
 
+#import <_javascript_Core/APICast.h>
 #import <_javascript_Core/JSContext.h>
+#import <_javascript_Core/JSGlobalObjectInlines.h>
 #import <_javascript_Core/JSValue.h>
 #import <wtf/NeverDestroyed.h>
 #import <wtf/RunLoop.h>
@@ -59,11 +61,16 @@
     RunLoop::Timer<SharedJSContext> m_timer;
 };
 
+static SharedJSContext& sharedContext()
+{
+    static NeverDestroyed<SharedJSContext> sharedContext;
+    return sharedContext.get();
+}
+
 id SerializedScriptValue::deserialize(WebCore::SerializedScriptValue& serializedScriptValue, JSValueRef* exception)
 {
     ASSERT(RunLoop::isMain());
-    static NeverDestroyed<SharedJSContext> sharedContext;
-    JSContext* context = sharedContext.get().ensureContext();
+    JSContext* context = sharedContext().ensureContext();
 
     JSValueRef valueRef = serializedScriptValue.deserialize([context JSGlobalContextRef], exception);
     if (!valueRef)
@@ -73,4 +80,20 @@
     return value.toObject;
 }
 
+Optional<Vector<uint8_t>> SerializedScriptValue::wireBytesFromNSObject(id object)
+{
+    ASSERT(RunLoop::isMain());
+    JSContext* context = sharedContext().ensureContext();
+    JSValue *value = [JSValue valueWithObject:object inContext:context];
+    if (!value)
+        return WTF::nullopt;
+
+    auto globalObject = toJS([context JSGlobalContextRef]);
+    ASSERT(globalObject);
+    JSC::JSLockHolder lock(globalObject);
+
+    auto coreValue = WebCore::SerializedScriptValue::create(*globalObject, toJS(globalObject, [value JSValueRef]));
+    return coreValue->toWireBytes();
+}
+
 } // API

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2019-12-30 20:23:20 UTC (rev 253950)
@@ -822,14 +822,81 @@
 
 - (void)evaluateJavaScript:(NSString *)_javascript_String completionHandler:(void (^)(id, NSError *))completionHandler
 {
-    [self _evaluateJavaScript:_javascript_String forceUserGesture:YES completionHandler:completionHandler];
+    [self _evaluateJavaScript:_javascript_String asAsyncFunction:NO withArguments:nil forceUserGesture:YES completionHandler:completionHandler];
 }
 
-- (void)_evaluateJavaScript:(NSString *)_javascript_String forceUserGesture:(BOOL)forceUserGesture completionHandler:(void (^)(id, NSError *))completionHandler
+static bool validateArgument(id argument)
 {
+    if ([argument isKindOfClass:[NSString class]] || [argument isKindOfClass:[NSNumber class]] || [argument isKindOfClass:[NSDate class]] || [argument isKindOfClass:[NSNull class]])
+        return true;
+
+    if ([argument isKindOfClass:[NSArray class]]) {
+        __block BOOL valid = true;
+
+        [argument enumerateObjectsUsingBlock:^(id object, NSUInteger, BOOL *stop) {
+            if (!validateArgument(object)) {
+                valid = false;
+                *stop = YES;
+            }
+        }];
+
+        return valid;
+    }
+
+    if ([argument isKindOfClass:[NSDictionary class]]) {
+        __block bool valid = true;
+
+        [argument enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
+            if (!validateArgument(key) || !validateArgument(value)) {
+                valid = false;
+                *stop = YES;
+            }
+        }];
+
+        return valid;
+    }
+
+    return false;
+}
+
+- (void)_evaluateJavaScript:(NSString *)_javascript_String asAsyncFunction:(BOOL)asAsyncFunction withArguments:(NSDictionary<NSString *, id> *)arguments forceUserGesture:(BOOL)forceUserGesture completionHandler:(void (^)(id, NSError *))completionHandler
+{
     auto handler = adoptNS([completionHandler copy]);
 
-    _page->runJavaScriptInMainFrame(_javascript_String, forceUserGesture, [handler](API::SerializedScriptValue* serializedScriptValue, Optional<WebCore::ExceptionDetails> details, WebKit::ScriptValueCallback::Error errorCode) {
+    Optional<WebCore::ArgumentWireBytesMap> argumentsMap;
+    if (asAsyncFunction)
+        argumentsMap = WebCore::ArgumentWireBytesMap { };
+    NSString *errorMessage = nil;
+
+    for (id key in arguments) {
+        id value = [arguments objectForKey:key];
+        if (!validateArgument(value)) {
+            errorMessage = @"Function argument values must be one of the following types, or contain only the following types: NSString, NSNumber, NSDate, NSArray, and NSDictionary";
+            break;
+        }
+    
+        auto wireBytes = API::SerializedScriptValue::wireBytesFromNSObject(value);
+        // Since we've validated the input dictionary above, we should never fail to serialize it into wire bytes.
+        ASSERT(wireBytes);
+        argumentsMap->set(key, *wireBytes);
+    }
+
+    if (errorMessage) {
+        RetainPtr<NSMutableDictionary> userInfo = adoptNS([[NSMutableDictionary alloc] init]);
+
+        [userInfo setObject:localizedDescriptionForErrorCode(WKErrorJavaScriptExceptionOccurred) forKey:NSLocalizedDescriptionKey];
+        [userInfo setObject:errorMessage forKey:_WKJavaScriptExceptionMessageErrorKey];
+
+        auto error = adoptNS([[NSError alloc] initWithDomain:WKErrorDomain code:WKErrorJavaScriptExceptionOccurred userInfo:userInfo.get()]);
+        dispatch_async(dispatch_get_main_queue(), [handler, error] {
+            auto rawHandler = (void (^)(id, NSError *))handler.get();
+            rawHandler(nil, error.get());
+        });
+
+        return;
+    }
+
+    _page->runJavaScriptInMainFrame(WebCore::RunJavaScriptParameters { _javascript_String, !!asAsyncFunction, WTFMove(argumentsMap), !!forceUserGesture }, [handler](API::SerializedScriptValue* serializedScriptValue, Optional<WebCore::ExceptionDetails> details, WebKit::ScriptValueCallback::Error errorCode) {
         if (!handler)
             return;
 
@@ -1923,6 +1990,11 @@
     [self createPDFWithConfiguration:pdfConfiguration completionHandler:completionHandler];
 }
 
+- (void)_callAsyncFunction:(NSString *)_javascript_String withArguments:(NSDictionary<NSString *, id> *)arguments completionHandler:(void (^)(id, NSError *error))completionHandler
+{
+    [self _evaluateJavaScript:_javascript_String asAsyncFunction:YES withArguments:arguments forceUserGesture:YES completionHandler:completionHandler];
+}
+
 - (NSData *)_sessionStateData
 {
     // FIXME: This should not use the legacy session state encoder.
@@ -2065,7 +2137,7 @@
 
 - (void)_evaluateJavaScriptWithoutUserGesture:(NSString *)_javascript_String completionHandler:(void (^)(id, NSError *))completionHandler
 {
-    [self _evaluateJavaScript:_javascript_String forceUserGesture:NO completionHandler:completionHandler];
+    [self _evaluateJavaScript:_javascript_String asAsyncFunction:NO withArguments:nil forceUserGesture:NO completionHandler:completionHandler];
 }
 
 - (void)_updateWebsitePolicies:(_WKWebsitePolicies *)websitePolicies

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -315,6 +315,9 @@
 - (void)_focusTextInputContext:(_WKTextInputContext *)textInputElement completionHandler:(void(^)(BOOL))completionHandler WK_API_AVAILABLE(macos(10.15), ios(13.0));
 
 - (void)_takePDFSnapshotWithConfiguration:(WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(NSData *pdfSnapshotData, NSError *error))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+
+- (void)_callAsyncFunction:(NSString *)_javascript_String withArguments:(NSDictionary<NSString *, id> *)arguments completionHandler:(void (^)(id, NSError *error))completionHandler;
+
 @end
 
 #if TARGET_OS_IPHONE

Modified: trunk/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp	2019-12-30 20:23:20 UTC (rev 253950)
@@ -3636,7 +3636,7 @@
     g_return_if_fail(script);
 
     GRefPtr<GTask> task = adoptGRef(g_task_new(webView, cancellable, callback, userData));
-    getPage(webView).runJavaScriptInMainFrame(String::fromUTF8(script), true, [task = WTFMove(task)](API::SerializedScriptValue* serializedScriptValue, Optional<ExceptionDetails> details, WebKit::CallbackBase::Error) {
+    getPage(webView).runJavaScriptInMainFrame({ String::fromUTF8(script), false, WTF::nullopt, true }, [task = WTFMove(task)](API::SerializedScriptValue* serializedScriptValue, Optional<ExceptionDetails> details, WebKit::CallbackBase::Error) {
         ExceptionDetails exceptionDetails;
         if (details)
             exceptionDetails = *details;
@@ -3737,7 +3737,7 @@
     g_return_if_fail(worldName);
 
     GRefPtr<GTask> task = adoptGRef(g_task_new(webView, cancellable, callback, userData));
-    getPage(webView).runJavaScriptInMainFrameScriptWorld(String::fromUTF8(script), true, String::fromUTF8(worldName), [task = WTFMove(task)](API::SerializedScriptValue* serializedScriptValue, Optional<ExceptionDetails> details, WebKit::CallbackBase::Error) {
+    getPage(webView).runJavaScriptInMainFrameScriptWorld({ String::fromUTF8(script), false, WTF::nullopt, true }, String::fromUTF8(worldName), [task = WTFMove(task)](API::SerializedScriptValue* serializedScriptValue, Optional<ExceptionDetails> details, WebKit::CallbackBase::Error) {
         ExceptionDetails exceptionDetails;
         if (details)
             exceptionDetails = *details;
@@ -3779,7 +3779,7 @@
 
     WebKitWebView* webView = WEBKIT_WEB_VIEW(g_task_get_source_object(task.get()));
     gpointer outputStreamData = g_memory_output_stream_get_data(G_MEMORY_OUTPUT_STREAM(object));
-    getPage(webView).runJavaScriptInMainFrame(String::fromUTF8(reinterpret_cast<const gchar*>(outputStreamData)), true,
+    getPage(webView).runJavaScriptInMainFrame({ String::fromUTF8(reinterpret_cast<const gchar*>(outputStreamData)), false, WTF::nullopt, true},
         [task](API::SerializedScriptValue* serializedScriptValue, Optional<ExceptionDetails> details, WebKit::CallbackBase::Error) {
             ExceptionDetails exceptionDetails;
             if (details)

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2019-12-30 20:23:20 UTC (rev 253950)
@@ -3849,12 +3849,12 @@
         launchProcess({ }, ProcessLaunchReason::InitialProcess);
 }
 
-void WebPageProxy::runJavaScriptInMainFrame(const String& script, bool forceUserGesture, WTF::Function<void (API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails>, CallbackBase::Error)>&& callbackFunction)
+void WebPageProxy::runJavaScriptInMainFrame(RunJavaScriptParameters&& parameters, WTF::Function<void (API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails>, CallbackBase::Error)>&& callbackFunction)
 {
-    runJavaScriptInMainFrameScriptWorld(script, forceUserGesture, WTF::nullopt, WTFMove(callbackFunction));
+    runJavaScriptInMainFrameScriptWorld(WTFMove(parameters), WTF::nullopt, WTFMove(callbackFunction));
 }
 
-void WebPageProxy::runJavaScriptInMainFrameScriptWorld(const String& script, bool forceUserGesture, const Optional<String>& worldName, WTF::Function<void(API::SerializedScriptValue*, Optional<ExceptionDetails>, CallbackBase::Error)>&& callbackFunction)
+void WebPageProxy::runJavaScriptInMainFrameScriptWorld(RunJavaScriptParameters&& parameters, const Optional<String>& worldName, WTF::Function<void(API::SerializedScriptValue*, Optional<ExceptionDetails>, CallbackBase::Error)>&& callbackFunction)
 {
     // For backward-compatibility support running script in a WebView which has not done any loads yets.
     launchInitialProcessIfNecessary();
@@ -3865,7 +3865,7 @@
     }
 
     auto callbackID = m_callbacks.put(WTFMove(callbackFunction), m_process->throttler().backgroundActivity("WebPageProxy::runJavaScriptInMainFrameScriptWorld"_s));
-    send(Messages::WebPage::RunJavaScriptInMainFrameScriptWorld(script, forceUserGesture, worldName, callbackID));
+    send(Messages::WebPage::RunJavaScriptInMainFrameScriptWorld(parameters, worldName, callbackID));
 }
 
 void WebPageProxy::runJavaScriptInFrame(FrameIdentifier frameID, const String& script, bool forceUserGesture, WTF::Function<void(API::SerializedScriptValue*, Optional<ExceptionDetails>, CallbackBase::Error)>&& callbackFunction)

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.h	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -92,6 +92,7 @@
 #include <WebCore/PlatformSpeechSynthesizer.h>
 #include <WebCore/PointerID.h>
 #include <WebCore/RegistrableDomain.h>
+#include <WebCore/RunJavaScriptParameters.h>
 #include <WebCore/ScrollTypes.h>
 #include <WebCore/SearchPopupMenu.h>
 #include <WebCore/TextChecking.h>
@@ -1062,8 +1063,8 @@
     void getSelectionAsWebArchiveData(Function<void (API::Data*, CallbackBase::Error)>&&);
     void getSourceForFrame(WebFrameProxy*, WTF::Function<void (const String&, CallbackBase::Error)>&&);
     void getWebArchiveOfFrame(WebFrameProxy*, Function<void (API::Data*, CallbackBase::Error)>&&);
-    void runJavaScriptInMainFrame(const String&, bool, WTF::Function<void (API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails>, CallbackBase::Error)>&& callbackFunction);
-    void runJavaScriptInMainFrameScriptWorld(const String&, bool, const Optional<String>& worldName, WTF::Function<void(API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails>, CallbackBase::Error)>&& callbackFunction);
+    void runJavaScriptInMainFrame(WebCore::RunJavaScriptParameters&&, WTF::Function<void (API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails>, CallbackBase::Error)>&& callbackFunction);
+    void runJavaScriptInMainFrameScriptWorld(WebCore::RunJavaScriptParameters&&, const Optional<String>& worldName, WTF::Function<void(API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails>, CallbackBase::Error)>&& callbackFunction);
     // For sub frames.
     void runJavaScriptInFrame(WebCore::FrameIdentifier, const String& script, bool forceUserGesture, WTF::Function<void(API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails>, CallbackBase::Error)>&& callbackFunction);
     void forceRepaint(RefPtr<VoidCallback>&&);

Modified: trunk/Source/WebKit/UIProcess/socket/RemoteInspectorProtocolHandler.cpp (253949 => 253950)


--- trunk/Source/WebKit/UIProcess/socket/RemoteInspectorProtocolHandler.cpp	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/UIProcess/socket/RemoteInspectorProtocolHandler.cpp	2019-12-30 20:23:20 UTC (rev 253950)
@@ -126,7 +126,7 @@
 
 void RemoteInspectorProtocolHandler::runScript(const String& script)
 {
-    m_page.runJavaScriptInMainFrame(script, false,
+    m_page.runJavaScriptInMainFrame({ script, false, WTF::nullopt, false }, 
         [](API::SerializedScriptValue*, Optional<WebCore::ExceptionDetails> exceptionDetails, CallbackBase::Error) {
             if (exceptionDetails)
                 LOG_ERROR("Exception running script \"%s\"", exceptionDetails->message.utf8().data());

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (253949 => 253950)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-12-30 20:23:20 UTC (rev 253950)
@@ -214,6 +214,7 @@
 #include <WebCore/ResourceLoadStatistics.h>
 #include <WebCore/ResourceRequest.h>
 #include <WebCore/ResourceResponse.h>
+#include <WebCore/RunJavaScriptParameters.h>
 #include <WebCore/RuntimeEnabledFeatures.h>
 #include <WebCore/SWClientConnection.h>
 #include <WebCore/ScriptController.h>
@@ -3352,38 +3353,42 @@
     return static_cast<KeyboardUIMode>((fullKeyboardAccessEnabled ? KeyboardAccessFull : KeyboardAccessDefault) | (m_tabToLinks ? KeyboardAccessTabsToLinks : 0));
 }
 
-void WebPage::runJavaScript(WebFrame* frame, const String& script, bool forceUserGesture, const Optional<String>& worldName, CallbackID callbackID)
+void WebPage::runJavaScript(WebFrame* frame, RunJavaScriptParameters&& parameters, const Optional<String>& worldName, CallbackID callbackID)
 {
     // NOTE: We need to be careful when running scripts that the objects we depend on don't
     // disappear during script execution.
 
-    JSLockHolder lock(commonVM());
-    ValueOrException result;
-    RefPtr<SerializedScriptValue> serializedResultValue;
+    auto* world = worldName ? InjectedBundleScriptWorld::find(worldName.value()) : &InjectedBundleScriptWorld::normalWorld();
+    if (!frame || !frame->coreFrame() || !world) {
+        send(Messages::WebPageProxy::ScriptValueCallback({ }, ExceptionDetails { "Unable to execute _javascript_: Page is in invalid state"_s }, callbackID));
+        return;
+    }
 
-    auto* world = worldName ? InjectedBundleScriptWorld::find(worldName.value()) : &InjectedBundleScriptWorld::normalWorld();
-    if (frame && frame->coreFrame() && world) {
-        result = frame->coreFrame()->script().executeUserAgentScriptInWorld(world->coreWorld(), script, forceUserGesture);
+    auto resolveFunction = [protectedThis = makeRef(*this), this, world = makeRef(*world), frame = makeRef(*frame), callbackID](ValueOrException result) {
+        RefPtr<SerializedScriptValue> serializedResultValue;
         if (result) {
-            serializedResultValue = SerializedScriptValue::create(frame->jsContextForWorld(world),
+            serializedResultValue = SerializedScriptValue::create(frame->jsContextForWorld(world.ptr()),
                 toRef(frame->coreFrame()->script().globalObject(world->coreWorld()), result.value()), nullptr);
         }
-    }
 
-    IPC::DataReference dataReference;
-    if (serializedResultValue)
-        dataReference = serializedResultValue->data();
+        IPC::DataReference dataReference;
+        if (serializedResultValue)
+            dataReference = serializedResultValue->data();
 
-    Optional<ExceptionDetails> details;
-    if (!result)
-        details = result.error();
+        Optional<ExceptionDetails> details;
+        if (!result)
+            details = result.error();
 
-    send(Messages::WebPageProxy::ScriptValueCallback(dataReference, details, callbackID));
+        send(Messages::WebPageProxy::ScriptValueCallback(dataReference, details, callbackID));
+    };
+
+    JSLockHolder lock(commonVM());
+    frame->coreFrame()->script().executeAsynchronousUserAgentScriptInWorld(world->coreWorld(), WTFMove(parameters), WTFMove(resolveFunction));
 }
 
-void WebPage::runJavaScriptInMainFrameScriptWorld(const String& script, bool forceUserGesture, const Optional<String>& worldName, CallbackID callbackID)
+void WebPage::runJavaScriptInMainFrameScriptWorld(RunJavaScriptParameters&& parameters, const Optional<String>& worldName, CallbackID callbackID)
 {
-    runJavaScript(mainWebFrame(), script, forceUserGesture, worldName, callbackID);
+    runJavaScript(mainWebFrame(), WTFMove(parameters), worldName, callbackID);
 }
 
 void WebPage::runJavaScriptInFrame(FrameIdentifier frameID, const String& script, bool forceUserGesture, CallbackID callbackID)
@@ -3390,7 +3395,7 @@
 {
     WebFrame* frame = WebProcess::singleton().webFrame(frameID);
     ASSERT(mainWebFrame() != frame);
-    runJavaScript(frame, script, forceUserGesture, WTF::nullopt, callbackID);
+    runJavaScript(frame, { script, false, WTF::nullopt, forceUserGesture }, WTF::nullopt, callbackID);
 }
 
 void WebPage::getContentsAsString(CallbackID callbackID)

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (253949 => 253950)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -197,6 +197,7 @@
 struct Highlight;
 struct KeypressCommand;
 struct PromisedAttachmentInfo;
+struct RunJavaScriptParameters;
 struct TextCheckingResult;
 struct ViewportArguments;
 
@@ -1435,8 +1436,8 @@
     void getSelectionAsWebArchiveData(CallbackID);
     void getSourceForFrame(WebCore::FrameIdentifier, CallbackID);
     void getWebArchiveOfFrame(WebCore::FrameIdentifier, CallbackID);
-    void runJavaScript(WebFrame*, const String&, bool forceUserGesture, const Optional<String>& worldName, CallbackID);
-    void runJavaScriptInMainFrameScriptWorld(const String&, bool forceUserGesture, const Optional<String>& worldName, CallbackID);
+    void runJavaScript(WebFrame*, WebCore::RunJavaScriptParameters&&, const Optional<String>& worldName, CallbackID);
+    void runJavaScriptInMainFrameScriptWorld(WebCore::RunJavaScriptParameters&&, const Optional<String>& worldName, CallbackID);
     void runJavaScriptInFrame(WebCore::FrameIdentifier, const String&, bool forceUserGesture, CallbackID);
     void forceRepaint(CallbackID);
     void takeSnapshot(WebCore::IntRect snapshotRect, WebCore::IntSize bitmapSize, uint32_t options, CallbackID);

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in (253949 => 253950)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in	2019-12-30 20:23:20 UTC (rev 253950)
@@ -204,7 +204,7 @@
     GetSelectionAsWebArchiveData(WebKit::CallbackID callbackID)
     GetSourceForFrame(WebCore::FrameIdentifier frameID, WebKit::CallbackID callbackID)
     GetWebArchiveOfFrame(WebCore::FrameIdentifier frameID, WebKit::CallbackID callbackID)
-    RunJavaScriptInMainFrameScriptWorld(String script, bool forceUserGesture, Optional<String> worldName, WebKit::CallbackID callbackID)
+    RunJavaScriptInMainFrameScriptWorld(struct WebCore::RunJavaScriptParameters parameters, Optional<String> worldName, WebKit::CallbackID callbackID)
     RunJavaScriptInFrame(WebCore::FrameIdentifier frameID, String script, bool forceUserGesture, WebKit::CallbackID callbackID)
     ForceRepaint(WebKit::CallbackID callbackID)
 

Modified: trunk/Tools/ChangeLog (253949 => 253950)


--- trunk/Tools/ChangeLog	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Tools/ChangeLog	2019-12-30 20:23:20 UTC (rev 253950)
@@ -1,3 +1,17 @@
+2019-12-30  Brady Eidson  <beid...@apple.com>
+
+        Add WKWebView SPI to evaluate a function with arguments
+        https://bugs.webkit.org/show_bug.cgi?id=205239
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm: Added.
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[WKWebView objectByCallingAsyncFunction:withArguments:error:]):
+
 2019-12-30  Carlos Alberto Lopez Perez  <clo...@igalia.com>
 
         [GTK][WPE] Report number of total tests run and failed in API test runner like run-api-tests does

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (253949 => 253950)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2019-12-30 20:23:20 UTC (rev 253950)
@@ -290,6 +290,7 @@
 		51AF23DF1EF1A3730072F281 /* IconLoadingDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51AF23DE1EF1A3720072F281 /* IconLoadingDelegate.mm */; };
 		51B1EE961C80FAEF0064FB98 /* IndexedDBPersistence-1.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51B1EE941C80FADD0064FB98 /* IndexedDBPersistence-1.html */; };
 		51B1EE971C80FAEF0064FB98 /* IndexedDBPersistence-2.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51B1EE951C80FADD0064FB98 /* IndexedDBPersistence-2.html */; };
+		51B40D9E23AC962400E05241 /* AsyncFunction.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51B40D9D23AC960E00E05241 /* AsyncFunction.mm */; };
 		51BCEE4E1C84F53B0042C82E /* IndexedDBMultiProcess-1.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51BCEE4C1C84F52C0042C82E /* IndexedDBMultiProcess-1.html */; };
 		51BCEE4F1C84F53B0042C82E /* IndexedDBMultiProcess-2.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 51BCEE4D1C84F52C0042C82E /* IndexedDBMultiProcess-2.html */; };
 		51BE9E662376089F00B4E117 /* MediaType.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51BE9E652376089500B4E117 /* MediaType.mm */; };
@@ -1841,6 +1842,7 @@
 		51B1EE8D1C80F5880064FB98 /* IndexedDBPersistence.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IndexedDBPersistence.mm; sourceTree = "<group>"; };
 		51B1EE941C80FADD0064FB98 /* IndexedDBPersistence-1.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "IndexedDBPersistence-1.html"; sourceTree = "<group>"; };
 		51B1EE951C80FADD0064FB98 /* IndexedDBPersistence-2.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "IndexedDBPersistence-2.html"; sourceTree = "<group>"; };
+		51B40D9D23AC960E00E05241 /* AsyncFunction.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AsyncFunction.mm; sourceTree = "<group>"; };
 		51B454EB1B4E236B0085EAA6 /* WebViewCloseInsideDidFinishLoadForFrame.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebViewCloseInsideDidFinishLoadForFrame.mm; sourceTree = "<group>"; };
 		51BCEE491C84F4AF0042C82E /* IndexedDBMultiProcess.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IndexedDBMultiProcess.mm; sourceTree = "<group>"; };
 		51BCEE4C1C84F52C0042C82E /* IndexedDBMultiProcess-1.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "IndexedDBMultiProcess-1.html"; sourceTree = "<group>"; };
@@ -2829,6 +2831,7 @@
 				2DE71AFD1D49C0BD00904094 /* AnimatedResize.mm */,
 				A1798B8122431D65000764BD /* ApplePay.mm */,
 				63F668201F97C3AA0032EE51 /* ApplicationManifest.mm */,
+				51B40D9D23AC960E00E05241 /* AsyncFunction.mm */,
 				834138C6203261B900F26960 /* AsyncPolicyForNavigationResponse.mm */,
 				3760C4F0211249AF00233ACC /* AttrStyle.mm */,
 				754CEC801F6722DC00D0039A /* AutoFillAvailable.mm */,
@@ -4536,6 +4539,7 @@
 				A1798B8222431D65000764BD /* ApplePay.mm in Sources */,
 				63F668221F97F7F90032EE51 /* ApplicationManifest.mm in Sources */,
 				6354F4D11F7C3AB500D89DF3 /* ApplicationManifestParser.cpp in Sources */,
+				51B40D9E23AC962400E05241 /* AsyncFunction.mm in Sources */,
 				834138C7203261CA00F26960 /* AsyncPolicyForNavigationResponse.mm in Sources */,
 				7CCE7EB41A411A7E00447C4C /* AttributedString.mm in Sources */,
 				3760C4F1211249AF00233ACC /* AttrStyle.mm in Sources */,

Added: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm (0 => 253950)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm	2019-12-30 20:23:20 UTC (rev 253950)
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#import "config.h"
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKWebViewPrivate.h>
+
+namespace TestWebKitAPI {
+
+TEST(AsyncFunction, Basic)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+    NSError *error;
+
+    // Function with no return value.
+    // Returns _javascript_ undefined which translates to nil.
+    id result = [webView objectByCallingAsyncFunction:@"1" withArguments:nil error:&error];
+    EXPECT_NULL(error);
+    EXPECT_EQ(result, nil);
+
+    // Function returns explicit null.
+    // Returns _javascript_ null which translates to NSNull.
+    result = [webView objectByCallingAsyncFunction:@"return null" withArguments:nil error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([result isKindOfClass:[NSNull class]]);
+
+    // Function returns a number.
+    result = [webView objectByCallingAsyncFunction:@"return 1" withArguments:nil error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([result isKindOfClass:[NSNumber class]]);
+    EXPECT_TRUE([result isEqualToNumber:@1]);
+
+    // Function returns a string.
+    result = [webView objectByCallingAsyncFunction:@"return '1'" withArguments:nil error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+    EXPECT_TRUE([result isEqualToString:@"1"]);
+
+    // Takes multiple arguments.
+    result = [webView objectByCallingAsyncFunction:@"return a + b" withArguments:@{ @"a" : @40, @"b" : @2 } error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([result isKindOfClass:[NSNumber class]]);
+    EXPECT_TRUE([result isEqualToNumber:@42]);
+
+    // Takes multiple arguments of different types, follows _javascript_s "type appending" rules.
+    result = [webView objectByCallingAsyncFunction:@"return foo + bar" withArguments:@{ @"foo" : @"foo", @"bar" : @42 } error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+    EXPECT_TRUE([result isEqualToString:@"foo42"]);
+
+    // Invalid _javascript_, should return an error.
+    result = [webView objectByCallingAsyncFunction:@"retunr null" withArguments:nil error:&error];
+    EXPECT_FALSE(error == nil);
+    EXPECT_NULL(result);
+}
+
+TEST(AsyncFunction, InvalidArguments)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+    NSError *error;
+
+    // Values can only be NSString, NSNumber, NSDate, NSNull, NS(Mutable)Array, NS(Mutable)Dictionary, NSNull, and may only contain those 6 types.
+    id result = [webView objectByCallingAsyncFunction:@"return 1" withArguments:@{ @"a" : [NSData data] } error:&error];
+    EXPECT_NULL(result);
+    EXPECT_FALSE(error == nil);
+
+    result = [webView objectByCallingAsyncFunction:@"return 1" withArguments:@{ @"a" : @[ @1, [NSData data] ] } error:&error];
+    EXPECT_NULL(result);
+    EXPECT_FALSE(error == nil);
+
+    // References an argument that was not provided.
+    result = [webView objectByCallingAsyncFunction:@"return a + b" withArguments:@{ @"a" : @40 } error:&error];
+    EXPECT_NULL(result);
+    EXPECT_FALSE(error == nil);
+}
+
+TEST(AsyncFunction, RoundTrip)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+    NSError *error;
+
+    // Tests round tripping a whole bunch of argument inputs and verifying the result.
+
+    id value = @42;
+    id arguments = @{ @"a" : value };
+    id result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    value = [NSNull null];
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    value = @"Foo";
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    value = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    value = @[ @1, @2 ];
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
+    [mutableArray addObject:value];
+    arguments = @{ @"a" : mutableArray };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([mutableArray isEqual:result]);
+    [mutableArray release];
+
+    value = @[ @"foo", [NSDate dateWithTimeIntervalSinceReferenceDate:0] ];
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    value = @{ @"a" : @1, @"b" : @2 };
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    NSMutableDictionary<NSString *, id> *mutableDictionary = [[NSMutableDictionary alloc] init];
+    mutableDictionary[@"foo"] = value;
+    arguments = @{ @"a" : mutableDictionary };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([mutableDictionary isEqual:result]);
+    [mutableDictionary release];
+
+    value = @{ @"a" : @"foo", @"b" : [NSDate dateWithTimeIntervalSinceReferenceDate:0] };
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+
+    value = @{ @"a" : @{ @"a" : @1 } };
+    arguments = @{ @"a" : value };
+    result = [webView objectByCallingAsyncFunction:@"return a" withArguments:arguments error:&error];
+    EXPECT_NULL(error);
+    EXPECT_TRUE([value isEqual:result]);
+}
+
+}
+

Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h (253949 => 253950)


--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2019-12-30 20:23:20 UTC (rev 253950)
@@ -63,6 +63,7 @@
 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script;
 - (id)objectByEvaluatingJavaScriptWithUserGesture:(NSString *)script;
 - (id)objectByEvaluatingJavaScript:(NSString *)script;
+- (id)objectByCallingAsyncFunction:(NSString *)script withArguments:(NSDictionary *)arguments error:(NSError **)errorOut;
 - (unsigned)waitUntilClientWidthIs:(unsigned)expectedClientWidth;
 @end
 

Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm (253949 => 253950)


--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm	2019-12-30 20:16:54 UTC (rev 253949)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm	2019-12-30 20:23:20 UTC (rev 253950)
@@ -178,6 +178,27 @@
     return evalResult.autorelease();
 }
 
+- (id)objectByCallingAsyncFunction:(NSString *)script withArguments:(NSDictionary *)arguments error:(NSError **)errorOut
+{
+    bool isWaitingForJavaScript = false;
+    if (errorOut)
+        *errorOut = nil;
+
+    RetainPtr<id> evalResult;
+    [self _callAsyncFunction:script withArguments:arguments completionHandler:[&] (id result, NSError *error) {
+        evalResult = result;
+        if (errorOut)
+            *errorOut = [error retain];
+        isWaitingForJavaScript = true;
+    }];
+    TestWebKitAPI::Util::run(&isWaitingForJavaScript);
+
+    if (errorOut)
+        [*errorOut autorelease];
+
+    return evalResult.autorelease();
+}
+
 - (NSString *)stringByEvaluatingJavaScript:(NSString *)script
 {
     return [NSString stringWithFormat:@"%@", [self objectByEvaluatingJavaScript:script]];
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to