tor-browser

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

SourcesTreeItem.js (8053B)


      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 { div, span } from "devtools/client/shared/vendor/react-dom-factories";
      7 import PropTypes from "devtools/client/shared/vendor/react-prop-types";
      8 import { connect } from "devtools/client/shared/vendor/react-redux";
      9 
     10 import SourceIcon from "../shared/SourceIcon";
     11 import DebuggerImage from "../shared/DebuggerImage";
     12 
     13 import {
     14  getHideIgnoredSources,
     15  isSourceOverridden,
     16  getGeneratedSourceByURL,
     17 } from "../../selectors/index";
     18 import actions from "../../actions/index";
     19 
     20 import { sourceTypes } from "../../utils/source";
     21 import { createLocation } from "../../utils/location";
     22 
     23 const classnames = require("resource://devtools/client/shared/classnames.js");
     24 
     25 class SourceTreeItemContents extends Component {
     26  static get propTypes() {
     27    return {
     28      autoExpand: PropTypes.bool.isRequired,
     29      depth: PropTypes.number.isRequired,
     30      expanded: PropTypes.bool.isRequired,
     31      focusItem: PropTypes.func.isRequired,
     32      focused: PropTypes.bool.isRequired,
     33      hasMatchingGeneratedSource: PropTypes.bool,
     34      item: PropTypes.object.isRequired,
     35      selectSourceItem: PropTypes.func.isRequired,
     36      setExpanded: PropTypes.func.isRequired,
     37      getParent: PropTypes.func.isRequired,
     38      hideIgnoredSources: PropTypes.bool,
     39      arrow: PropTypes.object,
     40    };
     41  }
     42 
     43  componentDidMount() {
     44    const { autoExpand, item } = this.props;
     45    if (autoExpand) {
     46      this.props.setExpanded(item, true, false);
     47    }
     48  }
     49 
     50  onClick = () => {
     51    const { item, focusItem, selectSourceItem } = this.props;
     52 
     53    focusItem(item);
     54    if (item.type == "source") {
     55      selectSourceItem(item);
     56    }
     57  };
     58 
     59  onContextMenu = event => {
     60    event.stopPropagation();
     61    event.preventDefault();
     62    this.props.showSourceTreeItemContextMenu(
     63      event,
     64      this.props.item,
     65      this.props.depth,
     66      this.props.setExpanded,
     67      this.renderItemName(),
     68      this.props.isSourceOverridden
     69    );
     70  };
     71 
     72  renderIcon(item) {
     73    if (item.type == "thread") {
     74      const icon = item.thread.targetType.includes("worker")
     75        ? "worker"
     76        : "window";
     77      return React.createElement(DebuggerImage, {
     78        name: icon,
     79      });
     80    }
     81    if (item.type == "group") {
     82      if (item.groupName === "Webpack") {
     83        return React.createElement(DebuggerImage, {
     84          name: "webpack",
     85        });
     86      } else if (item.groupName === "Angular") {
     87        return React.createElement(DebuggerImage, {
     88          name: "angular",
     89        });
     90      }
     91      // Check if the group relates to an extension.
     92      // This happens when a webextension injects a content script.
     93      if (item.isForExtensionSource) {
     94        return React.createElement(DebuggerImage, {
     95          name: "extension",
     96        });
     97      }
     98      return React.createElement(DebuggerImage, {
     99        name: "globe-small",
    100      });
    101    }
    102    if (item.type == "directory") {
    103      return React.createElement(DebuggerImage, {
    104        name: "folder",
    105      });
    106    }
    107    if (item.type == "source") {
    108      const { source, sourceActor } = item;
    109      return React.createElement(SourceIcon, {
    110        location: createLocation({
    111          source,
    112          sourceActor,
    113        }),
    114        modifier: icon => {
    115          // In the SourceTree, extension files should use the file-extension based icon,
    116          // whereas we use the extension icon in other Components (eg. source tabs and breakpoints pane).
    117          if (icon === "extension") {
    118            return sourceTypes[source.displayURL.fileExtension] || "javascript";
    119          }
    120          return (
    121            icon +
    122            (this.props.isSourceOverridden ? " has-network-override" : "")
    123          );
    124        },
    125      });
    126    }
    127    return null;
    128  }
    129  renderItemName() {
    130    const { item } = this.props;
    131 
    132    if (item.type == "thread") {
    133      const { thread } = item;
    134      return (
    135        thread.name +
    136        (thread.serviceWorkerStatus ? ` (${thread.serviceWorkerStatus})` : "")
    137      );
    138    }
    139    if (item.type == "group") {
    140      return item.groupName;
    141    }
    142    if (item.type == "directory") {
    143      const parentItem = this.props.getParent(item);
    144      return item.path.replace(parentItem.path, "").replace(/^\//, "");
    145    }
    146    if (item.type == "source") {
    147      return item.source.longName;
    148    }
    149 
    150    return null;
    151  }
    152 
    153  renderItemTooltip() {
    154    const { item } = this.props;
    155 
    156    if (item.type == "thread") {
    157      return item.thread.name;
    158    }
    159    if (item.type == "group") {
    160      return item.groupName;
    161    }
    162    if (item.type == "directory") {
    163      return item.path;
    164    }
    165    if (item.type == "source") {
    166      return item.source.url;
    167    }
    168 
    169    return null;
    170  }
    171 
    172  render() {
    173    const { item, focused, hasMatchingGeneratedSource, hideIgnoredSources } =
    174      this.props;
    175 
    176    if (hideIgnoredSources && item.isBlackBoxed) {
    177      return null;
    178    }
    179    const suffix = hasMatchingGeneratedSource
    180      ? span(
    181          {
    182            className: "suffix",
    183          },
    184          L10N.getStr("sourceFooter.mappedSuffix")
    185        )
    186      : null;
    187    return div(
    188      {
    189        className: classnames("node", {
    190          focused,
    191          blackboxed: item.type == "source" && item.isBlackBoxed,
    192        }),
    193        key: item.path,
    194        onClick: this.onClick,
    195        onContextMenu: this.onContextMenu,
    196        title: this.renderItemTooltip(),
    197      },
    198      this.props.arrow,
    199      this.renderIcon(item),
    200      span(
    201        {
    202          className: "label",
    203        },
    204        this.renderItemName(),
    205        suffix
    206      )
    207    );
    208  }
    209 }
    210 
    211 function getHasMatchingGeneratedSource(state, source) {
    212  if (!source || !source.isOriginal) {
    213    return false;
    214  }
    215 
    216  return !!getGeneratedSourceByURL(state, source.url);
    217 }
    218 
    219 const toolboxMapStateToProps = (state, props) => {
    220  const { item } = props;
    221  return {
    222    isSourceOverridden: isSourceOverridden(state, item.source),
    223  };
    224 };
    225 
    226 const SourceTreeItemInner = connect(toolboxMapStateToProps, {}, undefined, {
    227  storeKey: "toolbox-store",
    228 })(SourceTreeItemContents);
    229 
    230 class SourcesTreeItem extends Component {
    231  static get propTypes() {
    232    return {
    233      autoExpand: PropTypes.bool.isRequired,
    234      depth: PropTypes.bool.isRequired,
    235      expanded: PropTypes.bool.isRequired,
    236      focusItem: PropTypes.func.isRequired,
    237      focused: PropTypes.bool.isRequired,
    238      hasMatchingGeneratedSource: PropTypes.bool.isRequired,
    239      item: PropTypes.object.isRequired,
    240      selectSourceItem: PropTypes.func.isRequired,
    241      setExpanded: PropTypes.func.isRequired,
    242      showSourceTreeItemContextMenu: PropTypes.func.isRequired,
    243      getParent: PropTypes.func.isRequired,
    244      hideIgnoredSources: PropTypes.bool,
    245      arrow: PropTypes.object,
    246    };
    247  }
    248 
    249  render() {
    250    return React.createElement(SourceTreeItemInner, {
    251      autoExpand: this.props.autoExpand,
    252      depth: this.props.depth,
    253      expanded: this.props.expanded,
    254      focusItem: this.props.focusItem,
    255      focused: this.props.focused,
    256      hasMatchingGeneratedSource: this.props.hasMatchingGeneratedSource,
    257      item: this.props.item,
    258      selectSourceItem: this.props.selectSourceItem,
    259      setExpanded: this.props.setExpanded,
    260      showSourceTreeItemContextMenu: this.props.showSourceTreeItemContextMenu,
    261      getParent: this.props.getParent,
    262      hideIgnoredSources: this.props.hideIgnoredSources,
    263      arrow: this.props.arrow,
    264    });
    265  }
    266 }
    267 
    268 const mapStateToProps = (state, props) => {
    269  const { item } = props;
    270  if (item.type == "source") {
    271    const { source } = item;
    272    return {
    273      hasMatchingGeneratedSource: getHasMatchingGeneratedSource(state, source),
    274      hideIgnoredSources: getHideIgnoredSources(state),
    275    };
    276  }
    277  return {};
    278 };
    279 
    280 export default connect(mapStateToProps, {
    281  showSourceTreeItemContextMenu: actions.showSourceTreeItemContextMenu,
    282 })(SourcesTreeItem);