Add WKWebView SPI to evaluate a function with arguments
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 30 Dec 2019 20:23:20 +0000 (20:23 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 30 Dec 2019 20:23:20 +0000 (20:23 +0000)
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:]):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@253950 268f45cc-cd09-0410-ab3c-d52691b4dbfc

27 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Headers.cmake
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/ExceptionDetails.h
Source/WebCore/bindings/js/RunJavaScriptParameters.h [new file with mode: 0644]
Source/WebCore/bindings/js/ScriptController.cpp
Source/WebCore/bindings/js/ScriptController.h
Source/WebCore/xml/XPathGrammar.cpp
Source/WebCore/xml/XPathGrammar.y
Source/WebKit/ChangeLog
Source/WebKit/Shared/API/APISerializedScriptValue.h
Source/WebKit/UIProcess/API/C/WKPage.cpp
Source/WebKit/UIProcess/API/Cocoa/APISerializedScriptValueCocoa.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/socket/RemoteInspectorProtocolHandler.cpp
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm [new file with mode: 0644]
Tools/TestWebKitAPI/cocoa/TestWKWebView.h
Tools/TestWebKitAPI/cocoa/TestWKWebView.mm

index 223576e..bc7ea02 100644 (file)
@@ -1,3 +1,36 @@
+2019-12-30  Brady Eidson  <beidson@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  <antti@apple.com>
 
         Style invalidation cleanups
index a419470..aa1603c 100644 (file)
@@ -240,6 +240,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
     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
index f04bf31..e62e670 100644 (file)
                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, ); }; };
                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>"; };
                                41B459DA1F4CADB90000F6FD /* ReadableStream.h */,
                                418C395E1C8F0AAB0051C8A3 /* ReadableStreamDefaultController.cpp */,
                                418C395F1C8F0AAB0051C8A3 /* ReadableStreamDefaultController.h */,
+                               51BA946F23AC305000444846 /* RunJavaScriptParameters.h */,
                                41F1D21E0EF35C2A00DA8753 /* ScriptCachedFrameData.cpp */,
                                41F1D21D0EF35C2A00DA8753 /* ScriptCachedFrameData.h */,
                                93B70D5309EB0C7C009D8468 /* ScriptController.cpp */,
                                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 */,
index e80a990..4924118 100644 (file)
@@ -33,7 +33,10 @@ struct ExceptionDetails {
     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
diff --git a/Source/WebCore/bindings/js/RunJavaScriptParameters.h b/Source/WebCore/bindings/js/RunJavaScriptParameters.h
new file mode 100644 (file)
index 0000000..47a3ffb
--- /dev/null
@@ -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
index 41ebb74..19627ac 100644 (file)
@@ -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,20 +566,116 @@ JSC::JSValue ScriptController::executeScriptIgnoringException(const String& scri
 
 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)
@@ -588,12 +685,26 @@ JSC::JSValue ScriptController::executeUserAgentScriptInWorldIgnoringException(DO
 }
 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
index 26881cb..0049f46 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "FrameLoaderTypes.h"
 #include "JSWindowProxy.h"
+#include "SerializedScriptValue.h"
 #include "WindowProxy.h"
 #include <JavaScriptCore/JSBase.h>
 #include <JavaScriptCore/Strong.h>
@@ -63,7 +64,11 @@ class ModuleFetchParameters;
 class ScriptSourceCode;
 class SecurityOrigin;
 class Widget;
+
+enum class RunAsAsyncFunction : bool;
+
 struct ExceptionDetails;
+struct RunJavaScriptParameters;
 
 enum ReasonForCallingCanExecuteScripts {
     AboutToCreateEventListener,
@@ -91,11 +96,15 @@ public:
 
     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 @@ public:
     // 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 @@ public:
     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();
index 5e069d7..0875a5e 100644 (file)
@@ -1969,7 +1969,7 @@ yyreduce:
 #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 @@ yyreduce:
     {
         (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;
 
index 4eb20b9..b751a61 100644 (file)
@@ -401,14 +401,14 @@ PathExpr:
     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
     {
         $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));
     }
     ;
 
index 6093a45..ef40232 100644 (file)
@@ -1,3 +1,49 @@
+2019-12-30  Brady Eidson  <beidson@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.liu6@apple.com>
 
         Tweak the format and comment in the code to support media in GPU process
index 54c34e8..1f43e63 100644 (file)
@@ -61,6 +61,7 @@ public:
     
 #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(); }
index 0c15f8b..dd796b8 100644 (file)
@@ -2472,7 +2472,7 @@ void WKPageSetPageStateClient(WKPageRef page, WKPageStateClientBase* client)
 
 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);
     });
 }
index a2673e2..66e8b27 100644 (file)
@@ -26,7 +26,9 @@
 #include "config.h"
 #include "APISerializedScriptValue.h"
 
+#import <JavaScriptCore/APICast.h>
 #import <JavaScriptCore/JSContext.h>
+#import <JavaScriptCore/JSGlobalObjectInlines.h>
 #import <JavaScriptCore/JSValue.h>
 #import <wtf/NeverDestroyed.h>
 #import <wtf/RunLoop.h>
@@ -59,11 +61,16 @@ private:
     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 @@ id SerializedScriptValue::deserialize(WebCore::SerializedScriptValue& serialized
     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
index 4e7bc0c..ddef0b7 100644 (file)
@@ -822,14 +822,81 @@ static WKErrorCode callbackErrorCode(WebKit::CallbackBase::Error error)
 
 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler
 {
-    [self _evaluateJavaScript:javaScriptString forceUserGesture:YES completionHandler:completionHandler];
+    [self _evaluateJavaScript:javaScriptString asAsyncFunction:NO withArguments:nil forceUserGesture:YES completionHandler:completionHandler];
 }
 
-- (void)_evaluateJavaScript:(NSString *)javaScriptString 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 *)javaScriptString asAsyncFunction:(BOOL)asAsyncFunction withArguments:(NSDictionary<NSString *, id> *)arguments forceUserGesture:(BOOL)forceUserGesture completionHandler:(void (^)(id, NSError *))completionHandler
 {
     auto handler = adoptNS([completionHandler copy]);
 
-    _page->runJavaScriptInMainFrame(javaScriptString, 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 { javaScriptString, !!asAsyncFunction, WTFMove(argumentsMap), !!forceUserGesture }, [handler](API::SerializedScriptValue* serializedScriptValue, Optional<WebCore::ExceptionDetails> details, WebKit::ScriptValueCallback::Error errorCode) {
         if (!handler)
             return;
 
@@ -1923,6 +1990,11 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKCONTENTVIEW)
     [self createPDFWithConfiguration:pdfConfiguration completionHandler:completionHandler];
 }
 
+- (void)_callAsyncFunction:(NSString *)javaScriptString withArguments:(NSDictionary<NSString *, id> *)arguments completionHandler:(void (^)(id, NSError *error))completionHandler
+{
+    [self _evaluateJavaScript:javaScriptString asAsyncFunction:YES withArguments:arguments forceUserGesture:YES completionHandler:completionHandler];
+}
+
 - (NSData *)_sessionStateData
 {
     // FIXME: This should not use the legacy session state encoder.
@@ -2065,7 +2137,7 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKCONTENTVIEW)
 
 - (void)_evaluateJavaScriptWithoutUserGesture:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler
 {
-    [self _evaluateJavaScript:javaScriptString forceUserGesture:NO completionHandler:completionHandler];
+    [self _evaluateJavaScript:javaScriptString asAsyncFunction:NO withArguments:nil forceUserGesture:NO completionHandler:completionHandler];
 }
 
 - (void)_updateWebsitePolicies:(_WKWebsitePolicies *)websitePolicies
index c312c81..8d3c45a 100644 (file)
@@ -315,6 +315,9 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 - (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 *)javaScriptString withArguments:(NSDictionary<NSString *, id> *)arguments completionHandler:(void (^)(id, NSError *error))completionHandler;
+
 @end
 
 #if TARGET_OS_IPHONE
index 2a43988..646e45a 100644 (file)
@@ -3636,7 +3636,7 @@ void webkit_web_view_run_javascript(WebKitWebView* webView, const gchar* script,
     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 @@ void webkit_web_view_run_javascript_in_world(WebKitWebView* webView, const gchar
     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 @@ static void resourcesStreamReadCallback(GObject* object, GAsyncResult* result, g
 
     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)
index 4a4ccc2..838b4da 100644 (file)
@@ -3849,12 +3849,12 @@ void WebPageProxy::launchInitialProcessIfNecessary()
         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 @@ void WebPageProxy::runJavaScriptInMainFrameScriptWorld(const String& script, boo
     }
 
     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)
index acb70f5..77fc045 100644 (file)
@@ -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 @@ public:
     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>&&);
index 7bc18ff..be20f1d 100644 (file)
@@ -126,7 +126,7 @@ void RemoteInspectorProtocolHandler::inspect(const String& hostAndPort, Connecti
 
 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());
index 18bd68d..13d43ed 100644 (file)
 #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,45 +3353,49 @@ KeyboardUIMode WebPage::keyboardUIMode()
     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) {
-        result = frame->coreFrame()->script().executeUserAgentScriptInWorld(world->coreWorld(), script, forceUserGesture);
+    if (!frame || !frame->coreFrame() || !world) {
+        send(Messages::WebPageProxy::ScriptValueCallback({ }, ExceptionDetails { "Unable to execute JavaScript: Page is in invalid state"_s }, callbackID));
+        return;
+    }
+
+    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)
 {
     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)
index 78ea02e..b07cf17 100644 (file)
@@ -197,6 +197,7 @@ struct GlobalWindowIdentifier;
 struct Highlight;
 struct KeypressCommand;
 struct PromisedAttachmentInfo;
+struct RunJavaScriptParameters;
 struct TextCheckingResult;
 struct ViewportArguments;
 
@@ -1435,8 +1436,8 @@ private:
     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);
index baad76e..9bf21ec 100644 (file)
@@ -204,7 +204,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType
     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)
 
index 0f48f6b..5a61263 100644 (file)
@@ -1,3 +1,17 @@
+2019-12-30  Brady Eidson  <beidson@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  <clopez@igalia.com>
 
         [GTK][WPE] Report number of total tests run and failed in API test runner like run-api-tests does
index e0e8009..fedfcf2 100644 (file)
                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 */; };
                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>"; };
                                2DE71AFD1D49C0BD00904094 /* AnimatedResize.mm */,
                                A1798B8122431D65000764BD /* ApplePay.mm */,
                                63F668201F97C3AA0032EE51 /* ApplicationManifest.mm */,
+                               51B40D9D23AC960E00E05241 /* AsyncFunction.mm */,
                                834138C6203261B900F26960 /* AsyncPolicyForNavigationResponse.mm */,
                                3760C4F0211249AF00233ACC /* AttrStyle.mm */,
                                754CEC801F6722DC00D0039A /* AutoFillAvailable.mm */,
                                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 */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm
new file mode 100644 (file)
index 0000000..d1f97d3
--- /dev/null
@@ -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 javascripts "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]);
+}
+
+}
+
index 2ad15ef..3cc434a 100644 (file)
@@ -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
 
index aaff25c..74ad581 100644 (file)
@@ -178,6 +178,27 @@ SOFT_LINK_CLASS(UIKit, UIWindow)
     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]];