Bug 1550542 - fix a number of issues around aria-level within the accessibility tree. r=nchevobbe
authorYura Zenevich <yura.zenevich@gmail.com>
Tue, 14 May 2019 12:31:13 +0000
changeset 532602 fe4e40766efbace8751f3da5d9d3ba0588958202
parent 532601 230016dbba05b36ecc1ccada9abdc2d5370a0ae7
child 532603 99abc0a1fcf5384f663c0ab5606da09ae6b75b68
push id11270
push userrgurzau@mozilla.com
push dateWed, 15 May 2019 15:07:19 +0000
treeherdermozilla-beta@571bc76da583 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1550542
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1550542 - fix a number of issues around aria-level within the accessibility tree. r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D30594
devtools/client/accessibility/components/AccessibilityTree.js
devtools/client/accessibility/provider.js
devtools/client/accessibility/test/browser/browser_accessibility_tree_audit.js
devtools/client/accessibility/test/browser/head.js
devtools/client/shared/components/tree/TreeRow.js
devtools/client/shared/components/tree/TreeView.js
--- a/devtools/client/accessibility/components/AccessibilityTree.js
+++ b/devtools/client/accessibility/components/AccessibilityTree.js
@@ -179,17 +179,17 @@ class AccessibilityTree extends Componen
       }));
     };
     const className = filtered ? "filtered" : undefined;
 
     return (
       TreeView({
         object: walker,
         mode: MODE.SHORT,
-        provider: new Provider(accessibles, dispatch),
+        provider: new Provider(accessibles, filtered, dispatch),
         columns: columns,
         className,
         renderValue: this.renderValue,
         renderRow,
         label: L10N.getStr("accessibility.treeName"),
         header: true,
         expandedNodes: expanded,
         selected,
--- a/devtools/client/accessibility/provider.js
+++ b/devtools/client/accessibility/provider.js
@@ -9,18 +9,19 @@ const { fetchChildren } = require("./act
  * Data provider that is responsible for mapping of an accessibles cache to the
  * data format that is supported by the TreeView component.
  * @param {Map}      accessibles accessibles object cache
  * @param {Function} dispatch    react dispatch function that triggers a redux
  *                               action.
  */
 
 class Provider {
-  constructor(accessibles, dispatch) {
+  constructor(accessibles, filtered, dispatch) {
     this.accessibles = accessibles;
+    this.filtered = filtered;
     this.dispatch = dispatch;
   }
 
   /**
    * Get accessible's cached children if available, if not fetch them from
    * backend.
    * @param {Object}  accessible accessible object whose children to get.
    * @returns {Array} arraof of accessible children.
@@ -82,11 +83,28 @@ class Provider {
    * Get a type of an accesible object. Corresponds to the type of an accessible
    * front.
    * @param {Object}   accessible accessible object
    * @returns {String} accessible object type
    */
   getType(accessible) {
     return accessible.typeName;
   }
+
+  /**
+   * Get the depth of the accesible object in the accessibility tree. When the
+   * tree is filtered it is flattened and the level is set to 0. Otherwise use
+   * internal TreeView level.
+   *
+   * @param {Object}   accessible
+   *                   accessible object
+   * @param {Number}   defaultLevel
+   *                   default level provided by the TreeView component.
+   *
+   * @returns {null|Number}
+   *          depth level of the accessible object.
+   */
+  getLevel(accessible, defaultLevel) {
+    return this.filtered ? 0 : defaultLevel;
+  }
 }
 
 exports.Provider = Provider;
--- a/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit.js
+++ b/devtools/client/accessibility/test/browser/browser_accessibility_tree_audit.js
@@ -34,69 +34,80 @@ const tests = [{
   setup: async ({ doc }) => {
     await toggleRow(doc, 0);
     await toggleRow(doc, 1);
   },
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
+      level: 1,
     }, {
       role: "heading",
       name: `"Top level header"`,
+      level: 2,
     }, {
       role: "text leaf",
       name: `"Top level header "contrast`,
       badges: [ "contrast" ],
+      level: 3,
     }, {
       role: "heading",
       name: `"Second level header"`,
+      level: 2,
     }],
   },
 }, {
   desc: "Click on the contrast filter.",
   setup: async ({ doc }) => {
     await toggleFilter(doc, 0);
   },
   expected: {
     tree: [{
       role: "text leaf",
       name: `"Top level header "contrast`,
       badges: [ "contrast" ],
+      level: 1,
     }, {
       role: "text leaf",
       name: `"Second level header "contrast`,
       badges: [ "contrast" ],
       selected: true,
+      level: 1,
     }],
   },
 }, {
   desc: "Click on the contrast filter again.",
   setup: async ({ doc }) => {
     await toggleFilter(doc, 0);
   },
   expected: {
     tree: [{
       role: "document",
       name: `"Accessibility Panel Test"`,
+      level: 1,
     }, {
       role: "heading",
       name: `"Top level header"`,
+      level: 2,
     }, {
       role: "text leaf",
       name: `"Top level header "contrast`,
       badges: [ "contrast" ],
+      level: 3,
     }, {
       role: "heading",
       name: `"Second level header"`,
+      level: 2,
     }, {
       role: "text leaf",
       name: `"Second level header "contrast`,
       badges: [ "contrast" ],
       selected: true,
+      level: 3,
     }],
   },
 }];
 
 /**
  * Simple test that checks content of the Accessibility panel tree when one of
  * the tree rows has a "contrast" badge and auditing is activated via toolbar
  * filter.
--- a/devtools/client/accessibility/test/browser/head.js
+++ b/devtools/client/accessibility/test/browser/head.js
@@ -232,29 +232,48 @@ function checkSelected(row, expected) {
   if (row.classList.contains("selected") !== expected) {
     return false;
   }
 
   return isVisible(row);
 }
 
 /**
+ * Check level for a given row in the accessibility tree.
+ * @param   {DOMNode} row
+ *          DOMNode for a given accessibility row.
+ * @param   {Boolean} expected
+ *          Expected row level (aria-level).
+ *
+ * @returns {Boolean}
+ *          True if the aria-level for the row is as expected.
+ */
+function checkLevel(row, expected) {
+  if (!expected) {
+    return true;
+  }
+
+  return parseInt(row.getAttribute("aria-level"), 10) === expected;
+}
+
+/**
  * Check the state of the accessibility tree.
  * @param  {document} doc       panel documnent.
  * @param  {Array}    expected  an array that represents an expected row list.
  */
 async function checkTreeState(doc, expected) {
   info("Checking tree state.");
   const hasExpectedStructure = await BrowserTestUtils.waitForCondition(() =>
     [...doc.querySelectorAll(".treeRow")].every((row, i) => {
-      const { role, name, badges, selected } = expected[i];
+      const { role, name, badges, selected, level } = expected[i];
       return row.querySelector(".treeLabelCell").textContent === role &&
         row.querySelector(".treeValueCell").textContent === name &&
         compareBadges(row.querySelector(".badges"), badges) &&
-        checkSelected(row, selected);
+        checkSelected(row, selected) &&
+        checkLevel(row, level);
     }), "Wait for the right tree update.");
 
   ok(hasExpectedStructure, "Tree structure is correct.");
 }
 
 /**
  * Check if relations object matches what is expected. Note: targets are matched by their
  * name and role.
--- a/devtools/client/shared/components/tree/TreeRow.js
+++ b/devtools/client/shared/components/tree/TreeRow.js
@@ -23,16 +23,17 @@ define(function(require, exports, module
 
   const { focusableSelector } = require("devtools/client/shared/focus");
 
   const UPDATE_ON_PROPS = [
     "name",
     "open",
     "value",
     "loading",
+    "level",
     "selected",
     "active",
     "hasChildren",
   ];
 
   /**
    * This template represents a node in TreeView component. It's rendered
    * using <tr> element (the entire tree is one big <table>).
@@ -225,17 +226,17 @@ define(function(require, exports, module
     render() {
       const member = this.props.member;
       const decorator = this.props.decorator;
 
       const props = {
         id: this.props.id,
         ref: this.treeRowRef,
         role: "treeitem",
-        "aria-level": member.level,
+        "aria-level": member.level + 1,
         "aria-selected": !!member.selected,
         onClick: this.props.onClick,
         onContextMenu: this.props.onContextMenu,
         onKeyDownCapture: member.active ? this._onKeyDown : undefined,
         onMouseOver: this.props.onMouseOver,
         onMouseOut: this.props.onMouseOut,
       };
 
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -60,16 +60,17 @@ define(function(require, exports, module
    * The tree is maintaining its (presentation) state, which consists
    * from list of expanded nodes and list of columns.
    *
    * Complete data provider interface:
    * var TreeProvider = {
    *   getChildren: function(object);
    *   hasChildren: function(object);
    *   getLabel: function(object, colId);
+   *   getLevel: function(object); // optional
    *   getValue: function(object, colId);
    *   getKey: function(object);
    *   getType: function(object);
    * }
    *
    * Complete tree decorator interface:
    * var TreeDecorator = {
    *   getRowClass: function(object);
@@ -516,17 +517,17 @@ define(function(require, exports, module
           object: child,
           // A label for the child node
           name: provider.getLabel(child),
           // Data type of the child node (used for CSS customization)
           type: type,
           // Class attribute computed from the type.
           rowClass: "treeRow-" + type,
           // Level of the child within the hierarchy (top == 0)
-          level: level,
+          level: provider.getLevel ? provider.getLevel(child, level) : level,
           // True if this node has children.
           hasChildren: hasChildren,
           // Value associated with this node (as provided by the data provider)
           value: value,
           // True if the node is expanded.
           open: this.isExpanded(nodePath),
           // Node path
           path: nodePath,