tor-browser

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

AccessibilityTree.js (8616B)


      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 "use strict";
      5 
      6 /* global EVENTS */
      7 
      8 // React & Redux
      9 const {
     10  Component,
     11  createFactory,
     12 } = require("resource://devtools/client/shared/vendor/react.mjs");
     13 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     14 const {
     15  connect,
     16 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     17 
     18 const TreeView = createFactory(
     19  ChromeUtils.importESModule(
     20    "resource://devtools/client/shared/components/tree/TreeView.mjs"
     21  ).default
     22 );
     23 // Reps
     24 const { MODE } = ChromeUtils.importESModule(
     25  "resource://devtools/client/shared/components/reps/index.mjs"
     26 );
     27 
     28 const {
     29  fetchChildren,
     30 } = require("resource://devtools/client/accessibility/actions/accessibles.js");
     31 
     32 const {
     33  L10N,
     34 } = require("resource://devtools/client/accessibility/utils/l10n.js");
     35 const {
     36  isFiltered,
     37 } = require("resource://devtools/client/accessibility/utils/audit.js");
     38 const AccessibilityRow = createFactory(
     39  require("resource://devtools/client/accessibility/components/AccessibilityRow.js")
     40 );
     41 const AccessibilityRowValue = createFactory(
     42  require("resource://devtools/client/accessibility/components/AccessibilityRowValue.js")
     43 );
     44 const {
     45  Provider,
     46 } = require("resource://devtools/client/accessibility/provider.js");
     47 
     48 const { scrollIntoView } = ChromeUtils.importESModule(
     49  "resource://devtools/client/shared/scroll.mjs"
     50 );
     51 
     52 /**
     53 * Renders Accessibility panel tree.
     54 */
     55 class AccessibilityTree extends Component {
     56  static get propTypes() {
     57    return {
     58      toolboxDoc: PropTypes.object.isRequired,
     59      dispatch: PropTypes.func.isRequired,
     60      accessibles: PropTypes.object,
     61      expanded: PropTypes.object,
     62      selected: PropTypes.string,
     63      highlighted: PropTypes.object,
     64      filtered: PropTypes.bool,
     65      getAccessibilityTreeRoot: PropTypes.func.isRequired,
     66      startListeningForAccessibilityEvents: PropTypes.func.isRequired,
     67      stopListeningForAccessibilityEvents: PropTypes.func.isRequired,
     68      highlightAccessible: PropTypes.func.isRequired,
     69      unhighlightAccessible: PropTypes.func.isRequired,
     70    };
     71  }
     72 
     73  constructor(props) {
     74    super(props);
     75 
     76    this.onNameChange = this.onNameChange.bind(this);
     77    this.onReorder = this.onReorder.bind(this);
     78    this.onTextChange = this.onTextChange.bind(this);
     79    this.renderValue = this.renderValue.bind(this);
     80    this.scrollSelectedRowIntoView = this.scrollSelectedRowIntoView.bind(this);
     81  }
     82 
     83  /**
     84   * Add accessibility event listeners that affect tree rendering and updates.
     85   */
     86  // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
     87  UNSAFE_componentWillMount() {
     88    this.props.startListeningForAccessibilityEvents({
     89      reorder: this.onReorder,
     90      "name-change": this.onNameChange,
     91      "text-change": this.onTextChange,
     92    });
     93    window.on(
     94      EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED,
     95      this.scrollSelectedRowIntoView
     96    );
     97    return null;
     98  }
     99 
    100  componentDidUpdate(prevProps) {
    101    // When filtering is toggled, make sure that the selected row remains in
    102    // view.
    103    if (this.props.filtered !== prevProps.filtered) {
    104      this.scrollSelectedRowIntoView();
    105    }
    106 
    107    window.emit(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED);
    108  }
    109 
    110  /**
    111   * Remove accessible event listeners.
    112   */
    113  componentWillUnmount() {
    114    this.props.stopListeningForAccessibilityEvents({
    115      reorder: this.onReorder,
    116      "name-change": this.onNameChange,
    117      "text-change": this.onTextChange,
    118    });
    119 
    120    window.off(
    121      EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED,
    122      this.scrollSelectedRowIntoView
    123    );
    124  }
    125 
    126  /**
    127   * Handle accessible reorder event. If the accessible is cached and rendered
    128   * within the accessibility tree, re-fetch its children and re-render the
    129   * corresponding subtree.
    130   *
    131   * @param {object} accessibleFront
    132   *        accessible front that had its subtree reordered.
    133   */
    134  onReorder(accessibleFront) {
    135    if (this.props.accessibles.has(accessibleFront.actorID)) {
    136      this.props.dispatch(fetchChildren(accessibleFront));
    137    }
    138  }
    139 
    140  scrollSelectedRowIntoView() {
    141    const { treeview } = this.refs;
    142    if (!treeview) {
    143      return;
    144    }
    145 
    146    const treeEl = treeview.treeRef.current;
    147    if (!treeEl) {
    148      return;
    149    }
    150 
    151    const selected = treeEl.ownerDocument.querySelector(
    152      ".treeTable .treeRow.selected"
    153    );
    154    if (selected) {
    155      scrollIntoView(selected, { center: true });
    156    }
    157  }
    158 
    159  /**
    160   * Handle accessible name change event. If the name of an accessible changes
    161   * and that accessible is cached and rendered within the accessibility tree,
    162   * re-fetch its parent's children and re-render the corresponding subtree.
    163   *
    164   * @param {object} accessibleFront
    165   *        accessible front that had its name changed.
    166   * @param {object} parentFront
    167   *        optional parent accessible front. Note: if it parent is not
    168   *        present, we assume that the top level document's name has changed
    169   *        and use accessible walker as a parent.
    170   */
    171  onNameChange(accessibleFront, parentFront) {
    172    const { accessibles, dispatch } = this.props;
    173    const accessibleWalkerFront = accessibleFront.getParent();
    174    parentFront = parentFront || accessibleWalkerFront;
    175 
    176    if (
    177      accessibles.has(accessibleFront.actorID) ||
    178      accessibles.has(parentFront.actorID)
    179    ) {
    180      dispatch(fetchChildren(parentFront));
    181    }
    182  }
    183 
    184  /**
    185   * Handle accessible text change (change/insert/remove) event. If the text of
    186   * an accessible changes and that accessible is cached and rendered within the
    187   * accessibility tree, re-fetch its children and re-render the corresponding
    188   * subtree.
    189   *
    190   * @param  {object} accessibleFront
    191   *         accessible front that had its child text changed.
    192   */
    193  onTextChange(accessibleFront) {
    194    const { accessibles, dispatch } = this.props;
    195    if (accessibles.has(accessibleFront.actorID)) {
    196      dispatch(fetchChildren(accessibleFront));
    197    }
    198  }
    199 
    200  renderValue(props) {
    201    return AccessibilityRowValue(props);
    202  }
    203 
    204  /**
    205   * Render Accessibility panel content
    206   */
    207  render() {
    208    const columns = [
    209      {
    210        id: "default",
    211        title: L10N.getStr("accessibility.role"),
    212      },
    213      {
    214        id: "value",
    215        title: L10N.getStr("accessibility.name"),
    216      },
    217    ];
    218 
    219    const {
    220      accessibles,
    221      dispatch,
    222      expanded,
    223      selected,
    224      highlighted: highlightedItem,
    225      toolboxDoc,
    226      filtered,
    227      getAccessibilityTreeRoot,
    228      highlightAccessible,
    229      unhighlightAccessible,
    230    } = this.props;
    231 
    232    const renderRow = rowProps => {
    233      const { object } = rowProps.member;
    234      const highlighted = object === highlightedItem;
    235      return AccessibilityRow(
    236        Object.assign({}, rowProps, {
    237          toolboxDoc,
    238          highlighted,
    239          decorator: {
    240            getRowClass() {
    241              return highlighted ? ["highlighted"] : [];
    242            },
    243          },
    244          highlightAccessible,
    245          unhighlightAccessible,
    246        })
    247      );
    248    };
    249    const className = filtered ? "filtered" : undefined;
    250 
    251    return TreeView({
    252      ref: "treeview",
    253      object: getAccessibilityTreeRoot(),
    254      mode: MODE.SHORT,
    255      provider: new Provider(accessibles, filtered, dispatch),
    256      columns,
    257      className,
    258      renderValue: this.renderValue,
    259      renderRow,
    260      label: L10N.getStr("accessibility.treeName"),
    261      header: true,
    262      expandedNodes: expanded,
    263      selected,
    264      onClickRow(nodePath, event) {
    265        if (event.target.classList.contains("theme-twisty")) {
    266          this.toggle(nodePath);
    267        }
    268 
    269        this.selectRow(
    270          this.rows.find(row => row.props.member.path === nodePath),
    271          { preventAutoScroll: true }
    272        );
    273 
    274        return true;
    275      },
    276      onContextMenuTree(e) {
    277        // If context menu event is triggered on (or bubbled to) the TreeView, it was
    278        // done via keyboard. Open context menu for currently selected row.
    279        let row = this.getSelectedRow();
    280        if (!row) {
    281          return;
    282        }
    283 
    284        row = row.getWrappedInstance();
    285        row.onContextMenu(e);
    286      },
    287    });
    288  }
    289 }
    290 
    291 const mapStateToProps = ({
    292  accessibles,
    293  ui: { expanded, selected, highlighted },
    294  audit: { filters },
    295 }) => ({
    296  accessibles,
    297  expanded,
    298  selected,
    299  highlighted,
    300  filtered: isFiltered(filters),
    301 });
    302 // Exports from this module
    303 module.exports = connect(mapStateToProps)(AccessibilityTree);