no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / accessible / base / EventQueue.cpp
blob8a5e22cd480d5f661f58d906c9da036f16aacd31
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"
12 #ifdef A11Y_LOG
13 # include "Logging.h"
14 #endif
15 #include "Relation.h"
17 namespace mozilla {
18 namespace a11y {
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 ////////////////////////////////////////////////////////////////////////////////
25 // EventQueue
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) {
34 mFocusEvent = aEvent;
35 return true;
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);
42 // Filter events.
43 CoalesceEvents();
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);
51 return true;
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) {
70 return false;
72 bool pushed = false;
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;
78 do {
79 // Test possible name dependent parent.
80 if (doName) {
81 if (nameCheckAncestor && (maybeTargetNameChanged || parent != target) &&
82 nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
83 nsAutoString name;
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);
104 if (doDesc) {
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.
115 break;
117 parent = parent->LocalParent();
118 } while (parent &&
119 nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule));
121 return pushed;
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();
135 MOZ_ASSERT(
136 target->IsApplication() || target->IsOuterDoc() ||
137 target->IsXULTree(),
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;
151 return;
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,
167 index);
168 return;
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
201 // bug 927159).
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
219 // event.
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;
226 return;
229 break; // case eRemoveDupes
232 default:
233 break; // case eAllowDupes, eDoNotEmit
234 } // switch
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;
262 return;
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;
274 return;
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;
282 return;
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;
306 return;
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();
326 #ifdef A11Y_LOG
327 if ((eventCount > 0 || mFocusEvent) && logging::IsEnabled(logging::eEvents)) {
328 logging::MsgBegin("EVENTS", "events processing");
329 logging::Address("document", mDocument);
330 logging::MsgEnd();
332 #endif
334 if (mFocusEvent) {
335 // Always fire a pending focus event before all other events. We do this for
336 // two reasons:
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()) {
357 continue;
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()) {
373 uint64_t itemID =
374 item->IsDoc() ? 0 : reinterpret_cast<uint64_t>(item->UniqueID());
375 bool selected = selChangeEvent->mSelChangeType ==
376 AccSelChangeEvent::eSelectionAdd;
377 if (selected) {
378 selectedIDs.AppendElement(itemID);
379 } else {
380 unselectedIDs.AppendElement(itemID);
386 if (event->mEventRule == AccEvent::eDoNotEmit) {
387 continue;
390 // Dispatch caret moved and text selection change events.
391 if (eventType == nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) {
392 SelectionMgr()->ProcessTextSelChangeEvent(event);
393 continue;
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,
414 states::SELECTED,
415 (selChangeEvent->mPackedEvent->mSelChangeType ==
416 AccSelChangeEvent::eSelectionAdd),
417 selChangeEvent->mPackedEvent->mIsFromUserInput);
421 nsEventShell::FireEvent(event);
423 if (!mDocument) {
424 return;
428 if (mDocument && IPCAccessibilityActive() &&
429 (!selectedIDs.IsEmpty() || !unselectedIDs.IsEmpty())) {
430 DocAccessibleChild* ipcDoc = mDocument->IPCDoc();
431 ipcDoc->SendSelectedAccessiblesChanged(selectedIDs, unselectedIDs);
435 } // namespace a11y
436 } // namespace mozilla