Title: [254222] trunk
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);
+}
+
+}
+
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to