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 "LocalAccessible-inl.h"
9 #include "nsEventShell.h"
10 #include "DocAccessible.h"
11 #include "DocAccessibleChild.h"
12 #include "nsAccessibilityService.h"
13 #include "nsTextEquivUtils.h"
22 // Defines the number of selection add/remove events in the queue when they
23 // aren't packed into single selection within event.
24 const unsigned int kSelChangeCountToPack
= 5;
26 ////////////////////////////////////////////////////////////////////////////////
28 ////////////////////////////////////////////////////////////////////////////////
30 bool EventQueue::PushEvent(AccEvent
* aEvent
) {
31 NS_ASSERTION((aEvent
->mAccessible
&& aEvent
->mAccessible
->IsApplication()) ||
32 aEvent
->Document() == mDocument
,
33 "Queued event belongs to another document!");
35 // XXX(Bug 1631371) Check if this should use a fallible operation as it
36 // pretended earlier, or change the return type to void.
37 mEvents
.AppendElement(aEvent
);
42 if (aEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
43 (aEvent
->mEventType
== nsIAccessibleEvent::EVENT_NAME_CHANGE
||
44 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_REMOVED
||
45 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_INSERTED
)) {
46 PushNameOrDescriptionChange(aEvent
->mAccessible
);
51 bool EventQueue::PushNameOrDescriptionChange(LocalAccessible
* aTarget
) {
52 // Fire name/description change event on parent or related LocalAccessible
53 // being labelled/described given that this event hasn't been coalesced, the
54 // dependent's name/description was calculated from this subtree, and the
55 // subtree was changed.
56 const bool doName
= aTarget
->HasNameDependent();
57 const bool doDesc
= aTarget
->HasDescriptionDependent();
58 if (!doName
&& !doDesc
) {
62 bool nameCheckAncestor
= true;
63 // Only continue traversing up the tree if it's possible that the parent
64 // LocalAccessible's name (or a LocalAccessible being labelled by this
65 // LocalAccessible or an ancestor) can depend on this LocalAccessible's name.
66 LocalAccessible
* parent
= aTarget
;
68 // Test possible name dependent parent.
70 if (nameCheckAncestor
&& parent
!= aTarget
&&
71 nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeRule
)) {
73 ENameValueFlag nameFlag
= parent
->Name(name
);
74 // If name is obtained from subtree, fire name change event.
75 if (nameFlag
== eNameFromSubtree
) {
76 RefPtr
<AccEvent
> nameChangeEvent
=
77 new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
, parent
);
78 pushed
|= PushEvent(nameChangeEvent
);
80 nameCheckAncestor
= false;
83 Relation rel
= parent
->RelationByType(RelationType::LABEL_FOR
);
84 while (LocalAccessible
* relTarget
= rel
.Next()) {
85 RefPtr
<AccEvent
> nameChangeEvent
=
86 new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
, relTarget
);
87 pushed
|= PushEvent(nameChangeEvent
);
92 Relation rel
= parent
->RelationByType(RelationType::DESCRIPTION_FOR
);
93 while (LocalAccessible
* relTarget
= rel
.Next()) {
94 RefPtr
<AccEvent
> descChangeEvent
= new AccEvent(
95 nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE
, relTarget
);
96 pushed
|= PushEvent(descChangeEvent
);
100 parent
= parent
->LocalParent();
102 nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeIfReqRule
));
107 ////////////////////////////////////////////////////////////////////////////////
108 // EventQueue: private
110 void EventQueue::CoalesceEvents() {
111 NS_ASSERTION(mEvents
.Length(), "There should be at least one pending event!");
112 uint32_t tail
= mEvents
.Length() - 1;
113 AccEvent
* tailEvent
= mEvents
[tail
];
115 switch (tailEvent
->mEventRule
) {
116 case AccEvent::eCoalesceReorder
: {
117 DebugOnly
<LocalAccessible
*> target
= tailEvent
->mAccessible
.get();
119 target
->IsApplication() || target
->IsOuterDoc() ||
121 "Only app or outerdoc accessible reorder events are in the queue");
122 MOZ_ASSERT(tailEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
,
123 "only reorder events should be queued");
124 break; // case eCoalesceReorder
127 case AccEvent::eCoalesceOfSameType
: {
128 // Coalesce old events by newer event.
129 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
130 AccEvent
* accEvent
= mEvents
[index
];
131 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
132 accEvent
->mEventRule
== tailEvent
->mEventRule
) {
133 accEvent
->mEventRule
= AccEvent::eDoNotEmit
;
137 break; // case eCoalesceOfSameType
140 case AccEvent::eCoalesceSelectionChange
: {
141 AccSelChangeEvent
* tailSelChangeEvent
= downcast_accEvent(tailEvent
);
142 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
143 AccEvent
* thisEvent
= mEvents
[index
];
144 if (thisEvent
->mEventRule
== tailEvent
->mEventRule
) {
145 AccSelChangeEvent
* thisSelChangeEvent
= downcast_accEvent(thisEvent
);
147 // Coalesce selection change events within same control.
148 if (tailSelChangeEvent
->mWidget
== thisSelChangeEvent
->mWidget
) {
149 CoalesceSelChangeEvents(tailSelChangeEvent
, thisSelChangeEvent
,
155 break; // eCoalesceSelectionChange
158 case AccEvent::eCoalesceStateChange
: {
159 // If state change event is duped then ignore previous event. If state
160 // change event is opposite to previous event then no event is emitted
161 // (accessible state wasn't changed).
162 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
163 AccEvent
* thisEvent
= mEvents
[index
];
164 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
165 thisEvent
->mEventType
== tailEvent
->mEventType
&&
166 thisEvent
->mAccessible
== tailEvent
->mAccessible
) {
167 AccStateChangeEvent
* thisSCEvent
= downcast_accEvent(thisEvent
);
168 AccStateChangeEvent
* tailSCEvent
= downcast_accEvent(tailEvent
);
169 if (thisSCEvent
->mState
== tailSCEvent
->mState
) {
170 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
171 if (thisSCEvent
->mIsEnabled
!= tailSCEvent
->mIsEnabled
) {
172 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
177 break; // eCoalesceStateChange
180 case AccEvent::eCoalesceTextSelChange
: {
181 // Coalesce older event by newer event for the same selection or target.
182 // Events for same selection may have different targets and vice versa one
183 // target may be pointed by different selections (for latter see
185 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
186 AccEvent
* thisEvent
= mEvents
[index
];
187 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
188 thisEvent
->mEventType
== tailEvent
->mEventType
) {
189 AccTextSelChangeEvent
* thisTSCEvent
= downcast_accEvent(thisEvent
);
190 AccTextSelChangeEvent
* tailTSCEvent
= downcast_accEvent(tailEvent
);
191 if (thisTSCEvent
->mSel
== tailTSCEvent
->mSel
||
192 thisEvent
->mAccessible
== tailEvent
->mAccessible
) {
193 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
197 break; // eCoalesceTextSelChange
200 case AccEvent::eRemoveDupes
: {
201 // Check for repeat events, coalesce newly appended event by more older
203 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
204 AccEvent
* accEvent
= mEvents
[index
];
205 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
206 accEvent
->mEventRule
== tailEvent
->mEventRule
&&
207 accEvent
->mAccessible
== tailEvent
->mAccessible
) {
208 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
212 break; // case eRemoveDupes
216 break; // case eAllowDupes, eDoNotEmit
220 void EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent
* aTailEvent
,
221 AccSelChangeEvent
* aThisEvent
,
222 uint32_t aThisIndex
) {
223 aTailEvent
->mPreceedingCount
= aThisEvent
->mPreceedingCount
+ 1;
225 // Pack all preceding events into single selection within event
226 // when we receive too much selection add/remove events.
227 if (aTailEvent
->mPreceedingCount
>= kSelChangeCountToPack
) {
228 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
;
229 aTailEvent
->mAccessible
= aTailEvent
->mWidget
;
230 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
232 // Do not emit any preceding selection events for same widget if they
233 // weren't coalesced yet.
234 if (aThisEvent
->mEventType
!= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
) {
235 for (uint32_t jdx
= aThisIndex
- 1; jdx
< aThisIndex
; jdx
--) {
236 AccEvent
* prevEvent
= mEvents
[jdx
];
237 if (prevEvent
->mEventRule
== aTailEvent
->mEventRule
) {
238 AccSelChangeEvent
* prevSelChangeEvent
= downcast_accEvent(prevEvent
);
239 if (prevSelChangeEvent
->mWidget
== aTailEvent
->mWidget
) {
240 prevSelChangeEvent
->mEventRule
= AccEvent::eDoNotEmit
;
248 // Pack sequential selection remove and selection add events into
249 // single selection change event.
250 if (aTailEvent
->mPreceedingCount
== 1 &&
251 aTailEvent
->mItem
!= aThisEvent
->mItem
) {
252 if (aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
253 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
254 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
255 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
256 aTailEvent
->mPackedEvent
= aThisEvent
;
260 if (aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
261 aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
262 aTailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
263 aThisEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
264 aThisEvent
->mPackedEvent
= aTailEvent
;
269 // Unpack the packed selection change event because we've got one
270 // more selection add/remove.
271 if (aThisEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
272 if (aThisEvent
->mPackedEvent
) {
273 aThisEvent
->mPackedEvent
->mEventType
=
274 aThisEvent
->mPackedEvent
->mSelChangeType
==
275 AccSelChangeEvent::eSelectionAdd
276 ? nsIAccessibleEvent::EVENT_SELECTION_ADD
277 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
279 aThisEvent
->mPackedEvent
->mEventRule
= AccEvent::eCoalesceSelectionChange
;
281 aThisEvent
->mPackedEvent
= nullptr;
284 aThisEvent
->mEventType
=
285 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
286 ? nsIAccessibleEvent::EVENT_SELECTION_ADD
287 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
292 // Convert into selection add since control has single selection but other
293 // selection events for this control are queued.
294 if (aTailEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
295 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_ADD
;
299 ////////////////////////////////////////////////////////////////////////////////
300 // EventQueue: event queue
302 void EventQueue::ProcessEventQueue() {
303 // Process only currently queued events.
304 const nsTArray
<RefPtr
<AccEvent
> > events
= std::move(mEvents
);
306 uint32_t eventCount
= events
.Length();
308 if (eventCount
> 0 && logging::IsEnabled(logging::eEvents
)) {
309 logging::MsgBegin("EVENTS", "events processing");
310 logging::Address("document", mDocument
);
315 for (uint32_t idx
= 0; idx
< eventCount
; idx
++) {
316 AccEvent
* event
= events
[idx
];
317 if (event
->mEventRule
!= AccEvent::eDoNotEmit
) {
318 LocalAccessible
* target
= event
->GetAccessible();
319 if (!target
|| target
->IsDefunct()) continue;
321 // Dispatch the focus event if target is still focused.
322 if (event
->mEventType
== nsIAccessibleEvent::EVENT_FOCUS
) {
323 FocusMgr()->ProcessFocusEvent(event
);
327 // Dispatch caret moved and text selection change events.
328 if (event
->mEventType
==
329 nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
) {
330 SelectionMgr()->ProcessTextSelChangeEvent(event
);
334 // Fire selected state change events in support to selection events.
335 if (event
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION_ADD
) {
336 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
, true,
337 event
->mIsFromUserInput
);
339 } else if (event
->mEventType
==
340 nsIAccessibleEvent::EVENT_SELECTION_REMOVE
) {
341 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
, false,
342 event
->mIsFromUserInput
);
344 } else if (event
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
345 AccSelChangeEvent
* selChangeEvent
= downcast_accEvent(event
);
346 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
,
347 (selChangeEvent
->mSelChangeType
==
348 AccSelChangeEvent::eSelectionAdd
),
349 event
->mIsFromUserInput
);
351 if (selChangeEvent
->mPackedEvent
) {
352 nsEventShell::FireEvent(
353 selChangeEvent
->mPackedEvent
->mAccessible
, states::SELECTED
,
354 (selChangeEvent
->mPackedEvent
->mSelChangeType
==
355 AccSelChangeEvent::eSelectionAdd
),
356 selChangeEvent
->mPackedEvent
->mIsFromUserInput
);
360 nsEventShell::FireEvent(event
);
363 if (!mDocument
) return;
368 } // namespace mozilla