/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ const SplitBox = require("devtools/client/shared/components/splitter/SplitBox"); import React, { Component } from "react"; import PropTypes from "prop-types"; import { isGeneratedId } from "devtools/client/shared/source-map-loader/index"; import { connect } from "../../utils/connect"; import actions from "../../actions"; import { getTopFrame, getExpressions, getPauseCommand, isMapScopesEnabled, getSelectedFrame, getShouldPauseOnExceptions, getShouldPauseOnCaughtExceptions, getThreads, getCurrentThread, getThreadContext, getPauseReason, getShouldBreakpointsPaneOpenOnPause, getSkipPausing, shouldLogEventBreakpoints, } from "../../selectors"; import AccessibleImage from "../shared/AccessibleImage"; import { prefs } from "../../utils/prefs"; import Breakpoints from "./Breakpoints"; import Expressions from "./Expressions"; import Frames from "./Frames"; import Threads from "./Threads"; import Accordion from "../shared/Accordion"; import CommandBar from "./CommandBar"; import XHRBreakpoints from "./XHRBreakpoints"; import EventListeners from "./EventListeners"; import DOMMutationBreakpoints from "./DOMMutationBreakpoints"; import WhyPaused from "./WhyPaused"; import Scopes from "./Scopes"; const classnames = require("devtools/client/shared/classnames.js"); import "./SecondaryPanes.css"; function debugBtn(onClick, type, className, tooltip) { return ( ); } const mdnLink = "https://firefox-source-docs.mozilla.org/devtools-user/debugger/using_the_debugger_map_scopes_feature/"; class SecondaryPanes extends Component { constructor(props) { super(props); this.state = { showExpressionsInput: false, showXHRInput: false, }; } static get propTypes() { return { cx: PropTypes.object.isRequired, evaluateExpressions: PropTypes.func.isRequired, expressions: PropTypes.array.isRequired, hasFrames: PropTypes.bool.isRequired, horizontal: PropTypes.bool.isRequired, logEventBreakpoints: PropTypes.bool.isRequired, mapScopesEnabled: PropTypes.bool.isRequired, pauseOnExceptions: PropTypes.func.isRequired, pauseReason: PropTypes.string.isRequired, shouldBreakpointsPaneOpenOnPause: PropTypes.bool.isRequired, thread: PropTypes.string.isRequired, renderWhyPauseDelay: PropTypes.number.isRequired, selectedFrame: PropTypes.object, shouldPauseOnCaughtExceptions: PropTypes.bool.isRequired, shouldPauseOnExceptions: PropTypes.bool.isRequired, skipPausing: PropTypes.bool.isRequired, source: PropTypes.object, toggleEventLogging: PropTypes.func.isRequired, resetBreakpointsPaneState: PropTypes.func.isRequired, toggleMapScopes: PropTypes.func.isRequired, threads: PropTypes.array.isRequired, removeAllBreakpoints: PropTypes.func.isRequired, removeAllXHRBreakpoints: PropTypes.func.isRequired, }; } onExpressionAdded = () => { this.setState({ showExpressionsInput: false }); }; onXHRAdded = () => { this.setState({ showXHRInput: false }); }; watchExpressionHeaderButtons() { const { expressions } = this.props; const buttons = []; if (expressions.length) { buttons.push( debugBtn( evt => { evt.stopPropagation(); this.props.evaluateExpressions(this.props.cx); }, "refresh", "active", L10N.getStr("watchExpressions.refreshButton") ) ); } buttons.push( debugBtn( evt => { if (prefs.expressionsVisible) { evt.stopPropagation(); } this.setState({ showExpressionsInput: true }); }, "plus", "active", L10N.getStr("expressions.placeholder") ) ); return buttons; } xhrBreakpointsHeaderButtons() { return [ debugBtn( evt => { if (prefs.xhrBreakpointsVisible) { evt.stopPropagation(); } this.setState({ showXHRInput: true }); }, "plus", "active", L10N.getStr("xhrBreakpoints.label") ), debugBtn( evt => { evt.stopPropagation(); this.props.removeAllXHRBreakpoints(); }, "removeAll", "active", L10N.getStr("xhrBreakpoints.removeAll.tooltip") ), ]; } breakpointsHeaderButtons() { return [ debugBtn( evt => { evt.stopPropagation(); this.props.removeAllBreakpoints(this.props.cx); }, "removeAll", "active", L10N.getStr("breakpointMenuItem.deleteAll") ), ]; } getScopeItem() { return { header: L10N.getStr("scopes.header"), className: "scopes-pane", component: , opened: prefs.scopesVisible, buttons: this.getScopesButtons(), onToggle: opened => { prefs.scopesVisible = opened; }, }; } getScopesButtons() { const { selectedFrame, mapScopesEnabled, source } = this.props; if ( !selectedFrame || isGeneratedId(selectedFrame.location.sourceId) || source?.isPrettyPrinted ) { return null; } return [
e.stopPropagation()} title={L10N.getStr("scopes.helpTooltip.label")} >
, ]; } getEventButtons() { const { logEventBreakpoints } = this.props; return [
, ]; } getWatchItem() { return { header: L10N.getStr("watchExpressions.header"), className: "watch-expressions-pane", buttons: this.watchExpressionHeaderButtons(), component: ( ), opened: prefs.expressionsVisible, onToggle: opened => { prefs.expressionsVisible = opened; }, }; } getXHRItem() { const { pauseReason } = this.props; return { header: L10N.getStr("xhrBreakpoints.header"), className: "xhr-breakpoints-pane", buttons: this.xhrBreakpointsHeaderButtons(), component: ( ), opened: prefs.xhrBreakpointsVisible || pauseReason === "XHR", onToggle: opened => { prefs.xhrBreakpointsVisible = opened; }, }; } getCallStackItem() { return { header: L10N.getStr("callStack.header"), className: "call-stack-pane", component: , opened: prefs.callStackVisible, onToggle: opened => { prefs.callStackVisible = opened; }, }; } getThreadsItem() { return { header: L10N.getStr("threadsHeader"), className: "threads-pane", component: , opened: prefs.threadsVisible, onToggle: opened => { prefs.threadsVisible = opened; }, }; } getBreakpointsItem() { const { shouldPauseOnExceptions, shouldPauseOnCaughtExceptions, pauseOnExceptions, pauseReason, shouldBreakpointsPaneOpenOnPause, thread, } = this.props; return { header: L10N.getStr("breakpoints.header"), className: "breakpoints-pane", buttons: this.breakpointsHeaderButtons(), component: ( ), opened: prefs.breakpointsVisible || (pauseReason === "breakpoint" && shouldBreakpointsPaneOpenOnPause), onToggle: opened => { prefs.breakpointsVisible = opened; // one-shot flag used to force open the Breakpoints Pane only // when hitting a breakpoint, but not when selecting frames etc... if (shouldBreakpointsPaneOpenOnPause) { this.props.resetBreakpointsPaneState(thread); } }, }; } getEventListenersItem() { const { pauseReason } = this.props; return { header: L10N.getStr("eventListenersHeader1"), className: "event-listeners-pane", buttons: this.getEventButtons(), component: , opened: prefs.eventListenersVisible || pauseReason === "eventBreakpoint", onToggle: opened => { prefs.eventListenersVisible = opened; }, }; } getDOMMutationsItem() { const { pauseReason } = this.props; return { header: L10N.getStr("domMutationHeader"), className: "dom-mutations-pane", buttons: [], component: , opened: prefs.domMutationBreakpointsVisible || pauseReason === "mutationBreakpoint", onToggle: opened => { prefs.domMutationBreakpointsVisible = opened; }, }; } getStartItems() { const items = []; const { horizontal, hasFrames } = this.props; if (horizontal) { if (this.props.threads.length) { items.push(this.getThreadsItem()); } items.push(this.getWatchItem()); } items.push(this.getBreakpointsItem()); if (hasFrames) { items.push(this.getCallStackItem()); if (horizontal) { items.push(this.getScopeItem()); } } items.push(this.getXHRItem()); items.push(this.getEventListenersItem()); items.push(this.getDOMMutationsItem()); return items; } getEndItems() { if (this.props.horizontal) { return []; } const items = []; if (this.props.threads.length) { items.push(this.getThreadsItem()); } items.push(this.getWatchItem()); if (this.props.hasFrames) { items.push(this.getScopeItem()); } return items; } getItems() { return [...this.getStartItems(), ...this.getEndItems()]; } renderHorizontalLayout() { const { renderWhyPauseDelay } = this.props; return (
); } renderVerticalLayout() { return ( } endPanel={} /> ); } render() { const { skipPausing } = this.props; return (
{this.props.horizontal ? this.renderHorizontalLayout() : this.renderVerticalLayout()}
); } } // Checks if user is in debugging mode and adds a delay preventing // excessive vertical 'jumpiness' function getRenderWhyPauseDelay(state, thread) { const inPauseCommand = !!getPauseCommand(state, thread); if (!inPauseCommand) { return 100; } return 0; } const mapStateToProps = state => { const thread = getCurrentThread(state); const selectedFrame = getSelectedFrame(state, thread); const pauseReason = getPauseReason(state, thread); const shouldBreakpointsPaneOpenOnPause = getShouldBreakpointsPaneOpenOnPause( state, thread ); return { cx: getThreadContext(state), expressions: getExpressions(state), hasFrames: !!getTopFrame(state, thread), renderWhyPauseDelay: getRenderWhyPauseDelay(state, thread), selectedFrame, mapScopesEnabled: isMapScopesEnabled(state), shouldPauseOnExceptions: getShouldPauseOnExceptions(state), shouldPauseOnCaughtExceptions: getShouldPauseOnCaughtExceptions(state), threads: getThreads(state), skipPausing: getSkipPausing(state), logEventBreakpoints: shouldLogEventBreakpoints(state), source: selectedFrame && selectedFrame.location.source, pauseReason: pauseReason?.type ?? "", shouldBreakpointsPaneOpenOnPause, thread, }; }; export default connect(mapStateToProps, { evaluateExpressions: actions.evaluateExpressions, pauseOnExceptions: actions.pauseOnExceptions, toggleMapScopes: actions.toggleMapScopes, breakOnNext: actions.breakOnNext, toggleEventLogging: actions.toggleEventLogging, removeAllBreakpoints: actions.removeAllBreakpoints, removeAllXHRBreakpoints: actions.removeAllXHRBreakpoints, resetBreakpointsPaneState: actions.resetBreakpointsPaneState, })(SecondaryPanes);