1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/jsat/EventManager.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,612 @@ 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 Ci = Components.interfaces; 1.11 +const Cu = Components.utils; 1.12 + 1.13 +const TEXT_NODE = 3; 1.14 + 1.15 +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); 1.16 +XPCOMUtils.defineLazyModuleGetter(this, 'Services', 1.17 + 'resource://gre/modules/Services.jsm'); 1.18 +XPCOMUtils.defineLazyModuleGetter(this, 'Utils', 1.19 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.20 +XPCOMUtils.defineLazyModuleGetter(this, 'Logger', 1.21 + 'resource://gre/modules/accessibility/Utils.jsm'); 1.22 +XPCOMUtils.defineLazyModuleGetter(this, 'Presentation', 1.23 + 'resource://gre/modules/accessibility/Presentation.jsm'); 1.24 +XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules', 1.25 + 'resource://gre/modules/accessibility/TraversalRules.jsm'); 1.26 +XPCOMUtils.defineLazyModuleGetter(this, 'Roles', 1.27 + 'resource://gre/modules/accessibility/Constants.jsm'); 1.28 +XPCOMUtils.defineLazyModuleGetter(this, 'Events', 1.29 + 'resource://gre/modules/accessibility/Constants.jsm'); 1.30 +XPCOMUtils.defineLazyModuleGetter(this, 'States', 1.31 + 'resource://gre/modules/accessibility/Constants.jsm'); 1.32 + 1.33 +this.EXPORTED_SYMBOLS = ['EventManager']; 1.34 + 1.35 +this.EventManager = function EventManager(aContentScope) { 1.36 + this.contentScope = aContentScope; 1.37 + this.addEventListener = this.contentScope.addEventListener.bind( 1.38 + this.contentScope); 1.39 + this.removeEventListener = this.contentScope.removeEventListener.bind( 1.40 + this.contentScope); 1.41 + this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind( 1.42 + this.contentScope); 1.43 + this.webProgress = this.contentScope.docShell. 1.44 + QueryInterface(Ci.nsIInterfaceRequestor). 1.45 + getInterface(Ci.nsIWebProgress); 1.46 +}; 1.47 + 1.48 +this.EventManager.prototype = { 1.49 + editState: {}, 1.50 + 1.51 + start: function start() { 1.52 + try { 1.53 + if (!this._started) { 1.54 + Logger.debug('EventManager.start'); 1.55 + 1.56 + this._started = true; 1.57 + 1.58 + AccessibilityEventObserver.addListener(this); 1.59 + 1.60 + this.webProgress.addProgressListener(this, 1.61 + (Ci.nsIWebProgress.NOTIFY_STATE_ALL | 1.62 + Ci.nsIWebProgress.NOTIFY_LOCATION)); 1.63 + this.addEventListener('wheel', this, true); 1.64 + this.addEventListener('scroll', this, true); 1.65 + this.addEventListener('resize', this, true); 1.66 + } 1.67 + this.present(Presentation.tabStateChanged(null, 'newtab')); 1.68 + 1.69 + } catch (x) { 1.70 + Logger.logException(x, 'Failed to start EventManager'); 1.71 + } 1.72 + }, 1.73 + 1.74 + // XXX: Stop is not called when the tab is closed (|TabClose| event is too 1.75 + // late). It is only called when the AccessFu is disabled explicitly. 1.76 + stop: function stop() { 1.77 + if (!this._started) { 1.78 + return; 1.79 + } 1.80 + Logger.debug('EventManager.stop'); 1.81 + AccessibilityEventObserver.removeListener(this); 1.82 + try { 1.83 + this.webProgress.removeProgressListener(this); 1.84 + this.removeEventListener('wheel', this, true); 1.85 + this.removeEventListener('scroll', this, true); 1.86 + this.removeEventListener('resize', this, true); 1.87 + } catch (x) { 1.88 + // contentScope is dead. 1.89 + } finally { 1.90 + this._started = false; 1.91 + } 1.92 + }, 1.93 + 1.94 + handleEvent: function handleEvent(aEvent) { 1.95 + Logger.debug(() => { 1.96 + return ['DOMEvent', aEvent.type]; 1.97 + }); 1.98 + 1.99 + try { 1.100 + switch (aEvent.type) { 1.101 + case 'wheel': 1.102 + { 1.103 + let attempts = 0; 1.104 + let delta = aEvent.deltaX || aEvent.deltaY; 1.105 + this.contentScope.contentControl.autoMove( 1.106 + null, 1.107 + { moveMethod: delta > 0 ? 'moveNext' : 'movePrevious', 1.108 + onScreenOnly: true, noOpIfOnScreen: true, delay: 500 }); 1.109 + break; 1.110 + } 1.111 + case 'scroll': 1.112 + case 'resize': 1.113 + { 1.114 + // the target could be an element, document or window 1.115 + let window = null; 1.116 + if (aEvent.target instanceof Ci.nsIDOMWindow) 1.117 + window = aEvent.target; 1.118 + else if (aEvent.target instanceof Ci.nsIDOMDocument) 1.119 + window = aEvent.target.defaultView; 1.120 + else if (aEvent.target instanceof Ci.nsIDOMElement) 1.121 + window = aEvent.target.ownerDocument.defaultView; 1.122 + this.present(Presentation.viewportChanged(window)); 1.123 + break; 1.124 + } 1.125 + } 1.126 + } catch (x) { 1.127 + Logger.logException(x, 'Error handling DOM event'); 1.128 + } 1.129 + }, 1.130 + 1.131 + handleAccEvent: function handleAccEvent(aEvent) { 1.132 + Logger.debug(() => { 1.133 + return ['A11yEvent', Logger.eventToString(aEvent), 1.134 + Logger.accessibleToString(aEvent.accessible)]; 1.135 + }); 1.136 + 1.137 + // Don't bother with non-content events in firefox. 1.138 + if (Utils.MozBuildApp == 'browser' && 1.139 + aEvent.eventType != Events.VIRTUALCURSOR_CHANGED && 1.140 + // XXX Bug 442005 results in DocAccessible::getDocType returning 1.141 + // NS_ERROR_FAILURE. Checking for aEvent.accessibleDocument.docType == 1.142 + // 'window' does not currently work. 1.143 + (aEvent.accessibleDocument.DOMDocument.doctype && 1.144 + aEvent.accessibleDocument.DOMDocument.doctype.name === 'window')) { 1.145 + return; 1.146 + } 1.147 + 1.148 + switch (aEvent.eventType) { 1.149 + case Events.VIRTUALCURSOR_CHANGED: 1.150 + { 1.151 + let pivot = aEvent.accessible. 1.152 + QueryInterface(Ci.nsIAccessibleDocument).virtualCursor; 1.153 + let position = pivot.position; 1.154 + if (position && position.role == Roles.INTERNAL_FRAME) 1.155 + break; 1.156 + let event = aEvent. 1.157 + QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent); 1.158 + let reason = event.reason; 1.159 + let oldAccessible = event.oldAccessible; 1.160 + 1.161 + if (this.editState.editing) { 1.162 + aEvent.accessibleDocument.takeFocus(); 1.163 + } 1.164 + this.present( 1.165 + Presentation.pivotChanged(position, oldAccessible, reason, 1.166 + pivot.startOffset, pivot.endOffset)); 1.167 + 1.168 + break; 1.169 + } 1.170 + case Events.STATE_CHANGE: 1.171 + { 1.172 + let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent); 1.173 + let state = Utils.getState(event); 1.174 + if (state.contains(States.CHECKED)) { 1.175 + this.present( 1.176 + Presentation. 1.177 + actionInvoked(aEvent.accessible, 1.178 + event.isEnabled ? 'check' : 'uncheck')); 1.179 + } else if (state.contains(States.SELECTED)) { 1.180 + this.present( 1.181 + Presentation. 1.182 + actionInvoked(aEvent.accessible, 1.183 + event.isEnabled ? 'select' : 'unselect')); 1.184 + } 1.185 + break; 1.186 + } 1.187 + case Events.SCROLLING_START: 1.188 + { 1.189 + this.contentScope.contentControl.autoMove(aEvent.accessible); 1.190 + break; 1.191 + } 1.192 + case Events.TEXT_CARET_MOVED: 1.193 + { 1.194 + let acc = aEvent.accessible; 1.195 + let characterCount = acc. 1.196 + QueryInterface(Ci.nsIAccessibleText).characterCount; 1.197 + let caretOffset = aEvent. 1.198 + QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset; 1.199 + 1.200 + // Update editing state, both for presenter and other things 1.201 + let state = Utils.getState(acc); 1.202 + let editState = { 1.203 + editing: state.contains(States.EDITABLE), 1.204 + multiline: state.contains(States.MULTI_LINE), 1.205 + atStart: caretOffset == 0, 1.206 + atEnd: caretOffset == characterCount 1.207 + }; 1.208 + 1.209 + // Not interesting 1.210 + if (!editState.editing && editState.editing == this.editState.editing) 1.211 + break; 1.212 + 1.213 + if (editState.editing != this.editState.editing) 1.214 + this.present(Presentation.editingModeChanged(editState.editing)); 1.215 + 1.216 + if (editState.editing != this.editState.editing || 1.217 + editState.multiline != this.editState.multiline || 1.218 + editState.atEnd != this.editState.atEnd || 1.219 + editState.atStart != this.editState.atStart) 1.220 + this.sendMsgFunc("AccessFu:Input", editState); 1.221 + 1.222 + this.present(Presentation.textSelectionChanged(acc.getText(0,-1), 1.223 + caretOffset, caretOffset, 0, 0, aEvent.isFromUserInput)); 1.224 + 1.225 + this.editState = editState; 1.226 + break; 1.227 + } 1.228 + case Events.SHOW: 1.229 + { 1.230 + let {liveRegion, isPolite} = this._handleLiveRegion(aEvent, 1.231 + ['additions', 'all']); 1.232 + // Only handle show if it is a relevant live region. 1.233 + if (!liveRegion) { 1.234 + break; 1.235 + } 1.236 + // Show for text is handled by the EVENT_TEXT_INSERTED handler. 1.237 + if (aEvent.accessible.role === Roles.TEXT_LEAF) { 1.238 + break; 1.239 + } 1.240 + this._dequeueLiveEvent(Events.HIDE, liveRegion); 1.241 + this.present(Presentation.liveRegion(liveRegion, isPolite, false)); 1.242 + break; 1.243 + } 1.244 + case Events.HIDE: 1.245 + { 1.246 + let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent); 1.247 + let {liveRegion, isPolite} = this._handleLiveRegion( 1.248 + evt, ['removals', 'all']); 1.249 + if (liveRegion) { 1.250 + // Hide for text is handled by the EVENT_TEXT_REMOVED handler. 1.251 + if (aEvent.accessible.role === Roles.TEXT_LEAF) { 1.252 + break; 1.253 + } 1.254 + this._queueLiveEvent(Events.HIDE, liveRegion, isPolite); 1.255 + } else { 1.256 + let vc = Utils.getVirtualCursor(this.contentScope.content.document); 1.257 + if (vc.position && 1.258 + (Utils.getState(vc.position).contains(States.DEFUNCT) || 1.259 + Utils.isInSubtree(vc.position, aEvent.accessible))) { 1.260 + this.contentScope.contentControl.autoMove( 1.261 + evt.targetPrevSibling || evt.targetParent, 1.262 + { moveToFocused: true, delay: 500 }); 1.263 + } 1.264 + } 1.265 + break; 1.266 + } 1.267 + case Events.TEXT_INSERTED: 1.268 + case Events.TEXT_REMOVED: 1.269 + { 1.270 + let {liveRegion, isPolite} = this._handleLiveRegion(aEvent, 1.271 + ['text', 'all']); 1.272 + if (aEvent.isFromUserInput || liveRegion) { 1.273 + // Handle all text mutations coming from the user or if they happen 1.274 + // on a live region. 1.275 + this._handleText(aEvent, liveRegion, isPolite); 1.276 + } 1.277 + break; 1.278 + } 1.279 + case Events.FOCUS: 1.280 + { 1.281 + // Put vc where the focus is at 1.282 + let acc = aEvent.accessible; 1.283 + let doc = aEvent.accessibleDocument; 1.284 + if (acc.role != Roles.DOCUMENT && doc.role != Roles.CHROME_WINDOW) { 1.285 + this.contentScope.contentControl.autoMove(acc); 1.286 + } 1.287 + break; 1.288 + } 1.289 + case Events.DOCUMENT_LOAD_COMPLETE: 1.290 + { 1.291 + if (aEvent.accessible === aEvent.accessibleDocument) { 1.292 + break; 1.293 + } 1.294 + this.contentScope.contentControl.autoMove( 1.295 + aEvent.accessible, { delay: 500 }); 1.296 + break; 1.297 + } 1.298 + case Events.VALUE_CHANGE: 1.299 + { 1.300 + let position = this.contentScope.contentControl.vc.position; 1.301 + let target = aEvent.accessible; 1.302 + if (position === target || 1.303 + Utils.getEmbeddedControl(position) === target) { 1.304 + this.present(Presentation.valueChanged(target)); 1.305 + } 1.306 + } 1.307 + } 1.308 + }, 1.309 + 1.310 + _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) { 1.311 + let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent); 1.312 + let isInserted = event.isInserted; 1.313 + let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText); 1.314 + 1.315 + let text = ''; 1.316 + try { 1.317 + text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT); 1.318 + } catch (x) { 1.319 + // XXX we might have gotten an exception with of a 1.320 + // zero-length text. If we did, ignore it (bug #749810). 1.321 + if (txtIface.characterCount) { 1.322 + throw x; 1.323 + } 1.324 + } 1.325 + // If there are embedded objects in the text, ignore them. 1.326 + // Assuming changes to the descendants would already be handled by the 1.327 + // show/hide event. 1.328 + let modifiedText = event.modifiedText.replace(/\uFFFC/g, '').trim(); 1.329 + if (!modifiedText) { 1.330 + return; 1.331 + } 1.332 + if (aLiveRegion) { 1.333 + if (aEvent.eventType === Events.TEXT_REMOVED) { 1.334 + this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite, 1.335 + modifiedText); 1.336 + } else { 1.337 + this._dequeueLiveEvent(Events.TEXT_REMOVED, aLiveRegion); 1.338 + this.present(Presentation.liveRegion(aLiveRegion, aIsPolite, false, 1.339 + modifiedText)); 1.340 + } 1.341 + } else { 1.342 + this.present(Presentation.textChanged(isInserted, event.start, 1.343 + event.length, text, modifiedText)); 1.344 + } 1.345 + }, 1.346 + 1.347 + _handleLiveRegion: function _handleLiveRegion(aEvent, aRelevant) { 1.348 + if (aEvent.isFromUserInput) { 1.349 + return {}; 1.350 + } 1.351 + let parseLiveAttrs = function parseLiveAttrs(aAccessible) { 1.352 + let attrs = Utils.getAttributes(aAccessible); 1.353 + if (attrs['container-live']) { 1.354 + return { 1.355 + live: attrs['container-live'], 1.356 + relevant: attrs['container-relevant'] || 'additions text', 1.357 + busy: attrs['container-busy'], 1.358 + atomic: attrs['container-atomic'], 1.359 + memberOf: attrs['member-of'] 1.360 + }; 1.361 + } 1.362 + return null; 1.363 + }; 1.364 + // XXX live attributes are not set for hidden accessibles yet. Need to 1.365 + // climb up the tree to check for them. 1.366 + let getLiveAttributes = function getLiveAttributes(aEvent) { 1.367 + let liveAttrs = parseLiveAttrs(aEvent.accessible); 1.368 + if (liveAttrs) { 1.369 + return liveAttrs; 1.370 + } 1.371 + let parent = aEvent.targetParent; 1.372 + while (parent) { 1.373 + liveAttrs = parseLiveAttrs(parent); 1.374 + if (liveAttrs) { 1.375 + return liveAttrs; 1.376 + } 1.377 + parent = parent.parent 1.378 + } 1.379 + return {}; 1.380 + }; 1.381 + let {live, relevant, busy, atomic, memberOf} = getLiveAttributes(aEvent); 1.382 + // If container-live is not present or is set to |off| ignore the event. 1.383 + if (!live || live === 'off') { 1.384 + return {}; 1.385 + } 1.386 + // XXX: support busy and atomic. 1.387 + 1.388 + // Determine if the type of the mutation is relevant. Default is additions 1.389 + // and text. 1.390 + let isRelevant = Utils.matchAttributeValue(relevant, aRelevant); 1.391 + if (!isRelevant) { 1.392 + return {}; 1.393 + } 1.394 + return { 1.395 + liveRegion: aEvent.accessible, 1.396 + isPolite: live === 'polite' 1.397 + }; 1.398 + }, 1.399 + 1.400 + _dequeueLiveEvent: function _dequeueLiveEvent(aEventType, aLiveRegion) { 1.401 + let domNode = aLiveRegion.DOMNode; 1.402 + if (this._liveEventQueue && this._liveEventQueue.has(domNode)) { 1.403 + let queue = this._liveEventQueue.get(domNode); 1.404 + let nextEvent = queue[0]; 1.405 + if (nextEvent.eventType === aEventType) { 1.406 + Utils.win.clearTimeout(nextEvent.timeoutID); 1.407 + queue.shift(); 1.408 + if (queue.length === 0) { 1.409 + this._liveEventQueue.delete(domNode) 1.410 + } 1.411 + } 1.412 + } 1.413 + }, 1.414 + 1.415 + _queueLiveEvent: function _queueLiveEvent(aEventType, aLiveRegion, aIsPolite, aModifiedText) { 1.416 + if (!this._liveEventQueue) { 1.417 + this._liveEventQueue = new WeakMap(); 1.418 + } 1.419 + let eventHandler = { 1.420 + eventType: aEventType, 1.421 + timeoutID: Utils.win.setTimeout(this.present.bind(this), 1.422 + 20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event. 1.423 + Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText)) 1.424 + }; 1.425 + 1.426 + let domNode = aLiveRegion.DOMNode; 1.427 + if (this._liveEventQueue.has(domNode)) { 1.428 + this._liveEventQueue.get(domNode).push(eventHandler); 1.429 + } else { 1.430 + this._liveEventQueue.set(domNode, [eventHandler]); 1.431 + } 1.432 + }, 1.433 + 1.434 + present: function present(aPresentationData) { 1.435 + this.sendMsgFunc("AccessFu:Present", aPresentationData); 1.436 + }, 1.437 + 1.438 + onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { 1.439 + let tabstate = ''; 1.440 + 1.441 + let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING | 1.442 + Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; 1.443 + let loadedState = Ci.nsIWebProgressListener.STATE_STOP | 1.444 + Ci.nsIWebProgressListener.STATE_IS_NETWORK; 1.445 + 1.446 + if ((aStateFlags & loadingState) == loadingState) { 1.447 + tabstate = 'loading'; 1.448 + } else if ((aStateFlags & loadedState) == loadedState && 1.449 + !aWebProgress.isLoadingDocument) { 1.450 + tabstate = 'loaded'; 1.451 + } 1.452 + 1.453 + if (tabstate) { 1.454 + let docAcc = Utils.AccRetrieval.getAccessibleFor(aWebProgress.DOMWindow.document); 1.455 + this.present(Presentation.tabStateChanged(docAcc, tabstate)); 1.456 + } 1.457 + }, 1.458 + 1.459 + onProgressChange: function onProgressChange() {}, 1.460 + 1.461 + onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { 1.462 + let docAcc = Utils.AccRetrieval.getAccessibleFor(aWebProgress.DOMWindow.document); 1.463 + this.present(Presentation.tabStateChanged(docAcc, 'newdoc')); 1.464 + }, 1.465 + 1.466 + onStatusChange: function onStatusChange() {}, 1.467 + 1.468 + onSecurityChange: function onSecurityChange() {}, 1.469 + 1.470 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, 1.471 + Ci.nsISupportsWeakReference, 1.472 + Ci.nsISupports, 1.473 + Ci.nsIObserver]) 1.474 +}; 1.475 + 1.476 +const AccessibilityEventObserver = { 1.477 + 1.478 + /** 1.479 + * A WeakMap containing [content, EventManager] pairs. 1.480 + */ 1.481 + eventManagers: new WeakMap(), 1.482 + 1.483 + /** 1.484 + * A total number of registered eventManagers. 1.485 + */ 1.486 + listenerCount: 0, 1.487 + 1.488 + /** 1.489 + * An indicator of an active 'accessible-event' observer. 1.490 + */ 1.491 + started: false, 1.492 + 1.493 + /** 1.494 + * Start an AccessibilityEventObserver. 1.495 + */ 1.496 + start: function start() { 1.497 + if (this.started || this.listenerCount === 0) { 1.498 + return; 1.499 + } 1.500 + Services.obs.addObserver(this, 'accessible-event', false); 1.501 + this.started = true; 1.502 + }, 1.503 + 1.504 + /** 1.505 + * Stop an AccessibilityEventObserver. 1.506 + */ 1.507 + stop: function stop() { 1.508 + if (!this.started) { 1.509 + return; 1.510 + } 1.511 + Services.obs.removeObserver(this, 'accessible-event'); 1.512 + // Clean up all registered event managers. 1.513 + this.eventManagers.clear(); 1.514 + this.listenerCount = 0; 1.515 + this.started = false; 1.516 + }, 1.517 + 1.518 + /** 1.519 + * Register an EventManager and start listening to the 1.520 + * 'accessible-event' messages. 1.521 + * 1.522 + * @param aEventManager EventManager 1.523 + * An EventManager object that was loaded into the specific content. 1.524 + */ 1.525 + addListener: function addListener(aEventManager) { 1.526 + let content = aEventManager.contentScope.content; 1.527 + if (!this.eventManagers.has(content)) { 1.528 + this.listenerCount++; 1.529 + } 1.530 + this.eventManagers.set(content, aEventManager); 1.531 + // Since at least one EventManager was registered, start listening. 1.532 + Logger.debug('AccessibilityEventObserver.addListener. Total:', 1.533 + this.listenerCount); 1.534 + this.start(); 1.535 + }, 1.536 + 1.537 + /** 1.538 + * Unregister an EventManager and, optionally, stop listening to the 1.539 + * 'accessible-event' messages. 1.540 + * 1.541 + * @param aEventManager EventManager 1.542 + * An EventManager object that was stopped in the specific content. 1.543 + */ 1.544 + removeListener: function removeListener(aEventManager) { 1.545 + let content = aEventManager.contentScope.content; 1.546 + if (!this.eventManagers.delete(content)) { 1.547 + return; 1.548 + } 1.549 + this.listenerCount--; 1.550 + Logger.debug('AccessibilityEventObserver.removeListener. Total:', 1.551 + this.listenerCount); 1.552 + if (this.listenerCount === 0) { 1.553 + // If there are no EventManagers registered at the moment, stop listening 1.554 + // to the 'accessible-event' messages. 1.555 + this.stop(); 1.556 + } 1.557 + }, 1.558 + 1.559 + /** 1.560 + * Lookup an EventManager for a specific content. If the EventManager is not 1.561 + * found, walk up the hierarchy of parent windows. 1.562 + * @param content Window 1.563 + * A content Window used to lookup the corresponding EventManager. 1.564 + */ 1.565 + getListener: function getListener(content) { 1.566 + let eventManager = this.eventManagers.get(content); 1.567 + if (eventManager) { 1.568 + return eventManager; 1.569 + } 1.570 + let parent = content.parent; 1.571 + if (parent === content) { 1.572 + // There is no parent or the parent is of a different type. 1.573 + return null; 1.574 + } 1.575 + return this.getListener(parent); 1.576 + }, 1.577 + 1.578 + /** 1.579 + * Handle the 'accessible-event' message. 1.580 + */ 1.581 + observe: function observe(aSubject, aTopic, aData) { 1.582 + if (aTopic !== 'accessible-event') { 1.583 + return; 1.584 + } 1.585 + let event = aSubject.QueryInterface(Ci.nsIAccessibleEvent); 1.586 + if (!event.accessibleDocument) { 1.587 + Logger.warning( 1.588 + 'AccessibilityEventObserver.observe: no accessible document:', 1.589 + Logger.eventToString(event), "accessible:", 1.590 + Logger.accessibleToString(event.accessible)); 1.591 + return; 1.592 + } 1.593 + let content = event.accessibleDocument.window; 1.594 + // Match the content window to its EventManager. 1.595 + let eventManager = this.getListener(content); 1.596 + if (!eventManager || !eventManager._started) { 1.597 + if (Utils.MozBuildApp === 'browser' && 1.598 + !(content instanceof Ci.nsIDOMChromeWindow)) { 1.599 + Logger.warning( 1.600 + 'AccessibilityEventObserver.observe: ignored event:', 1.601 + Logger.eventToString(event), "accessible:", 1.602 + Logger.accessibleToString(event.accessible), "document:", 1.603 + Logger.accessibleToString(event.accessibleDocument)); 1.604 + } 1.605 + return; 1.606 + } 1.607 + try { 1.608 + eventManager.handleAccEvent(event); 1.609 + } catch (x) { 1.610 + Logger.logException(x, 'Error handing accessible event'); 1.611 + } finally { 1.612 + return; 1.613 + } 1.614 + } 1.615 +};