зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
30752f7d2b
Коммит
24d10f8c80
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче