/src/mozilla-central/accessible/base/EventQueue.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "EventQueue.h" |
7 | | |
8 | | #include "Accessible-inl.h" |
9 | | #include "nsEventShell.h" |
10 | | #include "DocAccessible.h" |
11 | | #include "DocAccessibleChild.h" |
12 | | #include "nsAccessibilityService.h" |
13 | | #include "nsTextEquivUtils.h" |
14 | | #ifdef A11Y_LOG |
15 | | #include "Logging.h" |
16 | | #endif |
17 | | |
18 | | using namespace mozilla; |
19 | | using namespace mozilla::a11y; |
20 | | |
21 | | // Defines the number of selection add/remove events in the queue when they |
22 | | // aren't packed into single selection within event. |
23 | | const unsigned int kSelChangeCountToPack = 5; |
24 | | |
25 | | //////////////////////////////////////////////////////////////////////////////// |
26 | | // EventQueue |
27 | | //////////////////////////////////////////////////////////////////////////////// |
28 | | |
29 | | bool |
30 | | EventQueue::PushEvent(AccEvent* aEvent) |
31 | 0 | { |
32 | 0 | NS_ASSERTION((aEvent->mAccessible && aEvent->mAccessible->IsApplication()) || |
33 | 0 | aEvent->Document() == mDocument, |
34 | 0 | "Queued event belongs to another document!"); |
35 | 0 |
|
36 | 0 | if (!mEvents.AppendElement(aEvent)) |
37 | 0 | return false; |
38 | 0 | |
39 | 0 | // Filter events. |
40 | 0 | CoalesceEvents(); |
41 | 0 |
|
42 | 0 | if (aEvent->mEventRule != AccEvent::eDoNotEmit && |
43 | 0 | (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE || |
44 | 0 | aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED || |
45 | 0 | aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED)) { |
46 | 0 | PushNameChange(aEvent->mAccessible); |
47 | 0 | } |
48 | 0 | return true; |
49 | 0 | } |
50 | | |
51 | | bool |
52 | | EventQueue::PushNameChange(Accessible* aTarget) |
53 | 0 | { |
54 | 0 | // Fire name change event on parent given that this event hasn't been |
55 | 0 | // coalesced, the parent's name was calculated from its subtree, and the |
56 | 0 | // subtree was changed. |
57 | 0 | if (aTarget->HasNameDependentParent()) { |
58 | 0 | // Only continue traversing up the tree if it's possible that the parent |
59 | 0 | // accessible's name can depend on this accessible's name. |
60 | 0 | Accessible* parent = aTarget->Parent(); |
61 | 0 | while (parent && |
62 | 0 | nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) { |
63 | 0 | // Test possible name dependent parent. |
64 | 0 | if (nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) { |
65 | 0 | nsAutoString name; |
66 | 0 | ENameValueFlag nameFlag = parent->Name(name); |
67 | 0 | // If name is obtained from subtree, fire name change event. |
68 | 0 | if (nameFlag == eNameFromSubtree) { |
69 | 0 | RefPtr<AccEvent> nameChangeEvent = |
70 | 0 | new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent); |
71 | 0 | return PushEvent(nameChangeEvent); |
72 | 0 | } |
73 | 0 | break; |
74 | 0 | } |
75 | 0 | parent = parent->Parent(); |
76 | 0 | } |
77 | 0 | } |
78 | 0 | return false; |
79 | 0 | } |
80 | | |
81 | | //////////////////////////////////////////////////////////////////////////////// |
82 | | // EventQueue: private |
83 | | |
84 | | void |
85 | | EventQueue::CoalesceEvents() |
86 | 0 | { |
87 | 0 | NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!"); |
88 | 0 | uint32_t tail = mEvents.Length() - 1; |
89 | 0 | AccEvent* tailEvent = mEvents[tail]; |
90 | 0 |
|
91 | 0 | switch(tailEvent->mEventRule) { |
92 | 0 | case AccEvent::eCoalesceReorder: |
93 | 0 | { |
94 | 0 | DebugOnly<Accessible*> target = tailEvent->mAccessible.get(); |
95 | 0 | MOZ_ASSERT(target->IsApplication() || |
96 | 0 | target->IsOuterDoc() || |
97 | 0 | target->IsXULTree(), |
98 | 0 | "Only app or outerdoc accessible reorder events are in the queue"); |
99 | 0 | MOZ_ASSERT(tailEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER, "only reorder events should be queued"); |
100 | 0 | break; // case eCoalesceReorder |
101 | 0 | } |
102 | 0 |
|
103 | 0 | case AccEvent::eCoalesceOfSameType: |
104 | 0 | { |
105 | 0 | // Coalesce old events by newer event. |
106 | 0 | for (uint32_t index = tail - 1; index < tail; index--) { |
107 | 0 | AccEvent* accEvent = mEvents[index]; |
108 | 0 | if (accEvent->mEventType == tailEvent->mEventType && |
109 | 0 | accEvent->mEventRule == tailEvent->mEventRule) { |
110 | 0 | accEvent->mEventRule = AccEvent::eDoNotEmit; |
111 | 0 | return; |
112 | 0 | } |
113 | 0 | } |
114 | 0 | } break; // case eCoalesceOfSameType |
115 | 0 |
|
116 | 0 | case AccEvent::eCoalesceSelectionChange: |
117 | 0 | { |
118 | 0 | AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent); |
119 | 0 | for (uint32_t index = tail - 1; index < tail; index--) { |
120 | 0 | AccEvent* thisEvent = mEvents[index]; |
121 | 0 | if (thisEvent->mEventRule == tailEvent->mEventRule) { |
122 | 0 | AccSelChangeEvent* thisSelChangeEvent = |
123 | 0 | downcast_accEvent(thisEvent); |
124 | 0 |
|
125 | 0 | // Coalesce selection change events within same control. |
126 | 0 | if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) { |
127 | 0 | CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index); |
128 | 0 | return; |
129 | 0 | } |
130 | 0 | } |
131 | 0 | } |
132 | 0 |
|
133 | 0 | } break; // eCoalesceSelectionChange |
134 | 0 |
|
135 | 0 | case AccEvent::eCoalesceStateChange: |
136 | 0 | { |
137 | 0 | // If state change event is duped then ignore previous event. If state |
138 | 0 | // change event is opposite to previous event then no event is emitted |
139 | 0 | // (accessible state wasn't changed). |
140 | 0 | for (uint32_t index = tail - 1; index < tail; index--) { |
141 | 0 | AccEvent* thisEvent = mEvents[index]; |
142 | 0 | if (thisEvent->mEventRule != AccEvent::eDoNotEmit && |
143 | 0 | thisEvent->mEventType == tailEvent->mEventType && |
144 | 0 | thisEvent->mAccessible == tailEvent->mAccessible) { |
145 | 0 | AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent); |
146 | 0 | AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent); |
147 | 0 | if (thisSCEvent->mState == tailSCEvent->mState) { |
148 | 0 | thisEvent->mEventRule = AccEvent::eDoNotEmit; |
149 | 0 | if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled) |
150 | 0 | tailEvent->mEventRule = AccEvent::eDoNotEmit; |
151 | 0 | } |
152 | 0 | } |
153 | 0 | } |
154 | 0 | break; // eCoalesceStateChange |
155 | 0 | } |
156 | 0 |
|
157 | 0 | case AccEvent::eCoalesceTextSelChange: |
158 | 0 | { |
159 | 0 | // Coalesce older event by newer event for the same selection or target. |
160 | 0 | // Events for same selection may have different targets and vice versa one |
161 | 0 | // target may be pointed by different selections (for latter see |
162 | 0 | // bug 927159). |
163 | 0 | for (uint32_t index = tail - 1; index < tail; index--) { |
164 | 0 | AccEvent* thisEvent = mEvents[index]; |
165 | 0 | if (thisEvent->mEventRule != AccEvent::eDoNotEmit && |
166 | 0 | thisEvent->mEventType == tailEvent->mEventType) { |
167 | 0 | AccTextSelChangeEvent* thisTSCEvent = downcast_accEvent(thisEvent); |
168 | 0 | AccTextSelChangeEvent* tailTSCEvent = downcast_accEvent(tailEvent); |
169 | 0 | if (thisTSCEvent->mSel == tailTSCEvent->mSel || |
170 | 0 | thisEvent->mAccessible == tailEvent->mAccessible) |
171 | 0 | thisEvent->mEventRule = AccEvent::eDoNotEmit; |
172 | 0 | } |
173 | 0 |
|
174 | 0 | } |
175 | 0 | } break; // eCoalesceTextSelChange |
176 | 0 |
|
177 | 0 | case AccEvent::eRemoveDupes: |
178 | 0 | { |
179 | 0 | // Check for repeat events, coalesce newly appended event by more older |
180 | 0 | // event. |
181 | 0 | for (uint32_t index = tail - 1; index < tail; index--) { |
182 | 0 | AccEvent* accEvent = mEvents[index]; |
183 | 0 | if (accEvent->mEventType == tailEvent->mEventType && |
184 | 0 | accEvent->mEventRule == tailEvent->mEventRule && |
185 | 0 | accEvent->mAccessible == tailEvent->mAccessible) { |
186 | 0 | tailEvent->mEventRule = AccEvent::eDoNotEmit; |
187 | 0 | return; |
188 | 0 | } |
189 | 0 | } |
190 | 0 | } break; // case eRemoveDupes |
191 | 0 |
|
192 | 0 | default: |
193 | 0 | break; // case eAllowDupes, eDoNotEmit |
194 | 0 | } // switch |
195 | 0 | } |
196 | | |
197 | | void |
198 | | EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent, |
199 | | AccSelChangeEvent* aThisEvent, |
200 | | uint32_t aThisIndex) |
201 | 0 | { |
202 | 0 | aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1; |
203 | 0 |
|
204 | 0 | // Pack all preceding events into single selection within event |
205 | 0 | // when we receive too much selection add/remove events. |
206 | 0 | if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) { |
207 | 0 | aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN; |
208 | 0 | aTailEvent->mAccessible = aTailEvent->mWidget; |
209 | 0 | aThisEvent->mEventRule = AccEvent::eDoNotEmit; |
210 | 0 |
|
211 | 0 | // Do not emit any preceding selection events for same widget if they |
212 | 0 | // weren't coalesced yet. |
213 | 0 | if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) { |
214 | 0 | for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) { |
215 | 0 | AccEvent* prevEvent = mEvents[jdx]; |
216 | 0 | if (prevEvent->mEventRule == aTailEvent->mEventRule) { |
217 | 0 | AccSelChangeEvent* prevSelChangeEvent = |
218 | 0 | downcast_accEvent(prevEvent); |
219 | 0 | if (prevSelChangeEvent->mWidget == aTailEvent->mWidget) |
220 | 0 | prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | } |
224 | 0 | return; |
225 | 0 | } |
226 | 0 |
|
227 | 0 | // Pack sequential selection remove and selection add events into |
228 | 0 | // single selection change event. |
229 | 0 | if (aTailEvent->mPreceedingCount == 1 && |
230 | 0 | aTailEvent->mItem != aThisEvent->mItem) { |
231 | 0 | if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd && |
232 | 0 | aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) { |
233 | 0 | aThisEvent->mEventRule = AccEvent::eDoNotEmit; |
234 | 0 | aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION; |
235 | 0 | aTailEvent->mPackedEvent = aThisEvent; |
236 | 0 | return; |
237 | 0 | } |
238 | 0 | |
239 | 0 | if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd && |
240 | 0 | aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) { |
241 | 0 | aTailEvent->mEventRule = AccEvent::eDoNotEmit; |
242 | 0 | aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION; |
243 | 0 | aThisEvent->mPackedEvent = aTailEvent; |
244 | 0 | return; |
245 | 0 | } |
246 | 0 | } |
247 | 0 | |
248 | 0 | // Unpack the packed selection change event because we've got one |
249 | 0 | // more selection add/remove. |
250 | 0 | if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) { |
251 | 0 | if (aThisEvent->mPackedEvent) { |
252 | 0 | aThisEvent->mPackedEvent->mEventType = |
253 | 0 | aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ? |
254 | 0 | nsIAccessibleEvent::EVENT_SELECTION_ADD : |
255 | 0 | nsIAccessibleEvent::EVENT_SELECTION_REMOVE; |
256 | 0 |
|
257 | 0 | aThisEvent->mPackedEvent->mEventRule = |
258 | 0 | AccEvent::eCoalesceSelectionChange; |
259 | 0 |
|
260 | 0 | aThisEvent->mPackedEvent = nullptr; |
261 | 0 | } |
262 | 0 |
|
263 | 0 | aThisEvent->mEventType = |
264 | 0 | aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ? |
265 | 0 | nsIAccessibleEvent::EVENT_SELECTION_ADD : |
266 | 0 | nsIAccessibleEvent::EVENT_SELECTION_REMOVE; |
267 | 0 |
|
268 | 0 | return; |
269 | 0 | } |
270 | 0 |
|
271 | 0 | // Convert into selection add since control has single selection but other |
272 | 0 | // selection events for this control are queued. |
273 | 0 | if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) |
274 | 0 | aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; |
275 | 0 | } |
276 | | |
277 | | //////////////////////////////////////////////////////////////////////////////// |
278 | | // EventQueue: event queue |
279 | | |
280 | | void |
281 | | EventQueue::ProcessEventQueue() |
282 | 0 | { |
283 | 0 | // Process only currently queued events. |
284 | 0 | nsTArray<RefPtr<AccEvent> > events; |
285 | 0 | events.SwapElements(mEvents); |
286 | 0 |
|
287 | 0 | uint32_t eventCount = events.Length(); |
288 | 0 | #ifdef A11Y_LOG |
289 | 0 | if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) { |
290 | 0 | logging::MsgBegin("EVENTS", "events processing"); |
291 | 0 | logging::Address("document", mDocument); |
292 | 0 | logging::MsgEnd(); |
293 | 0 | } |
294 | 0 | #endif |
295 | 0 |
|
296 | 0 | for (uint32_t idx = 0; idx < eventCount; idx++) { |
297 | 0 | AccEvent* event = events[idx]; |
298 | 0 | if (event->mEventRule != AccEvent::eDoNotEmit) { |
299 | 0 | Accessible* target = event->GetAccessible(); |
300 | 0 | if (!target || target->IsDefunct()) |
301 | 0 | continue; |
302 | 0 | |
303 | 0 | // Dispatch the focus event if target is still focused. |
304 | 0 | if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) { |
305 | 0 | FocusMgr()->ProcessFocusEvent(event); |
306 | 0 | continue; |
307 | 0 | } |
308 | 0 | |
309 | 0 | // Dispatch caret moved and text selection change events. |
310 | 0 | if (event->mEventType == nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) { |
311 | 0 | SelectionMgr()->ProcessTextSelChangeEvent(event); |
312 | 0 | continue; |
313 | 0 | } |
314 | 0 | |
315 | 0 | // Fire selected state change events in support to selection events. |
316 | 0 | if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) { |
317 | 0 | nsEventShell::FireEvent(event->mAccessible, states::SELECTED, |
318 | 0 | true, event->mIsFromUserInput); |
319 | 0 |
|
320 | 0 | } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) { |
321 | 0 | nsEventShell::FireEvent(event->mAccessible, states::SELECTED, |
322 | 0 | false, event->mIsFromUserInput); |
323 | 0 |
|
324 | 0 | } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) { |
325 | 0 | AccSelChangeEvent* selChangeEvent = downcast_accEvent(event); |
326 | 0 | nsEventShell::FireEvent(event->mAccessible, states::SELECTED, |
327 | 0 | (selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd), |
328 | 0 | event->mIsFromUserInput); |
329 | 0 |
|
330 | 0 | if (selChangeEvent->mPackedEvent) { |
331 | 0 | nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible, |
332 | 0 | states::SELECTED, |
333 | 0 | (selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd), |
334 | 0 | selChangeEvent->mPackedEvent->mIsFromUserInput); |
335 | 0 | } |
336 | 0 | } |
337 | 0 |
|
338 | 0 | nsEventShell::FireEvent(event); |
339 | 0 | } |
340 | 0 |
|
341 | 0 | if (!mDocument) |
342 | 0 | return; |
343 | 0 | } |
344 | 0 | } |