michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: 'use strict'; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import('resource://gre/modules/XPCOMUtils.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Utils', michael@0: 'resource://gre/modules/accessibility/Utils.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Logger', michael@0: 'resource://gre/modules/accessibility/Utils.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext', michael@0: 'resource://gre/modules/accessibility/Utils.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator', michael@0: 'resource://gre/modules/accessibility/OutputGenerator.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator', michael@0: 'resource://gre/modules/accessibility/OutputGenerator.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Roles', michael@0: 'resource://gre/modules/accessibility/Constants.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'States', michael@0: 'resource://gre/modules/accessibility/Constants.jsm'); michael@0: michael@0: this.EXPORTED_SYMBOLS = ['Presentation']; michael@0: michael@0: /** michael@0: * The interface for all presenter classes. A presenter could be, for example, michael@0: * a speech output module, or a visual cursor indicator. michael@0: */ michael@0: function Presenter() {} michael@0: michael@0: Presenter.prototype = { michael@0: /** michael@0: * The type of presenter. Used for matching it with the appropriate output method. michael@0: */ michael@0: type: 'Base', michael@0: michael@0: /** michael@0: * The virtual cursor's position changed. michael@0: * @param {PivotContext} aContext the context object for the new pivot michael@0: * position. michael@0: * @param {int} aReason the reason for the pivot change. michael@0: * See nsIAccessiblePivot. michael@0: */ michael@0: pivotChanged: function pivotChanged(aContext, aReason) {}, michael@0: michael@0: /** michael@0: * An object's action has been invoked. michael@0: * @param {nsIAccessible} aObject the object that has been invoked. michael@0: * @param {string} aActionName the name of the action. michael@0: */ michael@0: actionInvoked: function actionInvoked(aObject, aActionName) {}, michael@0: michael@0: /** michael@0: * Text has changed, either by the user or by the system. TODO. michael@0: */ michael@0: textChanged: function textChanged(aIsInserted, aStartOffset, michael@0: aLength, aText, michael@0: aModifiedText) {}, michael@0: michael@0: /** michael@0: * Text selection has changed. TODO. michael@0: */ michael@0: textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUser) {}, michael@0: michael@0: /** michael@0: * Selection has changed. TODO. michael@0: * @param {nsIAccessible} aObject the object that has been selected. michael@0: */ michael@0: selectionChanged: function selectionChanged(aObject) {}, michael@0: michael@0: /** michael@0: * Value has changed. michael@0: * @param {nsIAccessible} aAccessible the object whose value has changed. michael@0: */ michael@0: valueChanged: function valueChanged(aAccessible) {}, michael@0: michael@0: /** michael@0: * The tab, or the tab's document state has changed. michael@0: * @param {nsIAccessible} aDocObj the tab document accessible that has had its michael@0: * state changed, or null if the tab has no associated document yet. michael@0: * @param {string} aPageState the state name for the tab, valid states are: michael@0: * 'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'. michael@0: */ michael@0: tabStateChanged: function tabStateChanged(aDocObj, aPageState) {}, michael@0: michael@0: /** michael@0: * The current tab has changed. michael@0: * @param {PivotContext} aDocContext context object for tab's michael@0: * document. michael@0: * @param {PivotContext} aVCContext context object for tab's current michael@0: * virtual cursor position. michael@0: */ michael@0: tabSelected: function tabSelected(aDocContext, aVCContext) {}, michael@0: michael@0: /** michael@0: * The viewport has changed, either a scroll, pan, zoom, or michael@0: * landscape/portrait toggle. michael@0: * @param {Window} aWindow window of viewport that changed. michael@0: */ michael@0: viewportChanged: function viewportChanged(aWindow) {}, michael@0: michael@0: /** michael@0: * We have entered or left text editing mode. michael@0: */ michael@0: editingModeChanged: function editingModeChanged(aIsEditing) {}, michael@0: michael@0: /** michael@0: * Announce something. Typically an app state change. michael@0: */ michael@0: announce: function announce(aAnnouncement) {}, michael@0: michael@0: michael@0: michael@0: /** michael@0: * Announce a live region. michael@0: * @param {PivotContext} aContext context object for an accessible. michael@0: * @param {boolean} aIsPolite A politeness level for a live region. michael@0: * @param {boolean} aIsHide An indicator of hide/remove event. michael@0: * @param {string} aModifiedText Optional modified text. michael@0: */ michael@0: liveRegion: function liveRegionShown(aContext, aIsPolite, aIsHide, michael@0: aModifiedText) {} michael@0: }; michael@0: michael@0: /** michael@0: * Visual presenter. Draws a box around the virtual cursor's position. michael@0: */ michael@0: michael@0: this.VisualPresenter = function VisualPresenter() { michael@0: this._displayedAccessibles = new WeakMap(); michael@0: }; michael@0: michael@0: VisualPresenter.prototype = { michael@0: __proto__: Presenter.prototype, michael@0: michael@0: type: 'Visual', michael@0: michael@0: /** michael@0: * The padding in pixels between the object and the highlight border. michael@0: */ michael@0: BORDER_PADDING: 2, michael@0: michael@0: viewportChanged: function VisualPresenter_viewportChanged(aWindow) { michael@0: let currentDisplay = this._displayedAccessibles.get(aWindow); michael@0: if (!currentDisplay) { michael@0: return null; michael@0: } michael@0: michael@0: let currentAcc = currentDisplay.accessible; michael@0: let start = currentDisplay.startOffset; michael@0: let end = currentDisplay.endOffset; michael@0: if (Utils.isAliveAndVisible(currentAcc)) { michael@0: let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) : michael@0: Utils.getTextBounds(currentAcc, start, end); michael@0: michael@0: return { michael@0: type: this.type, michael@0: details: { michael@0: method: 'showBounds', michael@0: bounds: bounds, michael@0: padding: this.BORDER_PADDING michael@0: } michael@0: }; michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: pivotChanged: function VisualPresenter_pivotChanged(aContext, aReason) { michael@0: if (!aContext.accessible) { michael@0: // XXX: Don't hide because another vc may be using the highlight. michael@0: return null; michael@0: } michael@0: michael@0: this._displayedAccessibles.set(aContext.accessible.document.window, michael@0: { accessible: aContext.accessibleForBounds, michael@0: startOffset: aContext.startOffset, michael@0: endOffset: aContext.endOffset }); michael@0: michael@0: try { michael@0: aContext.accessibleForBounds.scrollTo( michael@0: Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE); michael@0: michael@0: let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ? michael@0: aContext.bounds : Utils.getTextBounds(aContext.accessibleForBounds, michael@0: aContext.startOffset, michael@0: aContext.endOffset); michael@0: michael@0: return { michael@0: type: this.type, michael@0: details: { michael@0: method: 'showBounds', michael@0: bounds: bounds, michael@0: padding: this.BORDER_PADDING michael@0: } michael@0: }; michael@0: } catch (e) { michael@0: Logger.logException(e, 'Failed to get bounds'); michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: tabSelected: function VisualPresenter_tabSelected(aDocContext, aVCContext) { michael@0: return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE); michael@0: }, michael@0: michael@0: tabStateChanged: function VisualPresenter_tabStateChanged(aDocObj, michael@0: aPageState) { michael@0: if (aPageState == 'newdoc') michael@0: return {type: this.type, details: {method: 'hideBounds'}}; michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: announce: function VisualPresenter_announce(aAnnouncement) { michael@0: return { michael@0: type: this.type, michael@0: details: { michael@0: method: 'showAnnouncement', michael@0: text: aAnnouncement, michael@0: duration: 1000 michael@0: } michael@0: }; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Android presenter. Fires Android a11y events. michael@0: */ michael@0: michael@0: this.AndroidPresenter = function AndroidPresenter() {}; michael@0: michael@0: AndroidPresenter.prototype = { michael@0: __proto__: Presenter.prototype, michael@0: michael@0: type: 'Android', michael@0: michael@0: // Android AccessibilityEvent type constants. michael@0: ANDROID_VIEW_CLICKED: 0x01, michael@0: ANDROID_VIEW_LONG_CLICKED: 0x02, michael@0: ANDROID_VIEW_SELECTED: 0x04, michael@0: ANDROID_VIEW_FOCUSED: 0x08, michael@0: ANDROID_VIEW_TEXT_CHANGED: 0x10, michael@0: ANDROID_WINDOW_STATE_CHANGED: 0x20, michael@0: ANDROID_VIEW_HOVER_ENTER: 0x80, michael@0: ANDROID_VIEW_HOVER_EXIT: 0x100, michael@0: ANDROID_VIEW_SCROLLED: 0x1000, michael@0: ANDROID_VIEW_TEXT_SELECTION_CHANGED: 0x2000, michael@0: ANDROID_ANNOUNCEMENT: 0x4000, michael@0: ANDROID_VIEW_ACCESSIBILITY_FOCUSED: 0x8000, michael@0: ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000, michael@0: michael@0: pivotChanged: function AndroidPresenter_pivotChanged(aContext, aReason) { michael@0: if (!aContext.accessible) michael@0: return null; michael@0: michael@0: let androidEvents = []; michael@0: michael@0: let isExploreByTouch = (aReason == Ci.nsIAccessiblePivot.REASON_POINT && michael@0: Utils.AndroidSdkVersion >= 14); michael@0: let focusEventType = (Utils.AndroidSdkVersion >= 16) ? michael@0: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED : michael@0: this.ANDROID_VIEW_FOCUSED; michael@0: michael@0: if (isExploreByTouch) { michael@0: // This isn't really used by TalkBack so this is a half-hearted attempt michael@0: // for now. michael@0: androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []}); michael@0: } michael@0: michael@0: let brailleOutput = {}; michael@0: if (Utils.AndroidSdkVersion >= 16) { michael@0: if (!this._braillePresenter) { michael@0: this._braillePresenter = new BraillePresenter(); michael@0: } michael@0: brailleOutput = this._braillePresenter.pivotChanged(aContext, aReason). michael@0: details; michael@0: } michael@0: michael@0: if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) { michael@0: if (Utils.AndroidSdkVersion >= 16) { michael@0: let adjustedText = aContext.textAndAdjustedOffsets; michael@0: michael@0: androidEvents.push({ michael@0: eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, michael@0: text: [adjustedText.text], michael@0: fromIndex: adjustedText.startOffset, michael@0: toIndex: adjustedText.endOffset michael@0: }); michael@0: } michael@0: } else { michael@0: let state = Utils.getState(aContext.accessible); michael@0: androidEvents.push({eventType: (isExploreByTouch) ? michael@0: this.ANDROID_VIEW_HOVER_ENTER : focusEventType, michael@0: text: UtteranceGenerator.genForContext(aContext).output, michael@0: bounds: aContext.bounds, michael@0: clickable: aContext.accessible.actionCount > 0, michael@0: checkable: state.contains(States.CHECKABLE), michael@0: checked: state.contains(States.CHECKED), michael@0: brailleOutput: brailleOutput}); michael@0: } michael@0: michael@0: michael@0: return { michael@0: type: this.type, michael@0: details: androidEvents michael@0: }; michael@0: }, michael@0: michael@0: actionInvoked: function AndroidPresenter_actionInvoked(aObject, aActionName) { michael@0: let state = Utils.getState(aObject); michael@0: michael@0: // Checkable objects will have a state changed event we will use instead. michael@0: if (state.contains(States.CHECKABLE)) michael@0: return null; michael@0: michael@0: return { michael@0: type: this.type, michael@0: details: [{ michael@0: eventType: this.ANDROID_VIEW_CLICKED, michael@0: text: UtteranceGenerator.genForAction(aObject, aActionName), michael@0: checked: state.contains(States.CHECKED) michael@0: }] michael@0: }; michael@0: }, michael@0: michael@0: tabSelected: function AndroidPresenter_tabSelected(aDocContext, aVCContext) { michael@0: // Send a pivot change message with the full context utterance for this doc. michael@0: return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE); michael@0: }, michael@0: michael@0: tabStateChanged: function AndroidPresenter_tabStateChanged(aDocObj, michael@0: aPageState) { michael@0: return this.announce( michael@0: UtteranceGenerator.genForTabStateChange(aDocObj, aPageState).join(' ')); michael@0: }, michael@0: michael@0: textChanged: function AndroidPresenter_textChanged(aIsInserted, aStart, michael@0: aLength, aText, michael@0: aModifiedText) { michael@0: let eventDetails = { michael@0: eventType: this.ANDROID_VIEW_TEXT_CHANGED, michael@0: text: [aText], michael@0: fromIndex: aStart, michael@0: removedCount: 0, michael@0: addedCount: 0 michael@0: }; michael@0: michael@0: if (aIsInserted) { michael@0: eventDetails.addedCount = aLength; michael@0: eventDetails.beforeText = michael@0: aText.substring(0, aStart) + aText.substring(aStart + aLength); michael@0: } else { michael@0: eventDetails.removedCount = aLength; michael@0: eventDetails.beforeText = michael@0: aText.substring(0, aStart) + aModifiedText + aText.substring(aStart); michael@0: } michael@0: michael@0: return {type: this.type, details: [eventDetails]}; michael@0: }, michael@0: michael@0: textSelectionChanged: function AndroidPresenter_textSelectionChanged(aText, aStart, michael@0: aEnd, aOldStart, michael@0: aOldEnd, aIsFromUser) { michael@0: let androidEvents = []; michael@0: michael@0: if (Utils.AndroidSdkVersion >= 14 && !aIsFromUser) { michael@0: if (!this._braillePresenter) { michael@0: this._braillePresenter = new BraillePresenter(); michael@0: } michael@0: let brailleOutput = this._braillePresenter.textSelectionChanged(aText, aStart, aEnd, michael@0: aOldStart, aOldEnd, michael@0: aIsFromUser).details; michael@0: michael@0: androidEvents.push({ michael@0: eventType: this.ANDROID_VIEW_TEXT_SELECTION_CHANGED, michael@0: text: [aText], michael@0: fromIndex: aStart, michael@0: toIndex: aEnd, michael@0: itemCount: aText.length, michael@0: brailleOutput: brailleOutput michael@0: }); michael@0: } michael@0: michael@0: if (Utils.AndroidSdkVersion >= 16 && aIsFromUser) { michael@0: let [from, to] = aOldStart < aStart ? [aOldStart, aStart] : [aStart, aOldStart]; michael@0: androidEvents.push({ michael@0: eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, michael@0: text: [aText], michael@0: fromIndex: from, michael@0: toIndex: to michael@0: }); michael@0: } michael@0: michael@0: return { michael@0: type: this.type, michael@0: details: androidEvents michael@0: }; michael@0: }, michael@0: michael@0: viewportChanged: function AndroidPresenter_viewportChanged(aWindow) { michael@0: if (Utils.AndroidSdkVersion < 14) michael@0: return null; michael@0: michael@0: return { michael@0: type: this.type, michael@0: details: [{ michael@0: eventType: this.ANDROID_VIEW_SCROLLED, michael@0: text: [], michael@0: scrollX: aWindow.scrollX, michael@0: scrollY: aWindow.scrollY, michael@0: maxScrollX: aWindow.scrollMaxX, michael@0: maxScrollY: aWindow.scrollMaxY michael@0: }] michael@0: }; michael@0: }, michael@0: michael@0: editingModeChanged: function AndroidPresenter_editingModeChanged(aIsEditing) { michael@0: return this.announce( michael@0: UtteranceGenerator.genForEditingMode(aIsEditing).join(' ')); michael@0: }, michael@0: michael@0: announce: function AndroidPresenter_announce(aAnnouncement) { michael@0: return { michael@0: type: this.type, michael@0: details: [{ michael@0: eventType: (Utils.AndroidSdkVersion >= 16) ? michael@0: this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED, michael@0: text: [aAnnouncement], michael@0: addedCount: aAnnouncement.length, michael@0: removedCount: 0, michael@0: fromIndex: 0 michael@0: }] michael@0: }; michael@0: }, michael@0: michael@0: liveRegion: function AndroidPresenter_liveRegion(aContext, aIsPolite, michael@0: aIsHide, aModifiedText) { michael@0: return this.announce( michael@0: UtteranceGenerator.genForLiveRegion(aContext, aIsHide, michael@0: aModifiedText).join(' ')); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * A speech presenter for direct TTS output michael@0: */ michael@0: michael@0: this.SpeechPresenter = function SpeechPresenter() {}; michael@0: michael@0: SpeechPresenter.prototype = { michael@0: __proto__: Presenter.prototype, michael@0: michael@0: type: 'Speech', michael@0: michael@0: pivotChanged: function SpeechPresenter_pivotChanged(aContext, aReason) { michael@0: if (!aContext.accessible) michael@0: return null; michael@0: michael@0: return { michael@0: type: this.type, michael@0: details: { michael@0: actions: [ michael@0: {method: 'playEarcon', michael@0: data: aContext.accessible.role === Roles.KEY ? michael@0: 'virtual_cursor_key' : 'virtual_cursor_move', michael@0: options: {}}, michael@0: {method: 'speak', michael@0: data: UtteranceGenerator.genForContext(aContext).output.join(' '), michael@0: options: {enqueue: true}} michael@0: ] michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: valueChanged: function SpeechPresenter_valueChanged(aAccessible) { michael@0: return { michael@0: type: this.type, michael@0: details: { michael@0: actions: [ michael@0: { method: 'speak', michael@0: data: aAccessible.value, michael@0: options: { enqueue: false } } michael@0: ] michael@0: } michael@0: } michael@0: }, michael@0: michael@0: actionInvoked: function SpeechPresenter_actionInvoked(aObject, aActionName) { michael@0: let actions = []; michael@0: if (aActionName === 'click') { michael@0: actions.push({method: 'playEarcon', michael@0: data: 'clicked', michael@0: options: {}}); michael@0: } else { michael@0: actions.push({method: 'speak', michael@0: data: UtteranceGenerator.genForAction(aObject, aActionName).join(' '), michael@0: options: {enqueue: false}}); michael@0: } michael@0: return { type: this.type, details: { actions: actions } }; michael@0: }, michael@0: michael@0: liveRegion: function SpeechPresenter_liveRegion(aContext, aIsPolite, aIsHide, michael@0: aModifiedText) { michael@0: return { michael@0: type: this.type, michael@0: details: { michael@0: actions: [{ michael@0: method: 'speak', michael@0: data: UtteranceGenerator.genForLiveRegion(aContext, aIsHide, michael@0: aModifiedText).join(' '), michael@0: options: {enqueue: aIsPolite} michael@0: }] michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: announce: function SpeechPresenter_announce(aAnnouncement) { michael@0: return { michael@0: type: this.type, michael@0: details: { michael@0: actions: [{ michael@0: method: 'speak', data: aAnnouncement, options: { enqueue: false } michael@0: }] michael@0: } michael@0: }; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * A haptic presenter michael@0: */ michael@0: michael@0: this.HapticPresenter = function HapticPresenter() {}; michael@0: michael@0: HapticPresenter.prototype = { michael@0: __proto__: Presenter.prototype, michael@0: michael@0: type: 'Haptic', michael@0: michael@0: PIVOT_CHANGE_PATTERN: [40], michael@0: michael@0: pivotChanged: function HapticPresenter_pivotChanged(aContext, aReason) { michael@0: return { type: this.type, details: { pattern: this.PIVOT_CHANGE_PATTERN } }; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * A braille presenter michael@0: */ michael@0: michael@0: this.BraillePresenter = function BraillePresenter() {}; michael@0: michael@0: BraillePresenter.prototype = { michael@0: __proto__: Presenter.prototype, michael@0: michael@0: type: 'Braille', michael@0: michael@0: pivotChanged: function BraillePresenter_pivotChanged(aContext, aReason) { michael@0: if (!aContext.accessible) { michael@0: return null; michael@0: } michael@0: michael@0: let brailleOutput = BrailleGenerator.genForContext(aContext); michael@0: brailleOutput.output = brailleOutput.output.join(' '); michael@0: brailleOutput.selectionStart = 0; michael@0: brailleOutput.selectionEnd = 0; michael@0: michael@0: return { type: this.type, details: brailleOutput }; michael@0: }, michael@0: michael@0: textSelectionChanged: function BraillePresenter_textSelectionChanged(aText, aStart, michael@0: aEnd, aOldStart, michael@0: aOldEnd, aIsFromUser) { michael@0: return { type: this.type, michael@0: details: { selectionStart: aStart, michael@0: selectionEnd: aEnd } }; michael@0: }, michael@0: michael@0: michael@0: }; michael@0: michael@0: this.Presentation = { michael@0: get presenters() { michael@0: delete this.presenters; michael@0: let presenterMap = { michael@0: 'mobile/android': [VisualPresenter, AndroidPresenter], michael@0: 'b2g': [VisualPresenter, SpeechPresenter, HapticPresenter], michael@0: 'browser': [VisualPresenter, SpeechPresenter, HapticPresenter, michael@0: AndroidPresenter] michael@0: }; michael@0: this.presenters = [new P() for (P of presenterMap[Utils.MozBuildApp])]; michael@0: return this.presenters; michael@0: }, michael@0: michael@0: pivotChanged: function Presentation_pivotChanged(aPosition, aOldPosition, aReason, michael@0: aStartOffset, aEndOffset) { michael@0: let context = new PivotContext(aPosition, aOldPosition, aStartOffset, aEndOffset); michael@0: return [p.pivotChanged(context, aReason) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: actionInvoked: function Presentation_actionInvoked(aObject, aActionName) { michael@0: return [p.actionInvoked(aObject, aActionName) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: textChanged: function Presentation_textChanged(aIsInserted, aStartOffset, michael@0: aLength, aText, michael@0: aModifiedText) { michael@0: return [p.textChanged(aIsInserted, aStartOffset, aLength, michael@0: aText, aModifiedText) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd, michael@0: aOldStart, aOldEnd, michael@0: aIsFromUser) { michael@0: return [p.textSelectionChanged(aText, aStart, aEnd, michael@0: aOldStart, aOldEnd, aIsFromUser) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: valueChanged: function valueChanged(aAccessible) { michael@0: return [ p.valueChanged(aAccessible) for (p of this.presenters) ]; michael@0: }, michael@0: michael@0: tabStateChanged: function Presentation_tabStateChanged(aDocObj, aPageState) { michael@0: return [p.tabStateChanged(aDocObj, aPageState) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: viewportChanged: function Presentation_viewportChanged(aWindow) { michael@0: return [p.viewportChanged(aWindow) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: editingModeChanged: function Presentation_editingModeChanged(aIsEditing) { michael@0: return [p.editingModeChanged(aIsEditing) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: announce: function Presentation_announce(aAnnouncement) { michael@0: // XXX: Typically each presenter uses the UtteranceGenerator, michael@0: // but there really isn't a point here. michael@0: return [p.announce(UtteranceGenerator.genForAnnouncement(aAnnouncement)[0]) michael@0: for each (p in this.presenters)]; michael@0: }, michael@0: michael@0: liveRegion: function Presentation_liveRegion(aAccessible, aIsPolite, aIsHide, michael@0: aModifiedText) { michael@0: let context; michael@0: if (!aModifiedText) { michael@0: context = new PivotContext(aAccessible, null, -1, -1, true, michael@0: aIsHide ? true : false); michael@0: } michael@0: return [p.liveRegion(context, aIsPolite, aIsHide, aModifiedText) for ( michael@0: p of this.presenters)]; michael@0: } michael@0: };