Bug 1062016 - Editing state gets out of sync. r=yzen

This commit is contained in:
Eitan Isaacson 2014-09-09 15:53:46 -07:00
parent 9f367ab3d4
commit 142c5d79bb
5 changed files with 198 additions and 69 deletions

View File

@ -885,6 +885,7 @@ var Input = {
},
setEditState: function setEditState(aEditState) {
Logger.debug(() => { return ['setEditState', JSON.stringify(aEditState)] });
this.editState = aEditState;
},

View File

@ -191,38 +191,18 @@ this.EventManager.prototype = {
}
case Events.TEXT_CARET_MOVED:
{
let acc = aEvent.accessible;
let characterCount = acc.
QueryInterface(Ci.nsIAccessibleText).characterCount;
let acc = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
let caretOffset = aEvent.
QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;
// Update editing state, both for presenter and other things
let state = Utils.getState(acc);
let editState = {
editing: state.contains(States.EDITABLE),
multiline: state.contains(States.MULTI_LINE),
atStart: caretOffset == 0,
atEnd: caretOffset == characterCount
};
// Not interesting
if (!editState.editing && editState.editing == this.editState.editing)
break;
if (editState.editing != this.editState.editing)
this.present(Presentation.editingModeChanged(editState.editing));
if (editState.editing != this.editState.editing ||
editState.multiline != this.editState.multiline ||
editState.atEnd != this.editState.atEnd ||
editState.atStart != this.editState.atStart)
this.sendMsgFunc("AccessFu:Input", editState);
// We could get a caret move in an accessible that is not focused,
// it doesn't mean we are not on any editable accessible. just not
// on this one..
if (Utils.getState(acc).contains(States.FOCUSED)) {
this._setEditingMode(aEvent, caretOffset);
}
this.present(Presentation.textSelectionChanged(acc.getText(0,-1),
caretOffset, caretOffset, 0, 0, aEvent.isFromUserInput));
this.editState = editState;
break;
}
case Events.OBJECT_ATTRIBUTE_CHANGED:
@ -268,6 +248,7 @@ this.EventManager.prototype = {
// Put vc where the focus is at
let acc = aEvent.accessible;
let doc = aEvent.accessibleDocument;
this._setEditingMode(aEvent);
if ([Roles.CHROME_WINDOW,
Roles.DOCUMENT,
Roles.APPLICATION].indexOf(acc.role) < 0) {
@ -293,6 +274,54 @@ this.EventManager.prototype = {
}
},
_setEditingMode: function _setEditingMode(aEvent, aCaretOffset) {
let acc = aEvent.accessible;
let accText, characterCount;
let caretOffset = aCaretOffset;
try {
accText = acc.QueryInterface(Ci.nsIAccessibleText);
} catch (e) {
// No text interface on this accessible.
}
if (accText) {
characterCount = accText.characterCount;
if (caretOffset === undefined) {
caretOffset = accText.caretOffset;
}
}
// Update editing state, both for presenter and other things
let state = Utils.getState(acc);
let editState = {
editing: state.contains(States.EDITABLE) &&
state.contains(States.FOCUSED),
multiline: state.contains(States.MULTI_LINE),
atStart: caretOffset === 0,
atEnd: caretOffset === characterCount
};
// Not interesting
if (!editState.editing && editState.editing === this.editState.editing) {
return;
}
if (editState.editing !== this.editState.editing) {
this.present(Presentation.editingModeChanged(editState.editing));
}
if (editState.editing !== this.editState.editing ||
editState.multiline !== this.editState.multiline ||
editState.atEnd !== this.editState.atEnd ||
editState.atStart !== this.editState.atStart) {
this.sendMsgFunc("AccessFu:Input", editState);
}
this.editState = editState;
},
_handleShow: function _handleShow(aEvent) {
let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
['additions', 'all']);

View File

@ -10,5 +10,6 @@
<p>You're a good guy, mon frere. That means brother in French.
I don't know how I know that. I took four years of Spanish.</p>
<textarea>Please refrain from Mayoneggs during this salmonella scare.</textarea>
<label>So we don't get dessert?</label><input type="text">
</body>
</html>

View File

@ -177,7 +177,8 @@ function AccessFuContentTest(aFuncResultPairs) {
}
AccessFuContentTest.prototype = {
currentPair: null,
expected: [],
currentAction: null,
start: function(aFinishedCallback) {
Logger.logLevel = Logger.DEBUG;
@ -236,6 +237,7 @@ AccessFuContentTest.prototype = {
}
aMessageManager.addMessageListener('AccessFu:Present', this);
aMessageManager.addMessageListener('AccessFu:Input', this);
aMessageManager.addMessageListener('AccessFu:CursorCleared', this);
aMessageManager.addMessageListener('AccessFu:Ready', function () {
aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback);
@ -252,17 +254,25 @@ AccessFuContentTest.prototype = {
},
pump: function() {
this.currentPair = this.queue.shift();
this.expected.shift();
if (this.expected.length) {
return;
}
if (this.currentPair) {
if (typeof this.currentPair[0] === 'function') {
this.currentPair[0](this.mms[0]);
} else if (this.currentPair[0]) {
this.mms[0].sendAsyncMessage(this.currentPair[0].name,
this.currentPair[0].json);
var currentPair = this.queue.shift();
if (currentPair) {
this.currentAction = currentPair[0];
if (typeof this.currentAction === 'function') {
this.currentAction(this.mms[0]);
} else if (this.currentAction) {
this.mms[0].sendAsyncMessage(this.currentAction.name,
this.currentAction.json);
}
if (!this.currentPair[1]) {
this.expected = currentPair.slice(1, currentPair.length);
if (!this.expected[0]) {
this.pump();
}
} else {
@ -271,12 +281,12 @@ AccessFuContentTest.prototype = {
},
receiveMessage: function(aMessage) {
if (!this.currentPair) {
var expected = this.expected[0];
if (!expected) {
return;
}
var expected = this.currentPair[1] || {};
// |expected| can simply be a name of a message, no more further testing.
if (aMessage.name === expected) {
ok(true, 'Received ' + expected);
@ -284,17 +294,20 @@ AccessFuContentTest.prototype = {
return;
}
var speech = this.extractUtterance(aMessage.json);
var android = this.extractAndroid(aMessage.json, expected.android);
if ((speech && expected.speak) || (android && expected.android)) {
var editState = this.extractEditeState(aMessage);
var speech = this.extractUtterance(aMessage);
var android = this.extractAndroid(aMessage, expected.android);
if ((speech && expected.speak)
|| (android && expected.android)
|| (editState && expected.editState)) {
if (expected.speak) {
var checkFunc = SimpleTest[expected.speak_checkFunc] || isDeeply;
checkFunc.apply(SimpleTest, [speech, expected.speak,
'spoken: ' + JSON.stringify(speech) +
' expected: ' + JSON.stringify(expected.speak) +
' after: ' + (typeof this.currentPair[0] === 'function' ?
this.currentPair[0].toString() :
JSON.stringify(this.currentPair[0]))]);
' after: ' + (typeof this.currentAction === 'function' ?
this.currentAction.toString() :
JSON.stringify(this.currentAction))]);
}
if (expected.android) {
@ -303,10 +316,20 @@ AccessFuContentTest.prototype = {
this.lazyCompare(android, expected.android));
}
if (expected.editState) {
var checkFunc = SimpleTest[expected.editState_checkFunc] || isDeeply;
checkFunc.apply(SimpleTest, [editState, expected.editState,
'editState: ' + JSON.stringify(editState) +
' expected: ' + JSON.stringify(expected.editState) +
' after: ' + (typeof this.currentAction === 'function' ?
this.currentAction.toString() :
JSON.stringify(this.currentAction))]);
}
if (expected.focused) {
var doc = currentTabDocument();
is(doc.activeElement, doc.querySelector(expected.focused),
'Correct element is focused');
'Correct element is focused: ' + expected.focused);
}
this.pump();
@ -337,12 +360,20 @@ AccessFuContentTest.prototype = {
return [matches, delta.join(' ')];
},
extractUtterance: function(aData) {
if (!aData) {
extractEditeState: function(aMessage) {
if (!aMessage || aMessage.name !== 'AccessFu:Input') {
return null;
}
for (var output of aData) {
return aMessage.json;
},
extractUtterance: function(aMessage) {
if (!aMessage || aMessage.name !== 'AccessFu:Present') {
return null;
}
for (var output of aMessage.json) {
if (output && output.type === 'B2G') {
if (output.details && output.details.data[0].string !== 'clickAction') {
return output.details.data;
@ -353,12 +384,12 @@ AccessFuContentTest.prototype = {
return null;
},
extractAndroid: function(aData, aExpectedEvents) {
if (!aData) {
extractAndroid: function(aMessage, aExpectedEvents) {
if (!aMessage || aMessage.name !== 'AccessFu:Present') {
return null;
}
for (var output of aData) {
for (var output of aMessage.json) {
if (output && output.type === 'Android') {
for (var i in output.details) {
// Only extract if event types match expected event types.

View File

@ -132,27 +132,36 @@
// Editable text tests.
[ContentMessages.focusSelector('textarea'), {
speak: ['Please refrain from Mayoneggs during this ' +
'salmonella scare.', {string: 'textarea'}]
}],
[null, { // When we first focus, caret is at 0.
android: [{
eventType: AndroidEvent.VIEW_TEXT_SELECTION_CHANGED,
brailleOutput: {
selectionStart: 0,
selectionEnd: 0
}
}]
editState: {
editing: true,
multiline: true,
atStart: true,
atEnd: false
}
],
}, {
speak: ['Please refrain from Mayoneggs during this ' +
'salmonella scare.', {string: 'textarea'}]
}, { // When we first focus, caret is at 0.
android: [{
eventType: AndroidEvent.VIEW_TEXT_SELECTION_CHANGED,
brailleOutput: {
selectionStart: 0,
selectionEnd: 0
}
}]
}],
[ContentMessages.activateCurrent(10), {
android: [{
eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
fromIndex: 0,
toIndex: 10
}]
}],
[null, {
}, {
editState: { editing: true,
multiline: true,
atStart: false,
atEnd: false }
}, {
android: [{
eventType: AndroidEvent.VIEW_TEXT_SELECTION_CHANGED,
brailleOutput: {
@ -167,8 +176,7 @@
fromIndex: 10,
toIndex: 20
}]
}],
[null, {
}, {
android: [{
eventType: AndroidEvent.VIEW_TEXT_SELECTION_CHANGED,
brailleOutput: {
@ -217,7 +225,66 @@
eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
fromIndex: 53,
toIndex: 59
}]
}],
focused: 'textarea'
}],
// bug xxx
[ContentMessages.simpleMoveNext, {
speak: ['So we don\'t get dessert?', {string: 'label'}],
focused: 'html'
}, {
editState: {
editing: false,
multiline: false,
atStart: true,
atEnd: false }
}],
[ContentMessages.simpleMoveNext, {
speak: [{ string : 'entry' }],
focused: 'html'
}],
[ContentMessages.activateCurrent(0), {
editState: {
editing: true,
multiline: false,
atStart: true,
atEnd: true
},
focused: 'input[type=text]'
}],
[ContentMessages.simpleMovePrevious, {
editState: {
editing: false,
multiline: false,
atStart: true,
atEnd: false
},
focused: 'html'
}],
[ContentMessages.simpleMoveNext, {
speak: [{ string : 'entry' }],
focused: 'html'
}],
[ContentMessages.activateCurrent(0), {
editState: {
editing: true,
multiline: false,
atStart: true,
atEnd: true
},
focused: 'input[type=text]'
}],
[ContentMessages.simpleMovePrevious, {
speak: [ 'So we don\'t get dessert?', {string: 'label'} ]
}, {
editState: {
editing: false,
multiline: false,
atStart: true,
atEnd: false
},
focused: 'html'
}]
]);