Files
tubestation/devtools/client/accessibility/components/AccessibilityTree.js
2019-01-19 18:13:55 +00:00

215 lines
6.4 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* global EVENTS */
// React & Redux
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const TreeView = createFactory(require("devtools/client/shared/components/tree/TreeView"));
// Reps
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
const Rep = createFactory(REPS.Rep);
const Grip = REPS.Grip;
const { fetchChildren } = require("../actions/accessibles");
const { L10N } = require("../utils/l10n");
const AccessibilityRow = createFactory(require("./AccessibilityRow"));
const { Provider } = require("../provider");
/**
* Renders Accessibility panel tree.
*/
class AccessibilityTree extends Component {
static get propTypes() {
return {
walker: PropTypes.object,
dispatch: PropTypes.func.isRequired,
accessibles: PropTypes.object,
expanded: PropTypes.object,
selected: PropTypes.string,
highlighted: PropTypes.object,
supports: PropTypes.object,
};
}
constructor(props) {
super(props);
this.onNameChange = this.onNameChange.bind(this);
this.onReorder = this.onReorder.bind(this);
this.onTextChange = this.onTextChange.bind(this);
}
/**
* Add accessibility walker front event listeners that affect tree rendering
* and updates.
*/
componentWillMount() {
const { walker } = this.props;
walker.on("reorder", this.onReorder);
walker.on("name-change", this.onNameChange);
walker.on("text-change", this.onTextChange);
return null;
}
componentDidUpdate() {
window.emit(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED);
}
/**
* Remove accessible walker front event listeners.
*/
componentWillUnmount() {
const { walker } = this.props;
walker.off("reorder", this.onReorder);
walker.off("name-change", this.onNameChange);
walker.off("text-change", this.onTextChange);
}
/**
* Handle accessible reorder event. If the accessible is cached and rendered
* within the accessibility tree, re-fetch its children and re-render the
* corresponding subtree.
* @param {Object} accessible accessible object that had its subtree
* reordered.
*/
onReorder(accessible) {
if (this.props.accessibles.has(accessible.actorID)) {
this.props.dispatch(fetchChildren(accessible));
}
}
/**
* Handle accessible name change event. If the name of an accessible changes
* and that accessible is cached and rendered within the accessibility tree,
* re-fetch its parent's children and re-render the corresponding subtree.
* @param {Object} accessible accessible object that had its name changed.
* @param {Object} parent optional parent accessible object. Note: if it
* parent is not present, we assume that the top
* level document's name has changed and use
* accessible walker as a parent.
*/
onNameChange(accessible, parent) {
const { accessibles, walker, dispatch } = this.props;
parent = parent || walker;
if (accessibles.has(accessible.actorID) ||
accessibles.has(parent.actorID)) {
dispatch(fetchChildren(parent));
}
}
/**
* Handle accessible text change (change/insert/remove) event. If the text of
* an accessible changes and that accessible is cached and rendered within the
* accessibility tree, re-fetch its children and re-render the corresponding
* subtree.
* @param {Object} accessible accessible object that had its child text
* changed.
*/
onTextChange(accessible) {
const { accessibles, dispatch } = this.props;
if (accessibles.has(accessible.actorID)) {
dispatch(fetchChildren(accessible));
}
}
/**
* Render Accessibility panel content
*/
render() {
const columns = [{
"id": "default",
"title": L10N.getStr("accessibility.role"),
}, {
"id": "value",
"title": L10N.getStr("accessibility.name"),
}];
const {
accessibles,
dispatch,
expanded,
selected,
highlighted: highlightedItem,
supports,
walker,
} = this.props;
// Historically, the first context menu item is snapshot function and it is available
// for all accessible object.
const hasContextMenu = supports.snapshot;
const renderValue = props => {
return Rep(Object.assign({}, props, {
defaultRep: Grip,
cropLimit: 50,
}));
};
const renderRow = rowProps => {
const { object } = rowProps.member;
const highlighted = object === highlightedItem;
return AccessibilityRow(Object.assign({}, rowProps, {
walker,
hasContextMenu,
highlighted,
decorator: {
getRowClass: function() {
return highlighted ? ["highlighted"] : [];
},
},
}));
};
return (
TreeView({
object: walker,
mode: MODE.SHORT,
provider: new Provider(accessibles, dispatch),
columns: columns,
renderValue,
renderRow,
label: L10N.getStr("accessibility.treeName"),
header: true,
expandedNodes: expanded,
selected,
onClickRow(nodePath, event) {
event.stopPropagation();
if (event.target.classList.contains("theme-twisty")) {
this.toggle(nodePath);
}
this.selectRow(event.currentTarget);
},
onContextMenuTree: hasContextMenu && function(e) {
// If context menu event is triggered on (or bubbled to) the TreeView, it was
// done via keyboard. Open context menu for currently selected row.
let row = this.getSelectedRow();
if (!row) {
return;
}
row = row.getWrappedInstance();
row.onContextMenu(e);
},
})
);
}
}
const mapStateToProps = ({ accessibles, ui }) => ({
accessibles,
expanded: ui.expanded,
selected: ui.selected,
supports: ui.supports,
highlighted: ui.highlighted,
});
// Exports from this module
module.exports = connect(mapStateToProps)(AccessibilityTree);