Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

Commit e98a98c

Browse files
loganfsmythjasonLaster
authored andcommitted
Use original source location to automatically stepOver locations with useless mappings. (#5485)
1 parent 06de18b commit e98a98c

File tree

17 files changed

+418
-14
lines changed

17 files changed

+418
-14
lines changed

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
src/workers/parser/tests/fixtures/functionNames.js
22
src/workers/parser/tests/fixtures/scopes/*.js
3+
src/workers/parser/tests/fixtures/pause/*.js
34
src/test/mochitest/examples/babel/polyfill-bundle.js
45
src/test/mochitest/examples/babel/fixtures/*/input.js
56
src/test/mochitest/examples/babel/fixtures/*/output.js

src/actions/pause/mapFrames.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getFrames } from "../../selectors";
55
import type { Frame } from "../../types";
66
import type { ThunkArgs } from "../types";
77

8-
function updateFrameLocation(frame: Frame, sourceMaps: any) {
8+
export function updateFrameLocation(frame: Frame, sourceMaps: any) {
99
return sourceMaps.getOriginalLocation(frame.location).then(loc => ({
1010
...frame,
1111
location: loc,

src/actions/pause/paused.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,28 @@ import {
1010
getHiddenBreakpointLocation,
1111
isEvaluatingExpression,
1212
getSelectedFrame,
13-
getVisibleSelectedFrame
13+
getVisibleSelectedFrame,
14+
getSources
1415
} from "../../selectors";
16+
1517
import { mapFrames } from ".";
1618
import { removeBreakpoint } from "../breakpoints";
1719
import { evaluateExpressions } from "../expressions";
18-
import { selectLocation } from "../sources";
20+
import { selectLocation, loadSourceText } from "../sources";
1921
import { togglePaneCollapse } from "../ui";
22+
import { command } from "./commands";
23+
import { shouldStep } from "../../utils/pause";
24+
25+
import { updateFrameLocation } from "./mapFrames";
2026

2127
import { fetchScopes } from "./fetchScopes";
2228

23-
import type { Pause } from "../../types";
29+
import type { Pause, Frame } from "../../types";
2430
import type { ThunkArgs } from "../types";
2531

32+
async function getOriginalSourceForFrame(state, frame: Frame) {
33+
return getSources(state).get(frame.location.sourceId);
34+
}
2635
/**
2736
* Debugger has just paused
2837
*
@@ -33,12 +42,26 @@ import type { ThunkArgs } from "../types";
3342
export function paused(pauseInfo: Pause) {
3443
return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
3544
const { frames, why, loadedObjects } = pauseInfo;
45+
const rootFrame = frames.length > 0 ? frames[0] : null;
46+
47+
if (rootFrame) {
48+
const mappedFrame = await updateFrameLocation(rootFrame, sourceMaps);
49+
const source = await getOriginalSourceForFrame(getState(), mappedFrame);
50+
51+
// Ensure that the original file has loaded if there is one.
52+
await dispatch(loadSourceText(source));
53+
54+
if (await shouldStep(mappedFrame, getState(), sourceMaps)) {
55+
dispatch(command("stepOver"));
56+
return;
57+
}
58+
}
3659

3760
dispatch({
3861
type: "PAUSED",
3962
why,
4063
frames,
41-
selectedFrameId: frames[0] ? frames[0].id : undefined,
64+
selectedFrameId: rootFrame ? rootFrame.id : undefined,
4265
loadedObjects: loadedObjects || []
4366
});
4467

src/reducers/pause.js

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { getSelectedSource } from "./sources";
1717

1818
import type { OriginalScope } from "../actions/pause/mapScopes";
1919
import type { Action } from "../actions/types";
20-
import type { Why, Scope, SourceId, FrameId } from "../types";
20+
import type { Why, Scope, SourceId, FrameId, Location } from "../types";
2121

2222
export type PauseState = {
2323
why: ?Why,
@@ -43,7 +43,11 @@ export type PauseState = {
4343
shouldIgnoreCaughtExceptions: boolean,
4444
canRewind: boolean,
4545
debuggeeUrl: string,
46-
command: string
46+
command: string,
47+
previousLocation: ?{
48+
location: Location,
49+
generatedLocation: Location
50+
}
4751
};
4852

4953
export const createPauseState = (): PauseState => ({
@@ -60,7 +64,8 @@ export const createPauseState = (): PauseState => ({
6064
shouldIgnoreCaughtExceptions: prefs.ignoreCaughtExceptions,
6165
canRewind: false,
6266
debuggeeUrl: "",
63-
command: ""
67+
command: "",
68+
previousLocation: null
6469
});
6570

6671
const emptyPauseState = {
@@ -71,7 +76,8 @@ const emptyPauseState = {
7176
original: {}
7277
},
7378
selectedFrameId: null,
74-
loadedObjects: {}
79+
loadedObjects: {},
80+
previousLocation: null
7581
};
7682

7783
function update(
@@ -190,10 +196,16 @@ function update(
190196
shouldIgnoreCaughtExceptions
191197
};
192198

193-
case "COMMAND":
199+
case "COMMAND": {
194200
return action.status === "start"
195-
? { ...state, ...emptyPauseState, command: action.command }
201+
? {
202+
...state,
203+
...emptyPauseState,
204+
command: action.command,
205+
previousLocation: buildPreviousLocation(state, action)
206+
}
196207
: { ...state, command: "" };
208+
}
197209

198210
case "RESUME":
199211
// We clear why on resume because we need it to decide if
@@ -213,6 +225,24 @@ function update(
213225
return state;
214226
}
215227

228+
function buildPreviousLocation(state, action) {
229+
const { frames, previousLocation } = state;
230+
231+
if (action.command !== "stepOver") {
232+
return null;
233+
}
234+
235+
const frame = frames && frames.length > 0 ? frames[0] : null;
236+
if (!frame) {
237+
return previousLocation;
238+
}
239+
240+
return {
241+
location: frame.location,
242+
generatedLocation: frame.generatedLocation
243+
};
244+
}
245+
216246
// Selectors
217247

218248
// Unfortunately, it's really hard to make these functions accept just
@@ -243,6 +273,10 @@ export function isPaused(state: OuterState) {
243273
return !!getFrames(state);
244274
}
245275

276+
export function getPreviousPauseFrameLocation(state: OuterState) {
277+
return state.pause.previousLocation;
278+
}
279+
246280
export function isEvaluatingExpression(state: OuterState) {
247281
return state.pause.command === "expression";
248282
}

src/test/mochitest/browser.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,9 @@ support-files =
117117

118118
[browser_dbg-asm.js]
119119
[browser_dbg-async-stepping.js]
120-
[browser_dbg-babel.js]
120+
[browser_dbg-babel-scopes.js]
121121
skip-if = (os == "win" && ccov) # bug 1438797
122+
[browser_dbg-babel-stepping.js]
122123
[browser_dbg-breaking.js]
123124
[browser_dbg-breaking-from-console.js]
124125
[browser_dbg-breakpoints.js]
File renamed without changes.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* Any copyright is dedicated to the Public Domain.
2+
* http://creativecommons.org/publicdomain/zero/1.0/ */
3+
4+
// Tests for stepping through Babel's compile output.
5+
6+
async function breakpointSteps(dbg, fixture, { line, column }, steps) {
7+
const { selectors: { getBreakpoint, getBreakpoints }, getState } = dbg;
8+
9+
const filename = `fixtures/${fixture}/input.js`;
10+
await waitForSources(dbg, filename);
11+
12+
ok(true, "Original sources exist");
13+
const source = findSource(dbg, filename);
14+
15+
await selectSource(dbg, source);
16+
17+
// Test that breakpoint is not off by a line.
18+
await addBreakpoint(dbg, source, line);
19+
20+
is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
21+
ok(
22+
getBreakpoint(getState(), { sourceId: source.id, line, column }),
23+
"Breakpoint has correct line"
24+
);
25+
26+
const fnName = fixture.replace(/-([a-z])/g, (s, c) => c.toUpperCase());
27+
28+
const invokeResult = invokeInTab(fnName);
29+
30+
let invokeFailed = await Promise.race([
31+
waitForPaused(dbg),
32+
invokeResult.then(() => new Promise(() => {}), () => true)
33+
]);
34+
35+
if (invokeFailed) {
36+
return invokeResult;
37+
}
38+
39+
assertPausedLocation(dbg);
40+
41+
await removeBreakpoint(dbg, source.id, line, column);
42+
43+
is(getBreakpoints(getState()).size, 0, "Breakpoint reverted");
44+
45+
await runSteps(dbg, source, steps);
46+
47+
await resume(dbg);
48+
49+
// If the invoke errored later somehow, capture here so the error is
50+
// reported nicely.
51+
await invokeResult;
52+
53+
ok(true, `Ran tests for ${fixture} at line ${line} column ${column}`);
54+
}
55+
56+
async function runSteps(dbg, source, steps) {
57+
const { selectors: { getVisibleSelectedFrame }, getState } = dbg;
58+
59+
for (const [i, [type, position]] of steps.entries()) {
60+
switch (type) {
61+
case "stepOver":
62+
await stepOver(dbg);
63+
break;
64+
case "stepIn":
65+
await stepIn(dbg);
66+
break;
67+
default:
68+
throw new Error("Unknown stepping type");
69+
}
70+
71+
const { location } = getVisibleSelectedFrame(getState());
72+
73+
is(location.sourceId, source.id, `Step ${i} has correct sourceId`);
74+
is(location.line, position.line, `Step ${i} has correct line`);
75+
is(location.column, position.column, `Step ${i} has correct column`);
76+
77+
assertPausedLocation(dbg);
78+
}
79+
}
80+
81+
add_task(async function() {
82+
requestLongerTimeout(2);
83+
84+
const dbg = await initDebugger("doc-babel.html");
85+
86+
await breakpointSteps(dbg, "step-over-for-of", { line: 4, column: 2 }, [
87+
["stepOver", { line: 6, column: 2 }],
88+
["stepOver", { line: 7, column: 4 }],
89+
["stepOver", { line: 6, column: 2 }],
90+
["stepOver", { line: 7, column: 4 }],
91+
["stepOver", { line: 6, column: 2 }],
92+
["stepOver", { line: 10, column: 2 }]
93+
]);
94+
95+
// This codifies the current behavior, but stepping twice over the for
96+
// header isn't ideal.
97+
await breakpointSteps(dbg, "step-over-for-of-array", { line: 3, column: 2 }, [
98+
["stepOver", { line: 5, column: 2 }],
99+
["stepOver", { line: 5, column: 7 }],
100+
["stepOver", { line: 6, column: 4 }],
101+
["stepOver", { line: 5, column: 2 }],
102+
["stepOver", { line: 5, column: 7 }],
103+
["stepOver", { line: 6, column: 4 }],
104+
["stepOver", { line: 5, column: 2 }],
105+
["stepOver", { line: 9, column: 2 }]
106+
]);
107+
108+
// The closure means it isn't actually possible to step into the for body,
109+
// and Babel doesn't map the _loop() call, so we step past it automatically.
110+
await breakpointSteps(
111+
dbg,
112+
"step-over-for-of-closure",
113+
{ line: 6, column: 2 },
114+
[
115+
["stepOver", { line: 8, column: 2 }],
116+
["stepOver", { line: 12, column: 2 }]
117+
]
118+
);
119+
120+
// Same as the previous, not possible to step into the body. The less
121+
// complicated array logic makes it possible to step into the header at least,
122+
// but this does end up double-visiting the for head.
123+
await breakpointSteps(
124+
dbg,
125+
"step-over-for-of-array-closure",
126+
{ line: 3, column: 2 },
127+
[
128+
["stepOver", { line: 5, column: 2 }],
129+
["stepOver", { line: 5, column: 7 }],
130+
["stepOver", { line: 5, column: 2 }],
131+
["stepOver", { line: 5, column: 7 }],
132+
["stepOver", { line: 5, column: 2 }],
133+
["stepOver", { line: 9, column: 2 }]
134+
]
135+
);
136+
137+
await breakpointSteps(
138+
dbg,
139+
"step-over-function-params",
140+
{ line: 6, column: 2 },
141+
[["stepOver", { line: 7, column: 2 }], ["stepIn", { line: 2, column: 2 }]]
142+
);
143+
144+
await breakpointSteps(
145+
dbg,
146+
"step-over-regenerator-await",
147+
{ line: 2, column: 2 },
148+
[
149+
// Won't work until a fix to regenerator lands and we rebuild.
150+
// https://github.com/facebook/regenerator/issues/342
151+
// ["stepOver", { line: 4, column: 2 }],
152+
]
153+
);
154+
});

src/test/mochitest/browser_dbg-breaking-from-console.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ add_task(async function() {
2626
const { selectors: { getSelectedSource }, getState } = dbg;
2727

2828
// Make sure the thread is paused in the right source and location
29-
await waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
29+
await waitForPaused(dbg);
3030
is(dbg.win.cm.getValue(), "debugger");
3131
const source = getSelectedSource(getState()).toJS();
3232
assertPausedLocation(dbg);

src/utils/pause/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
4+
5+
// @flow
6+
7+
export * from "./why";
8+
export * from "./stepping";

0 commit comments

Comments
 (0)