tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

XHRBreakpoints.js (10037B)


      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/>. */
      4 
      5 import React, { Component } from "devtools/client/shared/vendor/react";
      6 import {
      7  div,
      8  form,
      9  input,
     10  li,
     11  label,
     12  ul,
     13  option,
     14  select,
     15 } from "devtools/client/shared/vendor/react-dom-factories";
     16 import PropTypes from "devtools/client/shared/vendor/react-prop-types";
     17 import { connect } from "devtools/client/shared/vendor/react-redux";
     18 import actions from "../../actions/index";
     19 
     20 import { CloseButton } from "../shared/Button/index";
     21 
     22 import { getXHRBreakpoints, shouldPauseOnAnyXHR } from "../../selectors/index";
     23 import ExceptionOption from "./Breakpoints/ExceptionOption";
     24 
     25 const classnames = require("resource://devtools/client/shared/classnames.js");
     26 
     27 // At present, the "Pause on any URL" checkbox creates an xhrBreakpoint
     28 // of "ANY" with no path, so we can remove that before creating the list
     29 function getExplicitXHRBreakpoints(xhrBreakpoints) {
     30  return xhrBreakpoints.filter(bp => bp.path !== "");
     31 }
     32 
     33 const xhrMethods = [
     34  "ANY",
     35  "GET",
     36  "POST",
     37  "PUT",
     38  "HEAD",
     39  "DELETE",
     40  "PATCH",
     41  "OPTIONS",
     42 ];
     43 
     44 class XHRBreakpoints extends Component {
     45  constructor(props) {
     46    super(props);
     47 
     48    this.state = {
     49      editing: false,
     50      inputValue: "",
     51      inputMethod: "ANY",
     52      focused: false,
     53      editIndex: -1,
     54      clickedOnFormElement: false,
     55    };
     56  }
     57 
     58  static get propTypes() {
     59    return {
     60      disableXHRBreakpoint: PropTypes.func.isRequired,
     61      enableXHRBreakpoint: PropTypes.func.isRequired,
     62      onXHRAdded: PropTypes.func.isRequired,
     63      removeXHRBreakpoint: PropTypes.func.isRequired,
     64      setXHRBreakpoint: PropTypes.func.isRequired,
     65      shouldPauseOnAny: PropTypes.bool.isRequired,
     66      showInput: PropTypes.bool.isRequired,
     67      togglePauseOnAny: PropTypes.func.isRequired,
     68      updateXHRBreakpoint: PropTypes.func.isRequired,
     69      xhrBreakpoints: PropTypes.array.isRequired,
     70    };
     71  }
     72 
     73  componentDidMount() {
     74    const { showInput } = this.props;
     75 
     76    // Ensures that the input is focused when the "+"
     77    // is clicked while the panel is collapsed
     78    if (this._input && showInput) {
     79      this._input.focus();
     80    }
     81  }
     82 
     83  componentDidUpdate(prevProps, prevState) {
     84    const _input = this._input;
     85 
     86    if (!_input) {
     87      return;
     88    }
     89 
     90    if (!prevState.editing && this.state.editing) {
     91      _input.setSelectionRange(0, _input.value.length);
     92      _input.focus();
     93    } else if (this.props.showInput && !this.state.focused) {
     94      _input.focus();
     95    }
     96  }
     97 
     98  handleNewSubmit = e => {
     99    e.preventDefault();
    100    e.stopPropagation();
    101 
    102    // Prevent adding breakpoint with empty path
    103    if (!this.state.inputValue.trim()) {
    104      return;
    105    }
    106 
    107    const setXHRBreakpoint = function () {
    108      this.props.setXHRBreakpoint(
    109        this.state.inputValue,
    110        this.state.inputMethod
    111      );
    112      this.hideInput();
    113    };
    114 
    115    // force update inputMethod in state for mochitest purposes
    116    // before setting XHR breakpoint
    117    this.setState(
    118      { inputMethod: e.target.children[1].value },
    119      setXHRBreakpoint
    120    );
    121  };
    122 
    123  handleExistingSubmit = e => {
    124    e.preventDefault();
    125    e.stopPropagation();
    126 
    127    const { editIndex, inputValue, inputMethod } = this.state;
    128    const { xhrBreakpoints } = this.props;
    129    const { path, method } = xhrBreakpoints[editIndex];
    130 
    131    if (path !== inputValue || method != inputMethod) {
    132      this.props.updateXHRBreakpoint(editIndex, inputValue, inputMethod);
    133    }
    134 
    135    this.hideInput();
    136  };
    137 
    138  handleChange = e => {
    139    this.setState({ inputValue: e.target.value });
    140  };
    141 
    142  handleMethodChange = e => {
    143    this.setState({
    144      focused: true,
    145      editing: true,
    146      inputMethod: e.target.value,
    147    });
    148  };
    149 
    150  hideInput = () => {
    151    if (this.state.clickedOnFormElement) {
    152      this.setState({
    153        focused: true,
    154        clickedOnFormElement: false,
    155      });
    156    } else {
    157      this.setState({
    158        focused: false,
    159        editing: false,
    160        editIndex: -1,
    161        inputValue: "",
    162        inputMethod: "ANY",
    163      });
    164      this.props.onXHRAdded();
    165    }
    166  };
    167 
    168  onFocus = () => {
    169    this.setState({ focused: true, editing: true });
    170  };
    171 
    172  onMouseDown = () => {
    173    this.setState({ editing: false, clickedOnFormElement: true });
    174  };
    175 
    176  handleTab = e => {
    177    if (e.key !== "Tab") {
    178      return;
    179    }
    180 
    181    if (e.currentTarget.nodeName === "INPUT") {
    182      this.setState({
    183        clickedOnFormElement: true,
    184        editing: false,
    185      });
    186    } else if (e.currentTarget.nodeName === "SELECT" && !e.shiftKey) {
    187      // The user has tabbed off the select and we should
    188      // cancel the edit
    189      this.hideInput();
    190    }
    191  };
    192 
    193  editExpression = index => {
    194    const { xhrBreakpoints } = this.props;
    195    const { path, method } = xhrBreakpoints[index];
    196    this.setState({
    197      inputValue: path,
    198      inputMethod: method,
    199      editing: true,
    200      editIndex: index,
    201    });
    202  };
    203 
    204  renderXHRInput(onSubmit) {
    205    const { focused, inputValue } = this.state;
    206    const placeholder = L10N.getStr("xhrBreakpoints.placeholder");
    207    return form(
    208      {
    209        key: "xhr-input-container",
    210        className: classnames("xhr-input-container xhr-input-form", {
    211          focused,
    212        }),
    213        onSubmit,
    214      },
    215      input({
    216        className: "xhr-input-url",
    217        type: "text",
    218        placeholder,
    219        onChange: this.handleChange,
    220        onBlur: this.hideInput,
    221        onFocus: this.onFocus,
    222        value: inputValue,
    223        onKeyDown: this.handleTab,
    224        ref: c => (this._input = c),
    225      }),
    226      this.renderMethodSelectElement(),
    227      input({
    228        type: "submit",
    229        style: {
    230          display: "none",
    231        },
    232      })
    233    );
    234  }
    235 
    236  handleCheckbox = index => {
    237    const { xhrBreakpoints, enableXHRBreakpoint, disableXHRBreakpoint } =
    238      this.props;
    239    const breakpoint = xhrBreakpoints[index];
    240    if (breakpoint.disabled) {
    241      enableXHRBreakpoint(index);
    242    } else {
    243      disableXHRBreakpoint(index);
    244    }
    245  };
    246 
    247  renderBreakpoint = breakpoint => {
    248    const { path, disabled, method } = breakpoint;
    249    const { editIndex } = this.state;
    250    const { removeXHRBreakpoint, xhrBreakpoints } = this.props;
    251 
    252    // The "pause on any" checkbox
    253    if (!path) {
    254      return null;
    255    }
    256 
    257    // Finds the xhrbreakpoint so as to not make assumptions about position
    258    const index = xhrBreakpoints.findIndex(
    259      bp => bp.path === path && bp.method === method
    260    );
    261 
    262    if (index === editIndex) {
    263      return this.renderXHRInput(this.handleExistingSubmit);
    264    }
    265    return li(
    266      {
    267        className: "xhr-container",
    268        key: `${path}-${method}`,
    269        title: path,
    270        onDoubleClick: () => this.editExpression(index),
    271      },
    272      label(
    273        null,
    274        React.createElement("input", {
    275          type: "checkbox",
    276          className: "xhr-checkbox",
    277          checked: !disabled,
    278          onChange: () => this.handleCheckbox(index),
    279          onClick: ev => ev.stopPropagation(),
    280        }),
    281        div(
    282          {
    283            className: "xhr-label-method",
    284          },
    285          method
    286        ),
    287        div(
    288          {
    289            className: "xhr-label-url",
    290          },
    291          path
    292        ),
    293        div(
    294          {
    295            className: "xhr-container__close-btn",
    296          },
    297          React.createElement(CloseButton, {
    298            handleClick: () => removeXHRBreakpoint(index),
    299          })
    300        )
    301      )
    302    );
    303  };
    304 
    305  renderBreakpoints = explicitXhrBreakpoints => {
    306    const { showInput } = this.props;
    307    return React.createElement(
    308      React.Fragment,
    309      null,
    310      ul(
    311        {
    312          className: "pane expressions-list",
    313        },
    314        explicitXhrBreakpoints.map(this.renderBreakpoint)
    315      ),
    316      showInput && this.renderXHRInput(this.handleNewSubmit)
    317    );
    318  };
    319 
    320  renderCheckbox = explicitXhrBreakpoints => {
    321    const { shouldPauseOnAny, togglePauseOnAny } = this.props;
    322    return div(
    323      {
    324        className: classnames("breakpoints-options", {
    325          empty: explicitXhrBreakpoints.length === 0,
    326        }),
    327      },
    328      React.createElement(ExceptionOption, {
    329        className: "breakpoints-exceptions",
    330        label: L10N.getStr("pauseOnAnyXHR"),
    331        isChecked: shouldPauseOnAny,
    332        onChange: () => togglePauseOnAny(),
    333      })
    334    );
    335  };
    336  renderMethodOption = method => {
    337    return option(
    338      {
    339        key: method,
    340        value: method,
    341        // e.stopPropagation() required here since otherwise Firefox triggers 2x
    342        // onMouseDown events on <select> upon clicking on an <option>
    343        onMouseDown: e => e.stopPropagation(),
    344      },
    345      method
    346    );
    347  };
    348 
    349  renderMethodSelectElement = () => {
    350    return select(
    351      {
    352        value: this.state.inputMethod,
    353        className: "xhr-input-method",
    354        onChange: this.handleMethodChange,
    355        onMouseDown: this.onMouseDown,
    356        onKeyDown: this.handleTab,
    357      },
    358      xhrMethods.map(this.renderMethodOption)
    359    );
    360  };
    361 
    362  render() {
    363    const { xhrBreakpoints } = this.props;
    364    const explicitXhrBreakpoints = getExplicitXHRBreakpoints(xhrBreakpoints);
    365    return React.createElement(
    366      React.Fragment,
    367      null,
    368      this.renderCheckbox(explicitXhrBreakpoints),
    369      explicitXhrBreakpoints.length === 0
    370        ? this.renderXHRInput(this.handleNewSubmit)
    371        : this.renderBreakpoints(explicitXhrBreakpoints)
    372    );
    373  }
    374 }
    375 
    376 const mapStateToProps = state => ({
    377  xhrBreakpoints: getXHRBreakpoints(state),
    378  shouldPauseOnAny: shouldPauseOnAnyXHR(state),
    379 });
    380 
    381 export default connect(mapStateToProps, {
    382  setXHRBreakpoint: actions.setXHRBreakpoint,
    383  removeXHRBreakpoint: actions.removeXHRBreakpoint,
    384  enableXHRBreakpoint: actions.enableXHRBreakpoint,
    385  disableXHRBreakpoint: actions.disableXHRBreakpoint,
    386  updateXHRBreakpoint: actions.updateXHRBreakpoint,
    387  togglePauseOnAny: actions.togglePauseOnAny,
    388 })(XHRBreakpoints);