From c9142c2b799d314fe1e33b9bc19203e899e89f25 Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Sat, 21 Sep 2019 01:31:20 +0000 Subject: [PATCH] Bug 1580334 - Hovering on the timeline should update the debugger. Differential Revision: https://phabricator.services.mozilla.com/D46550 --HG-- extra : moz-landing-system : lando --- devtools/client/debugger/panel.js | 8 +++ .../debugger/src/actions/pause/index.js | 4 ++ .../debugger/src/actions/pause/moz.build | 1 + .../actions/pause/previewPausedLocation.js | 39 ++++++++++++++ .../debugger/src/actions/types/index.js | 15 +++++- .../src/components/Editor/DebugLine.js | 53 ++++++++++--------- .../components/Editor/tests/DebugLine.spec.js | 22 ++++---- .../client/debugger/src/reducers/pause.js | 18 ++++++- .../webreplay/components/WebReplayPlayer.js | 26 ++++++--- 9 files changed, 142 insertions(+), 44 deletions(-) create mode 100644 devtools/client/debugger/src/actions/pause/previewPausedLocation.js diff --git a/devtools/client/debugger/panel.js b/devtools/client/debugger/panel.js index fd81a2f37617..67b8632dbab0 100644 --- a/devtools/client/debugger/panel.js +++ b/devtools/client/debugger/panel.js @@ -168,6 +168,14 @@ DebuggerPanel.prototype = { return this._actions.selectSourceURL(cx, url, { line, column }); }, + previewPausedLocation(location) { + return this._actions.previewPausedLocation(location); + }, + + clearPreviewPausedLocation() { + return this._actions.clearPreviewPausedLocation(); + }, + async selectSource(sourceId, line, column) { const cx = this._selectors.getContext(this._getState()); const location = { sourceId, line, column }; diff --git a/devtools/client/debugger/src/actions/pause/index.js b/devtools/client/debugger/src/actions/pause/index.js index ad7bfffc7015..3c226581c3b5 100644 --- a/devtools/client/debugger/src/actions/pause/index.js +++ b/devtools/client/debugger/src/actions/pause/index.js @@ -30,3 +30,7 @@ export { toggleSkipPausing, setSkipPausing } from "./skipPausing"; export { toggleMapScopes } from "./mapScopes"; export { setExpandedScope } from "./expandScopes"; export { generateInlinePreview } from "./inlinePreview"; +export { + previewPausedLocation, + clearPreviewPausedLocation, +} from "./previewPausedLocation"; diff --git a/devtools/client/debugger/src/actions/pause/moz.build b/devtools/client/debugger/src/actions/pause/moz.build index af947f5cb186..155aa75ce372 100644 --- a/devtools/client/debugger/src/actions/pause/moz.build +++ b/devtools/client/debugger/src/actions/pause/moz.build @@ -19,6 +19,7 @@ CompiledModules( 'mapScopes.js', 'paused.js', 'pauseOnExceptions.js', + 'previewPausedLocation.js', 'resumed.js', 'selectFrame.js', 'skipPausing.js', diff --git a/devtools/client/debugger/src/actions/pause/previewPausedLocation.js b/devtools/client/debugger/src/actions/pause/previewPausedLocation.js new file mode 100644 index 000000000000..b08c44a3d152 --- /dev/null +++ b/devtools/client/debugger/src/actions/pause/previewPausedLocation.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// @flow + +import { selectLocation } from "../sources"; +import { getContext, getSourceByURL } from "../../selectors"; +import type { ThunkArgs } from "../types"; + +type Location = { + sourceUrl: string, + column: number, + line: number, +}; + +export function previewPausedLocation(location: Location) { + return ({ dispatch, getState }: ThunkArgs) => { + const cx = getContext(getState()); + const source = getSourceByURL(getState(), location.sourceUrl); + if (!source) { + return; + } + + const sourceLocation = { ...location, sourceId: source.id }; + dispatch(selectLocation(cx, sourceLocation)); + + dispatch({ + type: "PREVIEW_PAUSED_LOCATION", + location: sourceLocation, + }); + }; +} + +export function clearPreviewPausedLocation() { + return { + type: "CLEAR_PREVIEW_PAUSED_LOCATION", + }; +} diff --git a/devtools/client/debugger/src/actions/types/index.js b/devtools/client/debugger/src/actions/types/index.js index 17d86b23a1ec..2c3348058954 100644 --- a/devtools/client/debugger/src/actions/types/index.js +++ b/devtools/client/debugger/src/actions/types/index.js @@ -5,7 +5,13 @@ // @flow import typeof SourceMaps from "devtools-source-map"; -import type { ThreadList, Thread, Context, ThreadId } from "../../types"; +import type { + ThreadList, + Thread, + Context, + ThreadId, + SourceLocation, +} from "../../types"; import type { State } from "../../reducers/types"; import type { MatchedLocations } from "../../reducers/file-search"; import type { TreeNode } from "../../utils/sources-tree/types"; @@ -149,6 +155,13 @@ export type DebuggeeAction = +type: "SELECT_THREAD", +cx: Context, +thread: ThreadId, + |} + | {| + +type: "PREVIEW_PAUSED_LOCATION", + +location: SourceLocation, + |} + | {| + +type: "CLEAR_PREVIEW_PAUSED_LOCATION", |}; export type { diff --git a/devtools/client/debugger/src/components/Editor/DebugLine.js b/devtools/client/debugger/src/components/Editor/DebugLine.js index 6560c74c053b..97a1f3e14dec 100644 --- a/devtools/client/debugger/src/components/Editor/DebugLine.js +++ b/devtools/client/debugger/src/components/Editor/DebugLine.js @@ -20,12 +20,13 @@ import { getPauseReason, getSourceWithContent, getCurrentThread, + getPausePreviewLocation, } from "../../selectors"; -import type { Frame, Why, SourceWithContent } from "../../types"; +import type { SourceLocation, Why, SourceWithContent } from "../../types"; type Props = { - frame: Frame, + location: SourceLocation, why: Why, source: ?SourceWithContent, }; @@ -35,42 +36,40 @@ type TextClasses = { lineClass: string, }; -function isDocumentReady(source: ?SourceWithContent, frame) { - return ( - frame && source && source.content && hasDocument(frame.location.sourceId) - ); +function isDocumentReady(source: ?SourceWithContent, location) { + return location && source && source.content && hasDocument(location.sourceId); } export class DebugLine extends PureComponent { debugExpression: null; componentDidMount() { - const { why, frame, source } = this.props; - this.setDebugLine(why, frame, source); + const { why, location, source } = this.props; + this.setDebugLine(why, location, source); } componentWillUnmount() { - const { why, frame, source } = this.props; - this.clearDebugLine(why, frame, source); + const { why, location, source } = this.props; + this.clearDebugLine(why, location, source); } componentDidUpdate(prevProps: Props) { - const { why, frame, source } = this.props; + const { why, location, source } = this.props; startOperation(); - this.clearDebugLine(prevProps.why, prevProps.frame, prevProps.source); - this.setDebugLine(why, frame, source); + this.clearDebugLine(prevProps.why, prevProps.location, prevProps.source); + this.setDebugLine(why, location, source); endOperation(); } - setDebugLine(why: Why, frame: Frame, source: ?SourceWithContent) { - if (!isDocumentReady(source, frame)) { + setDebugLine(why: Why, location: SourceLocation, source: ?SourceWithContent) { + if (!isDocumentReady(source, location)) { return; } - const sourceId = frame.location.sourceId; + const sourceId = location.sourceId; const doc = getDocument(sourceId); - let { line, column } = toEditorPosition(frame.location); + let { line, column } = toEditorPosition(location); let { markTextClass, lineClass } = this.getTextClasses(why); doc.addLineClass(line, "line", lineClass); @@ -92,8 +91,12 @@ export class DebugLine extends PureComponent { ); } - clearDebugLine(why: Why, frame: Frame, source: ?SourceWithContent) { - if (!isDocumentReady(source, frame)) { + clearDebugLine( + why: Why, + location: SourceLocation, + source: ?SourceWithContent + ) { + if (!isDocumentReady(source, location)) { return; } @@ -101,15 +104,15 @@ export class DebugLine extends PureComponent { this.debugExpression.clear(); } - const sourceId = frame.location.sourceId; - const { line } = toEditorPosition(frame.location); + const sourceId = location.sourceId; + const { line } = toEditorPosition(location); const doc = getDocument(sourceId); const { lineClass } = this.getTextClasses(why); doc.removeLineClass(line, "line", lineClass); } getTextClasses(why: Why): TextClasses { - if (isException(why)) { + if (why && isException(why)) { return { markTextClass: "debug-expression-error", lineClass: "new-debug-line-error", @@ -126,9 +129,11 @@ export class DebugLine extends PureComponent { const mapStateToProps = state => { const frame = getVisibleSelectedFrame(state); + const previewLocation = getPausePreviewLocation(state); + const location = previewLocation || (frame && frame.location); return { - frame, - source: frame && getSourceWithContent(state, frame.location.sourceId), + location, + source: location && getSourceWithContent(state, location.sourceId), why: getPauseReason(state, getCurrentThread(state)), }; }; diff --git a/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js b/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js index ad827962fdf0..a0e53c872c11 100644 --- a/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js +++ b/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js @@ -41,13 +41,11 @@ function generateDefaults(editor, overrides) { }; } -function createFrame(line) { +function createLocation(line) { return { - location: { - sourceId: "foo", - line, - column: 2, - }, + sourceId: "foo", + line, + column: 2, }; } @@ -80,9 +78,9 @@ describe("DebugLine Component", () => { }, }); const line = 2; - const frame = createFrame(line); + const location = createLocation(line); - component.setProps({ ...props, frame }); + component.setProps({ ...props, location }); expect(doc.removeLineClass.mock.calls).toEqual([]); expect(doc.addLineClass.mock.calls).toEqual([ @@ -107,10 +105,10 @@ describe("DebugLine Component", () => { const firstLine = 2; const secondLine = 2; - component.setProps({ ...props, frame: createFrame(firstLine) }); + component.setProps({ ...props, location: createLocation(firstLine) }); component.setProps({ ...props, - frame: createFrame(secondLine), + frame: createLocation(secondLine), }); expect(doc.removeLineClass.mock.calls).toEqual([ @@ -143,9 +141,9 @@ describe("DebugLine Component", () => { it("should not set the debug line", () => { const { component, props, doc } = render({ frame: null }); const line = 2; - const frame = createFrame(line); + const location = createLocation(line); - component.setProps({ ...props, frame }); + component.setProps({ ...props, location }); expect(doc.removeLineClass).not.toHaveBeenCalled(); }); }); diff --git a/devtools/client/debugger/src/reducers/pause.js b/devtools/client/debugger/src/reducers/pause.js index 522613b4c85d..226e20ca2881 100644 --- a/devtools/client/debugger/src/reducers/pause.js +++ b/devtools/client/debugger/src/reducers/pause.js @@ -3,7 +3,7 @@ * file, You can obtain one at . */ // @flow -/* eslint complexity: ["error", 30]*/ +/* eslint complexity: ["error", 35]*/ /** * Pause reducer @@ -29,6 +29,7 @@ import type { Context, ThreadContext, Previews, + SourceLocation, } from "../types"; export type Command = @@ -96,6 +97,7 @@ export type PauseState = { mapScopes: boolean, shouldPauseOnExceptions: boolean, shouldPauseOnCaughtExceptions: boolean, + previewLocation: ?SourceLocation, }; function createPauseState(thread: ThreadId = "UnknownThread") { @@ -109,6 +111,7 @@ function createPauseState(thread: ThreadId = "UnknownThread") { isPaused: false, pauseCounter: 0, }, + previewLocation: null, threads: {}, canRewind: false, skipPausing: prefs.skipPausing, @@ -191,6 +194,7 @@ function update( state = { ...state, + previewLocation: null, threadcx: { ...state.threadcx, pauseCounter: state.threadcx.pauseCounter + 1, @@ -207,6 +211,14 @@ function update( }); } + case "PREVIEW_PAUSED_LOCATION": { + return { ...state, previewLocation: action.location }; + } + + case "CLEAR_PREVIEW_PAUSED_LOCATION": { + return { ...state, previewLocation: null }; + } + case "MAP_FRAMES": { const { selectedFrameId, frames } = action; return updateThreadState({ frames, selectedFrameId }); @@ -677,4 +689,8 @@ export function getLastExpandedScopes(state: State, thread: ThreadId) { return getThreadPauseState(state.pause, thread).lastExpandedScopes; } +export function getPausePreviewLocation(state: State) { + return state.pause.previewLocation; +} + export default update; diff --git a/devtools/client/webreplay/components/WebReplayPlayer.js b/devtools/client/webreplay/components/WebReplayPlayer.js index fb49479a0195..4814deb09547 100644 --- a/devtools/client/webreplay/components/WebReplayPlayer.js +++ b/devtools/client/webreplay/components/WebReplayPlayer.js @@ -101,6 +101,13 @@ function sameLocation(m1, m2) { ); } +function getMessageLocation(message) { + const { + frame: { source, line, column }, + } = message; + return { sourceUrl: source, line, column }; +} + /* * * The player has 4 valid states @@ -341,11 +348,6 @@ class WebReplayPlayer extends Component { } } - async clearPreviewLocation() { - const dbg = await this.toolbox.loadTool("jsdebugger"); - dbg.clearPreviewPausedLocation(); - } - unhighlightConsoleMessage() { if (this.hoveredMessage) { this.hoveredMessage.classList.remove("highlight"); @@ -374,9 +376,20 @@ class WebReplayPlayer extends Component { } onMessageMouseEnter(message) { + this.previewLocation(message); this.showMessage(message); } + async previewLocation(closestMessage) { + const dbg = await this.toolbox.loadTool("jsdebugger"); + dbg.previewPausedLocation(getMessageLocation(closestMessage)); + } + + async clearPreviewLocation() { + const dbg = await this.toolbox.loadTool("jsdebugger"); + dbg.clearPreviewPausedLocation(); + } + onProgressBarClick(e) { if (!e.altKey) { return; @@ -403,6 +416,7 @@ class WebReplayPlayer extends Component { onPlayerMouseLeave() { this.unhighlightConsoleMessage(); + this.clearPreviewLocation(); return this.threadFront.paintCurrentPoint(); } @@ -614,7 +628,7 @@ class WebReplayPlayer extends Component { e.stopPropagation(); this.seek(message.executionPoint); }, - onMouseEnter: () => this.onMessageMouseEnter(message.executionPoint), + onMouseEnter: () => this.onMessageMouseEnter(message), }); }