mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 830748 - [PATCH 1/2] [AccessFu] Improved reading of table semantics. r=eeejay
This commit is contained in:
parent
310f9b591a
commit
95d1b39b68
@ -22,6 +22,11 @@ XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PluralForm',
|
||||
'resource://gre/modules/PluralForm.jsm');
|
||||
|
||||
|
||||
let gUtteranceOrder = new PrefCache('accessibility.accessfu.utterance');
|
||||
|
||||
@ -46,7 +51,7 @@ this.OutputGenerator = {
|
||||
let output = [];
|
||||
let self = this;
|
||||
let addOutput = function addOutput(aAccessible) {
|
||||
output.push.apply(output, self.genForObject(aAccessible));
|
||||
output.push.apply(output, self.genForObject(aAccessible, aContext));
|
||||
};
|
||||
let ignoreSubtree = function ignoreSubtree(aAccessible) {
|
||||
let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
|
||||
@ -83,14 +88,18 @@ this.OutputGenerator = {
|
||||
* Generates output for an object.
|
||||
* @param {nsIAccessible} aAccessible accessible object to generate utterance
|
||||
* for.
|
||||
* @param {PivotContext} aContext object that generates and caches
|
||||
* context information for a given accessible and its relationship with
|
||||
* another accessible.
|
||||
* @return {Array} Two string array. The first string describes the object
|
||||
* and its states. The second string is the object's name. Whether the
|
||||
* object's description or it's role is included is determined by
|
||||
* {@link roleRuleMap}.
|
||||
*/
|
||||
genForObject: function genForObject(aAccessible) {
|
||||
genForObject: function genForObject(aAccessible, aContext) {
|
||||
let roleString = Utils.AccRetrieval.getStringRole(aAccessible.role);
|
||||
let func = this.objectOutputFunctions[roleString.replace(' ', '')] ||
|
||||
let func = this.objectOutputFunctions[
|
||||
OutputGenerator._getOutputName(roleString)] ||
|
||||
this.objectOutputFunctions.defaultFunc;
|
||||
|
||||
let flags = this.roleRuleMap[roleString] || 0;
|
||||
@ -103,7 +112,7 @@ this.OutputGenerator = {
|
||||
aAccessible.getState(state, extState);
|
||||
let states = {base: state.value, ext: extState.value};
|
||||
|
||||
return func.apply(this, [aAccessible, roleString, states, flags]);
|
||||
return func.apply(this, [aAccessible, roleString, states, flags, aContext]);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -158,10 +167,20 @@ this.OutputGenerator = {
|
||||
}
|
||||
},
|
||||
|
||||
_getOutputName: function _getOutputName(aName) {
|
||||
return aName.replace(' ', '');
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {},
|
||||
|
||||
_getLocalizedStates: function _getLocalizedStates(aStates) {},
|
||||
|
||||
_getPluralFormString: function _getPluralFormString(aString, aCount) {
|
||||
let str = gStringBundle.GetStringFromName(this._getOutputName(aString));
|
||||
str = PluralForm.get(aCount, str);
|
||||
return str.replace('#1', aCount);
|
||||
},
|
||||
|
||||
roleRuleMap: {
|
||||
'menubar': INCLUDE_DESC,
|
||||
'scrollbar': INCLUDE_DESC,
|
||||
@ -170,10 +189,11 @@ this.OutputGenerator = {
|
||||
'menupopup': INCLUDE_DESC,
|
||||
'menuitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
|
||||
'tooltip': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
|
||||
'columnheader': NAME_FROM_SUBTREE_RULE,
|
||||
'rowheader': NAME_FROM_SUBTREE_RULE,
|
||||
'columnheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
|
||||
'rowheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
|
||||
'column': NAME_FROM_SUBTREE_RULE,
|
||||
'row': NAME_FROM_SUBTREE_RULE,
|
||||
'cell': INCLUDE_DESC | INCLUDE_NAME,
|
||||
'application': INCLUDE_NAME,
|
||||
'document': INCLUDE_NAME,
|
||||
'grouping': INCLUDE_DESC | INCLUDE_NAME,
|
||||
@ -263,6 +283,32 @@ this.OutputGenerator = {
|
||||
this._addName(output, aAccessible, aFlags);
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
table: function table(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let output = [];
|
||||
let table;
|
||||
try {
|
||||
table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
|
||||
} catch (x) {
|
||||
Logger.logException(x);
|
||||
return output;
|
||||
} finally {
|
||||
// Check if it's a layout table, and bail out if true.
|
||||
// We don't want to speak any table information for layout tables.
|
||||
if (table.isProbablyForLayout()) {
|
||||
return output;
|
||||
}
|
||||
let tableColumnInfo = this._getPluralFormString('tableColumnInfo',
|
||||
table.columnCount);
|
||||
let tableRowInfo = this._getPluralFormString('tableRowInfo',
|
||||
table.rowCount);
|
||||
output.push(gStringBundle.formatStringFromName(
|
||||
this._getOutputName('tableInfo'), [this._getLocalizedRole(aRoleStr),
|
||||
tableColumnInfo, tableRowInfo], 3));
|
||||
this._addName(output, aAccessible, aFlags);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -341,12 +387,11 @@ this.UtteranceGenerator = {
|
||||
},
|
||||
|
||||
objectOutputFunctions: {
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions.entry.apply(this, arguments);
|
||||
__proto__: OutputGenerator.objectOutputFunctions,
|
||||
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
},
|
||||
|
||||
heading: function heading(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
@ -392,6 +437,53 @@ this.UtteranceGenerator = {
|
||||
[aAccessible, aRoleStr, aStates, aFlags]);
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
cell: function cell(aAccessible, aRoleStr, aStates, aFlags, aContext) {
|
||||
let utterance = [];
|
||||
let cell = aContext.getCellInfo(aAccessible);
|
||||
if (cell) {
|
||||
let desc = [];
|
||||
let addCellChanged = function addCellChanged(aDesc, aChanged, aString, aIndex) {
|
||||
if (aChanged) {
|
||||
aDesc.push(gStringBundle.formatStringFromName(aString,
|
||||
[aIndex + 1], 1));
|
||||
}
|
||||
};
|
||||
let addExtent = function addExtent(aDesc, aExtent, aString) {
|
||||
if (aExtent > 1) {
|
||||
aDesc.push(gStringBundle.formatStringFromName(aString,
|
||||
[aExtent], 1));
|
||||
}
|
||||
};
|
||||
let addHeaders = function addHeaders(aDesc, aHeaders) {
|
||||
if (aHeaders.length > 0) {
|
||||
aDesc.push.apply(aDesc, aHeaders);
|
||||
}
|
||||
};
|
||||
|
||||
addCellChanged(desc, cell.columnChanged, 'columnInfo', cell.columnIndex);
|
||||
addCellChanged(desc, cell.rowChanged, 'rowInfo', cell.rowIndex);
|
||||
|
||||
addExtent(desc, cell.columnExtent, 'spansColumns');
|
||||
addExtent(desc, cell.rowExtent, 'spansRows');
|
||||
|
||||
addHeaders(desc, cell.columnHeaders);
|
||||
addHeaders(desc, cell.rowHeaders);
|
||||
|
||||
utterance.push(desc.join(' '));
|
||||
}
|
||||
|
||||
this._addName(utterance, aAccessible, aFlags);
|
||||
return utterance;
|
||||
},
|
||||
|
||||
columnheader: function columnheader() {
|
||||
return this.objectOutputFunctions.cell.apply(this, arguments);
|
||||
},
|
||||
|
||||
rowheader: function rowheader() {
|
||||
return this.objectOutputFunctions.cell.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
@ -401,7 +493,7 @@ this.UtteranceGenerator = {
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {
|
||||
try {
|
||||
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', ''));
|
||||
return gStringBundle.GetStringFromName(this._getOutputName(aRoleStr));
|
||||
} catch (x) {
|
||||
return '';
|
||||
}
|
||||
@ -467,8 +559,11 @@ this.BrailleGenerator = {
|
||||
defaultOutputOrder: OUTPUT_DESC_LAST,
|
||||
|
||||
objectOutputFunctions: {
|
||||
|
||||
__proto__: OutputGenerator.objectOutputFunctions,
|
||||
|
||||
defaultFunc: function defaultFunc(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
let braille = OutputGenerator.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
let braille = this.objectOutputFunctions._generateBaseOutput.apply(this, arguments);
|
||||
|
||||
if (aAccessible.indexInParent === 1 &&
|
||||
aAccessible.parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM &&
|
||||
@ -492,6 +587,38 @@ this.BrailleGenerator = {
|
||||
return braille;
|
||||
},
|
||||
|
||||
cell: function cell(aAccessible, aRoleStr, aStates, aFlags, aContext) {
|
||||
let braille = [];
|
||||
let cell = aContext.getCellInfo(aAccessible);
|
||||
if (cell) {
|
||||
let desc = [];
|
||||
let addHeaders = function addHeaders(aDesc, aHeaders) {
|
||||
if (aHeaders.length > 0) {
|
||||
aDesc.push.apply(aDesc, aHeaders);
|
||||
}
|
||||
};
|
||||
|
||||
desc.push(gStringBundle.formatStringFromName(
|
||||
this._getOutputName('cellInfo'), [cell.columnIndex + 1,
|
||||
cell.rowIndex + 1], 2));
|
||||
|
||||
addHeaders(desc, cell.columnHeaders);
|
||||
addHeaders(desc, cell.rowHeaders);
|
||||
braille.push(desc.join(' '));
|
||||
}
|
||||
|
||||
this._addName(braille, aAccessible, aFlags);
|
||||
return braille;
|
||||
},
|
||||
|
||||
columnheader: function columnheader() {
|
||||
return this.objectOutputFunctions.cell.apply(this, arguments);
|
||||
},
|
||||
|
||||
rowheader: function rowheader() {
|
||||
return this.objectOutputFunctions.cell.apply(this, arguments);
|
||||
},
|
||||
|
||||
statictext: function statictext(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
// Since we customize the list bullet's output, we add the static
|
||||
// text from the first node in each listitem, so skip it here.
|
||||
@ -523,10 +650,6 @@ this.BrailleGenerator = {
|
||||
|
||||
togglebutton: function radiobutton(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
|
||||
},
|
||||
|
||||
entry: function entry(aAccessible, aRoleStr, aStates, aFlags) {
|
||||
return OutputGenerator.objectOutputFunctions.entry.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
@ -538,12 +661,17 @@ this.BrailleGenerator = {
|
||||
return [];
|
||||
},
|
||||
|
||||
_getOutputName: function _getOutputName(aName) {
|
||||
return OutputGenerator._getOutputName(aName) + 'Abbr';
|
||||
},
|
||||
|
||||
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {
|
||||
try {
|
||||
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', '') + 'Abbr');
|
||||
return gStringBundle.GetStringFromName(this._getOutputName(aRoleStr));
|
||||
} catch (x) {
|
||||
try {
|
||||
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', ''));
|
||||
return gStringBundle.GetStringFromName(
|
||||
OutputGenerator._getOutputName(aRoleStr));
|
||||
} catch (y) {
|
||||
return '';
|
||||
}
|
||||
|
@ -350,6 +350,45 @@ PivotContext.prototype = {
|
||||
return this._oldAccessible;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of |aAccessible|'s ancestry up to the root.
|
||||
* @param {nsIAccessible} aAccessible.
|
||||
* @return {Array} Ancestry list.
|
||||
*/
|
||||
_getAncestry: function _getAncestry(aAccessible) {
|
||||
let ancestry = [];
|
||||
let parent = aAccessible;
|
||||
while (parent && (parent = parent.parent)) {
|
||||
ancestry.push(parent);
|
||||
}
|
||||
return ancestry.reverse();
|
||||
},
|
||||
|
||||
/**
|
||||
* A list of the old accessible's ancestry.
|
||||
*/
|
||||
get oldAncestry() {
|
||||
if (!this._oldAncestry) {
|
||||
if (!this._oldAccessible) {
|
||||
this._oldAncestry = [];
|
||||
} else {
|
||||
this._oldAncestry = this._getAncestry(this._oldAccessible);
|
||||
this._oldAncestry.push(this._oldAccessible);
|
||||
}
|
||||
}
|
||||
return this._oldAncestry;
|
||||
},
|
||||
|
||||
/**
|
||||
* A list of the current accessible's ancestry.
|
||||
*/
|
||||
get currentAncestry() {
|
||||
if (!this._currentAncestry) {
|
||||
this._currentAncestry = this._getAncestry(this._accessible);
|
||||
}
|
||||
return this._currentAncestry;
|
||||
},
|
||||
|
||||
/*
|
||||
* This is a list of the accessible's ancestry up to the common ancestor
|
||||
* of the accessible and the old accessible. It is useful for giving the
|
||||
@ -357,32 +396,10 @@ PivotContext.prototype = {
|
||||
*/
|
||||
get newAncestry() {
|
||||
if (!this._newAncestry) {
|
||||
let newLineage = [];
|
||||
let oldLineage = [];
|
||||
|
||||
let parent = this._accessible;
|
||||
while (parent && (parent = parent.parent))
|
||||
newLineage.push(parent);
|
||||
|
||||
parent = this._oldAccessible;
|
||||
while (parent && (parent = parent.parent))
|
||||
oldLineage.push(parent);
|
||||
|
||||
this._newAncestry = [];
|
||||
|
||||
while (true) {
|
||||
let newAncestor = newLineage.pop();
|
||||
let oldAncestor = oldLineage.pop();
|
||||
|
||||
if (newAncestor == undefined)
|
||||
break;
|
||||
|
||||
if (newAncestor != oldAncestor)
|
||||
this._newAncestry.push(newAncestor);
|
||||
}
|
||||
|
||||
this._newAncestry = [currentAncestor for (
|
||||
[index, currentAncestor] of Iterator(this.currentAncestry)) if (
|
||||
currentAncestor !== this.oldAncestry[index])];
|
||||
}
|
||||
|
||||
return this._newAncestry;
|
||||
},
|
||||
|
||||
@ -426,6 +443,99 @@ PivotContext.prototype = {
|
||||
return this._traverse(this._accessible, aPreorder, aStop);
|
||||
},
|
||||
|
||||
getCellInfo: function getCellInfo(aAccessible) {
|
||||
if (!this._cells) {
|
||||
this._cells = new WeakMap();
|
||||
}
|
||||
|
||||
let domNode = aAccessible.DOMNode;
|
||||
if (this._cells.has(domNode)) {
|
||||
return this._cells.get(domNode);
|
||||
}
|
||||
|
||||
let cellInfo = {};
|
||||
let getAccessibleCell = function getAccessibleCell(aAccessible) {
|
||||
if (!aAccessible) {
|
||||
return null;
|
||||
}
|
||||
if ([Ci.nsIAccessibleRole.ROLE_CELL,
|
||||
Ci.nsIAccessibleRole.ROLE_COLUMNHEADER,
|
||||
Ci.nsIAccessibleRole.ROLE_ROWHEADER].indexOf(
|
||||
aAccessible.role) < 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return aAccessible.QueryInterface(Ci.nsIAccessibleTableCell);
|
||||
} catch (x) {
|
||||
Logger.logException(x);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
let getHeaders = function getHeaders(aHeaderCells) {
|
||||
let enumerator = aHeaderCells.enumerate();
|
||||
while (enumerator.hasMoreElements()) {
|
||||
yield enumerator.getNext().QueryInterface(Ci.nsIAccessible).name;
|
||||
}
|
||||
};
|
||||
|
||||
cellInfo.current = getAccessibleCell(aAccessible);
|
||||
|
||||
if (!cellInfo.current) {
|
||||
Logger.warning(aAccessible,
|
||||
'does not support nsIAccessibleTableCell interface.');
|
||||
this._cells.set(domNode, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
let table = cellInfo.current.table;
|
||||
if (table.isProbablyForLayout()) {
|
||||
this._cells.set(domNode, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
cellInfo.previous = null;
|
||||
let oldAncestry = this.oldAncestry.reverse();
|
||||
let ancestor = oldAncestry.shift();
|
||||
while (!cellInfo.previous && ancestor) {
|
||||
let cell = getAccessibleCell(ancestor);
|
||||
if (cell && cell.table === table) {
|
||||
cellInfo.previous = cell;
|
||||
}
|
||||
ancestor = oldAncestry.shift();
|
||||
}
|
||||
|
||||
if (cellInfo.previous) {
|
||||
cellInfo.rowChanged = cellInfo.current.rowIndex !==
|
||||
cellInfo.previous.rowIndex;
|
||||
cellInfo.columnChanged = cellInfo.current.columnIndex !==
|
||||
cellInfo.previous.columnIndex;
|
||||
} else {
|
||||
cellInfo.rowChanged = true;
|
||||
cellInfo.columnChanged = true;
|
||||
}
|
||||
|
||||
cellInfo.rowExtent = cellInfo.current.rowExtent;
|
||||
cellInfo.columnExtent = cellInfo.current.columnExtent;
|
||||
cellInfo.columnIndex = cellInfo.current.columnIndex;
|
||||
cellInfo.rowIndex = cellInfo.current.rowIndex;
|
||||
|
||||
cellInfo.columnHeaders = [];
|
||||
if (cellInfo.columnChanged && cellInfo.current.role !==
|
||||
Ci.nsIAccessibleRole.ROLE_COLUMNHEADER) {
|
||||
cellInfo.columnHeaders = [headers for (headers of getHeaders(
|
||||
cellInfo.current.columnHeaderCells))];
|
||||
}
|
||||
cellInfo.rowHeaders = [];
|
||||
if (cellInfo.rowChanged && cellInfo.current.role ===
|
||||
Ci.nsIAccessibleRole.ROLE_CELL) {
|
||||
cellInfo.rowHeaders = [headers for (headers of getHeaders(
|
||||
cellInfo.current.rowHeaderCells))];
|
||||
}
|
||||
|
||||
this._cells.set(domNode, cellInfo);
|
||||
return cellInfo;
|
||||
},
|
||||
|
||||
get bounds() {
|
||||
if (!this._bounds) {
|
||||
let objX = {}, objY = {}, objW = {}, objH = {};
|
||||
|
@ -80,6 +80,20 @@ listStart = First item
|
||||
listEnd = Last item
|
||||
listItemCount = %S items
|
||||
|
||||
# Description of a table or grid:
|
||||
# 1 is a dynamically retrieved localized role of either 'table' or 'grid'.
|
||||
# 2 is the number of columns within the table.
|
||||
# 3 is the number of rows within the table or grid.
|
||||
tableInfo = %S with %S and %S
|
||||
tableColumnInfo = 1 column;#1 columns
|
||||
tableRowInfo = 1 row;#1 rows
|
||||
|
||||
# table or grid cell information
|
||||
columnInfo = Column %S
|
||||
rowInfo = Row %S
|
||||
spansColumns = spans %S columns
|
||||
spansRows = spans %S rows
|
||||
|
||||
# Invoked actions
|
||||
jumpAction = jumped
|
||||
pressAction = pressed
|
||||
@ -141,3 +155,8 @@ passwordtextAbbr = passwdtxt
|
||||
imagemapAbbr = imgmap
|
||||
figureAbbr = fig
|
||||
textareaAbbr = txtarea
|
||||
tableAbbr = tbl
|
||||
tableInfoAbbr = %S %S %S
|
||||
tableColumnInfoAbbr = #1c;#1c
|
||||
tableRowInfoAbbr = #1r;#1r
|
||||
cellInfoAbbr = c%Sr%S
|
||||
|
Loading…
Reference in New Issue
Block a user