diff options
author | Marco Cecchetti <marco.cecchetti@collabora.com> | 2023-07-10 12:14:17 +0200 |
---|---|---|
committer | Marco Cecchetti <mrcekets@gmail.com> | 2023-08-10 09:29:09 +0200 |
commit | 96a7cfb25eeeeabad75d4dbd17a698bdd432335b (patch) | |
tree | 6bc8eae2082280eae708a2899a516ec2fe8dfb2c | |
parent | Remove open_local_editor button in tablet view (diff) | |
download | online-96a7cfb25eeeeabad75d4dbd17a698bdd432335b.tar.gz online-96a7cfb25eeeeabad75d4dbd17a698bdd432335b.zip |
a11y: provide info about current table and cell to screen reader
When we get in one or more tables screen reader reports row and column
count.
When we get out one or more table screen report reports "out of table"
for each table.
When the fosused cell changes screen reader reports new row/col index.
Screen reader reports cell paragraph content, too.
Added also cypress tests for checking that the table/cell description
string is correct.
Signed-off-by: Marco Cecchetti <marco.cecchetti@collabora.com>
Change-Id: I078b29a2f76f91df479f75db76113c80405f7169
-rw-r--r-- | browser/src/layer/marker/A11yTextInput.js | 69 | ||||
-rw-r--r-- | browser/src/layer/tile/CanvasTileLayer.js | 10 | ||||
-rw-r--r-- | cypress_test/data/desktop/writer/table_accessibility.odt | bin | 0 -> 14065 bytes | |||
-rw-r--r-- | cypress_test/integration_tests/desktop/writer/table_accessibility_spec.js | 95 | ||||
-rw-r--r-- | kit/ChildSession.cpp | 5 |
5 files changed, 178 insertions, 1 deletions
diff --git a/browser/src/layer/marker/A11yTextInput.js b/browser/src/layer/marker/A11yTextInput.js index 01d18c03d9..cd30bb03c6 100644 --- a/browser/src/layer/marker/A11yTextInput.js +++ b/browser/src/layer/marker/A11yTextInput.js @@ -8,7 +8,7 @@ * text area itself. */ -/* global app */ +/* global app _ */ L.A11yTextInput = L.TextInput.extend({ initialize: function() { @@ -182,6 +182,73 @@ L.A11yTextInput = L.TextInput.extend({ } }, + _setDescription: function(text) { + window.app.console.log('setDescription: ' + text); + this._textArea.setAttribute('aria-description', text); + }, + + _updateTable: function(outCount, inList, row, col, rowSpan, colSpan) { + if (this._isDebugOn) { + window.app.console.log('_updateTable: ' + + '\n outCount: ' + outCount + + '\n inList: ' + inList.toString() + + '\n row: ' + row + ', rowSpan: ' + rowSpan + + '\n col: ' + col + ', colSpan: ' + colSpan + ); + } + + if (this._timeoutForTableDescription) + clearTimeout(this._timeoutForTableDescription); + + var eventDescription = ''; + if (outCount > 0 || inList.length > 0) { + this._lastRowIndex = 0; + this._lastColIndex = 0; + this._lastRowSpan = 1; + this._lastColSpan = 1; + } + for (var i = 0; i < outCount; i++) { + eventDescription += _('Out of table') + '. '; + } + for (i = 0; i < inList.length; i++) { + eventDescription += _('Table with') + ' ' + inList[i].rowCount + ' ' + _('rows') + ' ' + + _('and') + ' ' + inList[i].colCount + ' ' + _('columns') + '. '; + } + if (this._lastRowIndex !== row || this._lastRowSpan !== rowSpan) { + this._lastRowIndex = row; + eventDescription += _('Row') + ' ' + row; + if (this._lastRowSpan !== rowSpan) { + if (rowSpan > 1) { + eventDescription += ' ' + _('through') + ' ' + (row + rowSpan - 1) ; + } + this._lastRowSpan = rowSpan; + } + eventDescription += '. '; + } + if (this._lastColIndex !== col || this._lastColSpan !== colSpan) { + this._lastColIndex = col; + eventDescription += _('Column') + ' ' + col; + if (this._lastColSpan !== colSpan) { + if (colSpan > 1) { + eventDescription += ' ' + _('through') + ' ' + (col + colSpan - 1); + } + this._lastColSpan = colSpan; + } + eventDescription += '. '; + } + this._setDescription(eventDescription); + + var that = this; + this._timeoutForTableDescription = setTimeout(function() { + that._setDescription(''); + }, 1000); + }, + + onAccessibilityFocusedCellChanged: function(outCount, inList, row, col, rowSpan, colSpan, paragraph) { + this._setFocusedParagraph(paragraph.content, parseInt(paragraph.position), parseInt(paragraph.start), parseInt(paragraph.end)); + this._updateTable(outCount, inList, row + 1, col + 1, rowSpan, colSpan); + }, + // Check if a UTF-16 pair represents a Unicode code point _isSurrogatePair: function(hi, lo) { return hi >= 0xd800 && hi <= 0xdbff && lo >= 0xdc00 && lo <= 0xdfff; diff --git a/browser/src/layer/tile/CanvasTileLayer.js b/browser/src/layer/tile/CanvasTileLayer.js index 6202044ed1..feda5c2a45 100644 --- a/browser/src/layer/tile/CanvasTileLayer.js +++ b/browser/src/layer/tile/CanvasTileLayer.js @@ -1864,6 +1864,16 @@ L.CanvasTileLayer = L.Layer.extend({ obj = JSON.parse(textMsg.substring('a11ytextselectionchanged:'.length + 1)); this._map._textInput.onAccessibilityTextSelectionChanged(parseInt(obj.start), parseInt(obj.end)); } + else if (textMsg.startsWith('a11yfocusedcellchanged:')) { + obj = JSON.parse(textMsg.substring('a11yfocusedcellchanged:'.length + 1)); + var outCount = obj.outCount !== undefined ? parseInt(obj.outCount) : 0; + var inList = obj.inList !== undefined ? obj.inList : []; + var row = parseInt(obj.row); + var col = parseInt(obj.col); + var rowSpan = obj.rowSpan !== undefined ? parseInt(obj.rowSpan) : 1; + var colSpan = obj.colSpan !== undefined ? parseInt(obj.colSpan) : 1; + this._map._textInput.onAccessibilityFocusedCellChanged(outCount, inList, row, col, rowSpan, colSpan, obj.paragraph); + } else if (textMsg.startsWith('a11yfocusedparagraph:')) { obj = JSON.parse(textMsg.substring('a11yfocusedparagraph:'.length + 1)); this._map._textInput.setA11yFocusedParagraph(obj.content, parseInt(obj.position), parseInt(obj.start), parseInt(obj.end)); diff --git a/cypress_test/data/desktop/writer/table_accessibility.odt b/cypress_test/data/desktop/writer/table_accessibility.odt Binary files differnew file mode 100644 index 0000000000..aca7a9859b --- /dev/null +++ b/cypress_test/data/desktop/writer/table_accessibility.odt diff --git a/cypress_test/integration_tests/desktop/writer/table_accessibility_spec.js b/cypress_test/integration_tests/desktop/writer/table_accessibility_spec.js new file mode 100644 index 0000000000..1538008166 --- /dev/null +++ b/cypress_test/integration_tests/desktop/writer/table_accessibility_spec.js @@ -0,0 +1,95 @@ +/* global describe it cy beforeEach require afterEach */ + +var helper = require('../../common/helper'); +// var desktopHelper = require('../../common/desktop_helper'); +var ceHelper = require('../../common/contenteditable_helper'); + +describe(['taga11yenabled'], 'Table accessibility', function() { + var testFileName = 'table_accessibility.odt'; + + beforeEach(function () { + helper.beforeAll(testFileName, 'writer'); + cy.cGet('div.clipboard').as('clipboard'); + }); + + afterEach(function () { + helper.afterAll(testFileName, this.currentTest.state); + }); + + function checkCellDescription(expectedDescription) { + cy.wait(400); + cy.get('@clipboard').should('have.attr', 'aria-description', expectedDescription); + } + + it('Table content and cell address on navigation', function () { + ceHelper.checkPlainContent('Paragraph above.'); + ceHelper.moveCaret('down'); + checkCellDescription('Table with 4 rows and 3 columns. Row 1. Column 1. '); + ceHelper.checkPlainContent('Item 1.1'); + ceHelper.moveCaret('down'); + checkCellDescription('Row 2. '); + ceHelper.checkPlainContent('Item 2.1'); + ceHelper.moveCaret('down'); + checkCellDescription('Row 3 through 4. '); + ceHelper.checkPlainContent('Item 3.1'); + ceHelper.moveCaret('end'); + ceHelper.moveCaret('right'); + checkCellDescription('Row 3. Column 2 through 3. '); + ceHelper.checkPlainContent('Item 3.2'); + ceHelper.moveCaret('down'); + checkCellDescription('Row 4. '); + ceHelper.checkPlainContent('Item 4.2'); + ceHelper.moveCaret('down'); + checkCellDescription('Out of table. '); + ceHelper.checkPlainContent('Paragraph below.'); + ceHelper.moveCaret('up'); + checkCellDescription('Table with 4 rows and 3 columns. Row 3 through 4. Column 1. '); + ceHelper.checkPlainContent('Item 3.1'); + }); + + it('Nested table content and cell address on navigation', function () { + ceHelper.checkPlainContent('Paragraph above.'); + ceHelper.moveCaret('down'); + checkCellDescription('Table with 4 rows and 3 columns. Row 1. Column 1. '); + ceHelper.checkPlainContent('Item 1.1'); + ceHelper.moveCaret('end'); + ceHelper.moveCaret('right'); + checkCellDescription('Column 2. '); + ceHelper.checkPlainContent('Item 1.2'); + ceHelper.moveCaret('end'); + ceHelper.moveCaret('right'); + checkCellDescription('Column 3. '); + ceHelper.checkPlainContent('Item 1.3'); + ceHelper.moveCaret('down'); + checkCellDescription('Table with 2 rows and 2 columns. Row 1. Column 2. '); + ceHelper.checkPlainContent('Nested Cell 1.2'); + ceHelper.moveCaret('left'); + checkCellDescription('Column 1. '); + ceHelper.checkPlainContent('Nested Cell 1.1'); + ceHelper.moveCaret('home'); + ceHelper.moveCaret('left'); + checkCellDescription('Out of table. Row 2. Column 2. '); + ceHelper.checkPlainContent('Item 2.2'); + ceHelper.moveCaret('down'); + checkCellDescription('Row 3. Column 2 through 3. '); + ceHelper.checkPlainContent('Item 3.2'); + ceHelper.moveCaret('up'); + checkCellDescription('Row 2. Column 3. '); + ceHelper.checkPlainContent(''); + ceHelper.moveCaret('up'); + checkCellDescription('Table with 2 rows and 2 columns. Row 2. Column 1. '); + ceHelper.checkPlainContent('Nested Cell 2.1'); + ceHelper.moveCaret('end'); + ceHelper.moveCaret('right'); + checkCellDescription('Column 2. '); + ceHelper.checkPlainContent('Nested Cell 2.2'); + ceHelper.moveCaret('end'); + ceHelper.moveCaret('right'); + checkCellDescription('Out of table. Row 2. Column 3. '); + ceHelper.checkPlainContent(''); + ceHelper.moveCaret('end'); + ceHelper.moveCaret('right'); + checkCellDescription('Row 3 through 4. Column 1. '); + ceHelper.checkPlainContent('Item 3.1'); + }); +}); diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp index acb8905ab6..8cdabd8752 100644 --- a/kit/ChildSession.cpp +++ b/kit/ChildSession.cpp @@ -3117,6 +3117,11 @@ void ChildSession::loKitCallback(const int type, const std::string& payload) sendTextFrame("a11ytextselectionchanged: " + payload); break; } + case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED: + { + sendTextFrame("a11yfocusedcellchanged: " + payload); + break; + } case LOK_CALLBACK_COLOR_PALETTES: sendTextFrame("colorpalettes: " + payload); break; |