Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
* You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
"use strict";
6
7
ChromeUtils.defineModuleGetter(
8
this,
9
"Services",
11
);
12
ChromeUtils.defineModuleGetter(
13
this,
14
"Utils",
16
);
17
ChromeUtils.defineModuleGetter(
18
this,
19
"Logger",
21
);
22
ChromeUtils.defineModuleGetter(
23
this,
24
"Events",
26
);
27
28
var EXPORTED_SYMBOLS = ["EventManager"];
29
30
function EventManager(aContentScope) {
31
this.contentScope = aContentScope;
32
this.addEventListener = this.contentScope.addEventListener.bind(
33
this.contentScope
34
);
35
this.removeEventListener = this.contentScope.removeEventListener.bind(
36
this.contentScope
37
);
38
this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(this.contentScope);
39
}
40
41
this.EventManager.prototype = {
42
start: function start() {
43
try {
44
if (!this._started) {
45
Logger.debug("EventManager.start");
46
47
this._started = true;
48
49
AccessibilityEventObserver.addListener(this);
50
51
this._preDialogPosition = new WeakMap();
52
}
53
} catch (x) {
54
Logger.logException(x, "Failed to start EventManager");
55
}
56
},
57
58
// XXX: Stop is not called when the tab is closed (|TabClose| event is too
59
// late). It is only called when the AccessFu is disabled explicitly.
60
stop: function stop() {
61
if (!this._started) {
62
return;
63
}
64
Logger.debug("EventManager.stop");
65
AccessibilityEventObserver.removeListener(this);
66
try {
67
this._preDialogPosition = new WeakMap();
68
} catch (x) {
69
// contentScope is dead.
70
} finally {
71
this._started = false;
72
}
73
},
74
75
get contentControl() {
76
return this.contentScope._jsat_contentControl;
77
},
78
79
handleAccEvent: function handleAccEvent(aEvent) {
80
Logger.debug(() => {
81
return [
82
"A11yEvent",
83
Logger.eventToString(aEvent),
84
Logger.accessibleToString(aEvent.accessible),
85
];
86
});
87
88
// Don't bother with non-content events in firefox.
89
if (
90
Utils.MozBuildApp == "browser" &&
91
aEvent.eventType != Events.VIRTUALCURSOR_CHANGED &&
92
// XXX Bug 442005 results in DocAccessible::getDocType returning
93
// NS_ERROR_FAILURE. Checking for aEvent.accessibleDocument.docType ==
94
// 'window' does not currently work.
95
(aEvent.accessibleDocument.DOMDocument.doctype &&
96
aEvent.accessibleDocument.DOMDocument.doctype.name === "window")
97
) {
98
return;
99
}
100
101
switch (aEvent.eventType) {
102
case Events.TEXT_CARET_MOVED: {
103
if (
104
aEvent.accessible != aEvent.accessibleDocument &&
105
!aEvent.isFromUserInput
106
) {
107
// If caret moves in document without direct user
108
// we are probably stepping through results in find-in-page.
109
let acc = Utils.getTextLeafForOffset(
110
aEvent.accessible,
111
aEvent.QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset
112
);
113
this.contentControl.autoMove(acc);
114
}
115
break;
116
}
117
case Events.NAME_CHANGE: {
118
// XXX: Port to Android
119
break;
120
}
121
case Events.SCROLLING_START: {
122
this.contentControl.autoMove(aEvent.accessible);
123
break;
124
}
125
case Events.SHOW: {
126
// XXX: Port to Android
127
break;
128
}
129
case Events.HIDE: {
130
// XXX: Port to Android
131
break;
132
}
133
case Events.VALUE_CHANGE: {
134
// XXX: Port to Android
135
break;
136
}
137
}
138
},
139
140
QueryInterface: ChromeUtils.generateQI([
141
Ci.nsISupportsWeakReference,
142
Ci.nsIObserver,
143
]),
144
};
145
146
const AccessibilityEventObserver = {
147
/**
148
* A WeakMap containing [content, EventManager] pairs.
149
*/
150
eventManagers: new WeakMap(),
151
152
/**
153
* A total number of registered eventManagers.
154
*/
155
listenerCount: 0,
156
157
/**
158
* An indicator of an active 'accessible-event' observer.
159
*/
160
started: false,
161
162
/**
163
* Start an AccessibilityEventObserver.
164
*/
165
start: function start() {
166
if (this.started || this.listenerCount === 0) {
167
return;
168
}
169
Services.obs.addObserver(this, "accessible-event");
170
this.started = true;
171
},
172
173
/**
174
* Stop an AccessibilityEventObserver.
175
*/
176
stop: function stop() {
177
if (!this.started) {
178
return;
179
}
180
Services.obs.removeObserver(this, "accessible-event");
181
// Clean up all registered event managers.
182
this.eventManagers = new WeakMap();
183
this.listenerCount = 0;
184
this.started = false;
185
},
186
187
/**
188
* Register an EventManager and start listening to the
189
* 'accessible-event' messages.
190
*
191
* @param aEventManager EventManager
192
* An EventManager object that was loaded into the specific content.
193
*/
194
addListener: function addListener(aEventManager) {
195
let content = aEventManager.contentScope.content;
196
if (!this.eventManagers.has(content)) {
197
this.listenerCount++;
198
}
199
this.eventManagers.set(content, aEventManager);
200
// Since at least one EventManager was registered, start listening.
201
Logger.debug(
202
"AccessibilityEventObserver.addListener. Total:",
203
this.listenerCount
204
);
205
this.start();
206
},
207
208
/**
209
* Unregister an EventManager and, optionally, stop listening to the
210
* 'accessible-event' messages.
211
*
212
* @param aEventManager EventManager
213
* An EventManager object that was stopped in the specific content.
214
*/
215
removeListener: function removeListener(aEventManager) {
216
let content = aEventManager.contentScope.content;
217
if (!this.eventManagers.delete(content)) {
218
return;
219
}
220
this.listenerCount--;
221
Logger.debug(
222
"AccessibilityEventObserver.removeListener. Total:",
223
this.listenerCount
224
);
225
if (this.listenerCount === 0) {
226
// If there are no EventManagers registered at the moment, stop listening
227
// to the 'accessible-event' messages.
228
this.stop();
229
}
230
},
231
232
/**
233
* Lookup an EventManager for a specific content. If the EventManager is not
234
* found, walk up the hierarchy of parent windows.
235
* @param content Window
236
* A content Window used to lookup the corresponding EventManager.
237
*/
238
getListener: function getListener(content) {
239
let eventManager = this.eventManagers.get(content);
240
if (eventManager) {
241
return eventManager;
242
}
243
let parent = content.parent;
244
if (parent === content) {
245
// There is no parent or the parent is of a different type.
246
return null;
247
}
248
return this.getListener(parent);
249
},
250
251
/**
252
* Handle the 'accessible-event' message.
253
*/
254
observe: function observe(aSubject, aTopic, aData) {
255
if (aTopic !== "accessible-event") {
256
return;
257
}
258
let event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
259
if (!event.accessibleDocument) {
260
Logger.warning(
261
"AccessibilityEventObserver.observe: no accessible document:",
262
Logger.eventToString(event),
263
"accessible:",
264
Logger.accessibleToString(event.accessible)
265
);
266
return;
267
}
268
let content;
269
try {
270
content = event.accessibleDocument.window;
271
} catch (e) {
272
Logger.warning(
273
"AccessibilityEventObserver.observe: no window for accessible document:",
274
Logger.eventToString(event),
275
"accessible:",
276
Logger.accessibleToString(event.accessible)
277
);
278
return;
279
}
280
// Match the content window to its EventManager.
281
let eventManager = this.getListener(content);
282
if (!eventManager || !eventManager._started) {
283
if (Utils.MozBuildApp === "browser" && !content.isChromeWindow) {
284
Logger.warning(
285
"AccessibilityEventObserver.observe: ignored event:",
286
Logger.eventToString(event),
287
"accessible:",
288
Logger.accessibleToString(event.accessible),
289
"document:",
290
Logger.accessibleToString(event.accessibleDocument)
291
);
292
}
293
return;
294
}
295
try {
296
eventManager.handleAccEvent(event);
297
} catch (x) {
298
Logger.logException(x, "Error handing accessible event");
299
}
300
},
301
};