DebugLine.js (5642B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 import { PureComponent } from "devtools/client/shared/vendor/react"; 6 import PropTypes from "devtools/client/shared/vendor/react-prop-types"; 7 import { toEditorPosition } from "../../utils/editor/index"; 8 import { isException } from "../../utils/pause/index"; 9 import { connect } from "devtools/client/shared/vendor/react-redux"; 10 import { 11 getVisibleSelectedFrame, 12 getPauseReason, 13 getSourceTextContentForLocation, 14 getCurrentThread, 15 getViewport, 16 getSelectedTraceLocation, 17 } from "../../selectors/index"; 18 19 export class DebugLine extends PureComponent { 20 debugExpression; 21 22 static get propTypes() { 23 return { 24 editor: PropTypes.object, 25 selectedSource: PropTypes.object, 26 location: PropTypes.object, 27 why: PropTypes.object, 28 sourceTextContent: PropTypes.object, 29 }; 30 } 31 32 componentDidMount() { 33 this.setDebugLine(); 34 } 35 36 componentWillUnmount() { 37 this.clearDebugLine(this.props); 38 } 39 40 componentDidUpdate(prevProps) { 41 this.clearDebugLine(prevProps); 42 this.setDebugLine(); 43 } 44 45 setDebugLine() { 46 const { why, location, editor, selectedSource } = this.props; 47 if (!location) { 48 return; 49 } 50 51 if (!selectedSource || location.source.id !== selectedSource.id) { 52 return; 53 } 54 55 const { lineClass, markTextClass } = this.getTextClasses(why); 56 const editorLocation = toEditorPosition(location); 57 58 // Show the paused "caret", to highlight on which particular line **and column** we are paused. 59 // 60 // Using only a `positionClassName` wouldn't only be applied to the immediate 61 // token after the position and force to use ::before to show the paused location. 62 // Using ::before prevents using :hover to be able to hide the icon on mouse hovering. 63 // 64 // So we have to use `createPositionElementNode`, similarly to column breakpoints 65 // to have a new dedicated DOM element for the paused location. 66 editor.setPositionContentMarker({ 67 id: editor.markerTypes.PAUSED_LOCATION_MARKER, 68 69 // Ensure displaying the marker after all the other markers and especially the column breakpoint markers 70 displayLast: true, 71 72 positions: [editorLocation], 73 createPositionElementNode(_line, _column, isFirstNonSpaceColumn) { 74 const pausedLocation = document.createElement("span"); 75 pausedLocation.className = `paused-location${isFirstNonSpaceColumn ? " first-column" : ""}`; 76 77 const triangle = document.createElement("span"); 78 triangle.className = `triangle`; 79 pausedLocation.appendChild(triangle); 80 81 return pausedLocation; 82 }, 83 }); 84 85 editor.setLineContentMarker({ 86 id: editor.markerTypes.DEBUG_LINE_MARKER, 87 lineClassName: lineClass, 88 lines: [{ line: editorLocation.line }], 89 }); 90 editor.setPositionContentMarker({ 91 id: editor.markerTypes.DEBUG_POSITION_MARKER, 92 positionClassName: markTextClass, 93 positions: [editorLocation], 94 }); 95 } 96 97 clearDebugLine(otherProps = {}) { 98 const { location, editor, selectedSource } = this.props; 99 // Remove the debug line marker when no longer paused, or the selected source 100 // is no longer the source where the pause occured. 101 if ( 102 !location || 103 location.source.id !== selectedSource.id || 104 otherProps?.location !== location || 105 otherProps?.selectedSource?.id !== selectedSource.id 106 ) { 107 editor.removeLineContentMarker(editor.markerTypes.DEBUG_LINE_MARKER); 108 editor.removePositionContentMarker( 109 editor.markerTypes.DEBUG_POSITION_MARKER 110 ); 111 editor.removePositionContentMarker( 112 editor.markerTypes.PAUSED_LOCATION_MARKER 113 ); 114 } 115 } 116 117 getTextClasses(why) { 118 if (why && isException(why)) { 119 return { 120 markTextClass: "debug-expression-error", 121 lineClass: "new-debug-line-error", 122 }; 123 } 124 125 // We no longer highlight the next token via debug-expression 126 // and only highlight the line via paused-line. 127 return { 128 markTextClass: null, 129 lineClass: why == "tracer" ? "traced-line" : "paused-line", 130 }; 131 } 132 133 render() { 134 return null; 135 } 136 } 137 138 function isDocumentReady(location, sourceTextContent) { 139 return location && sourceTextContent; 140 } 141 142 const mapStateToProps = state => { 143 // If we aren't paused, fallback on showing the JS tracer 144 // currently selected trace location. 145 // If any trace is selected in the JS Tracer, this takes the lead over 146 // any paused location. (the same choice is made when showing inline previews) 147 let why; 148 let location = getSelectedTraceLocation(state); 149 if (location) { 150 why = "tracer"; 151 } else { 152 // Avoid unecessary intermediate updates when there is no location 153 // or the source text content isn't yet fully loaded 154 const frame = getVisibleSelectedFrame(state); 155 location = frame?.location; 156 157 // We are not tracing, nor pausing 158 if (!location) { 159 return {}; 160 } 161 162 why = getPauseReason(state, getCurrentThread(state)); 163 } 164 165 // if we have a valid viewport. 166 // This is a way to know if the actual source is displayed 167 // and we are no longer on the "loading..." message 168 if (!getViewport(state)) { 169 return {}; 170 } 171 172 const sourceTextContent = getSourceTextContentForLocation(state, location); 173 if (!isDocumentReady(location, sourceTextContent)) { 174 return {}; 175 } 176 177 return { 178 location, 179 why, 180 sourceTextContent, 181 }; 182 }; 183 184 export default connect(mapStateToProps)(DebugLine);