Title: [210033] trunk
Revision
210033
Author
joep...@webkit.org
Date
2016-12-20 13:41:03 -0800 (Tue, 20 Dec 2016)

Log Message

Web Inspector: Console could be made useful for very simple await expressions
https://bugs.webkit.org/show_bug.cgi?id=165681
<rdar://problem/29755339>

Reviewed by Brian Burg.

Source/WebInspectorUI:

Normally await expressions are only allowed inside of async functions.
They make dealing with async operations easy, but can't be used directly
in Web Inspector's console without making your own async function wrapper.

This change allows simple await expressions to be run in the console.
The supported syntaxes are (simple _expression_ with optional assignment):

    await <expr>
    x = await <expr>
    let x = await <expr>

Web Inspector's console will automatically wrap this in an async
function and report the resulting value or exception. For instance
in the last example above:

    let x;
    (async function() {
        try {
            x = await <expr>;
            console.info("%o", x);
        } catch (e) {
            console.error(e);
        }
    })();
    undefined

This way users can get the convenience of await in the Console.
This also gives users a nice way of extracting a value out of
a Promise without writing their own handlers.

* UserInterface/Controllers/RuntimeManager.js:
(WebInspector.RuntimeManager.prototype.evaluateInInspectedWindow):
(WebInspector.RuntimeManager.prototype._tryApplyAwaitConvenience):
Wrap simple await expressions into a function that will log the result.

LayoutTests:

* inspector/controller/runtime-controller-expected.txt:
* inspector/controller/runtime-controller.html:
Test the "await _expression_" convenience of RuntimeManager.

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (210032 => 210033)


--- trunk/LayoutTests/ChangeLog	2016-12-20 21:41:00 UTC (rev 210032)
+++ trunk/LayoutTests/ChangeLog	2016-12-20 21:41:03 UTC (rev 210033)
@@ -1,3 +1,15 @@
+2016-12-20  Joseph Pecoraro  <pecor...@apple.com>
+
+        Web Inspector: Console could be made useful for very simple await expressions
+        https://bugs.webkit.org/show_bug.cgi?id=165681
+        <rdar://problem/29755339>
+
+        Reviewed by Brian Burg.
+
+        * inspector/controller/runtime-controller-expected.txt:
+        * inspector/controller/runtime-controller.html:
+        Test the "await _expression_" convenience of RuntimeManager.
+
 2016-12-20  Ryan Haddad  <ryanhad...@apple.com>
 
         Rebaseline js/dom/global-constructors-attributes.html for mac-elcapitan after r210024.

Modified: trunk/LayoutTests/inspector/controller/runtime-controller-expected.txt (210032 => 210033)


--- trunk/LayoutTests/inspector/controller/runtime-controller-expected.txt	2016-12-20 21:41:00 UTC (rev 210032)
+++ trunk/LayoutTests/inspector/controller/runtime-controller-expected.txt	2016-12-20 21:41:03 UTC (rev 210033)
@@ -1,4 +1,13 @@
-Tests for the Runtime.parse command.
+CONSOLE MESSAGE: line 7: %o
+CONSOLE MESSAGE: line 7: %o
+CONSOLE MESSAGE: line 7: %o
+CONSOLE MESSAGE: line 9: Thrown exception
+CONSOLE MESSAGE: line 7: %o
+CONSOLE MESSAGE: line 9: Promise.reject
+CONSOLE MESSAGE: line 9: Rejection
+CONSOLE MESSAGE: line 7: %o
+CONSOLE MESSAGE: line 7: %o
+Tests for RuntimeManager operations.
 
 
 == Running test suite: RuntimeManager
@@ -20,3 +29,32 @@
 Source: ;{ let a = 1; a += 1; a }
 PASS: Evaluation should produce the labeled statement's value.
 
+-- Running test case: RuntimeManager.prototype.evaluateInInspectedWindow.AwaitConvenience
+
+Source: await 1
+PASS: Transformed. Should log the value or an exception.
+Source:    await 2   
+PASS: Transformed. Should log the value or an exception.
+Source: var x = await 3
+PASS: Transformed. Should log the value or an exception.
+Source: await causeExceptionImmediately()
+PASS: Transformed. Should log the value or an exception.
+Source: await Promise.resolve(4)
+PASS: Transformed. Should log the value or an exception.
+Source: await Promise.reject('Promise.reject')
+PASS: Transformed. Should log the value or an exception.
+Source: await rejectedEventually()
+PASS: Transformed. Should log the value or an exception.
+Source: await asyncOperation()
+PASS: Transformed. Should log the value or an exception.
+Source: x = await asyncOperation()
+PASS: Transformed. Should log the value or an exception.
+Source: return 10
+PASS: Exception. Should not get transformed and produce a SyntaxError.
+Source: await 10; 1
+PASS: Exception. Should not get transformed and produce a SyntaxError.
+Source: 1; await 10;
+PASS: Exception. Should not get transformed and produce a SyntaxError.
+Source: x = y = await 10
+PASS: Exception. Should not get transformed and produce a SyntaxError.
+

Modified: trunk/LayoutTests/inspector/controller/runtime-controller.html (210032 => 210033)


--- trunk/LayoutTests/inspector/controller/runtime-controller.html	2016-12-20 21:41:00 UTC (rev 210032)
+++ trunk/LayoutTests/inspector/controller/runtime-controller.html	2016-12-20 21:41:03 UTC (rev 210033)
@@ -3,6 +3,27 @@
 <head>
 <script src=""
 <script>
+let resultNumber = 100;
+function asyncOperation() {
+    return new Promise((resolve, reject) => {
+        setTimeout(() => {
+            resolve(resultNumber++);
+        }, 50);
+    });
+}
+
+function rejectedEventually() {
+    return new Promise((resolve, reject) => {
+        setTimeout(() => {
+            reject("Rejection");
+        }, 50);
+    });
+}
+
+function causeExceptionImmediately() {
+    throw "Thrown exception";
+}
+
 function test()
 {
     let suite = InspectorTest.createAsyncSuite("RuntimeManager");
@@ -10,7 +31,7 @@
     suite.addTestCase({
         name: "RuntimeManager.prototype.evaluateInInspectedWindow.ObjectLiteralConvenience",
         description: "Test evaluating an object literal string conveniently converts wraps it in parenthesis to avoid misinterpretation as a program with a block and labeled statement.",
-        test: (resolve, reject) => {
+        test(resolve, reject) {
             function testSource(_expression_, callback) {
                 WebInspector.runtimeManager.evaluateInInspectedWindow(_expression_, {objectGroup: "test"}, (result, wasThrown) => {
                     InspectorTest.log("Source: " + _expression_);
@@ -50,11 +71,64 @@
         }
     });
 
+    suite.addTestCase({
+        name: "RuntimeManager.prototype.evaluateInInspectedWindow.AwaitConvenience",
+        description: "Test evaluating a simple await _expression_ wraps it into an async function and eventually resolves the value.",
+        test(resolve, reject) {
+            function testSource(_expression_, callback) {
+                WebInspector.runtimeManager.evaluateInInspectedWindow(_expression_, {objectGroup: "test"}, (result, wasThrown) => {
+                    InspectorTest.log("Source: " + _expression_);
+                    callback(result, wasThrown);
+                });
+            }
+
+            function expectUndefined(result, wasThrown) {
+                InspectorTest.expectThat(result.isUndefined(), "Transformed. Should log the value or an exception.");
+            }
+
+            function expectException(result, wasThrown) {
+                InspectorTest.expectThat(wasThrown, "Exception. Should not get transformed and produce a SyntaxError.");
+            }
+
+            // The convenience will detect these and make an async task.
+            const expected = 6;
+            testSource("await 1", expectUndefined);
+            testSource("   await 2   ", expectUndefined);
+            testSource("var x = await 3", expectUndefined);
+            testSource("await causeExceptionImmediately()", expectUndefined);
+            testSource("await Promise.resolve(4)", expectUndefined);
+            testSource("await Promise.reject('Promise.reject')", expectUndefined);
+            testSource("await rejectedEventually()", expectUndefined);
+            testSource("await asyncOperation()", expectUndefined);
+            testSource("x = await asyncOperation()", expectUndefined);
+
+            InspectorTest.log("");
+
+            // The convenience will not apply to these noexpressions.
+            testSource("return 10", expectException);
+            testSource("await 10; 1", expectException);
+            testSource("1; await 10;", expectException);
+            testSource("x = y = await 10", expectException);
+
+            // We can resolve after receiving the expected number of console.info messages.
+            let seen = 0;
+            let listener = WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.MessageAdded, (event) => {
+                let consoleMessage = event.data.message;
+                if (consoleMessage.level !== WebInspector.ConsoleMessage.MessageLevel.Info)
+                    return;
+                if (++seen !== expected)
+                    return;
+                WebInspector.logManager.removeEventListener(WebInspector.LogManager.Event.MessageAdded, listener);
+                resolve();
+            });
+        }
+    });
+
     suite.runTestCasesAndFinish();
 }
 </script>
 </head>
 <body _onload_="runTest()">
-<p>Tests for the Runtime.parse command.</p>
+<p>Tests for RuntimeManager operations.</p>
 </body>
 </html>

Modified: trunk/Source/WebInspectorUI/ChangeLog (210032 => 210033)


--- trunk/Source/WebInspectorUI/ChangeLog	2016-12-20 21:41:00 UTC (rev 210032)
+++ trunk/Source/WebInspectorUI/ChangeLog	2016-12-20 21:41:03 UTC (rev 210033)
@@ -1,5 +1,48 @@
 2016-12-20  Joseph Pecoraro  <pecor...@apple.com>
 
+        Web Inspector: Console could be made useful for very simple await expressions
+        https://bugs.webkit.org/show_bug.cgi?id=165681
+        <rdar://problem/29755339>
+
+        Reviewed by Brian Burg.
+
+        Normally await expressions are only allowed inside of async functions.
+        They make dealing with async operations easy, but can't be used directly
+        in Web Inspector's console without making your own async function wrapper.
+
+        This change allows simple await expressions to be run in the console.
+        The supported syntaxes are (simple _expression_ with optional assignment):
+
+            await <expr>
+            x = await <expr>
+            let x = await <expr>
+
+        Web Inspector's console will automatically wrap this in an async
+        function and report the resulting value or exception. For instance
+        in the last example above:
+
+            let x;
+            (async function() {
+                try {
+                    x = await <expr>;
+                    console.info("%o", x);
+                } catch (e) {
+                    console.error(e);
+                }
+            })();
+            undefined
+
+        This way users can get the convenience of await in the Console.
+        This also gives users a nice way of extracting a value out of
+        a Promise without writing their own handlers.
+
+        * UserInterface/Controllers/RuntimeManager.js:
+        (WebInspector.RuntimeManager.prototype.evaluateInInspectedWindow):
+        (WebInspector.RuntimeManager.prototype._tryApplyAwaitConvenience):
+        Wrap simple await expressions into a function that will log the result.
+
+2016-12-20  Joseph Pecoraro  <pecor...@apple.com>
+
         Web Inspector: Update CodeMirror to support async/await keyword and other ES2017 features
         https://bugs.webkit.org/show_bug.cgi?id=165677
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js (210032 => 210033)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js	2016-12-20 21:41:00 UTC (rev 210032)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js	2016-12-20 21:41:03 UTC (rev 210033)
@@ -152,7 +152,7 @@
     _possiblePlaceholder()
     {
         let character = this._consume();
-        console.assert(character === "$")
+        console.assert(character === "$");
         let nextCharacter = this._peek();
 
         console.assert(this._states.lastValue === WebInspector.BreakpointLogMessageLexer.State.PossiblePlaceholder);

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js (210032 => 210033)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js	2016-12-20 21:41:00 UTC (rev 210032)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js	2016-12-20 21:41:03 UTC (rev 210033)
@@ -74,6 +74,9 @@
         } else if (/^\s*\{/.test(_expression_) && /\}\s*$/.test(_expression_)) {
             // Transform {a:1} to ({a:1}) so it is treated like an object literal instead of a block with a label.
             _expression_ = "(" + _expression_ + ")";
+        } else if (/\bawait\b/.test(_expression_)) {
+            // Transform `await <expr>` into an async function assignment.
+            _expression_ = this._tryApplyAwaitConvenience(_expression_);
         }
 
         _expression_ = sourceURLAppender(_expression_);
@@ -162,6 +165,91 @@
         if (currentContextWasDestroyed)
             this.activeExecutionContext = WebInspector.mainTarget.executionContext;
     }
+
+    _tryApplyAwaitConvenience(originalExpression)
+    {
+        let esprimaSyntaxTree;
+
+        // Do not transform if the original code parses just fine.
+        try {
+            esprima.parse(originalExpression);
+            return originalExpression;
+        } catch (error) { }
+
+        // Do not transform if the async function version does not parse.
+        try {
+            esprimaSyntaxTree = esprima.parse("(async function(){" + originalExpression + "})");
+        } catch (error) {
+            return originalExpression;
+        }
+
+        // Assert expected AST produced by our wrapping code.
+        console.assert(esprimaSyntaxTree.type === "Program");
+        console.assert(esprimaSyntaxTree.body.length === 1);
+        console.assert(esprimaSyntaxTree.body[0].type === "ExpressionStatement");
+        console.assert(esprimaSyntaxTree.body[0]._expression_.type === "FunctionExpression");
+        console.assert(esprimaSyntaxTree.body[0]._expression_.async);
+        console.assert(esprimaSyntaxTree.body[0]._expression_.body.type === "BlockStatement");
+
+        // Do not transform if there is more than one statement.
+        let asyncFunctionBlock = esprimaSyntaxTree.body[0]._expression_.body;
+        if (asyncFunctionBlock.body.length !== 1)
+            return originalExpression;
+
+        // Extract the variable name for transformation.
+        let variableName;
+        let anonymous = false;
+        let declarationKind = "var";
+        let awaitPortion;
+        let statement = asyncFunctionBlock.body[0];
+        if (statement.type === "ExpressionStatement"
+            && statement._expression_.type === "AwaitExpression") {
+            // await <expr>
+            anonymous = true;
+        } else if (statement.type === "ExpressionStatement"
+            && statement._expression_.type === "AssignmentExpression"
+            && statement._expression_.right.type === "AwaitExpression"
+            && statement._expression_.left.type === "Identifier") {
+            // x = await <expr>
+            variableName = statement._expression_.left.name;
+            awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
+        } else if (statement.type === "VariableDeclaration"
+            && statement.declarations.length === 1
+            && statement.declarations[0].init.type === "AwaitExpression"
+            && statement.declarations[0].id.type === "Identifier") {
+            // var x = await <expr>
+            variableName = statement.declarations[0].id.name;
+            declarationKind = statement.kind;
+            awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
+        } else {
+            // Do not transform if this was not one of the simple supported syntaxes.
+            return originalExpression;
+        }
+
+        if (anonymous) {
+            return `
+(async function() {
+    try {
+        let result = ${originalExpression};
+        console.info("%o", result);
+    } catch (e) {
+        console.error(e);
+    }
+})();
+undefined`;
+        }
+
+        return `${declarationKind} ${variableName};
+(async function() {
+    try {
+        ${variableName} = ${awaitPortion};
+        console.info("%o", ${variableName});
+    } catch (e) {
+        console.error(e);
+    }
+})();
+undefined;`;
+    }
 };
 
 WebInspector.RuntimeManager.ConsoleObjectGroup = "console";
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to