Make the callAsyncJavaScriptFunction function actually be async (so await works).
<rdar://problem/58571682> and https://bugs.webkit.org/show_bug.cgi?id=206364

Reviewed by Geoffrey Garen.

Source/WebCore:

Covered by API tests.

* bindings/js/ScriptController.cpp:
(WebCore::ScriptController::callInWorld):

Source/WebKit:

* UIProcess/API/Cocoa/WKWebViewPrivate.h: Update callAsyncJavaScriptFunction: header docs.

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm:
(TestWebKitAPI::TEST):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@254704 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 43ef4d9..8d64028 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,15 @@
+2020-01-16  Brady Eidson  <beidson@apple.com>
+
+        Make the callAsyncJavaScriptFunction function actually be async (so await works).
+        <rdar://problem/58571682> and https://bugs.webkit.org/show_bug.cgi?id=206364
+
+        Reviewed by Geoffrey Garen.
+
+        Covered by API tests.
+
+        * bindings/js/ScriptController.cpp:
+        (WebCore::ScriptController::callInWorld):
+
 2020-01-16  Don Olmstead  <don.olmstead@sony.com>
 
         Non-unified build fixes mid January 2020 edition
diff --git a/Source/WebCore/bindings/js/ScriptController.cpp b/Source/WebCore/bindings/js/ScriptController.cpp
index 467ecae..00c60ef 100644
--- a/Source/WebCore/bindings/js/ScriptController.cpp
+++ b/Source/WebCore/bindings/js/ScriptController.cpp
@@ -604,7 +604,7 @@
     String errorMessage;
 
     // Build up a new script string that is an async function with arguments, and deserialize those arguments.
-    functionStringBuilder.append("(function(");
+    functionStringBuilder.append("(async function(");
     for (auto argument = parameters.arguments->begin(); argument != parameters.arguments->end();) {
         functionStringBuilder.append(argument->key);
         auto serializedArgument = SerializedScriptValue::createFromWireBytes(WTFMove(argument->value));
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index e2f415d..d500eda 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,12 @@
+2020-01-16  Brady Eidson  <beidson@apple.com>
+
+        Make the callAsyncJavaScriptFunction function actually be async (so await works).
+        <rdar://problem/58571682> and https://bugs.webkit.org/show_bug.cgi?id=206364
+
+        Reviewed by Geoffrey Garen.
+
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h: Update callAsyncJavaScriptFunction: header docs.
+
 2020-01-16  Don Olmstead  <don.olmstead@sony.com>
 
         Non-unified build fixes mid January 2020 edition
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h b/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
index 6ae61cc..efc1b60 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
@@ -325,8 +325,11 @@
  @param contentWorld The WKContentWorld in which to call the JavaScript function.
  @param completionHandler A block to invoke with the return value of the function call, or with the asynchronous resolution of the function's return value.
  @discussion The JavaScript string is treated as an anonymous JavaScript function that can be called with named arguments.
- Pass in JavaScript string formatted for evaluation, not as one of the variants of function call available in JavaScript.
- For example:
+ Do not format your string as one of the variants of function call available in JavaScript.
+ Instead pass in a JavaScript string representing the function text, formatted for evaluation.
+ For example do not pass in the string:
+     function(x, y, z) { return x ? y : z; }
+ Instead pass in the string:
      return x ? y : z;
 
  The arguments dictionary supplies the values for those arguments which are serialized into JavaScript equivalents.
@@ -355,6 +358,16 @@
  If the object calls "fulfill", your completion handler will be called with the result.
  If the object calls "reject", your completion handler will be called with a WKErrorJavaScriptAsyncFunctionResultRejected error containing the reject reason in the userInfo dictionary.
  If the object is garbage collected before it is resolved, your completion handler will be called with an error indicating that it will never be resolved.
+
+ Since the function is a JavaScript "async" function you can use JavaScript "await" on those objects inside your function text.
+ For example:
+     var p = new Promise(function (r) {
+         r(42);
+     });
+     await p;
+     return p;
+
+ The above function text will create a promise that will fulfull with the value 42, wait for it to resolve, then return the fulfillment value of 42.
 */
 - (void)_callAsyncJavaScriptFunction:(NSString *)javaScriptString withArguments:(NSDictionary<NSString *, id> *)arguments inWorld:(_WKContentWorld *)contentWorld completionHandler:(void (^)(id, NSError *error))completionHandler;
 
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index b6cd985..4f170f5 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,13 @@
+2020-01-16  Brady Eidson  <beidson@apple.com>
+
+        Make the callAsyncJavaScriptFunction function actually be async (so await works).
+        <rdar://problem/58571682> and https://bugs.webkit.org/show_bug.cgi?id=206364
+
+        Reviewed by Geoffrey Garen.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm:
+        (TestWebKitAPI::TEST):
+
 2020-01-16  Simon Fraser  <simon.fraser@apple.com>
 
         fast/forms/ios/zoom-after-input-tap-wide-input.html is timing out
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm
index 78cdd77..72b94fa 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm
@@ -243,6 +243,42 @@
     }];
     TestWebKitAPI::Util::run(&done);
 
+    // Verify we can await for a promise to be resolved before returning.
+    functionBody = @"var r = 0; var p = new Promise(function(fulfill, reject) { setTimeout(function(){ r = 42; fulfill(); }, 5);}); await p; return r;";
+
+    done = false;
+    [webView _callAsyncJavaScriptFunction:functionBody withArguments:nil inWorld:_WKContentWorld.pageContentWorld 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);
+
+    // Returning an already resolved promise gives the value it was resolved with.
+    functionBody = @"var p = new Promise(function(fulfill, reject) { setTimeout(function(){ fulfill('Fulfilled!') }, 5);}); await p; return p;";
+
+    done = false;
+    [webView _callAsyncJavaScriptFunction:functionBody withArguments:nil inWorld:_WKContentWorld.pageContentWorld completionHandler:[&] (id result, NSError *error) {
+        EXPECT_NULL(error);
+        EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+        EXPECT_TRUE([result isEqualToString:@"Fulfilled!"]);
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    // Chaining thenables should work.
+    functionBody = @"var p = new Promise(function (r) { r(new Promise(function (r) { r(42); })); }); await p; return 'Done';";
+
+    done = false;
+    [webView _callAsyncJavaScriptFunction:functionBody withArguments:nil inWorld:_WKContentWorld.pageContentWorld completionHandler:[&] (id result, NSError *error) {
+        EXPECT_NULL(error);
+        EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+        EXPECT_TRUE([result isEqualToString:@"Done"]);
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
     done = false;
     tryGCPromise(webView.get(), done);
     TestWebKitAPI::Util::run(&done);