diff --git a/devtools/client/webreplay/components/WebReplayPlayer.js b/devtools/client/webreplay/components/WebReplayPlayer.js index 18815b721f4f..22ef2bfcee5d 100644 --- a/devtools/client/webreplay/components/WebReplayPlayer.js +++ b/devtools/client/webreplay/components/WebReplayPlayer.js @@ -132,8 +132,13 @@ class WebReplayPlayer extends Component { start: 0, end: 1, }; + + this.lastPaint = null; this.overlayWidth = 1; - this.onClickProgressBar = this.onClickProgressBar.bind(this); + + this.onProgressBarClick = this.onProgressBarClick.bind(this); + this.onProgressBarMouseOver = this.onProgressBarMouseOver.bind(this); + this.onPlayerMouseLeave = this.onPlayerMouseLeave.bind(this); } componentDidMount() { @@ -169,6 +174,10 @@ class WebReplayPlayer extends Component { return this.toolbox.threadFront; } + isCached(message) { + return this.state.cachedPoints.includes(message.executionPoint.progress); + } + isRecording() { return !this.isPaused() && this.state.recording; } @@ -198,13 +207,31 @@ class WebReplayPlayer extends Component { return (1 - ratio) * maxSize + minSize; } + getClosestMessage(point) { + return getClosestMessage(this.state.messages, point); + } + + getMousePosition(e) { + const { start, end } = this.state; + + const { left, width } = e.currentTarget.getBoundingClientRect(); + const clickLeft = e.clientX; + + const clickPosition = (clickLeft - left) / width; + return (end - start) * clickPosition + start; + } + + paint(point) { + if (this.lastPaint !== point) { + this.lastPaint = point; + this.threadFront.paint(point); + } + } + onPaused(packet) { if (packet && packet.recordingEndpoint) { const { executionPoint, recordingEndpoint } = packet; - const closestMessage = getClosestMessage( - this.state.messages, - executionPoint - ); + const closestMessage = this.getClosestMessage(executionPoint); const pausedMessage = this.state.messages.find(message => pointEquals(message.executionPoint, executionPoint) @@ -285,23 +312,6 @@ class WebReplayPlayer extends Component { return null; } - onClickProgressBar(e) { - if (!e.altKey) { - return; - } - - const { start, end } = this.state; - - const direction = e.shiftKey ? "end" : "start"; - const { left, width } = e.currentTarget.getBoundingClientRect(); - const clickLeft = e.clientX; - - const clickPosition = (clickLeft - left) / width; - const position = (end - start) * clickPosition + start; - - this.setTimelinePosition({ position, direction }); - } - setTimelinePosition({ position, direction }) { this.setState({ [direction]: position }); } @@ -327,6 +337,38 @@ class WebReplayPlayer extends Component { } } + onMessageMouseEnter(executionPoint) { + return this.paint(executionPoint); + } + + onProgressBarClick(e) { + if (!e.altKey) { + return; + } + + const direction = e.shiftKey ? "end" : "start"; + const position = this.getMousePosition(e); + this.setTimelinePosition({ position, direction }); + } + + onProgressBarMouseOver(e) { + const mousePosition = this.getMousePosition(e) * 100; + + const closestMessage = sortBy(this.state.messages, message => + Math.abs(this.getVisiblePercent(message.executionPoint) - mousePosition) + ).filter(message => this.isCached(message))[0]; + + if (!closestMessage) { + return; + } + + this.paint(closestMessage.executionPoint); + } + + onPlayerMouseLeave() { + return this.threadFront.paintCurrentPoint(); + } + seek(executionPoint) { if (!executionPoint) { return null; @@ -487,7 +529,6 @@ class WebReplayPlayer extends Component { executionPoint, pausedMessage, highlightedMessage, - cachedPoints, } = this.state; const offset = this.getVisibleOffset(message.executionPoint); @@ -512,9 +553,7 @@ class WebReplayPlayer extends Component { const isHighlighted = highlightedMessage == message.id; - const uncached = - message.executionPoint && - !cachedPoints.includes(message.executionPoint.progress); + const uncached = message.executionPoint && !this.isCached(message); const atPausedLocation = pausedMessage && sameLocation(pausedMessage, message); @@ -544,6 +583,7 @@ class WebReplayPlayer extends Component { e.stopPropagation(); this.seek(message.executionPoint); }, + onMouseEnter: () => this.onMessageMouseEnter(message.executionPoint), }); } @@ -619,6 +659,7 @@ class WebReplayPlayer extends Component { recording: recording, paused: !recording, }), + onMouseLeave: this.onPlayerMouseLeave, }, div( { className: "overlay-container " }, @@ -626,8 +667,9 @@ class WebReplayPlayer extends Component { div( { className: "progressBar", - onClick: this.onClickProgressBar, + onClick: this.onProgressBarClick, onDoubleClick: () => this.setState({ start: 0, end: 1 }), + onMouseOver: this.onProgressBarMouseOver, }, div({ className: "progress", diff --git a/devtools/server/actors/replay/control.js b/devtools/server/actors/replay/control.js index 324824d1c9f2..86594ce7d7c1 100644 --- a/devtools/server/actors/replay/control.js +++ b/devtools/server/actors/replay/control.js @@ -1946,6 +1946,13 @@ const gControl = { return gControl.sendRequest({ type: "pauseData" }); }, + paint(point) { + const data = maybeGetPauseData(point); + if (data && data.paintData) { + RecordReplayControl.hadRepaint(data.paintData); + } + }, + repaint() { if (!gPausePoint) { return; diff --git a/devtools/server/actors/replay/debugger.js b/devtools/server/actors/replay/debugger.js index 39e281484f6b..c6f1562dcdbc 100644 --- a/devtools/server/actors/replay/debugger.js +++ b/devtools/server/actors/replay/debugger.js @@ -503,7 +503,20 @@ ReplayDebugger.prototype = { } }, - // Reset the per-pause pool when the child unpauses. + replayPaint(data) { + this._control.paint(data); + }, + + replayPaintCurrentPoint() { + if (this.replayIsRecording()) { + return RecordReplayControl.restoreMainGraphics(); + } + + const point = this._control.lastPausePoint(); + return this._control.paint(point); + }, + + // Clear out all data that becomes invalid when the child unpauses. _invalidateAfterUnpause() { this._pool = new ReplayPool(this); }, diff --git a/devtools/server/actors/thread.js b/devtools/server/actors/thread.js index 199fd9ddf93d..5ee9d66dc3b7 100644 --- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -1105,6 +1105,14 @@ const ThreadActor = ActorClassWithSpec(threadSpec, { } }, + paint: function(point) { + this.dbg.replayPaint(point); + }, + + paintCurrentPoint: function() { + this.dbg.replayPaintCurrentPoint(); + }, + /** * Handle a protocol request to resume execution of the debuggee. */ diff --git a/devtools/shared/specs/thread.js b/devtools/shared/specs/thread.js index 44d6c0b002f9..b3bcdcbd64d8 100644 --- a/devtools/shared/specs/thread.js +++ b/devtools/shared/specs/thread.js @@ -149,6 +149,16 @@ const threadSpec = generateActorSpec({ }, }, + paint: { + request: { + point: Arg(0, "json"), + }, + }, + + paintCurrentPoint: { + request: {}, + }, + // For testing. debuggerRequests: { response: {