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

Commit e5e324a

Browse files
loganfsmythjasonLaster
authored andcommitted
Use original scope mappings to rewrite preview and watch expressions (#5684)
1 parent d2c4986 commit e5e324a

File tree

15 files changed

+541
-73
lines changed

15 files changed

+541
-73
lines changed

src/actions/expressions.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
getExpressions,
1010
getSelectedFrame,
1111
getSelectedFrameId,
12-
getSource
12+
getSource,
13+
getSelectedSource,
14+
getSelectedScopeMappings
1315
} from "../selectors";
1416
import { PROMISE } from "./utils/middleware/promise";
1517
import { isGeneratedId } from "devtools-source-map";
@@ -115,9 +117,15 @@ function evaluateExpression(expression: Expression) {
115117
const source = getSource(getState(), location.sourceId);
116118
const sourceId = source.get("id");
117119

118-
if (!isGeneratedId(sourceId)) {
120+
const selectedSource = getSelectedSource(getState());
121+
122+
if (
123+
selectedSource &&
124+
!isGeneratedId(sourceId) &&
125+
!isGeneratedId(selectedSource.get("id"))
126+
) {
119127
input = await getMappedExpression(
120-
{ sourceMaps },
128+
{ getState, sourceMaps },
121129
generatedLocation,
122130
input
123131
);
@@ -139,9 +147,14 @@ function evaluateExpression(expression: Expression) {
139147
* and replaces all posible generated names.
140148
*/
141149
export async function getMappedExpression(
142-
{ sourceMaps }: Object,
150+
{ getState, sourceMaps }: Object,
143151
generatedLocation: Location,
144152
expression: string
145153
): Promise<string> {
146-
return expression;
154+
const mappings = getSelectedScopeMappings(getState());
155+
if (!mappings) {
156+
return expression;
157+
}
158+
159+
return await parser.mapOriginalExpression(expression, mappings);
147160
}

src/actions/pause/fetchScopes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ export function fetchScopes() {
2323
[PROMISE]: client.getFrameScopes(frame)
2424
});
2525

26-
dispatch(mapScopes(scopes, frame));
26+
await dispatch(mapScopes(scopes, frame));
2727
};
2828
}

src/actions/pause/mapScopes.js

Lines changed: 78 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function mapScopes(scopes: Promise<Scope>, frame: Frame) {
5555
!sourceRecord.get("isPrettyPrinted") &&
5656
!isGeneratedId(frame.location.sourceId);
5757

58-
dispatch({
58+
await dispatch({
5959
type: "MAP_SCOPES",
6060
frame,
6161
[PROMISE]: (async function() {
@@ -88,7 +88,12 @@ async function buildMappedScopes(
8888
scopes: Scope,
8989
sourceMaps: any,
9090
client: any
91-
): Promise<?OriginalScope> {
91+
): Promise<?{
92+
mappings: {
93+
[string]: string
94+
},
95+
scope: OriginalScope
96+
}> {
9297
const originalAstScopes = await getScopes(frame.location);
9398
const generatedAstScopes = await getScopes(frame.generatedLocation);
9499

@@ -102,42 +107,52 @@ async function buildMappedScopes(
102107
frame.this
103108
);
104109

105-
const mappedOriginalScopes = await Promise.all(
106-
Array.from(originalAstScopes, async item => {
107-
const generatedBindings = {};
110+
const expressionLookup = {};
111+
const mappedOriginalScopes = [];
108112

109-
await Promise.all(
110-
Object.keys(item.bindings).map(async name => {
111-
const binding = item.bindings[name];
113+
for (const item of originalAstScopes) {
114+
const generatedBindings = {};
112115

113-
const result = await findGeneratedBinding(
114-
sourceMaps,
115-
client,
116-
source,
117-
name,
118-
binding,
119-
generatedAstBindings
120-
);
116+
for (const name of Object.keys(item.bindings)) {
117+
const binding = item.bindings[name];
121118

122-
if (result) {
123-
generatedBindings[name] = result;
124-
}
125-
})
119+
const result = await findGeneratedBinding(
120+
sourceMaps,
121+
client,
122+
source,
123+
name,
124+
binding,
125+
generatedAstBindings
126126
);
127127

128-
return {
129-
...item,
130-
generatedBindings
131-
};
132-
})
133-
);
128+
if (result) {
129+
generatedBindings[name] = result.grip;
130+
131+
if (
132+
binding.refs.length !== 0 &&
133+
// These are assigned depth-first, so we don't want shadowed
134+
// bindings in parent scopes overwriting the expression.
135+
!Object.prototype.hasOwnProperty.call(expressionLookup, name)
136+
) {
137+
expressionLookup[name] = result.expression;
138+
}
139+
}
140+
}
141+
142+
mappedOriginalScopes.push({
143+
...item,
144+
generatedBindings
145+
});
146+
}
134147

135148
const mappedGeneratedScopes = generateClientScope(
136149
scopes,
137150
mappedOriginalScopes
138151
);
139152

140-
return isReliableScope(mappedGeneratedScopes) ? mappedGeneratedScopes : null;
153+
return isReliableScope(mappedGeneratedScopes)
154+
? { mappings: expressionLookup, scope: mappedGeneratedScopes }
155+
: null;
141156
}
142157

143158
/**
@@ -244,7 +259,10 @@ async function findGeneratedBinding(
244259
name: string,
245260
originalBinding: BindingData,
246261
generatedAstBindings: Array<GeneratedBindingLocation>
247-
): Promise<?BindingContents> {
262+
): Promise<?{
263+
grip: BindingContents,
264+
expression: string | null
265+
}> {
248266
// If there are no references to the implicits, then we have no way to
249267
// even attempt to map it back to the original since there is no location
250268
// data to use. Bail out instead of just showing it as unmapped.
@@ -275,7 +293,10 @@ async function findGeneratedBinding(
275293
}, null);
276294

277295
if (genContent && genContent.desc) {
278-
return genContent.desc;
296+
return {
297+
grip: genContent.desc,
298+
expression: genContent.expression
299+
};
279300
} else if (genContent) {
280301
// If there is no descriptor for 'this', then this is not the top-level
281302
// 'this' that the server gave us a binding for, and we can just ignore it.
@@ -287,17 +308,20 @@ async function findGeneratedBinding(
287308
// means that the server scope information didn't match the scope
288309
// information from the DevTools parsed scopes.
289310
return {
290-
configurable: false,
291-
enumerable: true,
292-
writable: false,
293-
value: {
294-
type: "unscoped",
295-
unscoped: true,
296-
297-
// HACK: Until support for "unscoped" lands in devtools-reps,
298-
// this will make these show as (unavailable).
299-
missingArguments: true
300-
}
311+
grip: {
312+
configurable: false,
313+
enumerable: true,
314+
writable: false,
315+
value: {
316+
type: "unscoped",
317+
unscoped: true,
318+
319+
// HACK: Until support for "unscoped" lands in devtools-reps,
320+
// this will make these show as (unavailable).
321+
missingArguments: true
322+
}
323+
},
324+
expression: null
301325
};
302326
}
303327

@@ -306,17 +330,20 @@ async function findGeneratedBinding(
306330
// of some scope, but the generated location is outside, leading
307331
// us to search for bindings that don't technically exist.
308332
return {
309-
configurable: false,
310-
enumerable: true,
311-
writable: false,
312-
value: {
313-
type: "unmapped",
314-
unmapped: true,
315-
316-
// HACK: Until support for "unmapped" lands in devtools-reps,
317-
// this will make these show as (unavailable).
318-
missingArguments: true
319-
}
333+
grip: {
334+
configurable: false,
335+
enumerable: true,
336+
writable: false,
337+
value: {
338+
type: "unmapped",
339+
unmapped: true,
340+
341+
// HACK: Until support for "unmapped" lands in devtools-reps,
342+
// this will make these show as (unavailable).
343+
missingArguments: true
344+
}
345+
},
346+
expression: null
320347
};
321348
}
322349

src/actions/pause/paused.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ export function paused(pauseInfo: Pause) {
7070
dispatch(removeBreakpoint(hiddenBreakpointLocation));
7171
}
7272

73-
if (!isEvaluatingExpression(getState())) {
74-
dispatch(evaluateExpressions());
75-
}
76-
7773
await dispatch(mapFrames());
7874
const selectedFrame = getSelectedFrame(getState());
7975

@@ -86,6 +82,12 @@ export function paused(pauseInfo: Pause) {
8682
}
8783

8884
dispatch(togglePaneCollapse("end", false));
89-
dispatch(fetchScopes());
85+
await dispatch(fetchScopes());
86+
87+
// Run after fetching scoping data so that it may make use of the sourcemap
88+
// expression mappings for local variables.
89+
if (!isEvaluatingExpression(getState())) {
90+
dispatch(evaluateExpressions());
91+
}
9092
};
9193
}

src/actions/preview.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export function setPreview(
158158
);
159159

160160
expression = await getMappedExpression(
161-
{ sourceMaps },
161+
{ sourceMaps, getState },
162162
generatedLocation,
163163
expression
164164
);

src/actions/types.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,12 @@ type PauseAction =
300300
type: "MAP_SCOPES",
301301
frame: Frame,
302302
status: AsyncStatus,
303-
value: Scope
303+
value: {
304+
scope: Scope,
305+
mappings: {
306+
[string]: string | null
307+
}
308+
}
304309
}
305310
| {
306311
type: "MAP_FRAMES",

src/reducers/pause.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export type PauseState = {
3535
pending: boolean,
3636
scope: OriginalScope
3737
}
38+
},
39+
mappings: {
40+
[FrameId]: {
41+
[string]: string | null
42+
}
3843
}
3944
},
4045
selectedFrameId: ?string,
@@ -57,7 +62,8 @@ export const createPauseState = (): PauseState => ({
5762
selectedFrameId: undefined,
5863
frameScopes: {
5964
generated: {},
60-
original: {}
65+
original: {},
66+
mappings: {}
6167
},
6268
loadedObjects: {},
6369
shouldPauseOnExceptions: prefs.pauseOnExceptions,
@@ -73,7 +79,8 @@ const emptyPauseState = {
7379
frames: null,
7480
frameScopes: {
7581
generated: {},
76-
original: {}
82+
original: {},
83+
mappings: {}
7784
},
7885
selectedFrameId: null,
7986
loadedObjects: {},
@@ -143,14 +150,20 @@ function update(
143150
...state.frameScopes.original,
144151
[selectedFrameId]: {
145152
pending: status !== "done",
146-
scope: value
153+
scope: value && value.scope
147154
}
148155
};
156+
157+
const mappings = {
158+
...state.frameScopes.mappings,
159+
[selectedFrameId]: value && value.mappings
160+
};
149161
return {
150162
...state,
151163
frameScopes: {
152164
...state.frameScopes,
153-
original
165+
original,
166+
mappings
154167
}
155168
};
156169
}
@@ -347,6 +360,19 @@ export function getSelectedScope(state: OuterState) {
347360
return scope || null;
348361
}
349362

363+
export function getSelectedScopeMappings(
364+
state: OuterState
365+
): {
366+
[string]: string | null
367+
} | null {
368+
const frameId = getSelectedFrameId(state);
369+
if (!frameId) {
370+
return null;
371+
}
372+
373+
return getFrameScopes(state).mappings[frameId];
374+
}
375+
350376
export function getSelectedFrameId(state: OuterState) {
351377
return state.pause.selectedFrameId;
352378
}

src/test/mochitest/browser.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ support-files =
128128
[browser_dbg-async-stepping.js]
129129
[browser_dbg-babel-scopes.js]
130130
[browser_dbg-babel-stepping.js]
131+
[browser_dbg-babel-preview.js]
131132
[browser_dbg-breaking.js]
132133
[browser_dbg-breaking-from-console.js]
133134
[browser_dbg-breakpoints.js]

0 commit comments

Comments
 (0)