/* 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 [
,
];
}
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);