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/. */
6 #include "EventQueue.h"
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"
18 using namespace mozilla
;
19 using namespace mozilla::a11y
;
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;
25 ////////////////////////////////////////////////////////////////////////////////
27 ////////////////////////////////////////////////////////////////////////////////
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!");
34 if (!mEvents
.AppendElement(aEvent
)) return false;
39 if (aEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
40 (aEvent
->mEventType
== nsIAccessibleEvent::EVENT_NAME_CHANGE
||
41 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_REMOVED
||
42 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_INSERTED
)) {
43 PushNameChange(aEvent
->mAccessible
);
48 bool EventQueue::PushNameChange(Accessible
* aTarget
) {
49 // Fire name change event on parent given that this event hasn't been
50 // coalesced, the parent's name was calculated from its subtree, and the
51 // subtree was changed.
52 if (aTarget
->HasNameDependentParent()) {
53 // Only continue traversing up the tree if it's possible that the parent
54 // accessible's name can depend on this accessible's name.
55 Accessible
* parent
= aTarget
->Parent();
57 nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeIfReqRule
)) {
58 // Test possible name dependent parent.
59 if (nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeRule
)) {
61 ENameValueFlag nameFlag
= parent
->Name(name
);
62 // If name is obtained from subtree, fire name change event.
63 if (nameFlag
== eNameFromSubtree
) {
64 RefPtr
<AccEvent
> nameChangeEvent
=
65 new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
, parent
);
66 return PushEvent(nameChangeEvent
);
70 parent
= parent
->Parent();
76 ////////////////////////////////////////////////////////////////////////////////
77 // EventQueue: private
79 void EventQueue::CoalesceEvents() {
80 NS_ASSERTION(mEvents
.Length(), "There should be at least one pending event!");
81 uint32_t tail
= mEvents
.Length() - 1;
82 AccEvent
* tailEvent
= mEvents
[tail
];
84 switch (tailEvent
->mEventRule
) {
85 case AccEvent::eCoalesceReorder
: {
86 DebugOnly
<Accessible
*> target
= tailEvent
->mAccessible
.get();
88 target
->IsApplication() || target
->IsOuterDoc() ||
90 "Only app or outerdoc accessible reorder events are in the queue");
91 MOZ_ASSERT(tailEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
,
92 "only reorder events should be queued");
93 break; // case eCoalesceReorder
96 case AccEvent::eCoalesceOfSameType
: {
97 // Coalesce old events by newer event.
98 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
99 AccEvent
* accEvent
= mEvents
[index
];
100 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
101 accEvent
->mEventRule
== tailEvent
->mEventRule
) {
102 accEvent
->mEventRule
= AccEvent::eDoNotEmit
;
106 break; // case eCoalesceOfSameType
109 case AccEvent::eCoalesceSelectionChange
: {
110 AccSelChangeEvent
* tailSelChangeEvent
= downcast_accEvent(tailEvent
);
111 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
112 AccEvent
* thisEvent
= mEvents
[index
];
113 if (thisEvent
->mEventRule
== tailEvent
->mEventRule
) {
114 AccSelChangeEvent
* thisSelChangeEvent
= downcast_accEvent(thisEvent
);
116 // Coalesce selection change events within same control.
117 if (tailSelChangeEvent
->mWidget
== thisSelChangeEvent
->mWidget
) {
118 CoalesceSelChangeEvents(tailSelChangeEvent
, thisSelChangeEvent
,
124 break; // eCoalesceSelectionChange
127 case AccEvent::eCoalesceStateChange
: {
128 // If state change event is duped then ignore previous event. If state
129 // change event is opposite to previous event then no event is emitted
130 // (accessible state wasn't changed).
131 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
132 AccEvent
* thisEvent
= mEvents
[index
];
133 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
134 thisEvent
->mEventType
== tailEvent
->mEventType
&&
135 thisEvent
->mAccessible
== tailEvent
->mAccessible
) {
136 AccStateChangeEvent
* thisSCEvent
= downcast_accEvent(thisEvent
);
137 AccStateChangeEvent
* tailSCEvent
= downcast_accEvent(tailEvent
);
138 if (thisSCEvent
->mState
== tailSCEvent
->mState
) {
139 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
140 if (thisSCEvent
->mIsEnabled
!= tailSCEvent
->mIsEnabled
)
141 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
145 break; // eCoalesceStateChange
148 case AccEvent::eCoalesceTextSelChange
: {
149 // Coalesce older event by newer event for the same selection or target.
150 // Events for same selection may have different targets and vice versa one
151 // target may be pointed by different selections (for latter see
153 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
154 AccEvent
* thisEvent
= mEvents
[index
];
155 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
156 thisEvent
->mEventType
== tailEvent
->mEventType
) {
157 AccTextSelChangeEvent
* thisTSCEvent
= downcast_accEvent(thisEvent
);
158 AccTextSelChangeEvent
* tailTSCEvent
= downcast_accEvent(tailEvent
);
159 if (thisTSCEvent
->mSel
== tailTSCEvent
->mSel
||
160 thisEvent
->mAccessible
== tailEvent
->mAccessible
)
161 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
164 break; // eCoalesceTextSelChange
167 case AccEvent::eRemoveDupes
: {
168 // Check for repeat events, coalesce newly appended event by more older
170 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
171 AccEvent
* accEvent
= mEvents
[index
];
172 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
173 accEvent
->mEventRule
== tailEvent
->mEventRule
&&
174 accEvent
->mAccessible
== tailEvent
->mAccessible
) {
175 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
179 break; // case eRemoveDupes
183 break; // case eAllowDupes, eDoNotEmit
187 void EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent
* aTailEvent
,
188 AccSelChangeEvent
* aThisEvent
,
189 uint32_t aThisIndex
) {
190 aTailEvent
->mPreceedingCount
= aThisEvent
->mPreceedingCount
+ 1;
192 // Pack all preceding events into single selection within event
193 // when we receive too much selection add/remove events.
194 if (aTailEvent
->mPreceedingCount
>= kSelChangeCountToPack
) {
195 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
;
196 aTailEvent
->mAccessible
= aTailEvent
->mWidget
;
197 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
199 // Do not emit any preceding selection events for same widget if they
200 // weren't coalesced yet.
201 if (aThisEvent
->mEventType
!= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
) {
202 for (uint32_t jdx
= aThisIndex
- 1; jdx
< aThisIndex
; jdx
--) {
203 AccEvent
* prevEvent
= mEvents
[jdx
];
204 if (prevEvent
->mEventRule
== aTailEvent
->mEventRule
) {
205 AccSelChangeEvent
* prevSelChangeEvent
= downcast_accEvent(prevEvent
);
206 if (prevSelChangeEvent
->mWidget
== aTailEvent
->mWidget
)
207 prevSelChangeEvent
->mEventRule
= AccEvent::eDoNotEmit
;
214 // Pack sequential selection remove and selection add events into
215 // single selection change event.
216 if (aTailEvent
->mPreceedingCount
== 1 &&
217 aTailEvent
->mItem
!= aThisEvent
->mItem
) {
218 if (aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
219 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
220 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
221 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
222 aTailEvent
->mPackedEvent
= aThisEvent
;
226 if (aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
227 aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
228 aTailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
229 aThisEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
230 aThisEvent
->mPackedEvent
= aTailEvent
;
235 // Unpack the packed selection change event because we've got one
236 // more selection add/remove.
237 if (aThisEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
238 if (aThisEvent
->mPackedEvent
) {
239 aThisEvent
->mPackedEvent
->mEventType
=
240 aThisEvent
->mPackedEvent
->mSelChangeType
==
241 AccSelChangeEvent::eSelectionAdd
242 ? nsIAccessibleEvent::EVENT_SELECTION_ADD
243 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
245 aThisEvent
->mPackedEvent
->mEventRule
= AccEvent::eCoalesceSelectionChange
;
247 aThisEvent
->mPackedEvent
= nullptr;
250 aThisEvent
->mEventType
=
251 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
252 ? nsIAccessibleEvent::EVENT_SELECTION_ADD
253 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
258 // Convert into selection add since control has single selection but other
259 // selection events for this control are queued.
260 if (aTailEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
)
261 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_ADD
;
264 ////////////////////////////////////////////////////////////////////////////////
265 // EventQueue: event queue
267 void EventQueue::ProcessEventQueue() {
268 // Process only currently queued events.
269 nsTArray
<RefPtr
<AccEvent
> > events
;
270 events
.SwapElements(mEvents
);
272 uint32_t eventCount
= events
.Length();
274 if (eventCount
> 0 && logging::IsEnabled(logging::eEvents
)) {
275 logging::MsgBegin("EVENTS", "events processing");
276 logging::Address("document", mDocument
);
281 for (uint32_t idx
= 0; idx
< eventCount
; idx
++) {
282 AccEvent
* event
= events
[idx
];
283 if (event
->mEventRule
!= AccEvent::eDoNotEmit
) {
284 Accessible
* target
= event
->GetAccessible();
285 if (!target
|| target
->IsDefunct()) continue;
287 // Dispatch the focus event if target is still focused.
288 if (event
->mEventType
== nsIAccessibleEvent::EVENT_FOCUS
) {
289 FocusMgr()->ProcessFocusEvent(event
);
293 // Dispatch caret moved and text selection change events.
294 if (event
->mEventType
==
295 nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
) {
296 SelectionMgr()->ProcessTextSelChangeEvent(event
);
300 // Fire selected state change events in support to selection events.
301 if (event
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION_ADD
) {
302 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
, true,
303 event
->mIsFromUserInput
);
305 } else if (event
->mEventType
==
306 nsIAccessibleEvent::EVENT_SELECTION_REMOVE
) {
307 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
, false,
308 event
->mIsFromUserInput
);
310 } else if (event
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
311 AccSelChangeEvent
* selChangeEvent
= downcast_accEvent(event
);
312 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
,
313 (selChangeEvent
->mSelChangeType
==
314 AccSelChangeEvent::eSelectionAdd
),
315 event
->mIsFromUserInput
);
317 if (selChangeEvent
->mPackedEvent
) {
318 nsEventShell::FireEvent(
319 selChangeEvent
->mPackedEvent
->mAccessible
, states::SELECTED
,
320 (selChangeEvent
->mPackedEvent
->mSelChangeType
==
321 AccSelChangeEvent::eSelectionAdd
),
322 selChangeEvent
->mPackedEvent
->mIsFromUserInput
);
326 nsEventShell::FireEvent(event
);
329 if (!mDocument
) return;