mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
Bug 1062016 - Editing state gets out of sync. r=yzen
This commit is contained in:
parent
d3e6ae3032
commit
7f06f725b5
@ -885,6 +885,7 @@ var Input = {
|
||||
},
|
||||
|
||||
setEditState: function setEditState(aEditState) {
|
||||
Logger.debug(() => { return ['setEditState', JSON.stringify(aEditState)] });
|
||||
this.editState = aEditState;
|
||||
},
|
||||
|
||||
|
@ -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']);
|
||||
|
@ -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>
|
@ -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.
|
||||
|
@ -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'
|
||||
}]
|
||||
]);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user