Bug 785852 - Support editing mode navigation in Jelly Bean. r=eeejay r=kats

This commit is contained in:
Max Li 2013-06-19 16:11:46 -04:00
parent d8deaa0bf9
commit a7dbb70ce3
4 changed files with 144 additions and 4 deletions

View File

@ -116,6 +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);
Utils.win.addEventListener('TabOpen', this);
Utils.win.addEventListener('TabClose', this);
Utils.win.addEventListener('TabSelect', this);
@ -158,6 +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');
if (this.doneCallback) {
this.doneCallback();
@ -277,6 +279,9 @@ this.AccessFu = {
{action: 'whereIsIt', move: true});
}
break;
case 'Accessibility:MoveCaret':
this.Input.moveCaret(JSON.parse(aData));
break;
case 'remote-browser-frame-shown':
case 'in-process-browser-or-app-frame-shown':
{
@ -667,6 +672,18 @@ var Input = {
inputType: aInputType});
},
moveCaret: function moveCaret(aDetails) {
if (!this.editState.editing) {
return;
}
aDetails.atStart = this.editState.atStart;
aDetails.atEnd = this.editState.atEnd;
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
mm.sendAsyncMessage('AccessFu:MoveCaret', aDetails);
},
activateCurrent: function activateCurrent() {
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
mm.sendAsyncMessage('AccessFu:Activate', {});

View File

@ -61,7 +61,7 @@ Presenter.prototype = {
/**
* Text selection has changed. TODO.
*/
textSelectionChanged: function textSelectionChanged() {},
textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd) {},
/**
* Selection has changed. TODO.
@ -205,8 +205,10 @@ AndroidPresenter.prototype = {
ANDROID_VIEW_HOVER_ENTER: 0x80,
ANDROID_VIEW_HOVER_EXIT: 0x100,
ANDROID_VIEW_SCROLLED: 0x1000,
ANDROID_VIEW_TEXT_SELECTION_CHANGED: 0x2000,
ANDROID_ANNOUNCEMENT: 0x4000,
ANDROID_VIEW_ACCESSIBILITY_FOCUSED: 0x8000,
ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000,
pivotChanged: function AndroidPresenter_pivotChanged(aContext, aReason) {
if (!aContext.accessible)
@ -302,6 +304,37 @@ AndroidPresenter.prototype = {
return {type: this.type, details: [eventDetails]};
},
textSelectionChanged: function AndroidPresenter_textSelectionChanged(aText, aStart,
aEnd, aOldStart,
aOldEnd) {
let androidEvents = [];
if (Utils.AndroidSdkVersion >= 14) {
androidEvents.push({
eventType: this.ANDROID_VIEW_TEXT_SELECTION_CHANGED,
text: [aText],
fromIndex: aStart,
toIndex: aEnd,
itemCount: aText.length
});
}
if (Utils.AndroidSdkVersion >= 16) {
let [from, to] = aOldStart < aStart ? [aOldStart, aStart] : [aStart, aOldStart];
androidEvents.push({
eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
text: [aText],
fromIndex: from,
toIndex: to
});
}
return {
type: this.type,
details: androidEvents
};
},
viewportChanged: function AndroidPresenter_viewportChanged(aWindow) {
if (Utils.AndroidSdkVersion < 14)
return null;
@ -445,6 +478,11 @@ this.Presentation = {
for each (p in this.presenters)];
},
textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd) {
return [p.textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd)
for each (p in this.presenters)];
},
tabStateChanged: function Presentation_tabStateChanged(aDocObj, aPageState) {
return [p.tabStateChanged(aDocObj, aPageState)
for each (p in this.presenters)];

View File

@ -172,6 +172,58 @@ function activateContextMenu(aMessage) {
sendContextMenuCoordinates(vc.position);
}
function moveCaret(aMessage) {
const MOVEMENT_GRANULARITY_CHARACTER = 1;
const MOVEMENT_GRANULARITY_WORD = 2;
const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
let direction = aMessage.json.direction;
let granularity = aMessage.json.granularity;
let accessible = Utils.getVirtualCursor(content.document).position;
let accText = accessible.QueryInterface(Ci.nsIAccessibleText);
let oldOffset = accText.caretOffset;
let text = accText.getText(0, accText.characterCount);
let start = {}, end = {};
if (direction === 'Previous' && !aMessage.json.atStart) {
switch (granularity) {
case MOVEMENT_GRANULARITY_CHARACTER:
accText.caretOffset--;
break;
case MOVEMENT_GRANULARITY_WORD:
accText.getTextBeforeOffset(accText.caretOffset,
Ci.nsIAccessibleText.BOUNDARY_WORD_START, start, end);
accText.caretOffset = end.value === accText.caretOffset ? start.value : end.value;
break;
case MOVEMENT_GRANULARITY_PARAGRAPH:
let startOfParagraph = text.lastIndexOf('\n', accText.caretOffset - 1);
accText.caretOffset = startOfParagraph !== -1 ? startOfParagraph : 0;
break;
}
} else if (direction === 'Next' && !aMessage.json.atEnd) {
switch (granularity) {
case MOVEMENT_GRANULARITY_CHARACTER:
accText.caretOffset++;
break;
case MOVEMENT_GRANULARITY_WORD:
accText.getTextAtOffset(accText.caretOffset,
Ci.nsIAccessibleText.BOUNDARY_WORD_END, start, end);
accText.caretOffset = end.value;
break;
case MOVEMENT_GRANULARITY_PARAGRAPH:
accText.caretOffset = text.indexOf('\n', accText.caretOffset + 1);
break;
}
}
let newOffset = accText.caretOffset;
if (oldOffset !== newOffset) {
let msg = Presentation.textSelectionChanged(text, newOffset, newOffset,
oldOffset, oldOffset);
sendAsyncMessage('AccessFu:Present', msg);
}
}
function scroll(aMessage) {
let vc = Utils.getVirtualCursor(content.document);
@ -262,6 +314,7 @@ addMessageListener(
addMessageListener('AccessFu:Activate', activateCurrent);
addMessageListener('AccessFu:ContextMenu', activateContextMenu);
addMessageListener('AccessFu:Scroll', scroll);
addMessageListener('AccessFu:MoveCaret', moveCaret);
if (!eventManager) {
eventManager = new EventManager(this);
@ -278,6 +331,7 @@ addMessageListener(
removeMessageListener('AccessFu:Activate', activateCurrent);
removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
removeMessageListener('AccessFu:Scroll', scroll);
removeMessageListener('AccessFu:MoveCaret', moveCaret);
eventManager.stop();
});

View File

@ -10,6 +10,7 @@ import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.ActivityManager;
@ -286,7 +287,6 @@ public class GeckoAccessibility {
AccessibilityNodeInfo.obtain(sVirtualCursorNode) :
AccessibilityNodeInfo.obtain(host, virtualDescendantId);
switch (virtualDescendantId) {
case View.NO_ID:
// This is the parent LayerView node, populate it with children.
@ -304,6 +304,11 @@ public class GeckoAccessibility {
info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
break;
}
return info;
@ -332,10 +337,36 @@ public class GeckoAccessibility {
GeckoAppShell.
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ActivateObject", null));
return true;
} else if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY && virtualViewId == VIRTUAL_CURSOR_POSITION) {
} else if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY &&
virtualViewId == VIRTUAL_CURSOR_POSITION) {
// XXX: Self brailling gives this action with a bogus argument instead of an actual click action
int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
if (granularity < 0) {
GeckoAppShell.
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ActivateObject", null));
} else {
JSONObject movementData = new JSONObject();
try {
movementData.put("direction", "Next");
movementData.put("granularity", granularity);
} catch (JSONException e) {
return true;
}
GeckoAppShell.
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveCaret", movementData.toString()));
}
return true;
} else if (action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY &&
virtualViewId == VIRTUAL_CURSOR_POSITION) {
JSONObject movementData = new JSONObject();
try {
movementData.put("direction", "Previous");
movementData.put("granularity", arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
} catch (JSONException e) {
return true;
}
GeckoAppShell.
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ActivateObject", null));
sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveCaret", movementData.toString()));
return true;
}
return host.performAccessibilityAction(action, arguments);