/* 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 . */
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "../../utils/connect";
import actions from "../../actions";
import { CloseButton } from "../shared/Button";
import "./XHRBreakpoints.css";
import { getXHRBreakpoints, shouldPauseOnAnyXHR } from "../../selectors";
import ExceptionOption from "./Breakpoints/ExceptionOption";
const classnames = require("devtools/client/shared/classnames.js");
// At present, the "Pause on any URL" checkbox creates an xhrBreakpoint
// of "ANY" with no path, so we can remove that before creating the list
function getExplicitXHRBreakpoints(xhrBreakpoints) {
return xhrBreakpoints.filter(bp => bp.path !== "");
}
const xhrMethods = [
"ANY",
"GET",
"POST",
"PUT",
"HEAD",
"DELETE",
"PATCH",
"OPTIONS",
];
class XHRBreakpoints extends Component {
constructor(props) {
super(props);
this.state = {
editing: false,
inputValue: "",
inputMethod: "ANY",
focused: false,
editIndex: -1,
clickedOnFormElement: false,
};
}
static get propTypes() {
return {
disableXHRBreakpoint: PropTypes.func.isRequired,
enableXHRBreakpoint: PropTypes.func.isRequired,
onXHRAdded: PropTypes.func.isRequired,
removeXHRBreakpoint: PropTypes.func.isRequired,
setXHRBreakpoint: PropTypes.func.isRequired,
shouldPauseOnAny: PropTypes.bool.isRequired,
showInput: PropTypes.bool.isRequired,
togglePauseOnAny: PropTypes.func.isRequired,
updateXHRBreakpoint: PropTypes.func.isRequired,
xhrBreakpoints: PropTypes.array.isRequired,
};
}
componentDidMount() {
const { showInput } = this.props;
// Ensures that the input is focused when the "+"
// is clicked while the panel is collapsed
if (this._input && showInput) {
this._input.focus();
}
}
componentDidUpdate(prevProps, prevState) {
const input = this._input;
if (!input) {
return;
}
if (!prevState.editing && this.state.editing) {
input.setSelectionRange(0, input.value.length);
input.focus();
} else if (this.props.showInput && !this.state.focused) {
input.focus();
}
}
handleNewSubmit = e => {
e.preventDefault();
e.stopPropagation();
const setXHRBreakpoint = function () {
this.props.setXHRBreakpoint(
this.state.inputValue,
this.state.inputMethod
);
this.hideInput();
};
// force update inputMethod in state for mochitest purposes
// before setting XHR breakpoint
this.setState(
{ inputMethod: e.target.children[1].value },
setXHRBreakpoint
);
};
handleExistingSubmit = e => {
e.preventDefault();
e.stopPropagation();
const { editIndex, inputValue, inputMethod } = this.state;
const { xhrBreakpoints } = this.props;
const { path, method } = xhrBreakpoints[editIndex];
if (path !== inputValue || method != inputMethod) {
this.props.updateXHRBreakpoint(editIndex, inputValue, inputMethod);
}
this.hideInput();
};
handleChange = e => {
this.setState({ inputValue: e.target.value });
};
handleMethodChange = e => {
this.setState({
focused: true,
editing: true,
inputMethod: e.target.value,
});
};
hideInput = () => {
if (this.state.clickedOnFormElement) {
this.setState({
focused: true,
clickedOnFormElement: false,
});
} else {
this.setState({
focused: false,
editing: false,
editIndex: -1,
inputValue: "",
inputMethod: "ANY",
});
this.props.onXHRAdded();
}
};
onFocus = () => {
this.setState({ focused: true, editing: true });
};
onMouseDown = e => {
this.setState({ editing: false, clickedOnFormElement: true });
};
handleTab = e => {
if (e.key !== "Tab") {
return;
}
if (e.currentTarget.nodeName === "INPUT") {
this.setState({
clickedOnFormElement: true,
editing: false,
});
} else if (e.currentTarget.nodeName === "SELECT" && !e.shiftKey) {
// The user has tabbed off the select and we should
// cancel the edit
this.hideInput();
}
};
editExpression = index => {
const { xhrBreakpoints } = this.props;
const { path, method } = xhrBreakpoints[index];
this.setState({
inputValue: path,
inputMethod: method,
editing: true,
editIndex: index,
});
};
renderXHRInput(onSubmit) {
const { focused, inputValue } = this.state;
const placeholder = L10N.getStr("xhrBreakpoints.placeholder");
return (
);
}
handleCheckbox = index => {
const { xhrBreakpoints, enableXHRBreakpoint, disableXHRBreakpoint } =
this.props;
const breakpoint = xhrBreakpoints[index];
if (breakpoint.disabled) {
enableXHRBreakpoint(index);
} else {
disableXHRBreakpoint(index);
}
};
renderBreakpoint = breakpoint => {
const { path, disabled, method } = breakpoint;
const { editIndex } = this.state;
const { removeXHRBreakpoint, xhrBreakpoints } = this.props;
// The "pause on any" checkbox
if (!path) {
return null;
}
// Finds the xhrbreakpoint so as to not make assumptions about position
const index = xhrBreakpoints.findIndex(
bp => bp.path === path && bp.method === method
);
if (index === editIndex) {
return this.renderXHRInput(this.handleExistingSubmit);
}
return (
this.editExpression(index)}
>
);
};
renderBreakpoints = explicitXhrBreakpoints => {
const { showInput } = this.props;
return (
<>
{explicitXhrBreakpoints.map(this.renderBreakpoint)}
{showInput && this.renderXHRInput(this.handleNewSubmit)}
>
);
};
renderCheckbox = explicitXhrBreakpoints => {
const { shouldPauseOnAny, togglePauseOnAny } = this.props;
return (
togglePauseOnAny()}
/>
);
};
renderMethodOption = method => {
return (
);
};
renderMethodSelectElement = () => {
return (
);
};
render() {
const { xhrBreakpoints } = this.props;
const explicitXhrBreakpoints = getExplicitXHRBreakpoints(xhrBreakpoints);
return (
<>
{this.renderCheckbox(explicitXhrBreakpoints)}
{explicitXhrBreakpoints.length === 0
? this.renderXHRInput(this.handleNewSubmit)
: this.renderBreakpoints(explicitXhrBreakpoints)}
>
);
}
}
const mapStateToProps = state => ({
xhrBreakpoints: getXHRBreakpoints(state),
shouldPauseOnAny: shouldPauseOnAnyXHR(state),
});
export default connect(mapStateToProps, {
setXHRBreakpoint: actions.setXHRBreakpoint,
removeXHRBreakpoint: actions.removeXHRBreakpoint,
enableXHRBreakpoint: actions.enableXHRBreakpoint,
disableXHRBreakpoint: actions.disableXHRBreakpoint,
updateXHRBreakpoint: actions.updateXHRBreakpoint,
togglePauseOnAny: actions.togglePauseOnAny,
})(XHRBreakpoints);