Bug 1833709 - Update message list ARIA role from listbox to tree if needed. r=darktrojan

**How to test this**
- Use a screen reader, or simply the code inspector
- Switch between threaded, unthreaded, and Grouped by Sort
- Ensure that the table body role changes between tree and listbox respecting this patter:
  - Threded: tree
  - Unthreded: listbox
  - Grouped by Sort: tree

I also improved a bit the handling of the dummy row.

Differential Revision: https://phabricator.services.mozilla.com/D179162

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alessandro Castellani 2023-05-31 15:20:39 +00:00
Родитель 9587905b77
Коммит f4a4eefe32
4 изменённых файлов: 98 добавлений и 2 удалений

Просмотреть файл

@ -2129,6 +2129,9 @@ var folderPane = {
// At this point `dbViewWrapperListener.onCreatedView` gets called,
// setting up gDBView and scrolling threadTree to the right end.
threadPane.updateListRole(
!gViewWrapper?.showThreaded && !gViewWrapper?.showGroupedBySort
);
threadPane.restoreSortIndicator();
threadPane.restoreSelection();
threadPaneHeader.onFolderSelected();
@ -4914,6 +4917,16 @@ var threadPane = {
break;
}
},
/**
* Update the ARIA Role of the tree view table body to properly communicate
* to assistive techonology the type of list we're rendering.
*
* @param {boolean} isListbox - If the list should have a listbox role.
*/
updateListRole(isListbox) {
threadTree.table.body.setAttribute("role", isListbox ? "listbox" : "tree");
},
};
var messagePane = {
@ -5316,6 +5329,18 @@ customElements.whenDefined("tree-view-table-row").then(() => {
let ariaLabelPromises = [];
const propertiesSet = new Set(properties.value.split(" "));
if (propertiesSet.has("dummy")) {
const cell = this.querySelector(".subjectcol-column");
const textIndex = textColumns.indexOf("subjectCol");
const label = cellTexts[textIndex];
const span = cell.querySelector(".subject-line span");
cell.title = span.textContent = label;
this.setAttribute("aria-label", label);
this.dataset.properties = "dummy";
return;
}
this.dataset.properties = properties.value.trim();
for (let column of threadPane.columns) {
@ -5775,6 +5800,7 @@ var sortController = {
}
},
sortByThread() {
threadPane.updateListRole(false);
gViewWrapper.showThreaded = true;
this.sortThreadPane("byDate");
},
@ -5856,18 +5882,23 @@ var sortController = {
},
toggleThreaded() {
if (gViewWrapper.showThreaded) {
threadPane.updateListRole(true);
gViewWrapper.showUnthreaded = true;
} else {
threadPane.updateListRole(false);
gViewWrapper.showThreaded = true;
}
},
sortThreaded() {
threadPane.updateListRole(false);
gViewWrapper.showThreaded = true;
},
groupBySort() {
threadPane.updateListRole(false);
gViewWrapper.showGroupedBySort = true;
},
sortUnthreaded() {
threadPane.updateListRole(true);
gViewWrapper.showUnthreaded = true;
},
sortAscending() {

Просмотреть файл

@ -2354,7 +2354,7 @@ class TreeViewTableBody extends HTMLTableSectionElement {
this.tabIndex = 0;
this.setAttribute("is", "tree-view-table-body");
this.setAttribute("role", "treeview");
this.setAttribute("role", "tree");
this.setAttribute("aria-multiselectable", "true");
let treeView = this.closest("tree-view");
@ -2412,11 +2412,16 @@ class TreeViewTableRow extends HTMLTableRowElement {
}
set index(index) {
this.setAttribute(
"role",
this.list.table.body.getAttribute("role") === "tree"
? "treeitem"
: "option"
);
this.setAttribute("aria-posinset", index + 1);
this.id = `${this.list.id}-row${index}`;
const isGroup = this.view.isContainer(index);
this.setAttribute("role", isGroup ? "group" : "treeitem");
this.classList.toggle("children", isGroup);
const isGroupOpen = this.view.isContainerOpen(index);

Просмотреть файл

@ -53,6 +53,17 @@ add_task(async function testSwitchToCardsView() {
"The tree view should not switch to a card layout"
);
Assert.equal(
threadTree.table.body.getAttribute("role"),
"tree",
"The message list table should be presented as Tree View"
);
Assert.equal(
threadTree.getRowAtIndex(0).getAttribute("role"),
"treeitem",
"The message row should be presented as Tree Item"
);
displayContext = about3Pane.document.getElementById(
"threadPaneDisplayContext"
);
@ -90,6 +101,16 @@ add_task(async function testSwitchToCardsView() {
"thread-card",
"tree view in cards layout"
);
Assert.equal(
threadTree.table.body.getAttribute("role"),
"tree",
"The message list table should remain as Tree View"
);
Assert.equal(
threadTree.getRowAtIndex(0).getAttribute("role"),
"treeitem",
"The message row should remain as Tree Item"
);
let row = threadTree.getRowAtIndex(0);
let star = row.querySelector(".button-star");

Просмотреть файл

@ -559,3 +559,42 @@ async function restoreMessages() {
sourceMessageIDs.indexOf(b.messageId)
);
}
add_task(async function testThreadTreeA11yRoles() {
Assert.equal(
threadTree.table.body.getAttribute("role"),
"listbox",
"The tree view should be presented as ListBox"
);
Assert.equal(
threadTree.getRowAtIndex(0).getAttribute("role"),
"option",
"The message row should be presented as Option"
);
about3Pane.sortController.sortThreaded();
await BrowserTestUtils.waitForCondition(
() => threadTree.table.body.getAttribute("role") == "tree",
"The tree view should switch to a Tree View role"
);
Assert.equal(
threadTree.getRowAtIndex(0).getAttribute("role"),
"treeitem",
"The message row should be presented as Tree Item"
);
about3Pane.sortController.groupBySort();
await BrowserTestUtils.waitForCondition(
() => threadTree.table.body.getAttribute("role") == "tree",
"The message list table should remain presented as Tree View"
);
Assert.equal(
threadTree.getRowAtIndex(0).getAttribute("role"),
"treeitem",
"The first dummy message row should be presented as Tree Item"
);
about3Pane.sortController.sortUnthreaded();
});