Bug 1733869 [wpt PR 30916] - Add a note about counterintuitive send_keys() code point...
[gecko.git] / accessible / base / EventQueue.cpp
blobd62d8ecd351137ebe0bc2acc1683f4d2fe8ddf80
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"
14 #ifdef A11Y_LOG
15 # include "Logging.h"
16 #endif
17 #include "Relation.h"
19 namespace mozilla {
20 namespace a11y {
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 ////////////////////////////////////////////////////////////////////////////////
27 // EventQueue
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);
39 // Filter events.
40 CoalesceEvents();
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);
48 return true;
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) {
59 return false;
61 bool pushed = false;
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;
67 do {
68 // Test possible name dependent parent.
69 if (doName) {
70 if (nameCheckAncestor && parent != aTarget &&
71 nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
72 nsAutoString name;
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);
91 if (doDesc) {
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();
101 } while (parent &&
102 nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule));
104 return pushed;
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();
118 MOZ_ASSERT(
119 target->IsApplication() || target->IsOuterDoc() ||
120 target->IsXULTree(),
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;
134 return;
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,
150 index);
151 return;
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
184 // bug 927159).
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
202 // event.
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;
209 return;
212 break; // case eRemoveDupes
215 default:
216 break; // case eAllowDupes, eDoNotEmit
217 } // switch
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;
245 return;
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;
257 return;
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;
265 return;
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;
289 return;
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();
307 #ifdef A11Y_LOG
308 if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
309 logging::MsgBegin("EVENTS", "events processing");
310 logging::Address("document", mDocument);
311 logging::MsgEnd();
313 #endif
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);
324 continue;
327 // Dispatch caret moved and text selection change events.
328 if (event->mEventType ==
329 nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) {
330 SelectionMgr()->ProcessTextSelChangeEvent(event);
331 continue;
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;
367 } // namespace a11y
368 } // namespace mozilla