summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/components/SecondaryPanes/XHRBreakpoints.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/components/SecondaryPanes/XHRBreakpoints.js')
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/XHRBreakpoints.js361
1 files changed, 361 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/XHRBreakpoints.js b/devtools/client/debugger/src/components/SecondaryPanes/XHRBreakpoints.js
new file mode 100644
index 0000000000..721b132a3b
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/XHRBreakpoints.js
@@ -0,0 +1,361 @@
+/* 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 <http://mozilla.org/MPL/2.0/>. */
+
+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 (
+ <form
+ key="xhr-input-container"
+ className={classnames("xhr-input-container xhr-input-form", {
+ focused,
+ })}
+ onSubmit={onSubmit}
+ >
+ <input
+ className="xhr-input-url"
+ type="text"
+ placeholder={placeholder}
+ onChange={this.handleChange}
+ onBlur={this.hideInput}
+ onFocus={this.onFocus}
+ value={inputValue}
+ onKeyDown={this.handleTab}
+ ref={c => (this._input = c)}
+ />
+ {this.renderMethodSelectElement()}
+ <input type="submit" style={{ display: "none" }} />
+ </form>
+ );
+ }
+
+ 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 (
+ <li
+ className="xhr-container"
+ key={`${path}-${method}`}
+ title={path}
+ onDoubleClick={(items, options) => this.editExpression(index)}
+ >
+ <label>
+ <input
+ type="checkbox"
+ className="xhr-checkbox"
+ checked={!disabled}
+ onChange={() => this.handleCheckbox(index)}
+ onClick={ev => ev.stopPropagation()}
+ />
+ <div className="xhr-label-method">{method}</div>
+ <div className="xhr-label-url">{path}</div>
+ <div className="xhr-container__close-btn">
+ <CloseButton handleClick={e => removeXHRBreakpoint(index)} />
+ </div>
+ </label>
+ </li>
+ );
+ };
+
+ renderBreakpoints = explicitXhrBreakpoints => {
+ const { showInput } = this.props;
+
+ return (
+ <>
+ <ul className="pane expressions-list">
+ {explicitXhrBreakpoints.map(this.renderBreakpoint)}
+ </ul>
+ {showInput && this.renderXHRInput(this.handleNewSubmit)}
+ </>
+ );
+ };
+
+ renderCheckbox = explicitXhrBreakpoints => {
+ const { shouldPauseOnAny, togglePauseOnAny } = this.props;
+
+ return (
+ <div
+ className={classnames("breakpoints-exceptions-options", {
+ empty: explicitXhrBreakpoints.length === 0,
+ })}
+ >
+ <ExceptionOption
+ className="breakpoints-exceptions"
+ label={L10N.getStr("pauseOnAnyXHR")}
+ isChecked={shouldPauseOnAny}
+ onChange={() => togglePauseOnAny()}
+ />
+ </div>
+ );
+ };
+
+ renderMethodOption = method => {
+ return (
+ <option
+ key={method}
+ value={method}
+ // e.stopPropagation() required here since otherwise Firefox triggers 2x
+ // onMouseDown events on <select> upon clicking on an <option>
+ onMouseDown={e => e.stopPropagation()}
+ >
+ {method}
+ </option>
+ );
+ };
+
+ renderMethodSelectElement = () => {
+ return (
+ <select
+ value={this.state.inputMethod}
+ className="xhr-input-method"
+ onChange={this.handleMethodChange}
+ onMouseDown={this.onMouseDown}
+ onKeyDown={this.handleTab}
+ >
+ {xhrMethods.map(this.renderMethodOption)}
+ </select>
+ );
+ };
+
+ 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);