diff options
Diffstat (limited to 'accessible/jsat/Presentation.jsm')
-rw-r--r-- | accessible/jsat/Presentation.jsm | 769 |
1 files changed, 769 insertions, 0 deletions
diff --git a/accessible/jsat/Presentation.jsm b/accessible/jsat/Presentation.jsm new file mode 100644 index 000000000..6912d0ea5 --- /dev/null +++ b/accessible/jsat/Presentation.jsm @@ -0,0 +1,769 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global Components, XPCOMUtils, Utils, Logger, BraillePresenter, Presentation, + UtteranceGenerator, BrailleGenerator, States, Roles, PivotContext */ +/* exported Presentation */ + +'use strict'; + +const {utils: Cu, interfaces: Ci} = Components; + +Cu.import('resource://gre/modules/XPCOMUtils.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 + 'resource://gre/modules/accessibility/Utils.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator', // jshint ignore:line + 'resource://gre/modules/accessibility/OutputGenerator.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator', // jshint ignore:line + 'resource://gre/modules/accessibility/OutputGenerator.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line + 'resource://gre/modules/accessibility/Constants.jsm'); +XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line + 'resource://gre/modules/accessibility/Constants.jsm'); + +this.EXPORTED_SYMBOLS = ['Presentation']; // jshint ignore:line + +/** + * The interface for all presenter classes. A presenter could be, for example, + * a speech output module, or a visual cursor indicator. + */ +function Presenter() {} + +Presenter.prototype = { + /** + * The type of presenter. Used for matching it with the appropriate output method. + */ + type: 'Base', + + /** + * The virtual cursor's position changed. + * @param {PivotContext} aContext the context object for the new pivot + * position. + * @param {int} aReason the reason for the pivot change. + * See nsIAccessiblePivot. + * @param {bool} aIsFromUserInput the pivot change was invoked by the user + */ + pivotChanged: function pivotChanged(aContext, aReason, aIsFromUserInput) {}, // jshint ignore:line + + /** + * An object's action has been invoked. + * @param {nsIAccessible} aObject the object that has been invoked. + * @param {string} aActionName the name of the action. + */ + actionInvoked: function actionInvoked(aObject, aActionName) {}, // jshint ignore:line + + /** + * Text has changed, either by the user or by the system. TODO. + */ + textChanged: function textChanged(aAccessible, aIsInserted, aStartOffset, // jshint ignore:line + aLength, aText, aModifiedText) {}, // jshint ignore:line + + /** + * Text selection has changed. TODO. + */ + textSelectionChanged: function textSelectionChanged( + aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput) {}, // jshint ignore:line + + /** + * Selection has changed. TODO. + * @param {nsIAccessible} aObject the object that has been selected. + */ + selectionChanged: function selectionChanged(aObject) {}, // jshint ignore:line + + /** + * Name has changed. + * @param {nsIAccessible} aAccessible the object whose value has changed. + */ + nameChanged: function nameChanged(aAccessible) {}, // jshint ignore: line + + /** + * Value has changed. + * @param {nsIAccessible} aAccessible the object whose value has changed. + */ + valueChanged: function valueChanged(aAccessible) {}, // jshint ignore:line + + /** + * The tab, or the tab's document state has changed. + * @param {nsIAccessible} aDocObj the tab document accessible that has had its + * state changed, or null if the tab has no associated document yet. + * @param {string} aPageState the state name for the tab, valid states are: + * 'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'. + */ + tabStateChanged: function tabStateChanged(aDocObj, aPageState) {}, // jshint ignore:line + + /** + * The current tab has changed. + * @param {PivotContext} aDocContext context object for tab's + * document. + * @param {PivotContext} aVCContext context object for tab's current + * virtual cursor position. + */ + tabSelected: function tabSelected(aDocContext, aVCContext) {}, // jshint ignore:line + + /** + * The viewport has changed, either a scroll, pan, zoom, or + * landscape/portrait toggle. + * @param {Window} aWindow window of viewport that changed. + * @param {PivotContext} aCurrentContext context of last pivot change. + */ + viewportChanged: function viewportChanged(aWindow, aCurrentContext) {}, // jshint ignore:line + + /** + * We have entered or left text editing mode. + */ + editingModeChanged: function editingModeChanged(aIsEditing) {}, // jshint ignore:line + + /** + * Announce something. Typically an app state change. + */ + announce: function announce(aAnnouncement) {}, // jshint ignore:line + + + /** + * User tried to move cursor forward or backward with no success. + * @param {string} aMoveMethod move method that was used (eg. 'moveNext'). + */ + noMove: function noMove(aMoveMethod) {}, + + /** + * Announce a live region. + * @param {PivotContext} aContext context object for an accessible. + * @param {boolean} aIsPolite A politeness level for a live region. + * @param {boolean} aIsHide An indicator of hide/remove event. + * @param {string} aModifiedText Optional modified text. + */ + liveRegion: function liveRegionShown(aContext, aIsPolite, aIsHide, // jshint ignore:line + aModifiedText) {} // jshint ignore:line +}; + +/** + * Visual presenter. Draws a box around the virtual cursor's position. + */ +function VisualPresenter() {} + +VisualPresenter.prototype = Object.create(Presenter.prototype); + +VisualPresenter.prototype.type = 'Visual'; + +/** + * The padding in pixels between the object and the highlight border. + */ +VisualPresenter.prototype.BORDER_PADDING = 2; + +VisualPresenter.prototype.viewportChanged = + function VisualPresenter_viewportChanged(aWindow, aCurrentContext) { + if (!aCurrentContext) { + return null; + } + + let currentAcc = aCurrentContext.accessibleForBounds; + let start = aCurrentContext.startOffset; + let end = aCurrentContext.endOffset; + if (Utils.isAliveAndVisible(currentAcc)) { + let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) : + Utils.getTextBounds(currentAcc, start, end); + + return { + type: this.type, + details: { + eventType: 'viewport-change', + bounds: bounds, + padding: this.BORDER_PADDING + } + }; + } + + return null; + }; + +VisualPresenter.prototype.pivotChanged = + function VisualPresenter_pivotChanged(aContext) { + if (!aContext.accessible) { + // XXX: Don't hide because another vc may be using the highlight. + return null; + } + + try { + aContext.accessibleForBounds.scrollTo( + Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE); + + let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ? + aContext.bounds : Utils.getTextBounds(aContext.accessibleForBounds, + aContext.startOffset, + aContext.endOffset); + + return { + type: this.type, + details: { + eventType: 'vc-change', + bounds: bounds, + padding: this.BORDER_PADDING + } + }; + } catch (e) { + Logger.logException(e, 'Failed to get bounds'); + return null; + } + }; + +VisualPresenter.prototype.tabSelected = + function VisualPresenter_tabSelected(aDocContext, aVCContext) { + return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE); + }; + +VisualPresenter.prototype.tabStateChanged = + function VisualPresenter_tabStateChanged(aDocObj, aPageState) { + if (aPageState == 'newdoc') { + return {type: this.type, details: {eventType: 'tabstate-change'}}; + } + + return null; + }; + +/** + * Android presenter. Fires Android a11y events. + */ +function AndroidPresenter() {} + +AndroidPresenter.prototype = Object.create(Presenter.prototype); + +AndroidPresenter.prototype.type = 'Android'; + +// Android AccessibilityEvent type constants. +AndroidPresenter.prototype.ANDROID_VIEW_CLICKED = 0x01; +AndroidPresenter.prototype.ANDROID_VIEW_LONG_CLICKED = 0x02; +AndroidPresenter.prototype.ANDROID_VIEW_SELECTED = 0x04; +AndroidPresenter.prototype.ANDROID_VIEW_FOCUSED = 0x08; +AndroidPresenter.prototype.ANDROID_VIEW_TEXT_CHANGED = 0x10; +AndroidPresenter.prototype.ANDROID_WINDOW_STATE_CHANGED = 0x20; +AndroidPresenter.prototype.ANDROID_VIEW_HOVER_ENTER = 0x80; +AndroidPresenter.prototype.ANDROID_VIEW_HOVER_EXIT = 0x100; +AndroidPresenter.prototype.ANDROID_VIEW_SCROLLED = 0x1000; +AndroidPresenter.prototype.ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000; +AndroidPresenter.prototype.ANDROID_ANNOUNCEMENT = 0x4000; +AndroidPresenter.prototype.ANDROID_VIEW_ACCESSIBILITY_FOCUSED = 0x8000; +AndroidPresenter.prototype.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = + 0x20000; + +AndroidPresenter.prototype.pivotChanged = + function AndroidPresenter_pivotChanged(aContext, aReason) { + if (!aContext.accessible) { + return null; + } + + let androidEvents = []; + + let isExploreByTouch = (aReason == Ci.nsIAccessiblePivot.REASON_POINT && + Utils.AndroidSdkVersion >= 14); + let focusEventType = (Utils.AndroidSdkVersion >= 16) ? + this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED : + this.ANDROID_VIEW_FOCUSED; + + if (isExploreByTouch) { + // This isn't really used by TalkBack so this is a half-hearted attempt + // for now. + androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []}); + } + + let brailleOutput = {}; + if (Utils.AndroidSdkVersion >= 16) { + if (!this._braillePresenter) { + this._braillePresenter = new BraillePresenter(); + } + brailleOutput = this._braillePresenter.pivotChanged(aContext, aReason). + details; + } + + if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) { + if (Utils.AndroidSdkVersion >= 16) { + let adjustedText = aContext.textAndAdjustedOffsets; + + androidEvents.push({ + eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, + text: [adjustedText.text], + fromIndex: adjustedText.startOffset, + toIndex: adjustedText.endOffset + }); + } + } else { + let state = Utils.getState(aContext.accessible); + androidEvents.push({eventType: (isExploreByTouch) ? + this.ANDROID_VIEW_HOVER_ENTER : focusEventType, + text: Utils.localize(UtteranceGenerator.genForContext( + aContext)), + bounds: aContext.bounds, + clickable: aContext.accessible.actionCount > 0, + checkable: state.contains(States.CHECKABLE), + checked: state.contains(States.CHECKED), + brailleOutput: brailleOutput}); + } + + + return { + type: this.type, + details: androidEvents + }; + }; + +AndroidPresenter.prototype.actionInvoked = + function AndroidPresenter_actionInvoked(aObject, aActionName) { + let state = Utils.getState(aObject); + + // Checkable objects use TalkBack's text derived from the event state, + // so we don't populate the text here. + let text = ''; + if (!state.contains(States.CHECKABLE)) { + text = Utils.localize(UtteranceGenerator.genForAction(aObject, + aActionName)); + } + + return { + type: this.type, + details: [{ + eventType: this.ANDROID_VIEW_CLICKED, + text: text, + checked: state.contains(States.CHECKED) + }] + }; + }; + +AndroidPresenter.prototype.tabSelected = + function AndroidPresenter_tabSelected(aDocContext, aVCContext) { + // Send a pivot change message with the full context utterance for this doc. + return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE); + }; + +AndroidPresenter.prototype.tabStateChanged = + function AndroidPresenter_tabStateChanged(aDocObj, aPageState) { + return this.announce( + UtteranceGenerator.genForTabStateChange(aDocObj, aPageState)); + }; + +AndroidPresenter.prototype.textChanged = function AndroidPresenter_textChanged( + aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) { + let eventDetails = { + eventType: this.ANDROID_VIEW_TEXT_CHANGED, + text: [aText], + fromIndex: aStart, + removedCount: 0, + addedCount: 0 + }; + + if (aIsInserted) { + eventDetails.addedCount = aLength; + eventDetails.beforeText = + aText.substring(0, aStart) + aText.substring(aStart + aLength); + } else { + eventDetails.removedCount = aLength; + eventDetails.beforeText = + aText.substring(0, aStart) + aModifiedText + aText.substring(aStart); + } + + return {type: this.type, details: [eventDetails]}; + }; + +AndroidPresenter.prototype.textSelectionChanged = + function AndroidPresenter_textSelectionChanged(aText, aStart, aEnd, aOldStart, + aOldEnd, aIsFromUserInput) { + let androidEvents = []; + + if (Utils.AndroidSdkVersion >= 14 && !aIsFromUserInput) { + if (!this._braillePresenter) { + this._braillePresenter = new BraillePresenter(); + } + let brailleOutput = this._braillePresenter.textSelectionChanged( + aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput).details; + + androidEvents.push({ + eventType: this.ANDROID_VIEW_TEXT_SELECTION_CHANGED, + text: [aText], + fromIndex: aStart, + toIndex: aEnd, + itemCount: aText.length, + brailleOutput: brailleOutput + }); + } + + if (Utils.AndroidSdkVersion >= 16 && aIsFromUserInput) { + 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 + }; + }; + +AndroidPresenter.prototype.viewportChanged = + function AndroidPresenter_viewportChanged(aWindow, aCurrentContext) { + if (Utils.AndroidSdkVersion < 14) { + return null; + } + + let events = [{ + eventType: this.ANDROID_VIEW_SCROLLED, + text: [], + scrollX: aWindow.scrollX, + scrollY: aWindow.scrollY, + maxScrollX: aWindow.scrollMaxX, + maxScrollY: aWindow.scrollMaxY + }]; + + if (Utils.AndroidSdkVersion >= 16 && aCurrentContext) { + let currentAcc = aCurrentContext.accessibleForBounds; + if (Utils.isAliveAndVisible(currentAcc)) { + events.push({ + eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED, + bounds: Utils.getBounds(currentAcc) + }); + } + } + + return { + type: this.type, + details: events + }; + }; + +AndroidPresenter.prototype.editingModeChanged = + function AndroidPresenter_editingModeChanged(aIsEditing) { + return this.announce(UtteranceGenerator.genForEditingMode(aIsEditing)); + }; + +AndroidPresenter.prototype.announce = + function AndroidPresenter_announce(aAnnouncement) { + let localizedAnnouncement = Utils.localize(aAnnouncement).join(' '); + return { + type: this.type, + details: [{ + eventType: (Utils.AndroidSdkVersion >= 16) ? + this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED, + text: [localizedAnnouncement], + addedCount: localizedAnnouncement.length, + removedCount: 0, + fromIndex: 0 + }] + }; + }; + +AndroidPresenter.prototype.liveRegion = + function AndroidPresenter_liveRegion(aContext, aIsPolite, + aIsHide, aModifiedText) { + return this.announce( + UtteranceGenerator.genForLiveRegion(aContext, aIsHide, aModifiedText)); + }; + +AndroidPresenter.prototype.noMove = + function AndroidPresenter_noMove(aMoveMethod) { + return { + type: this.type, + details: [ + { eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED, + exitView: aMoveMethod, + text: [''] + }] + }; + }; + +/** + * A B2G presenter for Gaia. + */ +function B2GPresenter() {} + +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} + */ +B2GPresenter.prototype.PIVOT_CHANGE_HAPTIC_PATTERN = [40]; + +/** + * Pivot move reasons. + * @type {Array} + */ +B2GPresenter.prototype.pivotChangedReasons = ['none', 'next', 'prev', 'first', + 'last', 'text', 'point']; + +B2GPresenter.prototype.pivotChanged = + function B2GPresenter_pivotChanged(aContext, aReason, aIsUserInput) { + if (!aContext.accessible) { + return null; + } + + return { + type: this.type, + details: { + eventType: 'vc-change', + data: UtteranceGenerator.genForContext(aContext), + options: { + pattern: this.PIVOT_CHANGE_HAPTIC_PATTERN, + isKey: Utils.isActivatableOnFingerUp(aContext.accessible), + reason: this.pivotChangedReasons[aReason], + isUserInput: aIsUserInput, + hints: aContext.interactionHints + } + } + }; + }; + +B2GPresenter.prototype.nameChanged = + function B2GPresenter_nameChanged(aAccessible, aIsPolite = true) { + return { + type: this.type, + details: { + eventType: 'name-change', + data: aAccessible.name, + options: {enqueue: aIsPolite} + } + }; + }; + +B2GPresenter.prototype.valueChanged = + function B2GPresenter_valueChanged(aAccessible, aIsPolite = true) { + + // 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: { + eventType: 'value-change', + data: aAccessible.value, + options: {enqueue: aIsPolite} + } + }; + }; + +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 { + type: this.type, + details: { + eventType: 'action', + data: UtteranceGenerator.genForAction(aObject, aActionName) + } + }; + }; + +B2GPresenter.prototype.liveRegion = function B2GPresenter_liveRegion(aContext, + aIsPolite, aIsHide, aModifiedText) { + return { + type: this.type, + details: { + eventType: 'liveregion-change', + data: UtteranceGenerator.genForLiveRegion(aContext, aIsHide, + aModifiedText), + options: {enqueue: aIsPolite} + } + }; + }; + +B2GPresenter.prototype.announce = + function B2GPresenter_announce(aAnnouncement) { + return { + type: this.type, + details: { + eventType: 'announcement', + data: aAnnouncement + } + }; + }; + +B2GPresenter.prototype.noMove = + function B2GPresenter_noMove(aMoveMethod) { + return { + type: this.type, + details: { + eventType: 'no-move', + data: aMoveMethod + } + }; + }; + +/** + * A braille presenter + */ +function BraillePresenter() {} + +BraillePresenter.prototype = Object.create(Presenter.prototype); + +BraillePresenter.prototype.type = 'Braille'; + +BraillePresenter.prototype.pivotChanged = + function BraillePresenter_pivotChanged(aContext) { + if (!aContext.accessible) { + return null; + } + + return { + type: this.type, + details: { + output: Utils.localize(BrailleGenerator.genForContext(aContext)).join( + ' '), + selectionStart: 0, + selectionEnd: 0 + } + }; + }; + +BraillePresenter.prototype.textSelectionChanged = + function BraillePresenter_textSelectionChanged(aText, aStart, aEnd) { + return { + type: this.type, + details: { + selectionStart: aStart, + selectionEnd: aEnd + } + }; + }; + +this.Presentation = { // jshint ignore:line + get presenters() { + delete this.presenters; + let presenterMap = { + 'mobile/android': [VisualPresenter, AndroidPresenter], + 'b2g': [VisualPresenter, B2GPresenter], + 'browser': [VisualPresenter, B2GPresenter, AndroidPresenter] + }; + this.presenters = presenterMap[Utils.MozBuildApp].map(P => new P()); + return this.presenters; + }, + + get displayedAccessibles() { + delete this.displayedAccessibles; + this.displayedAccessibles = new WeakMap(); + return this.displayedAccessibles; + }, + + pivotChanged: function Presentation_pivotChanged( + aPosition, aOldPosition, aReason, aStartOffset, aEndOffset, aIsUserInput) { + let context = new PivotContext( + aPosition, aOldPosition, aStartOffset, aEndOffset); + if (context.accessible) { + this.displayedAccessibles.set(context.accessible.document.window, context); + } + + return this.presenters.map(p => p.pivotChanged(context, aReason, aIsUserInput)); + }, + + actionInvoked: function Presentation_actionInvoked(aObject, aActionName) { + return this.presenters.map(p => p.actionInvoked(aObject, aActionName)); + }, + + textChanged: function Presentation_textChanged(aAccessible, aIsInserted, + aStartOffset, aLength, aText, + aModifiedText) { + return this.presenters.map(p => p.textChanged(aAccessible, aIsInserted, + aStartOffset, aLength, + aText, aModifiedText)); + }, + + textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd, + aOldStart, aOldEnd, + aIsFromUserInput) { + return this.presenters.map(p => p.textSelectionChanged(aText, aStart, aEnd, + aOldStart, aOldEnd, + aIsFromUserInput)); + }, + + nameChanged: function nameChanged(aAccessible) { + return this.presenters.map(p => p.nameChanged(aAccessible)); + }, + + valueChanged: function valueChanged(aAccessible) { + return this.presenters.map(p => p.valueChanged(aAccessible)); + }, + + tabStateChanged: function Presentation_tabStateChanged(aDocObj, aPageState) { + return this.presenters.map(p => p.tabStateChanged(aDocObj, aPageState)); + }, + + viewportChanged: function Presentation_viewportChanged(aWindow) { + let context = this.displayedAccessibles.get(aWindow); + return this.presenters.map(p => p.viewportChanged(aWindow, context)); + }, + + editingModeChanged: function Presentation_editingModeChanged(aIsEditing) { + return this.presenters.map(p => p.editingModeChanged(aIsEditing)); + }, + + announce: function Presentation_announce(aAnnouncement) { + // XXX: Typically each presenter uses the UtteranceGenerator, + // but there really isn't a point here. + return this.presenters.map(p => p.announce(UtteranceGenerator.genForAnnouncement(aAnnouncement))); + }, + + noMove: function Presentation_noMove(aMoveMethod) { + return this.presenters.map(p => p.noMove(aMoveMethod)); + }, + + liveRegion: function Presentation_liveRegion(aAccessible, aIsPolite, aIsHide, + aModifiedText) { + let context; + if (!aModifiedText) { + context = new PivotContext(aAccessible, null, -1, -1, true, + aIsHide ? true : false); + } + return this.presenters.map(p => p.liveRegion(context, aIsPolite, aIsHide, + aModifiedText)); + } +}; |