index.js (14412B)
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 SplitBox = require("resource://devtools/client/shared/components/splitter/SplitBox.js"); 6 7 import React, { Component } from "devtools/client/shared/vendor/react"; 8 import { 9 div, 10 input, 11 label, 12 button, 13 a, 14 } from "devtools/client/shared/vendor/react-dom-factories"; 15 import PropTypes from "devtools/client/shared/vendor/react-prop-types"; 16 import { connect } from "devtools/client/shared/vendor/react-redux"; 17 18 import actions from "../../actions/index"; 19 import { 20 getTopFrame, 21 getExpressions, 22 isMapScopesEnabled, 23 getSelectedFrame, 24 getSelectedSource, 25 getThreads, 26 getCurrentThread, 27 getPauseReason, 28 getShouldBreakpointsPaneOpenOnPause, 29 getSkipPausing, 30 shouldLogEventBreakpoints, 31 } from "../../selectors/index"; 32 33 import DebuggerImage from "../shared/DebuggerImage"; 34 import { prefs } from "../../utils/prefs"; 35 36 import Breakpoints from "./Breakpoints/index"; 37 import Expressions from "./Expressions"; 38 import Frames from "./Frames/index"; 39 import Threads from "./Threads"; 40 import Accordion from "../shared/Accordion"; 41 import CommandBar from "./CommandBar"; 42 import XHRBreakpoints from "./XHRBreakpoints"; 43 import EventListeners from "../shared/EventListeners"; 44 import DOMMutationBreakpoints from "./DOMMutationBreakpoints"; 45 import WhyPaused from "./WhyPaused"; 46 47 import Scopes from "./Scopes"; 48 49 const classnames = require("resource://devtools/client/shared/classnames.js"); 50 51 function debugBtn(onClick, type, className, tooltip) { 52 return button( 53 { 54 onClick, 55 className: `${type} ${className}`, 56 key: type, 57 title: tooltip, 58 }, 59 React.createElement(DebuggerImage, { 60 name: type, 61 title: tooltip, 62 "aria-label": tooltip, 63 }) 64 ); 65 } 66 67 const mdnLink = 68 "https://firefox-source-docs.mozilla.org/devtools-user/debugger/using_the_debugger_map_scopes_feature/"; 69 70 class SecondaryPanes extends Component { 71 constructor(props) { 72 super(props); 73 74 this.state = { 75 showExpressionsInput: false, 76 showXHRInput: false, 77 expandedFrameGroups: {}, 78 }; 79 } 80 81 static get propTypes() { 82 return { 83 evaluateExpressionsForCurrentContext: PropTypes.func.isRequired, 84 expressions: PropTypes.array.isRequired, 85 hasFrames: PropTypes.bool.isRequired, 86 horizontal: PropTypes.bool.isRequired, 87 logEventBreakpoints: PropTypes.bool.isRequired, 88 mapScopesEnabled: PropTypes.bool.isRequired, 89 pauseReason: PropTypes.string.isRequired, 90 shouldBreakpointsPaneOpenOnPause: PropTypes.bool.isRequired, 91 thread: PropTypes.string, 92 skipPausing: PropTypes.bool.isRequired, 93 showScopesButtons: PropTypes.bool.isRequired, 94 toggleEventLogging: PropTypes.func.isRequired, 95 resetBreakpointsPaneState: PropTypes.func.isRequired, 96 toggleMapScopes: PropTypes.func.isRequired, 97 showThreads: PropTypes.bool.isRequired, 98 removeAllBreakpoints: PropTypes.func.isRequired, 99 removeAllXHRBreakpoints: PropTypes.func.isRequired, 100 }; 101 } 102 103 onExpressionAdded = () => { 104 this.setState({ showExpressionsInput: false }); 105 }; 106 107 onXHRAdded = () => { 108 this.setState({ showXHRInput: false }); 109 }; 110 111 onExpandFrameGroup = expandedFrameGroups => { 112 this.setState({ 113 expandedFrameGroups: { ...expandedFrameGroups }, 114 }); 115 }; 116 117 watchExpressionHeaderButtons() { 118 const { expressions } = this.props; 119 const buttons = []; 120 121 if (expressions.length) { 122 buttons.push( 123 debugBtn( 124 () => { 125 this.props.evaluateExpressionsForCurrentContext(); 126 }, 127 "refresh", 128 "active", 129 L10N.getStr("watchExpressions.refreshButton") 130 ) 131 ); 132 } 133 buttons.push( 134 debugBtn( 135 () => { 136 if (!prefs.expressionsVisible) { 137 this.onWatchExpressionPaneToggle(true); 138 } 139 this.setState({ showExpressionsInput: true }); 140 }, 141 "plus", 142 "active", 143 L10N.getStr("expressions.placeholder2") 144 ) 145 ); 146 return buttons; 147 } 148 149 xhrBreakpointsHeaderButtons() { 150 return [ 151 debugBtn( 152 () => { 153 if (!prefs.xhrBreakpointsVisible) { 154 this.onXHRPaneToggle(true); 155 } 156 this.setState({ showXHRInput: true }); 157 }, 158 "plus", 159 "active", 160 L10N.getStr("xhrBreakpoints.label") 161 ), 162 163 debugBtn( 164 () => { 165 this.props.removeAllXHRBreakpoints(); 166 }, 167 "removeAll", 168 "active", 169 L10N.getStr("xhrBreakpoints.removeAll.tooltip") 170 ), 171 ]; 172 } 173 174 breakpointsHeaderButtons() { 175 return [ 176 debugBtn( 177 () => { 178 this.props.removeAllBreakpoints(); 179 }, 180 "removeAll", 181 "active", 182 L10N.getStr("breakpointMenuItem.deleteAll") 183 ), 184 ]; 185 } 186 187 getScopeItem() { 188 return { 189 header: L10N.getStr("scopes.header"), 190 className: "scopes-pane", 191 id: "scopes-pane", 192 component: React.createElement(Scopes, null), 193 opened: prefs.scopesVisible, 194 buttons: this.getScopesButtons(), 195 onToggle: opened => { 196 prefs.scopesVisible = opened; 197 }, 198 }; 199 } 200 201 getScopesButtons() { 202 if (!this.props.showScopesButtons) { 203 return null; 204 } 205 const { mapScopesEnabled } = this.props; 206 207 return [ 208 div( 209 { 210 key: "scopes-buttons", 211 }, 212 label( 213 { 214 className: "map-scopes-header", 215 title: L10N.getStr("scopes.showOriginalScopesTooltip"), 216 onClick: e => e.stopPropagation(), 217 }, 218 input({ 219 type: "checkbox", 220 checked: mapScopesEnabled ? "checked" : "", 221 onChange: () => this.props.toggleMapScopes(), 222 }), 223 L10N.getStr("scopes.showOriginalScopes") 224 ), 225 a( 226 { 227 className: "mdn", 228 target: "_blank", 229 href: mdnLink, 230 onClick: e => e.stopPropagation(), 231 title: L10N.getStr("scopes.showOriginalScopesHelpTooltip"), 232 }, 233 React.createElement(DebuggerImage, { 234 name: "shortcuts", 235 }) 236 ) 237 ), 238 ]; 239 } 240 241 getEventButtons() { 242 const { logEventBreakpoints } = this.props; 243 return [ 244 div( 245 { 246 key: "events-buttons", 247 }, 248 label( 249 { 250 className: "events-header", 251 title: L10N.getStr("eventlisteners.log.label"), 252 }, 253 input({ 254 type: "checkbox", 255 checked: logEventBreakpoints ? "checked" : "", 256 onChange: () => this.props.toggleEventLogging(), 257 }), 258 L10N.getStr("eventlisteners.log") 259 ) 260 ), 261 ]; 262 } 263 264 onWatchExpressionPaneToggle(opened) { 265 prefs.expressionsVisible = opened; 266 } 267 268 getWatchItem() { 269 return { 270 header: L10N.getStr("watchExpressions.header"), 271 id: "watch-expressions-pane", 272 className: "watch-expressions-pane", 273 buttons: this.watchExpressionHeaderButtons(), 274 component: React.createElement(Expressions, { 275 showInput: this.state.showExpressionsInput, 276 onExpressionAdded: this.onExpressionAdded, 277 }), 278 opened: prefs.expressionsVisible, 279 onToggle: this.onWatchExpressionPaneToggle, 280 }; 281 } 282 283 onXHRPaneToggle(opened) { 284 prefs.xhrBreakpointsVisible = opened; 285 } 286 287 getXHRItem() { 288 const { pauseReason } = this.props; 289 290 return { 291 header: L10N.getStr("xhrBreakpoints.header"), 292 id: "xhr-breakpoints-pane", 293 className: "xhr-breakpoints-pane", 294 buttons: this.xhrBreakpointsHeaderButtons(), 295 component: React.createElement(XHRBreakpoints, { 296 showInput: this.state.showXHRInput, 297 onXHRAdded: this.onXHRAdded, 298 }), 299 opened: prefs.xhrBreakpointsVisible || pauseReason === "XHR", 300 onToggle: this.onXHRPaneToggle, 301 }; 302 } 303 304 getCallStackItem() { 305 return { 306 header: L10N.getStr("callStack.header"), 307 id: "call-stack-pane", 308 className: "call-stack-pane", 309 component: React.createElement(Frames, { 310 panel: "debugger", 311 // These props enable storing and using the current expanded state 312 // of the frame groups. This is we always handle displaying selected frames 313 // in groups correctly. 314 onExpandFrameGroup: this.onExpandFrameGroup, 315 expandedFrameGroups: this.state.expandedFrameGroups, 316 }), 317 opened: prefs.callStackVisible, 318 onToggle: opened => { 319 prefs.callStackVisible = opened; 320 }, 321 }; 322 } 323 324 getThreadsItem() { 325 return { 326 header: L10N.getStr("threadsHeader"), 327 id: "threads-pane", 328 className: "threads-pane", 329 component: React.createElement(Threads, null), 330 opened: prefs.threadsVisible, 331 onToggle: opened => { 332 prefs.threadsVisible = opened; 333 }, 334 }; 335 } 336 337 getBreakpointsItem() { 338 const { pauseReason, shouldBreakpointsPaneOpenOnPause, thread } = 339 this.props; 340 341 return { 342 header: L10N.getStr("breakpoints.header"), 343 id: "breakpoints-pane", 344 className: "breakpoints-pane", 345 buttons: this.breakpointsHeaderButtons(), 346 component: React.createElement(Breakpoints), 347 opened: 348 prefs.breakpointsVisible || 349 (pauseReason === "breakpoint" && shouldBreakpointsPaneOpenOnPause), 350 onToggle: opened => { 351 prefs.breakpointsVisible = opened; 352 // one-shot flag used to force open the Breakpoints Pane only 353 // when hitting a breakpoint, but not when selecting frames etc... 354 if (shouldBreakpointsPaneOpenOnPause) { 355 this.props.resetBreakpointsPaneState(thread); 356 } 357 }, 358 }; 359 } 360 361 getEventListenersItem() { 362 const { pauseReason } = this.props; 363 364 return { 365 header: L10N.getStr("eventListenersHeader1"), 366 id: "event-listeners-pane", 367 className: "event-listeners-pane", 368 buttons: this.getEventButtons(), 369 component: React.createElement(EventListeners, { 370 panelKey: "breakpoint", 371 }), 372 opened: prefs.eventListenersVisible || pauseReason === "eventBreakpoint", 373 onToggle: opened => { 374 prefs.eventListenersVisible = opened; 375 }, 376 }; 377 } 378 379 getDOMMutationsItem() { 380 const { pauseReason } = this.props; 381 382 return { 383 header: L10N.getStr("domMutationHeader"), 384 id: "dom-mutations-pane", 385 className: "dom-mutations-pane", 386 buttons: [], 387 component: React.createElement(DOMMutationBreakpoints, null), 388 opened: 389 prefs.domMutationBreakpointsVisible || 390 pauseReason === "mutationBreakpoint", 391 onToggle: opened => { 392 prefs.domMutationBreakpointsVisible = opened; 393 }, 394 }; 395 } 396 397 getStartItems() { 398 const items = []; 399 const { horizontal, hasFrames } = this.props; 400 401 if (horizontal) { 402 if (this.props.showThreads) { 403 items.push(this.getThreadsItem()); 404 } 405 406 items.push(this.getWatchItem()); 407 } 408 409 items.push(this.getBreakpointsItem()); 410 411 if (hasFrames) { 412 items.push(this.getCallStackItem()); 413 if (horizontal) { 414 items.push(this.getScopeItem()); 415 } 416 } 417 418 items.push(this.getXHRItem()); 419 420 items.push(this.getEventListenersItem()); 421 422 items.push(this.getDOMMutationsItem()); 423 424 return items; 425 } 426 427 getEndItems() { 428 if (this.props.horizontal) { 429 return []; 430 } 431 432 const items = []; 433 if (this.props.showThreads) { 434 items.push(this.getThreadsItem()); 435 } 436 437 items.push(this.getWatchItem()); 438 439 if (this.props.hasFrames) { 440 items.push(this.getScopeItem()); 441 } 442 443 return items; 444 } 445 446 getItems() { 447 return [...this.getStartItems(), ...this.getEndItems()]; 448 } 449 450 renderHorizontalLayout() { 451 return div( 452 null, 453 React.createElement(WhyPaused), 454 React.createElement(Accordion, { 455 items: this.getItems(), 456 }) 457 ); 458 } 459 460 renderVerticalLayout() { 461 return React.createElement(SplitBox, { 462 initialSize: "300px", 463 minSize: 10, 464 maxSize: "50%", 465 splitterSize: 1, 466 startPanel: div( 467 { 468 style: { 469 width: "inherit", 470 }, 471 }, 472 React.createElement(WhyPaused), 473 React.createElement(Accordion, { 474 items: this.getStartItems(), 475 }) 476 ), 477 endPanel: React.createElement(Accordion, { 478 items: this.getEndItems(), 479 }), 480 }); 481 } 482 483 render() { 484 const { skipPausing } = this.props; 485 return div( 486 { 487 className: "secondary-panes-wrapper", 488 }, 489 React.createElement(CommandBar, { 490 horizontal: this.props.horizontal, 491 }), 492 React.createElement( 493 "div", 494 { 495 className: classnames( 496 "secondary-panes", 497 skipPausing && "skip-pausing" 498 ), 499 }, 500 this.props.horizontal 501 ? this.renderHorizontalLayout() 502 : this.renderVerticalLayout() 503 ) 504 ); 505 } 506 } 507 508 const mapStateToProps = state => { 509 const thread = getCurrentThread(state); 510 const selectedFrame = getSelectedFrame(state); 511 const selectedSource = getSelectedSource(state); 512 const pauseReason = getPauseReason(state, thread); 513 const shouldBreakpointsPaneOpenOnPause = getShouldBreakpointsPaneOpenOnPause( 514 state, 515 thread 516 ); 517 518 return { 519 expressions: getExpressions(state), 520 hasFrames: !!getTopFrame(state, thread), 521 mapScopesEnabled: isMapScopesEnabled(state), 522 showThreads: !!getThreads(state).length, 523 skipPausing: getSkipPausing(state), 524 logEventBreakpoints: shouldLogEventBreakpoints(state), 525 showScopesButtons: 526 selectedFrame && 527 selectedSource && 528 selectedSource.isOriginal && 529 !selectedSource.isPrettyPrinted, 530 pauseReason: pauseReason?.type ?? "", 531 shouldBreakpointsPaneOpenOnPause, 532 thread, 533 }; 534 }; 535 536 export default connect(mapStateToProps, { 537 evaluateExpressionsForCurrentContext: 538 actions.evaluateExpressionsForCurrentContext, 539 toggleMapScopes: actions.toggleMapScopes, 540 breakOnNext: actions.breakOnNext, 541 toggleEventLogging: actions.toggleEventLogging, 542 removeAllBreakpoints: actions.removeAllBreakpoints, 543 removeAllXHRBreakpoints: actions.removeAllXHRBreakpoints, 544 resetBreakpointsPaneState: actions.resetBreakpointsPaneState, 545 })(SecondaryPanes);