Title: [238850] trunk
Revision
238850
Author
drou...@apple.com
Date
2018-12-04 01:08:42 -0800 (Tue, 04 Dec 2018)

Log Message

Web Inspector: Audit: tests should support async operations
https://bugs.webkit.org/show_bug.cgi?id=192171
<rdar://problem/46423562>

Reviewed by Joseph Pecoraro.

Source/_javascript_Core:

Add `awaitPromise` command for executing a callback when a Promise gets settled.

Drive-by: allow `wasThrown` to be optional, instead of expecting it to always have a value.

* inspector/protocol/Runtime.json:

* inspector/InjectedScriptSource.js:
(InjectedScript.prototype.awaitPromise): Added.

* inspector/InjectedScript.h:
* inspector/InjectedScript.cpp:
(Inspector::InjectedScript::evaluate):
(Inspector::InjectedScript::awaitPromise): Added.
(Inspector::InjectedScript::callFunctionOn):
(Inspector::InjectedScript::evaluateOnCallFrame):

* inspector/InjectedScriptBase.h:
* inspector/InjectedScriptBase.cpp:
(Inspector::InjectedScriptBase::makeEvalCall):
(Inspector::InjectedScriptBase::makeAsyncCall): Added.
(Inspector::InjcetedScriptBase::checkCallResult): Added.
(Inspector::InjcetedScriptBase::checkAsyncCallResult): Added.

* inspector/agents/InspectorRuntimeAgent.h:
* inspector/agents/InspectorRuntimeAgent.cpp:
(Inspector::InspectorRuntimeAgent::evaluate):
(Inspector::InspectorRuntimeAgent::awaitPromise):
(Inspector::InspectorRuntimeAgent::callFunctionOn):

* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::evaluateOnCallFrame):

Source/WebCore:

* page/Settings.yaml:
* dom/ScriptExecutionContext.cpp:
(ScriptExecutionContext::reportUnhandledPromiseRejection):
Add setting for muting the "Unhandled Promise Rejection" console message.

Source/WebInspectorUI:

* UserInterface/Controllers/RuntimeManager.js:
(WI.RuntimeManager.supportsAwaitPromise): Added.

* UserInterface/Models/AuditTestCase.js:
(WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty.addErrorForValueType): Deleted.
(WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty): Deleted.
(WI.AuditTestCase.prototype.async run.async parseResponse.async resultArrayForEach): Deleted.
(WI.AuditTestCase.prototype.async run.async parseResponse): Added.
(WI.AuditTestCase.prototype.async run):
(WI.AuditTestCase.prototype.async run.checkResultProperty.addErrorForValueType): Deleted.
(WI.AuditTestCase.prototype.async run.checkResultProperty): Deleted.
(WI.AuditTestCase.prototype.async run.async resultArrayForEach): Deleted.

* UserInterface/Models/AuditTestCaseResult.js:
(WI.AuditTestCaseResult.async fromPayload):
(WI.AuditTestCaseResult.prototype.toJSON):

* UserInterface/Views/AuditTestCaseContentView.js:
(WI.AuditTestCaseContentView.prototype.layout):

LayoutTests:

* inspector/audit/resources/audit-utilities.js:
(TestPage.registerInitializer.InspectorTest.Audit.addFunctionlessTest):
(TestPage.registerInitializer.InspectorTest.Audit.addStringTest):
(TestPage.registerInitializer.InspectorTest.Audit.addObjectTest):
(TestPage.registerInitializer.InspectorTest.Audit.addPromiseTest): Added.
* inspector/audit/basic-expected.txt:
* inspector/audit/basic.html:

* inspector/model/auditTestCaseResult-expected.txt:
* inspector/model/auditTestCaseResult.html:
* inspector/model/auditTestGroupResult-expected.txt:
* inspector/model/auditTestGroupResult.html:

* inspector/runtime/awaitPromise-expected.txt: Added.
* inspector/runtime/awaitPromise.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (238849 => 238850)


--- trunk/LayoutTests/ChangeLog	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/ChangeLog	2018-12-04 09:08:42 UTC (rev 238850)
@@ -1,3 +1,27 @@
+2018-12-04  Devin Rousso  <drou...@apple.com>
+
+        Web Inspector: Audit: tests should support async operations
+        https://bugs.webkit.org/show_bug.cgi?id=192171
+        <rdar://problem/46423562>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/audit/resources/audit-utilities.js:
+        (TestPage.registerInitializer.InspectorTest.Audit.addFunctionlessTest):
+        (TestPage.registerInitializer.InspectorTest.Audit.addStringTest):
+        (TestPage.registerInitializer.InspectorTest.Audit.addObjectTest):
+        (TestPage.registerInitializer.InspectorTest.Audit.addPromiseTest): Added.
+        * inspector/audit/basic-expected.txt:
+        * inspector/audit/basic.html:
+
+        * inspector/model/auditTestCaseResult-expected.txt:
+        * inspector/model/auditTestCaseResult.html:
+        * inspector/model/auditTestGroupResult-expected.txt:
+        * inspector/model/auditTestGroupResult.html:
+
+        * inspector/runtime/awaitPromise-expected.txt: Added.
+        * inspector/runtime/awaitPromise.html: Added.
+
 2018-12-03  Carlos Garcia Campos  <cgar...@igalia.com>
 
         [GTK] Bump freetype, fontconfig, harfbuzz, cairo and icu in jhbuild

Modified: trunk/LayoutTests/inspector/audit/basic-expected.txt (238849 => 238850)


--- trunk/LayoutTests/inspector/audit/basic-expected.txt	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/inspector/audit/basic-expected.txt	2018-12-04 09:08:42 UTC (rev 238850)
@@ -50,23 +50,51 @@
 Testing value `{"level":"unsupported"}`...
 PASS: Result should be "unsupported".
 
+-- Running test case: Audit.Basic.Promise.Boolean.True
+Testing value `new Promise((resolve, reject) => resolve(true))`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Promise.String.Pass
+Testing value `new Promise((resolve, reject) => resolve("pass"))`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Promise.Object.Pass
+Testing value `new Promise((resolve, reject) => resolve({level: "pass"}))`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Async.Boolean.True
+Testing value `true`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Async.String.Pass
+Testing value `"pass"`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Async.Object.Pass
+Testing value `{"level":"pass"}`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Timeout.Pass
+Testing value `new Promise((resolve, reject) => setTimeout(resolve, 0, "pass"))`...
+PASS: Result should be "pass".
+
 -- Running test case: Audit.Basic.Error.Undefined
-Testing...
+Testing value `undefined`...
 PASS: Result should be "error".
   errors:
-   - TypeError: eval(undefined) is not a function. (In 'eval(undefined)()', 'eval(undefined)' is undefined)
+   - Return value is not an object, string, or boolean
 
 -- Running test case: Audit.Basic.Error.Null
-Testing...
+Testing value `null`...
 PASS: Result should be "error".
   errors:
-   - TypeError: eval(null) is not a function. (In 'eval(null)()', 'eval(null)' is null)
+   - Return value is not an object, string, or boolean
 
 -- Running test case: Audit.Basic.Error.Number
-Testing...
+Testing value `42`...
 PASS: Result should be "error".
   errors:
-   - TypeError: eval(42) is not a function. (In 'eval(42)()', 'eval(42)' is 42)
+   - Return value is not an object, string, or boolean
 
 -- Running test case: Audit.Basic.Error.String
 Testing value `"foo"`...
@@ -81,8 +109,20 @@
    - Missing result level
 
 -- Running test case: Audit.Basic.Error.Variable
-Testing...
+Testing value `INVALID`...
 PASS: Result should be "error".
   errors:
    - ReferenceError: Can't find variable: INVALID
 
+-- Running test case: Audit.Basic.Error.Promise.Resolved
+Testing value `new Promise((resolve, reject) => setTimeout(resolve, 0))`...
+PASS: Result should be "error".
+  errors:
+   - Return value is not an object, string, or boolean
+
+-- Running test case: Audit.Basic.Error.Promise.Rejected
+Testing value `new Promise((resolve, reject) => setTimeout(reject, 0, "rejected"))`...
+PASS: Result should be "error".
+  errors:
+   - rejected
+

Modified: trunk/LayoutTests/inspector/audit/basic.html (238849 => 238850)


--- trunk/LayoutTests/inspector/audit/basic.html	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/inspector/audit/basic.html	2018-12-04 09:08:42 UTC (rev 238850)
@@ -4,6 +4,9 @@
 <script src=""
 <script src=""
 <script>
+if (window.internals)
+    window.internals.settings.setUnhandledPromiseRejectionToConsoleEnabled(false);
+
 function test()
 {
     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
@@ -23,12 +26,24 @@
     InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Error", {level: WI.AuditTestCaseResult.Level.Error}, WI.AuditTestCaseResult.Level.Error);
     InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Unsupported", {level: WI.AuditTestCaseResult.Level.Unsupported}, WI.AuditTestCaseResult.Level.Unsupported);
 
-    InspectorTest.Audit.addTest("Audit.Basic.Error.Undefined", undefined, WI.AuditTestCaseResult.Level.Error);
-    InspectorTest.Audit.addTest("Audit.Basic.Error.Null", null, WI.AuditTestCaseResult.Level.Error);
-    InspectorTest.Audit.addTest("Audit.Basic.Error.Number", 42, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addPromiseTest("Audit.Basic.Promise.Boolean.True", `resolve(true)`, WI.AuditTestCaseResult.Level.Pass);
+    InspectorTest.Audit.addPromiseTest("Audit.Basic.Promise.String.Pass", `resolve("${WI.AuditTestCaseResult.Level.Pass}")`, WI.AuditTestCaseResult.Level.Pass);
+    InspectorTest.Audit.addPromiseTest("Audit.Basic.Promise.Object.Pass", `resolve({level: "${WI.AuditTestCaseResult.Level.Pass}"})`, WI.AuditTestCaseResult.Level.Pass);
+
+    InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Async.Boolean.True", true, WI.AuditTestCaseResult.Level.Pass, {async: true});
+    InspectorTest.Audit.addStringTest("Audit.Basic.Async.String.Pass", WI.AuditTestCaseResult.Level.Pass, WI.AuditTestCaseResult.Level.Pass, {async: true});
+    InspectorTest.Audit.addObjectTest("Audit.Basic.Async.Object.Pass", {level: WI.AuditTestCaseResult.Level.Pass}, WI.AuditTestCaseResult.Level.Pass, {async: true});
+
+    InspectorTest.Audit.addPromiseTest("Audit.Basic.Timeout.Pass", `setTimeout(resolve, 0, "${WI.AuditTestCaseResult.Level.Pass}")`, WI.AuditTestCaseResult.Level.Pass);
+
+    InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Error.Undefined", undefined, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Error.Null", null, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Error.Number", 42, WI.AuditTestCaseResult.Level.Error);
     InspectorTest.Audit.addStringTest("Audit.Basic.Error.String", "foo", WI.AuditTestCaseResult.Level.Error);
     InspectorTest.Audit.addObjectTest("Audit.Basic.Error.Object", {}, WI.AuditTestCaseResult.Level.Error);
-    InspectorTest.Audit.addTest("Audit.Basic.Error.Variable", "INVALID", WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Error.Variable", "INVALID", WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addPromiseTest("Audit.Basic.Error.Promise.Resolved", `setTimeout(resolve, 0)`, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addPromiseTest("Audit.Basic.Error.Promise.Rejected", `setTimeout(reject, 0, "rejected")`, WI.AuditTestCaseResult.Level.Error);
 
     suite.runTestCasesAndFinish();
 }

Modified: trunk/LayoutTests/inspector/audit/resources/audit-utilities.js (238849 => 238850)


--- trunk/LayoutTests/inspector/audit/resources/audit-utilities.js	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/inspector/audit/resources/audit-utilities.js	2018-12-04 09:08:42 UTC (rev 238850)
@@ -69,20 +69,24 @@
         });
     };
 
-    InspectorTest.Audit.addFunctionlessTest = function(name, test, level) {
-        InspectorTest.Audit.addTest(name, `function() { return ${test} }`, level, {
+    InspectorTest.Audit.addFunctionlessTest = function(name, test, level, options = {}) {
+        InspectorTest.Audit.addTest(name, (options.async ? "async " : "") + `function() { return ${test} }`, level, {
             beforeStart: ` value \`${test}\``,
         });
     };
 
-    InspectorTest.Audit.addStringTest = function(name, test, level) {
-        InspectorTest.Audit.addFunctionlessTest(name, `"${test}"`, level);
+    InspectorTest.Audit.addStringTest = function(name, test, level, options = {}) {
+        InspectorTest.Audit.addFunctionlessTest(name, `"${test}"`, level, options);
     };
 
-    InspectorTest.Audit.addObjectTest = function(name, test, level) {
-        InspectorTest.Audit.addFunctionlessTest(name, JSON.stringify(test), level);
+    InspectorTest.Audit.addObjectTest = function(name, test, level, options = {}) {
+        InspectorTest.Audit.addFunctionlessTest(name, JSON.stringify(test), level, options);
     };
 
+    InspectorTest.Audit.addPromiseTest = function(name, test, level, options = {}) {
+        InspectorTest.Audit.addFunctionlessTest(name, `new Promise((resolve, reject) => ${test})`, level, options);
+    };
+
     InspectorTest.Audit.addDOMSelectorTest = function(name, test, level) {
         InspectorTest.Audit.addTest(name, querySelectorTest.format(test), level, {
             beforeStart: ` selector \`${test}\``,

Modified: trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt (238849 => 238850)


--- trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt	2018-12-04 09:08:42 UTC (rev 238850)
@@ -55,7 +55,8 @@
   },
   "metadata": {
     "startTimestamp": "0001-01-01T00:00:00.000Z",
-    "endTimestamp": "0002-01-01T00:00:00.000Z",
+    "asyncTimestamp": "0002-01-01T00:00:00.000Z",
+    "endTimestamp": "0003-01-01T00:00:00.000Z",
     "url": "validWithValidSubOptionals test result url"
   }
 }

Modified: trunk/LayoutTests/inspector/model/auditTestCaseResult.html (238849 => 238850)


--- trunk/LayoutTests/inspector/model/auditTestCaseResult.html	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/inspector/model/auditTestCaseResult.html	2018-12-04 09:08:42 UTC (rev 238850)
@@ -71,6 +71,7 @@
                 },
                 metadata: {
                     startTimestamp: null,
+                    asyncTimestamp: null,
                     endTimestamp: null,
                     url: null,
                 },
@@ -90,7 +91,8 @@
                 },
                 metadata: {
                     startTimestamp: "1",
-                    endTimestamp: "2",
+                    asyncTimestamp: "2",
+                    endTimestamp: "3",
                     url: "validWithValidSubOptionals test result url",
                 },
             },

Modified: trunk/LayoutTests/inspector/model/auditTestGroupResult-expected.txt (238849 => 238850)


--- trunk/LayoutTests/inspector/model/auditTestGroupResult-expected.txt	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/inspector/model/auditTestGroupResult-expected.txt	2018-12-04 09:08:42 UTC (rev 238850)
@@ -70,7 +70,8 @@
       },
       "metadata": {
         "startTimestamp": "0001-01-01T00:00:00.000Z",
-        "endTimestamp": "0002-01-01T00:00:00.000Z",
+        "asyncTimestamp": "0002-01-01T00:00:00.000Z",
+        "endTimestamp": "0003-01-01T00:00:00.000Z",
         "url": "validWithValidOptionals test result url"
       }
     }
@@ -106,7 +107,8 @@
           },
           "metadata": {
             "startTimestamp": "0001-01-01T00:00:00.000Z",
-            "endTimestamp": "0002-01-01T00:00:00.000Z",
+            "asyncTimestamp": "0002-01-01T00:00:00.000Z",
+            "endTimestamp": "0003-01-01T00:00:00.000Z",
             "url": "validNested nested test result url"
           }
         }
@@ -129,8 +131,9 @@
         ]
       },
       "metadata": {
-        "startTimestamp": "0003-01-01T00:00:00.000Z",
-        "endTimestamp": "0004-01-01T00:00:00.000Z",
+        "startTimestamp": "0004-01-01T00:00:00.000Z",
+        "asyncTimestamp": "0005-01-01T00:00:00.000Z",
+        "endTimestamp": "0006-01-01T00:00:00.000Z",
         "url": "validNested test result url"
       }
     }

Modified: trunk/LayoutTests/inspector/model/auditTestGroupResult.html (238849 => 238850)


--- trunk/LayoutTests/inspector/model/auditTestGroupResult.html	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/LayoutTests/inspector/model/auditTestGroupResult.html	2018-12-04 09:08:42 UTC (rev 238850)
@@ -107,7 +107,8 @@
                         },
                         metadata: {
                             startTimestamp: "1",
-                            endTimestamp: "2",
+                            asyncTimestamp: "2",
+                            endTimestamp: "3",
                             url: "validWithValidOptionals test result url",
                         },
                     },
@@ -138,7 +139,8 @@
                                 },
                                 metadata: {
                                     startTimestamp: "1",
-                                    endTimestamp: "2",
+                                    asyncTimestamp: "2",
+                                    endTimestamp: "3",
                                     url: "validNested nested test result url",
                                 },
                             },
@@ -155,8 +157,9 @@
                             errors: ["validNested test result error"],
                         },
                         metadata: {
-                            startTimestamp: "3",
-                            endTimestamp: "4",
+                            startTimestamp: "4",
+                            asyncTimestamp: "5",
+                            endTimestamp: "6",
                             url: "validNested test result url",
                         },
                     },

Added: trunk/LayoutTests/inspector/runtime/awaitPromise-expected.txt (0 => 238850)


--- trunk/LayoutTests/inspector/runtime/awaitPromise-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/runtime/awaitPromise-expected.txt	2018-12-04 09:08:42 UTC (rev 238850)
@@ -0,0 +1,60 @@
+Tests functionality of Runtime.awaitPromise.
+
+
+== Running test suite: Runtime.awaitPromise
+-- Running test case: Runtime.awaitPromise.Resolve.Undefined
+PASS: The resolved value should be undefined
+
+-- Running test case: Runtime.awaitPromise.Resolve.Null
+PASS: The resolved value should be null
+
+-- Running test case: Runtime.awaitPromise.Resolve.Boolean
+PASS: The resolved value should be true
+
+-- Running test case: Runtime.awaitPromise.Resolve.Number
+PASS: The resolved value should be 42
+
+-- Running test case: Runtime.awaitPromise.Resolve.String
+PASS: The resolved value should be "foo"
+
+-- Running test case: Runtime.awaitPromise.Resolve.Array
+PASS: The resolved value should be [0,1]
+
+-- Running test case: Runtime.awaitPromise.Resolve.Object
+PASS: The resolved value should be {"a":1,"b":2}
+
+-- Running test case: Runtime.awaitPromise.Resolve.Chain
+PASS: The resolved value should be 3.
+
+-- Running test case: Runtime.awaitPromise.Reject.Undefined
+PASS: The rejected value should be undefined
+
+-- Running test case: Runtime.awaitPromise.Reject.Null
+PASS: The rejected value should be null
+
+-- Running test case: Runtime.awaitPromise.Reject.Boolean
+PASS: The rejected value should be true
+
+-- Running test case: Runtime.awaitPromise.Reject.Number
+PASS: The rejected value should be 42
+
+-- Running test case: Runtime.awaitPromise.Reject.String
+PASS: The rejected value should be "foo"
+
+-- Running test case: Runtime.awaitPromise.Reject.Array
+PASS: The rejected value should be [0,1]
+
+-- Running test case: Runtime.awaitPromise.Reject.Object
+PASS: The rejected value should be {"a":1,"b":2}
+
+-- Running test case: Runtime.awaitPromise.Reject.Chain
+PASS: The rejected value should be 3.
+
+-- Running test case: Runtime.awaitPromise.Error.NonPromiseObjectId
+PASS: Should produce an error.
+Error: Error: Object with given id is not a Promise
+
+-- Running test case: Runtime.awaitPromise.Error.InvalidPromiseObjectId
+PASS: Should produce an error.
+Error: Could not find InjectedScript for promiseObjectId
+

Added: trunk/LayoutTests/inspector/runtime/awaitPromise.html (0 => 238850)


--- trunk/LayoutTests/inspector/runtime/awaitPromise.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/runtime/awaitPromise.html	2018-12-04 09:08:42 UTC (rev 238850)
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script src=""
+<script>
+if (window.internals)
+    window.internals.settings.setUnhandledPromiseRejectionToConsoleEnabled(false);
+
+function test()
+{
+    let savedResultCount = 0;
+
+    let suite = InspectorTest.createAsyncSuite("Runtime.awaitPromise");
+
+    function addTest(name, _expression_, options = {}, callback) {
+        suite.addTestCase({
+            name,
+            async test() {
+                let evaluateResponse = await RuntimeAgent.evaluate(_expression_);
+                InspectorTest.assert(evaluateResponse.result.type === "object");
+                InspectorTest.assert(evaluateResponse.result.className === "Promise");
+
+                let awaitPromiseResponse = await RuntimeAgent.awaitPromise(evaluateResponse.result.objectId, options.returnByValue, options.generatePreview, options.saveResult);
+
+                if (!awaitPromiseResponse.wasThrown && options.saveResult)
+                    InspectorTest.assert(++savedResultCount === awaitPromiseResponse.savedResultIndex, "savedResultIndex should match.");
+
+                await callback(WI.RemoteObject.fromPayload(awaitPromiseResponse.result), awaitPromiseResponse.wasThrown, awaitPromiseResponse.savedResultIndex);
+            },
+        });
+    }
+
+    function addResolveTest(name, value, options = {}) {
+        let _expression_ = `new Promise((resolve, reject) => setTimeout(resolve, 0, ${JSON.stringify(value)}))`;
+        addTest(name, _expression_, options, async (remoteObject, wasThrown) => {
+            InspectorTest.assert(!wasThrown, "There should be no error.");
+            if (options.returnByValue) {
+                if (value && typeof value === "object")
+                    InspectorTest.expectShallowEqual(remoteObject.value, value, "The resolved value should be " + JSON.stringify(value));
+                else
+                    InspectorTest.expectEqual(remoteObject.value, value, "The resolved value should be " + JSON.stringify(value));
+            } else {
+                InspectorTest.expectEqual(remoteObject.type, value.type, "The type should be " + value.type);
+                InspectorTest.expectEqual(remoteObject.subtype, value.subtype, "The subtype should be " + value.subtype);
+                InspectorTest.expectEqual(remoteObject.description, value.description, "The description should be " + value.description);
+            }
+        });
+    }
+
+    function addRejectTest(name, value, options = {}) {
+        let _expression_ = `new Promise((resolve, reject) => setTimeout(reject, 0, ${JSON.stringify(value)}))`;
+        addTest(name, _expression_, options, async (remoteObject, wasThrown) => {
+            InspectorTest.assert(wasThrown, "There should be an error.");
+            if (value && typeof value === "object") {
+                let propertyDescriptors = await new Promise((resolve) => remoteObject.getPropertyDescriptorsAsObject(resolve));
+                let properties = Array.isArray(value) ? [] : {};
+                for (let key in value)
+                    properties[key] = propertyDescriptors[key].value.value;
+                InspectorTest.expectShallowEqual(properties, value, "The rejected value should be " + JSON.stringify(value));
+            } else
+                InspectorTest.expectEqual(remoteObject.value, value, "The rejected value should be " + JSON.stringify(value));
+        });
+    }
+
+    addResolveTest("Runtime.awaitPromise.Resolve.Undefined", undefined, {returnByValue: true, saveResult: true});
+    addResolveTest("Runtime.awaitPromise.Resolve.Null", null, {returnByValue: true, saveResult: true});
+    addResolveTest("Runtime.awaitPromise.Resolve.Boolean", true, {returnByValue: true, saveResult: true});
+    addResolveTest("Runtime.awaitPromise.Resolve.Number", 42, {returnByValue: true, saveResult: true});
+    addResolveTest("Runtime.awaitPromise.Resolve.String", "foo", {returnByValue: true, saveResult: true});
+    addResolveTest("Runtime.awaitPromise.Resolve.Array", [0, 1], {returnByValue: true, saveResult: true});
+    addResolveTest("Runtime.awaitPromise.Resolve.Object", {a: 1, b: 2}, {returnByValue: true, saveResult: true});
+
+    addTest("Runtime.awaitPromise.Resolve.Chain", `Promise.resolve(1).then(() => 2).then(() => 3)`, {returnByValue: true, saveResult: true}, async (remoteObject, wasThrown) => {
+        InspectorTest.assert(!wasThrown, "There should be no error.");
+        InspectorTest.expectEqual(remoteObject.value, 3, "The resolved value should be 3.");
+    });
+
+    addRejectTest("Runtime.awaitPromise.Reject.Undefined", undefined);
+    addRejectTest("Runtime.awaitPromise.Reject.Null", null);
+    addRejectTest("Runtime.awaitPromise.Reject.Boolean", true);
+    addRejectTest("Runtime.awaitPromise.Reject.Number", 42);
+    addRejectTest("Runtime.awaitPromise.Reject.String", "foo");
+    addRejectTest("Runtime.awaitPromise.Reject.Array", [0, 1]);
+    addRejectTest("Runtime.awaitPromise.Reject.Object", {a: 1, b: 2});
+
+    addTest("Runtime.awaitPromise.Reject.Chain", `Promise.reject(1).catch(() => Promise.reject(2)).catch(() => Promise.reject(3))`, {}, async (remoteObject, wasThrown) => {
+        InspectorTest.assert(wasThrown, "There should be an error.");
+        InspectorTest.expectEqual(remoteObject.value, 3, "The rejected value should be 3.");
+    });
+
+    suite.addTestCase({
+        name: "Runtime.awaitPromise.Error.NonPromiseObjectId",
+        test(resolve, reject) {
+            RuntimeAgent.evaluate("window")
+            .then((response) => RuntimeAgent.awaitPromise(response.result.objectId))
+            .then((response) => {
+                InspectorTest.fail("Should not be able to call awaitPromise for a non-Promise object.");
+                resolve();
+            })
+            .catch((error) => {
+                InspectorTest.expectThat(error, "Should produce an error.");
+                InspectorTest.log("Error: " + error);
+                resolve();
+            });
+        },
+    });
+
+    suite.addTestCase({
+        name: "Runtime.awaitPromise.Error.InvalidPromiseObjectId",
+        test(resolve, reject) {
+            const promiseObjectId = "DOES_NOT_EXIST";
+            RuntimeAgent.awaitPromise(promiseObjectId, (error) => {
+                InspectorTest.expectThat(error, "Should produce an error.");
+                InspectorTest.log("Error: " + error);
+                resolve();
+            });
+        },
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Tests functionality of Runtime.awaitPromise.</p>
+</body>
+</html>

Modified: trunk/Source/_javascript_Core/ChangeLog (238849 => 238850)


--- trunk/Source/_javascript_Core/ChangeLog	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/ChangeLog	2018-12-04 09:08:42 UTC (rev 238850)
@@ -1,3 +1,43 @@
+2018-12-04  Devin Rousso  <drou...@apple.com>
+
+        Web Inspector: Audit: tests should support async operations
+        https://bugs.webkit.org/show_bug.cgi?id=192171
+        <rdar://problem/46423562>
+
+        Reviewed by Joseph Pecoraro.
+
+        Add `awaitPromise` command for executing a callback when a Promise gets settled.
+
+        Drive-by: allow `wasThrown` to be optional, instead of expecting it to always have a value.
+
+        * inspector/protocol/Runtime.json:
+
+        * inspector/InjectedScriptSource.js:
+        (InjectedScript.prototype.awaitPromise): Added.
+
+        * inspector/InjectedScript.h:
+        * inspector/InjectedScript.cpp:
+        (Inspector::InjectedScript::evaluate):
+        (Inspector::InjectedScript::awaitPromise): Added.
+        (Inspector::InjectedScript::callFunctionOn):
+        (Inspector::InjectedScript::evaluateOnCallFrame):
+
+        * inspector/InjectedScriptBase.h:
+        * inspector/InjectedScriptBase.cpp:
+        (Inspector::InjectedScriptBase::makeEvalCall):
+        (Inspector::InjectedScriptBase::makeAsyncCall): Added.
+        (Inspector::InjcetedScriptBase::checkCallResult): Added.
+        (Inspector::InjcetedScriptBase::checkAsyncCallResult): Added.
+
+        * inspector/agents/InspectorRuntimeAgent.h:
+        * inspector/agents/InspectorRuntimeAgent.cpp:
+        (Inspector::InspectorRuntimeAgent::evaluate):
+        (Inspector::InspectorRuntimeAgent::awaitPromise):
+        (Inspector::InspectorRuntimeAgent::callFunctionOn):
+
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::evaluateOnCallFrame):
+
 2018-12-03  Ryan Haddad  <ryanhad...@apple.com>
 
         Unreviewed, rolling out r238833.

Modified: trunk/Source/_javascript_Core/inspector/InjectedScript.cpp (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/InjectedScript.cpp	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/InjectedScript.cpp	2018-12-04 09:08:42 UTC (rev 238850)
@@ -54,7 +54,7 @@
 {
 }
 
-void InjectedScript::evaluate(ErrorString& errorString, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, bool& wasThrown, std::optional<int>& savedResultIndex)
+void InjectedScript::evaluate(ErrorString& errorString, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex)
 {
     Deprecated::ScriptFunctionCall function(injectedScriptObject(), "evaluate"_s, inspectorEnvironment()->functionCallHandler());
     function.appendArgument(_expression_);
@@ -66,8 +66,18 @@
     makeEvalCall(errorString, function, result, wasThrown, savedResultIndex);
 }
 
-void InjectedScript::callFunctionOn(ErrorString& errorString, const String& objectId, const String& _expression_, const String& arguments, bool returnByValue, bool generatePreview, RefPtr<Protocol::Runtime::RemoteObject>& result, bool& wasThrown)
+void InjectedScript::awaitPromise(const String& promiseObjectId, bool returnByValue, bool generatePreview, bool saveResult, AsyncCallCallback&& callback)
 {
+    Deprecated::ScriptFunctionCall function(injectedScriptObject(), "awaitPromise"_s, inspectorEnvironment()->functionCallHandler());
+    function.appendArgument(promiseObjectId);
+    function.appendArgument(returnByValue);
+    function.appendArgument(generatePreview);
+    function.appendArgument(saveResult);
+    makeAsyncCall(function, WTFMove(callback));
+}
+
+void InjectedScript::callFunctionOn(ErrorString& errorString, const String& objectId, const String& _expression_, const String& arguments, bool returnByValue, bool generatePreview, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown)
+{
     Deprecated::ScriptFunctionCall function(injectedScriptObject(), "callFunctionOn"_s, inspectorEnvironment()->functionCallHandler());
     function.appendArgument(objectId);
     function.appendArgument(_expression_);
@@ -74,12 +84,13 @@
     function.appendArgument(arguments);
     function.appendArgument(returnByValue);
     function.appendArgument(generatePreview);
-    
-    std::optional<int> unused;
-    makeEvalCall(errorString, function, result, wasThrown, unused);
+
+    std::optional<int> savedResultIndex;
+    makeEvalCall(errorString, function, result, wasThrown, savedResultIndex);
+    ASSERT(!savedResultIndex);
 }
 
-void InjectedScript::evaluateOnCallFrame(ErrorString& errorString, JSC::JSValue callFrames, const String& callFrameId, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, bool& wasThrown, std::optional<int>& savedResultIndex)
+void InjectedScript::evaluateOnCallFrame(ErrorString& errorString, JSC::JSValue callFrames, const String& callFrameId, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex)
 {
     Deprecated::ScriptFunctionCall function(injectedScriptObject(), "evaluateOnCallFrame"_s, inspectorEnvironment()->functionCallHandler());
     function.appendArgument(callFrames);

Modified: trunk/Source/_javascript_Core/inspector/InjectedScript.h (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/InjectedScript.h	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/InjectedScript.h	2018-12-04 09:08:42 UTC (rev 238850)
@@ -33,6 +33,7 @@
 
 #include "InjectedScriptBase.h"
 #include <wtf/Forward.h>
+#include <wtf/Function.h>
 #include <wtf/RefPtr.h>
 
 namespace Deprecated {
@@ -50,9 +51,10 @@
     InjectedScript(Deprecated::ScriptObject, InspectorEnvironment*);
     virtual ~InjectedScript();
 
-    void evaluate(ErrorString&, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, bool& wasThrown, std::optional<int>& savedResultIndex);
-    void evaluateOnCallFrame(ErrorString&, JSC::JSValue callFrames, const String& callFrameId, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, bool& wasThrown, std::optional<int>& savedResultIndex);
-    void callFunctionOn(ErrorString&, const String& objectId, const String& _expression_, const String& arguments, bool returnByValue, bool generatePreview, RefPtr<Protocol::Runtime::RemoteObject>& result, bool& wasThrown);
+    void evaluate(ErrorString&, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex);
+    void awaitPromise(const String& promiseObjectId, bool returnByValue, bool generatePreview, bool saveResult, AsyncCallCallback&&);
+    void evaluateOnCallFrame(ErrorString&, JSC::JSValue callFrames, const String& callFrameId, const String& _expression_, const String& objectGroup, bool includeCommandLineAPI, bool returnByValue, bool generatePreview, bool saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex);
+    void callFunctionOn(ErrorString&, const String& objectId, const String& _expression_, const String& arguments, bool returnByValue, bool generatePreview, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown);
     void getFunctionDetails(ErrorString&, const String& functionId, RefPtr<Protocol::Debugger::FunctionDetails>& result);
     void functionDetails(ErrorString&, JSC::JSValue, RefPtr<Protocol::Debugger::FunctionDetails>& result);
     void getPreview(ErrorString&, const String& objectId, RefPtr<Protocol::Runtime::ObjectPreview>& result);

Modified: trunk/Source/_javascript_Core/inspector/InjectedScriptBase.cpp (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/InjectedScriptBase.cpp	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/InjectedScriptBase.cpp	2018-12-04 09:08:42 UTC (rev 238850)
@@ -35,6 +35,9 @@
 #include "DebuggerEvalEnabler.h"
 #include "JSCInlines.h"
 #include "JSGlobalObject.h"
+#include "JSLock.h"
+#include "JSNativeStdFunction.h"
+#include "NativeStdFunctionCell.h"
 #include "ScriptFunctionCall.h"
 #include <wtf/JSONValues.h>
 #include <wtf/text/WTFString.h>
@@ -93,9 +96,53 @@
     return resultJSONValue.releaseNonNull();
 }
 
-void InjectedScriptBase::makeEvalCall(ErrorString& errorString, Deprecated::ScriptFunctionCall& function, RefPtr<Protocol::Runtime::RemoteObject>& out_resultObject, bool& out_wasThrown, std::optional<int>& out_savedResultIndex)
+void InjectedScriptBase::makeEvalCall(ErrorString& errorString, Deprecated::ScriptFunctionCall& function, RefPtr<Protocol::Runtime::RemoteObject>& out_resultObject, std::optional<bool>& out_wasThrown, std::optional<int>& out_savedResultIndex)
 {
-    RefPtr<JSON::Value> result = makeCall(function);
+    checkCallResult(errorString, makeCall(function), out_resultObject, out_wasThrown, out_savedResultIndex);
+}
+
+void InjectedScriptBase::makeAsyncCall(Deprecated::ScriptFunctionCall& function, AsyncCallCallback&& callback)
+{
+    if (hasNoValue() || !hasAccessToInspectedScriptState()) {
+        checkAsyncCallResult(JSON::Value::null(), callback);
+        return;
+    }
+
+    auto* scriptState = m_injectedScriptObject.scriptState();
+    JSC::VM& vm = scriptState->vm();
+
+    JSC::JSNativeStdFunction* jsFunction;
+
+    {
+        JSC::JSLockHolder locker(vm);
+
+        jsFunction = JSC::JSNativeStdFunction::create(vm, scriptState->lexicalGlobalObject(), 1, String(), [&, callback = WTFMove(callback)] (JSC::ExecState* exec) {
+            if (!exec)
+                checkAsyncCallResult(JSON::Value::create("Exception while making a call."), callback);
+            if (auto resultJSONValue = toInspectorValue(*exec, exec->argument(0)))
+                checkAsyncCallResult(resultJSONValue, callback);
+            else
+                checkAsyncCallResult(JSON::Value::create(String::format("Object has too long reference chain (must not be longer than %d)", JSON::Value::maxDepth)), callback);
+            return JSC::JSValue::encode(JSC::jsUndefined());
+        });
+    }
+
+    function.appendArgument(JSC::JSValue(jsFunction));
+
+    bool hadException = false;
+    auto resultJSValue = callFunctionWithEvalEnabled(function, hadException);
+    ASSERT_UNUSED(resultJSValue, resultJSValue.isUndefined());
+
+    ASSERT(!hadException);
+    if (hadException) {
+        // Since `callback` is moved above, we can't call it if there's an exception while trying to
+        // execute the `JSNativeStdFunction` inside InjectedScriptSource.js.
+        jsFunction->nativeStdFunctionCell()->function()(nullptr);
+    }
+}
+
+void InjectedScriptBase::checkCallResult(ErrorString& errorString, RefPtr<JSON::Value> result, RefPtr<Protocol::Runtime::RemoteObject>& out_resultObject, std::optional<bool>& out_wasThrown, std::optional<int>& out_savedResultIndex)
+{
     if (!result) {
         errorString = "Internal error: result value is empty"_s;
         return;
@@ -126,12 +173,26 @@
     }
 
     out_resultObject = BindingTraits<Protocol::Runtime::RemoteObject>::runtimeCast(resultObject);
-    out_wasThrown = wasThrown;
 
+    if (wasThrown)
+        out_wasThrown = wasThrown;
+
     int savedResultIndex;
     if (resultTuple->getInteger("savedResultIndex"_s, savedResultIndex))
         out_savedResultIndex = savedResultIndex;
 }
 
+void InjectedScriptBase::checkAsyncCallResult(RefPtr<JSON::Value> result, const AsyncCallCallback& callback)
+{
+    ErrorString errorString;
+    RefPtr<Protocol::Runtime::RemoteObject> resultObject;
+    std::optional<bool> wasThrown;
+    std::optional<int> savedResultIndex;
+
+    checkCallResult(errorString, result, resultObject, wasThrown, savedResultIndex);
+
+    callback(errorString, WTFMove(resultObject), wasThrown, savedResultIndex);
+}
+
 } // namespace Inspector
 

Modified: trunk/Source/_javascript_Core/inspector/InjectedScriptBase.h (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/InjectedScriptBase.h	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/InjectedScriptBase.h	2018-12-04 09:08:42 UTC (rev 238850)
@@ -35,6 +35,7 @@
 #include "InspectorProtocolObjects.h"
 #include "ScriptObject.h"
 #include <wtf/Forward.h>
+#include <wtf/Function.h>
 #include <wtf/RefPtr.h>
 
 namespace Deprecated {
@@ -44,6 +45,7 @@
 namespace Inspector {
 
 typedef String ErrorString;
+typedef WTF::Function<void(ErrorString&, RefPtr<Protocol::Runtime::RemoteObject>&&, std::optional<bool>&, std::optional<int>&)> AsyncCallCallback;
 
 class JS_EXPORT_PRIVATE InjectedScriptBase {
 public:
@@ -64,9 +66,13 @@
     const Deprecated::ScriptObject& injectedScriptObject() const;
     JSC::JSValue callFunctionWithEvalEnabled(Deprecated::ScriptFunctionCall&, bool& hadException) const;
     Ref<JSON::Value> makeCall(Deprecated::ScriptFunctionCall&);
-    void makeEvalCall(ErrorString&, Deprecated::ScriptFunctionCall&, RefPtr<Protocol::Runtime::RemoteObject>& resultObject, bool& wasThrown, std::optional<int>& savedResultIndex);
+    void makeEvalCall(ErrorString&, Deprecated::ScriptFunctionCall&, RefPtr<Protocol::Runtime::RemoteObject>& resultObject, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex);
+    void makeAsyncCall(Deprecated::ScriptFunctionCall&, AsyncCallCallback&&);
 
 private:
+    void checkCallResult(ErrorString&, RefPtr<JSON::Value> result, RefPtr<Protocol::Runtime::RemoteObject>& resultObject, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex);
+    void checkAsyncCallResult(RefPtr<JSON::Value> result, const AsyncCallCallback&);
+
     String m_name;
     Deprecated::ScriptObject m_injectedScriptObject;
     InspectorEnvironment* m_environment { nullptr };

Modified: trunk/Source/_javascript_Core/inspector/InjectedScriptSource.js (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/InjectedScriptSource.js	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/InjectedScriptSource.js	2018-12-04 09:08:42 UTC (rev 238850)
@@ -108,6 +108,43 @@
         return this._evaluateAndWrap(InjectedScriptHost.evaluateWithScopeExtension, InjectedScriptHost, _expression_, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
     }
 
+    awaitPromise(promiseObjectId, returnByValue, generatePreview, saveResult, callback)
+    {
+        let parsedPromiseObjectId = this._parseObjectId(promiseObjectId);
+        let promiseObject = this._objectForId(parsedPromiseObjectId);
+        let promiseObjectGroupName = this._idToObjectGroupName[parsedPromiseObjectId.id];
+
+        if (!isDefined(promiseObject)) {
+            callback("Could not find object with given id");
+            return;
+        }
+
+        if (!(promiseObject instanceof Promise)) {
+            callback("Object with given id is not a Promise");
+            return;
+        }
+
+        let resolve = (value) => {
+            let returnObject = {
+                wasThrown: false,
+                result: RemoteObject.create(value, promiseObjectGroupName, returnByValue, generatePreview),
+            };
+
+            if (saveResult) {
+                this._savedResultIndex = 0;
+                this._saveResult(returnObject.result);
+                if (this._savedResultIndex)
+                    returnObject.savedResultIndex = this._savedResultIndex;
+            }
+
+            callback(returnObject);
+        };
+        let reject = (reason) => {
+            callback(this._createThrownValue(reason, promiseObjectGroupName));
+        };
+        promiseObject.then(resolve, reject);
+    }
+
     evaluateOnCallFrame(topCallFrame, callFrameId, _expression_, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
     {
         let callFrame = this._callFrameForId(topCallFrame, callFrameId);

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp	2018-12-04 09:08:42 UTC (rev 238850)
@@ -827,7 +827,7 @@
     m_pauseOnAssertionFailures = enabled;
 }
 
-void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString& errorString, const String& callFrameId, const String& _expression_, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& outWasThrown, std::optional<int>& savedResultIndex)
+void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString& errorString, const String& callFrameId, const String& _expression_, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex)
 {
     if (!m_currentCallStack) {
         errorString = "Not paused"_s;
@@ -848,11 +848,9 @@
         muteConsole();
     }
 
-    bool wasThrown;
     injectedScript.evaluateOnCallFrame(errorString, m_currentCallStack.get(), callFrameId, _expression_,
         objectGroup ? *objectGroup : emptyString(), includeCommandLineAPI && *includeCommandLineAPI, returnByValue && *returnByValue, generatePreview && *generatePreview, saveResult && *saveResult,
         result, wasThrown, savedResultIndex);
-    outWasThrown = wasThrown;
 
     if (pauseAndMute) {
         unmuteConsole();

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.cpp (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.cpp	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.cpp	2018-12-04 09:08:42 UTC (rev 238850)
@@ -111,7 +111,7 @@
     }
 }
 
-void InspectorRuntimeAgent::evaluate(ErrorString& errorString, const String& _expression_, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const int* executionContextId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& outWasThrown, std::optional<int>& savedResultIndex)
+void InspectorRuntimeAgent::evaluate(ErrorString& errorString, const String& _expression_, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const int* executionContextId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex)
 {
     InjectedScript injectedScript = injectedScriptForEval(errorString, executionContextId);
     if (injectedScript.hasNoValue())
@@ -123,9 +123,7 @@
     if (asBool(doNotPauseOnExceptionsAndMuteConsole))
         muteConsole();
 
-    bool wasThrown;
     injectedScript.evaluate(errorString, _expression_, objectGroup ? *objectGroup : String(), asBool(includeCommandLineAPI), asBool(returnByValue), asBool(generatePreview), asBool(saveResult), result, wasThrown, savedResultIndex);
-    outWasThrown = wasThrown;
 
     if (asBool(doNotPauseOnExceptionsAndMuteConsole)) {
         unmuteConsole();
@@ -133,8 +131,24 @@
     }
 }
 
-void InspectorRuntimeAgent::callFunctionOn(ErrorString& errorString, const String& objectId, const String& _expression_, const JSON::Array* optionalArguments, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& outWasThrown)
+void InspectorRuntimeAgent::awaitPromise(const String& promiseObjectId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, Ref<AwaitPromiseCallback>&& callback)
 {
+    InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(promiseObjectId);
+    if (injectedScript.hasNoValue()) {
+        callback->sendFailure("Could not find InjectedScript for promiseObjectId"_s);
+        return;
+    }
+
+    injectedScript.awaitPromise(promiseObjectId, asBool(returnByValue), asBool(generatePreview), asBool(saveResult), [callback = WTFMove(callback)] (ErrorString& errorString, RefPtr<Protocol::Runtime::RemoteObject>&& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex) {
+        if (!errorString.isEmpty())
+            callback->sendFailure(errorString);
+        else
+            callback->sendSuccess(WTFMove(result), wasThrown, savedResultIndex);
+    });
+}
+
+void InspectorRuntimeAgent::callFunctionOn(ErrorString& errorString, const String& objectId, const String& _expression_, const JSON::Array* optionalArguments, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown)
+{
     InjectedScript injectedScript = m_injectedScriptManager.injectedScriptForObjectId(objectId);
     if (injectedScript.hasNoValue()) {
         errorString = "Could not find InjectedScript for objectId"_s;
@@ -151,12 +165,8 @@
     if (asBool(doNotPauseOnExceptionsAndMuteConsole))
         muteConsole();
 
-    bool wasThrown;
-
     injectedScript.callFunctionOn(errorString, objectId, _expression_, arguments, asBool(returnByValue), asBool(generatePreview), result, wasThrown);
 
-    outWasThrown = wasThrown;
-
     if (asBool(doNotPauseOnExceptionsAndMuteConsole)) {
         unmuteConsole();
         setPauseOnExceptionsState(m_scriptDebugServer, previousPauseOnExceptionsState);

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.h (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.h	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorRuntimeAgent.h	2018-12-04 09:08:42 UTC (rev 238850)
@@ -59,6 +59,7 @@
     void disable(ErrorString&) override { m_enabled = false; }
     void parse(ErrorString&, const String& _expression_, Protocol::Runtime::SyntaxErrorType* result, std::optional<String>& message, RefPtr<Protocol::Runtime::ErrorRange>&) final;
     void evaluate(ErrorString&, const String& _expression_, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const int* executionContextId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown, std::optional<int>& savedResultIndex) final;
+    void awaitPromise(const String& promiseObjectId, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, Ref<AwaitPromiseCallback>&&) final;
     void callFunctionOn(ErrorString&, const String& objectId, const String& _expression_, const JSON::Array* optionalArguments, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, RefPtr<Protocol::Runtime::RemoteObject>& result, std::optional<bool>& wasThrown) final;
     void releaseObject(ErrorString&, const ErrorString& objectId) final;
     void getPreview(ErrorString&, const String& objectId, RefPtr<Protocol::Runtime::ObjectPreview>&) final;

Modified: trunk/Source/_javascript_Core/inspector/protocol/Runtime.json (238849 => 238850)


--- trunk/Source/_javascript_Core/inspector/protocol/Runtime.json	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/_javascript_Core/inspector/protocol/Runtime.json	2018-12-04 09:08:42 UTC (rev 238850)
@@ -226,6 +226,22 @@
             ]
         },
         {
+            "name": "awaitPromise",
+            "description": "Calls the async callback when the promise with the given ID gets settled.",
+            "parameters": [
+                { "name": "promiseObjectId", "$ref": "RemoteObjectId", "description": "Identifier of the promise." },
+                { "name": "returnByValue", "type": "boolean", "optional": true, "description": "Whether the result is expected to be a JSON object that should be sent by value." },
+                { "name": "generatePreview", "type": "boolean", "optional": true, "description": "Whether preview should be generated for the result." },
+                { "name": "saveResult", "type": "boolean", "optional": true, "description": "Whether the resulting value should be considered for saving in the $n history." }
+            ],
+            "returns": [
+                { "name": "result", "$ref": "RemoteObject", "description": "Evaluation result." },
+                { "name": "wasThrown", "type": "boolean", "optional": true, "description": "True if the result was thrown during the evaluation." },
+                { "name": "savedResultIndex", "type": "integer", "optional": true, "description": "If the result was saved, this is the $n index that can be used to access the value." }
+            ],
+            "async": true
+        },
+        {
             "name": "callFunctionOn",
             "description": "Calls function with given declaration on the given object. Object group of the result is inherited from the target object.",
             "parameters": [

Modified: trunk/Source/WebCore/ChangeLog (238849 => 238850)


--- trunk/Source/WebCore/ChangeLog	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebCore/ChangeLog	2018-12-04 09:08:42 UTC (rev 238850)
@@ -1,3 +1,16 @@
+2018-12-04  Devin Rousso  <drou...@apple.com>
+
+        Web Inspector: Audit: tests should support async operations
+        https://bugs.webkit.org/show_bug.cgi?id=192171
+        <rdar://problem/46423562>
+
+        Reviewed by Joseph Pecoraro.
+
+        * page/Settings.yaml:
+        * dom/ScriptExecutionContext.cpp:
+        (ScriptExecutionContext::reportUnhandledPromiseRejection):
+        Add setting for muting the "Unhandled Promise Rejection" console message.
+
 2018-12-03  Tim Horton  <timothy_hor...@apple.com>
 
         Fix the build

Modified: trunk/Source/WebCore/dom/ScriptExecutionContext.cpp (238849 => 238850)


--- trunk/Source/WebCore/dom/ScriptExecutionContext.cpp	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebCore/dom/ScriptExecutionContext.cpp	2018-12-04 09:08:42 UTC (rev 238850)
@@ -39,6 +39,7 @@
 #include "JSDOMWindow.h"
 #include "MessagePort.h"
 #include "Navigator.h"
+#include "Page.h"
 #include "PublicURLManager.h"
 #include "RejectedPromiseTracker.h"
 #include "ResourceRequest.h"
@@ -392,6 +393,14 @@
 
 void ScriptExecutionContext::reportUnhandledPromiseRejection(JSC::ExecState& state, JSC::JSPromise& promise, RefPtr<Inspector::ScriptCallStack>&& callStack)
 {
+    Page* page = nullptr;
+    if (is<Document>(this))
+        page = downcast<Document>(this)->page();
+    // FIXME: allow Workers to mute unhandled promise rejection messages.
+
+    if (page && !page->settings().unhandledPromiseRejectionToConsoleEnabled())
+        return;
+
     JSC::VM& vm = state.vm();
     auto scope = DECLARE_CATCH_SCOPE(vm);
 

Modified: trunk/Source/WebCore/page/Settings.yaml (238849 => 238850)


--- trunk/Source/WebCore/page/Settings.yaml	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebCore/page/Settings.yaml	2018-12-04 09:08:42 UTC (rev 238850)
@@ -224,6 +224,8 @@
   initial: false
 webGLErrorsToConsoleEnabled:
   initial: true
+unhandledPromiseRejectionToConsoleEnabled:
+  initial: true
 forceSoftwareWebGLRendering:
   initial: false
 forceWebGLUsesLowPower:

Modified: trunk/Source/WebInspectorUI/ChangeLog (238849 => 238850)


--- trunk/Source/WebInspectorUI/ChangeLog	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebInspectorUI/ChangeLog	2018-12-04 09:08:42 UTC (rev 238850)
@@ -1,3 +1,31 @@
+2018-12-04  Devin Rousso  <drou...@apple.com>
+
+        Web Inspector: Audit: tests should support async operations
+        https://bugs.webkit.org/show_bug.cgi?id=192171
+        <rdar://problem/46423562>
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Controllers/RuntimeManager.js:
+        (WI.RuntimeManager.supportsAwaitPromise): Added.
+
+        * UserInterface/Models/AuditTestCase.js:
+        (WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty.addErrorForValueType): Deleted.
+        (WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty): Deleted.
+        (WI.AuditTestCase.prototype.async run.async parseResponse.async resultArrayForEach): Deleted.
+        (WI.AuditTestCase.prototype.async run.async parseResponse): Added.
+        (WI.AuditTestCase.prototype.async run):
+        (WI.AuditTestCase.prototype.async run.checkResultProperty.addErrorForValueType): Deleted.
+        (WI.AuditTestCase.prototype.async run.checkResultProperty): Deleted.
+        (WI.AuditTestCase.prototype.async run.async resultArrayForEach): Deleted.
+
+        * UserInterface/Models/AuditTestCaseResult.js:
+        (WI.AuditTestCaseResult.async fromPayload):
+        (WI.AuditTestCaseResult.prototype.toJSON):
+
+        * UserInterface/Views/AuditTestCaseContentView.js:
+        (WI.AuditTestCaseContentView.prototype.layout):
+
 2018-12-03  Devin Rousso  <drou...@apple.com>
 
         Web Inspector: Audit: save the expanded state of test groups

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js (238849 => 238850)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js	2018-12-04 09:08:42 UTC (rev 238850)
@@ -34,6 +34,14 @@
         WI.Frame.addEventListener(WI.Frame.Event.ExecutionContextsCleared, this._frameExecutionContextsCleared, this);
     }
 
+    // Static
+
+    static supportsAwaitPromise()
+    {
+        // COMPATIBILITY (iOS 12): Runtime.awaitPromise did not exist
+        return !!InspectorBackend.domains.Runtime.awaitPromise;
+    }
+
     // Target
 
     initializeTarget(target)

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js (238849 => 238850)


--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js	2018-12-04 09:08:42 UTC (rev 238850)
@@ -112,13 +112,9 @@
             doNotPauseOnExceptionsAndMuteConsole: true,
         };
 
-        try {
-            metadata.startTimestamp = new Date;
-            let evaluateResponse = await RuntimeAgent.evaluate.invoke(evaluateArguments);
-            metadata.endTimestamp = new Date;
-
-            let remoteObject = WI.RemoteObject.fromPayload(evaluateResponse.result, WI.mainTarget);
-            if (evaluateResponse.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error"))
+        async function parseResponse(response) {
+            let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget);
+            if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error"))
                 addError(remoteObject.description);
             else if (remoteObject.type === "boolean")
                 setLevel(remoteObject.value ? WI.AuditTestCaseResult.Level.Pass : WI.AuditTestCaseResult.Level.Fail);
@@ -234,6 +230,27 @@
                 });
             } else
                 addError(WI.UIString("Return value is not an object, string, or boolean"));
+        }
+
+        try {
+            metadata.startTimestamp = new Date;
+            let response = await RuntimeAgent.evaluate.invoke(evaluateArguments);
+            metadata.endTimestamp = new Date;
+
+            if (response.result.type === "object" && response.result.className === "Promise") {
+                if (WI.RuntimeManager.supportsAwaitPromise()) {
+                    metadata.asyncTimestamp = metadata.endTimestamp;
+                    response = await RuntimeAgent.awaitPromise(response.result.objectId);
+                    metadata.endTimestamp = new Date;
+                } else {
+                    response = null;
+                    addError(WI.UIString("Async audits are not supported."));
+                    setLevel(WI.AuditTestCaseResult.Level.Unsupported);
+                }
+            }
+
+            if (response)
+                await parseResponse(response);
         } catch (error) {
             metadata.endTimestamp = new Date;
             addError(error.message);

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js (238849 => 238850)


--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js	2018-12-04 09:08:42 UTC (rev 238850)
@@ -80,6 +80,7 @@
             metadata = {};
         else {
             metadata.startTimestamp = typeof metadata.startTimestamp === "string" ? new Date(metadata.startTimestamp) : null;
+            metadata.asyncTimestamp = typeof metadata.asyncTimestamp === "string" ? new Date(metadata.asyncTimestamp) : null;
             metadata.endTimestamp = typeof metadata.endTimestamp === "string" ? new Date(metadata.endTimestamp) : null;
             metadata.url = "" metadata.url ="" "string" ? metadata.url : null;
         }
@@ -112,6 +113,8 @@
             options.metadata = {};
             if (metadata.startTimestamp && !isNaN(metadata.startTimestamp))
                 options.metadata.startTimestamp = metadata.startTimestamp;
+            if (metadata.asyncTimestamp && !isNaN(metadata.asyncTimestamp))
+                options.metadata.asyncTimestamp = metadata.asyncTimestamp;
             if (metadata.endTimestamp && !isNaN(metadata.endTimestamp))
                 options.metadata.endTimestamp = metadata.endTimestamp;
             if (metadata.url)
@@ -177,6 +180,8 @@
         let metadata = {};
         if (this._metadata.startTimestamp && !isNaN(this._metadata.startTimestamp))
             metadata.startTimestamp = this._metadata.startTimestamp;
+        if (this._metadata.asyncTimestamp && !isNaN(this._metadata.asyncTimestamp))
+            metadata.asyncTimestamp = this._metadata.asyncTimestamp;
         if (this._metadata.endTimestamp && !isNaN(this._metadata.endTimestamp))
             metadata.endTimestamp = this._metadata.endTimestamp;
         if (this._metadata.url)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js (238849 => 238850)


--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js	2018-12-04 09:00:54 UTC (rev 238849)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js	2018-12-04 09:08:42 UTC (rev 238850)
@@ -104,9 +104,19 @@
                 timeElement.textContent = metadata.startTimestamp.toLocaleString();
 
                 if (metadata.endTimestamp) {
+                    let totalDuration = Number.secondsToString((metadata.endTimestamp - metadata.startTimestamp) / 1000);
+
                     let durationElement = this._metadataElement.appendChild(document.createElement("span"));
                     durationElement.classList.add("duration");
-                    durationElement.textContent = Number.secondsToString((metadata.endTimestamp - metadata.startTimestamp) / 1000);
+                    durationElement.textContent = totalDuration;
+
+                    if (metadata.asyncTimestamp) {
+                        let evalDuration = Number.secondsToString((metadata.asyncTimestamp - metadata.startTimestamp) / 1000);
+                        let asyncDuration = Number.secondsToString((metadata.endTimestamp - metadata.asyncTimestamp) / 1000);
+
+                        durationElement.classList.add("async");
+                        durationElement.title = WI.UIString("%s eval\n%s async").format(evalDuration, asyncDuration);
+                    }
                 }
             }
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to