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 "DocAccessibleChild.h"
11 #include "nsTextEquivUtils.h"
20 // Defines the number of selection add/remove events in the queue when they
21 // aren't packed into single selection within event.
22 const unsigned int kSelChangeCountToPack
= 5;
24 ////////////////////////////////////////////////////////////////////////////////
26 ////////////////////////////////////////////////////////////////////////////////
28 bool EventQueue::PushEvent(AccEvent
* aEvent
) {
29 NS_ASSERTION((aEvent
->mAccessible
&& aEvent
->mAccessible
->IsApplication()) ||
30 aEvent
->Document() == mDocument
,
31 "Queued event belongs to another document!");
33 if (aEvent
->mEventType
== nsIAccessibleEvent::EVENT_FOCUS
) {
38 // XXX(Bug 1631371) Check if this should use a fallible operation as it
39 // pretended earlier, or change the return type to void.
40 mEvents
.AppendElement(aEvent
);
45 if (aEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
46 (aEvent
->mEventType
== nsIAccessibleEvent::EVENT_NAME_CHANGE
||
47 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_REMOVED
||
48 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_INSERTED
)) {
49 PushNameOrDescriptionChange(aEvent
);
54 bool EventQueue::PushNameOrDescriptionChange(AccEvent
* aOrigEvent
) {
55 // Fire name/description change event on parent or related LocalAccessible
56 // being labelled/described given that this event hasn't been coalesced, the
57 // dependent's name/description was calculated from this subtree, and the
58 // subtree was changed.
59 LocalAccessible
* target
= aOrigEvent
->mAccessible
;
60 // If the text of a text leaf changed without replacing the leaf, the only
61 // event we get is text inserted on the container. In this case, we might
62 // need to fire a name change event on the target itself.
63 const bool maybeTargetNameChanged
=
64 (aOrigEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_REMOVED
||
65 aOrigEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_INSERTED
) &&
66 nsTextEquivUtils::HasNameRule(target
, eNameFromSubtreeRule
);
67 const bool doName
= target
->HasNameDependent() || maybeTargetNameChanged
;
68 const bool doDesc
= target
->HasDescriptionDependent();
69 if (!doName
&& !doDesc
) {
73 bool nameCheckAncestor
= true;
74 // Only continue traversing up the tree if it's possible that the parent
75 // LocalAccessible's name (or a LocalAccessible being labelled by this
76 // LocalAccessible or an ancestor) can depend on this LocalAccessible's name.
77 LocalAccessible
* parent
= target
;
79 // Test possible name dependent parent.
81 if (nameCheckAncestor
&& (maybeTargetNameChanged
|| parent
!= target
) &&
82 nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeRule
)) {
84 ENameValueFlag nameFlag
= parent
->Name(name
);
85 // If name is obtained from subtree, fire name change event.
86 // HTML file inputs always get part of their name from the subtree, even
87 // if the author provided a name.
88 if (nameFlag
== eNameFromSubtree
|| parent
->IsHTMLFileInput()) {
89 RefPtr
<AccEvent
> nameChangeEvent
=
90 new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
, parent
);
91 pushed
|= PushEvent(nameChangeEvent
);
93 nameCheckAncestor
= false;
96 Relation rel
= parent
->RelationByType(RelationType::LABEL_FOR
);
97 while (LocalAccessible
* relTarget
= rel
.LocalNext()) {
98 RefPtr
<AccEvent
> nameChangeEvent
=
99 new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
, relTarget
);
100 pushed
|= PushEvent(nameChangeEvent
);
105 Relation rel
= parent
->RelationByType(RelationType::DESCRIPTION_FOR
);
106 while (LocalAccessible
* relTarget
= rel
.LocalNext()) {
107 RefPtr
<AccEvent
> descChangeEvent
= new AccEvent(
108 nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE
, relTarget
);
109 pushed
|= PushEvent(descChangeEvent
);
113 if (parent
->IsDoc()) {
114 // Never cross document boundaries.
117 parent
= parent
->LocalParent();
119 nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeIfReqRule
));
124 ////////////////////////////////////////////////////////////////////////////////
125 // EventQueue: private
127 void EventQueue::CoalesceEvents() {
128 NS_ASSERTION(mEvents
.Length(), "There should be at least one pending event!");
129 uint32_t tail
= mEvents
.Length() - 1;
130 AccEvent
* tailEvent
= mEvents
[tail
];
132 switch (tailEvent
->mEventRule
) {
133 case AccEvent::eCoalesceReorder
: {
134 DebugOnly
<LocalAccessible
*> target
= tailEvent
->mAccessible
.get();
136 target
->IsApplication() || target
->IsOuterDoc() ||
138 "Only app or outerdoc accessible reorder events are in the queue");
139 MOZ_ASSERT(tailEvent
->GetEventType() == nsIAccessibleEvent::EVENT_REORDER
,
140 "only reorder events should be queued");
141 break; // case eCoalesceReorder
144 case AccEvent::eCoalesceOfSameType
: {
145 // Coalesce old events by newer event.
146 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
147 AccEvent
* accEvent
= mEvents
[index
];
148 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
149 accEvent
->mEventRule
== tailEvent
->mEventRule
) {
150 accEvent
->mEventRule
= AccEvent::eDoNotEmit
;
154 break; // case eCoalesceOfSameType
157 case AccEvent::eCoalesceSelectionChange
: {
158 AccSelChangeEvent
* tailSelChangeEvent
= downcast_accEvent(tailEvent
);
159 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
160 AccEvent
* thisEvent
= mEvents
[index
];
161 if (thisEvent
->mEventRule
== tailEvent
->mEventRule
) {
162 AccSelChangeEvent
* thisSelChangeEvent
= downcast_accEvent(thisEvent
);
164 // Coalesce selection change events within same control.
165 if (tailSelChangeEvent
->mWidget
== thisSelChangeEvent
->mWidget
) {
166 CoalesceSelChangeEvents(tailSelChangeEvent
, thisSelChangeEvent
,
172 break; // eCoalesceSelectionChange
175 case AccEvent::eCoalesceStateChange
: {
176 // If state change event is duped then ignore previous event. If state
177 // change event is opposite to previous event then no event is emitted
178 // (accessible state wasn't changed).
179 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
180 AccEvent
* thisEvent
= mEvents
[index
];
181 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
182 thisEvent
->mEventType
== tailEvent
->mEventType
&&
183 thisEvent
->mAccessible
== tailEvent
->mAccessible
) {
184 AccStateChangeEvent
* thisSCEvent
= downcast_accEvent(thisEvent
);
185 AccStateChangeEvent
* tailSCEvent
= downcast_accEvent(tailEvent
);
186 if (thisSCEvent
->mState
== tailSCEvent
->mState
) {
187 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
188 if (thisSCEvent
->mIsEnabled
!= tailSCEvent
->mIsEnabled
) {
189 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
194 break; // eCoalesceStateChange
197 case AccEvent::eCoalesceTextSelChange
: {
198 // Coalesce older event by newer event for the same selection or target.
199 // Events for same selection may have different targets and vice versa one
200 // target may be pointed by different selections (for latter see
202 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
203 AccEvent
* thisEvent
= mEvents
[index
];
204 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
205 thisEvent
->mEventType
== tailEvent
->mEventType
) {
206 AccTextSelChangeEvent
* thisTSCEvent
= downcast_accEvent(thisEvent
);
207 AccTextSelChangeEvent
* tailTSCEvent
= downcast_accEvent(tailEvent
);
208 if (thisTSCEvent
->mSel
== tailTSCEvent
->mSel
||
209 thisEvent
->mAccessible
== tailEvent
->mAccessible
) {
210 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
214 break; // eCoalesceTextSelChange
217 case AccEvent::eRemoveDupes
: {
218 // Check for repeat events, coalesce newly appended event by more older
220 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
221 AccEvent
* accEvent
= mEvents
[index
];
222 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
223 accEvent
->mEventRule
== tailEvent
->mEventRule
&&
224 accEvent
->mAccessible
== tailEvent
->mAccessible
) {
225 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
229 break; // case eRemoveDupes
233 break; // case eAllowDupes, eDoNotEmit
237 void EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent
* aTailEvent
,
238 AccSelChangeEvent
* aThisEvent
,
239 uint32_t aThisIndex
) {
240 aTailEvent
->mPreceedingCount
= aThisEvent
->mPreceedingCount
+ 1;
242 // Pack all preceding events into single selection within event
243 // when we receive too much selection add/remove events.
244 if (aTailEvent
->mPreceedingCount
>= kSelChangeCountToPack
) {
245 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
;
246 aTailEvent
->mAccessible
= aTailEvent
->mWidget
;
247 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
249 // Do not emit any preceding selection events for same widget if they
250 // weren't coalesced yet.
251 if (aThisEvent
->mEventType
!= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
) {
252 for (uint32_t jdx
= aThisIndex
- 1; jdx
< aThisIndex
; jdx
--) {
253 AccEvent
* prevEvent
= mEvents
[jdx
];
254 if (prevEvent
->mEventRule
== aTailEvent
->mEventRule
) {
255 AccSelChangeEvent
* prevSelChangeEvent
= downcast_accEvent(prevEvent
);
256 if (prevSelChangeEvent
->mWidget
== aTailEvent
->mWidget
) {
257 prevSelChangeEvent
->mEventRule
= AccEvent::eDoNotEmit
;
265 // Pack sequential selection remove and selection add events into
266 // single selection change event.
267 if (aTailEvent
->mPreceedingCount
== 1 &&
268 aTailEvent
->mItem
!= aThisEvent
->mItem
) {
269 if (aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
270 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
271 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
272 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
273 aTailEvent
->mPackedEvent
= aThisEvent
;
277 if (aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
278 aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
279 aTailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
280 aThisEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
281 aThisEvent
->mPackedEvent
= aTailEvent
;
286 // Unpack the packed selection change event because we've got one
287 // more selection add/remove.
288 if (aThisEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
289 if (aThisEvent
->mPackedEvent
) {
290 aThisEvent
->mPackedEvent
->mEventType
=
291 aThisEvent
->mPackedEvent
->mSelChangeType
==
292 AccSelChangeEvent::eSelectionAdd
293 ? nsIAccessibleEvent::EVENT_SELECTION_ADD
294 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
296 aThisEvent
->mPackedEvent
->mEventRule
= AccEvent::eCoalesceSelectionChange
;
298 aThisEvent
->mPackedEvent
= nullptr;
301 aThisEvent
->mEventType
=
302 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
303 ? nsIAccessibleEvent::EVENT_SELECTION_ADD
304 : nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
309 // Convert into selection add since control has single selection but other
310 // selection events for this control are queued.
311 if (aTailEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
312 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_ADD
;
316 ////////////////////////////////////////////////////////////////////////////////
317 // EventQueue: event queue
319 void EventQueue::ProcessEventQueue() {
320 // Process only currently queued events.
321 const nsTArray
<RefPtr
<AccEvent
> > events
= std::move(mEvents
);
322 nsTArray
<uint64_t> selectedIDs
;
323 nsTArray
<uint64_t> unselectedIDs
;
325 uint32_t eventCount
= events
.Length();
327 if ((eventCount
> 0 || mFocusEvent
) && logging::IsEnabled(logging::eEvents
)) {
328 logging::MsgBegin("EVENTS", "events processing");
329 logging::Address("document", mDocument
);
335 // Always fire a pending focus event before all other events. We do this for
337 // 1. It prevents extraneous screen reader speech if the name, states, etc.
338 // of the currently focused item change at the same time as another item is
339 // focused. If aria-activedescendant is used, even setting
340 // aria-activedescendant before changing other properties results in the
341 // property change events being queued before the focus event because we
342 // process aria-activedescendant async.
343 // 2. It improves perceived performance. Focus changes should be reported as
344 // soon as possible, so clients should handle focus events before they spend
345 // time on anything else.
346 RefPtr
<AccEvent
> event
= std::move(mFocusEvent
);
347 if (!event
->mAccessible
->IsDefunct()) {
348 FocusMgr()->ProcessFocusEvent(event
);
352 for (uint32_t idx
= 0; idx
< eventCount
; idx
++) {
353 AccEvent
* event
= events
[idx
];
354 uint32_t eventType
= event
->mEventType
;
355 LocalAccessible
* target
= event
->GetAccessible();
356 if (!target
|| target
->IsDefunct()) {
360 // Collect select changes
361 if (IPCAccessibilityActive()) {
362 if ((event
->mEventRule
== AccEvent::eDoNotEmit
&&
363 (eventType
== nsIAccessibleEvent::EVENT_SELECTION_ADD
||
364 eventType
== nsIAccessibleEvent::EVENT_SELECTION_REMOVE
||
365 eventType
== nsIAccessibleEvent::EVENT_SELECTION
)) ||
366 eventType
== nsIAccessibleEvent::EVENT_SELECTION_WITHIN
) {
367 // The selection even was either dropped or morphed to a
368 // selection-within. We need to collect the items from all these events
369 // and manually push their new state to the parent process.
370 AccSelChangeEvent
* selChangeEvent
= downcast_accEvent(event
);
371 LocalAccessible
* item
= selChangeEvent
->mItem
;
372 if (!item
->IsDefunct()) {
374 item
->IsDoc() ? 0 : reinterpret_cast<uint64_t>(item
->UniqueID());
375 bool selected
= selChangeEvent
->mSelChangeType
==
376 AccSelChangeEvent::eSelectionAdd
;
378 selectedIDs
.AppendElement(itemID
);
380 unselectedIDs
.AppendElement(itemID
);
386 if (event
->mEventRule
== AccEvent::eDoNotEmit
) {
390 // Dispatch caret moved and text selection change events.
391 if (eventType
== nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
) {
392 SelectionMgr()->ProcessTextSelChangeEvent(event
);
396 // Fire selected state change events in support to selection events.
397 if (eventType
== nsIAccessibleEvent::EVENT_SELECTION_ADD
) {
398 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
, true,
399 event
->mIsFromUserInput
);
401 } else if (eventType
== nsIAccessibleEvent::EVENT_SELECTION_REMOVE
) {
402 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
, false,
403 event
->mIsFromUserInput
);
405 } else if (eventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
406 AccSelChangeEvent
* selChangeEvent
= downcast_accEvent(event
);
407 nsEventShell::FireEvent(
408 event
->mAccessible
, states::SELECTED
,
409 (selChangeEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
),
410 event
->mIsFromUserInput
);
412 if (selChangeEvent
->mPackedEvent
) {
413 nsEventShell::FireEvent(selChangeEvent
->mPackedEvent
->mAccessible
,
415 (selChangeEvent
->mPackedEvent
->mSelChangeType
==
416 AccSelChangeEvent::eSelectionAdd
),
417 selChangeEvent
->mPackedEvent
->mIsFromUserInput
);
421 nsEventShell::FireEvent(event
);
428 if (mDocument
&& IPCAccessibilityActive() &&
429 (!selectedIDs
.IsEmpty() || !unselectedIDs
.IsEmpty())) {
430 DocAccessibleChild
* ipcDoc
= mDocument
->IPCDoc();
431 ipcDoc
->SendSelectedAccessiblesChanged(selectedIDs
, unselectedIDs
);
436 } // namespace mozilla