Scopes.js (11692B)
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 button, 9 span, 10 } from "devtools/client/shared/vendor/react-dom-factories"; 11 import PropTypes from "devtools/client/shared/vendor/react-prop-types"; 12 import DebuggerImage from "../shared/DebuggerImage"; 13 import { showMenu } from "../../context-menu/menu"; 14 import { connect } from "devtools/client/shared/vendor/react-redux"; 15 import actions from "../../actions/index"; 16 17 import { 18 getSelectedFrame, 19 getSelectedSource, 20 getGeneratedFrameScope, 21 getOriginalFrameScope, 22 getPauseReason, 23 isMapScopesEnabled, 24 getLastExpandedScopes, 25 getIsCurrentThreadPaused, 26 } from "../../selectors/index"; 27 import { 28 getScopesItemsForSelectedFrame, 29 getScopeItemPath, 30 } from "../../utils/pause/scopes"; 31 import { clientCommands } from "../../client/firefox"; 32 33 import * as objectInspector from "resource://devtools/client/shared/components/object-inspector/index.js"; 34 const { ObjectInspector } = objectInspector; 35 36 class Scopes extends PureComponent { 37 constructor(props) { 38 const { why, selectedFrame, originalFrameScopes, generatedFrameScopes } = 39 props; 40 41 super(props); 42 43 this.state = { 44 originalScopes: getScopesItemsForSelectedFrame( 45 why, 46 selectedFrame, 47 originalFrameScopes 48 ), 49 generatedScopes: getScopesItemsForSelectedFrame( 50 why, 51 selectedFrame, 52 generatedFrameScopes 53 ), 54 }; 55 } 56 57 static get propTypes() { 58 return { 59 addWatchpoint: PropTypes.func.isRequired, 60 expandedScopes: PropTypes.array.isRequired, 61 generatedFrameScopes: PropTypes.object, 62 highlightDomElement: PropTypes.func.isRequired, 63 isLoading: PropTypes.bool.isRequired, 64 isPaused: PropTypes.bool.isRequired, 65 mapScopesEnabled: PropTypes.bool.isRequired, 66 openElementInInspector: PropTypes.func.isRequired, 67 openLink: PropTypes.func.isRequired, 68 originalFrameScopes: PropTypes.object, 69 removeWatchpoint: PropTypes.func.isRequired, 70 setExpandedScope: PropTypes.func.isRequired, 71 unHighlightDomElement: PropTypes.func.isRequired, 72 why: PropTypes.object.isRequired, 73 selectedFrame: PropTypes.object, 74 toggleMapScopes: PropTypes.func.isRequired, 75 selectedSource: PropTypes.object.isRequired, 76 }; 77 } 78 79 // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 80 UNSAFE_componentWillReceiveProps(nextProps) { 81 const { 82 selectedFrame, 83 originalFrameScopes, 84 generatedFrameScopes, 85 isPaused, 86 selectedSource, 87 } = this.props; 88 const isPausedChanged = isPaused !== nextProps.isPaused; 89 const selectedFrameChanged = selectedFrame !== nextProps.selectedFrame; 90 const originalFrameScopesChanged = 91 originalFrameScopes !== nextProps.originalFrameScopes; 92 const generatedFrameScopesChanged = 93 generatedFrameScopes !== nextProps.generatedFrameScopes; 94 const selectedSourceChanged = selectedSource !== nextProps.selectedSource; 95 96 if ( 97 isPausedChanged || 98 selectedFrameChanged || 99 originalFrameScopesChanged || 100 generatedFrameScopesChanged || 101 selectedSourceChanged 102 ) { 103 this.setState({ 104 originalScopes: getScopesItemsForSelectedFrame( 105 nextProps.why, 106 nextProps.selectedFrame, 107 nextProps.originalFrameScopes 108 ), 109 generatedScopes: getScopesItemsForSelectedFrame( 110 nextProps.why, 111 nextProps.selectedFrame, 112 nextProps.generatedFrameScopes 113 ), 114 }); 115 } 116 } 117 118 onContextMenu = (event, item) => { 119 const { addWatchpoint, removeWatchpoint } = this.props; 120 121 if (!item.parent || !item.contents.configurable) { 122 return; 123 } 124 125 if (!item.contents || item.contents.watchpoint) { 126 const removeWatchpointLabel = L10N.getStr("watchpoints.removeWatchpoint"); 127 128 const removeWatchpointItem = { 129 id: "node-menu-remove-watchpoint", 130 label: removeWatchpointLabel, 131 disabled: false, 132 click: () => removeWatchpoint(item), 133 }; 134 135 const menuItems = [removeWatchpointItem]; 136 showMenu(event, menuItems); 137 return; 138 } 139 140 const addSetWatchpointLabel = L10N.getStr("watchpoints.setWatchpoint"); 141 const addGetWatchpointLabel = L10N.getStr("watchpoints.getWatchpoint"); 142 const addGetOrSetWatchpointLabel = L10N.getStr( 143 "watchpoints.getOrSetWatchpoint" 144 ); 145 const watchpointsSubmenuLabel = L10N.getStr("watchpoints.submenu"); 146 147 const addSetWatchpointItem = { 148 id: "node-menu-add-set-watchpoint", 149 label: addSetWatchpointLabel, 150 disabled: false, 151 click: () => addWatchpoint(item, "set"), 152 }; 153 154 const addGetWatchpointItem = { 155 id: "node-menu-add-get-watchpoint", 156 label: addGetWatchpointLabel, 157 disabled: false, 158 click: () => addWatchpoint(item, "get"), 159 }; 160 161 const addGetOrSetWatchpointItem = { 162 id: "node-menu-add-get-watchpoint", 163 label: addGetOrSetWatchpointLabel, 164 disabled: false, 165 click: () => addWatchpoint(item, "getorset"), 166 }; 167 168 const watchpointsSubmenuItem = { 169 id: "node-menu-watchpoints", 170 label: watchpointsSubmenuLabel, 171 disabled: false, 172 click: () => addWatchpoint(item, "set"), 173 submenu: [ 174 addSetWatchpointItem, 175 addGetWatchpointItem, 176 addGetOrSetWatchpointItem, 177 ], 178 }; 179 180 const menuItems = [watchpointsSubmenuItem]; 181 showMenu(event, menuItems); 182 }; 183 184 renderWatchpointButton = item => { 185 const { removeWatchpoint } = this.props; 186 187 if ( 188 !item || 189 !item.contents || 190 !item.contents.watchpoint || 191 typeof L10N === "undefined" 192 ) { 193 return null; 194 } 195 196 const { watchpoint } = item.contents; 197 return button({ 198 className: `remove-watchpoint-${watchpoint}`, 199 title: L10N.getStr("watchpoints.removeWatchpointTooltip"), 200 onClick: e => { 201 e.stopPropagation(); 202 removeWatchpoint(item); 203 }, 204 }); 205 }; 206 207 renderScopesList() { 208 const { 209 isLoading, 210 openLink, 211 openElementInInspector, 212 highlightDomElement, 213 unHighlightDomElement, 214 mapScopesEnabled, 215 selectedFrame, 216 setExpandedScope, 217 expandedScopes, 218 selectedSource, 219 } = this.props; 220 221 if (!selectedSource) { 222 return div( 223 { className: "pane scopes-list" }, 224 div({ className: "pane-info" }, L10N.getStr("scopes.notAvailable")) 225 ); 226 } 227 228 const { originalScopes, generatedScopes } = this.state; 229 let scopes = null; 230 231 if ( 232 selectedSource.isOriginal && 233 !selectedSource.isPrettyPrinted && 234 !selectedFrame.generatedLocation?.source.isWasm 235 ) { 236 if (!mapScopesEnabled) { 237 return div( 238 { className: "pane scopes-list" }, 239 div( 240 { 241 className: "pane-info no-original-scopes-info", 242 "aria-role": "status", 243 }, 244 span( 245 { className: "info icon" }, 246 React.createElement(DebuggerImage, { name: "sourcemap" }) 247 ), 248 L10N.getFormatStr( 249 "scopes.noOriginalScopes", 250 L10N.getStr("scopes.showOriginalScopes") 251 ) 252 ) 253 ); 254 } 255 if (isLoading) { 256 return div( 257 { 258 className: "pane scopes-list", 259 }, 260 div( 261 { className: "pane-info" }, 262 span( 263 { className: "info icon" }, 264 React.createElement(DebuggerImage, { name: "loader" }) 265 ), 266 L10N.getStr("scopes.loadingOriginalScopes") 267 ) 268 ); 269 } 270 scopes = originalScopes; 271 } else { 272 if (isLoading) { 273 return div( 274 { 275 className: "pane scopes-list", 276 }, 277 div( 278 { className: "pane-info" }, 279 span( 280 { className: "info icon" }, 281 React.createElement(DebuggerImage, { name: "loader" }) 282 ), 283 L10N.getStr("loadingText") 284 ) 285 ); 286 } 287 scopes = generatedScopes; 288 } 289 290 function getInitiallyExpanded(item) { 291 return expandedScopes.some(path => path == getScopeItemPath(item)); 292 } 293 294 if (scopes && !!scopes.length) { 295 return div( 296 { 297 className: "pane scopes-list", 298 }, 299 React.createElement(ObjectInspector, { 300 roots: scopes, 301 autoExpandAll: false, 302 autoExpandDepth: 1, 303 client: clientCommands, 304 createElement: tagName => document.createElement(tagName), 305 disableWrap: true, 306 dimTopLevelWindow: true, 307 frame: selectedFrame, 308 mayUseCustomFormatter: true, 309 openLink, 310 onDOMNodeClick: grip => openElementInInspector(grip), 311 onInspectIconClick: grip => openElementInInspector(grip), 312 onDOMNodeMouseOver: grip => highlightDomElement(grip), 313 onDOMNodeMouseOut: grip => unHighlightDomElement(grip), 314 onContextMenu: this.onContextMenu, 315 preventBlur: true, 316 setExpanded: (path, expand) => 317 setExpandedScope(selectedFrame, path, expand), 318 getInitiallyExpanded, 319 renderItemActions: this.renderWatchpointButton, 320 shouldRenderTooltip: true, 321 }) 322 ); 323 } 324 325 return div( 326 { 327 className: "pane scopes-list", 328 }, 329 div( 330 { 331 className: "pane-info", 332 }, 333 L10N.getStr("scopes.notAvailable") 334 ) 335 ); 336 } 337 338 render() { 339 return div( 340 { 341 className: "scopes-content", 342 }, 343 this.renderScopesList() 344 ); 345 } 346 } 347 348 const mapStateToProps = state => { 349 // This component doesn't need any prop when we are not paused 350 const selectedFrame = getSelectedFrame(state); 351 if (!selectedFrame) { 352 return {}; 353 } 354 const why = getPauseReason(state, selectedFrame.thread); 355 const expandedScopes = getLastExpandedScopes(state, selectedFrame.thread); 356 const isPaused = getIsCurrentThreadPaused(state); 357 const selectedSource = getSelectedSource(state); 358 359 let originalFrameScopes; 360 let generatedFrameScopes; 361 let isLoading; 362 let mapScopesEnabled = false; 363 364 if ( 365 selectedSource?.isOriginal && 366 !selectedSource?.isPrettyPrinted && 367 !selectedFrame.generatedLocation?.source.isWasm 368 ) { 369 const { scope, pending: originalPending } = getOriginalFrameScope( 370 state, 371 selectedFrame 372 ) || { 373 scope: null, 374 pending: false, 375 }; 376 originalFrameScopes = scope; 377 isLoading = originalPending; 378 mapScopesEnabled = isMapScopesEnabled(state); 379 } else { 380 const { scope, pending: generatedPending } = getGeneratedFrameScope( 381 state, 382 selectedFrame 383 ) || { 384 scope: null, 385 pending: false, 386 }; 387 generatedFrameScopes = scope; 388 isLoading = generatedPending; 389 } 390 391 return { 392 originalFrameScopes, 393 generatedFrameScopes, 394 mapScopesEnabled, 395 selectedFrame, 396 isLoading, 397 why, 398 expandedScopes, 399 isPaused, 400 selectedSource, 401 }; 402 }; 403 404 export default connect(mapStateToProps, { 405 openLink: actions.openLink, 406 openElementInInspector: actions.openElementInInspectorCommand, 407 highlightDomElement: actions.highlightDomElement, 408 unHighlightDomElement: actions.unHighlightDomElement, 409 setExpandedScope: actions.setExpandedScope, 410 addWatchpoint: actions.addWatchpoint, 411 removeWatchpoint: actions.removeWatchpoint, 412 toggleMapScopes: actions.toggleMapScopes, 413 })(Scopes);