Bug 886076 - Part 2: Support movement by granularity in AccessFu. r=eeejay

This commit is contained in:
Max Li 2013-07-24 17:52:57 -04:00
parent 42034f7543
commit dec1b1843d
7 changed files with 167 additions and 41 deletions

View File

@ -116,7 +116,7 @@ this.AccessFu = {
Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
Services.obs.addObserver(this, 'Accessibility:Focus', false);
Services.obs.addObserver(this, 'Accessibility:ActivateObject', false);
Services.obs.addObserver(this, 'Accessibility:MoveCaret', false);
Services.obs.addObserver(this, 'Accessibility:MoveByGranularity', false);
Utils.win.addEventListener('TabOpen', this);
Utils.win.addEventListener('TabClose', this);
Utils.win.addEventListener('TabSelect', this);
@ -159,7 +159,7 @@ this.AccessFu = {
Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
Services.obs.removeObserver(this, 'Accessibility:Focus');
Services.obs.removeObserver(this, 'Accessibility:ActivateObject');
Services.obs.removeObserver(this, 'Accessibility:MoveCaret');
Services.obs.removeObserver(this, 'Accessibility:MoveByGranularity');
if (this.doneCallback) {
this.doneCallback();
@ -277,8 +277,8 @@ this.AccessFu = {
this.showCurrent(true);
}
break;
case 'Accessibility:MoveCaret':
this.Input.moveCaret(JSON.parse(aData));
case 'Accessibility:MoveByGranularity':
this.Input.moveByGranularity(JSON.parse(aData));
break;
case 'remote-browser-frame-shown':
case 'in-process-browser-or-app-frame-shown':
@ -788,16 +788,23 @@ var Input = {
origin: 'top', inputType: aInputType});
},
moveCaret: function moveCaret(aDetails) {
moveByGranularity: function moveByGranularity(aDetails) {
const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
if (!this.editState.editing) {
return;
if (aDetails.granularity === MOVEMENT_GRANULARITY_PARAGRAPH) {
this.moveCursor('move' + aDetails.direction, 'Paragraph', 'gesture');
return;
}
} else {
aDetails.atStart = this.editState.atStart;
aDetails.atEnd = this.editState.atEnd;
}
aDetails.atStart = this.editState.atStart;
aDetails.atEnd = this.editState.atEnd;
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
mm.sendAsyncMessage('AccessFu:MoveCaret', aDetails);
let type = this.editState.editing ? 'AccessFu:MoveCaret' :
'AccessFu:MoveByGranularity';
mm.sendAsyncMessage(type, aDetails);
},
activateCurrent: function activateCurrent(aData) {

View File

@ -156,11 +156,12 @@ this.EventManager.prototype = {
QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
let reason = event.reason;
if (this.editState.editing)
if (this.editState.editing) {
aEvent.accessibleDocument.takeFocus();
}
this.present(
Presentation.pivotChanged(position, event.oldAccessible, reason));
Presentation.pivotChanged(position, event.oldAccessible, reason,
pivot.startOffset, pivot.endOffset));
break;
}

View File

@ -124,9 +124,18 @@ VisualPresenter.prototype = {
BORDER_PADDING: 2,
viewportChanged: function VisualPresenter_viewportChanged(aWindow) {
let currentAcc = this._displayedAccessibles.get(aWindow);
let currentDisplay = this._displayedAccessibles.get(aWindow);
if (!currentDisplay) {
return null;
}
let currentAcc = currentDisplay.accessible;
let start = currentDisplay.startOffset;
let end = currentDisplay.endOffset;
if (Utils.isAliveAndVisible(currentAcc)) {
let bounds = Utils.getBounds(currentAcc);
let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) :
Utils.getTextBounds(currentAcc, start, end);
return {
type: this.type,
details: {
@ -142,7 +151,9 @@ VisualPresenter.prototype = {
pivotChanged: function VisualPresenter_pivotChanged(aContext, aReason) {
this._displayedAccessibles.set(aContext.accessible.document.window,
aContext.accessible);
{ accessible: aContext.accessible,
startOffset: aContext.startOffset,
endOffset: aContext.endOffset });
if (!aContext.accessible)
return {type: this.type, details: {method: 'hideBounds'}};
@ -150,11 +161,16 @@ VisualPresenter.prototype = {
try {
aContext.accessible.scrollTo(
Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ?
aContext.bounds : Utils.getTextBounds(aContext.accessible,
aContext.startOffset, aContext.endOffset);
return {
type: this.type,
details: {
method: 'showBounds',
bounds: aContext.bounds,
bounds: bounds,
padding: this.BORDER_PADDING
}
};
@ -232,8 +248,6 @@ AndroidPresenter.prototype = {
androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []});
}
let state = Utils.getStates(aContext.accessible)[0];
let brailleOutput = {};
if (Utils.AndroidSdkVersion >= 16) {
if (!this._braillePresenter) {
@ -243,16 +257,30 @@ AndroidPresenter.prototype = {
details;
}
androidEvents.push({eventType: (isExploreByTouch) ?
this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
text: UtteranceGenerator.genForContext(aContext).output,
bounds: aContext.bounds,
clickable: aContext.accessible.actionCount > 0,
checkable: !!(state &
Ci.nsIAccessibleStates.STATE_CHECKABLE),
checked: !!(state &
Ci.nsIAccessibleStates.STATE_CHECKED),
brailleOutput: brailleOutput});
if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) {
if (Utils.AndroidSdkVersion >= 16) {
let adjustedText = aContext.textAndAdjustedOffsets;
androidEvents.push({
eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
text: [adjustedText.text],
fromIndex: adjustedText.startOffset,
toIndex: adjustedText.endOffset
});
}
} else {
let state = Utils.getStates(aContext.accessible)[0];
androidEvents.push({eventType: (isExploreByTouch) ?
this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
text: UtteranceGenerator.genForContext(aContext).output,
bounds: aContext.bounds,
clickable: aContext.accessible.actionCount > 0,
checkable: !!(state &
Ci.nsIAccessibleStates.STATE_CHECKABLE),
checked: !!(state &
Ci.nsIAccessibleStates.STATE_CHECKED),
brailleOutput: brailleOutput});
}
return {
@ -494,10 +522,9 @@ this.Presentation = {
return this.presenters;
},
pivotChanged: function Presentation_pivotChanged(aPosition,
aOldPosition,
aReason) {
let context = new PivotContext(aPosition, aOldPosition);
pivotChanged: function Presentation_pivotChanged(aPosition, aOldPosition, aReason,
aStartOffset, aEndOffset) {
let context = new PivotContext(aPosition, aOldPosition, aStartOffset, aEndOffset);
return [p.pivotChanged(context, aReason)
for each (p in this.presenters)];
},

View File

@ -43,6 +43,8 @@ const ROLE_TERM = Ci.nsIAccessibleRole.ROLE_TERM;
const ROLE_SEPARATOR = Ci.nsIAccessibleRole.ROLE_SEPARATOR;
const ROLE_TABLE = Ci.nsIAccessibleRole.ROLE_TABLE;
const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
const ROLE_PARAGRAPH = Ci.nsIAccessibleRole.ROLE_PARAGRAPH;
const ROLE_SECTION = Ci.nsIAccessibleRole.ROLE_SECTION;
this.EXPORTED_SYMBOLS = ['TraversalRules'];
@ -248,6 +250,19 @@ this.TraversalRules = {
PageTab: new BaseTraversalRule(
[ROLE_PAGETAB]),
Paragraph: new BaseTraversalRule(
[ROLE_PARAGRAPH,
ROLE_SECTION],
function Paragraph_match(aAccessible) {
for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
if (child.role === ROLE_TEXT_LEAF) {
return FILTER_MATCH | FILTER_IGNORE_SUBTREE;
}
}
return FILTER_IGNORE;
}),
RadioButton: new BaseTraversalRule(
[ROLE_RADIOBUTTON,
ROLE_RADIO_MENU_ITEM]),

View File

@ -227,6 +227,14 @@ this.Utils = {
return new Rect(objX.value, objY.value, objW.value, objH.value);
},
getTextBounds: function getTextBounds(aAccessible, aStart, aEnd) {
let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
let objX = {}, objY = {}, objW = {}, objH = {};
accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
return new Rect(objX.value, objY.value, objW.value, objH.value);
},
inHiddenSubtree: function inHiddenSubtree(aAccessible) {
for (let acc=aAccessible; acc; acc=acc.parent) {
let hidden = Utils.getAttributes(acc).hidden;
@ -413,10 +421,13 @@ this.Logger = {
* 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.PivotContext = function PivotContext(aAccessible, aOldAccessible,
aStartOffset, aEndOffset) {
this._accessible = aAccessible;
this._oldAccessible =
this._isDefunct(aOldAccessible) ? null : aOldAccessible;
this.startOffset = aStartOffset;
this.endOffset = aEndOffset;
}
PivotContext.prototype = {
@ -428,6 +439,45 @@ PivotContext.prototype = {
return this._oldAccessible;
},
get textAndAdjustedOffsets() {
if (this.startOffset === -1 && this.endOffset === -1) {
return null;
}
if (!this._textAndAdjustedOffsets) {
let result = {startOffset: this.startOffset,
endOffset: this.endOffset,
text: this._accessible.QueryInterface(Ci.nsIAccessibleText).
getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)};
let hypertextAcc = this._accessible.QueryInterface(Ci.nsIAccessibleHyperText);
// Iterate through the links in backwards order so text replacements don't
// affect the offsets of links yet to be processed.
for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) {
let link = hypertextAcc.getLinkAt(i);
let linkText = '';
if (link instanceof Ci.nsIAccessibleText) {
linkText = link.QueryInterface(Ci.nsIAccessibleText).
getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
}
let start = link.startIndex;
let end = link.endIndex;
for (let offset of ['startOffset', 'endOffset']) {
if (this[offset] >= end) {
result[offset] += linkText.length - (end - start);
}
}
result.text = result.text.substring(0, start) + linkText +
result.text.substring(end);
}
this._textAndAdjustedOffsets = result;
}
return this._textAndAdjustedOffsets;
},
/**
* Get a list of |aAccessible|'s ancestry up to the root.
* @param {nsIAccessible} aAccessible.

View File

@ -8,6 +8,10 @@ let Cu = Components.utils;
const ROLE_ENTRY = Ci.nsIAccessibleRole.ROLE_ENTRY;
const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
const MOVEMENT_GRANULARITY_CHARACTER = 1;
const MOVEMENT_GRANULARITY_WORD = 2;
const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
'resource://gre/modules/accessibility/Utils.jsm');
@ -117,7 +121,8 @@ function showCurrent(aMessage) {
vc.moveFirst(TraversalRules.Simple);
} else {
sendAsyncMessage('AccessFu:Present', Presentation.pivotChanged(
vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE));
vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
vc.startOffset, vc.endOffset));
}
}
}
@ -226,11 +231,30 @@ function activateContextMenu(aMessage) {
}
}
function moveCaret(aMessage) {
const MOVEMENT_GRANULARITY_CHARACTER = 1;
const MOVEMENT_GRANULARITY_WORD = 2;
const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
function moveByGranularity(aMessage) {
let direction = aMessage.json.direction;
let vc = Utils.getVirtualCursor(content.document);
let granularity;
switch(aMessage.json.granularity) {
case MOVEMENT_GRANULARITY_CHARACTER:
granularity = Ci.nsIAccessiblePivot.CHAR_BOUNDARY;
break;
case MOVEMENT_GRANULARITY_WORD:
granularity = Ci.nsIAccessiblePivot.WORD_BOUNDARY;
break;
default:
return;
}
if (direction === 'Previous') {
vc.movePreviousByText(granularity);
} else if (direction === 'Next') {
vc.moveNextByText(granularity);
}
}
function moveCaret(aMessage) {
let direction = aMessage.json.direction;
let granularity = aMessage.json.granularity;
let accessible = Utils.getVirtualCursor(content.document).position;
@ -373,6 +397,7 @@ addMessageListener(
addMessageListener('AccessFu:ContextMenu', activateContextMenu);
addMessageListener('AccessFu:Scroll', scroll);
addMessageListener('AccessFu:MoveCaret', moveCaret);
addMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
if (!eventManager) {
eventManager = new EventManager(this);
@ -392,6 +417,7 @@ addMessageListener(
removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
removeMessageListener('AccessFu:Scroll', scroll);
removeMessageListener('AccessFu:MoveCaret', moveCaret);
removeMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
eventManager.stop();
});

View File

@ -364,7 +364,7 @@ public class GeckoAccessibility {
return true;
}
GeckoAppShell.
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveCaret", movementData.toString()));
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveByGranularity", movementData.toString()));
}
return true;
} else if (action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY &&
@ -377,7 +377,7 @@ public class GeckoAccessibility {
return true;
}
GeckoAppShell.
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveCaret", movementData.toString()));
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveByGranularity", movementData.toString()));
return true;
}
return host.performAccessibilityAction(action, arguments);