diff --git a/src/actions/pause.js b/src/actions/pause.js index 50e4b15a17..22a565d863 100644 --- a/src/actions/pause.js +++ b/src/actions/pause.js @@ -44,13 +44,17 @@ export function resumed() { export function paused(pauseInfo: Pause) { return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) { let { frames, why, loadedObjects } = pauseInfo; + frames = await updateFrameLocations(frames, sourceMaps); const frame = frames[0]; + const scopes = await client.getFrameScopes(frame); + dispatch({ type: "PAUSED", pauseInfo: { why, frame, frames }, frames: frames, + scopes, selectedFrameId: frame.id, loadedObjects: loadedObjects || [] }); @@ -184,14 +188,18 @@ export function breakOnNext() { * @static */ export function selectFrame(frame: Frame) { - return ({ dispatch }: ThunkArgs) => { + return async ({ dispatch, client }: ThunkArgs) => { dispatch(evaluateExpressions(frame.id)); dispatch( selectSource(frame.location.sourceId, { line: frame.location.line }) ); + + const scopes = await client.getFrameScopes(frame); + dispatch({ type: "SELECT_FRAME", - frame + frame, + scopes }); }; } diff --git a/src/actions/tests/ast.js b/src/actions/tests/ast.js index f8f8b1d595..14cb65e6a9 100644 --- a/src/actions/tests/ast.js +++ b/src/actions/tests/ast.js @@ -16,6 +16,9 @@ const threadClient = { }) ); }, + getFrameScopes: function() { + return Promise.resolve({}); + }, evaluate: function(expression) { return new Promise((resolve, reject) => resolve({ diff --git a/src/actions/types.js b/src/actions/types.js index 9756a7124a..6ff31fb966 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -9,6 +9,7 @@ import type { GeneratedLocation, SourceText, Frame, + Scope, Why } from "debugger-html"; @@ -167,6 +168,7 @@ type PauseAction = frame: Frame, isInterrupted?: boolean }, + scopes: Scope[], frames: Frame[], selectedFrameId: string, loadedObjects: LoadedObject[] @@ -177,7 +179,7 @@ type PauseAction = shouldIgnoreCaughtExceptions: boolean } | { type: "COMMAND", value: void } - | { type: "SELECT_FRAME", frame: Frame } + | { type: "SELECT_FRAME", frame: Frame, scopes: Scope[] } | { type: "LOAD_OBJECT_PROPERTIES", objectId: string, diff --git a/src/client/firefox/commands.js b/src/client/firefox/commands.js index f886401a4c..ca78368d91 100644 --- a/src/client/firefox/commands.js +++ b/src/client/firefox/commands.js @@ -3,6 +3,7 @@ import type { BreakpointId, BreakpointResult, + Frame, FrameId, ActorId, Location, @@ -193,6 +194,14 @@ function getProperties(grip: Grip): Promise<*> { }); } +async function getFrameScopes(frame: Frame): Promise<*> { + if (frame.scope) { + return frame.scope; + } + + return threadClient.getEnvironment(frame.id); +} + function pauseOnExceptions( shouldPauseOnExceptions: boolean, shouldIgnoreCaughtExceptions: boolean @@ -260,6 +269,7 @@ const clientCommands = { navigate, reload, getProperties, + getFrameScopes, pauseOnExceptions, prettyPrint, disablePrettyPrint, diff --git a/src/client/firefox/create.js b/src/client/firefox/create.js index 567987569e..83152263d7 100644 --- a/src/client/firefox/create.js +++ b/src/client/firefox/create.js @@ -31,7 +31,7 @@ function createFrame(frame: FramePacket): Frame { }; } -function createSource(source: SourcePayload): Source { +export function createSource(source: SourcePayload): Source { return { id: source.actor, url: source.url, @@ -41,7 +41,10 @@ function createSource(source: SourcePayload): Source { }; } -function createPause(packet: PausedPacket, response: FramesResponse): any { +export function createPause( + packet: PausedPacket, + response: FramesResponse +): any { // NOTE: useful when the debugger is already paused const frame = packet.frame || response.frames[0]; @@ -50,9 +53,3 @@ function createPause(packet: PausedPacket, response: FramesResponse): any { frames: response.frames.map(createFrame) }); } - -module.exports = { - createFrame, - createSource, - createPause -}; diff --git a/src/client/firefox/types.js b/src/client/firefox/types.js index 0c36fca817..4f307b5787 100644 --- a/src/client/firefox/types.js +++ b/src/client/firefox/types.js @@ -338,6 +338,7 @@ export type ThreadClient = { interrupt: () => Promise<*>, eventListeners: () => Promise<*>, getFrames: (number, number) => FramesResponse, + getEnvironment: () => Promise<*>, addListener: (string, Function) => void, getSources: () => Promise, reconfigure: ({ observeAsmJS: boolean }) => Promise<*>, diff --git a/src/components/Editor/Tabs.js b/src/components/Editor/Tabs.js index 2f150d3e31..41fda73e17 100644 --- a/src/components/Editor/Tabs.js +++ b/src/components/Editor/Tabs.js @@ -93,7 +93,7 @@ class SourceTabs extends PureComponent { selectedSource: SourceRecord, selectSource: (string, ?Object) => any, closeTab: string => any, - closeTabs: List => any, + closeTabs: (List) => any, toggleProjectSearch: () => any, togglePrettyPrint: string => any, togglePaneCollapse: () => any, diff --git a/src/components/Editor/index.js b/src/components/Editor/index.js index d85e265264..5633725fc7 100644 --- a/src/components/Editor/index.js +++ b/src/components/Editor/index.js @@ -910,7 +910,8 @@ Editor.contextTypes = { const expressionsSel = state => state.expressions.expressions; const getExpressionSel = createSelector(expressionsSel, expressions => input => - expressions.find(exp => exp.input == input)); + expressions.find(exp => exp.input == input) +); export default connect( state => { diff --git a/src/components/SecondaryPanes/Scopes.js b/src/components/SecondaryPanes/Scopes.js index 7dea16056f..5c8fe825a1 100644 --- a/src/components/SecondaryPanes/Scopes.js +++ b/src/components/SecondaryPanes/Scopes.js @@ -3,7 +3,12 @@ import { DOM as dom, PropTypes, PureComponent, createFactory } from "react"; import { bindActionCreators } from "redux"; import { connect } from "react-redux"; import actions from "../../actions"; -import { getSelectedFrame, getLoadedObjects, getPause } from "../../selectors"; +import { + getSelectedFrame, + getLoadedObjects, + getFrameScopes, + getPause +} from "../../selectors"; import { getScopes } from "../../utils/scopes"; import _ObjectInspector from "../shared/ObjectInspector"; @@ -21,12 +26,12 @@ class Scopes extends PureComponent { }; constructor(props, ...args) { - const { pauseInfo, selectedFrame } = props; + const { pauseInfo, selectedFrame, frameScopes } = props; super(props, ...args); this.state = { - scopes: getScopes(pauseInfo, selectedFrame) + scopes: getScopes(pauseInfo, selectedFrame, frameScopes) }; } @@ -37,7 +42,11 @@ class Scopes extends PureComponent { if (pauseInfoChanged || selectedFrameChange) { this.setState({ - scopes: getScopes(nextProps.pauseInfo, nextProps.selectedFrame) + scopes: getScopes( + nextProps.pauseInfo, + nextProps.selectedFrame, + nextProps.frameScopes + ) }); } } @@ -66,16 +75,24 @@ Scopes.propTypes = { pauseInfo: PropTypes.object, loadedObjects: PropTypes.object, loadObjectProperties: PropTypes.func, - selectedFrame: PropTypes.object + selectedFrame: PropTypes.object, + frameScopes: PropTypes.object }; Scopes.displayName = "Scopes"; export default connect( - state => ({ - pauseInfo: getPause(state), - selectedFrame: getSelectedFrame(state), - loadedObjects: getLoadedObjects(state) - }), + state => { + const selectedFrame = getSelectedFrame(state); + const frameScopes = selectedFrame + ? getFrameScopes(state, selectedFrame.id) + : null; + return { + selectedFrame, + pauseInfo: getPause(state), + frameScopes: frameScopes, + loadedObjects: getLoadedObjects(state) + }; + }, dispatch => bindActionCreators(actions, dispatch) )(Scopes); diff --git a/src/reducers/pause.js b/src/reducers/pause.js index 3750500255..2377942acd 100644 --- a/src/reducers/pause.js +++ b/src/reducers/pause.js @@ -12,6 +12,7 @@ type PauseState = { pause: ?any, isWaitingOnBreak: boolean, frames: ?(any[]), + frameScopes: any, selectedFrameId: ?string, loadedObjects: Object, shouldPauseOnExceptions: boolean, @@ -24,6 +25,7 @@ export const State = (): PauseState => ({ isWaitingOnBreak: false, frames: undefined, selectedFrameId: undefined, + frameScopes: {}, loadedObjects: {}, shouldPauseOnExceptions: prefs.pauseOnExceptions, shouldIgnoreCaughtExceptions: prefs.ignoreCaughtExceptions, @@ -33,9 +35,17 @@ export const State = (): PauseState => ({ function update(state: PauseState = State(), action: Action): PauseState { switch (action.type) { case "PAUSED": { - const { selectedFrameId, frames, loadedObjects, pauseInfo } = action; + const { + selectedFrameId, + frames, + scopes, + loadedObjects, + pauseInfo + } = action; pauseInfo.isInterrupted = pauseInfo.why.type === "interrupted"; + const frameScopes = { [selectedFrameId]: scopes }; + // turn this into an object keyed by object id let objectMap = {}; loadedObjects.forEach(obj => { @@ -47,6 +57,7 @@ function update(state: PauseState = State(), action: Action): PauseState { pause: pauseInfo, selectedFrameId, frames, + frameScopes, loadedObjects: objectMap }); } @@ -75,7 +86,13 @@ function update(state: PauseState = State(), action: Action): PauseState { return Object.assign({}, state, { isWaitingOnBreak: true }); case "SELECT_FRAME": - return Object.assign({}, state, { selectedFrameId: action.frame.id }); + const { frame, scopes } = action; + const selectedFrameId = frame.id; + return { + ...state, + frameScopes: { ...state.frameScopes, [selectedFrameId]: scopes }, + selectedFrameId + }; case "LOAD_OBJECT_PROPERTIES": if (action.status === "start") { @@ -172,6 +189,10 @@ export function getFrames(state: OuterState) { return state.pause.frames; } +export function getFrameScopes(state: OuterState, frameId: string) { + return state.pause.frameScopes[frameId]; +} + const getSelectedFrameId = createSelector(getPauseState, pauseWrapper => { return pauseWrapper.selectedFrameId; }); diff --git a/src/selectors.js b/src/selectors.js index 44d9e4a3ab..670395eb10 100644 --- a/src/selectors.js +++ b/src/selectors.js @@ -40,6 +40,7 @@ module.exports = { getChromeScopes: pause.getChromeScopes, getLoadedObjects: pause.getLoadedObjects, getLoadedObject: pause.getLoadedObject, + getFrameScopes: pause.getFrameScopes, getObjectProperties: pause.getObjectProperties, getIsWaitingOnBreak: pause.getIsWaitingOnBreak, getShouldPauseOnExceptions: pause.getShouldPauseOnExceptions, diff --git a/src/utils/frame.js b/src/utils/frame.js index 9f2f392656..ac7ddb7861 100644 --- a/src/utils/frame.js +++ b/src/utils/frame.js @@ -94,7 +94,8 @@ const displayNameMap = { }, React: { // eslint-disable-next-line max-len - "ReactCompositeComponent._renderValidatedComponentWithoutOwnerOrContext/renderedElement<": "Render" + "ReactCompositeComponent._renderValidatedComponentWithoutOwnerOrContext/renderedElement<": + "Render" }, Webpack: { // eslint-disable-next-line camelcase diff --git a/src/utils/parser/tests/fixtures/var.js b/src/utils/parser/tests/fixtures/var.js index 80ff5111b9..d5307424d4 100644 --- a/src/utils/parser/tests/fixtures/var.js +++ b/src/utils/parser/tests/fixtures/var.js @@ -1,4 +1,5 @@ var foo = 1; let bar = 2; const baz = 3; -const a = 4, b = 5; +const a = 4, + b = 5; diff --git a/src/utils/scopes.js b/src/utils/scopes.js index e4f728a02e..d5c08bb5ce 100644 --- a/src/utils/scopes.js +++ b/src/utils/scopes.js @@ -3,7 +3,7 @@ import toPairs from "lodash/toPairs"; const get = require("lodash/get"); -import type { Frame, Pause } from "debugger-html"; +import type { Frame, Pause, Scope } from "debugger-html"; type ScopeData = { name: string, @@ -69,13 +69,16 @@ function getThisVariable(frame: any, path: string) { export function getScopes( pauseInfo: Pause, - selectedFrame: Frame + selectedFrame: Frame, + selectedScope: ?Scope ): ?(ScopeData[]) { if (!pauseInfo || !selectedFrame) { return null; } - let selectedScope = selectedFrame.scope; + // NOTE: it's possible that we're inspecting an old server + // that does not support getting frame scopes directly + selectedScope = selectedScope || selectedFrame.scope; if (!selectedScope) { return null; diff --git a/src/utils/tests/scopes.js b/src/utils/tests/scopes.js index dd8c4e5ae6..c5ff3bffcd 100644 --- a/src/utils/tests/scopes.js +++ b/src/utils/tests/scopes.js @@ -16,7 +16,8 @@ const errorGrip = { kind: "Error", name: "Error", message: "blah", - stack: "onclick@http://localhost:8000/examples/doc-return-values.html:1:18\n", + stack: + "onclick@http://localhost:8000/examples/doc-return-values.html:1:18\n", fileName: "http://localhost:8000/examples/doc-return-values.html", lineNumber: 1, columnNumber: 18