1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/jsat/Presentation.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,665 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +'use strict'; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 +const Cr = Components.results; 1.14 + 1.15 +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); 1.16 +XPCOMUtils.defineLazyModuleGetter(this, 'Utils', 1.17 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.18 +XPCOMUtils.defineLazyModuleGetter(this, 'Logger', 1.19 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.20 +XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext', 1.21 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.22 +XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator', 1.23 + 'resource://gre/modules/accessibility/OutputGenerator.jsm'); 1.24 +XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator', 1.25 + 'resource://gre/modules/accessibility/OutputGenerator.jsm'); 1.26 +XPCOMUtils.defineLazyModuleGetter(this, 'Roles', 1.27 + 'resource://gre/modules/accessibility/Constants.jsm'); 1.28 +XPCOMUtils.defineLazyModuleGetter(this, 'States', 1.29 + 'resource://gre/modules/accessibility/Constants.jsm'); 1.30 + 1.31 +this.EXPORTED_SYMBOLS = ['Presentation']; 1.32 + 1.33 +/** 1.34 + * The interface for all presenter classes. A presenter could be, for example, 1.35 + * a speech output module, or a visual cursor indicator. 1.36 + */ 1.37 +function Presenter() {} 1.38 + 1.39 +Presenter.prototype = { 1.40 + /** 1.41 + * The type of presenter. Used for matching it with the appropriate output method. 1.42 + */ 1.43 + type: 'Base', 1.44 + 1.45 + /** 1.46 + * The virtual cursor's position changed. 1.47 + * @param {PivotContext} aContext the context object for the new pivot 1.48 + * position. 1.49 + * @param {int} aReason the reason for the pivot change. 1.50 + * See nsIAccessiblePivot. 1.51 + */ 1.52 + pivotChanged: function pivotChanged(aContext, aReason) {}, 1.53 + 1.54 + /** 1.55 + * An object's action has been invoked. 1.56 + * @param {nsIAccessible} aObject the object that has been invoked. 1.57 + * @param {string} aActionName the name of the action. 1.58 + */ 1.59 + actionInvoked: function actionInvoked(aObject, aActionName) {}, 1.60 + 1.61 + /** 1.62 + * Text has changed, either by the user or by the system. TODO. 1.63 + */ 1.64 + textChanged: function textChanged(aIsInserted, aStartOffset, 1.65 + aLength, aText, 1.66 + aModifiedText) {}, 1.67 + 1.68 + /** 1.69 + * Text selection has changed. TODO. 1.70 + */ 1.71 + textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUser) {}, 1.72 + 1.73 + /** 1.74 + * Selection has changed. TODO. 1.75 + * @param {nsIAccessible} aObject the object that has been selected. 1.76 + */ 1.77 + selectionChanged: function selectionChanged(aObject) {}, 1.78 + 1.79 + /** 1.80 + * Value has changed. 1.81 + * @param {nsIAccessible} aAccessible the object whose value has changed. 1.82 + */ 1.83 + valueChanged: function valueChanged(aAccessible) {}, 1.84 + 1.85 + /** 1.86 + * The tab, or the tab's document state has changed. 1.87 + * @param {nsIAccessible} aDocObj the tab document accessible that has had its 1.88 + * state changed, or null if the tab has no associated document yet. 1.89 + * @param {string} aPageState the state name for the tab, valid states are: 1.90 + * 'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'. 1.91 + */ 1.92 + tabStateChanged: function tabStateChanged(aDocObj, aPageState) {}, 1.93 + 1.94 + /** 1.95 + * The current tab has changed. 1.96 + * @param {PivotContext} aDocContext context object for tab's 1.97 + * document. 1.98 + * @param {PivotContext} aVCContext context object for tab's current 1.99 + * virtual cursor position. 1.100 + */ 1.101 + tabSelected: function tabSelected(aDocContext, aVCContext) {}, 1.102 + 1.103 + /** 1.104 + * The viewport has changed, either a scroll, pan, zoom, or 1.105 + * landscape/portrait toggle. 1.106 + * @param {Window} aWindow window of viewport that changed. 1.107 + */ 1.108 + viewportChanged: function viewportChanged(aWindow) {}, 1.109 + 1.110 + /** 1.111 + * We have entered or left text editing mode. 1.112 + */ 1.113 + editingModeChanged: function editingModeChanged(aIsEditing) {}, 1.114 + 1.115 + /** 1.116 + * Announce something. Typically an app state change. 1.117 + */ 1.118 + announce: function announce(aAnnouncement) {}, 1.119 + 1.120 + 1.121 + 1.122 + /** 1.123 + * Announce a live region. 1.124 + * @param {PivotContext} aContext context object for an accessible. 1.125 + * @param {boolean} aIsPolite A politeness level for a live region. 1.126 + * @param {boolean} aIsHide An indicator of hide/remove event. 1.127 + * @param {string} aModifiedText Optional modified text. 1.128 + */ 1.129 + liveRegion: function liveRegionShown(aContext, aIsPolite, aIsHide, 1.130 + aModifiedText) {} 1.131 +}; 1.132 + 1.133 +/** 1.134 + * Visual presenter. Draws a box around the virtual cursor's position. 1.135 + */ 1.136 + 1.137 +this.VisualPresenter = function VisualPresenter() { 1.138 + this._displayedAccessibles = new WeakMap(); 1.139 +}; 1.140 + 1.141 +VisualPresenter.prototype = { 1.142 + __proto__: Presenter.prototype, 1.143 + 1.144 + type: 'Visual', 1.145 + 1.146 + /** 1.147 + * The padding in pixels between the object and the highlight border. 1.148 + */ 1.149 + BORDER_PADDING: 2, 1.150 + 1.151 + viewportChanged: function VisualPresenter_viewportChanged(aWindow) { 1.152 + let currentDisplay = this._displayedAccessibles.get(aWindow); 1.153 + if (!currentDisplay) { 1.154 + return null; 1.155 + } 1.156 + 1.157 + let currentAcc = currentDisplay.accessible; 1.158 + let start = currentDisplay.startOffset; 1.159 + let end = currentDisplay.endOffset; 1.160 + if (Utils.isAliveAndVisible(currentAcc)) { 1.161 + let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) : 1.162 + Utils.getTextBounds(currentAcc, start, end); 1.163 + 1.164 + return { 1.165 + type: this.type, 1.166 + details: { 1.167 + method: 'showBounds', 1.168 + bounds: bounds, 1.169 + padding: this.BORDER_PADDING 1.170 + } 1.171 + }; 1.172 + } 1.173 + 1.174 + return null; 1.175 + }, 1.176 + 1.177 + pivotChanged: function VisualPresenter_pivotChanged(aContext, aReason) { 1.178 + if (!aContext.accessible) { 1.179 + // XXX: Don't hide because another vc may be using the highlight. 1.180 + return null; 1.181 + } 1.182 + 1.183 + this._displayedAccessibles.set(aContext.accessible.document.window, 1.184 + { accessible: aContext.accessibleForBounds, 1.185 + startOffset: aContext.startOffset, 1.186 + endOffset: aContext.endOffset }); 1.187 + 1.188 + try { 1.189 + aContext.accessibleForBounds.scrollTo( 1.190 + Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE); 1.191 + 1.192 + let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ? 1.193 + aContext.bounds : Utils.getTextBounds(aContext.accessibleForBounds, 1.194 + aContext.startOffset, 1.195 + aContext.endOffset); 1.196 + 1.197 + return { 1.198 + type: this.type, 1.199 + details: { 1.200 + method: 'showBounds', 1.201 + bounds: bounds, 1.202 + padding: this.BORDER_PADDING 1.203 + } 1.204 + }; 1.205 + } catch (e) { 1.206 + Logger.logException(e, 'Failed to get bounds'); 1.207 + return null; 1.208 + } 1.209 + }, 1.210 + 1.211 + tabSelected: function VisualPresenter_tabSelected(aDocContext, aVCContext) { 1.212 + return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE); 1.213 + }, 1.214 + 1.215 + tabStateChanged: function VisualPresenter_tabStateChanged(aDocObj, 1.216 + aPageState) { 1.217 + if (aPageState == 'newdoc') 1.218 + return {type: this.type, details: {method: 'hideBounds'}}; 1.219 + 1.220 + return null; 1.221 + }, 1.222 + 1.223 + announce: function VisualPresenter_announce(aAnnouncement) { 1.224 + return { 1.225 + type: this.type, 1.226 + details: { 1.227 + method: 'showAnnouncement', 1.228 + text: aAnnouncement, 1.229 + duration: 1000 1.230 + } 1.231 + }; 1.232 + } 1.233 +}; 1.234 + 1.235 +/** 1.236 + * Android presenter. Fires Android a11y events. 1.237 + */ 1.238 + 1.239 +this.AndroidPresenter = function AndroidPresenter() {}; 1.240 + 1.241 +AndroidPresenter.prototype = { 1.242 + __proto__: Presenter.prototype, 1.243 + 1.244 + type: 'Android', 1.245 + 1.246 + // Android AccessibilityEvent type constants. 1.247 + ANDROID_VIEW_CLICKED: 0x01, 1.248 + ANDROID_VIEW_LONG_CLICKED: 0x02, 1.249 + ANDROID_VIEW_SELECTED: 0x04, 1.250 + ANDROID_VIEW_FOCUSED: 0x08, 1.251 + ANDROID_VIEW_TEXT_CHANGED: 0x10, 1.252 + ANDROID_WINDOW_STATE_CHANGED: 0x20, 1.253 + ANDROID_VIEW_HOVER_ENTER: 0x80, 1.254 + ANDROID_VIEW_HOVER_EXIT: 0x100, 1.255 + ANDROID_VIEW_SCROLLED: 0x1000, 1.256 + ANDROID_VIEW_TEXT_SELECTION_CHANGED: 0x2000, 1.257 + ANDROID_ANNOUNCEMENT: 0x4000, 1.258 + ANDROID_VIEW_ACCESSIBILITY_FOCUSED: 0x8000, 1.259 + ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000, 1.260 + 1.261 + pivotChanged: function AndroidPresenter_pivotChanged(aContext, aReason) { 1.262 + if (!aContext.accessible) 1.263 + return null; 1.264 + 1.265 + let androidEvents = []; 1.266 + 1.267 + let isExploreByTouch = (aReason == Ci.nsIAccessiblePivot.REASON_POINT && 1.268 + Utils.AndroidSdkVersion >= 14); 1.269 + let focusEventType = (Utils.AndroidSdkVersion >= 16) ? 1.270 + this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED : 1.271 + this.ANDROID_VIEW_FOCUSED; 1.272 + 1.273 + if (isExploreByTouch) { 1.274 + // This isn't really used by TalkBack so this is a half-hearted attempt 1.275 + // for now. 1.276 + androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []}); 1.277 + } 1.278 + 1.279 + let brailleOutput = {}; 1.280 + if (Utils.AndroidSdkVersion >= 16) { 1.281 + if (!this._braillePresenter) { 1.282 + this._braillePresenter = new BraillePresenter(); 1.283 + } 1.284 + brailleOutput = this._braillePresenter.pivotChanged(aContext, aReason). 1.285 + details; 1.286 + } 1.287 + 1.288 + if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) { 1.289 + if (Utils.AndroidSdkVersion >= 16) { 1.290 + let adjustedText = aContext.textAndAdjustedOffsets; 1.291 + 1.292 + androidEvents.push({ 1.293 + eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, 1.294 + text: [adjustedText.text], 1.295 + fromIndex: adjustedText.startOffset, 1.296 + toIndex: adjustedText.endOffset 1.297 + }); 1.298 + } 1.299 + } else { 1.300 + let state = Utils.getState(aContext.accessible); 1.301 + androidEvents.push({eventType: (isExploreByTouch) ? 1.302 + this.ANDROID_VIEW_HOVER_ENTER : focusEventType, 1.303 + text: UtteranceGenerator.genForContext(aContext).output, 1.304 + bounds: aContext.bounds, 1.305 + clickable: aContext.accessible.actionCount > 0, 1.306 + checkable: state.contains(States.CHECKABLE), 1.307 + checked: state.contains(States.CHECKED), 1.308 + brailleOutput: brailleOutput}); 1.309 + } 1.310 + 1.311 + 1.312 + return { 1.313 + type: this.type, 1.314 + details: androidEvents 1.315 + }; 1.316 + }, 1.317 + 1.318 + actionInvoked: function AndroidPresenter_actionInvoked(aObject, aActionName) { 1.319 + let state = Utils.getState(aObject); 1.320 + 1.321 + // Checkable objects will have a state changed event we will use instead. 1.322 + if (state.contains(States.CHECKABLE)) 1.323 + return null; 1.324 + 1.325 + return { 1.326 + type: this.type, 1.327 + details: [{ 1.328 + eventType: this.ANDROID_VIEW_CLICKED, 1.329 + text: UtteranceGenerator.genForAction(aObject, aActionName), 1.330 + checked: state.contains(States.CHECKED) 1.331 + }] 1.332 + }; 1.333 + }, 1.334 + 1.335 + tabSelected: function AndroidPresenter_tabSelected(aDocContext, aVCContext) { 1.336 + // Send a pivot change message with the full context utterance for this doc. 1.337 + return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE); 1.338 + }, 1.339 + 1.340 + tabStateChanged: function AndroidPresenter_tabStateChanged(aDocObj, 1.341 + aPageState) { 1.342 + return this.announce( 1.343 + UtteranceGenerator.genForTabStateChange(aDocObj, aPageState).join(' ')); 1.344 + }, 1.345 + 1.346 + textChanged: function AndroidPresenter_textChanged(aIsInserted, aStart, 1.347 + aLength, aText, 1.348 + aModifiedText) { 1.349 + let eventDetails = { 1.350 + eventType: this.ANDROID_VIEW_TEXT_CHANGED, 1.351 + text: [aText], 1.352 + fromIndex: aStart, 1.353 + removedCount: 0, 1.354 + addedCount: 0 1.355 + }; 1.356 + 1.357 + if (aIsInserted) { 1.358 + eventDetails.addedCount = aLength; 1.359 + eventDetails.beforeText = 1.360 + aText.substring(0, aStart) + aText.substring(aStart + aLength); 1.361 + } else { 1.362 + eventDetails.removedCount = aLength; 1.363 + eventDetails.beforeText = 1.364 + aText.substring(0, aStart) + aModifiedText + aText.substring(aStart); 1.365 + } 1.366 + 1.367 + return {type: this.type, details: [eventDetails]}; 1.368 + }, 1.369 + 1.370 + textSelectionChanged: function AndroidPresenter_textSelectionChanged(aText, aStart, 1.371 + aEnd, aOldStart, 1.372 + aOldEnd, aIsFromUser) { 1.373 + let androidEvents = []; 1.374 + 1.375 + if (Utils.AndroidSdkVersion >= 14 && !aIsFromUser) { 1.376 + if (!this._braillePresenter) { 1.377 + this._braillePresenter = new BraillePresenter(); 1.378 + } 1.379 + let brailleOutput = this._braillePresenter.textSelectionChanged(aText, aStart, aEnd, 1.380 + aOldStart, aOldEnd, 1.381 + aIsFromUser).details; 1.382 + 1.383 + androidEvents.push({ 1.384 + eventType: this.ANDROID_VIEW_TEXT_SELECTION_CHANGED, 1.385 + text: [aText], 1.386 + fromIndex: aStart, 1.387 + toIndex: aEnd, 1.388 + itemCount: aText.length, 1.389 + brailleOutput: brailleOutput 1.390 + }); 1.391 + } 1.392 + 1.393 + if (Utils.AndroidSdkVersion >= 16 && aIsFromUser) { 1.394 + let [from, to] = aOldStart < aStart ? [aOldStart, aStart] : [aStart, aOldStart]; 1.395 + androidEvents.push({ 1.396 + eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, 1.397 + text: [aText], 1.398 + fromIndex: from, 1.399 + toIndex: to 1.400 + }); 1.401 + } 1.402 + 1.403 + return { 1.404 + type: this.type, 1.405 + details: androidEvents 1.406 + }; 1.407 + }, 1.408 + 1.409 + viewportChanged: function AndroidPresenter_viewportChanged(aWindow) { 1.410 + if (Utils.AndroidSdkVersion < 14) 1.411 + return null; 1.412 + 1.413 + return { 1.414 + type: this.type, 1.415 + details: [{ 1.416 + eventType: this.ANDROID_VIEW_SCROLLED, 1.417 + text: [], 1.418 + scrollX: aWindow.scrollX, 1.419 + scrollY: aWindow.scrollY, 1.420 + maxScrollX: aWindow.scrollMaxX, 1.421 + maxScrollY: aWindow.scrollMaxY 1.422 + }] 1.423 + }; 1.424 + }, 1.425 + 1.426 + editingModeChanged: function AndroidPresenter_editingModeChanged(aIsEditing) { 1.427 + return this.announce( 1.428 + UtteranceGenerator.genForEditingMode(aIsEditing).join(' ')); 1.429 + }, 1.430 + 1.431 + announce: function AndroidPresenter_announce(aAnnouncement) { 1.432 + return { 1.433 + type: this.type, 1.434 + details: [{ 1.435 + eventType: (Utils.AndroidSdkVersion >= 16) ? 1.436 + this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED, 1.437 + text: [aAnnouncement], 1.438 + addedCount: aAnnouncement.length, 1.439 + removedCount: 0, 1.440 + fromIndex: 0 1.441 + }] 1.442 + }; 1.443 + }, 1.444 + 1.445 + liveRegion: function AndroidPresenter_liveRegion(aContext, aIsPolite, 1.446 + aIsHide, aModifiedText) { 1.447 + return this.announce( 1.448 + UtteranceGenerator.genForLiveRegion(aContext, aIsHide, 1.449 + aModifiedText).join(' ')); 1.450 + } 1.451 +}; 1.452 + 1.453 +/** 1.454 + * A speech presenter for direct TTS output 1.455 + */ 1.456 + 1.457 +this.SpeechPresenter = function SpeechPresenter() {}; 1.458 + 1.459 +SpeechPresenter.prototype = { 1.460 + __proto__: Presenter.prototype, 1.461 + 1.462 + type: 'Speech', 1.463 + 1.464 + pivotChanged: function SpeechPresenter_pivotChanged(aContext, aReason) { 1.465 + if (!aContext.accessible) 1.466 + return null; 1.467 + 1.468 + return { 1.469 + type: this.type, 1.470 + details: { 1.471 + actions: [ 1.472 + {method: 'playEarcon', 1.473 + data: aContext.accessible.role === Roles.KEY ? 1.474 + 'virtual_cursor_key' : 'virtual_cursor_move', 1.475 + options: {}}, 1.476 + {method: 'speak', 1.477 + data: UtteranceGenerator.genForContext(aContext).output.join(' '), 1.478 + options: {enqueue: true}} 1.479 + ] 1.480 + } 1.481 + }; 1.482 + }, 1.483 + 1.484 + valueChanged: function SpeechPresenter_valueChanged(aAccessible) { 1.485 + return { 1.486 + type: this.type, 1.487 + details: { 1.488 + actions: [ 1.489 + { method: 'speak', 1.490 + data: aAccessible.value, 1.491 + options: { enqueue: false } } 1.492 + ] 1.493 + } 1.494 + } 1.495 + }, 1.496 + 1.497 + actionInvoked: function SpeechPresenter_actionInvoked(aObject, aActionName) { 1.498 + let actions = []; 1.499 + if (aActionName === 'click') { 1.500 + actions.push({method: 'playEarcon', 1.501 + data: 'clicked', 1.502 + options: {}}); 1.503 + } else { 1.504 + actions.push({method: 'speak', 1.505 + data: UtteranceGenerator.genForAction(aObject, aActionName).join(' '), 1.506 + options: {enqueue: false}}); 1.507 + } 1.508 + return { type: this.type, details: { actions: actions } }; 1.509 + }, 1.510 + 1.511 + liveRegion: function SpeechPresenter_liveRegion(aContext, aIsPolite, aIsHide, 1.512 + aModifiedText) { 1.513 + return { 1.514 + type: this.type, 1.515 + details: { 1.516 + actions: [{ 1.517 + method: 'speak', 1.518 + data: UtteranceGenerator.genForLiveRegion(aContext, aIsHide, 1.519 + aModifiedText).join(' '), 1.520 + options: {enqueue: aIsPolite} 1.521 + }] 1.522 + } 1.523 + }; 1.524 + }, 1.525 + 1.526 + announce: function SpeechPresenter_announce(aAnnouncement) { 1.527 + return { 1.528 + type: this.type, 1.529 + details: { 1.530 + actions: [{ 1.531 + method: 'speak', data: aAnnouncement, options: { enqueue: false } 1.532 + }] 1.533 + } 1.534 + }; 1.535 + } 1.536 +}; 1.537 + 1.538 +/** 1.539 + * A haptic presenter 1.540 + */ 1.541 + 1.542 +this.HapticPresenter = function HapticPresenter() {}; 1.543 + 1.544 +HapticPresenter.prototype = { 1.545 + __proto__: Presenter.prototype, 1.546 + 1.547 + type: 'Haptic', 1.548 + 1.549 + PIVOT_CHANGE_PATTERN: [40], 1.550 + 1.551 + pivotChanged: function HapticPresenter_pivotChanged(aContext, aReason) { 1.552 + return { type: this.type, details: { pattern: this.PIVOT_CHANGE_PATTERN } }; 1.553 + } 1.554 +}; 1.555 + 1.556 +/** 1.557 + * A braille presenter 1.558 + */ 1.559 + 1.560 +this.BraillePresenter = function BraillePresenter() {}; 1.561 + 1.562 +BraillePresenter.prototype = { 1.563 + __proto__: Presenter.prototype, 1.564 + 1.565 + type: 'Braille', 1.566 + 1.567 + pivotChanged: function BraillePresenter_pivotChanged(aContext, aReason) { 1.568 + if (!aContext.accessible) { 1.569 + return null; 1.570 + } 1.571 + 1.572 + let brailleOutput = BrailleGenerator.genForContext(aContext); 1.573 + brailleOutput.output = brailleOutput.output.join(' '); 1.574 + brailleOutput.selectionStart = 0; 1.575 + brailleOutput.selectionEnd = 0; 1.576 + 1.577 + return { type: this.type, details: brailleOutput }; 1.578 + }, 1.579 + 1.580 + textSelectionChanged: function BraillePresenter_textSelectionChanged(aText, aStart, 1.581 + aEnd, aOldStart, 1.582 + aOldEnd, aIsFromUser) { 1.583 + return { type: this.type, 1.584 + details: { selectionStart: aStart, 1.585 + selectionEnd: aEnd } }; 1.586 + }, 1.587 + 1.588 + 1.589 +}; 1.590 + 1.591 +this.Presentation = { 1.592 + get presenters() { 1.593 + delete this.presenters; 1.594 + let presenterMap = { 1.595 + 'mobile/android': [VisualPresenter, AndroidPresenter], 1.596 + 'b2g': [VisualPresenter, SpeechPresenter, HapticPresenter], 1.597 + 'browser': [VisualPresenter, SpeechPresenter, HapticPresenter, 1.598 + AndroidPresenter] 1.599 + }; 1.600 + this.presenters = [new P() for (P of presenterMap[Utils.MozBuildApp])]; 1.601 + return this.presenters; 1.602 + }, 1.603 + 1.604 + pivotChanged: function Presentation_pivotChanged(aPosition, aOldPosition, aReason, 1.605 + aStartOffset, aEndOffset) { 1.606 + let context = new PivotContext(aPosition, aOldPosition, aStartOffset, aEndOffset); 1.607 + return [p.pivotChanged(context, aReason) 1.608 + for each (p in this.presenters)]; 1.609 + }, 1.610 + 1.611 + actionInvoked: function Presentation_actionInvoked(aObject, aActionName) { 1.612 + return [p.actionInvoked(aObject, aActionName) 1.613 + for each (p in this.presenters)]; 1.614 + }, 1.615 + 1.616 + textChanged: function Presentation_textChanged(aIsInserted, aStartOffset, 1.617 + aLength, aText, 1.618 + aModifiedText) { 1.619 + return [p.textChanged(aIsInserted, aStartOffset, aLength, 1.620 + aText, aModifiedText) 1.621 + for each (p in this.presenters)]; 1.622 + }, 1.623 + 1.624 + textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd, 1.625 + aOldStart, aOldEnd, 1.626 + aIsFromUser) { 1.627 + return [p.textSelectionChanged(aText, aStart, aEnd, 1.628 + aOldStart, aOldEnd, aIsFromUser) 1.629 + for each (p in this.presenters)]; 1.630 + }, 1.631 + 1.632 + valueChanged: function valueChanged(aAccessible) { 1.633 + return [ p.valueChanged(aAccessible) for (p of this.presenters) ]; 1.634 + }, 1.635 + 1.636 + tabStateChanged: function Presentation_tabStateChanged(aDocObj, aPageState) { 1.637 + return [p.tabStateChanged(aDocObj, aPageState) 1.638 + for each (p in this.presenters)]; 1.639 + }, 1.640 + 1.641 + viewportChanged: function Presentation_viewportChanged(aWindow) { 1.642 + return [p.viewportChanged(aWindow) 1.643 + for each (p in this.presenters)]; 1.644 + }, 1.645 + 1.646 + editingModeChanged: function Presentation_editingModeChanged(aIsEditing) { 1.647 + return [p.editingModeChanged(aIsEditing) 1.648 + for each (p in this.presenters)]; 1.649 + }, 1.650 + 1.651 + announce: function Presentation_announce(aAnnouncement) { 1.652 + // XXX: Typically each presenter uses the UtteranceGenerator, 1.653 + // but there really isn't a point here. 1.654 + return [p.announce(UtteranceGenerator.genForAnnouncement(aAnnouncement)[0]) 1.655 + for each (p in this.presenters)]; 1.656 + }, 1.657 + 1.658 + liveRegion: function Presentation_liveRegion(aAccessible, aIsPolite, aIsHide, 1.659 + aModifiedText) { 1.660 + let context; 1.661 + if (!aModifiedText) { 1.662 + context = new PivotContext(aAccessible, null, -1, -1, true, 1.663 + aIsHide ? true : false); 1.664 + } 1.665 + return [p.liveRegion(context, aIsPolite, aIsHide, aModifiedText) for ( 1.666 + p of this.presenters)]; 1.667 + } 1.668 +};