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/>. */
5 const SplitBox = require("devtools/client/shared/components/splitter/SplitBox");
7 import React, { Component } from "react";
8 import { div, input, label, button, a } from "react-dom-factories";
9 import PropTypes from "prop-types";
10 import { connect } from "../../utils/connect";
12 import actions from "../../actions";
23 getShouldBreakpointsPaneOpenOnPause,
25 shouldLogEventBreakpoints,
26 } from "../../selectors";
28 import AccessibleImage from "../shared/AccessibleImage";
29 import { prefs } from "../../utils/prefs";
31 import Breakpoints from "./Breakpoints";
32 import Expressions from "./Expressions";
33 import Frames from "./Frames";
34 import Threads from "./Threads";
35 import Accordion from "../shared/Accordion";
36 import CommandBar from "./CommandBar";
37 import XHRBreakpoints from "./XHRBreakpoints";
38 import EventListeners from "./EventListeners";
39 import DOMMutationBreakpoints from "./DOMMutationBreakpoints";
40 import WhyPaused from "./WhyPaused";
42 import Scopes from "./Scopes";
44 const classnames = require("devtools/client/shared/classnames.js");
46 import "./SecondaryPanes.css";
48 function debugBtn(onClick, type, className, tooltip) {
52 className: `${type} ${className}`,
56 React.createElement(AccessibleImage, {
59 "aria-label": tooltip,
65 "https://firefox-source-docs.mozilla.org/devtools-user/debugger/using_the_debugger_map_scopes_feature/";
67 class SecondaryPanes extends Component {
72 showExpressionsInput: false,
77 static get propTypes() {
79 evaluateExpressionsForCurrentContext: PropTypes.func.isRequired,
80 expressions: PropTypes.array.isRequired,
81 hasFrames: PropTypes.bool.isRequired,
82 horizontal: PropTypes.bool.isRequired,
83 logEventBreakpoints: PropTypes.bool.isRequired,
84 mapScopesEnabled: PropTypes.bool.isRequired,
85 pauseReason: PropTypes.string.isRequired,
86 shouldBreakpointsPaneOpenOnPause: PropTypes.bool.isRequired,
87 thread: PropTypes.string.isRequired,
88 renderWhyPauseDelay: PropTypes.number.isRequired,
89 selectedFrame: PropTypes.object,
90 skipPausing: PropTypes.bool.isRequired,
91 source: PropTypes.object,
92 toggleEventLogging: PropTypes.func.isRequired,
93 resetBreakpointsPaneState: PropTypes.func.isRequired,
94 toggleMapScopes: PropTypes.func.isRequired,
95 threads: PropTypes.array.isRequired,
96 removeAllBreakpoints: PropTypes.func.isRequired,
97 removeAllXHRBreakpoints: PropTypes.func.isRequired,
101 onExpressionAdded = () => {
102 this.setState({ showExpressionsInput: false });
106 this.setState({ showXHRInput: false });
109 watchExpressionHeaderButtons() {
110 const { expressions } = this.props;
113 if (expressions.length) {
117 this.props.evaluateExpressionsForCurrentContext();
121 L10N.getStr("watchExpressions.refreshButton")
128 if (!prefs.expressionsVisible) {
129 this.onWatchExpressionPaneToggle(true);
131 this.setState({ showExpressionsInput: true });
135 L10N.getStr("expressions.placeholder")
141 xhrBreakpointsHeaderButtons() {
145 if (!prefs.xhrBreakpointsVisible) {
146 this.onXHRPaneToggle(true);
148 this.setState({ showXHRInput: true });
152 L10N.getStr("xhrBreakpoints.label")
157 this.props.removeAllXHRBreakpoints();
161 L10N.getStr("xhrBreakpoints.removeAll.tooltip")
166 breakpointsHeaderButtons() {
170 this.props.removeAllBreakpoints();
174 L10N.getStr("breakpointMenuItem.deleteAll")
181 header: L10N.getStr("scopes.header"),
182 className: "scopes-pane",
183 component: React.createElement(Scopes, null),
184 opened: prefs.scopesVisible,
185 buttons: this.getScopesButtons(),
186 onToggle: opened => {
187 prefs.scopesVisible = opened;
193 const { selectedFrame, mapScopesEnabled, source } = this.props;
195 if (!selectedFrame || !source?.isOriginal || source?.isPrettyPrinted) {
202 key: "scopes-buttons",
206 className: "map-scopes-header",
207 title: L10N.getStr("scopes.showOriginalScopesTooltip"),
208 onClick: e => e.stopPropagation(),
212 checked: mapScopesEnabled ? "checked" : "",
213 onChange: e => this.props.toggleMapScopes(),
215 L10N.getStr("scopes.showOriginalScopes")
222 onClick: e => e.stopPropagation(),
223 title: L10N.getStr("scopes.showOriginalScopesHelpTooltip"),
225 React.createElement(AccessibleImage, {
226 className: "shortcuts",
234 const { logEventBreakpoints } = this.props;
238 key: "events-buttons",
242 className: "events-header",
243 title: L10N.getStr("eventlisteners.log.label"),
247 checked: logEventBreakpoints ? "checked" : "",
248 onChange: e => this.props.toggleEventLogging(),
250 L10N.getStr("eventlisteners.log")
256 onWatchExpressionPaneToggle(opened) {
257 prefs.expressionsVisible = opened;
262 header: L10N.getStr("watchExpressions.header"),
263 id: "watch-expressions-pane",
264 className: "watch-expressions-pane",
265 buttons: this.watchExpressionHeaderButtons(),
266 component: React.createElement(Expressions, {
267 showInput: this.state.showExpressionsInput,
268 onExpressionAdded: this.onExpressionAdded,
270 opened: prefs.expressionsVisible,
271 onToggle: this.onWatchExpressionPaneToggle,
275 onXHRPaneToggle(opened) {
276 prefs.xhrBreakpointsVisible = opened;
280 const { pauseReason } = this.props;
283 header: L10N.getStr("xhrBreakpoints.header"),
284 id: "xhr-breakpoints-pane",
285 className: "xhr-breakpoints-pane",
286 buttons: this.xhrBreakpointsHeaderButtons(),
287 component: React.createElement(XHRBreakpoints, {
288 showInput: this.state.showXHRInput,
289 onXHRAdded: this.onXHRAdded,
291 opened: prefs.xhrBreakpointsVisible || pauseReason === "XHR",
292 onToggle: this.onXHRPaneToggle,
298 header: L10N.getStr("callStack.header"),
299 id: "call-stack-pane",
300 className: "call-stack-pane",
301 component: React.createElement(Frames, {
304 opened: prefs.callStackVisible,
305 onToggle: opened => {
306 prefs.callStackVisible = opened;
313 header: L10N.getStr("threadsHeader"),
315 className: "threads-pane",
316 component: React.createElement(Threads, null),
317 opened: prefs.threadsVisible,
318 onToggle: opened => {
319 prefs.threadsVisible = opened;
324 getBreakpointsItem() {
325 const { pauseReason, shouldBreakpointsPaneOpenOnPause, thread } =
329 header: L10N.getStr("breakpoints.header"),
330 id: "breakpoints-pane",
331 className: "breakpoints-pane",
332 buttons: this.breakpointsHeaderButtons(),
333 component: React.createElement(Breakpoints),
335 prefs.breakpointsVisible ||
336 (pauseReason === "breakpoint" && shouldBreakpointsPaneOpenOnPause),
337 onToggle: opened => {
338 prefs.breakpointsVisible = opened;
339 // one-shot flag used to force open the Breakpoints Pane only
340 // when hitting a breakpoint, but not when selecting frames etc...
341 if (shouldBreakpointsPaneOpenOnPause) {
342 this.props.resetBreakpointsPaneState(thread);
348 getEventListenersItem() {
349 const { pauseReason } = this.props;
352 header: L10N.getStr("eventListenersHeader1"),
353 id: "event-listeners-pane",
354 className: "event-listeners-pane",
355 buttons: this.getEventButtons(),
356 component: React.createElement(EventListeners, null),
357 opened: prefs.eventListenersVisible || pauseReason === "eventBreakpoint",
358 onToggle: opened => {
359 prefs.eventListenersVisible = opened;
364 getDOMMutationsItem() {
365 const { pauseReason } = this.props;
368 header: L10N.getStr("domMutationHeader"),
369 id: "dom-mutations-pane",
370 className: "dom-mutations-pane",
372 component: React.createElement(DOMMutationBreakpoints, null),
374 prefs.domMutationBreakpointsVisible ||
375 pauseReason === "mutationBreakpoint",
376 onToggle: opened => {
377 prefs.domMutationBreakpointsVisible = opened;
384 const { horizontal, hasFrames } = this.props;
387 if (this.props.threads.length) {
388 items.push(this.getThreadsItem());
391 items.push(this.getWatchItem());
394 items.push(this.getBreakpointsItem());
397 items.push(this.getCallStackItem());
399 items.push(this.getScopeItem());
403 items.push(this.getXHRItem());
405 items.push(this.getEventListenersItem());
407 items.push(this.getDOMMutationsItem());
413 if (this.props.horizontal) {
418 if (this.props.threads.length) {
419 items.push(this.getThreadsItem());
422 items.push(this.getWatchItem());
424 if (this.props.hasFrames) {
425 items.push(this.getScopeItem());
432 return [...this.getStartItems(), ...this.getEndItems()];
435 renderHorizontalLayout() {
436 const { renderWhyPauseDelay } = this.props;
439 React.createElement(WhyPaused, {
440 delay: renderWhyPauseDelay,
442 React.createElement(Accordion, {
443 items: this.getItems(),
448 renderVerticalLayout() {
449 return React.createElement(SplitBox, {
450 initialSize: "300px",
460 React.createElement(WhyPaused, {
461 delay: this.props.renderWhyPauseDelay,
463 React.createElement(Accordion, {
464 items: this.getStartItems(),
467 endPanel: React.createElement(Accordion, {
468 items: this.getEndItems(),
474 const { skipPausing } = this.props;
477 className: "secondary-panes-wrapper",
479 React.createElement(CommandBar, {
480 horizontal: this.props.horizontal,
485 className: classnames(
487 skipPausing && "skip-pausing"
490 this.props.horizontal
491 ? this.renderHorizontalLayout()
492 : this.renderVerticalLayout()
498 // Checks if user is in debugging mode and adds a delay preventing
499 // excessive vertical 'jumpiness'
500 function getRenderWhyPauseDelay(state, thread) {
501 const inPauseCommand = !!getPauseCommand(state, thread);
503 if (!inPauseCommand) {
510 const mapStateToProps = state => {
511 const thread = getCurrentThread(state);
512 const selectedFrame = getSelectedFrame(state, thread);
513 const pauseReason = getPauseReason(state, thread);
514 const shouldBreakpointsPaneOpenOnPause = getShouldBreakpointsPaneOpenOnPause(
520 expressions: getExpressions(state),
521 hasFrames: !!getTopFrame(state, thread),
522 renderWhyPauseDelay: getRenderWhyPauseDelay(state, thread),
524 mapScopesEnabled: isMapScopesEnabled(state),
525 threads: getThreads(state),
526 skipPausing: getSkipPausing(state),
527 logEventBreakpoints: shouldLogEventBreakpoints(state),
528 source: getSelectedSource(state),
529 pauseReason: pauseReason?.type ?? "",
530 shouldBreakpointsPaneOpenOnPause,
535 export default connect(mapStateToProps, {
536 evaluateExpressionsForCurrentContext:
537 actions.evaluateExpressionsForCurrentContext,
538 toggleMapScopes: actions.toggleMapScopes,
539 breakOnNext: actions.breakOnNext,
540 toggleEventLogging: actions.toggleEventLogging,
541 removeAllBreakpoints: actions.removeAllBreakpoints,
542 removeAllXHRBreakpoints: actions.removeAllXHRBreakpoints,
543 resetBreakpointsPaneState: actions.resetBreakpointsPaneState,