diff --git a/devtools/client/debugger/src/components/Editor/Breakpoints.css b/devtools/client/debugger/src/components/Editor/Breakpoints.css index 92121e0f466b..498368803153 100644 --- a/devtools/client/debugger/src/components/Editor/Breakpoints.css +++ b/devtools/client/debugger/src/components/Editor/Breakpoints.css @@ -95,12 +95,16 @@ right: -16px; } -.new-breakpoint.has-condition .CodeMirror-gutter-wrapper svg { +.new-breakpoint.has-condition .CodeMirror-gutter-wrapper svg, +/* Codemirror 6*/ +.cm6-gutter-breakpoint .breakpoint-marker.has-condition svg { fill: var(--breakpoint-condition-fill); stroke: var(--breakpoint-condition-stroke); } -.new-breakpoint.has-log .CodeMirror-gutter-wrapper svg { +.new-breakpoint.has-log .CodeMirror-gutter-wrapper svg, +/* Codemirror 6*/ +.cm6-gutter-breakpoint .breakpoint-marker.has-log svg { fill: var(--logpoint-fill); stroke: var(--logpoint-stroke); } diff --git a/devtools/client/debugger/src/components/Editor/ConditionalPanel.css b/devtools/client/debugger/src/components/Editor/ConditionalPanel.css index 4ce8dbcd8cea..1aeac9160415 100644 --- a/devtools/client/debugger/src/components/Editor/ConditionalPanel.css +++ b/devtools/client/debugger/src/components/Editor/ConditionalPanel.css @@ -37,3 +37,8 @@ /* Match the color of the placeholder text to existing inputs in the Debugger */ color: var(--theme-text-color-alt); } + +/* Removing the line padding for Codemirror 6 */ +.cm-line:has(div.conditional-breakpoint-panel) { + padding: 0; +} diff --git a/devtools/client/debugger/src/components/Editor/ConditionalPanel.js b/devtools/client/debugger/src/components/Editor/ConditionalPanel.js index 97876f2f00a6..b2af48fc7e4d 100644 --- a/devtools/client/debugger/src/components/Editor/ConditionalPanel.js +++ b/devtools/client/debugger/src/components/Editor/ConditionalPanel.js @@ -11,7 +11,8 @@ import ReactDOM from "devtools/client/shared/vendor/react-dom"; import PropTypes from "devtools/client/shared/vendor/react-prop-types"; import { connect } from "devtools/client/shared/vendor/react-redux"; import { toEditorLine } from "../../utils/editor/index"; -import { prefs } from "../../utils/prefs"; +import { createEditor } from "../../utils/editor/create-editor"; +import { prefs, features } from "../../utils/prefs"; import actions from "../../actions/index"; import { @@ -21,6 +22,7 @@ import { } from "../../selectors/index"; const classnames = require("resource://devtools/client/shared/classnames.js"); +const CONDITIONAL_BP_MARKER = "conditional-breakpoint-panel-marker"; function addNewLine(doc) { const cursor = doc.getCursor(); @@ -49,6 +51,7 @@ export class ConditionalPanel extends PureComponent { log: PropTypes.bool.isRequired, openConditionalPanel: PropTypes.func.isRequired, setBreakpointOptions: PropTypes.func.isRequired, + selectedSource: PropTypes.object.isRequired, }; } @@ -111,17 +114,53 @@ export class ConditionalPanel extends PureComponent { } }; + showConditionalPanel(prevProps) { + const { location, editor, breakpoint, selectedSource } = this.props; + // When breakpoint is removed + if (prevProps?.breakpoint && !breakpoint) { + editor.removeLineContentMarker(CONDITIONAL_BP_MARKER); + return; + } + if (selectedSource.id !== location.source.id) { + editor.removeLineContentMarker(CONDITIONAL_BP_MARKER); + return; + } + const editorLine = toEditorLine(location.source.id, location.line || 0); + editor.setLineContentMarker({ + id: CONDITIONAL_BP_MARKER, + condition: line => line == editorLine, + createLineElementNode: () => { + // Create a Codemirror 5 editor for the breakpoint panel + // TODO: Switch to use Codemirror 6 version Bug 1890205 + const breakpointPanelEditor = createEditor(); + breakpointPanelEditor.appendToLocalElement( + document.createElement("div") + ); + return this.renderConditionalPanel(this.props, breakpointPanelEditor); + }, + }); + } + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 UNSAFE_componentWillMount() { - return this.renderToWidget(this.props); + if (features.codemirrorNext) { + this.showConditionalPanel(); + } else { + this.renderToWidget(this.props); + } } // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 UNSAFE_componentWillUpdate() { - return this.clearConditionalPanel(); + if (!features.codemirrorNext) { + this.clearConditionalPanel(); + } } - componentDidUpdate() { + componentDidUpdate(prevProps) { + if (features.codemirrorNext) { + this.showConditionalPanel(prevProps); + } this.keepFocusOnInput(); } @@ -129,7 +168,12 @@ export class ConditionalPanel extends PureComponent { // This is called if CodeMirror is re-initializing itself before the // user closes the conditional panel. Clear the widget, and re-render it // as soon as this component gets remounted - return this.clearConditionalPanel(); + const { editor } = this.props; + if (features.codemirrorNext) { + editor.removeLineContentMarker(CONDITIONAL_BP_MARKER); + } else { + this.clearConditionalPanel(); + } } renderToWidget(props) { @@ -141,7 +185,7 @@ export class ConditionalPanel extends PureComponent { const editorLine = toEditorLine(location.source.id, location.line || 0); this.cbPanel = editor.codeMirror.addLineWidget( editorLine, - this.renderConditionalPanel(props), + this.renderConditionalPanel(props, editor), { coverGutter: true, noHScroll: true, @@ -168,8 +212,8 @@ export class ConditionalPanel extends PureComponent { } } - createEditor = input => { - const { log, editor, closeConditionalPanel } = this.props; + createEditor = (input, editor) => { + const { log, closeConditionalPanel } = this.props; const codeMirror = editor.CodeMirror.fromTextArea(input, { mode: "javascript", theme: "mozilla", @@ -189,8 +233,11 @@ export class ConditionalPanel extends PureComponent { codeMirror.on("blur", (cm, e) => { if ( - e?.relatedTarget && - e.relatedTarget.closest(".conditional-breakpoint-panel") + // if there is no actual element getting the focus or the focus is the conditional panel + // do not close the conditional panel + e?.relatedTarget == null || + (e?.relatedTarget && + e.relatedTarget.closest(".conditional-breakpoint-panel")) ) { return; } @@ -217,7 +264,7 @@ export class ConditionalPanel extends PureComponent { return log ? options.logValue : options.condition; } - renderConditionalPanel(props) { + renderConditionalPanel(props, editor) { const { log } = props; const defaultValue = this.getDefaultValue(); @@ -239,7 +286,7 @@ export class ConditionalPanel extends PureComponent { ), textarea({ defaultValue, - ref: input => this.createEditor(input), + ref: input => this.createEditor(input, editor), }) ), panel diff --git a/devtools/client/debugger/src/components/Editor/index.js b/devtools/client/debugger/src/components/Editor/index.js index 7d0cf73c3a8e..4ec680e4c89b 100644 --- a/devtools/client/debugger/src/components/Editor/index.js +++ b/devtools/client/debugger/src/components/Editor/index.js @@ -283,7 +283,7 @@ class Editor extends PureComponent { } }; - componentDidUpdate(prevProps) { + componentDidUpdate() { const { selectedSource, blackboxedRanges, @@ -298,30 +298,8 @@ class Editor extends PureComponent { // Sets the breakables lines for codemirror 6 if (features.codemirrorNext) { - const shouldUpdateBreakableLines = - prevProps.breakableLines.size !== this.props.breakableLines.size || - prevProps.selectedSource?.id !== selectedSource.id; - const isSourceWasm = isWasm(selectedSource.id); - - if (shouldUpdateBreakableLines) { - editor.setLineGutterMarkers([ - { - id: "empty-line-marker", - lineClassName: "empty-line", - condition: line => { - const lineNumber = fromEditorLine( - selectedSource.id, - line, - isSourceWasm - ); - return !breakableLines.has(lineNumber); - }, - }, - ]); - } - - function condition(line) { + function blackboxCondition(line) { const lineNumber = fromEditorLine(selectedSource.id, line); return isLineBlackboxed( @@ -332,16 +310,29 @@ class Editor extends PureComponent { } editor.setLineGutterMarkers([ + { + id: "empty-line-marker", + lineClassName: "empty-line", + condition: line => { + const lineNumber = fromEditorLine( + selectedSource.id, + line, + isSourceWasm + ); + return !breakableLines.has(lineNumber); + }, + }, { id: "blackboxed-line-gutter-marker", lineClassName: "blackboxed-line", - condition, + condition: blackboxCondition, }, ]); + editor.setLineContentMarker({ id: "blackboxed-line-marker", lineClassName: "blackboxed-line", - condition, + condition: blackboxCondition, }); } } @@ -824,7 +815,13 @@ class Editor extends PureComponent { editor, }), React.createElement(DebugLine, { editor, selectedSource }), - React.createElement(Exceptions, { editor }) + React.createElement(Exceptions, { editor }), + conditionalPanelLocation + ? React.createElement(ConditionalPanel, { + editor, + selectedSource, + }) + : null ); } diff --git a/devtools/client/shared/sourceeditor/editor.js b/devtools/client/shared/sourceeditor/editor.js index cf51d615b825..ca2be4cf85a2 100644 --- a/devtools/client/shared/sourceeditor/editor.js +++ b/devtools/client/shared/sourceeditor/editor.js @@ -707,10 +707,17 @@ class Editor extends EventEmitter { */ #lineContentMarkersExtension({ markers, domEventHandlers }) { const { - codemirrorView: { Decoration, ViewPlugin }, + codemirrorView: { Decoration, ViewPlugin, WidgetType }, codemirrorState: { RangeSetBuilder, RangeSet }, } = this.#CodeMirror6; + class LineContentWidget extends WidgetType { + constructor(createElementNode) { + super(); + this.toDOM = createElementNode; + } + } + // Build and return the decoration set function buildDecorations(view) { if (!markers) { @@ -720,13 +727,20 @@ class Editor extends EventEmitter { for (const { from, to } of view.visibleRanges) { for (let pos = from; pos <= to; ) { const line = view.state.doc.lineAt(pos); - for (const { lineClassName, condition } of markers) { - if (condition(line.number)) { - builder.add( - line.from, - line.from, - Decoration.line({ class: lineClassName }) - ); + for (const marker of markers) { + if (marker.condition(line.number)) { + if (marker.lineClassName) { + const classDecoration = Decoration.line({ + class: marker.lineClassName, + }); + builder.add(line.from, line.from, classDecoration); + } + if (marker.createLineElementNode) { + const nodeDecoration = Decoration.widget({ + widget: new LineContentWidget(marker.createLineElementNode), + }); + builder.add(line.to, line.to, nodeDecoration); + } } } pos = line.to + 1; @@ -798,6 +812,8 @@ class Editor extends EventEmitter { * @property {string} marker.lineClassName - The css class to add to the line * @property {function} marker.condition - The condition that decides if the marker/class gets added or removed. * The line is passed as an argument. + * @property {function} marker.createLineElementNode - This should return the DOM element which + * is used for the marker. This is optional. */ setLineContentMarker(marker) { const cm = editors.get(this);