tor-browser

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

PropertiesView.js (7543B)


      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 /* eslint-disable react/prop-types */
      6 
      7 "use strict";
      8 
      9 const {
     10  Component,
     11  createFactory,
     12 } = require("resource://devtools/client/shared/vendor/react.mjs");
     13 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     14 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     15 const {
     16  connect,
     17 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     18 const {
     19  setTargetSearchResult,
     20 } = require("resource://devtools/client/netmonitor/src/actions/search.js");
     21 
     22 // Components
     23 const TreeViewClass = ChromeUtils.importESModule(
     24  "resource://devtools/client/shared/components/tree/TreeView.mjs"
     25 ).default;
     26 const TreeView = createFactory(TreeViewClass);
     27 const PropertiesViewContextMenu = require("resource://devtools/client/netmonitor/src/widgets/PropertiesViewContextMenu.js");
     28 
     29 loader.lazyGetter(this, "Rep", function () {
     30  return ChromeUtils.importESModule(
     31    "resource://devtools/client/shared/components/reps/index.mjs"
     32  ).REPS.Rep;
     33 });
     34 loader.lazyGetter(this, "MODE", function () {
     35  return ChromeUtils.importESModule(
     36    "resource://devtools/client/shared/components/reps/index.mjs"
     37  ).MODE;
     38 });
     39 
     40 // Constants
     41 const {
     42  AUTO_EXPAND_MAX_LEVEL,
     43  AUTO_EXPAND_MAX_NODES,
     44 } = require("resource://devtools/client/netmonitor/src/constants.js");
     45 
     46 const { div } = dom;
     47 
     48 /**
     49 * Properties View component
     50 * A scrollable tree view component which provides some useful features for
     51 * representing object properties.
     52 *
     53 * Tree view
     54 * Rep
     55 */
     56 class PropertiesView extends Component {
     57  static get propTypes() {
     58    return {
     59      object: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
     60      provider: PropTypes.object,
     61      enableInput: PropTypes.bool,
     62      expandableStrings: PropTypes.bool,
     63      expandedNodes: PropTypes.object,
     64      useBaseTreeViewExpand: PropTypes.bool,
     65      filterText: PropTypes.string,
     66      cropLimit: PropTypes.number,
     67      targetSearchResult: PropTypes.object,
     68      resetTargetSearchResult: PropTypes.func,
     69      selectPath: PropTypes.func,
     70      mode: PropTypes.symbol,
     71      defaultSelectFirstNode: PropTypes.bool,
     72      useQuotes: PropTypes.bool,
     73      onClickRow: PropTypes.func,
     74      contextMenuFormatters: PropTypes.object,
     75    };
     76  }
     77 
     78  static get defaultProps() {
     79    return {
     80      enableInput: true,
     81      enableFilter: true,
     82      expandableStrings: false,
     83      cropLimit: 1024,
     84      useQuotes: true,
     85      contextMenuFormatters: {},
     86      useBaseTreeViewExpand: false,
     87    };
     88  }
     89 
     90  constructor(props) {
     91    super(props);
     92    this.onFilter = this.onFilter.bind(this);
     93    this.renderValueWithRep = this.renderValueWithRep.bind(this);
     94    this.getSelectedPath = this.getSelectedPath.bind(this);
     95 
     96    this.expandedNodes = new Set();
     97  }
     98 
     99  /**
    100   * Update only if:
    101   * 1) The rendered object has changed
    102   * 2) The filter text has changed
    103   * 3) The user selected another search result target.
    104   */
    105  shouldComponentUpdate(nextProps) {
    106    return (
    107      this.props.object !== nextProps.object ||
    108      this.props.filterText !== nextProps.filterText ||
    109      (this.props.targetSearchResult !== nextProps.targetSearchResult &&
    110        nextProps.targetSearchResult !== null)
    111    );
    112  }
    113 
    114  onFilter(props) {
    115    const { name, value } = props;
    116    const { filterText } = this.props;
    117 
    118    if (!filterText) {
    119      return true;
    120    }
    121 
    122    const jsonString = JSON.stringify({ [name]: value }).toLowerCase();
    123    return jsonString.includes(filterText.toLowerCase());
    124  }
    125 
    126  getSelectedPath(targetSearchResult) {
    127    if (!targetSearchResult) {
    128      return null;
    129    }
    130 
    131    return `/${targetSearchResult.label}`;
    132  }
    133 
    134  /**
    135   * If target is selected, let's scroll the content
    136   * so the property is visible. This is used for search result navigation,
    137   * which happens when the user clicks on a search result.
    138   */
    139  scrollSelectedIntoView() {
    140    const { targetSearchResult, resetTargetSearchResult, selectPath } =
    141      this.props;
    142    if (!targetSearchResult) {
    143      return;
    144    }
    145 
    146    const path =
    147      typeof selectPath == "function"
    148        ? selectPath(targetSearchResult)
    149        : this.getSelectedPath(targetSearchResult);
    150    const element = document.getElementById(path);
    151    if (element) {
    152      element.scrollIntoView({ block: "center" });
    153    }
    154 
    155    resetTargetSearchResult();
    156  }
    157 
    158  onContextMenuRow(member, evt) {
    159    evt.preventDefault();
    160 
    161    const { object } = member;
    162 
    163    // Select the right clicked row
    164    this.selectRow({ props: { member } });
    165 
    166    // if data exists and can be copied, then show the contextmenu
    167    if (typeof object === "object") {
    168      if (!this.contextMenu) {
    169        this.contextMenu = new PropertiesViewContextMenu({
    170          customFormatters: this.props.contextMenuFormatters,
    171        });
    172      }
    173      this.contextMenu.open(evt, window.getSelection(), {
    174        member,
    175        object: this.props.object,
    176      });
    177    }
    178  }
    179 
    180  renderValueWithRep(props) {
    181    const { member } = props;
    182 
    183    /* Hide strings with following conditions
    184     * - the `value` object has a `value` property (only happens in Cookies panel)
    185     */
    186    if (typeof member.value === "object" && member.value?.value) {
    187      return null;
    188    }
    189 
    190    return Rep(
    191      Object.assign(props, {
    192        // FIXME: A workaround for the issue in StringRep
    193        // Force StringRep to crop the text every time
    194        member: Object.assign({}, member, { open: false }),
    195        mode: this.props.mode || MODE.TINY,
    196        cropLimit: this.props.cropLimit,
    197        noGrip: true,
    198      })
    199    );
    200  }
    201 
    202  render() {
    203    const {
    204      useBaseTreeViewExpand,
    205      expandedNodes,
    206      object,
    207      renderValue,
    208      targetSearchResult,
    209      selectPath,
    210    } = this.props;
    211 
    212    let currentExpandedNodes;
    213    // In the TreeView, when the component is re-rendered
    214    // the state of `expandedNodes` is persisted by default
    215    // e.g. when you open a node and filter the properties list,
    216    // the node remains open.
    217    // We have the prop `useBaseTreeViewExpand` to flag when we want to use
    218    // this functionality or not.
    219    if (!useBaseTreeViewExpand) {
    220      currentExpandedNodes =
    221        expandedNodes ||
    222        TreeViewClass.getExpandedNodes(object, {
    223          maxLevel: AUTO_EXPAND_MAX_LEVEL,
    224          maxNodes: AUTO_EXPAND_MAX_NODES,
    225        });
    226    } else {
    227      // Ensure passing a stable expanded Set,
    228      // so that TreeView doesn't reset to default prop's Set
    229      // on each new received props.
    230      currentExpandedNodes = this.expandedNodes;
    231    }
    232    return div(
    233      { className: "properties-view" },
    234      div(
    235        { className: "tree-container" },
    236        TreeView({
    237          ...this.props,
    238          ref: () => this.scrollSelectedIntoView(),
    239          columns: [{ id: "value", width: "100%" }],
    240 
    241          expandedNodes: currentExpandedNodes,
    242 
    243          onFilter: props => this.onFilter(props),
    244          renderValue: renderValue || this.renderValueWithRep,
    245          onContextMenuRow: this.onContextMenuRow,
    246          selected:
    247            typeof selectPath == "function"
    248              ? selectPath(targetSearchResult)
    249              : this.getSelectedPath(targetSearchResult),
    250        })
    251      )
    252    );
    253  }
    254 }
    255 
    256 module.exports = connect(null, dispatch => ({
    257  resetTargetSearchResult: () => dispatch(setTargetSearchResult(null)),
    258 }))(PropertiesView);