Bug 1745940 - [devtools] Implement blackboxing lines UI functionality r=nchevobbe

Basic UI functionality for blackboxing

Enable this feature pref `devtools.debugger.features.blackbox-lines`

This is mainly to gather feeback from the team and webcompat, things
like the selection colors will likely change, maybe we can add a context menu
to the editor gutter as well etc.

Differential Revision: https://phabricator.services.mozilla.com/D132368
This commit is contained in:
Hubert Boma Manilla 2021-12-16 23:53:26 +00:00
Родитель f1fedb6b37
Коммит 1490eb2f73
11 изменённых файлов: 322 добавлений и 11 удалений

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

@ -0,0 +1,134 @@
/* 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/>. */
import { connect } from "../../utils/connect";
import PropTypes from "prop-types";
import { Component } from "react";
import { toEditorLine, fromEditorLine } from "../../utils/editor";
import { getBlackBoxRanges, getSelectedSource } from "../../selectors";
// This renders blackbox line highlighting in the editor
class BlackboxLines extends Component {
static get propTypes() {
return {
editor: PropTypes.object,
selectedSource: PropTypes.object,
blackboxedRanges: PropTypes.object,
};
}
componentDidMount() {
const { selectedSource, blackboxedRanges, editor } = this.props;
const ranges = blackboxedRanges[selectedSource.url];
if (!selectedSource.isBlackBoxed && !ranges) {
return;
}
if (ranges) {
if (!ranges.length) {
// The whole source was blackboxed
this.setAllBlackboxLines(editor);
} else {
editor.codeMirror.operation(() => {
ranges.forEach(range => {
const start = toEditorLine(selectedSource.id, range.start.line);
const end = toEditorLine(selectedSource.id, range.end.line);
editor.codeMirror.eachLine(start, end, lineHandle => {
this.setBlackboxLine(editor, lineHandle);
});
});
});
}
}
}
componentDidUpdate() {
const { selectedSource, blackboxedRanges, editor } = this.props;
const ranges = blackboxedRanges[selectedSource.url];
// when unblackboxed
if (!ranges) {
this.clearAllBlackboxLines(editor);
return;
}
// When the whole source is blackboxed
if (!ranges.length) {
this.setAllBlackboxLines(editor);
return;
}
// TODO: Possible perf improvement. Instead of going
// over all the lines each time get diffs of what has
// changed and update those.
editor.codeMirror.operation(() => {
editor.codeMirror.eachLine(lineHandle => {
const line = fromEditorLine(
selectedSource.id,
editor.codeMirror.getLineNumber(lineHandle)
);
if (this.isLineBlackboxed(ranges, line)) {
this.setBlackboxLine(editor, lineHandle);
} else {
this.clearBlackboxLine(editor, lineHandle);
}
});
});
}
componentWillUnmount() {
// Lets make sure we remove everything relating to
// blackboxing lines when this component is unmounted.
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 => {
this.clearBlackboxLine(editor, lineHandle);
});
});
}
setAllBlackboxLines(editor) {
//TODO:We might be able to handle the whole source
// than adding the blackboxing line by line
editor.codeMirror.operation(() => {
editor.codeMirror.eachLine(lineHandle => {
this.setBlackboxLine(editor, lineHandle);
});
});
}
clearBlackboxLine(editor, lineHandle) {
editor.codeMirror.removeLineClass(
lineHandle,
"wrapClass",
"blackboxed-line"
);
}
setBlackboxLine(editor, lineHandle) {
editor.codeMirror.addLineClass(lineHandle, "wrapClass", "blackboxed-line");
}
render() {
return null;
}
}
const mapStateToProps = state => {
return {
blackboxedRanges: getBlackBoxRanges(state),
selectedSource: getSelectedSource(state),
};
};
export default connect(mapStateToProps)(BlackboxLines);

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

@ -84,7 +84,8 @@
stroke: var(--logpoint-stroke);
}
.editor.new-breakpoint.breakpoint-disabled svg {
.editor.new-breakpoint.breakpoint-disabled svg,
.blackboxed-line .editor.new-breakpoint svg {
fill-opacity: var(--breakpoint-disabled-opacity);
stroke-opacity: var(--breakpoint-disabled-opacity);
}

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

@ -2,6 +2,7 @@
* 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/>. */
import PropTypes from "prop-types";
import React, { Component } from "react";
import Breakpoint from "./Breakpoint";
@ -12,6 +13,16 @@ import { breakpointItemActions } from "./menus/breakpoints";
import { editorItemActions } from "./menus/editor";
class Breakpoints extends Component {
static get propTypes() {
return {
cx: PropTypes.object,
breakpoints: PropTypes.array,
editor: PropTypes.object,
breakpointActions: PropTypes.object,
editorActions: PropTypes.object,
selectedSource: PropTypes.object,
};
}
render() {
const {
cx,
@ -22,7 +33,7 @@ class Breakpoints extends Component {
editorActions,
} = this.props;
if (!selectedSource || !breakpoints || selectedSource.isBlackBoxed) {
if (!selectedSource || !breakpoints) {
return null;
}

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

@ -53,14 +53,6 @@ html[dir="rtl"] .editor-mount {
direction: ltr;
}
.theme-light .cm-s-mozilla .empty-line .CodeMirror-linenumber {
color: var(--grey-40);
}
.theme-dark .cm-s-mozilla .empty-line .CodeMirror-linenumber {
color: var(--grey-50);
}
.function-search {
max-height: 300px;
overflow: hidden;

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

@ -13,6 +13,7 @@ import {
getIsCurrentThreadPaused,
getThreadContext,
isSourceWithMap,
getBlackBoxRanges,
} from "../../selectors";
import { editorMenuItems, editorItemActions } from "./menus/editor";
@ -30,6 +31,7 @@ class EditorMenu extends Component {
cx,
editor,
selectedSource,
blackboxedRanges,
editorActions,
hasMappedLocation,
isPaused,
@ -50,12 +52,14 @@ class EditorMenu extends Component {
cx,
editorActions,
selectedSource,
blackboxedRanges,
hasMappedLocation,
location,
isPaused,
editorWrappingEnabled,
selectionText: editor.codeMirror.getSelection().trim(),
isTextSelected: editor.codeMirror.somethingSelected(),
editor,
})
);
}
@ -67,6 +71,7 @@ class EditorMenu extends Component {
const mapStateToProps = (state, props) => ({
cx: getThreadContext(state),
blackboxedRanges: getBlackBoxRanges(state),
isPaused: getIsCurrentThreadPaused(state),
hasMappedLocation:
(props.selectedSource.isOriginal ||

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

@ -52,6 +52,7 @@ import ConditionalPanel from "./ConditionalPanel";
import InlinePreviews from "./InlinePreviews";
import HighlightCalls from "./HighlightCalls";
import Exceptions from "./Exceptions";
import BlackboxLines from "./BlackboxLines";
import {
showSourceText,
@ -98,6 +99,37 @@ const cssVars = {
};
class Editor extends PureComponent {
static get propTypes() {
return {
selectedSource: PropTypes.object,
cx: PropTypes.object,
closeTab: PropTypes.func,
toggleBreakpointAtLine: PropTypes.func,
conditionalPanelLocation: PropTypes.object,
closeConditionalPanel: PropTypes.func,
openConditionalPanel: PropTypes.func,
updateViewport: PropTypes.func,
isPaused: PropTypes.bool,
highlightCalls: PropTypes.func,
unhighlightCalls: PropTypes.func,
breakpointActions: PropTypes.object,
editorActions: PropTypes.object,
addBreakpointAtLine: PropTypes.func,
continueToHere: PropTypes.func,
toggleBlackBox: PropTypes.func,
updateCursorPosition: PropTypes.func,
jumpToMappedLocation: PropTypes.func,
selectedLocation: PropTypes.object,
symbols: PropTypes.object,
startPanelSize: PropTypes.number,
endPanelSize: PropTypes.number,
searchOn: PropTypes.bool,
inlinePreviewEnabled: PropTypes.bool,
editorWrappingEnabled: PropTypes.bool,
skipPausing: PropTypes.bool,
};
}
$editorWrapper;
constructor(props) {
super(props);
@ -606,6 +638,7 @@ class Editor extends PureComponent {
<Breakpoints editor={editor} cx={cx} />
<Preview editor={editor} editorRef={this.$editorWrapper} />
<HighlightLines editor={editor} />
{features.blackboxLines ? <BlackboxLines editor={editor} /> : null}
<Exceptions />
{
<EditorMenu

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

@ -10,7 +10,7 @@ import {
getFilename,
shouldBlackbox,
} from "../../../utils/source";
import { toSourceLine } from "../../../utils/editor";
import { downloadFile } from "../../../utils/utils";
import { features } from "../../../utils/prefs";
@ -90,6 +90,85 @@ const blackBoxMenuItem = (cx, selectedSource, editorActions) => ({
disabled: !shouldBlackbox(selectedSource),
click: () => editorActions.toggleBlackBox(cx, selectedSource),
});
/**
* Checks if a blackbox range exist for the line range.
* That is if any start and end lines overlap any of the
* blackbox ranges
*
* @param {Object} source - The current selected source
* @param {Object} blackboxedRanges - the store of blackboxedRanges
* @param {Object} line - The start and end line range
*/
function findBlackBoxRange(source, blackboxedRanges, line) {
const ranges = blackboxedRanges[source.url];
if (!ranges || !ranges.length) {
return null;
}
return ranges.find(
range =>
(line.start >= range.start.line && line.start <= range.end.line) ||
(line.end >= range.start.line && line.end <= range.end.line)
);
}
const blackBoxLinesMenuItem = (
cx,
selectedSource,
editorActions,
editor,
blackboxedRanges
) => {
const { codeMirror } = editor;
const from = codeMirror.getCursor("from");
const to = codeMirror.getCursor("to");
const startLine = toSourceLine(selectedSource.id, from.line);
const endLine = toSourceLine(selectedSource.id, to.line);
const shouldDisable =
selectedSource.isBlackBoxed && !blackboxedRanges[selectedSource.url].length;
const blackboxRange = findBlackBoxRange(selectedSource, blackboxedRanges, {
start: startLine,
end: endLine,
});
return {
id: "node-menu-blackbox-lines",
label: !blackboxRange
? L10N.getStr("ignoreContextItem.ignoreLines")
: L10N.getStr("ignoreContextItem.unignoreLines"),
accesskey: !blackboxRange
? L10N.getStr("ignoreContextItem.ignoreLines.accesskey")
: L10N.getStr("ignoreContextItem.unignoreLines.accesskey"),
disabled: shouldDisable,
click: () => {
const selectionRange = {
start: {
line: startLine,
column: from.ch,
},
end: {
line: endLine,
column: to.ch,
},
};
// removes the current selection
editor.codeMirror.replaceSelection(
editor.codeMirror.getSelection(),
"start"
);
editorActions.toggleBlackBox(
cx,
selectedSource,
!blackboxRange,
blackboxRange ? [blackboxRange] : [selectionRange]
);
},
};
};
const watchExpressionItem = (
cx,
@ -140,12 +219,14 @@ export function editorMenuItems({
cx,
editorActions,
selectedSource,
blackboxedRanges,
location,
selectionText,
hasMappedLocation,
isTextSelected,
isPaused,
editorWrappingEnabled,
editor,
}) {
const items = [];
@ -176,9 +257,22 @@ export function editorMenuItems({
: []),
{ type: "separator" },
showSourceMenuItem(cx, selectedSource, editorActions),
{ type: "separator" },
blackBoxMenuItem(cx, selectedSource, editorActions)
);
if (features.blackboxLines) {
items.push(
blackBoxLinesMenuItem(
cx,
selectedSource,
editorActions,
editor,
blackboxedRanges
)
);
}
if (isTextSelected) {
items.push(
{ type: "separator" },
@ -206,6 +300,7 @@ export function editorItemActions(dispatch) {
jumpToMappedLocation: actions.jumpToMappedLocation,
showSource: actions.showSource,
toggleBlackBox: actions.toggleBlackBox,
toggleBlackBoxLines: actions.toggleBlackBoxLines,
toggleInlinePreview: actions.toggleInlinePreview,
toggleEditorWrapping: actions.toggleEditorWrapping,
},

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

@ -9,6 +9,7 @@ DIRS += [
]
CompiledModules(
"BlackboxLines.js",
"Breakpoint.js",
"Breakpoints.js",
"ColumnBreakpoint.js",

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

@ -642,6 +642,16 @@ ignoreContextItem.ignore.accesskey=I
ignoreContextItem.unignore=Unignore source
ignoreContextItem.unignore.accesskey=U
# LOCALIZATION NOTE (ignoreContextItem.ignoreLines): Text associated
# with the ignore lines context menu item
ignoreContextItem.ignoreLines=Ignore lines
ignoreContextItem.ignoreLines.accesskey=i
# LOCALIZATION NOTE (ignoreContextItem.unignoreLines): Text associated
# with the unignore lines context menu item
ignoreContextItem.unignoreLines=Unignore lines
ignoreContextItem.unignoreLines.accesskey=u
# LOCALIZATION NOTE (sourceFooter.mappedSource): Text associated
# with a mapped source. %S is replaced by the source map origin.
sourceFooter.mappedSource=(From %S)

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

@ -232,6 +232,21 @@ div.CodeMirror span.marked-text {
margin-inline-end: -1px;
}
.cm-s-mozilla .empty-line .CodeMirror-linenumber {
color: var(--grey-50);
}
/* Blackboxing lines */
.cm-s-mozilla .CodeMirror-lines .blackboxed-line {
background-color: hsl(211, 89%, 17%);
}
.cm-s-mozilla .blackboxed-line .CodeMirror-linenumber {
color: var(--theme-icon-checked-color);
}
/* Highlight for evaluating current statement. */
div.CodeMirror span.eval-text {
background-color: #556;

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

@ -220,6 +220,20 @@ div.CodeMirror span.marked-text {
margin-inline-end: -1px;
}
.cm-s-mozilla .empty-line .CodeMirror-linenumber {
color: var(--grey-40);
}
/* Blackboxing lines */
.cm-s-mozilla .CodeMirror-lines .blackboxed-line {
background-color: hsl(211, 89%, 95%);
}
.theme-light .cm-s-mozilla .blackboxed-line .CodeMirror-linenumber {
color: var(--theme-icon-checked-color);
}
/* Highlight for evaluating current statement. */
div.CodeMirror span.eval-text {
background-color: #ccd;