Skip to content
Permalink
Browse files

Fix table navigation commands in Google Docs (#9562)

* Allow table navigation in Google Docs.
Specifically:
* Expose the real IAccessible2 table coordinates for navigation via rowNumber, columnNumber, rowCount and columnCount NvDAObject properties, and controlField attributes, rather than IA2-specific table properties.
Add new optional properties to NVDAObjects: presentationalRowNumber, presentationalColumnNumber, presentationalRowCount and presentationColumnCount.
* Provide implementations of the presentational* properties for tables on NVDAObjects for IAccessible2 that uses rowindex and colindex IA2 aria attributes, allowing web authors to override how table coordinates are presented without confusing actual table navigation logic.
* Similarly for controlFields, expose table-* and table-*-presentational attributes allowing for overriding of table coordinate presentation when the web author has specified it.
* IA2TextInfo's updateSelection method: If the requested start and end offsets are the same (I.e. the caller wants a collapsed selection) call setCaretOffset instead. This gets around strange bugs in Google Ghrome / Google Docs where making collapsed selections in table cells selects the entire cell.

* Address review comments.

* Address review comments.

* Update what's new.
  • Loading branch information...
michaelDCurran committed May 15, 2019
1 parent 5cb7713 commit c20a503220b00bcce07252bf345692204b9165b4
@@ -113,18 +113,14 @@ static IAccessible2* IAccessible2FromIdentifier(int docHandle, int ID) {
template<typename TableType> inline void fillTableCounts(VBufStorage_controlFieldNode_t* node, IAccessible2* pacc, TableType* paccTable) {
wostringstream s;
long count = 0;
// Fetch row and column counts and add them as two sets of attributes on this vbuf node.
// The first set: table-physicalrowcount and table-physicalcolumncount represent the physical topology of the table and can be used programmatically to understand table limits.
// The second set: table-rowcount and table-columncount are duplicates of the physical ones, however may be overridden later on in fillVBuf with ARIA attributes. They are what is reported to the user.
// Fetch row and column counts and add them as attributes on this vbuf node.
if (paccTable->get_nRows(&count) == S_OK) {
s << count;
node->addAttribute(L"table-physicalrowcount", s.str());
node->addAttribute(L"table-rowcount", s.str());
s.str(L"");
}
if (paccTable->get_nColumns(&count) == S_OK) {
s << count;
node->addAttribute(L"table-physicalcolumncount", s.str());
node->addAttribute(L"table-columncount", s.str());
}
}
@@ -151,16 +147,11 @@ inline void fillTableCellInfo_IATable(VBufStorage_controlFieldNode_t* node, IAcc
long row, column, rowExtents, columnExtents;
boolean isSelected;
// Fetch row and column extents and add them as attributes on this node.
// for rowNumber and columnNumber, store these as two sets of attributes.
// The first set: table-physicalrownumber and table-physicalcolumnnumber represent the physical topology of the table and can be used programmatically to fetch other table cells with IAccessibleTable etc.
// The second set: table-rownumber and table-columnnumber are duplicates of the physical ones, however may be overridden later on in fillVBuf with ARIA attributes. They are what is reported to the user.
if (paccTable->get_rowColumnExtentsAtIndex(cellIndex, &row, &column, &rowExtents, &columnExtents, &isSelected) == S_OK) {
s << row + 1;
node->addAttribute(L"table-physicalrownumber", s.str());
node->addAttribute(L"table-rownumber", s.str());
s.str(L"");
s << column + 1;
node->addAttribute(L"table-physicalcolumnnumber", s.str());
node->addAttribute(L"table-columnnumber", s.str());
if (columnExtents > 1) {
s.str(L"");
@@ -215,16 +206,11 @@ inline void GeckoVBufBackend_t::fillTableCellInfo_IATable2(VBufStorage_controlFi
long row, column, rowExtents, columnExtents;
boolean isSelected;
// Fetch row and column extents and add them as attributes on this node.
// for rowNumber and columnNumber, store these as two sets of attributes.
// The first set: table-physicalrownumber and table-physicalcolumnnumber represent the physical topology of the table and can be used programmatically to fetch other table cells with IAccessibleTable etc.
// The second set: table-rownumber and table-columnnumber are duplicates of the physical ones, however may be overridden later on in fillVBuf with ARIA attributes. They are what is reported to the user.
if (paccTableCell->get_rowColumnExtents(&row, &column, &rowExtents, &columnExtents, &isSelected) == S_OK) {
s << row + 1;
node->addAttribute(L"table-physicalrownumber", s.str());
node->addAttribute(L"table-rownumber", s.str());
s.str(L"");
s << column + 1;
node->addAttribute(L"table-physicalcolumnnumber", s.str());
node->addAttribute(L"table-columnnumber", s.str());
if (columnExtents > 1) {
s.str(L"");
@@ -815,21 +801,29 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc,
}

// Add some presentational table attributes
// Note these are only for reporting, the physical table attributes (table-physicalrownumber etc) for aiding in navigation etc are added later on.
// propagate table-rownumber down to the cell as Gecko only includes it on the row itself
if(parentPresentationalRowNumber)
parentNode->addAttribute(L"table-rownumber",parentPresentationalRowNumber);
// Note these are only for reporting, the physical table attributes (table-rownumber etc) for aiding in navigation etc are added later on.
// propagate table-rownumber-presentational down to the cell as Gecko only includes it on the row itself
if(parentPresentationalRowNumber) {
parentNode->addAttribute(L"table-rownumber-presentational",parentPresentationalRowNumber);
}
const wchar_t* presentationalRowNumber=NULL;
if((IA2AttribsMapIt = IA2AttribsMap.find(L"rowindex")) != IA2AttribsMap.end()) {
parentNode->addAttribute(L"table-rownumber",IA2AttribsMapIt->second);
IA2AttribsMapIt = IA2AttribsMap.find(L"rowindex");
if(IA2AttribsMapIt != IA2AttribsMap.end()) {
parentNode->addAttribute(L"table-rownumber-presentational",IA2AttribsMapIt->second);
presentationalRowNumber=IA2AttribsMapIt->second.c_str();
}
if((IA2AttribsMapIt = IA2AttribsMap.find(L"colindex")) != IA2AttribsMap.end())
parentNode->addAttribute(L"table-columnnumber",IA2AttribsMapIt->second);
if((IA2AttribsMapIt = IA2AttribsMap.find(L"rowcount")) != IA2AttribsMap.end())
parentNode->addAttribute(L"table-rowcount",IA2AttribsMapIt->second);
if((IA2AttribsMapIt = IA2AttribsMap.find(L"colcount")) != IA2AttribsMap.end())
parentNode->addAttribute(L"table-columncount",IA2AttribsMapIt->second);
IA2AttribsMapIt = IA2AttribsMap.find(L"colindex");
if(IA2AttribsMapIt != IA2AttribsMap.end()) {
parentNode->addAttribute(L"table-columnnumber-presentational",IA2AttribsMapIt->second);
}
IA2AttribsMapIt = IA2AttribsMap.find(L"rowcount");
if(IA2AttribsMapIt != IA2AttribsMap.end()) {
parentNode->addAttribute(L"table-rowcount-presentational",IA2AttribsMapIt->second);
}
IA2AttribsMapIt = IA2AttribsMap.find(L"colcount");
if(IA2AttribsMapIt != IA2AttribsMap.end()) {
parentNode->addAttribute(L"table-columncount-presentational",IA2AttribsMapIt->second);
}

BSTR value=NULL;
if(pacc->get_accValue(varChild,&value)==S_OK) {
@@ -201,7 +201,12 @@ def _getSelectionOffsets(self):
def _setSelectionOffsets(self,start,end):
for selIndex in xrange(self.obj.IAccessibleTextObject.NSelections):
self.obj.IAccessibleTextObject.RemoveSelection(selIndex)
self.obj.IAccessibleTextObject.AddSelection(start,end)
if start!=end:
self.obj.IAccessibleTextObject.AddSelection(start,end)
else:
# A collapsed selection is the caret.
# Specifically handling it here as a setCaretOffset gets around some strange bugs in Chrome where setting a collapsed selection selects an entire table cell.
self._setCaretOffset(start)

def _getStoryLength(self):
try:
@@ -1078,7 +1083,7 @@ def event_IA2AttributeChange(self):
# We currently only care about changes to the accessible drag and drop attributes, which we map to states, so treat this as a stateChange.
self.event_stateChange()

def _get_IA2PhysicalRowNumber(self):
def _get_rowNumber(self):
tableCell=self._IATableCell
if tableCell:
return tableCell.rowIndex+1
@@ -1098,20 +1103,25 @@ def _get_IA2PhysicalRowNumber(self):
log.debugWarning("IAccessibleTable::rowIndex failed", exc_info=True)
raise NotImplementedError

def _get_rowNumber(self):
def _get_presentationalRowNumber(self):
index=self.IA2Attributes.get('rowindex')
if index is None and isinstance(self.parent,IAccessible):
index=self.parent.IA2Attributes.get('rowindex')
try:
index=int(index)
except (ValueError,TypeError):
log.debugWarning("value %s is not an int"%index,exc_info=True)
index=None
if index is None:
index=self.IA2PhysicalRowNumber
raise NotImplementedError
return index

def _get_rowSpan(self):
if self._IATableCell:
return self._IATableCell.rowExtent
raise NotImplementedError

def _get_IA2PhysicalColumnNumber(self):
def _get_columnNumber(self):
tableCell=self._IATableCell
if tableCell:
return tableCell.columnIndex+1
@@ -1144,18 +1154,23 @@ def _get_cellCoordsText(self):
rowText=self.rowNumber
return "%s %s"%(colText,rowText)

def _get_columnNumber(self):
def _get_presentationalColumnNumber(self):
index=self.IA2Attributes.get('colindex')
try:
index=int(index)
except (ValueError,TypeError):
log.debugWarning("value %s is not an int"%index,exc_info=True)
index=None
if index is None:
index=self.IA2PhysicalColumnNumber
raise NotImplementedError
return index

def _get_columnSpan(self):
if self._IATableCell:
return self._IATableCell.columnExtent
raise NotImplementedError

def _get_IA2PhysicalRowCount(self):
def _get_rowCount(self):
if hasattr(self,'IAccessibleTable2Object'):
try:
return self.IAccessibleTable2Object.nRows
@@ -1168,13 +1183,18 @@ def _get_IA2PhysicalRowCount(self):
log.debugWarning("IAccessibleTable::nRows failed", exc_info=True)
raise NotImplementedError

def _get_rowCount(self):
def _get_presentationalRowCount(self):
count=self.IA2Attributes.get('rowcount')
try:
count=int(count)
except (ValueError,TypeError):
log.debugWarning("value %s is not an int"%count,exc_info=True)
count=None
if count is None:
count=self.IA2PhysicalRowCount
raise NotImplementedError
return count

def _get_IA2PhysicalColumnCount(self):
def _get_columnCount(self):
if hasattr(self,'IAccessibleTable2Object'):
try:
return self.IAccessibleTable2Object.nColumns
@@ -1187,10 +1207,15 @@ def _get_IA2PhysicalColumnCount(self):
log.debugWarning("IAccessibleTable::nColumns failed", exc_info=True)
raise NotImplementedError

def _get_columnCount(self):
def _get_presentationalColumnCount(self):
count=self.IA2Attributes.get('colcount')
try:
count=int(count)
except (ValueError,TypeError):
log.debugWarning("value %s is not an int"%count,exc_info=True)
count=None
if count is None:
count=self.IA2PhysicalColumnCount
raise NotImplementedError
return count

def _get__IATableCell(self):
Oops, something went wrong.

0 comments on commit c20a503

Please sign in to comment.
You can’t perform that action at this time.