diff --git a/devtools/client/debugger/bin/module-manifest.json b/devtools/client/debugger/bin/module-manifest.json index 0d21f2b264c5..9b83e9ec986b 100644 --- a/devtools/client/debugger/bin/module-manifest.json +++ b/devtools/client/debugger/bin/module-manifest.json @@ -2445,7 +2445,7 @@ "byName": {}, "byBlocks": {}, "usedIds": { - "1": 1 + "0": 0 } } } @@ -2466,7 +2466,7 @@ "byName": {}, "byBlocks": {}, "usedIds": { - "1": 1 + "0": 0 } } } diff --git a/devtools/client/debugger/dist/parser-worker.js b/devtools/client/debugger/dist/parser-worker.js index cef78d0104dd..d6fc98240af6 100644 --- a/devtools/client/debugger/dist/parser-worker.js +++ b/devtools/client/debugger/dist/parser-worker.js @@ -47826,8 +47826,16 @@ function translateDeclarationIntoAssignment(node) { function addReturnNode(ast) { const statements = ast.program.body; - const lastStatement = statements[statements.length - 1]; - return statements.slice(0, -1).concat(t.returnStatement(lastStatement.expression)); + const lastStatement = statements.pop(); // if the last expression is an awaitExpression, strip the `await` part and directly + // return the argument to avoid calling the argument's `then` function twice when the + // mapped expression gets evaluated (See Bug 1771428) + + if (t.isAwaitExpression(lastStatement.expression)) { + lastStatement.expression = lastStatement.expression.argument; + } + + statements.push(t.returnStatement(lastStatement.expression)); + return statements; } function getDeclarations(node) { diff --git a/devtools/client/debugger/src/workers/parser/mapAwaitExpression.js b/devtools/client/debugger/src/workers/parser/mapAwaitExpression.js index 8f750cdb1f8e..fca97c8a32f0 100644 --- a/devtools/client/debugger/src/workers/parser/mapAwaitExpression.js +++ b/devtools/client/debugger/src/workers/parser/mapAwaitExpression.js @@ -39,10 +39,16 @@ function translateDeclarationIntoAssignment(node) { */ function addReturnNode(ast) { const statements = ast.program.body; - const lastStatement = statements[statements.length - 1]; - return statements - .slice(0, -1) - .concat(t.returnStatement(lastStatement.expression)); + const lastStatement = statements.pop(); + + // if the last expression is an awaitExpression, strip the `await` part and directly + // return the argument to avoid calling the argument's `then` function twice when the + // mapped expression gets evaluated (See Bug 1771428) + if (t.isAwaitExpression(lastStatement.expression)) { + lastStatement.expression = lastStatement.expression.argument; + } + statements.push(t.returnStatement(lastStatement.expression)); + return statements; } function getDeclarations(node) { diff --git a/devtools/client/debugger/src/workers/parser/tests/mapExpression.spec.js b/devtools/client/debugger/src/workers/parser/tests/mapExpression.spec.js index bbb845d2ecfa..839b366469a0 100644 --- a/devtools/client/debugger/src/workers/parser/tests/mapExpression.spec.js +++ b/devtools/client/debugger/src/workers/parser/tests/mapExpression.spec.js @@ -39,7 +39,7 @@ describe("mapExpression", () => { { name: "await", expression: "await a()", - newExpression: formatAwait("return await a()"), + newExpression: formatAwait("return a()"), bindings: [], mappings: {}, shouldMapBindings: true, @@ -78,7 +78,7 @@ describe("mapExpression", () => { { name: "await (multiple awaits)", expression: "const x = await a(); await b(x)", - newExpression: formatAwait("self.x = await a(); return await b(x);"), + newExpression: formatAwait("self.x = await a(); return b(x);"), bindings: [], mappings: {}, shouldMapBindings: true, diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_await.js b/devtools/client/webconsole/test/browser/browser_jsterm_await.js index eb55a7994b2d..c90fe6de7d13 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_await.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_await.js @@ -64,4 +64,21 @@ add_task(async function() { `Promise {` ); ok(message, "Promise are displayed as expected"); + + info("Check that then getters aren't called twice"); + message = await executeAndWaitForResultMessage( + hud, + // It's important to keep the last statement of the expression as it covers the original issue. + // We could execute another expression to get `object.called`, but since we get a preview + // of the object with an accurate `called` value, this is enough. + ` + var obj = { + called: 0, + get then(){ + this.called++ + } + }; + await obj`, + `Object { called: 1, then: Getter }` + ); });