void EventTree::Process() { while (mFirst) { // Skip a node and its subtree if its container is not in the document. if (mFirst->mContainer->IsInDocument()) { mFirst->Process(); } mFirst = mFirst->mNext.forget(); } MOZ_ASSERT(mContainer || mDependentEvents.IsEmpty(), "No container, no events"); MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(), "Processing events for defunct container"); // Fire mutation events. uint32_t eventsCount = mDependentEvents.Length(); for (uint32_t jdx = 0; jdx < eventsCount; jdx++) { AccMutationEvent* mtEvent = mDependentEvents[jdx]; MOZ_ASSERT(mtEvent->mEventRule != AccEvent::eDoNotEmit, "The event shouldn't be presented in the tree"); nsEventShell::FireEvent(mtEvent); if (mtEvent->mTextChangeEvent) { nsEventShell::FireEvent(mtEvent->mTextChangeEvent); } if (mtEvent->IsHide()) { // Fire menupopup end event before a hide event if a menu goes away. // XXX: We don't look into children of hidden subtree to find hiding // menupopup (as we did prior bug 570275) because we don't do that when // menu is showing (and that's impossible until bug 606924 is fixed). // Nevertheless we should do this at least because layout coalesces // the changes before our processing and we may miss some menupopup // events. Now we just want to be consistent in content insertion/removal // handling. if (mtEvent->mAccessible->ARIARole() == roles::MENUPOPUP) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, mtEvent->mAccessible); } AccHideEvent* hideEvent = downcast_accEvent(mtEvent); if (hideEvent->NeedsShutdown()) { mtEvent->GetDocAccessible()->ShutdownChildrenInSubtree(mtEvent->mAccessible); } } } // Fire reorder event at last. if (mFireReorder) { MOZ_ASSERT(mContainer); nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer); mContainer->Document()->MaybeNotifyOfValueChange(mContainer); } mDependentEvents.Clear(); }
void EventTree::Log(uint32_t aLevel) const { if (aLevel == UINT32_MAX) { if (mFirst) { mFirst->Log(0); } return; } for (uint32_t i = 0; i < aLevel; i++) { printf(" "); } logging::AccessibleInfo("container", mContainer); for (uint32_t i = 0; i < mDependentEvents.Length(); i++) { AccMutationEvent* ev = mDependentEvents[i]; if (ev->IsShow()) { for (uint32_t i = 0; i < aLevel + 1; i++) { printf(" "); } logging::AccessibleInfo("shown", ev->mAccessible); AccShowEvent* showEv = downcast_accEvent(ev); for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) { for (uint32_t j = 0; j < aLevel + 1; j++) { printf(" "); } logging::AccessibleInfo("preceding", showEv->mPrecedingEvents[i]->mAccessible); } } else { for (uint32_t i = 0; i < aLevel + 1; i++) { printf(" "); } logging::AccessibleInfo("hidden", ev->mAccessible); } } if (mFirst) { mFirst->Log(aLevel + 1); } if (mNext) { mNext->Log(aLevel); } }
void EventQueue::CoalesceEvents() { NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!"); uint32_t tail = mEvents.Length() - 1; AccEvent* tailEvent = mEvents[tail]; switch(tailEvent->mEventRule) { case AccEvent::eCoalesceReorder: CoalesceReorderEvents(tailEvent); break; // case eCoalesceReorder case AccEvent::eCoalesceMutationTextChange: { for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* thisEvent = mEvents[index]; if (thisEvent->mEventRule != tailEvent->mEventRule) continue; // We don't currently coalesce text change events from show/hide events. if (thisEvent->mEventType != tailEvent->mEventType) continue; // Show events may be duped because of reinsertion (removal is ignored // because initial insertion is not processed). Ignore initial // insertion. if (thisEvent->mAccessible == tailEvent->mAccessible) thisEvent->mEventRule = AccEvent::eDoNotEmit; AccMutationEvent* tailMutationEvent = downcast_accEvent(tailEvent); AccMutationEvent* thisMutationEvent = downcast_accEvent(thisEvent); if (tailMutationEvent->mParent != thisMutationEvent->mParent) continue; // Coalesce text change events for hide and show events. if (thisMutationEvent->IsHide()) { AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent); AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent); CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent); break; } AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent); AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent); CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent); break; } } break; // case eCoalesceMutationTextChange case AccEvent::eCoalesceOfSameType: { // Coalesce old events by newer event. for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* accEvent = mEvents[index]; if (accEvent->mEventType == tailEvent->mEventType && accEvent->mEventRule == tailEvent->mEventRule) { accEvent->mEventRule = AccEvent::eDoNotEmit; return; } } } break; // case eCoalesceOfSameType case AccEvent::eCoalesceSelectionChange: { AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent); for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* thisEvent = mEvents[index]; if (thisEvent->mEventRule == tailEvent->mEventRule) { AccSelChangeEvent* thisSelChangeEvent = downcast_accEvent(thisEvent); // Coalesce selection change events within same control. if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) { CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index); return; } } } } break; // eCoalesceSelectionChange case AccEvent::eCoalesceStateChange: { // If state change event is duped then ignore previous event. If state // change event is opposite to previous event then no event is emitted // (accessible state wasn't changed). for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* thisEvent = mEvents[index]; if (thisEvent->mEventRule != AccEvent::eDoNotEmit && thisEvent->mEventType == tailEvent->mEventType && thisEvent->mAccessible == tailEvent->mAccessible) { AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent); AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent); if (thisSCEvent->mState == tailSCEvent->mState) { thisEvent->mEventRule = AccEvent::eDoNotEmit; if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled) tailEvent->mEventRule = AccEvent::eDoNotEmit; } } } break; // eCoalesceStateChange } case AccEvent::eRemoveDupes: { // Check for repeat events, coalesce newly appended event by more older // event. for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* accEvent = mEvents[index]; if (accEvent->mEventType == tailEvent->mEventType && accEvent->mEventRule == tailEvent->mEventRule && accEvent->mAccessible == tailEvent->mAccessible) { tailEvent->mEventRule = AccEvent::eDoNotEmit; return; } } } break; // case eRemoveDupes default: break; // case eAllowDupes, eDoNotEmit } // switch }
nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible()); NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE); DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(accessible->Document()); if (doc) { switch (aEvent->GetEventType()) { case nsIAccessibleEvent::EVENT_FOCUS: { if (DocAccessibleWrap* topContentDoc = doc->GetTopLevelContentDoc(accessible)) { topContentDoc->CacheFocusPath(accessible); } break; } case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: { AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent); auto newPosition = static_cast<AccessibleWrap*>(vcEvent->NewAccessible()); if (newPosition) { if (DocAccessibleWrap* topContentDoc = doc->GetTopLevelContentDoc(accessible)) { topContentDoc->CacheFocusPath(newPosition); } } break; } } } nsresult rv = Accessible::HandleAccEvent(aEvent); NS_ENSURE_SUCCESS(rv, rv); if (IPCAccessibilityActive()) { return NS_OK; } // The accessible can become defunct if we have an xpcom event listener // which decides it would be fun to change the DOM and flush layout. if (accessible->IsDefunct() || !accessible->IsBoundToParent()) { return NS_OK; } if (doc) { if (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) { return NS_OK; } } SessionAccessibility* sessionAcc = SessionAccessibility::GetInstanceFor(accessible); if (!sessionAcc) { return NS_OK; } switch (aEvent->GetEventType()) { case nsIAccessibleEvent::EVENT_FOCUS: sessionAcc->SendFocusEvent(accessible); break; case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: { AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent); auto newPosition = static_cast<AccessibleWrap*>(vcEvent->NewAccessible()); auto oldPosition = static_cast<AccessibleWrap*>(vcEvent->OldAccessible()); if (sessionAcc && newPosition) { if (oldPosition != newPosition) { if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) { sessionAcc->SendHoverEnterEvent(newPosition); } else { sessionAcc->SendAccessibilityFocusedEvent(newPosition); } } if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) { sessionAcc->SendTextTraversedEvent( newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset()); } } break; } case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { AccCaretMoveEvent* event = downcast_accEvent(aEvent); sessionAcc->SendTextSelectionChangedEvent(accessible, event->GetCaretOffset()); break; } case nsIAccessibleEvent::EVENT_TEXT_INSERTED: case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { AccTextChangeEvent* event = downcast_accEvent(aEvent); sessionAcc->SendTextChangedEvent( accessible, event->ModifiedText(), event->GetStartOffset(), event->GetLength(), event->IsTextInserted(), event->IsFromUserInput()); break; } case nsIAccessibleEvent::EVENT_STATE_CHANGE: { AccStateChangeEvent* event = downcast_accEvent(aEvent); auto state = event->GetState(); if (state & states::CHECKED) { sessionAcc->SendClickedEvent(accessible, event->IsStateEnabled()); } if (state & states::SELECTED) { sessionAcc->SendSelectedEvent(accessible, event->IsStateEnabled()); } if (state & states::BUSY) { sessionAcc->SendWindowStateChangedEvent(accessible); } break; } case nsIAccessibleEvent::EVENT_SCROLLING: { AccScrollingEvent* event = downcast_accEvent(aEvent); sessionAcc->SendScrollingEvent(accessible, event->ScrollX(), event->ScrollY(), event->MaxScrollX(), event->MaxScrollY()); break; } case nsIAccessibleEvent::EVENT_SHOW: case nsIAccessibleEvent::EVENT_HIDE: { AccMutationEvent* event = downcast_accEvent(aEvent); auto parent = static_cast<AccessibleWrap*>(event->Parent()); sessionAcc->SendWindowContentChangedEvent(parent); break; } default: break; } return NS_OK; }
void NotificationController::CoalesceEvents() { uint32_t numQueuedEvents = mEvents.Length(); int32_t tail = numQueuedEvents - 1; AccEvent* tailEvent = mEvents[tail]; switch(tailEvent->mEventRule) { case AccEvent::eCoalesceReorder: CoalesceReorderEvents(tailEvent); break; // case eCoalesceReorder case AccEvent::eCoalesceMutationTextChange: { for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* thisEvent = mEvents[index]; if (thisEvent->mEventRule != tailEvent->mEventRule) continue; // We don't currently coalesce text change events from show/hide events. if (thisEvent->mEventType != tailEvent->mEventType) continue; // Show events may be duped because of reinsertion (removal is ignored // because initial insertion is not processed). Ignore initial // insertion. if (thisEvent->mAccessible == tailEvent->mAccessible) thisEvent->mEventRule = AccEvent::eDoNotEmit; AccMutationEvent* tailMutationEvent = downcast_accEvent(tailEvent); AccMutationEvent* thisMutationEvent = downcast_accEvent(thisEvent); if (tailMutationEvent->mParent != thisMutationEvent->mParent) continue; // Coalesce text change events for hide and show events. if (thisMutationEvent->IsHide()) { AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent); AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent); CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent); break; } AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent); AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent); CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent); break; } } break; // case eCoalesceMutationTextChange case AccEvent::eCoalesceOfSameType: { // Coalesce old events by newer event. for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* accEvent = mEvents[index]; if (accEvent->mEventType == tailEvent->mEventType && accEvent->mEventRule == tailEvent->mEventRule) { accEvent->mEventRule = AccEvent::eDoNotEmit; return; } } } break; // case eCoalesceOfSameType case AccEvent::eRemoveDupes: { // Check for repeat events, coalesce newly appended event by more older // event. for (uint32_t index = tail - 1; index < tail; index--) { AccEvent* accEvent = mEvents[index]; if (accEvent->mEventType == tailEvent->mEventType && accEvent->mEventRule == tailEvent->mEventRule && accEvent->mNode == tailEvent->mNode) { tailEvent->mEventRule = AccEvent::eDoNotEmit; return; } } } break; // case eRemoveDupes case AccEvent::eCoalesceSelectionChange: { AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent); int32_t index = tail - 1; for (; index >= 0; index--) { AccEvent* thisEvent = mEvents[index]; if (thisEvent->mEventRule == tailEvent->mEventRule) { AccSelChangeEvent* thisSelChangeEvent = downcast_accEvent(thisEvent); // Coalesce selection change events within same control. if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) { CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index); return; } } } } break; // eCoalesceSelectionChange default: break; // case eAllowDupes, eDoNotEmit } // switch }
void EventTree::Mutated(AccMutationEvent* aEv) { // If shown or hidden node is a root of previously mutated subtree, then // discard those subtree mutations as we are no longer interested in them. UniquePtr<EventTree>* node = &mFirst; while (*node) { Accessible* cntr = (*node)->mContainer; while (cntr != mContainer) { if (cntr == aEv->mAccessible) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eEventTree)) { logging::MsgBegin("EVENTS_TREE", "Trim subtree"); logging::AccessibleInfo("Show/hide container", aEv->mAccessible); logging::AccessibleInfo("Trimmed subtree root", (*node)->mContainer); logging::MsgEnd(); } #endif // If the new hide is part of a move and it contains existing child // shows, then move preceding events from the child shows to the buffer, // so the ongoing show event will pick them up. if (aEv->IsHide()) { AccHideEvent* hideEv = downcast_accEvent(aEv); if (!hideEv->mNeedsShutdown) { for (uint32_t i = 0; i < (*node)->mDependentEvents.Length(); i++) { AccMutationEvent* childEv = (*node)->mDependentEvents[i]; if (childEv->IsShow()) { AccShowEvent* childShowEv = downcast_accEvent(childEv); if (childShowEv->mPrecedingEvents.Length() > 0) { Controller(mContainer)->StorePrecedingEvents( mozilla::Move(childShowEv->mPrecedingEvents)); } } } } } // If the new show contains existing child shows, then move preceding // events from the child shows to the new show. else if (aEv->IsShow()) { AccShowEvent* showEv = downcast_accEvent(aEv); for (uint32_t i = 0; (*node)->mDependentEvents.Length(); i++) { AccMutationEvent* childEv = (*node)->mDependentEvents[i]; if (childEv->IsShow()) { AccShowEvent* showChildEv = downcast_accEvent(childEv); if (showChildEv->mPrecedingEvents.Length() > 0) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eEventTree)) { logging::MsgBegin("EVENTS_TREE", "Adopt preceding events"); logging::AccessibleInfo("Parent", aEv->mAccessible); for (uint32_t j = 0; j < showChildEv->mPrecedingEvents.Length(); j++) { logging::AccessibleInfo("Adoptee", showChildEv->mPrecedingEvents[i]->mAccessible); } logging::MsgEnd(); } #endif showEv->mPrecedingEvents.AppendElements(showChildEv->mPrecedingEvents); } } } } *node = Move((*node)->mNext); break; } cntr = cntr->Parent(); } if (cntr == aEv->mAccessible) { continue; } node = &(*node)->mNext; } AccMutationEvent* prevEvent = mDependentEvents.SafeLastElement(nullptr); mDependentEvents.AppendElement(aEv); // Coalesce text change events from this hide/show event and the previous one. if (prevEvent && aEv->mEventType == prevEvent->mEventType) { if (aEv->IsHide()) { // XXX: we need a way to ignore SplitNode and JoinNode() when they do not // affect the text within the hypertext. AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent; if (prevTextEvent) { AccHideEvent* hideEvent = downcast_accEvent(aEv); AccHideEvent* prevHideEvent = downcast_accEvent(prevEvent); if (prevHideEvent->mNextSibling == hideEvent->mAccessible) { hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText); } else if (prevHideEvent->mPrevSibling == hideEvent->mAccessible) { uint32_t oldLen = prevTextEvent->GetLength(); hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText); prevTextEvent->mStart -= prevTextEvent->GetLength() - oldLen; } hideEvent->mTextChangeEvent.swap(prevEvent->mTextChangeEvent); } } else { AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent; if (prevTextEvent) { if (aEv->mAccessible->IndexInParent() == prevEvent->mAccessible->IndexInParent() + 1) { // If tail target was inserted after this target, i.e. tail target is next // sibling of this target. aEv->mAccessible->AppendTextTo(prevTextEvent->mModifiedText); } else if (aEv->mAccessible->IndexInParent() == prevEvent->mAccessible->IndexInParent() - 1) { // If tail target was inserted before this target, i.e. tail target is // previous sibling of this target. nsAutoString startText; aEv->mAccessible->AppendTextTo(startText); prevTextEvent->mModifiedText = startText + prevTextEvent->mModifiedText; prevTextEvent->mStart -= startText.Length(); } aEv->mTextChangeEvent.swap(prevEvent->mTextChangeEvent); } } } // Create a text change event caused by this hide/show event. When a node is // hidden/removed or shown/appended, the text in an ancestor hyper text will // lose or get new characters. if (aEv->mTextChangeEvent || !mContainer->IsHyperText()) { return; } nsAutoString text; aEv->mAccessible->AppendTextTo(text); if (text.IsEmpty()) { return; } int32_t offset = mContainer->AsHyperText()->GetChildOffset(aEv->mAccessible); aEv->mTextChangeEvent = new AccTextChangeEvent(mContainer, offset, text, aEv->IsShow(), aEv->mIsFromUserInput ? eFromUserInput : eNoUserInput); }
EventTree* EventTree::FindOrInsert(Accessible* aContainer) { if (!mFirst) { mFirst.reset(new EventTree(aContainer, mDependentEvents.IsEmpty())); return mFirst.get(); } EventTree* prevNode = nullptr; EventTree* node = mFirst.get(); do { MOZ_ASSERT(!node->mContainer->IsApplication(), "No event for application accessible is expected here"); MOZ_ASSERT(!node->mContainer->IsDefunct(), "An event target has to be alive"); // Case of same target. if (node->mContainer == aContainer) { return node; } // Check if the given container is contained by a current node Accessible* top = mContainer ? mContainer : aContainer->Document(); Accessible* parent = aContainer; while (parent) { // Reached a top, no match for a current event. if (parent == top) { break; } // We got a match. if (parent->Parent() == node->mContainer) { // Reject the node if it's contained by a show/hide event target uint32_t evCount = node->mDependentEvents.Length(); for (uint32_t idx = 0; idx < evCount; idx++) { AccMutationEvent* ev = node->mDependentEvents[idx]; if (ev->GetAccessible() == parent) { #ifdef A11Y_LOG if (logging::IsEnabled(logging::eEventTree)) { logging::MsgBegin("EVENTS_TREE", "Rejecting node contained by show/hide"); logging::AccessibleInfo("Node", aContainer); logging::MsgEnd(); } #endif // If the node is rejected, then check if it has related hide event // on stack, and if so, then connect it to the parent show event. if (ev->IsShow()) { AccShowEvent* showEv = downcast_accEvent(ev); Controller(aContainer)-> WithdrawPrecedingEvents(&showEv->mPrecedingEvents); } return nullptr; } } return node->FindOrInsert(aContainer); } parent = parent->Parent(); MOZ_ASSERT(parent, "Wrong tree"); } // If the given container contains a current node // then // if show or hide of the given node contains a grand parent of the current node // then ignore the current node and its show and hide events // otherwise ignore the current node, but not its show and hide events Accessible* curParent = node->mContainer; while (curParent && !curParent->IsDoc()) { if (curParent->Parent() != aContainer) { curParent = curParent->Parent(); continue; } // Insert the tail node into the hierarchy between the current node and // its parent. node->mFireReorder = false; UniquePtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst; UniquePtr<EventTree> newNode(new EventTree(aContainer, mDependentEvents.IsEmpty())); newNode->mFirst = Move(nodeOwnerRef); nodeOwnerRef = Move(newNode); nodeOwnerRef->mNext = Move(node->mNext); // Check if a next node is contained by the given node too, and move them // under the given node if so. prevNode = nodeOwnerRef.get(); node = nodeOwnerRef->mNext.get(); UniquePtr<EventTree>* nodeRef = &nodeOwnerRef->mNext; EventTree* insNode = nodeOwnerRef->mFirst.get(); while (node) { Accessible* curParent = node->mContainer; while (curParent && !curParent->IsDoc()) { if (curParent->Parent() != aContainer) { curParent = curParent->Parent(); continue; } MOZ_ASSERT(!insNode->mNext); node->mFireReorder = false; insNode->mNext = Move(*nodeRef); insNode = insNode->mNext.get(); prevNode->mNext = Move(node->mNext); node = prevNode; break; } prevNode = node; nodeRef = &node->mNext; node = node->mNext.get(); } return nodeOwnerRef.get(); } prevNode = node; } while ((node = node->mNext.get())); MOZ_ASSERT(prevNode, "Nowhere to insert"); MOZ_ASSERT(!prevNode->mNext, "Taken by another node"); // If 'this' node contains the given container accessible, then // do not emit a reorder event for the container // if a dependent show event target contains the given container then do not // emit show / hide events (see Process() method) prevNode->mNext.reset(new EventTree(aContainer, mDependentEvents.IsEmpty())); return prevNode->mNext.get(); }