Bug 1573970 - Pause on debugger statements when using web replay, r=loganfsmyth.

Differential Revision: https://phabricator.services.mozilla.com/D42243

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Brian Hackett 2019-08-20 15:16:18 +00:00
Родитель 0ce65ed8c4
Коммит 3281985555
6 изменённых файлов: 127 добавлений и 26 удалений

Просмотреть файл

@ -13,14 +13,7 @@ support-files =
!/devtools/client/debugger/test/mochitest/helpers.js
!/devtools/client/debugger/test/mochitest/helpers/context.js
!/devtools/client/inspector/test/shared-head.js
examples/doc_rr_basic.html
examples/doc_rr_continuous.html
examples/doc_rr_logs.html
examples/doc_rr_error.html
examples/doc_control_flow.html
examples/doc_inspector_basic.html
examples/doc_inspector_styles.html
examples/styles.css
examples/*
[browser_dbg_rr_breakpoints-01.js]
[browser_dbg_rr_breakpoints-02.js]
@ -28,6 +21,7 @@ support-files =
[browser_dbg_rr_breakpoints-04.js]
[browser_dbg_rr_breakpoints-05.js]
[browser_dbg_rr_breakpoints-06.js]
[browser_dbg_rr_breakpoints-07.js]
[browser_dbg_rr_record.js]
[browser_dbg_rr_stepping-01.js]
[browser_dbg_rr_stepping-02.js]

Просмотреть файл

@ -0,0 +1,33 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-disable no-undef */
"use strict";
// Pausing at a debugger statement on startup confuses the debugger.
PromiseTestUtils.whitelistRejectionsGlobally(/Unknown source actor/);
// Test interaction of breakpoints with debugger statements.
add_task(async function() {
const dbg = await attachRecordingDebugger("doc_debugger_statements.html");
const { threadFront } = dbg;
await waitForPaused(dbg);
const { frames } = await threadFront.getFrames(0, 1);
ok(frames[0].where.line == 6, "Paused at first debugger statement");
const bp = await setBreakpoint(
threadFront,
"doc_debugger_statements.html",
7
);
await resumeToLine(threadFront, 7);
await resumeToLine(threadFront, 8);
await threadFront.removeBreakpoint(bp);
await rewindToLine(threadFront, 6);
await resumeToLine(threadFront, 8);
await shutdownDebugger(dbg);
});

Просмотреть файл

@ -0,0 +1,10 @@
<html lang="en" dir="ltr">
<body>
<div id="maindiv" style="padding-top:50px">Hello World!</div>
</body>
<script>
debugger;
document.getElementById("maindiv").innerHTML = "Foobar!";
debugger;
</script>
</html>

Просмотреть файл

@ -265,8 +265,11 @@ ChildProcess.prototype = {
// Get an estimate of the amount of time required for this child to reach an
// execution point.
timeToReachPoint(point) {
let startDelay = 0,
startPoint = this.lastPausePoint;
let startDelay = 0;
let startPoint = this.lastPausePoint;
if (!startPoint) {
startPoint = checkpointExecutionPoint(FirstCheckpointId);
}
if (!this.paused) {
if (this.manifest.expectedDuration) {
const elapsed = Date.now() - this.manifestSendTime;
@ -493,6 +496,9 @@ function CheckpointInfo() {
// If the checkpoint is saved and scanned, the duration of the scan.
this.scanDuration = null;
// If the checkpoint is saved, any debugger statement hits in its region.
this.debuggerStatements = [];
}
function getCheckpointInfo(id) {
@ -1169,6 +1175,9 @@ async function finishResume() {
);
}
// Always pause at debugger statements, as if they are breakpoint hits.
hits = hits.concat(getCheckpointInfo(checkpoint).debuggerStatements);
const hit = findClosestPoint(
hits,
gPausePoint,
@ -1442,6 +1451,7 @@ function handleResumeManifestResponse({
duration,
consoleMessages,
scripts,
debuggerStatements,
}) {
if (!point.position) {
addCheckpoint(point.checkpoint - 1, duration);
@ -1461,6 +1471,11 @@ function handleResumeManifestResponse({
queuePauseData(msg.executionPoint, /* trackCached */ true);
}
});
for (const point of debuggerStatements) {
const checkpoint = getSavedCheckpoint(point.checkpoint);
getCheckpointInfo(checkpoint).debuggerStatements.push(point);
}
}
// If necessary, continue executing in the main child.
@ -1486,7 +1501,11 @@ function maybeResumeRecording() {
return;
}
gMainChild.sendManifest({
contents: { kind: "resume", breakpoints: gBreakpoints },
contents: {
kind: "resume",
breakpoints: gBreakpoints,
pauseOnDebuggerStatement: true,
},
onFinished(response) {
handleResumeManifestResponse(response);
@ -1750,7 +1769,11 @@ const gControl = {
// We can only flush the recording at checkpoints, so we need to send the
// main child forward and pause/flush ASAP.
gMainChild.sendManifest({
contents: { kind: "resume", breakpoints: [] },
contents: {
kind: "resume",
breakpoints: [],
pauseOnDebuggerStatement: false,
},
onFinished(response) {
handleResumeManifestResponse(response);
},
@ -1872,6 +1895,16 @@ const gControl = {
}
}
},
isPausedAtDebuggerStatement() {
const point = gControl.pausePoint();
if (point) {
const checkpoint = getSavedCheckpoint(point.checkpoint);
const { debuggerStatements } = getCheckpointInfo(checkpoint);
return pointArrayIncludes(debuggerStatements, point);
}
return false;
},
};
///////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -285,6 +285,14 @@ ReplayDebugger.prototype = {
}
}
}
if (
this._control.isPausedAtDebuggerStatement() &&
this.onDebuggerStatement
) {
this._capturePauseData();
this.onDebuggerStatement(this.getNewestFrame());
}
}
// If no handlers entered a thread-wide pause (resetting this._direction)

Просмотреть файл

@ -655,6 +655,7 @@ const gOnPopFilters = [];
function clearPositionHandlers() {
dbg.clearAllBreakpoints();
dbg.onEnterFrame = undefined;
dbg.onDebuggerStatement = undefined;
gHasEnterFrameHandler = false;
gPendingPcHandlers.length = 0;
@ -908,13 +909,22 @@ let gManifest;
// manifest started executing.
let gManifestStartTime;
// Points of any debugger statements that need to be flushed to the middleman.
const gNewDebuggerStatements = [];
// Whether to pause on debugger statements when running forward.
let gPauseOnDebuggerStatement = false;
// Handlers that run when a manifest is first received. This must be specified
// for all manifests.
const gManifestStartHandlers = {
resume({ breakpoints }) {
resume({ breakpoints, pauseOnDebuggerStatement }) {
RecordReplayControl.resumeExecution();
gManifestStartTime = RecordReplayControl.currentExecutionTime();
breakpoints.forEach(ensurePositionHandler);
gPauseOnDebuggerStatement = pauseOnDebuggerStatement;
dbg.onDebuggerStatement = debuggerStatementHit;
},
restoreCheckpoint({ target }) {
@ -1044,18 +1054,24 @@ function currentScriptedExecutionPoint() {
});
}
function finishResume(point) {
RecordReplayControl.manifestFinished({
point,
duration: RecordReplayControl.currentExecutionTime() - gManifestStartTime,
consoleMessages: gNewConsoleMessages,
scripts: gNewScripts,
debuggerStatements: gNewDebuggerStatements,
});
gNewConsoleMessages.length = 0;
gNewScripts.length = 0;
gNewDebuggerStatements.length = 0;
}
// Handlers that run after a checkpoint is reached to see if the manifest has
// finished. This does not need to be specified for all manifests.
const gManifestFinishedAfterCheckpointHandlers = {
resume(_, point) {
RecordReplayControl.manifestFinished({
point,
duration: RecordReplayControl.currentExecutionTime() - gManifestStartTime,
consoleMessages: gNewConsoleMessages,
scripts: gNewScripts,
});
gNewConsoleMessages.length = 0;
gNewScripts.length = 0;
finishResume(point);
},
runToPoint({ endpoint }, point) {
@ -1136,11 +1152,7 @@ function AfterCheckpoint(id, restoredCheckpoint) {
const gManifestPositionHandlers = {
resume(manifest, point) {
clearPositionHandlers();
RecordReplayControl.manifestFinished({
point,
consoleMessages: gNewConsoleMessages,
scripts: gNewScripts,
});
finishResume(point);
},
runToPoint({ endpoint }, point) {
@ -1161,6 +1173,17 @@ function positionHit(position, frame) {
}
}
function debuggerStatementHit() {
assert(gManifest.kind == "resume");
const point = currentScriptedExecutionPoint();
gNewDebuggerStatements.push(point);
if (gPauseOnDebuggerStatement) {
clearPositionHandlers();
finishResume(point);
}
}
///////////////////////////////////////////////////////////////////////////////
// Handler Helpers
///////////////////////////////////////////////////////////////////////////////