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