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 Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: const TEXT_NODE = 3; michael@0: michael@0: Cu.import('resource://gre/modules/XPCOMUtils.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Services', michael@0: 'resource://gre/modules/Services.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, 'Presentation', michael@0: 'resource://gre/modules/accessibility/Presentation.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules', michael@0: 'resource://gre/modules/accessibility/TraversalRules.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Roles', michael@0: 'resource://gre/modules/accessibility/Constants.jsm'); michael@0: XPCOMUtils.defineLazyModuleGetter(this, 'Events', 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 = ['EventManager']; michael@0: michael@0: this.EventManager = function EventManager(aContentScope) { michael@0: this.contentScope = aContentScope; michael@0: this.addEventListener = this.contentScope.addEventListener.bind( michael@0: this.contentScope); michael@0: this.removeEventListener = this.contentScope.removeEventListener.bind( michael@0: this.contentScope); michael@0: this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind( michael@0: this.contentScope); michael@0: this.webProgress = this.contentScope.docShell. michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIWebProgress); michael@0: }; michael@0: michael@0: this.EventManager.prototype = { michael@0: editState: {}, michael@0: michael@0: start: function start() { michael@0: try { michael@0: if (!this._started) { michael@0: Logger.debug('EventManager.start'); michael@0: michael@0: this._started = true; michael@0: michael@0: AccessibilityEventObserver.addListener(this); michael@0: michael@0: this.webProgress.addProgressListener(this, michael@0: (Ci.nsIWebProgress.NOTIFY_STATE_ALL | michael@0: Ci.nsIWebProgress.NOTIFY_LOCATION)); michael@0: this.addEventListener('wheel', this, true); michael@0: this.addEventListener('scroll', this, true); michael@0: this.addEventListener('resize', this, true); michael@0: } michael@0: this.present(Presentation.tabStateChanged(null, 'newtab')); michael@0: michael@0: } catch (x) { michael@0: Logger.logException(x, 'Failed to start EventManager'); michael@0: } michael@0: }, michael@0: michael@0: // XXX: Stop is not called when the tab is closed (|TabClose| event is too michael@0: // late). It is only called when the AccessFu is disabled explicitly. michael@0: stop: function stop() { michael@0: if (!this._started) { michael@0: return; michael@0: } michael@0: Logger.debug('EventManager.stop'); michael@0: AccessibilityEventObserver.removeListener(this); michael@0: try { michael@0: this.webProgress.removeProgressListener(this); michael@0: this.removeEventListener('wheel', this, true); michael@0: this.removeEventListener('scroll', this, true); michael@0: this.removeEventListener('resize', this, true); michael@0: } catch (x) { michael@0: // contentScope is dead. michael@0: } finally { michael@0: this._started = false; michael@0: } michael@0: }, michael@0: michael@0: handleEvent: function handleEvent(aEvent) { michael@0: Logger.debug(() => { michael@0: return ['DOMEvent', aEvent.type]; michael@0: }); michael@0: michael@0: try { michael@0: switch (aEvent.type) { michael@0: case 'wheel': michael@0: { michael@0: let attempts = 0; michael@0: let delta = aEvent.deltaX || aEvent.deltaY; michael@0: this.contentScope.contentControl.autoMove( michael@0: null, michael@0: { moveMethod: delta > 0 ? 'moveNext' : 'movePrevious', michael@0: onScreenOnly: true, noOpIfOnScreen: true, delay: 500 }); michael@0: break; michael@0: } michael@0: case 'scroll': michael@0: case 'resize': michael@0: { michael@0: // the target could be an element, document or window michael@0: let window = null; michael@0: if (aEvent.target instanceof Ci.nsIDOMWindow) michael@0: window = aEvent.target; michael@0: else if (aEvent.target instanceof Ci.nsIDOMDocument) michael@0: window = aEvent.target.defaultView; michael@0: else if (aEvent.target instanceof Ci.nsIDOMElement) michael@0: window = aEvent.target.ownerDocument.defaultView; michael@0: this.present(Presentation.viewportChanged(window)); michael@0: break; michael@0: } michael@0: } michael@0: } catch (x) { michael@0: Logger.logException(x, 'Error handling DOM event'); michael@0: } michael@0: }, michael@0: michael@0: handleAccEvent: function handleAccEvent(aEvent) { michael@0: Logger.debug(() => { michael@0: return ['A11yEvent', Logger.eventToString(aEvent), michael@0: Logger.accessibleToString(aEvent.accessible)]; michael@0: }); michael@0: michael@0: // Don't bother with non-content events in firefox. michael@0: if (Utils.MozBuildApp == 'browser' && michael@0: aEvent.eventType != Events.VIRTUALCURSOR_CHANGED && michael@0: // XXX Bug 442005 results in DocAccessible::getDocType returning michael@0: // NS_ERROR_FAILURE. Checking for aEvent.accessibleDocument.docType == michael@0: // 'window' does not currently work. michael@0: (aEvent.accessibleDocument.DOMDocument.doctype && michael@0: aEvent.accessibleDocument.DOMDocument.doctype.name === 'window')) { michael@0: return; michael@0: } michael@0: michael@0: switch (aEvent.eventType) { michael@0: case Events.VIRTUALCURSOR_CHANGED: michael@0: { michael@0: let pivot = aEvent.accessible. michael@0: QueryInterface(Ci.nsIAccessibleDocument).virtualCursor; michael@0: let position = pivot.position; michael@0: if (position && position.role == Roles.INTERNAL_FRAME) michael@0: break; michael@0: let event = aEvent. michael@0: QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent); michael@0: let reason = event.reason; michael@0: let oldAccessible = event.oldAccessible; michael@0: michael@0: if (this.editState.editing) { michael@0: aEvent.accessibleDocument.takeFocus(); michael@0: } michael@0: this.present( michael@0: Presentation.pivotChanged(position, oldAccessible, reason, michael@0: pivot.startOffset, pivot.endOffset)); michael@0: michael@0: break; michael@0: } michael@0: case Events.STATE_CHANGE: michael@0: { michael@0: let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent); michael@0: let state = Utils.getState(event); michael@0: if (state.contains(States.CHECKED)) { michael@0: this.present( michael@0: Presentation. michael@0: actionInvoked(aEvent.accessible, michael@0: event.isEnabled ? 'check' : 'uncheck')); michael@0: } else if (state.contains(States.SELECTED)) { michael@0: this.present( michael@0: Presentation. michael@0: actionInvoked(aEvent.accessible, michael@0: event.isEnabled ? 'select' : 'unselect')); michael@0: } michael@0: break; michael@0: } michael@0: case Events.SCROLLING_START: michael@0: { michael@0: this.contentScope.contentControl.autoMove(aEvent.accessible); michael@0: break; michael@0: } michael@0: case Events.TEXT_CARET_MOVED: michael@0: { michael@0: let acc = aEvent.accessible; michael@0: let characterCount = acc. michael@0: QueryInterface(Ci.nsIAccessibleText).characterCount; michael@0: let caretOffset = aEvent. michael@0: QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset; michael@0: michael@0: // Update editing state, both for presenter and other things michael@0: let state = Utils.getState(acc); michael@0: let editState = { michael@0: editing: state.contains(States.EDITABLE), michael@0: multiline: state.contains(States.MULTI_LINE), michael@0: atStart: caretOffset == 0, michael@0: atEnd: caretOffset == characterCount michael@0: }; michael@0: michael@0: // Not interesting michael@0: if (!editState.editing && editState.editing == this.editState.editing) michael@0: break; michael@0: michael@0: if (editState.editing != this.editState.editing) michael@0: this.present(Presentation.editingModeChanged(editState.editing)); michael@0: michael@0: if (editState.editing != this.editState.editing || michael@0: editState.multiline != this.editState.multiline || michael@0: editState.atEnd != this.editState.atEnd || michael@0: editState.atStart != this.editState.atStart) michael@0: this.sendMsgFunc("AccessFu:Input", editState); michael@0: michael@0: this.present(Presentation.textSelectionChanged(acc.getText(0,-1), michael@0: caretOffset, caretOffset, 0, 0, aEvent.isFromUserInput)); michael@0: michael@0: this.editState = editState; michael@0: break; michael@0: } michael@0: case Events.SHOW: michael@0: { michael@0: let {liveRegion, isPolite} = this._handleLiveRegion(aEvent, michael@0: ['additions', 'all']); michael@0: // Only handle show if it is a relevant live region. michael@0: if (!liveRegion) { michael@0: break; michael@0: } michael@0: // Show for text is handled by the EVENT_TEXT_INSERTED handler. michael@0: if (aEvent.accessible.role === Roles.TEXT_LEAF) { michael@0: break; michael@0: } michael@0: this._dequeueLiveEvent(Events.HIDE, liveRegion); michael@0: this.present(Presentation.liveRegion(liveRegion, isPolite, false)); michael@0: break; michael@0: } michael@0: case Events.HIDE: michael@0: { michael@0: let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent); michael@0: let {liveRegion, isPolite} = this._handleLiveRegion( michael@0: evt, ['removals', 'all']); michael@0: if (liveRegion) { michael@0: // Hide for text is handled by the EVENT_TEXT_REMOVED handler. michael@0: if (aEvent.accessible.role === Roles.TEXT_LEAF) { michael@0: break; michael@0: } michael@0: this._queueLiveEvent(Events.HIDE, liveRegion, isPolite); michael@0: } else { michael@0: let vc = Utils.getVirtualCursor(this.contentScope.content.document); michael@0: if (vc.position && michael@0: (Utils.getState(vc.position).contains(States.DEFUNCT) || michael@0: Utils.isInSubtree(vc.position, aEvent.accessible))) { michael@0: this.contentScope.contentControl.autoMove( michael@0: evt.targetPrevSibling || evt.targetParent, michael@0: { moveToFocused: true, delay: 500 }); michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: case Events.TEXT_INSERTED: michael@0: case Events.TEXT_REMOVED: michael@0: { michael@0: let {liveRegion, isPolite} = this._handleLiveRegion(aEvent, michael@0: ['text', 'all']); michael@0: if (aEvent.isFromUserInput || liveRegion) { michael@0: // Handle all text mutations coming from the user or if they happen michael@0: // on a live region. michael@0: this._handleText(aEvent, liveRegion, isPolite); michael@0: } michael@0: break; michael@0: } michael@0: case Events.FOCUS: michael@0: { michael@0: // Put vc where the focus is at michael@0: let acc = aEvent.accessible; michael@0: let doc = aEvent.accessibleDocument; michael@0: if (acc.role != Roles.DOCUMENT && doc.role != Roles.CHROME_WINDOW) { michael@0: this.contentScope.contentControl.autoMove(acc); michael@0: } michael@0: break; michael@0: } michael@0: case Events.DOCUMENT_LOAD_COMPLETE: michael@0: { michael@0: if (aEvent.accessible === aEvent.accessibleDocument) { michael@0: break; michael@0: } michael@0: this.contentScope.contentControl.autoMove( michael@0: aEvent.accessible, { delay: 500 }); michael@0: break; michael@0: } michael@0: case Events.VALUE_CHANGE: michael@0: { michael@0: let position = this.contentScope.contentControl.vc.position; michael@0: let target = aEvent.accessible; michael@0: if (position === target || michael@0: Utils.getEmbeddedControl(position) === target) { michael@0: this.present(Presentation.valueChanged(target)); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) { michael@0: let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent); michael@0: let isInserted = event.isInserted; michael@0: let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText); michael@0: michael@0: let text = ''; michael@0: try { michael@0: text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT); michael@0: } catch (x) { michael@0: // XXX we might have gotten an exception with of a michael@0: // zero-length text. If we did, ignore it (bug #749810). michael@0: if (txtIface.characterCount) { michael@0: throw x; michael@0: } michael@0: } michael@0: // If there are embedded objects in the text, ignore them. michael@0: // Assuming changes to the descendants would already be handled by the michael@0: // show/hide event. michael@0: let modifiedText = event.modifiedText.replace(/\uFFFC/g, '').trim(); michael@0: if (!modifiedText) { michael@0: return; michael@0: } michael@0: if (aLiveRegion) { michael@0: if (aEvent.eventType === Events.TEXT_REMOVED) { michael@0: this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite, michael@0: modifiedText); michael@0: } else { michael@0: this._dequeueLiveEvent(Events.TEXT_REMOVED, aLiveRegion); michael@0: this.present(Presentation.liveRegion(aLiveRegion, aIsPolite, false, michael@0: modifiedText)); michael@0: } michael@0: } else { michael@0: this.present(Presentation.textChanged(isInserted, event.start, michael@0: event.length, text, modifiedText)); michael@0: } michael@0: }, michael@0: michael@0: _handleLiveRegion: function _handleLiveRegion(aEvent, aRelevant) { michael@0: if (aEvent.isFromUserInput) { michael@0: return {}; michael@0: } michael@0: let parseLiveAttrs = function parseLiveAttrs(aAccessible) { michael@0: let attrs = Utils.getAttributes(aAccessible); michael@0: if (attrs['container-live']) { michael@0: return { michael@0: live: attrs['container-live'], michael@0: relevant: attrs['container-relevant'] || 'additions text', michael@0: busy: attrs['container-busy'], michael@0: atomic: attrs['container-atomic'], michael@0: memberOf: attrs['member-of'] michael@0: }; michael@0: } michael@0: return null; michael@0: }; michael@0: // XXX live attributes are not set for hidden accessibles yet. Need to michael@0: // climb up the tree to check for them. michael@0: let getLiveAttributes = function getLiveAttributes(aEvent) { michael@0: let liveAttrs = parseLiveAttrs(aEvent.accessible); michael@0: if (liveAttrs) { michael@0: return liveAttrs; michael@0: } michael@0: let parent = aEvent.targetParent; michael@0: while (parent) { michael@0: liveAttrs = parseLiveAttrs(parent); michael@0: if (liveAttrs) { michael@0: return liveAttrs; michael@0: } michael@0: parent = parent.parent michael@0: } michael@0: return {}; michael@0: }; michael@0: let {live, relevant, busy, atomic, memberOf} = getLiveAttributes(aEvent); michael@0: // If container-live is not present or is set to |off| ignore the event. michael@0: if (!live || live === 'off') { michael@0: return {}; michael@0: } michael@0: // XXX: support busy and atomic. michael@0: michael@0: // Determine if the type of the mutation is relevant. Default is additions michael@0: // and text. michael@0: let isRelevant = Utils.matchAttributeValue(relevant, aRelevant); michael@0: if (!isRelevant) { michael@0: return {}; michael@0: } michael@0: return { michael@0: liveRegion: aEvent.accessible, michael@0: isPolite: live === 'polite' michael@0: }; michael@0: }, michael@0: michael@0: _dequeueLiveEvent: function _dequeueLiveEvent(aEventType, aLiveRegion) { michael@0: let domNode = aLiveRegion.DOMNode; michael@0: if (this._liveEventQueue && this._liveEventQueue.has(domNode)) { michael@0: let queue = this._liveEventQueue.get(domNode); michael@0: let nextEvent = queue[0]; michael@0: if (nextEvent.eventType === aEventType) { michael@0: Utils.win.clearTimeout(nextEvent.timeoutID); michael@0: queue.shift(); michael@0: if (queue.length === 0) { michael@0: this._liveEventQueue.delete(domNode) michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _queueLiveEvent: function _queueLiveEvent(aEventType, aLiveRegion, aIsPolite, aModifiedText) { michael@0: if (!this._liveEventQueue) { michael@0: this._liveEventQueue = new WeakMap(); michael@0: } michael@0: let eventHandler = { michael@0: eventType: aEventType, michael@0: timeoutID: Utils.win.setTimeout(this.present.bind(this), michael@0: 20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event. michael@0: Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText)) michael@0: }; michael@0: michael@0: let domNode = aLiveRegion.DOMNode; michael@0: if (this._liveEventQueue.has(domNode)) { michael@0: this._liveEventQueue.get(domNode).push(eventHandler); michael@0: } else { michael@0: this._liveEventQueue.set(domNode, [eventHandler]); michael@0: } michael@0: }, michael@0: michael@0: present: function present(aPresentationData) { michael@0: this.sendMsgFunc("AccessFu:Present", aPresentationData); michael@0: }, michael@0: michael@0: onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { michael@0: let tabstate = ''; michael@0: michael@0: let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING | michael@0: Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; michael@0: let loadedState = Ci.nsIWebProgressListener.STATE_STOP | michael@0: Ci.nsIWebProgressListener.STATE_IS_NETWORK; michael@0: michael@0: if ((aStateFlags & loadingState) == loadingState) { michael@0: tabstate = 'loading'; michael@0: } else if ((aStateFlags & loadedState) == loadedState && michael@0: !aWebProgress.isLoadingDocument) { michael@0: tabstate = 'loaded'; michael@0: } michael@0: michael@0: if (tabstate) { michael@0: let docAcc = Utils.AccRetrieval.getAccessibleFor(aWebProgress.DOMWindow.document); michael@0: this.present(Presentation.tabStateChanged(docAcc, tabstate)); michael@0: } michael@0: }, michael@0: michael@0: onProgressChange: function onProgressChange() {}, michael@0: michael@0: onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { michael@0: let docAcc = Utils.AccRetrieval.getAccessibleFor(aWebProgress.DOMWindow.document); michael@0: this.present(Presentation.tabStateChanged(docAcc, 'newdoc')); michael@0: }, michael@0: michael@0: onStatusChange: function onStatusChange() {}, michael@0: michael@0: onSecurityChange: function onSecurityChange() {}, michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, michael@0: Ci.nsISupportsWeakReference, michael@0: Ci.nsISupports, michael@0: Ci.nsIObserver]) michael@0: }; michael@0: michael@0: const AccessibilityEventObserver = { michael@0: michael@0: /** michael@0: * A WeakMap containing [content, EventManager] pairs. michael@0: */ michael@0: eventManagers: new WeakMap(), michael@0: michael@0: /** michael@0: * A total number of registered eventManagers. michael@0: */ michael@0: listenerCount: 0, michael@0: michael@0: /** michael@0: * An indicator of an active 'accessible-event' observer. michael@0: */ michael@0: started: false, michael@0: michael@0: /** michael@0: * Start an AccessibilityEventObserver. michael@0: */ michael@0: start: function start() { michael@0: if (this.started || this.listenerCount === 0) { michael@0: return; michael@0: } michael@0: Services.obs.addObserver(this, 'accessible-event', false); michael@0: this.started = true; michael@0: }, michael@0: michael@0: /** michael@0: * Stop an AccessibilityEventObserver. michael@0: */ michael@0: stop: function stop() { michael@0: if (!this.started) { michael@0: return; michael@0: } michael@0: Services.obs.removeObserver(this, 'accessible-event'); michael@0: // Clean up all registered event managers. michael@0: this.eventManagers.clear(); michael@0: this.listenerCount = 0; michael@0: this.started = false; michael@0: }, michael@0: michael@0: /** michael@0: * Register an EventManager and start listening to the michael@0: * 'accessible-event' messages. michael@0: * michael@0: * @param aEventManager EventManager michael@0: * An EventManager object that was loaded into the specific content. michael@0: */ michael@0: addListener: function addListener(aEventManager) { michael@0: let content = aEventManager.contentScope.content; michael@0: if (!this.eventManagers.has(content)) { michael@0: this.listenerCount++; michael@0: } michael@0: this.eventManagers.set(content, aEventManager); michael@0: // Since at least one EventManager was registered, start listening. michael@0: Logger.debug('AccessibilityEventObserver.addListener. Total:', michael@0: this.listenerCount); michael@0: this.start(); michael@0: }, michael@0: michael@0: /** michael@0: * Unregister an EventManager and, optionally, stop listening to the michael@0: * 'accessible-event' messages. michael@0: * michael@0: * @param aEventManager EventManager michael@0: * An EventManager object that was stopped in the specific content. michael@0: */ michael@0: removeListener: function removeListener(aEventManager) { michael@0: let content = aEventManager.contentScope.content; michael@0: if (!this.eventManagers.delete(content)) { michael@0: return; michael@0: } michael@0: this.listenerCount--; michael@0: Logger.debug('AccessibilityEventObserver.removeListener. Total:', michael@0: this.listenerCount); michael@0: if (this.listenerCount === 0) { michael@0: // If there are no EventManagers registered at the moment, stop listening michael@0: // to the 'accessible-event' messages. michael@0: this.stop(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Lookup an EventManager for a specific content. If the EventManager is not michael@0: * found, walk up the hierarchy of parent windows. michael@0: * @param content Window michael@0: * A content Window used to lookup the corresponding EventManager. michael@0: */ michael@0: getListener: function getListener(content) { michael@0: let eventManager = this.eventManagers.get(content); michael@0: if (eventManager) { michael@0: return eventManager; michael@0: } michael@0: let parent = content.parent; michael@0: if (parent === content) { michael@0: // There is no parent or the parent is of a different type. michael@0: return null; michael@0: } michael@0: return this.getListener(parent); michael@0: }, michael@0: michael@0: /** michael@0: * Handle the 'accessible-event' message. michael@0: */ michael@0: observe: function observe(aSubject, aTopic, aData) { michael@0: if (aTopic !== 'accessible-event') { michael@0: return; michael@0: } michael@0: let event = aSubject.QueryInterface(Ci.nsIAccessibleEvent); michael@0: if (!event.accessibleDocument) { michael@0: Logger.warning( michael@0: 'AccessibilityEventObserver.observe: no accessible document:', michael@0: Logger.eventToString(event), "accessible:", michael@0: Logger.accessibleToString(event.accessible)); michael@0: return; michael@0: } michael@0: let content = event.accessibleDocument.window; michael@0: // Match the content window to its EventManager. michael@0: let eventManager = this.getListener(content); michael@0: if (!eventManager || !eventManager._started) { michael@0: if (Utils.MozBuildApp === 'browser' && michael@0: !(content instanceof Ci.nsIDOMChromeWindow)) { michael@0: Logger.warning( michael@0: 'AccessibilityEventObserver.observe: ignored event:', michael@0: Logger.eventToString(event), "accessible:", michael@0: Logger.accessibleToString(event.accessible), "document:", michael@0: Logger.accessibleToString(event.accessibleDocument)); michael@0: } michael@0: return; michael@0: } michael@0: try { michael@0: eventManager.handleAccEvent(event); michael@0: } catch (x) { michael@0: Logger.logException(x, 'Error handing accessible event'); michael@0: } finally { michael@0: return; michael@0: } michael@0: } michael@0: };