Bug 753984 - added a preference for utterance order to make it configurable. Presenter context was moved to Utils and renamed to pivot context. Created a new method to generate utterance for pivot context. Moved context utterance generation from Pivot(Presentation) to UtteranceGenerator. r=eeejay

This commit is contained in:
Yura Zenevich 2013-04-04 15:16:37 -07:00
Родитель 30752f7d2b
Коммит 24d10f8c80
4 изменённых файлов: 201 добавлений и 172 удалений

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

@ -11,7 +11,6 @@ const Cr = Components.results;
Cu.import('resource://gre/modules/accessibility/Utils.jsm');
Cu.import('resource://gre/modules/accessibility/UtteranceGenerator.jsm');
Cu.import('resource://gre/modules/Geometry.jsm');
this.EXPORTED_SYMBOLS = ['Presentation'];
@ -29,7 +28,7 @@ Presenter.prototype = {
/**
* The virtual cursor's position changed.
* @param {PresenterContext} aContext the context object for the new pivot
* @param {PivotContext} aContext the context object for the new pivot
* position.
* @param {int} aReason the reason for the pivot change.
* See nsIAccessiblePivot.
@ -72,9 +71,9 @@ Presenter.prototype = {
/**
* The current tab has changed.
* @param {PresenterContext} aDocContext context object for tab's
* @param {PivotContext} aDocContext context object for tab's
* document.
* @param {PresenterContext} aVCContext context object for tab's current
* @param {PivotContext} aVCContext context object for tab's current
* virtual cursor position.
*/
tabSelected: function tabSelected(aDocContext, aVCContext) {},
@ -115,7 +114,7 @@ VisualPresenter.prototype = {
viewportChanged: function VisualPresenter_viewportChanged(aWindow) {
if (this._currentAccessible) {
let context = new PresenterContext(this._currentAccessible);
let context = new PivotContext(this._currentAccessible);
return {
type: this.type,
details: {
@ -218,26 +217,9 @@ AndroidPresenter.prototype = {
androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []});
}
let output = [];
aContext.newAncestry.forEach(
function(acc) {
output.push.apply(output, UtteranceGenerator.genForObject(acc));
}
);
output.push.apply(output,
UtteranceGenerator.genForObject(aContext.accessible));
aContext.subtreePreorder.forEach(
function(acc) {
output.push.apply(output, UtteranceGenerator.genForObject(acc));
}
);
androidEvents.push({eventType: (isExploreByTouch) ?
this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
text: output,
text: UtteranceGenerator.genForContext(aContext),
bounds: aContext.bounds});
return {
type: this.type,
@ -342,29 +324,14 @@ SpeechPresenter.prototype = {
if (!aContext.accessible)
return null;
let output = [];
aContext.newAncestry.forEach(
function(acc) {
output.push.apply(output, UtteranceGenerator.genForObject(acc));
}
);
output.push.apply(output,
UtteranceGenerator.genForObject(aContext.accessible));
aContext.subtreePreorder.forEach(
function(acc) {
output.push.apply(output, UtteranceGenerator.genForObject(acc));
}
);
return {
type: this.type,
details: {
actions: [
{method: 'playEarcon', data: 'tick', options: {}},
{method: 'speak', data: output.join(' '), options: {enqueue: true}}
{method: 'speak',
data: UtteranceGenerator.genForContext(aContext).join(' '),
options: {enqueue: true}}
]
}
};
@ -389,120 +356,6 @@ HapticPresenter.prototype = {
}
};
/**
* PresenterContext: An object that generates and caches context information
* for a given accessible and its relationship with another accessible.
*/
this.PresenterContext = function PresenterContext(aAccessible, aOldAccessible) {
this._accessible = aAccessible;
this._oldAccessible =
this._isDefunct(aOldAccessible) ? null : aOldAccessible;
}
PresenterContext.prototype = {
get accessible() {
return this._accessible;
},
get oldAccessible() {
return this._oldAccessible;
},
/*
* 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
* user context as to where they are in the heirarchy.
*/
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);
}
}
return this._newAncestry;
},
/*
* This is a flattened list of the accessible's subtree in preorder.
* It only includes the accessible's visible chidren.
*/
get subtreePreorder() {
function traversePreorder(aAccessible) {
let list = [];
let child = aAccessible.firstChild;
while (child) {
let state = {};
child.getState(state, {});
if (!(state.value & Ci.nsIAccessibleStates.STATE_INVISIBLE)) {
list.push(child);
list.push.apply(list, traversePreorder(child));
}
child = child.nextSibling;
}
return list;
}
if (!this._subtreePreOrder)
this._subtreePreOrder = traversePreorder(this._accessible);
return this._subtreePreOrder;
},
get bounds() {
if (!this._bounds) {
let objX = {}, objY = {}, objW = {}, objH = {};
this._accessible.getBounds(objX, objY, objW, objH);
// XXX: OOP content provides a screen offset of 0, while in-process provides a real
// offset. Removing the offset and using content-relative coords normalizes this.
let docX = {}, docY = {};
let docRoot = this._accessible.rootDocument.
QueryInterface(Ci.nsIAccessible);
docRoot.getBounds(docX, docY, {}, {});
this._bounds = new Rect(objX.value, objY.value, objW.value, objH.value).
translate(-docX.value, -docY.value);
}
return this._bounds.clone();
},
_isDefunct: function _isDefunct(aAccessible) {
try {
let extstate = {};
aAccessible.getState({}, extstate);
return !!(aAccessible.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT);
} catch (x) {
return true;
}
}
};
this.Presentation = {
get presenters() {
delete this.presenters;
@ -521,7 +374,7 @@ this.Presentation = {
pivotChanged: function Presentation_pivotChanged(aPosition,
aOldPosition,
aReason) {
let context = new PresenterContext(aPosition, aOldPosition);
let context = new PivotContext(aPosition, aOldPosition);
return [p.pivotChanged(context, aReason)
for each (p in this.presenters)];
},

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

@ -9,8 +9,9 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Geometry.jsm');
this.EXPORTED_SYMBOLS = ['Utils', 'Logger'];
this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext'];
this.Utils = {
_buildAppMap: {
@ -267,3 +268,132 @@ this.Logger = {
this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i), aIndent + 1);
}
};
/**
* PivotContext: An object that generates and caches context information
* for a given accessible and its relationship with another accessible.
*/
this.PivotContext = function PivotContext(aAccessible, aOldAccessible) {
this._accessible = aAccessible;
this._oldAccessible =
this._isDefunct(aOldAccessible) ? null : aOldAccessible;
}
PivotContext.prototype = {
get accessible() {
return this._accessible;
},
get oldAccessible() {
return this._oldAccessible;
},
/*
* 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
* user context as to where they are in the heirarchy.
*/
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);
}
}
return this._newAncestry;
},
/*
* Traverse the accessible's subtree in pre or post order.
* It only includes the accessible's visible chidren.
*/
_traverse: function _traverse(aAccessible, preorder) {
let list = [];
let child = aAccessible.firstChild;
while (child) {
let state = {};
child.getState(state, {});
if (!(state.value & Ci.nsIAccessibleStates.STATE_INVISIBLE)) {
let traversed = _traverse(child, preorder);
// Prepend or append a child, based on traverse order.
traversed[preorder ? "unshift" : "push"](child);
list.push.apply(list, traversed);
}
child = child.nextSibling;
}
return list;
},
/*
* This is a flattened list of the accessible's subtree in preorder.
* It only includes the accessible's visible chidren.
*/
get subtreePreorder() {
if (!this._subtreePreOrder)
this._subtreePreOrder = this._traverse(this._accessible, true);
return this._subtreePreOrder;
},
/*
* This is a flattened list of the accessible's subtree in postorder.
* It only includes the accessible's visible chidren.
*/
get subtreePostorder() {
if (!this._subtreePostOrder)
this._subtreePostOrder = this._traverse(this._accessible, false);
return this._subtreePostOrder;
},
get bounds() {
if (!this._bounds) {
let objX = {}, objY = {}, objW = {}, objH = {};
this._accessible.getBounds(objX, objY, objW, objH);
// XXX: OOP content provides a screen offset of 0, while in-process provides a real
// offset. Removing the offset and using content-relative coords normalizes this.
let docX = {}, docY = {};
let docRoot = this._accessible.rootDocument.
QueryInterface(Ci.nsIAccessible);
docRoot.getBounds(docX, docY, {}, {});
this._bounds = new Rect(objX.value, objY.value, objW.value, objH.value).
translate(-docX.value, -docY.value);
}
return this._bounds.clone();
},
_isDefunct: function _isDefunct(aAccessible) {
try {
let extstate = {};
aAccessible.getState({}, extstate);
return !!(aAccessible.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT);
} catch (x) {
return true;
}
}
};

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

@ -13,6 +13,23 @@ const INCLUDE_DESC = 0x01;
const INCLUDE_NAME = 0x02;
const INCLUDE_CUSTOM = 0x04;
const UTTERANCE_DESC_FIRST = 0;
// Read and observe changes to a setting for utterance order.
let gUtteranceOrder;
let prefsBranch = Cc['@mozilla.org/preferences-service;1']
.getService(Ci.nsIPrefService).getBranch('accessibility.accessfu.');
let observeUtterance = function observeUtterance(aSubject, aTopic, aData) {
try {
gUtteranceOrder = prefsBranch.getIntPref('utterance');
} catch (x) {
gUtteranceOrder = UTTERANCE_DESC_FIRST;
}
};
// Set initial gUtteranceOrder.
observeUtterance();
prefsBranch.addObserver('utterance', observeUtterance, false);
var gStringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
getService(Ci.nsIStringBundleService).
createBundle('chrome://global/locale/AccessFu.properties');
@ -55,6 +72,35 @@ this.UtteranceGenerator = {
cycle: 'cycleAction'
},
/**
* Generates an utterance for a PivotContext.
* @param {PivotContext} aContext object that generates and caches
* context information for a given accessible and its relationship with
* another accessible.
* @return {Array} An array of strings. Depending on the utterance order,
* the strings describe the context for an accessible object either
* starting from the accessible's ancestry or accessible's subtree.
*/
genForContext: function genForContext(aContext) {
let utterance = [];
let addUtterance = function addUtterance(aAccessible) {
utterance.push.apply(utterance,
UtteranceGenerator.genForObject(aAccessible));
};
if (gUtteranceOrder === UTTERANCE_DESC_FIRST) {
aContext.newAncestry.forEach(addUtterance);
addUtterance(aContext.accessible);
aContext.subtreePreorder.forEach(addUtterance);
} else {
aContext.subtreePostorder.forEach(addUtterance);
addUtterance(aContext.accessible);
aContext.newAncestry.reverse().forEach(addUtterance);
}
return utterance;
},
/**
* Generates an utterance for an object.
@ -213,9 +259,7 @@ this.UtteranceGenerator = {
utterance.push(desc.join(' '));
}
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
if (name)
utterance.push(name);
this._addName(utterance, aAccessible, aFlags);
return utterance;
},
@ -229,28 +273,23 @@ this.UtteranceGenerator = {
utterance.push(desc.join(' '));
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
if (name)
utterance.push(name);
this._addName(utterance, aAccessible, aFlags);
return utterance;
},
heading: function heading(aAccessible, aRoleStr, aStates, aFlags) {
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
let level = {};
aAccessible.groupPosition(level, {}, {});
let utterance =
[gStringBundle.formatStringFromName('headingLevel', [level.value], 1)];
if (name)
utterance.push(name);
this._addName(utterance, aAccessible, aFlags);
return utterance;
},
listitem: function listitem(aAccessible, aRoleStr, aStates, aFlags) {
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
let itemno = {};
let itemof = {};
aAccessible.groupPosition({}, itemof, itemno);
@ -260,8 +299,7 @@ this.UtteranceGenerator = {
else if (itemno.value == itemof.value) // last item
utterance.push(gStringBundle.GetStringFromName('listEnd'));
if (name)
utterance.push(name);
this._addName(utterance, aAccessible, aFlags);
return utterance;
},
@ -286,6 +324,14 @@ this.UtteranceGenerator = {
}
},
_addName: function _addName(utterance, aAccessible, aFlags) {
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
if (name) {
utterance[gUtteranceOrder === UTTERANCE_DESC_FIRST ?
"push" : "unshift"](name);
}
},
_getLocalizedRole: function _getLocalizedRole(aRoleStr) {
try {
return gStringBundle.GetStringFromName(aRoleStr.replace(' ', ''));
@ -325,7 +371,6 @@ this.UtteranceGenerator = {
},
_getListUtterance: function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
let name = (aFlags & INCLUDE_NAME) ? (aAccessible.name || '') : '';
let desc = [];
let roleStr = this._getLocalizedRole(aRoleStr);
if (roleStr)
@ -334,8 +379,7 @@ this.UtteranceGenerator = {
(gStringBundle.formatStringFromName('listItemCount', [aItemCount], 1));
let utterance = [desc.join(' ')];
if (name)
utterance.push(name);
this._addName(utterance, aAccessible, aFlags);
return utterance;
}

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

@ -633,6 +633,8 @@ pref("ui.scrolling.min_scrollable_distance", -1);
// Enable accessibility mode if platform accessibility is enabled.
pref("accessibility.accessfu.activate", 2);
pref("accessibility.accessfu.quicknav_modes", "Link,Heading,FormElement,ListItem");
// Setting for an utterance order (0 - description first, 1 - description last).
pref("accessibility.accessfu.utterance", 0);
// Mobile manages state by autodetection
pref("network.manage-offline-status", true);