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 "nsAccessibilityService.h"
12 #include "nsTextEquivUtils.h"
17 using namespace mozilla
;
18 using namespace mozilla::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 ////////////////////////////////////////////////////////////////////////////////
26 ////////////////////////////////////////////////////////////////////////////////
29 EventQueue::PushEvent(AccEvent
* aEvent
)
31 NS_ASSERTION((aEvent
->mAccessible
&& aEvent
->mAccessible
->IsApplication()) ||
32 aEvent
->GetDocAccessible() == mDocument
,
33 "Queued event belongs to another document!");
35 if (!mEvents
.AppendElement(aEvent
))
41 // Fire name change event on parent given that this event hasn't been
42 // coalesced, the parent's name was calculated from its subtree, and the
43 // subtree was changed.
44 Accessible
* target
= aEvent
->mAccessible
;
45 if (aEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
46 target
->HasNameDependentParent() &&
47 (aEvent
->mEventType
== nsIAccessibleEvent::EVENT_NAME_CHANGE
||
48 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_REMOVED
||
49 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_INSERTED
||
50 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_SHOW
||
51 aEvent
->mEventType
== nsIAccessibleEvent::EVENT_HIDE
)) {
52 // Only continue traversing up the tree if it's possible that the parent
53 // accessible's name can depend on this accessible's name.
54 Accessible
* parent
= target
->Parent();
56 nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeIfReqRule
)) {
57 // Test possible name dependent parent.
58 if (nsTextEquivUtils::HasNameRule(parent
, eNameFromSubtreeRule
)) {
60 ENameValueFlag nameFlag
= parent
->Name(name
);
61 // If name is obtained from subtree, fire name change event.
62 if (nameFlag
== eNameFromSubtree
) {
63 nsRefPtr
<AccEvent
> nameChangeEvent
=
64 new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
, parent
);
65 PushEvent(nameChangeEvent
);
69 parent
= parent
->Parent();
73 // Associate text change with hide event if it wasn't stolen from hiding
74 // siblings during coalescence.
75 AccMutationEvent
* showOrHideEvent
= downcast_accEvent(aEvent
);
76 if (showOrHideEvent
&& !showOrHideEvent
->mTextChangeEvent
)
77 CreateTextChangeEventFor(showOrHideEvent
);
82 ////////////////////////////////////////////////////////////////////////////////
83 // EventQueue: private
86 EventQueue::CoalesceEvents()
88 NS_ASSERTION(mEvents
.Length(), "There should be at least one pending event!");
89 uint32_t tail
= mEvents
.Length() - 1;
90 AccEvent
* tailEvent
= mEvents
[tail
];
92 switch(tailEvent
->mEventRule
) {
93 case AccEvent::eCoalesceReorder
:
94 CoalesceReorderEvents(tailEvent
);
95 break; // case eCoalesceReorder
97 case AccEvent::eCoalesceMutationTextChange
:
99 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
100 AccEvent
* thisEvent
= mEvents
[index
];
101 if (thisEvent
->mEventRule
!= tailEvent
->mEventRule
)
104 // We don't currently coalesce text change events from show/hide events.
105 if (thisEvent
->mEventType
!= tailEvent
->mEventType
)
108 // Show events may be duped because of reinsertion (removal is ignored
109 // because initial insertion is not processed). Ignore initial
111 if (thisEvent
->mAccessible
== tailEvent
->mAccessible
)
112 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
114 AccMutationEvent
* tailMutationEvent
= downcast_accEvent(tailEvent
);
115 AccMutationEvent
* thisMutationEvent
= downcast_accEvent(thisEvent
);
116 if (tailMutationEvent
->mParent
!= thisMutationEvent
->mParent
)
119 // Coalesce text change events for hide and show events.
120 if (thisMutationEvent
->IsHide()) {
121 AccHideEvent
* tailHideEvent
= downcast_accEvent(tailEvent
);
122 AccHideEvent
* thisHideEvent
= downcast_accEvent(thisEvent
);
123 CoalesceTextChangeEventsFor(tailHideEvent
, thisHideEvent
);
127 AccShowEvent
* tailShowEvent
= downcast_accEvent(tailEvent
);
128 AccShowEvent
* thisShowEvent
= downcast_accEvent(thisEvent
);
129 CoalesceTextChangeEventsFor(tailShowEvent
, thisShowEvent
);
132 } break; // case eCoalesceMutationTextChange
134 case AccEvent::eCoalesceOfSameType
:
136 // Coalesce old events by newer event.
137 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
138 AccEvent
* accEvent
= mEvents
[index
];
139 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
140 accEvent
->mEventRule
== tailEvent
->mEventRule
) {
141 accEvent
->mEventRule
= AccEvent::eDoNotEmit
;
145 } break; // case eCoalesceOfSameType
147 case AccEvent::eCoalesceSelectionChange
:
149 AccSelChangeEvent
* tailSelChangeEvent
= downcast_accEvent(tailEvent
);
150 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
151 AccEvent
* thisEvent
= mEvents
[index
];
152 if (thisEvent
->mEventRule
== tailEvent
->mEventRule
) {
153 AccSelChangeEvent
* thisSelChangeEvent
=
154 downcast_accEvent(thisEvent
);
156 // Coalesce selection change events within same control.
157 if (tailSelChangeEvent
->mWidget
== thisSelChangeEvent
->mWidget
) {
158 CoalesceSelChangeEvents(tailSelChangeEvent
, thisSelChangeEvent
, index
);
164 } break; // eCoalesceSelectionChange
166 case AccEvent::eCoalesceStateChange
:
168 // If state change event is duped then ignore previous event. If state
169 // change event is opposite to previous event then no event is emitted
170 // (accessible state wasn't changed).
171 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
172 AccEvent
* thisEvent
= mEvents
[index
];
173 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
174 thisEvent
->mEventType
== tailEvent
->mEventType
&&
175 thisEvent
->mAccessible
== tailEvent
->mAccessible
) {
176 AccStateChangeEvent
* thisSCEvent
= downcast_accEvent(thisEvent
);
177 AccStateChangeEvent
* tailSCEvent
= downcast_accEvent(tailEvent
);
178 if (thisSCEvent
->mState
== tailSCEvent
->mState
) {
179 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
180 if (thisSCEvent
->mIsEnabled
!= tailSCEvent
->mIsEnabled
)
181 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
185 break; // eCoalesceStateChange
188 case AccEvent::eCoalesceTextSelChange
:
190 // Coalesce older event by newer event for the same selection or target.
191 // Events for same selection may have different targets and vice versa one
192 // target may be pointed by different selections (for latter see
194 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
195 AccEvent
* thisEvent
= mEvents
[index
];
196 if (thisEvent
->mEventRule
!= AccEvent::eDoNotEmit
&&
197 thisEvent
->mEventType
== tailEvent
->mEventType
) {
198 AccTextSelChangeEvent
* thisTSCEvent
= downcast_accEvent(thisEvent
);
199 AccTextSelChangeEvent
* tailTSCEvent
= downcast_accEvent(tailEvent
);
200 if (thisTSCEvent
->mSel
== tailTSCEvent
->mSel
||
201 thisEvent
->mAccessible
== tailEvent
->mAccessible
)
202 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
206 } break; // eCoalesceTextSelChange
208 case AccEvent::eRemoveDupes
:
210 // Check for repeat events, coalesce newly appended event by more older
212 for (uint32_t index
= tail
- 1; index
< tail
; index
--) {
213 AccEvent
* accEvent
= mEvents
[index
];
214 if (accEvent
->mEventType
== tailEvent
->mEventType
&&
215 accEvent
->mEventRule
== tailEvent
->mEventRule
&&
216 accEvent
->mAccessible
== tailEvent
->mAccessible
) {
217 tailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
221 } break; // case eRemoveDupes
224 break; // case eAllowDupes, eDoNotEmit
229 EventQueue::CoalesceReorderEvents(AccEvent
* aTailEvent
)
231 uint32_t count
= mEvents
.Length();
232 for (uint32_t index
= count
- 2; index
< count
; index
--) {
233 AccEvent
* thisEvent
= mEvents
[index
];
235 // Skip events of different types and targeted to application accessible.
236 if (thisEvent
->mEventType
!= aTailEvent
->mEventType
||
237 thisEvent
->mAccessible
->IsApplication())
240 // If thisEvent target is not in document longer, i.e. if it was
241 // removed from the tree then do not emit the event.
242 if (!thisEvent
->mAccessible
->IsDoc() &&
243 !thisEvent
->mAccessible
->IsInDocument()) {
244 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
248 // Coalesce earlier event of the same target.
249 if (thisEvent
->mAccessible
== aTailEvent
->mAccessible
) {
250 if (thisEvent
->mEventRule
== AccEvent::eDoNotEmit
) {
251 AccReorderEvent
* tailReorder
= downcast_accEvent(aTailEvent
);
252 tailReorder
->DoNotEmitAll();
254 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
260 // If tailEvent contains thisEvent
262 // if show or hide of tailEvent contains a grand parent of thisEvent
263 // then ignore thisEvent and its show and hide events
264 // otherwise ignore thisEvent but not its show and hide events
265 Accessible
* thisParent
= thisEvent
->mAccessible
;
266 while (thisParent
&& thisParent
!= mDocument
) {
267 if (thisParent
->Parent() == aTailEvent
->mAccessible
) {
268 AccReorderEvent
* tailReorder
= downcast_accEvent(aTailEvent
);
269 uint32_t eventType
= tailReorder
->IsShowHideEventTarget(thisParent
);
271 // Sometimes InvalidateChildren() and
272 // DocAccessible::CacheChildrenInSubtree() can conspire to reparent an
273 // accessible in this case no need for mutation events. Se bug 883708
275 if (eventType
== nsIAccessibleEvent::EVENT_SHOW
||
276 eventType
== nsIAccessibleEvent::EVENT_HIDE
) {
277 AccReorderEvent
* thisReorder
= downcast_accEvent(thisEvent
);
278 thisReorder
->DoNotEmitAll();
280 thisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
286 thisParent
= thisParent
->Parent();
289 // If tailEvent is contained by thisEvent
291 // if show of thisEvent contains the tailEvent
292 // then ignore tailEvent
293 // if hide of thisEvent contains the tailEvent
295 // otherwise ignore tailEvent but not its show and hide events
296 Accessible
* tailParent
= aTailEvent
->mAccessible
;
297 while (tailParent
&& tailParent
!= mDocument
) {
298 if (tailParent
->Parent() == thisEvent
->mAccessible
) {
299 AccReorderEvent
* thisReorder
= downcast_accEvent(thisEvent
);
300 AccReorderEvent
* tailReorder
= downcast_accEvent(aTailEvent
);
301 uint32_t eventType
= thisReorder
->IsShowHideEventTarget(tailParent
);
302 if (eventType
== nsIAccessibleEvent::EVENT_SHOW
)
303 tailReorder
->DoNotEmitAll();
304 else if (eventType
== nsIAccessibleEvent::EVENT_HIDE
)
305 NS_ERROR("Accessible tree was modified after it was removed! Huh?");
307 aTailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
312 tailParent
= tailParent
->Parent();
319 EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent
* aTailEvent
,
320 AccSelChangeEvent
* aThisEvent
,
323 aTailEvent
->mPreceedingCount
= aThisEvent
->mPreceedingCount
+ 1;
325 // Pack all preceding events into single selection within event
326 // when we receive too much selection add/remove events.
327 if (aTailEvent
->mPreceedingCount
>= kSelChangeCountToPack
) {
328 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
;
329 aTailEvent
->mAccessible
= aTailEvent
->mWidget
;
330 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
332 // Do not emit any preceding selection events for same widget if they
333 // weren't coalesced yet.
334 if (aThisEvent
->mEventType
!= nsIAccessibleEvent::EVENT_SELECTION_WITHIN
) {
335 for (uint32_t jdx
= aThisIndex
- 1; jdx
< aThisIndex
; jdx
--) {
336 AccEvent
* prevEvent
= mEvents
[jdx
];
337 if (prevEvent
->mEventRule
== aTailEvent
->mEventRule
) {
338 AccSelChangeEvent
* prevSelChangeEvent
=
339 downcast_accEvent(prevEvent
);
340 if (prevSelChangeEvent
->mWidget
== aTailEvent
->mWidget
)
341 prevSelChangeEvent
->mEventRule
= AccEvent::eDoNotEmit
;
348 // Pack sequential selection remove and selection add events into
349 // single selection change event.
350 if (aTailEvent
->mPreceedingCount
== 1 &&
351 aTailEvent
->mItem
!= aThisEvent
->mItem
) {
352 if (aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
353 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
354 aThisEvent
->mEventRule
= AccEvent::eDoNotEmit
;
355 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
356 aTailEvent
->mPackedEvent
= aThisEvent
;
360 if (aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
&&
361 aTailEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionRemove
) {
362 aTailEvent
->mEventRule
= AccEvent::eDoNotEmit
;
363 aThisEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION
;
364 aThisEvent
->mPackedEvent
= aTailEvent
;
369 // Unpack the packed selection change event because we've got one
370 // more selection add/remove.
371 if (aThisEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
372 if (aThisEvent
->mPackedEvent
) {
373 aThisEvent
->mPackedEvent
->mEventType
=
374 aThisEvent
->mPackedEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
?
375 nsIAccessibleEvent::EVENT_SELECTION_ADD
:
376 nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
378 aThisEvent
->mPackedEvent
->mEventRule
=
379 AccEvent::eCoalesceSelectionChange
;
381 aThisEvent
->mPackedEvent
= nullptr;
384 aThisEvent
->mEventType
=
385 aThisEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
?
386 nsIAccessibleEvent::EVENT_SELECTION_ADD
:
387 nsIAccessibleEvent::EVENT_SELECTION_REMOVE
;
392 // Convert into selection add since control has single selection but other
393 // selection events for this control are queued.
394 if (aTailEvent
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
)
395 aTailEvent
->mEventType
= nsIAccessibleEvent::EVENT_SELECTION_ADD
;
399 EventQueue::CoalesceTextChangeEventsFor(AccHideEvent
* aTailEvent
,
400 AccHideEvent
* aThisEvent
)
402 // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
403 // affect the text within the hypertext.
405 AccTextChangeEvent
* textEvent
= aThisEvent
->mTextChangeEvent
;
409 if (aThisEvent
->mNextSibling
== aTailEvent
->mAccessible
) {
410 aTailEvent
->mAccessible
->AppendTextTo(textEvent
->mModifiedText
);
412 } else if (aThisEvent
->mPrevSibling
== aTailEvent
->mAccessible
) {
413 uint32_t oldLen
= textEvent
->GetLength();
414 aTailEvent
->mAccessible
->AppendTextTo(textEvent
->mModifiedText
);
415 textEvent
->mStart
-= textEvent
->GetLength() - oldLen
;
418 aTailEvent
->mTextChangeEvent
.swap(aThisEvent
->mTextChangeEvent
);
422 EventQueue::CoalesceTextChangeEventsFor(AccShowEvent
* aTailEvent
,
423 AccShowEvent
* aThisEvent
)
425 AccTextChangeEvent
* textEvent
= aThisEvent
->mTextChangeEvent
;
429 if (aTailEvent
->mAccessible
->IndexInParent() ==
430 aThisEvent
->mAccessible
->IndexInParent() + 1) {
431 // If tail target was inserted after this target, i.e. tail target is next
432 // sibling of this target.
433 aTailEvent
->mAccessible
->AppendTextTo(textEvent
->mModifiedText
);
435 } else if (aTailEvent
->mAccessible
->IndexInParent() ==
436 aThisEvent
->mAccessible
->IndexInParent() -1) {
437 // If tail target was inserted before this target, i.e. tail target is
438 // previous sibling of this target.
439 nsAutoString startText
;
440 aTailEvent
->mAccessible
->AppendTextTo(startText
);
441 textEvent
->mModifiedText
= startText
+ textEvent
->mModifiedText
;
442 textEvent
->mStart
-= startText
.Length();
445 aTailEvent
->mTextChangeEvent
.swap(aThisEvent
->mTextChangeEvent
);
449 EventQueue::CreateTextChangeEventFor(AccMutationEvent
* aEvent
)
451 Accessible
* container
= aEvent
->mAccessible
->Parent();
455 HyperTextAccessible
* textAccessible
= container
->AsHyperText();
459 // Don't fire event for the first html:br in an editor.
460 if (aEvent
->mAccessible
->Role() == roles::WHITESPACE
) {
461 nsCOMPtr
<nsIEditor
> editor
= textAccessible
->GetEditor();
463 bool isEmpty
= false;
464 editor
->GetDocumentIsEmpty(&isEmpty
);
470 int32_t offset
= textAccessible
->GetChildOffset(aEvent
->mAccessible
);
473 aEvent
->mAccessible
->AppendTextTo(text
);
477 aEvent
->mTextChangeEvent
=
478 new AccTextChangeEvent(textAccessible
, offset
, text
, aEvent
->IsShow(),
479 aEvent
->mIsFromUserInput
? eFromUserInput
: eNoUserInput
);
482 ////////////////////////////////////////////////////////////////////////////////
483 // EventQueue: event queue
486 EventQueue::ProcessEventQueue()
488 // Process only currently queued events.
489 nsTArray
<nsRefPtr
<AccEvent
> > events
;
490 events
.SwapElements(mEvents
);
492 uint32_t eventCount
= events
.Length();
494 if (eventCount
> 0 && logging::IsEnabled(logging::eEvents
)) {
495 logging::MsgBegin("EVENTS", "events processing");
496 logging::Address("document", mDocument
);
501 for (uint32_t idx
= 0; idx
< eventCount
; idx
++) {
502 AccEvent
* event
= events
[idx
];
503 if (event
->mEventRule
!= AccEvent::eDoNotEmit
) {
504 Accessible
* target
= event
->GetAccessible();
505 if (!target
|| target
->IsDefunct())
508 // Dispatch the focus event if target is still focused.
509 if (event
->mEventType
== nsIAccessibleEvent::EVENT_FOCUS
) {
510 FocusMgr()->ProcessFocusEvent(event
);
514 // Dispatch caret moved and text selection change events.
515 if (event
->mEventType
== nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
) {
516 SelectionMgr()->ProcessTextSelChangeEvent(event
);
520 // Fire selected state change events in support to selection events.
521 if (event
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION_ADD
) {
522 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
,
523 true, event
->mIsFromUserInput
);
525 } else if (event
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION_REMOVE
) {
526 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
,
527 false, event
->mIsFromUserInput
);
529 } else if (event
->mEventType
== nsIAccessibleEvent::EVENT_SELECTION
) {
530 AccSelChangeEvent
* selChangeEvent
= downcast_accEvent(event
);
531 nsEventShell::FireEvent(event
->mAccessible
, states::SELECTED
,
532 (selChangeEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
),
533 event
->mIsFromUserInput
);
535 if (selChangeEvent
->mPackedEvent
) {
536 nsEventShell::FireEvent(selChangeEvent
->mPackedEvent
->mAccessible
,
538 (selChangeEvent
->mPackedEvent
->mSelChangeType
== AccSelChangeEvent::eSelectionAdd
),
539 selChangeEvent
->mPackedEvent
->mIsFromUserInput
);
543 nsEventShell::FireEvent(event
);
545 // Fire text change events.
546 AccMutationEvent
* mutationEvent
= downcast_accEvent(event
);
548 if (mutationEvent
->mTextChangeEvent
)
549 nsEventShell::FireEvent(mutationEvent
->mTextChangeEvent
);
553 if (event
->mEventType
== nsIAccessibleEvent::EVENT_HIDE
)
554 mDocument
->ShutdownChildrenInSubtree(event
->mAccessible
);