Line data Source code
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 0 : EventQueue::PushEvent(AccEvent* aEvent)
31 : {
32 0 : NS_ASSERTION((aEvent->mAccessible && aEvent->mAccessible->IsApplication()) ||
33 : aEvent->Document() == mDocument,
34 : "Queued event belongs to another document!");
35 :
36 0 : if (!mEvents.AppendElement(aEvent))
37 0 : return false;
38 :
39 : // Filter events.
40 0 : CoalesceEvents();
41 :
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 : }
48 0 : return true;
49 : }
50 :
51 : bool
52 0 : EventQueue::PushNameChange(Accessible* aTarget)
53 : {
54 : // Fire name change event on parent given that this event hasn't been
55 : // coalesced, the parent's name was calculated from its subtree, and the
56 : // subtree was changed.
57 0 : if (aTarget->HasNameDependentParent()) {
58 : // Only continue traversing up the tree if it's possible that the parent
59 : // 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 : // Test possible name dependent parent.
64 0 : if (nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
65 0 : nsAutoString name;
66 0 : ENameValueFlag nameFlag = parent->Name(name);
67 : // If name is obtained from subtree, fire name change event.
68 0 : if (nameFlag == eNameFromSubtree) {
69 : RefPtr<AccEvent> nameChangeEvent =
70 0 : new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
71 0 : return PushEvent(nameChangeEvent);
72 : }
73 0 : break;
74 : }
75 0 : parent = parent->Parent();
76 : }
77 : }
78 0 : return false;
79 : }
80 :
81 : ////////////////////////////////////////////////////////////////////////////////
82 : // EventQueue: private
83 :
84 : void
85 0 : EventQueue::CoalesceEvents()
86 : {
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 :
91 0 : switch(tailEvent->mEventRule) {
92 : case AccEvent::eCoalesceReorder:
93 : {
94 0 : DebugOnly<Accessible*> target = tailEvent->mAccessible.get();
95 0 : MOZ_ASSERT(target->IsApplication() ||
96 : target->IsOuterDoc() ||
97 : target->IsXULTree(),
98 : "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 : }
102 :
103 : case AccEvent::eCoalesceOfSameType:
104 : {
105 : // 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 : }
113 : }
114 0 : } break; // case eCoalesceOfSameType
115 :
116 : case AccEvent::eCoalesceSelectionChange:
117 : {
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 : AccSelChangeEvent* thisSelChangeEvent =
123 0 : downcast_accEvent(thisEvent);
124 :
125 : // Coalesce selection change events within same control.
126 0 : if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
127 0 : CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
128 0 : return;
129 : }
130 : }
131 : }
132 :
133 0 : } break; // eCoalesceSelectionChange
134 :
135 : case AccEvent::eCoalesceStateChange:
136 : {
137 : // If state change event is duped then ignore previous event. If state
138 : // change event is opposite to previous event then no event is emitted
139 : // (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 : }
152 : }
153 : }
154 0 : break; // eCoalesceStateChange
155 : }
156 :
157 : case AccEvent::eCoalesceTextSelChange:
158 : {
159 : // Coalesce older event by newer event for the same selection or target.
160 : // Events for same selection may have different targets and vice versa one
161 : // target may be pointed by different selections (for latter see
162 : // 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 : }
173 :
174 : }
175 0 : } break; // eCoalesceTextSelChange
176 :
177 : case AccEvent::eRemoveDupes:
178 : {
179 : // Check for repeat events, coalesce newly appended event by more older
180 : // 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 : }
189 : }
190 0 : } break; // case eRemoveDupes
191 :
192 : default:
193 0 : break; // case eAllowDupes, eDoNotEmit
194 : } // switch
195 : }
196 :
197 : void
198 0 : EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
199 : AccSelChangeEvent* aThisEvent,
200 : uint32_t aThisIndex)
201 : {
202 0 : aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
203 :
204 : // Pack all preceding events into single selection within event
205 : // 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 :
211 : // Do not emit any preceding selection events for same widget if they
212 : // 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 : AccSelChangeEvent* prevSelChangeEvent =
218 0 : downcast_accEvent(prevEvent);
219 0 : if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
220 0 : prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
221 : }
222 : }
223 : }
224 0 : return;
225 : }
226 :
227 : // Pack sequential selection remove and selection add events into
228 : // 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 : }
238 :
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 : }
246 : }
247 :
248 : // Unpack the packed selection change event because we've got one
249 : // 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 : nsIAccessibleEvent::EVENT_SELECTION_ADD :
255 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
256 :
257 0 : aThisEvent->mPackedEvent->mEventRule =
258 : AccEvent::eCoalesceSelectionChange;
259 :
260 0 : aThisEvent->mPackedEvent = nullptr;
261 : }
262 :
263 0 : aThisEvent->mEventType =
264 0 : aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
265 : nsIAccessibleEvent::EVENT_SELECTION_ADD :
266 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
267 :
268 0 : return;
269 : }
270 :
271 : // Convert into selection add since control has single selection but other
272 : // selection events for this control are queued.
273 0 : if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
274 0 : aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
275 : }
276 :
277 : ////////////////////////////////////////////////////////////////////////////////
278 : // EventQueue: event queue
279 :
280 : void
281 0 : EventQueue::ProcessEventQueue()
282 : {
283 : // Process only currently queued events.
284 0 : nsTArray<RefPtr<AccEvent> > events;
285 0 : events.SwapElements(mEvents);
286 :
287 0 : uint32_t eventCount = events.Length();
288 : #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 : }
294 : #endif
295 :
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 :
303 : // 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 : }
308 :
309 : // 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 : }
314 :
315 : // 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 :
320 0 : } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) {
321 0 : nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
322 0 : false, event->mIsFromUserInput);
323 :
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 :
330 0 : if (selChangeEvent->mPackedEvent) {
331 0 : nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible,
332 : states::SELECTED,
333 0 : (selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
334 0 : selChangeEvent->mPackedEvent->mIsFromUserInput);
335 : }
336 : }
337 :
338 0 : nsEventShell::FireEvent(event);
339 : }
340 :
341 0 : if (!mDocument)
342 0 : return;
343 : }
344 : }
|