- Revision
- 254222
- Author
- beid...@apple.com
- Date
- 2020-01-08 13:47:17 -0800 (Wed, 08 Jan 2020)
Log Message
Make _callAsyncFunction:withArguments: work with promises.
https://bugs.webkit.org/show_bug.cgi?id=205654
Reviewed by Saam Barati.
Source/WebCore:
Covered by API tests.
* bindings/js/ScriptController.cpp:
(WebCore::ScriptController::executeAsynchronousUserAgentScriptInWorld):
Tools:
Test that:
- Resolve results in success handler being called
- Reject results in error handler being called
- Both resolve and reject becoming unreachable results in the error handler being called
- Both native Promise objects and arbitrary thenables work
- Any object where "then" is callable - even if not a function - works
* TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm:
(TestWebKitAPI::tryGCPromise):
(TestWebKitAPI::TEST):
Modified Paths
Diff
Modified: trunk/Source/WebCore/ChangeLog (254221 => 254222)
--- trunk/Source/WebCore/ChangeLog 2020-01-08 21:30:13 UTC (rev 254221)
+++ trunk/Source/WebCore/ChangeLog 2020-01-08 21:47:17 UTC (rev 254222)
@@ -1,3 +1,15 @@
+2020-01-08 Brady Eidson <beid...@apple.com>
+
+ Make _callAsyncFunction:withArguments: work with promises.
+ https://bugs.webkit.org/show_bug.cgi?id=205654
+
+ Reviewed by Saam Barati.
+
+ Covered by API tests.
+
+ * bindings/js/ScriptController.cpp:
+ (WebCore::ScriptController::executeAsynchronousUserAgentScriptInWorld):
+
2020-01-08 Myles C. Maxfield <mmaxfi...@apple.com>
Fix specification violation in Font Loading API
Modified: trunk/Source/WebCore/bindings/js/ScriptController.cpp (254221 => 254222)
--- trunk/Source/WebCore/bindings/js/ScriptController.cpp 2020-01-08 21:30:13 UTC (rev 254221)
+++ trunk/Source/WebCore/bindings/js/ScriptController.cpp 2020-01-08 21:47:17 UTC (rev 254222)
@@ -58,6 +58,7 @@
#include "npruntime_impl.h"
#include "runtime_root.h"
#include <_javascript_Core/Debugger.h>
+#include <_javascript_Core/Heap.h>
#include <_javascript_Core/InitializeThreading.h>
#include <_javascript_Core/JSFunction.h>
#include <_javascript_Core/JSInternalPromise.h>
@@ -70,6 +71,7 @@
#include <_javascript_Core/StrongInlines.h>
#include <_javascript_Core/WeakGCMapInlines.h>
#include <wtf/SetForScope.h>
+#include <wtf/SharedTask.h>
#include <wtf/Threading.h>
#include <wtf/text/TextPosition.h>
@@ -699,13 +701,67 @@
return executeScriptInWorld(world, WTFMove(parameters));
}
-void ScriptController::executeAsynchronousUserAgentScriptInWorld(DOMWrapperWorld& world, RunJavaScriptParameters&& parameters, ResolveFunction&& resolveFunction)
+void ScriptController::executeAsynchronousUserAgentScriptInWorld(DOMWrapperWorld& world, RunJavaScriptParameters&& parameters, ResolveFunction&& resolveCompletionHandler)
{
auto result = executeUserAgentScriptInWorldInternal(world, WTFMove(parameters));
+
+ if (parameters.runAsAsyncFunction == RunAsAsyncFunction::No || !result || !result.value().isObject()) {
+ resolveCompletionHandler(result);
+ return;
+ }
- // FIXME: If the result is a thenable, install the fulfill/reject handlers instead of resolving now.
+ // When running _javascript_ as an async function, any "thenable" object gets promise-like behavior of deferred completion.
+ auto thenIdentifier = world.vm().propertyNames->then;
+ auto& proxy = jsWindowProxy(world);
+ auto& globalObject = *proxy.window();
- resolveFunction(result);
+ auto thenFunction = result.value().get(&globalObject, thenIdentifier);
+ if (!thenFunction.isObject()) {
+ resolveCompletionHandler(result);
+ return;
+ }
+
+ CallData callData;
+ CallType callType = asObject(thenFunction)->methodTable(world.vm())->getCallData(asObject(thenFunction), callData);
+ if (callType == CallType::None) {
+ resolveCompletionHandler(result);
+ return;
+ }
+
+ auto sharedResolveFunction = createSharedTask<void(ValueOrException)>([resolveCompletionHandler = WTFMove(resolveCompletionHandler)](ValueOrException result) mutable {
+ if (resolveCompletionHandler)
+ resolveCompletionHandler(result);
+ resolveCompletionHandler = nullptr;
+ });
+
+ auto* fulfillHandler = JSC::JSNativeStdFunction::create(world.vm(), &globalObject, 1, String { }, [sharedResolveFunction = sharedResolveFunction.copyRef()] (JSGlobalObject*, CallFrame* callFrame) mutable {
+ sharedResolveFunction->run(callFrame->argument(0));
+ return JSValue::encode(jsUndefined());
+ });
+
+ auto* rejectHandler = JSC::JSNativeStdFunction::create(world.vm(), &globalObject, 1, String { }, [sharedResolveFunction = sharedResolveFunction.copyRef()] (JSGlobalObject* globalObject, CallFrame* callFrame) mutable {
+ sharedResolveFunction->run(makeUnexpected(ExceptionDetails { callFrame->argument(0).toWTFString(globalObject) }));
+ return JSValue::encode(jsUndefined());
+ });
+
+ auto finalizeCount = makeUniqueWithoutFastMallocCheck<unsigned>(0);
+ auto finalizeGuard = createSharedTask<void()>([sharedResolveFunction = WTFMove(sharedResolveFunction), finalizeCount = WTFMove(finalizeCount)]() {
+ if (++(*finalizeCount) == 2)
+ sharedResolveFunction->run(makeUnexpected(ExceptionDetails { "Completion handler for function call is no longer reachable"_s }));
+ });
+
+ world.vm().heap.addFinalizer(fulfillHandler, [finalizeGuard = finalizeGuard.copyRef()](JSCell*) {
+ finalizeGuard->run();
+ });
+ world.vm().heap.addFinalizer(rejectHandler, [finalizeGuard = finalizeGuard.copyRef()](JSCell*) {
+ finalizeGuard->run();
+ });
+
+ JSC::MarkedArgumentBuffer arguments;
+ arguments.append(fulfillHandler);
+ arguments.append(rejectHandler);
+
+ call(&globalObject, thenFunction, callType, callData, result.value(), arguments);
}
Expected<void, ExceptionDetails> ScriptController::shouldAllowUserAgentScripts(Document& document) const
Modified: trunk/Tools/ChangeLog (254221 => 254222)
--- trunk/Tools/ChangeLog 2020-01-08 21:30:13 UTC (rev 254221)
+++ trunk/Tools/ChangeLog 2020-01-08 21:47:17 UTC (rev 254222)
@@ -1,3 +1,21 @@
+2020-01-08 Brady Eidson <beid...@apple.com>
+
+ Make _callAsyncFunction:withArguments: work with promises.
+ https://bugs.webkit.org/show_bug.cgi?id=205654
+
+ Reviewed by Saam Barati.
+
+ Test that:
+ - Resolve results in success handler being called
+ - Reject results in error handler being called
+ - Both resolve and reject becoming unreachable results in the error handler being called
+ - Both native Promise objects and arbitrary thenables work
+ - Any object where "then" is callable - even if not a function - works
+
+ * TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm:
+ (TestWebKitAPI::tryGCPromise):
+ (TestWebKitAPI::TEST):
+
2020-01-08 Daniel Bates <daba...@apple.com>
Regression r254160: 6 API test failures
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm (254221 => 254222)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm 2020-01-08 21:30:13 UTC (rev 254221)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm 2020-01-08 21:47:17 UTC (rev 254222)
@@ -177,5 +177,75 @@
EXPECT_TRUE([value isEqual:result]);
}
+static void tryGCPromise(WKWebView *webView, bool& done)
+{
+ if (done)
+ return;
+
+ NSString *functionBody = @"return new Promise(function(resolve, reject) { })";
+ [webView _callAsyncFunction:functionBody withArguments:nil completionHandler:[&] (id result, NSError *error) {
+ EXPECT_NULL(result);
+ EXPECT_TRUE(error != nil);
+ EXPECT_TRUE([[error description] containsString:@"no longer reachable"]);
+ done = true;
+ }];
+
+ dispatch_async(dispatch_get_main_queue(), ^{ tryGCPromise(webView, done); });
}
+TEST(AsyncFunction, Promise)
+{
+ auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+
+ NSString *functionBody = @"return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(42) }, 0); })";
+
+ bool done = false;
+ [webView _callAsyncFunction:functionBody withArguments:nil completionHandler:[&] (id result, NSError *error) {
+ EXPECT_NULL(error);
+ EXPECT_TRUE([result isKindOfClass:[NSNumber class]]);
+ EXPECT_TRUE([result isEqualToNumber:@42]);
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+ done = false;
+
+ functionBody = @"return new Promise(function(resolve, reject) { setTimeout(function(){ reject('Rejected!') }, 0); })";
+
+ done = false;
+ [webView _callAsyncFunction:functionBody withArguments:nil completionHandler:[&] (id result, NSError *error) {
+ EXPECT_NULL(result);
+ EXPECT_TRUE(error != nil);
+ EXPECT_TRUE([[error description] containsString:@"Rejected!"]);
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+
+ functionBody = @"let p = new Proxy(function(resolve, reject) { setTimeout(function() { resolve(42); }, 0); }, { }); return { then: p };";
+
+ done = false;
+ [webView _callAsyncFunction:functionBody withArguments:nil completionHandler:[&] (id result, NSError *error) {
+ EXPECT_NULL(error);
+ EXPECT_TRUE([result isKindOfClass:[NSNumber class]]);
+ EXPECT_TRUE([result isEqualToNumber:@42]);
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+
+ functionBody = @"let p = new Proxy(function(resolve, reject) { setTimeout(function() { reject('Rejected!'); }, 0); }, { }); return { then: p };";
+
+ done = false;
+ [webView _callAsyncFunction:functionBody withArguments:nil completionHandler:[&] (id result, NSError *error) {
+ EXPECT_NULL(result);
+ EXPECT_TRUE(error != nil);
+ EXPECT_TRUE([[error description] containsString:@"Rejected!"]);
+ done = true;
+ }];
+ TestWebKitAPI::Util::run(&done);
+
+ done = false;
+ tryGCPromise(webView.get(), done);
+ TestWebKitAPI::Util::run(&done);
+}
+
+}
+