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";