diff --git a/accessible/jsat/EventManager.jsm b/accessible/jsat/EventManager.jsm
index 9bf6e9fc0ce7..e5bca29b2e4a 100644
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -402,10 +402,11 @@ this.EventManager.prototype = {
// If there are embedded objects in the text, ignore them.
// Assuming changes to the descendants would already be handled by the
// show/hide event.
- let modifiedText = event.modifiedText.replace(/\uFFFC/g, '').trim();
- if (!modifiedText) {
+ let modifiedText = event.modifiedText.replace(/\uFFFC/g, '');
+ if (modifiedText != event.modifiedText && !modifiedText.trim()) {
return;
}
+
if (aLiveRegion) {
if (aEvent.eventType === Events.TEXT_REMOVED) {
this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite,
@@ -416,8 +417,8 @@ this.EventManager.prototype = {
modifiedText));
}
} else {
- this.present(Presentation.textChanged(isInserted, event.start,
- event.length, text, modifiedText));
+ this.present(Presentation.textChanged(aEvent.accessible, isInserted,
+ event.start, event.length, text, modifiedText));
}
},
diff --git a/accessible/jsat/Presentation.jsm b/accessible/jsat/Presentation.jsm
index 2758ed5cce91..53407ce16630 100644
--- a/accessible/jsat/Presentation.jsm
+++ b/accessible/jsat/Presentation.jsm
@@ -11,8 +11,7 @@
const {utils: Cu, interfaces: Ci} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
- 'resource://gre/modules/accessibility/Utils.jsm');
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
'resource://gre/modules/accessibility/Utils.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext', // jshint ignore:line
@@ -60,8 +59,8 @@ Presenter.prototype = {
/**
* Text has changed, either by the user or by the system. TODO.
*/
- textChanged: function textChanged(aIsInserted, aStartOffset, aLength, aText, // jshint ignore:line
- aModifiedText) {}, // jshint ignore:line
+ textChanged: function textChanged(aAccessible, aIsInserted, aStartOffset, // jshint ignore:line
+ aLength, aText, aModifiedText) {}, // jshint ignore:line
/**
* Text selection has changed. TODO.
@@ -344,7 +343,7 @@ AndroidPresenter.prototype.tabStateChanged =
};
AndroidPresenter.prototype.textChanged = function AndroidPresenter_textChanged(
- aIsInserted, aStart, aLength, aText, aModifiedText) {
+ aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
let eventDetails = {
eventType: this.ANDROID_VIEW_TEXT_CHANGED,
text: [aText],
@@ -461,6 +460,13 @@ B2GPresenter.prototype = Object.create(Presenter.prototype);
B2GPresenter.prototype.type = 'B2G';
+B2GPresenter.prototype.keyboardEchoSetting =
+ new PrefCache('accessibility.accessfu.keyboard_echo');
+B2GPresenter.prototype.NO_ECHO = 0;
+B2GPresenter.prototype.CHARACTER_ECHO = 1;
+B2GPresenter.prototype.WORD_ECHO = 2;
+B2GPresenter.prototype.CHARACTER_AND_WORD_ECHO = 3;
+
/**
* A pattern used for haptic feedback.
* @type {Array}
@@ -497,6 +503,12 @@ B2GPresenter.prototype.pivotChanged =
B2GPresenter.prototype.valueChanged =
function B2GPresenter_valueChanged(aAccessible) {
+
+ // the editable value changes are handled in the text changed presenter
+ if (Utils.getState(aAccessible).contains(States.EDITABLE)) {
+ return null;
+ }
+
return {
type: this.type,
details: {
@@ -506,6 +518,42 @@ B2GPresenter.prototype.valueChanged =
};
};
+B2GPresenter.prototype.textChanged = function B2GPresenter_textChanged(
+ aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
+ let echoSetting = this.keyboardEchoSetting.value;
+ let text = '';
+
+ if (echoSetting == this.CHARACTER_ECHO ||
+ echoSetting == this.CHARACTER_AND_WORD_ECHO) {
+ text = aModifiedText;
+ }
+
+ // add word if word boundary is added
+ if ((echoSetting == this.WORD_ECHO ||
+ echoSetting == this.CHARACTER_AND_WORD_ECHO) &&
+ aIsInserted && aLength === 1) {
+ let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
+ let startBefore = {}, endBefore = {};
+ let startAfter = {}, endAfter = {};
+ accText.getTextBeforeOffset(aStart,
+ Ci.nsIAccessibleText.BOUNDARY_WORD_END, startBefore, endBefore);
+ let maybeWord = accText.getTextBeforeOffset(aStart + 1,
+ Ci.nsIAccessibleText.BOUNDARY_WORD_END, startAfter, endAfter);
+ if (endBefore.value !== endAfter.value) {
+ text += maybeWord;
+ }
+ }
+
+ return {
+ type: this.type,
+ details: {
+ eventType: 'text-change',
+ data: text
+ }
+ };
+
+ };
+
B2GPresenter.prototype.actionInvoked =
function B2GPresenter_actionInvoked(aObject, aActionName) {
return {
@@ -614,11 +662,11 @@ this.Presentation = { // jshint ignore:line
for each (p in this.presenters)]; // jshint ignore:line
},
- textChanged: function Presentation_textChanged(aIsInserted, aStartOffset,
- aLength, aText,
+ textChanged: function Presentation_textChanged(aAccessible, aIsInserted,
+ aStartOffset, aLength, aText,
aModifiedText) {
- return [p.textChanged(aIsInserted, aStartOffset, aLength, aText, // jshint ignore:line
- aModifiedText) for each (p in this.presenters)]; // jshint ignore:line
+ return [p.textChanged(aAccessible, aIsInserted, aStartOffset, aLength, // jshint ignore:line
+ aText, aModifiedText) for each (p in this.presenters)]; // jshint ignore:line
},
textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd,
diff --git a/accessible/tests/mochitest/jsat/jsatcommon.js b/accessible/tests/mochitest/jsat/jsatcommon.js
index 79abbd9ce33b..34f2490ffbc5 100644
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -626,6 +626,15 @@ function ExpectedValueChange(aValue, aOptions) {
ExpectedValueChange.prototype = Object.create(ExpectedPresent.prototype);
+function ExpectedTextChanged(aValue, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'text-change',
+ data: aValue
+ }, null, aOptions);
+}
+
+ExpectedTextChanged.prototype = Object.create(ExpectedPresent.prototype);
+
function ExpectedEditState(aEditState, aOptions) {
ExpectedMessage.call(this, 'AccessFu:Input', aOptions);
this.json = aEditState;
diff --git a/accessible/tests/mochitest/jsat/test_content_text.html b/accessible/tests/mochitest/jsat/test_content_text.html
index 5397c71f9b3a..352fc9c28244 100644
--- a/accessible/tests/mochitest/jsat/test_content_text.html
+++ b/accessible/tests/mochitest/jsat/test_content_text.html
@@ -8,6 +8,9 @@
+
@@ -169,9 +172,98 @@
multiline: false,
atStart: true,
atEnd: false
- }, { focused: 'html' })]
+ }, { focused: 'html' })],
+
+ [ContentMessages.focusSelector('input'),
+ new ExpectedAnnouncement('editing'),
+ new ExpectedEditState({
+ editing: true,
+ multiline: false,
+ atStart: true,
+ atEnd: true
+ }),
+ new ExpectedCursorChange([{string: 'entry'}]),
+ new ExpectedTextSelectionChanged(0, 0)
+ ],
+ [function() {
+ SpecialPowers.setIntPref(KEYBOARD_ECHO_SETTING, 3);
+ typeKey('a')();
+ },
+ new ExpectedTextChanged('a'),
+ new ExpectedTextSelectionChanged(1, 1),
+ ],
+ [typeKey('b'),
+ new ExpectedTextChanged('b'),
+ new ExpectedTextSelectionChanged(2, 2),
+ ],
+ [typeKey('c'),
+ new ExpectedTextChanged('c'),
+ new ExpectedTextSelectionChanged(3, 3),
+ ],
+ [typeKey('d'),
+ new ExpectedTextChanged('d'),
+ new ExpectedTextSelectionChanged(4, 4),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(' abcd'),
+ new ExpectedTextSelectionChanged(5, 5),
+ ],
+ [typeKey('e'),
+ new ExpectedTextChanged('e'),
+ new ExpectedTextSelectionChanged(6, 6),
+ ],
+ [function() {
+ SpecialPowers.setIntPref(KEYBOARD_ECHO_SETTING, 2);
+ typeKey('a')();
+ },
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(7, 7),
+ ],
+ [typeKey('d'),
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(8, 8),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(' ead'),
+ new ExpectedTextSelectionChanged(9, 9),
+ ],
+ [function() {
+ SpecialPowers.setIntPref(KEYBOARD_ECHO_SETTING, 1);
+ typeKey('f')();
+ },
+ new ExpectedTextChanged('f'),
+ new ExpectedTextSelectionChanged(10, 10),
+ ],
+ [typeKey('g'),
+ new ExpectedTextChanged('g'),
+ new ExpectedTextSelectionChanged(11, 11),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(' '),
+ new ExpectedTextSelectionChanged(12, 12),
+ ],
+ [function() {
+ SpecialPowers.setIntPref(KEYBOARD_ECHO_SETTING, 0);
+ typeKey('f')();
+ },
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(13, 13),
+ ],
+ [typeKey('g'),
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(14, 14),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(15, 15),
+ ],
]);
+ const KEYBOARD_ECHO_SETTING = 'accessibility.accessfu.keyboard_echo';
+ function typeKey(key) {
+ return function() { synthesizeKey(key, {}, currentTabWindow()); };
+ }
+
addA11yLoadEvent(function() {
textTest.start(function () {
closeBrowserWindow();
diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js
index d8c7573e4aa6..3f2cc3d05905 100644
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -797,6 +797,9 @@ pref("accessibility.accessfu.quicknav_index", 0);
pref("accessibility.accessfu.utterance", 1);
// Whether to skip images with empty alt text
pref("accessibility.accessfu.skip_empty_images", true);
+// Setting to change the verbosity of entered text (0 - none, 1 - characters,
+// 2 - words, 3 - both)
+pref("accessibility.accessfu.keyboard_echo", 3);
// Enable hit-target fluffing
pref("ui.touch.radius.enabled", true);