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);