summaryrefslogtreecommitdiffstats
path: root/devtools/client/accessibility/components/AccessibilityTree.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/accessibility/components/AccessibilityTree.js')
-rw-r--r--devtools/client/accessibility/components/AccessibilityTree.js298
1 files changed, 298 insertions, 0 deletions
diff --git a/devtools/client/accessibility/components/AccessibilityTree.js b/devtools/client/accessibility/components/AccessibilityTree.js
new file mode 100644
index 0000000000..df67377283
--- /dev/null
+++ b/devtools/client/accessibility/components/AccessibilityTree.js
@@ -0,0 +1,298 @@
+/* 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("resource://devtools/client/shared/vendor/react.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const TreeView = createFactory(
+ require("resource://devtools/client/shared/components/tree/TreeView.js")
+);
+// Reps
+const {
+ MODE,
+} = require("resource://devtools/client/shared/components/reps/index.js");
+
+const {
+ fetchChildren,
+} = require("resource://devtools/client/accessibility/actions/accessibles.js");
+
+const {
+ L10N,
+} = require("resource://devtools/client/accessibility/utils/l10n.js");
+const {
+ isFiltered,
+} = require("resource://devtools/client/accessibility/utils/audit.js");
+const AccessibilityRow = createFactory(
+ require("resource://devtools/client/accessibility/components/AccessibilityRow.js")
+);
+const AccessibilityRowValue = createFactory(
+ require("resource://devtools/client/accessibility/components/AccessibilityRowValue.js")
+);
+const {
+ Provider,
+} = require("resource://devtools/client/accessibility/provider.js");
+
+const {
+ scrollIntoView,
+} = require("resource://devtools/client/shared/scroll.js");
+
+/**
+ * Renders Accessibility panel tree.
+ */
+class AccessibilityTree extends Component {
+ static get propTypes() {
+ return {
+ toolboxDoc: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ accessibles: PropTypes.object,
+ expanded: PropTypes.object,
+ selected: PropTypes.string,
+ highlighted: PropTypes.object,
+ filtered: PropTypes.bool,
+ getAccessibilityTreeRoot: PropTypes.func.isRequired,
+ startListeningForAccessibilityEvents: PropTypes.func.isRequired,
+ stopListeningForAccessibilityEvents: PropTypes.func.isRequired,
+ highlightAccessible: PropTypes.func.isRequired,
+ unhighlightAccessible: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.onNameChange = this.onNameChange.bind(this);
+ this.onReorder = this.onReorder.bind(this);
+ this.onTextChange = this.onTextChange.bind(this);
+ this.renderValue = this.renderValue.bind(this);
+ this.scrollSelectedRowIntoView = this.scrollSelectedRowIntoView.bind(this);
+ }
+
+ /**
+ * Add accessibility event listeners that affect tree rendering and updates.
+ */
+ // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
+ UNSAFE_componentWillMount() {
+ this.props.startListeningForAccessibilityEvents({
+ reorder: this.onReorder,
+ "name-change": this.onNameChange,
+ "text-change": this.onTextChange,
+ });
+ window.on(
+ EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED,
+ this.scrollSelectedRowIntoView
+ );
+ return null;
+ }
+
+ componentDidUpdate(prevProps) {
+ // When filtering is toggled, make sure that the selected row remains in
+ // view.
+ if (this.props.filtered !== prevProps.filtered) {
+ this.scrollSelectedRowIntoView();
+ }
+
+ window.emit(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED);
+ }
+
+ /**
+ * Remove accessible event listeners.
+ */
+ componentWillUnmount() {
+ this.props.stopListeningForAccessibilityEvents({
+ reorder: this.onReorder,
+ "name-change": this.onNameChange,
+ "text-change": this.onTextChange,
+ });
+
+ window.off(
+ EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED,
+ this.scrollSelectedRowIntoView
+ );
+ }
+
+ /**
+ * 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} accessibleFront
+ * accessible front that had its subtree reordered.
+ */
+ onReorder(accessibleFront) {
+ if (this.props.accessibles.has(accessibleFront.actorID)) {
+ this.props.dispatch(fetchChildren(accessibleFront));
+ }
+ }
+
+ scrollSelectedRowIntoView() {
+ const { treeview } = this.refs;
+ if (!treeview) {
+ return;
+ }
+
+ const treeEl = treeview.treeRef.current;
+ if (!treeEl) {
+ return;
+ }
+
+ const selected = treeEl.ownerDocument.querySelector(
+ ".treeTable .treeRow.selected"
+ );
+ if (selected) {
+ scrollIntoView(selected, { center: true });
+ }
+ }
+
+ /**
+ * 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} accessibleFront
+ * accessible front that had its name changed.
+ * @param {Object} parentFront
+ * optional parent accessible front. 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(accessibleFront, parentFront) {
+ const { accessibles, dispatch } = this.props;
+ const accessibleWalkerFront = accessibleFront.getParent();
+ parentFront = parentFront || accessibleWalkerFront;
+
+ if (
+ accessibles.has(accessibleFront.actorID) ||
+ accessibles.has(parentFront.actorID)
+ ) {
+ dispatch(fetchChildren(parentFront));
+ }
+ }
+
+ /**
+ * 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} accessibleFront
+ * accessible front that had its child text changed.
+ */
+ onTextChange(accessibleFront) {
+ const { accessibles, dispatch } = this.props;
+ if (accessibles.has(accessibleFront.actorID)) {
+ dispatch(fetchChildren(accessibleFront));
+ }
+ }
+
+ renderValue(props) {
+ return AccessibilityRowValue(props);
+ }
+
+ /**
+ * 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,
+ toolboxDoc,
+ filtered,
+ getAccessibilityTreeRoot,
+ highlightAccessible,
+ unhighlightAccessible,
+ } = this.props;
+
+ const renderRow = rowProps => {
+ const { object } = rowProps.member;
+ const highlighted = object === highlightedItem;
+ return AccessibilityRow(
+ Object.assign({}, rowProps, {
+ toolboxDoc,
+ highlighted,
+ decorator: {
+ getRowClass() {
+ return highlighted ? ["highlighted"] : [];
+ },
+ },
+ highlightAccessible,
+ unhighlightAccessible,
+ })
+ );
+ };
+ const className = filtered ? "filtered" : undefined;
+
+ return TreeView({
+ ref: "treeview",
+ object: getAccessibilityTreeRoot(),
+ mode: MODE.SHORT,
+ provider: new Provider(accessibles, filtered, dispatch),
+ columns,
+ className,
+ renderValue: this.renderValue,
+ renderRow,
+ label: L10N.getStr("accessibility.treeName"),
+ header: true,
+ expandedNodes: expanded,
+ selected,
+ onClickRow(nodePath, event) {
+ if (event.target.classList.contains("theme-twisty")) {
+ this.toggle(nodePath);
+ }
+
+ this.selectRow(
+ this.rows.find(row => row.props.member.path === nodePath),
+ { preventAutoScroll: true }
+ );
+
+ return true;
+ },
+ onContextMenuTree(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: { expanded, selected, highlighted },
+ audit: { filters },
+}) => ({
+ accessibles,
+ expanded,
+ selected,
+ highlighted,
+ filtered: isFiltered(filters),
+});
+// Exports from this module
+module.exports = connect(mapStateToProps)(AccessibilityTree);