Accessibility/SoftFocus
Background
It is useful to have a focus-like state that is exclusive to accessibility and is independant of the DOM state. From the user's perspective this allows reviewing a document or web application without directly affecting its state, like moving the caret or cycling through focusable elements. This is useful in mobile, for example a user could navigate to a text entry without having it capture input. Only if the user chooses to enter text they would "activate" the text entry which would bring up an on screen keyboard, etc. This feature would also allow easier navigation of content.
Note: From what I gathered from Alex's input in Toronto, he thinks it would be useful to not limit this to a single node at a time, but to allow multiple focal points. It might pay off in the long term as this would be more future proof, and allow other use cases such as multi-region magnifier or interesting features in cognitive disablity ATs. This introduces a layer of complexity (pivots); nonetheles, the usage should not get complicated, we would just always used a pivot index of 0.
Pivots
A pivot (working title) is a focal point. Typically you would only have one pivot that would change depending on where the user is exploring. But you could have multiple pivots each pointing to something else and changing independantly.
nsIAccessible
interface nsIAccessible : nsISupports
{
...
/***
* Take the given pivot's soft focus.
*
* @param pivotIndex The pivot of which to take focus. If the given index is -1 or out
* of range (pivotIndex >= number of pivots), create a new pivot.
* @return The index of the affected pivot.
*/
long takeSoftFocus(in long pivotIndex);
}
State
An accessible that is soft focused would have a state of EXT_STATE_SOFT_FOCUSED.
Event
When an accessible gets or loses focus, an EVENT_STATE_CHANGED is fired.
it seems like it is important for event consumer to know which pivit changed, but I'm not really sure how to comunicate this best.
nsIAccessibleDocument
interface nsIAccessibleDocument : nsISupports
{
...
/***
* The collection of tracked soft focuses. The index in the array matches the pivot's
* index.
*/
readonly attribute nsIArray softFocusPivots;
/***
* Remove soft focus state from accessible on the given pivot. No-op if no
* accessible is focused on that pivot. On a successful call the
* nsIAccessibleDocument.softFocusPivots element at the given pivot index will
* be set to null.
*
* @param pivotIndex The pivot of which to blur focus.
*/
void blurSoftFocus(in PRInt32 pivotIndex);
does a negative index make sense?
}
nsIAccessibleText
In addition to soft focus, an accessible that supports nsIAccessibleText could have a soft text selection that is independant of any actual selection in the document.
interface nsIAccessibleText : nsISupports
{
...
/***
* Set the soft selection to the given range.
*
* @param startOffset Start index of text to soft select.
* @param endOffset End index of text to soft select.
*/
void setSoftSelection (in long startOffset, in long endOffset);
/***
* Clear the soft selection in this accessible.
*/
void clearSoftSelection ();
/***
* Get the soft selection range in this accessible.
*
* @param startOffset [out] Start index of soft selected text.
* @param endOffset [out] End index of soft selected text.
* @return false if no range is selected.
*/
boolean getSoftSelection (out long startOffset, out long endOffset);
}
Event
When the soft selection changes an EVENT_SOFT_TEXT_SELECTION_CHANGED event is fired. This event is only emitted if the accessible already has EXT_STATE_SOFT_FOCUSED.
Example Usage
Setting Soft Focus
An input event handler that changes the soft focus.
function inputEventHandler(event) {
...
let currNode = docAcc.softFocusPivots[0];
if (currNode.nextSibling)
currNode.nextSibling.takeSoftFocus(0);
}
Presenting Soft Focus
An event listener that highlights the bounds of a soft focus. drawRect() is left to your imagination.
function handleEvent(aEvent) {
if (aEvent.type == EVENT_STATE_CHANGED) {
let event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
if (event.state == EXT_STATE_SOFT_FOCUSED && event.isExtraState()) {
if (event.isEnabled())
showSoftFocus(aEvent.accessible);
else
hideSoftFocus();
}
} else if (aEvent.type == EVENT_SOFT_TEXT_SELECTION_CHANGED) {
showSoftFocus(aEvent.accessible);
}
}
function showSoftFocus (aAccessible) {
// Only interested in pivot index 0
if (accDoc.softFocusPivots[0] != aAccessible)
return;
let x = {};
let y = {};
let w = {};
let h = {};
try {
let textAcc = aAccessible.QueryInterface(nsIAccessibleText);
let start = {};
let end = {};
if (textAcc.getSoftSelection(start, end)) {
textAcc.getRangeExtents(start.value, end.value, x, y, w, h,
COORDTYPE_SCREEN_RELATIVE);
drawRect(x.value, y.value, w.value, h.value);
return;
}
catch (e) {
}
aAccessible.getBounds(x, y, w, h);
drawRect(x.value, y.value, w.value, h.value);
}