WhyPaused.js (7379B)
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 const { 6 LocalizationProvider, 7 Localized, 8 } = require("resource://devtools/client/shared/vendor/fluent-react.js"); 9 10 import React, { PureComponent } from "devtools/client/shared/vendor/react"; 11 import { div, span } from "devtools/client/shared/vendor/react-dom-factories"; 12 import PropTypes from "devtools/client/shared/vendor/react-prop-types"; 13 import { connect } from "devtools/client/shared/vendor/react-redux"; 14 import DebuggerImage from "../shared/DebuggerImage"; 15 import actions from "../../actions/index"; 16 17 const Reps = ChromeUtils.importESModule( 18 "resource://devtools/client/shared/components/reps/index.mjs" 19 ); 20 const { 21 REPS: { Rep }, 22 MODE, 23 } = Reps; 24 25 import { getPauseReason } from "../../utils/pause/index"; 26 import { 27 getCurrentThread, 28 getPauseCommand, 29 getPaneCollapse, 30 getPauseReason as getWhy, 31 getVisibleSelectedFrame, 32 } from "../../selectors/index"; 33 34 const classnames = require("resource://devtools/client/shared/classnames.js"); 35 36 class WhyPaused extends PureComponent { 37 constructor(props) { 38 super(props); 39 this.state = { hideWhyPaused: true }; 40 } 41 42 static get propTypes() { 43 return { 44 delay: PropTypes.number.isRequired, 45 endPanelCollapsed: PropTypes.bool.isRequired, 46 highlightDomElement: PropTypes.func.isRequired, 47 openElementInInspector: PropTypes.func.isRequired, 48 unHighlightDomElement: PropTypes.func.isRequired, 49 why: PropTypes.object, 50 }; 51 } 52 53 componentDidUpdate() { 54 const { delay } = this.props; 55 56 if (delay) { 57 setTimeout(() => { 58 this.setState({ hideWhyPaused: true }); 59 }, delay); 60 } else { 61 this.setState({ hideWhyPaused: false }); 62 } 63 } 64 65 renderExceptionSummary(exception) { 66 if (typeof exception === "string") { 67 return exception; 68 } 69 70 const { preview } = exception; 71 if (!preview || !preview.name || !preview.message) { 72 return null; 73 } 74 75 return `${preview.name}: ${preview.message}`; 76 } 77 78 renderMessage(why) { 79 const { type, exception, message } = why; 80 81 if (type == "exception" && exception) { 82 // Our types for 'Why' are too general because 'type' can be 'string'. 83 // $FlowFixMe - We should have a proper discriminating union of reasons. 84 const summary = this.renderExceptionSummary(exception); 85 return div( 86 { 87 className: "message error", 88 }, 89 summary 90 ); 91 } 92 93 if (type === "mutationBreakpoint" && why.nodeGrip) { 94 const { nodeGrip, ancestorGrip, action } = why; 95 const { 96 openElementInInspector, 97 highlightDomElement, 98 unHighlightDomElement, 99 } = this.props; 100 101 const targetRep = Rep({ 102 object: nodeGrip, 103 mode: MODE.TINY, 104 onDOMNodeClick: () => openElementInInspector(nodeGrip), 105 onInspectIconClick: () => openElementInInspector(nodeGrip), 106 onDOMNodeMouseOver: () => highlightDomElement(nodeGrip), 107 onDOMNodeMouseOut: () => unHighlightDomElement(), 108 }); 109 110 const ancestorRep = ancestorGrip 111 ? Rep({ 112 object: ancestorGrip, 113 mode: MODE.TINY, 114 onDOMNodeClick: () => openElementInInspector(ancestorGrip), 115 onInspectIconClick: () => openElementInInspector(ancestorGrip), 116 onDOMNodeMouseOver: () => highlightDomElement(ancestorGrip), 117 onDOMNodeMouseOut: () => unHighlightDomElement(), 118 }) 119 : null; 120 return div( 121 null, 122 div( 123 { 124 className: "message", 125 }, 126 why.message 127 ), 128 div( 129 { 130 className: "mutationNode", 131 }, 132 ancestorRep, 133 ancestorGrip 134 ? span( 135 { 136 className: "why-paused-ancestor", 137 }, 138 React.createElement(Localized, { 139 id: 140 action === "remove" 141 ? "whypaused-mutation-breakpoint-removed" 142 : "whypaused-mutation-breakpoint-added", 143 }), 144 targetRep 145 ) 146 : targetRep 147 ) 148 ); 149 } 150 151 if (typeof message == "string") { 152 return div( 153 { 154 className: "message", 155 }, 156 message 157 ); 158 } 159 160 return null; 161 } 162 163 renderLocation() { 164 const { visibleSelectedFrame } = this.props; 165 if (!visibleSelectedFrame || !visibleSelectedFrame.location?.source) { 166 return null; 167 } 168 const { location, displayName } = visibleSelectedFrame; 169 let pauseLocation = ""; 170 if (visibleSelectedFrame.displayName) { 171 pauseLocation += `${displayName} - `; 172 } 173 pauseLocation += `${location.source.displayURL?.filename}:${location.line}:${location.column}`; 174 return div({ className: "location" }, pauseLocation); 175 } 176 177 render() { 178 const { endPanelCollapsed, why } = this.props; 179 const { fluentBundles } = this.context; 180 const reason = getPauseReason(why); 181 182 let content = ""; 183 if (!why || !reason) { 184 if (this.state.hideWhyPaused) { 185 content = null; 186 } 187 } else { 188 content = div( 189 null, 190 div( 191 { 192 className: "info icon", 193 }, 194 React.createElement(DebuggerImage, { 195 name: "info", 196 }) 197 ), 198 div( 199 { 200 className: "pause reason", 201 }, 202 div( 203 {}, 204 React.createElement(Localized, { 205 id: reason, 206 }) 207 ), 208 this.renderLocation(), 209 this.renderMessage(why) 210 ) 211 ); 212 } 213 214 return ( 215 // We're rendering the LocalizationProvider component from here and not in an upper 216 // component because it does set a new context, overriding the context that we set 217 // in the first place in <App>, which breaks some components. 218 // This should be fixed in Bug 1743155. 219 React.createElement( 220 LocalizationProvider, 221 { 222 bundles: fluentBundles || [], 223 }, 224 // Always render the component so the live region works as expected 225 div( 226 { 227 className: classnames("pane why-paused", { 228 hidden: content == null || endPanelCollapsed, 229 }), 230 "aria-live": "polite", 231 }, 232 content 233 ) 234 ) 235 ); 236 } 237 } 238 239 WhyPaused.contextTypes = { fluentBundles: PropTypes.array }; 240 241 // Checks if user is in debugging mode and adds a delay preventing 242 // excessive vertical 'jumpiness' 243 function getDelay(state, thread) { 244 const inPauseCommand = !!getPauseCommand(state, thread); 245 246 if (!inPauseCommand) { 247 return 100; 248 } 249 250 return 0; 251 } 252 253 const mapStateToProps = state => { 254 const thread = getCurrentThread(state); 255 256 return { 257 delay: getDelay(state, thread), 258 endPanelCollapsed: getPaneCollapse(state, "end"), 259 why: getWhy(state, thread), 260 visibleSelectedFrame: getVisibleSelectedFrame(state), 261 }; 262 }; 263 264 export default connect(mapStateToProps, { 265 openElementInInspector: actions.openElementInInspectorCommand, 266 highlightDomElement: actions.highlightDomElement, 267 unHighlightDomElement: actions.unHighlightDomElement, 268 })(WhyPaused);