Bug 1828647 - [devtools] Stop automatically unblackboxing a source when a breakpoint is added to a blackboxed source r=devtools-reviewers,nchevobbe

There is a bit of complexity around blackboxing, let's avoid side effects based of blackboxing.
#### Highlights of this patch
- This removes auto unblackboxing a source when a breakpoint is added. This should also fix Bug 1828585.
  This will benefit the work around blackboxing sources on the x_google_ignoreList.
- Breakpoints created on ignored lines or in an ignored source are disabled by default
- Breakpoints are disabled once the line it is on gets ignored.
- Disabled breakpoints on ignored lines cannot be enabled until the line is unignored.
- Disabled breakpoints are enabled once the line is unignored.

Differential Revision: https://phabricator.services.mozilla.com/D177459
This commit is contained in:
Hubert Boma Manilla 2023-05-19 15:38:36 +00:00
parent 5c2d7ec950
commit 116486a521
26 changed files with 550 additions and 183 deletions

View file

@ -15,6 +15,7 @@ import {
getBreakpointsList,
getPendingBreakpointList,
isMapScopesEnabled,
getBlackBoxRanges,
} from "../../selectors";
import { setBreakpointPositions } from "./breakpointPositions";
@ -23,7 +24,7 @@ import { setSkipPausing } from "../pause/skipPausing";
import { PROMISE } from "../utils/middleware/promise";
import { recordEvent } from "../../utils/telemetry";
import { comparePosition } from "../../utils/location";
import { getTextAtPosition } from "../../utils/source";
import { getTextAtPosition, isLineBlackboxed } from "../../utils/source";
import { getMappedScopesForLocation } from "../pause/mapScopes";
import { validateNavigateContext } from "../../utils/context";
@ -81,7 +82,15 @@ export function enableBreakpoint(cx, initialBreakpoint) {
return thunkArgs => {
const { dispatch, getState, client } = thunkArgs;
const breakpoint = getBreakpoint(getState(), initialBreakpoint.location);
if (!breakpoint || !breakpoint.disabled) {
const blackboxedRanges = getBlackBoxRanges(getState());
if (
!breakpoint ||
!breakpoint.disabled ||
isLineBlackboxed(
blackboxedRanges[breakpoint.location.source.url],
breakpoint.location.line
)
) {
return null;
}

View file

@ -12,10 +12,12 @@ import {
originalToGeneratedId,
} from "devtools/client/shared/source-map-loader/index";
import { recordEvent } from "../../utils/telemetry";
import { toggleBreakpoints } from "../breakpoints";
import {
getSourceActorsForSource,
isSourceBlackBoxed,
getBlackBoxRanges,
getBreakpointsForSource,
} from "../../selectors";
async function _blackboxSourceActorsForSource(
@ -93,6 +95,12 @@ export function toggleBlackBox(cx, source, shouldBlackBox, ranges = []) {
// source. To do that lets reset the content to an empty array.
if (!ranges.length) {
dispatch({ type: "BLACKBOX_WHOLE_SOURCES", sources: [source] });
await toggleBreakpointsInBlackboxedSources({
thunkArgs,
cx,
shouldDisable: true,
sources: [source],
});
} else {
const currentRanges = getBlackBoxRanges(getState())[source.url] || [];
ranges = ranges.filter(newRange => {
@ -106,6 +114,13 @@ export function toggleBlackBox(cx, source, shouldBlackBox, ranges = []) {
return duplicate == -1;
});
dispatch({ type: "BLACKBOX_SOURCE_RANGES", source, ranges });
await toggleBreakpointsInRangesForBlackboxedSource({
thunkArgs,
cx,
shouldDisable: true,
source,
ranges,
});
}
} else {
// if there are no ranges to blackbox, then we are unblackboxing
@ -113,16 +128,57 @@ export function toggleBlackBox(cx, source, shouldBlackBox, ranges = []) {
// eslint-disable-next-line no-lonely-if
if (!ranges.length) {
dispatch({ type: "UNBLACKBOX_WHOLE_SOURCES", sources: [source] });
toggleBreakpointsInBlackboxedSources({
thunkArgs,
cx,
shouldDisable: false,
sources: [source],
});
} else {
dispatch({ type: "UNBLACKBOX_SOURCE_RANGES", source, ranges });
const blackboxRanges = getBlackBoxRanges(getState());
if (!blackboxRanges[source.url].length) {
dispatch({ type: "UNBLACKBOX_WHOLE_SOURCES", sources: [source] });
}
await toggleBreakpointsInRangesForBlackboxedSource({
thunkArgs,
cx,
shouldDisable: false,
source,
ranges,
});
}
}
};
}
async function toggleBreakpointsInRangesForBlackboxedSource({
thunkArgs,
cx,
shouldDisable,
source,
ranges,
}) {
const { dispatch, getState } = thunkArgs;
for (const range of ranges) {
const breakpoints = getBreakpointsForSource(getState(), source.id, range);
await dispatch(toggleBreakpoints(cx, shouldDisable, breakpoints));
}
}
async function toggleBreakpointsInBlackboxedSources({
thunkArgs,
cx,
shouldDisable,
sources,
}) {
const { dispatch, getState } = thunkArgs;
for (const source of sources) {
const breakpoints = getBreakpointsForSource(getState(), source.id);
await dispatch(toggleBreakpoints(cx, shouldDisable, breakpoints));
}
}
/*
* Blackboxes a group of sources together
*
@ -157,5 +213,11 @@ export function blackBoxSources(cx, sourcesToBlackBox, shouldBlackBox) {
: "UNBLACKBOX_WHOLE_SOURCES",
sources,
});
await toggleBreakpointsInBlackboxedSources({
thunkArgs,
cx,
shouldDisable: shouldBlackBox,
sources,
});
};
}

View file

@ -6,6 +6,7 @@ import { connect } from "../../utils/connect";
import PropTypes from "prop-types";
import { Component } from "react";
import { toEditorLine, fromEditorLine } from "../../utils/editor";
import { isLineBlackboxed } from "../../utils/source";
import { getBlackBoxRanges, getSelectedSource } from "../../selectors";
import { isWasm } from "../../utils/wasm";
@ -80,7 +81,7 @@ class BlackboxLines extends Component {
sourceIsWasm
);
if (this.isLineBlackboxed(blackboxedRangesForSelectedSource, line)) {
if (isLineBlackboxed(blackboxedRangesForSelectedSource, line)) {
this.setBlackboxLine(editor, lineHandle);
} else {
this.clearBlackboxLine(editor, lineHandle);
@ -95,12 +96,6 @@ class BlackboxLines extends Component {
this.clearAllBlackboxLines(this.props.editor);
}
isLineBlackboxed(ranges, line) {
return !!ranges.find(
range => line >= range.start.line && line <= range.end.line
);
}
clearAllBlackboxLines(editor) {
editor.codeMirror.operation(() => {
editor.codeMirror.eachLine(lineHandle => {

View file

@ -25,6 +25,7 @@ class Breakpoint extends PureComponent {
editor: PropTypes.object.isRequired,
editorActions: PropTypes.object.isRequired,
selectedSource: PropTypes.object,
blackboxedRangesForSelectedSource: PropTypes.array,
};
}
@ -95,14 +96,26 @@ class Breakpoint extends PureComponent {
};
onContextMenu = event => {
const { cx, breakpoint, selectedSource, breakpointActions } = this.props;
const {
cx,
breakpoint,
selectedSource,
breakpointActions,
blackboxedRangesForSelectedSource,
} = this.props;
event.stopPropagation();
event.preventDefault();
const selectedLocation = getSelectedLocation(breakpoint, selectedSource);
showMenu(
event,
breakpointItems(cx, breakpoint, selectedLocation, breakpointActions)
breakpointItems(
cx,
breakpoint,
selectedLocation,
breakpointActions,
blackboxedRangesForSelectedSource
)
);
};

View file

@ -6,7 +6,11 @@ import PropTypes from "prop-types";
import React, { Component } from "react";
import Breakpoint from "./Breakpoint";
import { getSelectedSource, getFirstVisibleBreakpoints } from "../../selectors";
import {
getSelectedSource,
getFirstVisibleBreakpoints,
getBlackBoxRanges,
} from "../../selectors";
import { makeBreakpointId } from "../../utils/breakpoint";
import { connect } from "../../utils/connect";
import { breakpointItemActions } from "./menus/breakpoints";
@ -21,6 +25,7 @@ class Breakpoints extends Component {
breakpointActions: PropTypes.object,
editorActions: PropTypes.object,
selectedSource: PropTypes.object,
blackboxedRanges: PropTypes.object,
};
}
render() {
@ -31,6 +36,7 @@ class Breakpoints extends Component {
editor,
breakpointActions,
editorActions,
blackboxedRanges,
} = this.props;
if (!selectedSource || !breakpoints) {
@ -46,6 +52,9 @@ class Breakpoints extends Component {
key={makeBreakpointId(bp.location)}
breakpoint={bp}
selectedSource={selectedSource}
blackboxedRangesForSelectedSource={
blackboxedRanges[selectedSource.url]
}
editor={editor}
breakpointActions={breakpointActions}
editorActions={editorActions}
@ -63,6 +72,7 @@ export default connect(
// breakpoint marker represents only the first breakpoint
breakpoints: getFirstVisibleBreakpoints(state),
selectedSource: getSelectedSource(state),
blackboxedRanges: getBlackBoxRanges(state),
}),
dispatch => ({
breakpointActions: breakpointItemActions(dispatch),

View file

@ -8,7 +8,7 @@ import { bindActionCreators } from "redux";
import ReactDOM from "react-dom";
import { connect } from "../../utils/connect";
import { getLineText } from "./../../utils/source";
import { getLineText, isLineBlackboxed } from "./../../utils/source";
import { createLocation } from "./../../utils/location";
import { features } from "../../utils/prefs";
import { getIndentation } from "../../utils/indentation";
@ -483,8 +483,8 @@ class Editor extends PureComponent {
closeConditionalPanel,
addBreakpointAtLine,
continueToHere,
toggleBlackBox,
breakableLines,
blackboxedRanges,
} = this.props;
// ignore right clicks in the gutter
@ -492,11 +492,6 @@ class Editor extends PureComponent {
return;
}
// if user clicks gutter to set breakpoint on blackboxed source, un-blackbox the source.
if (this.props.selectedSourceIsBlackBoxed) {
toggleBlackBox(cx, selectedSource);
}
if (conditionalPanelLocation) {
closeConditionalPanel();
return;
@ -525,7 +520,13 @@ class Editor extends PureComponent {
return;
}
addBreakpointAtLine(cx, sourceLine, ev.altKey, ev.shiftKey);
addBreakpointAtLine(
cx,
sourceLine,
ev.altKey,
ev.shiftKey ||
isLineBlackboxed(blackboxedRanges[selectedSource.url], sourceLine)
);
};
onGutterContextMenu = event => {

View file

@ -6,6 +6,7 @@ import actions from "../../../actions";
import { bindActionCreators } from "redux";
import { features } from "../../../utils/prefs";
import { formatKeyShortcut } from "../../../utils/text";
import { isLineBlackboxed } from "../../../utils/source";
export const addBreakpointItem = (cx, location, breakpointActions) => ({
id: "node-menu-add-breakpoint",
@ -86,11 +87,15 @@ export const logPointItem = (breakpoint, location, breakpointActions) => {
export const toggleDisabledBreakpointItem = (
cx,
breakpoint,
breakpointActions
breakpointActions,
blackboxedRangesForSelectedSource
) => {
return {
accesskey: L10N.getStr("editor.disableBreakpoint.accesskey"),
disabled: false,
disabled: isLineBlackboxed(
blackboxedRangesForSelectedSource,
breakpoint.location.line
),
click: () => breakpointActions.toggleDisabledBreakpoint(cx, breakpoint),
...(breakpoint.disabled
? {
@ -138,11 +143,17 @@ export function breakpointItems(
cx,
breakpoint,
selectedLocation,
breakpointActions
breakpointActions,
blackboxedRangesForSelectedSource
) {
const items = [
removeBreakpointItem(cx, breakpoint, breakpointActions),
toggleDisabledBreakpointItem(cx, breakpoint, breakpointActions),
toggleDisabledBreakpointItem(
cx,
breakpoint,
breakpointActions,
blackboxedRangesForSelectedSource
),
];
if (breakpoint.originalText.startsWith("debugger")) {
@ -161,7 +172,12 @@ export function breakpointItems(
{ type: "separator" },
removeBreakpointsOnLineItem(cx, selectedLocation, breakpointActions),
breakpoint.disabled
? enableBreakpointsOnLineItem(cx, selectedLocation, breakpointActions)
? enableBreakpointsOnLineItem(
cx,
selectedLocation,
breakpointActions,
blackboxedRangesForSelectedSource
)
: disableBreakpointsOnLineItem(cx, selectedLocation, breakpointActions),
{ type: "separator" }
);
@ -216,12 +232,13 @@ export const removeBreakpointsOnLineItem = (
export const enableBreakpointsOnLineItem = (
cx,
location,
breakpointActions
breakpointActions,
blackboxedRangesForSelectedSource
) => ({
id: "node-menu-remove-breakpoints-on-line",
label: L10N.getStr("breakpointMenuItem.enableAllAtLine.label"),
accesskey: L10N.getStr("breakpointMenuItem.enableAllAtLine.accesskey"),
disabled: false,
disabled: isLineBlackboxed(blackboxedRangesForSelectedSource, location.line),
click: () =>
breakpointActions.enableBreakpointsAtLine(
cx,

View file

@ -211,9 +211,6 @@ const blackBoxLinesMenuItem = (
},
};
// removes the current selection
codeMirror.replaceSelection(codeMirror.getSelection(), "start");
editorActions.toggleBlackBox(
cx,
selectedSource,

View file

@ -19,6 +19,10 @@ function generateDefaults(overrides) {
setGutterMarker: jest.fn(),
},
},
blackboxedRanges: {},
cx: {},
breakpointActions: {},
editorActions: {},
breakpoints: matchingBreakpoints,
...overrides,
};

View file

@ -11,6 +11,8 @@ exports[`Breakpoints Component should render breakpoints with columns 1`] = `
},
}
}
breakpointActions={Object {}}
cx={Object {}}
editor={
Object {
"codeMirror": Object {
@ -18,6 +20,7 @@ exports[`Breakpoints Component should render breakpoints with columns 1`] = `
},
}
}
editorActions={Object {}}
key="server1.conn1.child1/source1:undefined:2"
selectedSource={
Object {

View file

@ -13,6 +13,7 @@ import { CloseButton } from "../../shared/Button";
import { getSelectedText, makeBreakpointId } from "../../../utils/breakpoint";
import { getSelectedLocation } from "../../../utils/selected-location";
import { isLineBlackboxed } from "../../../utils/source";
import {
getBreakpointsList,
@ -38,6 +39,7 @@ class Breakpoint extends PureComponent {
selectSpecificLocation: PropTypes.func.isRequired,
selectedSource: PropTypes.object,
source: PropTypes.object.isRequired,
blackboxedRangesForSource: PropTypes.array.isRequired,
};
}
@ -117,7 +119,7 @@ class Breakpoint extends PureComponent {
}
render() {
const { breakpoint, editor } = this.props;
const { breakpoint, editor, blackboxedRangesForSource } = this.props;
const text = this.getBreakpointText();
const labelId = `${breakpoint.id}-label`;
@ -139,6 +141,10 @@ class Breakpoint extends PureComponent {
type="checkbox"
className="breakpoint-checkbox"
checked={!breakpoint.disabled}
disabled={isLineBlackboxed(
blackboxedRangesForSource,
breakpoint.location.line
)}
onChange={this.handleBreakpointCheckbox}
onClick={ev => ev.stopPropagation()}
aria-labelledby={labelId}

View file

@ -4,6 +4,7 @@
import { buildMenu, showMenu } from "../../../context-menu/menu";
import { getSelectedLocation } from "../../../utils/selected-location";
import { isLineBlackboxed } from "../../../utils/source";
import { features } from "../../../utils/prefs";
import { formatKeyShortcut } from "../../../utils/text";
@ -23,6 +24,7 @@ export default function showContextMenu(props) {
setBreakpointOptions,
openConditionalPanel,
contextMenuEvent,
blackboxedRangesForSource,
} = props;
contextMenuEvent.preventDefault();
@ -126,7 +128,10 @@ export default function showContextMenu(props) {
id: "node-menu-enable-self",
label: enableSelfLabel,
accesskey: enableSelfKey,
disabled: false,
disabled: isLineBlackboxed(
blackboxedRangesForSource,
breakpoint.location.line
),
click: () => {
toggleDisabledBreakpoint(cx, breakpoint);
},
@ -136,7 +141,10 @@ export default function showContextMenu(props) {
id: "node-menu-enable-all",
label: enableAllLabel,
accesskey: enableAllKey,
disabled: false,
disabled: isLineBlackboxed(
blackboxedRangesForSource,
breakpoint.location.line
),
click: () => toggleAllBreakpoints(cx, false),
};
@ -144,7 +152,10 @@ export default function showContextMenu(props) {
id: "node-menu-enable-others",
label: enableOthersLabel,
accesskey: enableOthersKey,
disabled: false,
disabled: isLineBlackboxed(
blackboxedRangesForSource,
breakpoint.location.line
),
click: () => toggleBreakpoints(cx, false, otherDisabledBreakpoints),
};

View file

@ -17,7 +17,11 @@ import { createHeadlessEditor } from "../../../utils/editor/create-editor";
import { makeBreakpointId } from "../../../utils/breakpoint";
import { getSelectedSource, getBreakpointSources } from "../../../selectors";
import {
getSelectedSource,
getBreakpointSources,
getBlackBoxRanges,
} from "../../../selectors";
const classnames = require("devtools/client/shared/classnames.js");
@ -31,6 +35,7 @@ class Breakpoints extends Component {
selectedSource: PropTypes.object,
shouldPauseOnCaughtExceptions: PropTypes.bool.isRequired,
shouldPauseOnExceptions: PropTypes.bool.isRequired,
blackboxedRanges: PropTypes.array.isRequired,
};
}
@ -91,7 +96,7 @@ class Breakpoints extends Component {
}
renderBreakpoints() {
const { breakpointSources, selectedSource } = this.props;
const { breakpointSources, selectedSource, blackboxedRanges } = this.props;
if (!breakpointSources.length) {
return null;
}
@ -112,6 +117,7 @@ class Breakpoints extends Component {
<Breakpoint
breakpoint={breakpoint}
source={source}
blackboxedRangesForSource={blackboxedRanges[source.url]}
selectedSource={selectedSource}
editor={editor}
key={makeBreakpointId(
@ -138,6 +144,7 @@ class Breakpoints extends Component {
const mapStateToProps = state => ({
breakpointSources: getBreakpointSources(state),
selectedSource: getSelectedSource(state),
blackboxedRanges: getBlackBoxRanges(state),
});
export default connect(mapStateToProps, {

View file

@ -80,6 +80,13 @@ function generateDefaults(overrides = {}, breakpointOverrides = {}) {
const breakpoint = makeBreakpoint(breakpointOverrides);
const selectedSource = createSourceObject("foo");
return {
cx: {},
disableBreakpoint: () => {},
enableBreakpoint: () => {},
openConditionalPanel: () => {},
removeBreakpoint: () => {},
selectSpecificLocation: () => {},
blackboxedRangesForSource: [],
source,
breakpoint,
selectedSource,

View file

@ -11,6 +11,7 @@ exports[`Breakpoint disabled 1`] = `
aria-labelledby="1-label"
checked={false}
className="breakpoint-checkbox"
disabled={true}
id={1}
onChange={[Function]}
onClick={[Function]}
@ -56,6 +57,7 @@ exports[`Breakpoint paused at a different 1`] = `
aria-labelledby="1-label"
checked={true}
className="breakpoint-checkbox"
disabled={true}
id={1}
onChange={[Function]}
onClick={[Function]}
@ -101,6 +103,7 @@ exports[`Breakpoint paused at a generatedLocation 1`] = `
aria-labelledby="1-label"
checked={true}
className="breakpoint-checkbox"
disabled={true}
id={1}
onChange={[Function]}
onClick={[Function]}
@ -146,6 +149,7 @@ exports[`Breakpoint paused at an original location 1`] = `
aria-labelledby="1-label"
checked={true}
className="breakpoint-checkbox"
disabled={true}
id={1}
onChange={[Function]}
onClick={[Function]}
@ -191,6 +195,7 @@ exports[`Breakpoint simple 1`] = `
aria-labelledby="1-label"
checked={true}
className="breakpoint-checkbox"
disabled={true}
id={1}
onChange={[Function]}
onClick={[Function]}

View file

@ -30,6 +30,15 @@ function generateDefaultState(propsOverride) {
text: 'URL contains ""',
},
],
enableXHRBreakpoint: () => {},
disableXHRBreakpoint: () => {},
updateXHRBreakpoint: () => {},
removeXHRBreakpoint: () => {},
setXHRBreakpoint: () => {},
togglePauseOnAny: () => {},
showInput: false,
shouldPauseOnAny: false,
onXHRAdded: () => {},
...propsOverride,
};
}

View file

@ -2,6 +2,15 @@
exports[`XHR Breakpoints should render with 0 expressions passed from props 1`] = `
<XHRBreakpoints
disableXHRBreakpoint={[Function]}
enableXHRBreakpoint={[Function]}
onXHRAdded={[Function]}
removeXHRBreakpoint={[Function]}
setXHRBreakpoint={[Function]}
shouldPauseOnAny={false}
showInput={false}
togglePauseOnAny={[Function]}
updateXHRBreakpoint={[Function]}
xhrBreakpoints={
Array [
Object {
@ -19,6 +28,7 @@ exports[`XHR Breakpoints should render with 0 expressions passed from props 1`]
>
<ExceptionOption
className="breakpoints-exceptions"
isChecked={false}
label="Pause on any URL"
onChange={[Function]}
>
@ -132,6 +142,15 @@ exports[`XHR Breakpoints should render with 0 expressions passed from props 1`]
exports[`XHR Breakpoints should render with 8 expressions passed from props 1`] = `
<XHRBreakpoints
disableXHRBreakpoint={[Function]}
enableXHRBreakpoint={[Function]}
onXHRAdded={[Function]}
removeXHRBreakpoint={[Function]}
setXHRBreakpoint={[Function]}
shouldPauseOnAny={false}
showInput={false}
togglePauseOnAny={[Function]}
updateXHRBreakpoint={[Function]}
xhrBreakpoints={
Array [
Object {
@ -205,6 +224,7 @@ exports[`XHR Breakpoints should render with 8 expressions passed from props 1`]
>
<ExceptionOption
className="breakpoints-exceptions"
isChecked={false}
label="Pause on any URL"
onChange={[Function]}
>

View file

@ -13,7 +13,6 @@ export function initialBreakpointsState(xhrBreakpoints = []) {
return {
breakpoints: {},
xhrBreakpoints,
breakpointsDisabled: false,
};
}

View file

@ -5,7 +5,6 @@
import { createSelector } from "reselect";
import { getSelectedSource } from "./sources";
import { getBreakpointsList } from "./breakpoints";
import { getBlackBoxRanges } from "./source-blackbox";
import { getFilename } from "../utils/source";
import { getSelectedLocation } from "../utils/selected-location";
@ -18,8 +17,7 @@ import { getSelectedLocation } from "../utils/selected-location";
export const getBreakpointSources = createSelector(
getBreakpointsList,
getSelectedSource,
getBlackBoxRanges,
(breakpoints, selectedSource, blackBoxRanges) => {
(breakpoints, selectedSource) => {
const visibleBreakpoints = breakpoints.filter(
bp =>
!bp.options.hidden &&
@ -33,11 +31,6 @@ export const getBreakpointSources = createSelector(
const location = getSelectedLocation(breakpoint, selectedSource);
const { source } = location;
// Ignore any blackboxed sources.
if (blackBoxRanges[source.url]) {
continue;
}
// We may have more than one breakpoint per source,
// so use the map to have a unique entry per source.
if (!sources.has(source)) {

View file

@ -30,7 +30,14 @@ export function getBreakpoint(state, location) {
return breakpoints[makeBreakpointId(location)];
}
export function getBreakpointsForSource(state, sourceId, line) {
/**
* Gets the breakpoints on a line or within a range of lines
* @param {Object} state
* @param {Number} sourceId
* @param {Number|Object} lines - line or an object with a start and end range of lines
* @returns {Array} breakpoints
*/
export function getBreakpointsForSource(state, sourceId, lines) {
if (!sourceId) {
return [];
}
@ -39,7 +46,16 @@ export function getBreakpointsForSource(state, sourceId, line) {
const breakpoints = getBreakpointsList(state);
return breakpoints.filter(bp => {
const location = isGeneratedSource ? bp.generatedLocation : bp.location;
return location.sourceId === sourceId && (!line || line == location.line);
if (lines) {
const isOnLineOrWithinRange =
typeof lines == "number"
? location.line == lines
: location.line >= lines.start.line &&
location.line <= lines.end.line;
return location.sourceId === sourceId && isOnLineOrWithinRange;
}
return location.sourceId === sourceId;
});
}

View file

@ -116,6 +116,26 @@ export function findBlackBoxRange(source, blackboxedRanges, lineRange) {
);
}
/**
* Checks if a source line is blackboxed
* @param {Array} ranges - Line ranges that are blackboxed
* @param {Number} line
* @returns boolean
*/
export function isLineBlackboxed(ranges, line) {
if (!ranges) {
return false;
}
// If the whole source is ignored , then the line is
// ignored.
if (!ranges.length) {
return true;
}
return !!ranges.find(
range => line >= range.start.line && line <= range.end.line
);
}
/**
* Returns true if the specified url and/or content type are specific to
* javascript files.

View file

@ -61,6 +61,7 @@ add_task(async function testBlackBoxOnMultipleFiles() {
"Ignore files outside this directory"
);
selectContextMenuItem(dbg, NODE_SELECTORS.nodeBlackBoxAllInside);
await waitForDispatch(dbg.store, "BLACKBOX_WHOLE_SOURCES");
await waitForBlackboxCount(dbg, 1);
await waitForRequestsToSettle(dbg);
@ -86,8 +87,11 @@ add_task(async function testBlackBoxOnMultipleFiles() {
"Unignore files outside this directory"
);
selectContextMenuItem(dbg, NODE_SELECTORS.nodeUnBlackBoxAllOutside);
await waitForDispatch(dbg.store, "UNBLACKBOX_WHOLE_SOURCES");
await waitForBlackboxCount(dbg, 0);
await waitForRequestsToSettle(dbg);
info("Wait for any breakpoints in the source to get enabled");
await waitForDispatch(dbg.store, "SET_BREAKPOINT");
assertSourceNodeIsBlackBoxed(dbg, SOURCE_FILES.nestedSource, false);
assertSourceNodeIsBlackBoxed(dbg, SOURCE_FILES.codeReload1, false);

View file

@ -83,7 +83,7 @@ add_task(async function testBlackBoxOnReload() {
assertNotPaused(dbg);
info("Ignoring line 2 using the gutter context menu");
await openContextMenu(dbg, "gutter", 2);
await openContextMenuInDebugger(dbg, "gutter", 2);
await selectBlackBoxContextMenuItem(dbg, "blackbox-line");
info("Ignoring line 7 to 9 using the editor context menu");
@ -141,7 +141,7 @@ add_task(async function testBlackBoxOnToolboxRestart() {
await onReloaded;
info("Ignoring line 2 using the gutter context menu");
await openContextMenu(dbg, "gutter", 2);
await openContextMenuInDebugger(dbg, "gutter", 2);
await selectBlackBoxContextMenuItem(dbg, "blackbox-line");
await reloadBrowser();
@ -194,7 +194,7 @@ async function testBlackBoxSource(dbg, source) {
info(
"Blackbox the whole simple4.js source file using the editor context menu"
);
await openContextMenu(dbg, "CodeMirrorLines");
await openContextMenuInDebugger(dbg, "CodeMirrorLines");
await selectBlackBoxContextMenuItem(dbg, "blackbox");
info("Assert that all lines in the source are styled correctly");
@ -392,7 +392,7 @@ async function testBlackBoxMultipleLines(dbg, source) {
async function testBlackBoxSingleLine(dbg, source) {
info("Black box line 2 of funcA() with the debugger statement");
await openContextMenu(dbg, "gutter", 2);
await openContextMenuInDebugger(dbg, "gutter", 2);
await selectBlackBoxContextMenuItem(dbg, "blackbox-line");
await assertEditorBlackBoxBoxContextMenuItems(dbg, {
@ -421,7 +421,7 @@ async function testBlackBoxSingleLine(dbg, source) {
});
info("Black box line 4 of funcC() with the debugger statement");
await openContextMenu(dbg, "gutter", 4);
await openContextMenuInDebugger(dbg, "gutter", 4);
await selectBlackBoxContextMenuItem(dbg, "blackbox-line");
info("Assert that the ignored line 4 is styled correctly");
@ -441,7 +441,7 @@ async function testBlackBoxSingleLine(dbg, source) {
info("Un-blackbox line 2 of funcA()");
selectEditorLines(dbg, 2, 2);
await openContextMenu(dbg, "CodeMirrorLines");
await openContextMenuInDebugger(dbg, "CodeMirrorLines");
await selectBlackBoxContextMenuItem(dbg, "blackbox-line");
await assertEditorBlackBoxBoxContextMenuItems(dbg, {
@ -511,7 +511,11 @@ async function assertGutterBlackBoxBoxContextMenuItems(dbg, testFixtures) {
info(
"Asserts that the gutter context menu items when clicking on the gutter of a blackboxed line"
);
const popup = await openContextMenu(dbg, "gutter", blackboxedLine);
const popup = await openContextMenuInDebugger(
dbg,
"gutter",
blackboxedLine
);
// When the whole source is blackboxed the the gutter visually shows `ignore line`
// but it is disabled indicating that individual lines cannot be nonBlackboxed.
const item =
@ -527,7 +531,11 @@ async function assertGutterBlackBoxBoxContextMenuItems(dbg, testFixtures) {
info(
"Asserts that the gutter context menu items when clicking on the gutter of a nonBlackboxed line"
);
const popup = await openContextMenu(dbg, "gutter", nonBlackBoxedLine);
const popup = await openContextMenuInDebugger(
dbg,
"gutter",
nonBlackBoxedLine
);
const item = contextMenuItems.ignoreLine;
await assertContextMenuLabel(dbg, item.selector, item.label);
@ -713,61 +721,6 @@ async function assertEditorBlackBoxBoxContextMenuItems(dbg, testFixtures) {
}
}
async function selectEditorLinesAndOpenContextMenu(dbg, lines) {
const { startLine, endLine } = lines;
const elementName = "line";
if (!endLine) {
await clickElement(dbg, elementName, startLine);
} else {
getCM(dbg).setSelection(
{ line: startLine - 1, ch: 0 },
{ line: endLine, ch: 0 }
);
}
return openContextMenu(dbg, elementName, startLine);
}
/**
* Opens the debugger editor context menu in either codemirror or the
* the debugger gutter.
* @param {Object} dbg
* @param {String} elementName
* The element to select
* @param {Number} line
* The line to open the context menu on.
*/
async function openContextMenu(dbg, elementName, line) {
const waitForOpen = waitForContextMenu(dbg);
info(`Open ${elementName} context menu on line ${line || ""}`);
rightClickElement(dbg, elementName, line);
return waitForOpen;
}
/**
* Selects the specific black box context menu item
* @param {Object} dbg
* @param {String} itemName
* The name of the context menu item.
*/
async function selectBlackBoxContextMenuItem(dbg, itemName) {
let wait = null;
if (itemName == "blackbox-line" || itemName == "blackbox-lines") {
wait = Promise.any([
waitForDispatch(dbg.store, "BLACKBOX_SOURCE_RANGES"),
waitForDispatch(dbg.store, "UNBLACKBOX_SOURCE_RANGES"),
]);
} else if (itemName == "blackbox") {
wait = Promise.any([
waitForDispatch(dbg.store, "BLACKBOX_WHOLE_SOURCES"),
waitForDispatch(dbg.store, "UNBLACKBOX_WHOLE_SOURCES"),
]);
}
info(`Select the ${itemName} context menu item`);
selectContextMenuItem(dbg, `#node-menu-${itemName}`);
return wait;
}
/**
* Selects a range of lines
* @param {Object} dbg
@ -780,64 +733,3 @@ function selectEditorLines(dbg, startLine, endLine) {
{ line: endLine, ch: 0 }
);
}
/**
* Asserts that the styling for ignored lines are applied
* @param {Object} dbg
* @param {Object} options
* lines {null | Number | Number[]} [lines] Line(s) to assert.
* - If null is passed, the assertion is on all the blackboxed lines
* - If a line number is passed, the assertion is on the specified line
* - If an array (start and end lines) is passed, the assertion is on the multiple lines seelected
* hasBlackboxedLinesClass
* If `true` assert that style exist, else assert that style does not exist
*/
function assertIgnoredStyleInSourceLines(
dbg,
{ lines, hasBlackboxedLinesClass }
) {
if (lines) {
if (!lines[1]) {
// Single line ignored
const element = findElement(dbg, "line", lines[0]);
const hasStyle = hasBlackboxedLinesClass
? element.parentNode.classList.contains("blackboxed-line")
: !element.parentNode.classList.contains("blackboxed-line");
ok(
hasStyle,
`Line ${lines[0]} ${
hasBlackboxedLinesClass ? "does not have" : "has"
} ignored styling`
);
} else {
// Multiple lines ignored
let currentLine = lines[0];
while (currentLine <= lines[1]) {
const element = findElement(dbg, "line", currentLine);
const hasStyle = hasBlackboxedLinesClass
? element.parentNode.classList.contains("blackboxed-line")
: !element.parentNode.classList.contains("blackboxed-line");
ok(
hasStyle,
`Line ${currentLine} ${
hasBlackboxedLinesClass ? "does not have" : "has"
} ignored styling`
);
currentLine = currentLine + 1;
}
}
} else {
const codeLines = findAllElementsWithSelector(
dbg,
".CodeMirror-code .CodeMirror-line"
);
const blackboxedLines = findAllElementsWithSelector(
dbg,
".CodeMirror-code .blackboxed-line"
);
is(
hasBlackboxedLinesClass ? codeLines.length : 0,
blackboxedLines.length,
`${blackboxedLines.length} of ${codeLines.length} lines are blackboxed`
);
}
}

View file

@ -114,3 +114,77 @@ add_task(async function testBreakpointsListForOriginalFiles() {
await removeBreakpoint(dbg, source.id, 5);
});
add_task(async function testBreakpointsListForIgnoredLines() {
await pushPref("devtools.debugger.features.blackbox-lines", true);
const dbg = await initDebugger("doc-sourcemaps.html", "entry.js");
info("Add breakpoint to the entry.js (original source)");
await selectSource(dbg, "entry.js");
await addBreakpoint(dbg, "entry.js", 5);
info("Ignoring line 5 to 6 which has a breakpoint already set");
await selectEditorLinesAndOpenContextMenu(dbg, {
startLine: 5,
endLine: 6,
});
await selectBlackBoxContextMenuItem(dbg, "blackbox-lines");
info("Assert the breakpoint on the ignored line");
let breakpointItems = findAllElements(dbg, "breakpointItems");
is(
breakpointItems[0].textContent,
"output(times2(1));5",
"The info displayed for the 1st breakpoint is correct"
);
const firstBreakpointCheck = breakpointItems[0].querySelector("input");
ok(
firstBreakpointCheck.disabled,
"The first breakpoint checkbox on an ignored line is disabled"
);
ok(
!firstBreakpointCheck.checked,
"The first breakpoint on an ignored line is not checked"
);
info("Ignoring line 8 to 9 which currently has not breakpoint");
await selectEditorLinesAndOpenContextMenu(dbg, {
startLine: 8,
endLine: 9,
});
await selectBlackBoxContextMenuItem(dbg, "blackbox-lines");
await addBreakpointViaGutter(dbg, 9);
breakpointItems = findAllElements(dbg, "breakpointItems");
is(
breakpointItems[1].textContent,
"output(times2(3));9",
"The info displayed for the 2nd breakpoint is correct"
);
const secondBreakpointCheck = breakpointItems[1].querySelector("input");
ok(
secondBreakpointCheck.disabled,
"The second breakpoint checkbox on an ignored line is disabled"
);
ok(
!secondBreakpointCheck.checked,
"The second breakpoint on an ignored line is not checked"
);
await clickElement(dbg, "blackbox");
await waitForDispatch(dbg.store, "UNBLACKBOX_WHOLE_SOURCES");
info("Assert that both breakpoints are now enabled");
breakpointItems = findAllElements(dbg, "breakpointItems");
[...breakpointItems].forEach(breakpointItem => {
const check = breakpointItem.querySelector("input");
ok(
!check.disabled,
"The breakpoint checkbox on the unignored line is enabled"
);
ok(check.checked, "The breakpoint on the unignored line is checked");
});
await dbg.toolbox.closeToolbox();
});

View file

@ -11,7 +11,7 @@
// rejections and make bug 1512742 permafail.
PromiseTestUtils.allowMatchingRejectionsGlobally(/NS_ERROR_NOT_INITIALIZED/);
add_task(async function() {
add_task(async function testGutterBreakpoints() {
const dbg = await initDebugger("doc-scripts.html", "simple1.js");
const source = findSource(dbg, "simple1.js");
@ -30,32 +30,117 @@ add_task(async function() {
await assertNoBreakpoint(dbg, 4);
});
add_task(async function() {
info("Ensure clicking on gutter to add breakpoint will un-blackbox source");
add_task(async function testGutterBreakpointsInIgnoredSource() {
await pushPref("devtools.debugger.features.blackbox-lines", true);
info(
"Ensure clicking on gutter to add breakpoint should not un-ignore source"
);
const dbg = await initDebugger("doc-sourcemaps3.html");
dbg.actions.toggleMapScopes();
await waitForSources(dbg, "bundle.js", "sorted.js", "test.js");
info("blackbox the source");
const sortedSrc = findSource(dbg, "sorted.js");
await selectSource(dbg, sortedSrc);
info("Ignore the source");
await selectSource(dbg, findSource(dbg, "sorted.js"));
await clickElement(dbg, "blackbox");
await waitForDispatch(dbg.store, "BLACKBOX_WHOLE_SOURCES");
// invoke test
invokeInTab("test");
// should not pause
// wait to make sure no pause has occured
await wait(1000);
assertNotPaused(dbg);
info("ensure gutter breakpoint gets set with click");
info("Ensure gutter breakpoint gets set with click");
await clickGutter(dbg, 4);
await waitForDispatch(dbg.store, "SET_BREAKPOINT");
info("Assert that the `Enable breakpoint` context menu item is disabled");
const popup = await openContextMenuInDebugger(dbg, "gutter", 4);
await assertContextMenuItemDisabled(
dbg,
"#node-menu-enable-breakpoint",
true
);
await closeContextMenu(dbg, popup);
is(dbg.selectors.getBreakpointCount(), 1, "One breakpoint exists");
await assertBreakpoint(dbg, 4);
is(
findBreakpoint(dbg, "sorted.js", 4).disabled,
true,
"The breakpoint in the ignored source looks disabled"
);
// click on test
invokeInTab("test");
// verify pause at breakpoint.
await waitForPaused(dbg);
ok(true, "source is un-blackboxed");
await wait(1000);
assertNotPaused(dbg);
ok(true, "source is still blackboxed");
});
add_task(async function testGutterBreakpointsForSourceWithIgnoredLines() {
await pushPref("devtools.debugger.features.blackbox-lines", true);
info(
"Asserts that adding a breakpoint to the gutter of an un-ignored line does not un-ignore other ranges in the source"
);
const dbg = await initDebugger("doc-sourcemaps3.html");
await waitForSources(dbg, "bundle.js", "sorted.js", "test.js");
await selectSource(dbg, findSource(dbg, "sorted.js"));
info("Ignoring line 17 to 21 using the editor context menu");
await selectEditorLinesAndOpenContextMenu(dbg, {
startLine: 17,
endLine: 21,
});
await selectBlackBoxContextMenuItem(dbg, "blackbox-lines");
info("Set breakpoint on an un-ignored line");
await clickGutter(dbg, 4);
await waitForDispatch(dbg.store, "SET_BREAKPOINT");
info("Assert that the `Disable breakpoint` context menu item is enabled");
const popup = await openContextMenuInDebugger(dbg, "gutter", 4);
await assertContextMenuItemDisabled(
dbg,
"#node-menu-disable-breakpoint",
false
);
await closeContextMenu(dbg, popup);
info("Assert that the lines 17 to 21 are still ignored");
assertIgnoredStyleInSourceLines(dbg, {
lines: [17, 21],
hasBlackboxedLinesClass: true,
});
info(
"Asserts that adding a breakpoint to the gutter of an ignored line creates a disabled breakpoint"
);
info("Set breakpoint on an ignored line");
await clickGutter(dbg, 19);
await waitForDispatch(dbg.store, "SET_BREAKPOINT");
info("Assert that the `Enable breakpoint` context menu item is disabled");
const popup2 = await openContextMenuInDebugger(dbg, "gutter", 19);
await assertContextMenuItemDisabled(
dbg,
"#node-menu-enable-breakpoint",
true
);
await closeContextMenu(dbg, popup2);
info("Assert that the breakpoint on the line 19 is visually disabled");
is(
findBreakpoint(dbg, "sorted.js", 19).disabled,
true,
"The breakpoint on an ignored line is disabled"
);
});
async function assertContextMenuItemDisabled(dbg, selector, expectedState) {
const item = await waitFor(() => findContextMenu(dbg, selector));
is(item.disabled, expectedState, "The context menu item is disabled");
}

View file

@ -1475,6 +1475,89 @@ async function getEditorLineEl(dbg, line) {
return el;
}
/**
* Opens the debugger editor context menu in either codemirror or the
* the debugger gutter.
* @param {Object} dbg
* @param {String} elementName
* The element to select
* @param {Number} line
* The line to open the context menu on.
*/
async function openContextMenuInDebugger(dbg, elementName, line) {
const waitForOpen = waitForContextMenu(dbg);
info(`Open ${elementName} context menu on line ${line || ""}`);
rightClickElement(dbg, elementName, line);
return waitForOpen;
}
/**
* Select a range of lines in the editor and open the contextmenu
* @param {Object} dbg
* @param {Object} lines
* @returns
*/
async function selectEditorLinesAndOpenContextMenu(dbg, lines) {
const { startLine, endLine } = lines;
const elementName = "line";
if (!endLine) {
await clickElement(dbg, elementName, startLine);
} else {
getCM(dbg).setSelection(
{ line: startLine - 1, ch: 0 },
{ line: endLine, ch: 0 }
);
}
return openContextMenuInDebugger(dbg, elementName, startLine);
}
/**
* Asserts that the styling for ignored lines are applied
* @param {Object} dbg
* @param {Object} options
* lines {null | Number[]} [lines] Line(s) to assert.
* - If null is passed, the assertion is on all the blackboxed lines
* - If an array of one item (start line) is passed, the assertion is on the specified line
* - If an array (start and end lines) is passed, the assertion is on the multiple lines seelected
* hasBlackboxedLinesClass
* If `true` assert that style exist, else assert that style does not exist
*/
function assertIgnoredStyleInSourceLines(
dbg,
{ lines, hasBlackboxedLinesClass }
) {
if (lines) {
let currentLine = lines[0];
do {
const element = findElement(dbg, "line", currentLine);
const hasStyle = hasBlackboxedLinesClass
? element.parentNode.classList.contains("blackboxed-line")
: !element.parentNode.classList.contains("blackboxed-line");
ok(
hasStyle,
`Line ${currentLine} ${
hasBlackboxedLinesClass ? "does not have" : "has"
} ignored styling`
);
currentLine = currentLine + 1;
} while (currentLine <= lines[1]);
} else {
const codeLines = findAllElementsWithSelector(
dbg,
".CodeMirror-code .CodeMirror-line"
);
const blackboxedLines = findAllElementsWithSelector(
dbg,
".CodeMirror-code .blackboxed-line"
);
is(
hasBlackboxedLinesClass ? codeLines.length : 0,
blackboxedLines.length,
`${blackboxedLines.length} of ${codeLines.length} lines are blackboxed`
);
}
}
/**
* Assert the text content on the line matches what is
* expected.
@ -2596,3 +2679,28 @@ if (protocolHandler.hasSubstitution("testing-common")) {
PromiseTestUtils.allowMatchingRejectionsGlobally(/Connection closed/);
this.PromiseTestUtils = PromiseTestUtils;
}
/**
* Selects the specific black box context menu item
* @param {Object} dbg
* @param {String} itemName
* The name of the context menu item.
*/
async function selectBlackBoxContextMenuItem(dbg, itemName) {
let wait = null;
if (itemName == "blackbox-line" || itemName == "blackbox-lines") {
wait = Promise.any([
waitForDispatch(dbg.store, "BLACKBOX_SOURCE_RANGES"),
waitForDispatch(dbg.store, "UNBLACKBOX_SOURCE_RANGES"),
]);
} else if (itemName == "blackbox") {
wait = Promise.any([
waitForDispatch(dbg.store, "BLACKBOX_WHOLE_SOURCES"),
waitForDispatch(dbg.store, "UNBLACKBOX_WHOLE_SOURCES"),
]);
}
info(`Select the ${itemName} context menu item`);
selectContextMenuItem(dbg, `#node-menu-${itemName}`);
return wait;
}