Breakpoint.js (7568B)
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 React, { PureComponent } from "devtools/client/shared/vendor/react"; 6 import { 7 div, 8 input, 9 span, 10 } from "devtools/client/shared/vendor/react-dom-factories"; 11 import PropTypes from "devtools/client/shared/vendor/react-prop-types"; 12 import { connect } from "devtools/client/shared/vendor/react-redux"; 13 import actions from "../../../actions/index"; 14 15 import { CloseButton } from "../../shared/Button/index"; 16 17 import { 18 getSelectedText, 19 makeBreakpointId, 20 } from "../../../utils/breakpoint/index"; 21 import { getSelectedLocation } from "../../../utils/selected-location"; 22 import { isLineBlackboxed } from "../../../utils/source"; 23 24 import { 25 getSelectedFrame, 26 getSelectedSource, 27 isSourceMapIgnoreListEnabled, 28 isSourceOnSourceMapIgnoreList, 29 getBlackBoxRanges, 30 } from "../../../selectors/index"; 31 32 const classnames = require("resource://devtools/client/shared/classnames.js"); 33 34 class Breakpoint extends PureComponent { 35 static get propTypes() { 36 return { 37 breakpoint: PropTypes.object.isRequired, 38 disableBreakpoint: PropTypes.func.isRequired, 39 editor: PropTypes.object.isRequired, 40 enableBreakpoint: PropTypes.func.isRequired, 41 openConditionalPanel: PropTypes.func.isRequired, 42 removeBreakpoint: PropTypes.func.isRequired, 43 selectSpecificLocation: PropTypes.func.isRequired, 44 selectedBreakpointLocation: PropTypes.object.isRequired, 45 isCurrentlyPausedAtBreakpoint: PropTypes.bool.isRequired, 46 source: PropTypes.object.isRequired, 47 checkSourceOnIgnoreList: PropTypes.func.isRequired, 48 isBreakpointLineBlackboxed: PropTypes.bool, 49 showBreakpointContextMenu: PropTypes.func.isRequired, 50 breakpointText: PropTypes.string.isRequired, 51 }; 52 } 53 54 onContextMenu = event => { 55 event.preventDefault(); 56 57 this.props.showBreakpointContextMenu( 58 event, 59 this.props.breakpoint, 60 this.props.source 61 ); 62 }; 63 64 stopClicks = event => event.stopPropagation(); 65 66 onDoubleClick = () => { 67 const { breakpoint, openConditionalPanel } = this.props; 68 if (breakpoint.options.condition) { 69 openConditionalPanel(this.props.selectedBreakpointLocation); 70 } else if (breakpoint.options.logValue) { 71 openConditionalPanel(this.props.selectedBreakpointLocation, true); 72 } 73 }; 74 75 onKeyDown = event => { 76 // Handling only the Enter/Space keys, bail if another key was pressed 77 if (event.key !== "Enter" && event.key !== " ") { 78 return; 79 } 80 81 if (event.shiftKey) { 82 this.onDoubleClick(); 83 return; 84 } 85 this.selectBreakpoint(event); 86 }; 87 88 selectBreakpoint = event => { 89 // Ignore double click as we have a dedicated double click listener 90 if (event.type == "click" && event.detail > 1) { 91 return; 92 } 93 event.preventDefault(); 94 const { selectSpecificLocation } = this.props; 95 selectSpecificLocation(this.props.selectedBreakpointLocation); 96 }; 97 98 removeBreakpoint = event => { 99 const { removeBreakpoint, breakpoint } = this.props; 100 event.stopPropagation(); 101 removeBreakpoint(breakpoint); 102 }; 103 104 handleBreakpointCheckbox = () => { 105 const { breakpoint, enableBreakpoint, disableBreakpoint } = this.props; 106 if (breakpoint.disabled) { 107 enableBreakpoint(breakpoint); 108 } else { 109 disableBreakpoint(breakpoint); 110 } 111 }; 112 113 getBreakpointLocation() { 114 const { source } = this.props; 115 const { column, line } = this.props.selectedBreakpointLocation; 116 117 const isWasm = source?.isWasm; 118 // column is 0-based everywhere, but we want to display 1-based to the user. 119 const columnVal = column ? `:${column + 1}` : ""; 120 const bpLocation = isWasm 121 ? `0x${line.toString(16).toUpperCase()}` 122 : `${line}${columnVal}`; 123 124 return bpLocation; 125 } 126 127 highlightText(text = "", editor) { 128 const htmlString = editor.highlightText(document, text); 129 return { __html: htmlString }; 130 } 131 132 render() { 133 const { breakpoint, editor, isBreakpointLineBlackboxed, breakpointText } = 134 this.props; 135 const labelId = `${breakpoint.id}-label`; 136 return div( 137 { 138 className: classnames({ 139 breakpoint, 140 paused: this.props.isCurrentlyPausedAtBreakpoint, 141 disabled: breakpoint.disabled, 142 "is-conditional": !!breakpoint.options.condition, 143 "is-log": !!breakpoint.options.logValue, 144 }), 145 onClick: this.selectBreakpoint, 146 onDoubleClick: this.onDoubleClick, 147 onContextMenu: this.onContextMenu, 148 onKeyDown: this.onKeyDown, 149 role: "button", 150 tabIndex: 0, 151 title: breakpointText, 152 }, 153 input({ 154 id: breakpoint.id, 155 type: "checkbox", 156 className: "breakpoint-checkbox", 157 checked: !breakpoint.disabled, 158 disabled: isBreakpointLineBlackboxed, 159 onChange: this.handleBreakpointCheckbox, 160 onClick: this.stopClicks, 161 "aria-labelledby": labelId, 162 }), 163 span( 164 { 165 id: labelId, 166 className: "breakpoint-label cm-s-mozilla devtools-monospace", 167 }, 168 span({ 169 className: "cm-highlighted", 170 dangerouslySetInnerHTML: this.highlightText(breakpointText, editor), 171 }) 172 ), 173 div( 174 { 175 className: "breakpoint-line-close", 176 }, 177 div( 178 { 179 className: "breakpoint-line devtools-monospace", 180 }, 181 this.getBreakpointLocation() 182 ), 183 React.createElement(CloseButton, { 184 handleClick: this.removeBreakpoint, 185 tooltip: L10N.getStr("breakpoints.removeBreakpointTooltip"), 186 }) 187 ) 188 ); 189 } 190 } 191 192 function isCurrentlyPausedAtBreakpoint( 193 state, 194 selectedBreakpointLocation, 195 selectedSource 196 ) { 197 const frame = getSelectedFrame(state); 198 if (!frame) { 199 return false; 200 } 201 const bpId = makeBreakpointId(selectedBreakpointLocation); 202 const frameId = makeBreakpointId(getSelectedLocation(frame, selectedSource)); 203 return bpId == frameId; 204 } 205 206 function getBreakpointText(breakpoint, selectedSource) { 207 const { condition, logValue } = breakpoint.options; 208 return logValue || condition || getSelectedText(breakpoint, selectedSource); 209 } 210 211 const mapStateToProps = (state, props) => { 212 const { breakpoint, source } = props; 213 const selectedSource = getSelectedSource(state); 214 const selectedBreakpointLocation = getSelectedLocation( 215 breakpoint, 216 selectedSource 217 ); 218 const blackboxedRangesForSource = getBlackBoxRanges(state)[source.url]; 219 const isSourceOnIgnoreList = 220 isSourceMapIgnoreListEnabled(state) && 221 isSourceOnSourceMapIgnoreList(state, source); 222 return { 223 selectedBreakpointLocation, 224 isCurrentlyPausedAtBreakpoint: isCurrentlyPausedAtBreakpoint( 225 state, 226 selectedBreakpointLocation, 227 selectedSource 228 ), 229 isBreakpointLineBlackboxed: isLineBlackboxed( 230 blackboxedRangesForSource, 231 breakpoint.location.line, 232 isSourceOnIgnoreList 233 ), 234 breakpointText: getBreakpointText(breakpoint, selectedSource), 235 }; 236 }; 237 238 export default connect(mapStateToProps, { 239 enableBreakpoint: actions.enableBreakpoint, 240 removeBreakpoint: actions.removeBreakpoint, 241 disableBreakpoint: actions.disableBreakpoint, 242 selectSpecificLocation: actions.selectSpecificLocation, 243 openConditionalPanel: actions.openConditionalPanel, 244 showBreakpointContextMenu: actions.showBreakpointContextMenu, 245 })(Breakpoint);