зеркало из https://github.com/mozilla/gecko-dev.git
288 строки
8.3 KiB
JavaScript
288 строки
8.3 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
ChromeUtils.defineModuleGetter(this, "Services",
|
|
"resource://gre/modules/Services.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "Utils",
|
|
"resource://gre/modules/accessibility/Utils.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "Logger",
|
|
"resource://gre/modules/accessibility/Utils.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "Roles",
|
|
"resource://gre/modules/accessibility/Constants.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "Events",
|
|
"resource://gre/modules/accessibility/Constants.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "States",
|
|
"resource://gre/modules/accessibility/Constants.jsm");
|
|
|
|
var EXPORTED_SYMBOLS = ["EventManager"];
|
|
|
|
function EventManager(aContentScope) {
|
|
this.contentScope = aContentScope;
|
|
this.addEventListener = this.contentScope.addEventListener.bind(
|
|
this.contentScope);
|
|
this.removeEventListener = this.contentScope.removeEventListener.bind(
|
|
this.contentScope);
|
|
this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
|
|
this.contentScope);
|
|
}
|
|
|
|
this.EventManager.prototype = {
|
|
start: function start() {
|
|
try {
|
|
if (!this._started) {
|
|
Logger.debug("EventManager.start");
|
|
|
|
this._started = true;
|
|
|
|
AccessibilityEventObserver.addListener(this);
|
|
|
|
this._preDialogPosition = new WeakMap();
|
|
}
|
|
} catch (x) {
|
|
Logger.logException(x, "Failed to start EventManager");
|
|
}
|
|
},
|
|
|
|
// XXX: Stop is not called when the tab is closed (|TabClose| event is too
|
|
// late). It is only called when the AccessFu is disabled explicitly.
|
|
stop: function stop() {
|
|
if (!this._started) {
|
|
return;
|
|
}
|
|
Logger.debug("EventManager.stop");
|
|
AccessibilityEventObserver.removeListener(this);
|
|
try {
|
|
this._preDialogPosition = new WeakMap();
|
|
} catch (x) {
|
|
// contentScope is dead.
|
|
} finally {
|
|
this._started = false;
|
|
}
|
|
},
|
|
|
|
get contentControl() {
|
|
return this.contentScope._jsat_contentControl;
|
|
},
|
|
|
|
handleAccEvent: function handleAccEvent(aEvent) {
|
|
Logger.debug(() => {
|
|
return ["A11yEvent", Logger.eventToString(aEvent),
|
|
Logger.accessibleToString(aEvent.accessible)];
|
|
});
|
|
|
|
// Don't bother with non-content events in firefox.
|
|
if (Utils.MozBuildApp == "browser" &&
|
|
aEvent.eventType != Events.VIRTUALCURSOR_CHANGED &&
|
|
// XXX Bug 442005 results in DocAccessible::getDocType returning
|
|
// NS_ERROR_FAILURE. Checking for aEvent.accessibleDocument.docType ==
|
|
// 'window' does not currently work.
|
|
(aEvent.accessibleDocument.DOMDocument.doctype &&
|
|
aEvent.accessibleDocument.DOMDocument.doctype.name === "window")) {
|
|
return;
|
|
}
|
|
|
|
switch (aEvent.eventType) {
|
|
case Events.VIRTUALCURSOR_CHANGED:
|
|
{
|
|
if (!aEvent.isFromUserInput) {
|
|
break;
|
|
}
|
|
|
|
const event = aEvent.
|
|
QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
|
|
const position = event.newAccessible;
|
|
|
|
// We pass control to the vc in the embedded frame.
|
|
if (position && position.role == Roles.INTERNAL_FRAME) {
|
|
break;
|
|
}
|
|
|
|
// Blur to document if new position is not explicitly focused.
|
|
if (!position || !Utils.getState(position).contains(States.FOCUSED)) {
|
|
aEvent.accessibleDocument.takeFocus();
|
|
}
|
|
break;
|
|
}
|
|
case Events.NAME_CHANGE:
|
|
{
|
|
// XXX: Port to Android
|
|
break;
|
|
}
|
|
case Events.SCROLLING_START:
|
|
{
|
|
this.contentControl.autoMove(aEvent.accessible);
|
|
break;
|
|
}
|
|
case Events.SHOW:
|
|
{
|
|
// XXX: Port to Android
|
|
break;
|
|
}
|
|
case Events.HIDE:
|
|
{
|
|
// XXX: Port to Android
|
|
break;
|
|
}
|
|
case Events.VALUE_CHANGE:
|
|
{
|
|
// XXX: Port to Android
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver]),
|
|
};
|
|
|
|
const AccessibilityEventObserver = {
|
|
|
|
/**
|
|
* A WeakMap containing [content, EventManager] pairs.
|
|
*/
|
|
eventManagers: new WeakMap(),
|
|
|
|
/**
|
|
* A total number of registered eventManagers.
|
|
*/
|
|
listenerCount: 0,
|
|
|
|
/**
|
|
* An indicator of an active 'accessible-event' observer.
|
|
*/
|
|
started: false,
|
|
|
|
/**
|
|
* Start an AccessibilityEventObserver.
|
|
*/
|
|
start: function start() {
|
|
if (this.started || this.listenerCount === 0) {
|
|
return;
|
|
}
|
|
Services.obs.addObserver(this, "accessible-event");
|
|
this.started = true;
|
|
},
|
|
|
|
/**
|
|
* Stop an AccessibilityEventObserver.
|
|
*/
|
|
stop: function stop() {
|
|
if (!this.started) {
|
|
return;
|
|
}
|
|
Services.obs.removeObserver(this, "accessible-event");
|
|
// Clean up all registered event managers.
|
|
this.eventManagers = new WeakMap();
|
|
this.listenerCount = 0;
|
|
this.started = false;
|
|
},
|
|
|
|
/**
|
|
* Register an EventManager and start listening to the
|
|
* 'accessible-event' messages.
|
|
*
|
|
* @param aEventManager EventManager
|
|
* An EventManager object that was loaded into the specific content.
|
|
*/
|
|
addListener: function addListener(aEventManager) {
|
|
let content = aEventManager.contentScope.content;
|
|
if (!this.eventManagers.has(content)) {
|
|
this.listenerCount++;
|
|
}
|
|
this.eventManagers.set(content, aEventManager);
|
|
// Since at least one EventManager was registered, start listening.
|
|
Logger.debug("AccessibilityEventObserver.addListener. Total:",
|
|
this.listenerCount);
|
|
this.start();
|
|
},
|
|
|
|
/**
|
|
* Unregister an EventManager and, optionally, stop listening to the
|
|
* 'accessible-event' messages.
|
|
*
|
|
* @param aEventManager EventManager
|
|
* An EventManager object that was stopped in the specific content.
|
|
*/
|
|
removeListener: function removeListener(aEventManager) {
|
|
let content = aEventManager.contentScope.content;
|
|
if (!this.eventManagers.delete(content)) {
|
|
return;
|
|
}
|
|
this.listenerCount--;
|
|
Logger.debug("AccessibilityEventObserver.removeListener. Total:",
|
|
this.listenerCount);
|
|
if (this.listenerCount === 0) {
|
|
// If there are no EventManagers registered at the moment, stop listening
|
|
// to the 'accessible-event' messages.
|
|
this.stop();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Lookup an EventManager for a specific content. If the EventManager is not
|
|
* found, walk up the hierarchy of parent windows.
|
|
* @param content Window
|
|
* A content Window used to lookup the corresponding EventManager.
|
|
*/
|
|
getListener: function getListener(content) {
|
|
let eventManager = this.eventManagers.get(content);
|
|
if (eventManager) {
|
|
return eventManager;
|
|
}
|
|
let parent = content.parent;
|
|
if (parent === content) {
|
|
// There is no parent or the parent is of a different type.
|
|
return null;
|
|
}
|
|
return this.getListener(parent);
|
|
},
|
|
|
|
/**
|
|
* Handle the 'accessible-event' message.
|
|
*/
|
|
observe: function observe(aSubject, aTopic, aData) {
|
|
if (aTopic !== "accessible-event") {
|
|
return;
|
|
}
|
|
let event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
|
|
if (!event.accessibleDocument) {
|
|
Logger.warning(
|
|
"AccessibilityEventObserver.observe: no accessible document:",
|
|
Logger.eventToString(event), "accessible:",
|
|
Logger.accessibleToString(event.accessible));
|
|
return;
|
|
}
|
|
let content;
|
|
try {
|
|
content = event.accessibleDocument.window;
|
|
} catch (e) {
|
|
Logger.warning(
|
|
"AccessibilityEventObserver.observe: no window for accessible document:",
|
|
Logger.eventToString(event), "accessible:",
|
|
Logger.accessibleToString(event.accessible));
|
|
return;
|
|
}
|
|
// Match the content window to its EventManager.
|
|
let eventManager = this.getListener(content);
|
|
if (!eventManager || !eventManager._started) {
|
|
if (Utils.MozBuildApp === "browser" && !content.isChromeWindow) {
|
|
Logger.warning(
|
|
"AccessibilityEventObserver.observe: ignored event:",
|
|
Logger.eventToString(event), "accessible:",
|
|
Logger.accessibleToString(event.accessible), "document:",
|
|
Logger.accessibleToString(event.accessibleDocument));
|
|
}
|
|
return;
|
|
}
|
|
try {
|
|
eventManager.handleAccEvent(event);
|
|
} catch (x) {
|
|
Logger.logException(x, "Error handing accessible event");
|
|
} finally {
|
|
return;
|
|
}
|
|
},
|
|
};
|