diff --git a/accessible/src/jsat/AccessFu.jsm b/accessible/src/jsat/AccessFu.jsm
index 3b27e431417..263c85d097a 100644
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -542,7 +542,7 @@ var Input = {
switch (gestureName) {
case 'dwell1':
case 'explore1':
- this.moveCursor('moveToPoint', 'Simple', 'gesture',
+ this.moveCursor('moveToPoint', 'SimpleTouch', 'gesture',
aGesture.x, aGesture.y);
break;
case 'doubletap1':
diff --git a/accessible/src/jsat/EventManager.jsm b/accessible/src/jsat/EventManager.jsm
index 1cee5f4681e..04357d84f58 100644
--- a/accessible/src/jsat/EventManager.jsm
+++ b/accessible/src/jsat/EventManager.jsm
@@ -57,8 +57,7 @@ this.EventManager.prototype = {
this.present(Presentation.tabStateChanged(null, 'newtab'));
} catch (x) {
- Logger.error('Failed to start EventManager');
- Logger.logException(x);
+ Logger.logException(x, 'Failed to start EventManager');
}
},
@@ -117,8 +116,7 @@ this.EventManager.prototype = {
}
}
} catch (x) {
- Logger.error('Error handling DOM event');
- Logger.logException(x);
+ Logger.logException(x, 'Error handling DOM event');
}
},
@@ -140,7 +138,7 @@ this.EventManager.prototype = {
let pivot = aEvent.accessible.
QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
let position = pivot.position;
- if (position.role == Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME)
+ if (position && position.role == Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME)
break;
let event = aEvent.
QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
@@ -421,8 +419,7 @@ const AccessibilityEventObserver = {
try {
eventManager.handleAccEvent(event);
} catch (x) {
- Logger.error('Error handing accessible event');
- Logger.logException(x);
+ Logger.logException(x, 'Error handing accessible event');
} finally {
return;
}
diff --git a/accessible/src/jsat/OutputGenerator.jsm b/accessible/src/jsat/OutputGenerator.jsm
index a6af8561d65..de1ce6fd473 100644
--- a/accessible/src/jsat/OutputGenerator.jsm
+++ b/accessible/src/jsat/OutputGenerator.jsm
@@ -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 '';
}
diff --git a/accessible/src/jsat/Presentation.jsm b/accessible/src/jsat/Presentation.jsm
index 7aa20884dbb..b88a934b201 100644
--- a/accessible/src/jsat/Presentation.jsm
+++ b/accessible/src/jsat/Presentation.jsm
@@ -155,7 +155,7 @@ VisualPresenter.prototype = {
}
};
} catch (e) {
- Logger.error('Failed to get bounds: ' + e);
+ Logger.logException(e, 'Failed to get bounds');
return null;
}
},
@@ -398,6 +398,19 @@ SpeechPresenter.prototype = {
]
}
};
+ },
+
+ actionInvoked: function SpeechPresenter_actionInvoked(aObject, aActionName) {
+ return {
+ type: this.type,
+ details: {
+ actions: [
+ {method: 'speak',
+ data: UtteranceGenerator.genForAction(aObject, aActionName).join(' '),
+ options: {enqueue: false}}
+ ]
+ }
+ };
}
};
diff --git a/accessible/src/jsat/TraversalRules.jsm b/accessible/src/jsat/TraversalRules.jsm
index 4631d9ee3e5..5bb6fcb8ea9 100644
--- a/accessible/src/jsat/TraversalRules.jsm
+++ b/accessible/src/jsat/TraversalRules.jsm
@@ -9,6 +9,10 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
+const FILTER_IGNORE = Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+const FILTER_MATCH = Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+const FILTER_IGNORE_SUBTREE = Ci.nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
+
this.EXPORTED_SYMBOLS = ['TraversalRules'];
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
@@ -35,14 +39,13 @@ BaseTraversalRule.prototype = {
{
if (aAccessible.role == Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME) {
return (Utils.getMessageManager(aAccessible.DOMNode)) ?
- Ci.nsIAccessibleTraversalRule.FILTER_MATCH :
- Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+ FILTER_MATCH | FILTER_IGNORE_SUBTREE : FILTER_IGNORE;
}
if (this._matchFunc)
return this._matchFunc(aAccessible);
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
@@ -78,40 +81,40 @@ this.TraversalRules = {
case Ci.nsIAccessibleRole.ROLE_COMBOBOX:
// We don't want to ignore the subtree because this is often
// where the list box hangs out.
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
case Ci.nsIAccessibleRole.ROLE_TEXT_LEAF:
{
// Nameless text leaves are boring, skip them.
let name = aAccessible.name;
if (name && name.trim())
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
else
- return Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+ return FILTER_IGNORE;
}
case Ci.nsIAccessibleRole.ROLE_LINK:
// If the link has children we should land on them instead.
// Image map links don't have children so we need to match those.
if (aAccessible.childCount == 0)
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
else
- return Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+ return FILTER_IGNORE;
case Ci.nsIAccessibleRole.ROLE_STATICTEXT:
{
let parent = aAccessible.parent;
// Ignore prefix static text in list items. They are typically bullets or numbers.
if (parent.childCount > 1 && aAccessible.indexInParent == 0 &&
parent.role == Ci.nsIAccessibleRole.ROLE_LISTITEM)
- return Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+ return FILTER_IGNORE;
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
}
case Ci.nsIAccessibleRole.ROLE_GRAPHIC:
return TraversalRules._shouldSkipImage(aAccessible);
default:
// Ignore the subtree, if there is one. So that we don't land on
// the same content that was already presented by its parent.
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH |
- Ci.nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
+ return FILTER_MATCH |
+ FILTER_IGNORE_SUBTREE;
}
}
),
@@ -119,8 +122,8 @@ this.TraversalRules = {
SimpleTouch: new BaseTraversalRule(
gSimpleTraversalRoles,
function Simple_match(aAccessible) {
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH |
- Ci.nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
+ return FILTER_MATCH |
+ FILTER_IGNORE_SUBTREE;
}
),
@@ -133,9 +136,9 @@ this.TraversalRules = {
let extraState = {};
aAccessible.getState(state, extraState);
if (state.value & Ci.nsIAccessibleStates.STATE_LINKED) {
- return Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+ return FILTER_IGNORE;
} else {
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
}
}),
@@ -193,9 +196,9 @@ this.TraversalRules = {
let extraState = {};
aAccessible.getState(state, extraState);
if (state.value & Ci.nsIAccessibleStates.STATE_LINKED) {
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
} else {
- return Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+ return FILTER_IGNORE;
}
}),
@@ -222,8 +225,8 @@ this.TraversalRules = {
_shouldSkipImage: function _shouldSkipImage(aAccessible) {
if (gSkipEmptyImages.value && aAccessible.name === '') {
- return Ci.nsIAccessibleTraversalRule.FILTER_IGNORE;
+ return FILTER_IGNORE;
}
- return Ci.nsIAccessibleTraversalRule.FILTER_MATCH;
+ return FILTER_MATCH;
}
};
diff --git a/accessible/src/jsat/Utils.jsm b/accessible/src/jsat/Utils.jsm
index cae5a133cb5..2ec09473771 100644
--- a/accessible/src/jsat/Utils.jsm
+++ b/accessible/src/jsat/Utils.jsm
@@ -191,15 +191,18 @@ this.Utils = {
},
getAttributes: function getAttributes(aAccessible) {
- let attributesEnum = aAccessible.attributes.enumerate();
let attributes = {};
- // Populate |attributes| object with |aAccessible|'s attribute key-value
- // pairs.
- while (attributesEnum.hasMoreElements()) {
- let attribute = attributesEnum.getNext().QueryInterface(
- Ci.nsIPropertyElement);
- attributes[attribute.key] = attribute.value;
+ if (aAccessible && aAccessible.attributes) {
+ let attributesEnum = aAccessible.attributes.enumerate();
+
+ // Populate |attributes| object with |aAccessible|'s attribute key-value
+ // pairs.
+ while (attributesEnum.hasMoreElements()) {
+ let attribute = attributesEnum.getNext().QueryInterface(
+ Ci.nsIPropertyElement);
+ attributes[attribute.key] = attribute.value;
+ }
}
return attributes;
@@ -268,12 +271,27 @@ this.Logger = {
this, [this.ERROR].concat(Array.prototype.slice.call(arguments)));
},
- logException: function logException(aException) {
+ logException: function logException(
+ aException, aErrorMessage = 'An exception has occured') {
try {
- let args = [aException.message];
- args.push.apply(args, aException.stack ? ['\n', aException.stack] :
- ['(' + aException.fileName + ':' + aException.lineNumber + ')']);
- this.error.apply(this, args);
+ let stackMessage = '';
+ if (aException.stack) {
+ stackMessage = ' ' + aException.stack.replace(/\n/g, '\n ');
+ } else if (aException.location) {
+ let frame = aException.location;
+ let stackLines = [];
+ while (frame && frame.lineNumber) {
+ stackLines.push(
+ ' ' + frame.name + '@' + frame.filename + ':' + frame.lineNumber);
+ frame = frame.caller;
+ }
+ stackMessage = stackLines.join('\n');
+ } else {
+ stackMessage = '(' + aException.fileName + ':' + aException.lineNumber + ')';
+ }
+ this.error(aErrorMessage + ':\n ' +
+ aException.message + '\n' +
+ stackMessage);
} catch (x) {
this.error(x);
}
@@ -350,6 +368,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 +414,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 +461,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 = {};
diff --git a/accessible/src/jsat/content-script.js b/accessible/src/jsat/content-script.js
index 8507f8c96f4..be555ff8db8 100644
--- a/accessible/src/jsat/content-script.js
+++ b/accessible/src/jsat/content-script.js
@@ -93,7 +93,7 @@ function virtualCursorControl(aMessage) {
sendAsyncMessage('AccessFu:VirtualCursor', aMessage.json);
}
} catch (x) {
- Logger.error(x);
+ Logger.logException(x, 'Failed to move virtual cursor');
}
}
diff --git a/accessible/tests/mochitest/jsat/Makefile.in b/accessible/tests/mochitest/jsat/Makefile.in
index 27378d47f7e..5b3c15583d2 100644
--- a/accessible/tests/mochitest/jsat/Makefile.in
+++ b/accessible/tests/mochitest/jsat/Makefile.in
@@ -15,9 +15,10 @@ MOCHITEST_A11Y_FILES =\
jsatcommon.js \
output.js \
test_alive.html \
-test_explicit_names.html \
-test_utterance_order.html \
test_braille.html \
+test_explicit_names.html \
+test_tables.html \
+test_utterance_order.html \
$(NULL)
include $(topsrcdir)/config/rules.mk
diff --git a/accessible/tests/mochitest/jsat/output.js b/accessible/tests/mochitest/jsat/output.js
index 7c899171459..5ab11dc7164 100644
--- a/accessible/tests/mochitest/jsat/output.js
+++ b/accessible/tests/mochitest/jsat/output.js
@@ -37,7 +37,8 @@ function testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aGenerator
*/
function testObjectOutput(aAccOrElmOrID, aGenerator) {
var accessible = getAccessible(aAccOrElmOrID);
- var output = aGenerator.genForObject(accessible);
+ var context = new PivotContext(accessible);
+ var output = aGenerator.genForObject(accessible, context);
var outputOrder;
try {
outputOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
diff --git a/accessible/tests/mochitest/jsat/test_explicit_names.html b/accessible/tests/mochitest/jsat/test_explicit_names.html
index a0a234eef29..c82cd86f702 100644
--- a/accessible/tests/mochitest/jsat/test_explicit_names.html
+++ b/accessible/tests/mochitest/jsat/test_explicit_names.html
@@ -54,9 +54,10 @@
expected: ["list 2 items", "Test List", "Last item", "2.", "list two"]
}, {
accOrElmOrID: "cell",
- expected: ["table", "Fruits and vegetables", "List of Fruits",
- "list 4 items","First item", "link", "Apples", "link", "Bananas",
- "link", "Peaches", "Last item", "link", "Plums"]
+ expected: ["table with 1 column and 1 row", "Fruits and vegetables",
+ "Column 1 Row 1", "List of Fruits", "list 4 items", "First item",
+ "link", "Apples", "link", "Bananas", "link", "Peaches", "Last item",
+ "link", "Plums"]
}, {
accOrElmOrID: "app.net",
expected: ["list 2 items", "First item", "link", "star", "Last item",
@@ -71,13 +72,12 @@
// Test pivot to li_one from list.
accOrElmOrID: "li_one",
oldAccOrElmOrID: "list",
- expected: ["list 2 items", "Test List", "First item", "Top of the list"]
+ expected: ["First item", "Top of the list"]
}, {
// Test pivot to "apples" link from the table cell.
accOrElmOrID: "apples",
oldAccOrElmOrID: "cell",
- expected: ["List of Fruits", "list 4 items", "First item", "link",
- "Apples"]
+ expected: ["list 4 items", "First item", "link", "Apples"]
}, {
// Test pivot to the table cell from the "apples" link.
accOrElmOrID: "cell",
diff --git a/accessible/tests/mochitest/jsat/test_tables.html b/accessible/tests/mochitest/jsat/test_tables.html
new file mode 100644
index 00000000000..ee4c700fd3b
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_tables.html
@@ -0,0 +1,297 @@
+
+
+ [AccessFu] Improve reading of table semantics
+
+
+
+
+
+
+
+
+
+
+ Mozilla Bug 830748
+
+
+
+
+
+
+
+ col1 |
+ col2 |
+
+
+
+
+ cell1 |
+ cell2 |
+
+
+
+
+
+ cell1 |
+
+
+
+
+ colheader |
+
+
+
+
+ bla |
+
+
+
+ |
+
+
+ col1 |
+ col2 |
+
+
+
+
+
+ col1 |
+ col2 |
+ col3 |
+
+
+
+
+ row1 |
+ cell1 |
+ cell2 |
+
+
+ row2 |
+ cell3 |
+ cell4 |
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/accessible/tests/mochitest/jsat/test_utterance_order.html b/accessible/tests/mochitest/jsat/test_utterance_order.html
index b76bbbc7dd5..14ee1a57cbf 100644
--- a/accessible/tests/mochitest/jsat/test_utterance_order.html
+++ b/accessible/tests/mochitest/jsat/test_utterance_order.html
@@ -57,13 +57,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=753984
}, {
accOrElmOrID: "cell",
expected: [[
- "table", "Fruits and vegetables", "list 4 items", "First item",
- "link", "Apples", "link", "Bananas", "link", "Peaches",
- "Last item", "link", "Plums"
+ "table with 1 column and 1 row", "Fruits and vegetables",
+ "Column 1 Row 1", "list 4 items", "First item", "link", "Apples",
+ "link", "Bananas", "link", "Peaches", "Last item", "link", "Plums"
], [
"Apples", "link", "First item", "Bananas", "link", "Peaches",
"link", "Plums", "link", "Last item", "list 4 items",
- "Fruits and vegetables", "table"
+ "Column 1 Row 1", "Fruits and vegetables",
+ "table with 1 column and 1 row"
]]
}, {
// Test pivot to list from li_one.
diff --git a/browser/base/content/browser-context.inc b/browser/base/content/browser-context.inc
index bd51fe28feb..beb220b432e 100644
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -180,6 +180,11 @@
label="&viewImageInfoCmd.label;"
accesskey="&viewImageInfoCmd.accesskey;"
oncommand="gContextMenu.viewImageInfo();"/>
+