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