fune/devtools/shared/execution-point-utils.js
Victor Porof b8157dfaaf Bug 1561435 - Format remaining devtools/, a=automatic-formatting, CLOSED TREE
# ignore-this-changeset

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

--HG--
extra : source : 4722b924e08478f5337ab509718bd66906bf472f
extra : amend_source : a5baa1aab21639fdba44537e3a10b179b0073cb4
2019-07-05 11:29:32 +02:00

152 lines
5.5 KiB
JavaScript

/* 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 http://mozilla.org/MPL/2.0/. */
"use strict";
// Utilities for working with execution points and breakpoint positions, which
// are used when replaying to identify points in the execution where the
// debugger can stop or where events of interest have occurred.
//
// A breakpoint position describes where breakpoints can be installed when
// replaying, and has the following properties:
//
// kind: The kind of breakpoint, which has one of the following values:
// "Break": Break at an offset in a script.
// "OnStep": Break at an offset in a script with a given frame depth.
// "OnPop": Break when a script's frame with a given frame depth is popped.
// "EnterFrame": Break when any script is entered.
//
// script: For all kinds but "EnterFrame", the ID of the position's script.
//
// offset: For "Break" and "OnStep", the offset within the script.
//
// frameIndex: For "OnStep" and "OnPop", the index of the topmost script frame.
// Indexes start at zero for the first frame pushed, and increase with the
// depth of the frame.
//
// An execution point is a unique identifier for a point in the recording where
// the debugger can pause, and has the following properties:
//
// checkpoint: ID of the most recent checkpoint.
//
// progress: Value of the progress counter when the point is reached.
//
// position: Optional breakpoint position where the pause occurs at. This cannot
// have the "Break" kind (see below) and is missing if the execution point is
// at the checkpoint itself.
//
// The above properties must uniquely identify a single point in the recording.
// This property is ensured mainly through how the progress counter is managed.
// The position in the point must not be able to execute multiple times without
// the progress counter being incremented.
//
// The progress counter is incremented whenever a frame is pushed onto the stack
// or a loop entry point is reached. Because it increments when looping, a
// specific JS frame cannot reach the same position multiple times with the same
// progress counter. Because it increments when pushing frames, different JS
// frames with the same frame depth cannot reach the same position multiple
// times with the same progress counter. The position must specify the frame
// depth for this argument to hold, so "Break" positions are not used in
// execution points. They are used when the user-specified breakpoints are being
// installed, though, and when pausing the execution point will use the
// appropriate "OnStep" position for the frame depth.
// Return whether pointA happens before pointB in the recording.
function pointPrecedes(pointA, pointB) {
if (pointA.checkpoint != pointB.checkpoint) {
return pointA.checkpoint < pointB.checkpoint;
}
if (pointA.progress != pointB.progress) {
return pointA.progress < pointB.progress;
}
const posA = pointA.position;
const posB = pointB.position;
// Except when we're at a checkpoint, all execution points have positions.
// Because the progress counter is bumped when executing script, points with
// the same checkpoint and progress counter will either both be at that
// checkpoint, or both be at an intra-checkpoint point.
assert(!!posA == !!posB);
if (!posA || positionEquals(posA, posB)) {
return false;
}
// If an execution point doesn't have a frame index (i.e. EnterFrame) then it
// has bumped the progress counter and predates everything else that is
// associated with the same progress counter.
if ("frameIndex" in posA != "frameIndex" in posB) {
return "frameIndex" in posB;
}
// Only certain execution point kinds do not bump the progress counter.
assert(posA.kind == "OnStep" || posA.kind == "OnPop");
assert(posB.kind == "OnStep" || posB.kind == "OnPop");
// Deeper frames predate shallower frames, if the progress counter is the
// same. We bump the progress counter when pushing frames, but not when
// popping them.
assert("frameIndex" in posA && "frameIndex" in posB);
if (posA.frameIndex != posB.frameIndex) {
return posA.frameIndex > posB.frameIndex;
}
// Within a frame, OnStep points come before OnPop points.
if (posA.kind != posB.kind) {
return posA.kind == "OnStep";
}
// Earlier script locations predate later script locations.
assert("offset" in posA && "offset" in posB);
return posA.offset < posB.offset;
}
// Return whether two execution points are the same.
// eslint-disable-next-line no-unused-vars
function pointEquals(pointA, pointB) {
return !pointPrecedes(pointA, pointB) && !pointPrecedes(pointB, pointA);
}
// Return whether two breakpoint positions are the same.
function positionEquals(posA, posB) {
return (
posA.kind == posB.kind &&
posA.script == posB.script &&
posA.offset == posB.offset &&
posA.frameIndex == posB.frameIndex
);
}
// Return whether an execution point matching posB also matches posA.
// eslint-disable-next-line no-unused-vars
function positionSubsumes(posA, posB) {
if (positionEquals(posA, posB)) {
return true;
}
if (
posA.kind == "Break" &&
posB.kind == "OnStep" &&
posA.script == posB.script &&
posA.offset == posB.offset
) {
return true;
}
return false;
}
function assert(v) {
if (!v) {
dump(`Assertion failed: ${Error().stack}\n`);
throw new Error("Assertion failed!");
}
}
this.EXPORTED_SYMBOLS = [
"pointPrecedes",
"pointEquals",
"positionEquals",
"positionSubsumes",
];