<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[210033] trunk</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/210033">210033</a></dd>
<dt>Author</dt> <dd>joepeck@webkit.org</dd>
<dt>Date</dt> <dd>2016-12-20 13:41:03 -0800 (Tue, 20 Dec 2016)</dd>
</dl>
<h3>Log Message</h3>
<pre>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.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsinspectorcontrollerruntimecontrollerexpectedtxt">trunk/LayoutTests/inspector/controller/runtime-controller-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorcontrollerruntimecontrollerhtml">trunk/LayoutTests/inspector/controller/runtime-controller.html</a></li>
<li><a href="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceControllersBreakpointLogMessageLexerjs">trunk/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceControllersRuntimeManagerjs">trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (210032 => 210033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-12-20 21:41:00 UTC (rev 210032)
+++ trunk/LayoutTests/ChangeLog        2016-12-20 21:41:03 UTC (rev 210033)
</span><span class="lines">@@ -1,3 +1,15 @@
</span><ins>+2016-12-20 Joseph Pecoraro <pecoraro@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.
+
</ins><span class="cx"> 2016-12-20 Ryan Haddad <ryanhaddad@apple.com>
</span><span class="cx">
</span><span class="cx"> Rebaseline js/dom/global-constructors-attributes.html for mac-elcapitan after r210024.
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorcontrollerruntimecontrollerexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/controller/runtime-controller-expected.txt (210032 => 210033)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,4 +1,13 @@
</span><del>-Tests for the Runtime.parse command.
</del><ins>+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.
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> == Running test suite: RuntimeManager
</span><span class="lines">@@ -20,3 +29,32 @@
</span><span class="cx"> Source: ;{ let a = 1; a += 1; a }
</span><span class="cx"> PASS: Evaluation should produce the labeled statement's value.
</span><span class="cx">
</span><ins>+-- 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.
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorcontrollerruntimecontrollerhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/controller/runtime-controller.html (210032 => 210033)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -3,6 +3,27 @@
</span><span class="cx"> <head>
</span><span class="cx"> <script src="../../http/tests/inspector/resources/inspector-test.js"></script>
</span><span class="cx"> <script>
</span><ins>+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";
+}
+
</ins><span class="cx"> function test()
</span><span class="cx"> {
</span><span class="cx"> let suite = InspectorTest.createAsyncSuite("RuntimeManager");
</span><span class="lines">@@ -10,7 +31,7 @@
</span><span class="cx"> suite.addTestCase({
</span><span class="cx"> name: "RuntimeManager.prototype.evaluateInInspectedWindow.ObjectLiteralConvenience",
</span><span class="cx"> 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.",
</span><del>- test: (resolve, reject) => {
</del><ins>+ test(resolve, reject) {
</ins><span class="cx"> function testSource(expression, callback) {
</span><span class="cx"> WebInspector.runtimeManager.evaluateInInspectedWindow(expression, {objectGroup: "test"}, (result, wasThrown) => {
</span><span class="cx"> InspectorTest.log("Source: " + expression);
</span><span class="lines">@@ -50,11 +71,64 @@
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+ 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();
+ });
+ }
+ });
+
</ins><span class="cx"> suite.runTestCasesAndFinish();
</span><span class="cx"> }
</span><span class="cx"> </script>
</span><span class="cx"> </head>
</span><span class="cx"> <body onload="runTest()">
</span><del>-<p>Tests for the Runtime.parse command.</p>
</del><ins>+<p>Tests for RuntimeManager operations.</p>
</ins><span class="cx"> </body>
</span><span class="cx"> </html>
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (210032 => 210033)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,5 +1,48 @@
</span><span class="cx"> 2016-12-20 Joseph Pecoraro <pecoraro@apple.com>
</span><span class="cx">
</span><ins>+ 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 <pecoraro@apple.com>
+
</ins><span class="cx"> Web Inspector: Update CodeMirror to support async/await keyword and other ES2017 features
</span><span class="cx"> https://bugs.webkit.org/show_bug.cgi?id=165677
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceControllersBreakpointLogMessageLexerjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/BreakpointLogMessageLexer.js (210032 => 210033)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -152,7 +152,7 @@
</span><span class="cx"> _possiblePlaceholder()
</span><span class="cx"> {
</span><span class="cx"> let character = this._consume();
</span><del>- console.assert(character === "$")
</del><ins>+ console.assert(character === "$");
</ins><span class="cx"> let nextCharacter = this._peek();
</span><span class="cx">
</span><span class="cx"> console.assert(this._states.lastValue === WebInspector.BreakpointLogMessageLexer.State.PossiblePlaceholder);
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceControllersRuntimeManagerjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/RuntimeManager.js (210032 => 210033)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -74,6 +74,9 @@
</span><span class="cx"> } else if (/^\s*\{/.test(expression) && /\}\s*$/.test(expression)) {
</span><span class="cx"> // Transform {a:1} to ({a:1}) so it is treated like an object literal instead of a block with a label.
</span><span class="cx"> expression = "(" + expression + ")";
</span><ins>+ } else if (/\bawait\b/.test(expression)) {
+ // Transform `await <expr>` into an async function assignment.
+ expression = this._tryApplyAwaitConvenience(expression);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> expression = sourceURLAppender(expression);
</span><span class="lines">@@ -162,6 +165,91 @@
</span><span class="cx"> if (currentContextWasDestroyed)
</span><span class="cx"> this.activeExecutionContext = WebInspector.mainTarget.executionContext;
</span><span class="cx"> }
</span><ins>+
+ _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;`;
+ }
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> WebInspector.RuntimeManager.ConsoleObjectGroup = "console";
</span></span></pre>
</div>
</div>
</body>
</html>