tor-browser

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

RequestBlockingPanel.js (9915B)


      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 "use strict";
      6 
      7 const {
      8  Component,
      9 } = require("resource://devtools/client/shared/vendor/react.mjs");
     10 const {
     11  button,
     12  div,
     13  form,
     14  input,
     15  label,
     16  li,
     17  span,
     18  ul,
     19 } = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     20 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     21 const {
     22  connect,
     23 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     24 const Actions = require("resource://devtools/client/netmonitor/src/actions/index.js");
     25 const {
     26  L10N,
     27 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
     28 const {
     29  PANELS,
     30 } = require("resource://devtools/client/netmonitor/src/constants.js");
     31 
     32 const RequestBlockingContextMenu = require("resource://devtools/client/netmonitor/src/widgets/RequestBlockingContextMenu.js");
     33 
     34 const ENABLE_BLOCKING_LABEL = L10N.getStr(
     35  "netmonitor.actionbar.enableBlocking"
     36 );
     37 const ADD_URL_PLACEHOLDER = L10N.getStr(
     38  "netmonitor.actionbar.blockSearchPlaceholder"
     39 );
     40 const REQUEST_BLOCKING_USAGE_NOTICE = L10N.getStr(
     41  "netmonitor.actionbar.requestBlockingUsageNotice"
     42 );
     43 const REQUEST_BLOCKING_ADD_NOTICE = L10N.getStr(
     44  "netmonitor.actionbar.requestBlockingAddNotice"
     45 );
     46 const REMOVE_URL_TOOLTIP = L10N.getStr("netmonitor.actionbar.removeBlockedUrl");
     47 
     48 class RequestBlockingPanel extends Component {
     49  static get propTypes() {
     50    return {
     51      blockedUrls: PropTypes.array.isRequired,
     52      addBlockedUrl: PropTypes.func.isRequired,
     53      isDisplaying: PropTypes.bool.isRequired,
     54      removeBlockedUrl: PropTypes.func.isRequired,
     55      toggleBlockingEnabled: PropTypes.func.isRequired,
     56      toggleBlockedUrl: PropTypes.func.isRequired,
     57      updateBlockedUrl: PropTypes.func.isRequired,
     58      removeAllBlockedUrls: PropTypes.func.isRequired,
     59      disableAllBlockedUrls: PropTypes.func.isRequired,
     60      enableAllBlockedUrls: PropTypes.func.isRequired,
     61      blockingEnabled: PropTypes.bool.isRequired,
     62    };
     63  }
     64 
     65  constructor(props) {
     66    super(props);
     67 
     68    this.state = {
     69      editingUrl: null,
     70    };
     71  }
     72 
     73  componentDidMount() {
     74    this.refs.addInput.focus();
     75  }
     76 
     77  componentDidUpdate(prevProps) {
     78    if (this.state.editingUrl) {
     79      this.refs.editInput.focus();
     80      this.refs.editInput.select();
     81    } else if (this.props.isDisplaying && !prevProps.isDisplaying) {
     82      this.refs.addInput.focus();
     83    }
     84  }
     85 
     86  componentWillUnmount() {
     87    if (this.scrollToBottomTimeout) {
     88      clearTimeout(this.scrollToBottomTimeout);
     89    }
     90  }
     91 
     92  scrollToBottom() {
     93    if (this.scrollToBottomTimeout) {
     94      clearTimeout(this.scrollToBottomTimeout);
     95    }
     96    this.scrollToBottomTimeout = setTimeout(() => {
     97      const { contents } = this.refs;
     98      if (contents.scrollHeight > contents.offsetHeight) {
     99        contents.scrollTo({ top: contents.scrollHeight });
    100      }
    101    }, 40);
    102  }
    103 
    104  renderEnableBar() {
    105    return div(
    106      { className: "request-blocking-enable-bar" },
    107      div(
    108        { className: "request-blocking-enable-form" },
    109        label(
    110          { className: "devtools-checkbox-label" },
    111          input({
    112            type: "checkbox",
    113            className: "devtools-checkbox",
    114            checked: this.props.blockingEnabled,
    115            ref: "enabledCheckbox",
    116            onChange: () =>
    117              this.props.toggleBlockingEnabled(
    118                this.refs.enabledCheckbox.checked
    119              ),
    120          }),
    121          span({ className: "request-blocking-label" }, ENABLE_BLOCKING_LABEL)
    122        )
    123      )
    124    );
    125  }
    126 
    127  renderItemContent({ url, enabled }) {
    128    const { toggleBlockedUrl, removeBlockedUrl } = this.props;
    129 
    130    return li(
    131      { key: url },
    132      label(
    133        {
    134          className: "devtools-checkbox-label",
    135          onDoubleClick: () => this.setState({ editingUrl: url }),
    136        },
    137        input({
    138          type: "checkbox",
    139          className: "devtools-checkbox",
    140          checked: enabled,
    141          onChange: () => toggleBlockedUrl(url),
    142        }),
    143        span(
    144          {
    145            className: "request-blocking-label request-blocking-editable-label",
    146            title: url,
    147          },
    148          url
    149        )
    150      ),
    151      button({
    152        className: "request-blocking-remove-button",
    153        title: REMOVE_URL_TOOLTIP,
    154        "aria-label": REMOVE_URL_TOOLTIP,
    155        onClick: () => removeBlockedUrl(url),
    156      })
    157    );
    158  }
    159 
    160  renderEditForm(url) {
    161    const { updateBlockedUrl, removeBlockedUrl } = this.props;
    162    return li(
    163      { key: url, className: "request-blocking-edit-item" },
    164      form(
    165        {
    166          onSubmit: e => {
    167            const { editInput } = this.refs;
    168            const newValue = editInput.value;
    169            e.preventDefault();
    170 
    171            if (url != newValue) {
    172              if (editInput.value.trim() === "") {
    173                removeBlockedUrl(url, newValue);
    174              } else {
    175                updateBlockedUrl(url, newValue);
    176              }
    177            }
    178            this.setState({ editingUrl: null });
    179          },
    180        },
    181        input({
    182          type: "text",
    183          defaultValue: url,
    184          ref: "editInput",
    185          className: "devtools-searchinput",
    186          placeholder: ADD_URL_PLACEHOLDER,
    187          onBlur: () => this.setState({ editingUrl: null }),
    188          onKeyDown: e => {
    189            if (e.key === "Escape") {
    190              e.stopPropagation();
    191              e.preventDefault();
    192              this.setState({ editingUrl: null });
    193            }
    194          },
    195        }),
    196 
    197        input({ type: "submit", style: { display: "none" } })
    198      )
    199    );
    200  }
    201 
    202  renderBlockedList() {
    203    const {
    204      blockedUrls,
    205      blockingEnabled,
    206      removeAllBlockedUrls,
    207      disableAllBlockedUrls,
    208      enableAllBlockedUrls,
    209    } = this.props;
    210 
    211    if (blockedUrls.length === 0) {
    212      return null;
    213    }
    214 
    215    const listItems = blockedUrls.map(item =>
    216      this.state.editingUrl === item.url
    217        ? this.renderEditForm(item.url)
    218        : this.renderItemContent(item)
    219    );
    220 
    221    return div(
    222      {
    223        className: "request-blocking-contents",
    224        ref: "contents",
    225        onContextMenu: event => {
    226          if (!this.contextMenu) {
    227            this.contextMenu = new RequestBlockingContextMenu({
    228              removeAllBlockedUrls,
    229              disableAllBlockedUrls,
    230              enableAllBlockedUrls,
    231            });
    232          }
    233 
    234          const contextMenuOptions = {
    235            disableDisableAllBlockedUrls: blockedUrls.every(
    236              ({ enabled }) => enabled === false
    237            ),
    238            disableEnableAllBlockedUrls: blockedUrls.every(
    239              ({ enabled }) => enabled === true
    240            ),
    241          };
    242 
    243          this.contextMenu.open(event, contextMenuOptions);
    244        },
    245      },
    246      ul(
    247        {
    248          className: `request-blocking-list ${
    249            blockingEnabled ? "" : "disabled"
    250          }`,
    251        },
    252        ...listItems
    253      )
    254    );
    255  }
    256 
    257  renderAddForm() {
    258    const { addBlockedUrl } = this.props;
    259    return div(
    260      { className: "request-blocking-footer" },
    261      form(
    262        {
    263          className: "request-blocking-add-form",
    264          onSubmit: e => {
    265            const { addInput } = this.refs;
    266            e.preventDefault();
    267            addBlockedUrl(addInput.value);
    268            addInput.value = "";
    269            addInput.focus();
    270            this.scrollToBottom();
    271          },
    272        },
    273        input({
    274          type: "text",
    275          ref: "addInput",
    276          className: "devtools-searchinput",
    277          placeholder: ADD_URL_PLACEHOLDER,
    278          onKeyDown: e => {
    279            if (e.key === "Escape") {
    280              e.stopPropagation();
    281              e.preventDefault();
    282 
    283              const { addInput } = this.refs;
    284              addInput.value = "";
    285              addInput.focus();
    286            }
    287          },
    288        }),
    289        input({ type: "submit", style: { display: "none" } })
    290      )
    291    );
    292  }
    293 
    294  renderEmptyListNotice() {
    295    return div(
    296      { className: "request-blocking-list-empty-notice" },
    297      div(
    298        { className: "request-blocking-notice-element" },
    299        REQUEST_BLOCKING_USAGE_NOTICE
    300      ),
    301      div(
    302        { className: "request-blocking-notice-element" },
    303        REQUEST_BLOCKING_ADD_NOTICE
    304      )
    305    );
    306  }
    307 
    308  render() {
    309    const { blockedUrls, addBlockedUrl } = this.props;
    310 
    311    return div(
    312      {
    313        className: "request-blocking-panel",
    314        onDragOver: e => {
    315          e.preventDefault();
    316        },
    317        onDrop: e => {
    318          e.preventDefault();
    319          const url = e.dataTransfer.getData("text/plain");
    320          addBlockedUrl(url);
    321          this.scrollToBottom();
    322        },
    323      },
    324      this.renderEnableBar(),
    325      this.renderBlockedList(),
    326      this.renderAddForm(),
    327      !blockedUrls.length && this.renderEmptyListNotice()
    328    );
    329  }
    330 }
    331 
    332 module.exports = connect(
    333  state => ({
    334    blockedUrls: state.requestBlocking.blockedUrls,
    335    blockingEnabled: state.requestBlocking.blockingEnabled,
    336    isDisplaying: state.ui.selectedActionBarTabId === PANELS.BLOCKING,
    337  }),
    338  dispatch => ({
    339    toggleBlockingEnabled: checked =>
    340      dispatch(Actions.toggleBlockingEnabled(checked)),
    341    addBlockedUrl: url => dispatch(Actions.addBlockedUrl(url)),
    342    removeBlockedUrl: url => dispatch(Actions.removeBlockedUrl(url)),
    343    toggleBlockedUrl: url => dispatch(Actions.toggleBlockedUrl(url)),
    344    removeAllBlockedUrls: () => dispatch(Actions.removeAllBlockedUrls()),
    345    enableAllBlockedUrls: () => dispatch(Actions.enableAllBlockedUrls()),
    346    disableAllBlockedUrls: () => dispatch(Actions.disableAllBlockedUrls()),
    347    updateBlockedUrl: (oldUrl, newUrl) =>
    348      dispatch(Actions.updateBlockedUrl(oldUrl, newUrl)),
    349  })
    350 )(RequestBlockingPanel);