From 51947ead2cfee575a7a234131d578547a41f4365 Mon Sep 17 00:00:00 2001 From: Alexander Surkov Date: Thu, 7 Apr 2016 09:30:22 -0400 Subject: [PATCH] Bug 1261425 - coalesce mutation events by a tree structure, r=yzen --- accessible/base/AccEvent.cpp | 22 - accessible/base/AccEvent.h | 40 +- accessible/base/EventQueue.cpp | 259 +-------- accessible/base/EventQueue.h | 23 +- accessible/base/EventTree.cpp | 536 ++++++++++++++++++ accessible/base/EventTree.h | 113 ++++ accessible/base/Logging.cpp | 1 + accessible/base/Logging.h | 17 +- accessible/base/NotificationController.cpp | 14 + accessible/base/NotificationController.h | 33 +- accessible/base/moz.build | 1 + accessible/base/nsAccessibilityService.cpp | 5 +- accessible/generic/Accessible.cpp | 44 +- accessible/generic/Accessible.h | 40 +- accessible/generic/DocAccessible.cpp | 126 ++-- accessible/generic/DocAccessible.h | 17 +- accessible/html/HTMLImageMapAccessible.cpp | 23 +- accessible/html/HTMLListAccessible.cpp | 20 +- .../tests/mochitest/actions/test_link.html | 5 +- .../mochitest/events/test_aria_alert.html | 7 +- .../mochitest/events/test_aria_menu.html | 10 +- .../mochitest/events/test_coalescence.html | 39 +- .../tests/mochitest/events/test_mutation.html | 4 +- .../mochitest/events/test_namechange.html | 24 +- .../mochitest/events/test_selection.html | 2 - .../tests/mochitest/events/test_selection.xul | 11 +- .../tests/mochitest/events/test_text.html | 5 +- .../mochitest/treeupdate/test_ariaowns.html | 12 +- .../mochitest/treeupdate/test_bug1189277.html | 5 +- 29 files changed, 896 insertions(+), 562 deletions(-) create mode 100644 accessible/base/EventTree.cpp create mode 100644 accessible/base/EventTree.h diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp index b2232d191e5d..d42a6001f7ad 100644 --- a/accessible/base/AccEvent.cpp +++ b/accessible/base/AccEvent.cpp @@ -77,28 +77,6 @@ AccTextChangeEvent:: (states::FOCUSED | states::EDITABLE); } - -//////////////////////////////////////////////////////////////////////////////// -// AccReorderEvent -//////////////////////////////////////////////////////////////////////////////// - -uint32_t -AccReorderEvent::IsShowHideEventTarget(const Accessible* aTarget) const -{ - uint32_t count = mDependentEvents.Length(); - for (uint32_t index = count - 1; index < count; index--) { - if (mDependentEvents[index]->mAccessible == aTarget) { - uint32_t eventType = mDependentEvents[index]->mEventType; - if (eventType == nsIAccessibleEvent::EVENT_SHOW || - eventType == nsIAccessibleEvent::EVENT_HIDE) { - return mDependentEvents[index]->mEventType; - } - } - } - - return 0; -} - //////////////////////////////////////////////////////////////////////////////// // AccHideEvent //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h index 5ca34e818d95..209f5fe8e994 100644 --- a/accessible/base/AccEvent.h +++ b/accessible/base/AccEvent.h @@ -130,7 +130,7 @@ protected: RefPtr mAccessible; friend class EventQueue; - friend class AccReorderEvent; + friend class EventTree; }; @@ -201,8 +201,7 @@ private: bool mIsInserted; nsString mModifiedText; - friend class EventQueue; - friend class AccReorderEvent; + friend class EventTree; }; @@ -239,7 +238,7 @@ protected: RefPtr mParent; RefPtr mTextChangeEvent; - friend class EventQueue; + friend class EventTree; }; @@ -269,7 +268,7 @@ protected: RefPtr mNextSibling; RefPtr mPrevSibling; - friend class EventQueue; + friend class EventTree; }; @@ -312,37 +311,6 @@ public: { return AccEvent::GetEventGroups() | (1U << eReorderEvent); } - - /** - * Get connected with mutation event. - */ - void AddSubMutationEvent(AccMutationEvent* aEvent) - { mDependentEvents.AppendElement(aEvent); } - - /** - * Do not emit the reorder event and its connected mutation events. - */ - void DoNotEmitAll() - { - mEventRule = AccEvent::eDoNotEmit; - uint32_t eventsCount = mDependentEvents.Length(); - for (uint32_t idx = 0; idx < eventsCount; idx++) - mDependentEvents[idx]->mEventRule = AccEvent::eDoNotEmit; - } - - /** - * Return true if the given accessible is a target of connected mutation - * event. - */ - uint32_t IsShowHideEventTarget(const Accessible* aTarget) const; - -protected: - /** - * Show and hide events causing this reorder event. - */ - nsTArray mDependentEvents; - - friend class EventQueue; }; diff --git a/accessible/base/EventQueue.cpp b/accessible/base/EventQueue.cpp index d060712f0bc7..43da8f70931f 100644 --- a/accessible/base/EventQueue.cpp +++ b/accessible/base/EventQueue.cpp @@ -39,20 +39,25 @@ EventQueue::PushEvent(AccEvent* aEvent) // Filter events. CoalesceEvents(); + if (aEvent->mEventRule != AccEvent::eDoNotEmit && + (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE || + aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED || + aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED)) { + PushNameChange(aEvent->mAccessible); + } + return true; +} + +bool +EventQueue::PushNameChange(Accessible* aTarget) +{ // Fire name change event on parent given that this event hasn't been // coalesced, the parent's name was calculated from its subtree, and the // subtree was changed. - Accessible* target = aEvent->mAccessible; - if (aEvent->mEventRule != AccEvent::eDoNotEmit && - target->HasNameDependentParent() && - (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE || - aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED || - aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED || - aEvent->mEventType == nsIAccessibleEvent::EVENT_SHOW || - aEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE)) { + if (aTarget->HasNameDependentParent()) { // Only continue traversing up the tree if it's possible that the parent // accessible's name can depend on this accessible's name. - Accessible* parent = target->Parent(); + Accessible* parent = aTarget->Parent(); while (parent && nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) { // Test possible name dependent parent. @@ -63,21 +68,14 @@ EventQueue::PushEvent(AccEvent* aEvent) if (nameFlag == eNameFromSubtree) { RefPtr nameChangeEvent = new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent); - PushEvent(nameChangeEvent); + return PushEvent(nameChangeEvent); } break; } parent = parent->Parent(); } } - - // Associate text change with hide event if it wasn't stolen from hiding - // siblings during coalescence. - AccMutationEvent* showOrHideEvent = downcast_accEvent(aEvent); - if (showOrHideEvent && !showOrHideEvent->mTextChangeEvent) - CreateTextChangeEventFor(showOrHideEvent); - - return true; + return false; } //////////////////////////////////////////////////////////////////////////////// @@ -92,46 +90,12 @@ EventQueue::CoalesceEvents() switch(tailEvent->mEventRule) { case AccEvent::eCoalesceReorder: - CoalesceReorderEvents(tailEvent); + MOZ_ASSERT(tailEvent->mAccessible->IsApplication() || + tailEvent->mAccessible->IsOuterDoc() || + tailEvent->mAccessible->IsXULTree(), + "Only app or outerdoc accessible reorder events are in the queue"); 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. @@ -226,93 +190,6 @@ EventQueue::CoalesceEvents() } // switch } -void -EventQueue::CoalesceReorderEvents(AccEvent* aTailEvent) -{ - uint32_t count = mEvents.Length(); - for (uint32_t index = count - 2; index < count; index--) { - AccEvent* thisEvent = mEvents[index]; - - // Skip events of different types and targeted to application accessible. - if (thisEvent->mEventType != aTailEvent->mEventType || - thisEvent->mAccessible->IsApplication()) - continue; - - // If thisEvent target is not in document longer, i.e. if it was - // removed from the tree then do not emit the event. - if (!thisEvent->mAccessible->IsDoc() && - !thisEvent->mAccessible->IsInDocument()) { - thisEvent->mEventRule = AccEvent::eDoNotEmit; - continue; - } - - // Coalesce earlier event of the same target. - if (thisEvent->mAccessible == aTailEvent->mAccessible) { - thisEvent->mEventRule = AccEvent::eDoNotEmit; - - return; - } - - // If tailEvent contains thisEvent - // then - // if show or hide of tailEvent contains a grand parent of thisEvent - // then ignore thisEvent and its show and hide events - // otherwise ignore thisEvent but not its show and hide events - Accessible* thisParent = thisEvent->mAccessible; - while (thisParent && thisParent != mDocument) { - if (thisParent->Parent() == aTailEvent->mAccessible) { - AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent); - uint32_t eventType = tailReorder->IsShowHideEventTarget(thisParent); - - // It can be either hide or show events which may occur because of - // accessible reparenting. - if (eventType == nsIAccessibleEvent::EVENT_SHOW || - eventType == nsIAccessibleEvent::EVENT_HIDE) { - AccReorderEvent* thisReorder = downcast_accEvent(thisEvent); - thisReorder->DoNotEmitAll(); - } else { - thisEvent->mEventRule = AccEvent::eDoNotEmit; - } - - return; - } - - thisParent = thisParent->Parent(); - } - - // If tailEvent is contained by thisEvent - // then - // if show of thisEvent contains the tailEvent - // then ignore tailEvent - // if hide of thisEvent contains the tailEvent - // then assert - // otherwise ignore tailEvent but not its show and hide events - Accessible* tailParent = aTailEvent->mAccessible; - while (tailParent && tailParent != mDocument) { - if (tailParent->Parent() == thisEvent->mAccessible) { - AccReorderEvent* thisReorder = downcast_accEvent(thisEvent); - AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent); - uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent); - if (eventType == nsIAccessibleEvent::EVENT_SHOW) { - tailReorder->DoNotEmitAll(); - } - else if (eventType == nsIAccessibleEvent::EVENT_HIDE) { - NS_ERROR("Accessible tree was modified after it was removed! Huh?"); - } - else { - aTailEvent->mEventRule = AccEvent::eDoNotEmit; - mEvents[index].swap(mEvents[count - 1]); - } - - return; - } - - tailParent = tailParent->Parent(); - } - - } // for (index) -} - void EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent, AccSelChangeEvent* aThisEvent, @@ -393,90 +270,6 @@ EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent, aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; } -void -EventQueue::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent, - AccHideEvent* aThisEvent) -{ - // XXX: we need a way to ignore SplitNode and JoinNode() when they do not - // affect the text within the hypertext. - - AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent; - if (!textEvent) - return; - - if (aThisEvent->mNextSibling == aTailEvent->mAccessible) { - aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText); - - } else if (aThisEvent->mPrevSibling == aTailEvent->mAccessible) { - uint32_t oldLen = textEvent->GetLength(); - aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText); - textEvent->mStart -= textEvent->GetLength() - oldLen; - } - - aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent); -} - -void -EventQueue::CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent, - AccShowEvent* aThisEvent) -{ - AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent; - if (!textEvent) - return; - - if (aTailEvent->mAccessible->IndexInParent() == - aThisEvent->mAccessible->IndexInParent() + 1) { - // If tail target was inserted after this target, i.e. tail target is next - // sibling of this target. - aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText); - - } else if (aTailEvent->mAccessible->IndexInParent() == - aThisEvent->mAccessible->IndexInParent() -1) { - // If tail target was inserted before this target, i.e. tail target is - // previous sibling of this target. - nsAutoString startText; - aTailEvent->mAccessible->AppendTextTo(startText); - textEvent->mModifiedText = startText + textEvent->mModifiedText; - textEvent->mStart -= startText.Length(); - } - - aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent); -} - -void -EventQueue::CreateTextChangeEventFor(AccMutationEvent* aEvent) -{ - Accessible* container = aEvent->mAccessible->Parent(); - if (!container) - return; - - HyperTextAccessible* textAccessible = container->AsHyperText(); - if (!textAccessible) - return; - - // Don't fire event for the first html:br in an editor. - if (aEvent->mAccessible->Role() == roles::WHITESPACE) { - nsCOMPtr editor = textAccessible->GetEditor(); - if (editor) { - bool isEmpty = false; - editor->GetDocumentIsEmpty(&isEmpty); - if (isEmpty) - return; - } - } - - int32_t offset = textAccessible->GetChildOffset(aEvent->mAccessible); - - nsAutoString text; - aEvent->mAccessible->AppendTextTo(text); - if (text.IsEmpty()) - return; - - aEvent->mTextChangeEvent = - new AccTextChangeEvent(textAccessible, offset, text, aEvent->IsShow(), - aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput); -} - //////////////////////////////////////////////////////////////////////////////// // EventQueue: event queue @@ -539,18 +332,6 @@ EventQueue::ProcessEventQueue() } nsEventShell::FireEvent(event); - - // Fire text change events. - AccMutationEvent* mutationEvent = downcast_accEvent(event); - if (mutationEvent) { - if (mutationEvent->mTextChangeEvent) - nsEventShell::FireEvent(mutationEvent->mTextChangeEvent); - } - } - - AccHideEvent* hideEvent = downcast_accEvent(event); - if (hideEvent && hideEvent->NeedsShutdown()) { - mDocument->ShutdownChildrenInSubtree(event->mAccessible); } if (!mDocument) diff --git a/accessible/base/EventQueue.h b/accessible/base/EventQueue.h index b80cddcbeb4d..57d1c236df10 100644 --- a/accessible/base/EventQueue.h +++ b/accessible/base/EventQueue.h @@ -26,6 +26,11 @@ protected: */ bool PushEvent(AccEvent* aEvent); + /** + * Puts a name change event into the queue, if needed. + */ + bool PushNameChange(Accessible* aTarget); + /** * Process events from the queue and fires events. */ @@ -53,23 +58,7 @@ private: AccSelChangeEvent* aThisEvent, uint32_t aThisIndex); - /** - * Coalesce text change events caused by sibling hide events. - */ - void CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent, - AccHideEvent* aThisEvent); - void CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent, - AccShowEvent* aThisEvent); - - /** - * Create text change event caused by hide or show event. When a node is - * hidden/removed or shown/appended, the text in an ancestor hyper text will - * lose or get new characters. - */ - void CreateTextChangeEventFor(AccMutationEvent* aEvent); - protected: - /** * The document accessible reference owning this queue. */ @@ -79,7 +68,7 @@ protected: * Pending events array. Don't make this an AutoTArray; we use * SwapElements() on it. */ - nsTArray > mEvents; + nsTArray> mEvents; }; } // namespace a11y diff --git a/accessible/base/EventTree.cpp b/accessible/base/EventTree.cpp new file mode 100644 index 000000000000..b18b1aad7a4a --- /dev/null +++ b/accessible/base/EventTree.cpp @@ -0,0 +1,536 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EventTree.h" + +#include "Accessible-inl.h" +#include "nsEventShell.h" +#include "DocAccessible.h" +#ifdef A11Y_LOG +#include "Logging.h" +#endif + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// TreeMutation class + +EventTree* const TreeMutation::kNoEventTree = reinterpret_cast(-1); + +TreeMutation::TreeMutation(Accessible* aParent, bool aNoEvents) : + mParent(aParent), mStartIdx(UINT32_MAX), + mStateFlagsCopy(mParent->mStateFlags), + mEventTree(aNoEvents ? kNoEventTree : nullptr) +{ +#ifdef DEBUG + mIsDone = false; +#endif + +#ifdef A11Y_LOG + if (mEventTree != kNoEventTree && logging::IsEnabled(logging::eEventTree)) { + logging::MsgBegin("EVENTS_TREE", "reordering tree before"); + logging::AccessibleInfo("reordering for", mParent); + Controller()->RootEventTree().Log(); + logging::MsgEnd(); + + logging::MsgBegin("EVENTS_TREE", "Container tree"); + if (logging::IsEnabled(logging::eVerbose)) { + nsAutoString level; + Accessible* root = mParent->Document(); + do { + const char* prefix = ""; + if (mParent == root) { + prefix = "_X_"; + } + else { + const EventTree& ret = Controller()->RootEventTree(); + if (ret.Find(root)) { + prefix = "_с_"; + } + } + + printf("%s", NS_ConvertUTF16toUTF8(level).get()); + logging::AccessibleInfo(prefix, root); + if (root->FirstChild() && !root->FirstChild()->IsDoc()) { + level.Append(NS_LITERAL_STRING(" ")); + root = root->FirstChild(); + continue; + } + int32_t idxInParent = root->mParent ? + root->mParent->mChildren.IndexOf(root) : -1; + if (idxInParent != -1 && + idxInParent < static_cast(root->mParent->mChildren.Length() - 1)) { + root = root->mParent->mChildren.ElementAt(idxInParent + 1); + continue; + } + + while ((root = root->Parent()) && !root->IsDoc()) { + level.Cut(0, 2); + + int32_t idxInParent = root->mParent ? + root->mParent->mChildren.IndexOf(root) : -1; + if (idxInParent != -1 && + idxInParent < static_cast(root->mParent->mChildren.Length() - 1)) { + root = root->mParent->mChildren.ElementAt(idxInParent + 1); + break; + } + } + } + while (root && !root->IsDoc()); + } + logging::MsgEnd(); + } +#endif + + mParent->mStateFlags |= Accessible::eKidsMutating; +} + +TreeMutation::~TreeMutation() +{ + MOZ_ASSERT(mIsDone, "Done() must be called explicitly"); +} + +void +TreeMutation::AfterInsertion(Accessible* aChild) +{ + MOZ_ASSERT(aChild->Parent() == mParent); + + if (static_cast(aChild->mIndexInParent) < mStartIdx) { + mStartIdx = aChild->mIndexInParent + 1; + } + + if (!mEventTree) { + mEventTree = Controller()->QueueMutation(mParent); + if (!mEventTree) { + mEventTree = kNoEventTree; + } + } + + if (mEventTree != kNoEventTree) { + mEventTree->Shown(aChild); + Controller()->QueueNameChange(aChild); + } +} + +void +TreeMutation::BeforeRemoval(Accessible* aChild, bool aNoShutdown) +{ + MOZ_ASSERT(aChild->Parent() == mParent); + + if (static_cast(aChild->mIndexInParent) < mStartIdx) { + mStartIdx = aChild->mIndexInParent; + } + + if (!mEventTree) { + mEventTree = Controller()->QueueMutation(mParent); + if (!mEventTree) { + mEventTree = kNoEventTree; + } + } + + if (mEventTree != kNoEventTree) { + mEventTree->Hidden(aChild, !aNoShutdown); + Controller()->QueueNameChange(aChild); + } +} + +void +TreeMutation::Done() +{ + MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating); + mParent->mStateFlags &= ~Accessible::eKidsMutating; + + uint32_t length = mParent->mChildren.Length(); +#ifdef DEBUG + for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) { + MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast(idx), + "Wrong index detected"); + } +#endif + + for (uint32_t idx = mStartIdx; idx < length; idx++) { + mParent->mChildren[idx]->mIndexInParent = idx; + mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty; + } + + if (mStartIdx < mParent->mChildren.Length() - 1) { + mParent->mEmbeddedObjCollector = nullptr; + } + + mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating; + +#ifdef DEBUG + mIsDone = true; +#endif + +#ifdef A11Y_LOG + if (mEventTree != kNoEventTree && logging::IsEnabled(logging::eEventTree)) { + logging::MsgBegin("EVENTS_TREE", "reordering tree after"); + logging::AccessibleInfo("reordering for", mParent); + Controller()->RootEventTree().Log(); + logging::MsgEnd(); + } +#endif +} + + +//////////////////////////////////////////////////////////////////////////////// +// EventTree + +void +EventTree::Process() +{ + EventTree* node = mFirst; + while (node) { + node->Process(); + node = node->mNext; + } + + // Fire mutation events. + if (mContainer) { + 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()) { + mContainer->Document()->ShutdownChildrenInSubtree(hideEvent->mAccessible); + } + } + } + + // Fire reorder event at last. + if (mFireReorder) { + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer); + } + } +} + +EventTree* +EventTree::FindOrInsert(Accessible* aContainer) +{ + if (!mFirst) { + return mFirst = new EventTree(aContainer); + } + + EventTree* prevNode = nullptr; + EventTree* node = mFirst; + 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* tailRoot = aContainer->Document(); + Accessible* tailParent = aContainer; + + EventTree* matchNode = nullptr; + Accessible* matchParent = nullptr; + while (true) { + // Reached a top, no match for a current event. + if (tailParent == tailRoot) { + // If we have a match in parents then continue to look in siblings. + if (matchNode && node->mNext) { + node = node->mNext; + if (node->mContainer == aContainer) { + return node; // case of same target + } + tailParent = aContainer; + continue; + } + break; + } + + // We got a match. + if (tailParent->Parent() == node->mContainer) { + matchNode = node; + matchParent = tailParent; + + // Search the subtree for a better match. + if (node->mFirst) { + tailRoot = node->mContainer; + node = node->mFirst; + if (node->mContainer == aContainer) { + return node; // case of same target + } + tailParent = aContainer; + continue; + } + break; + } + + tailParent = tailParent->Parent(); + MOZ_ASSERT(tailParent, "Wrong tree"); + if (!tailParent) { + break; + } + } + + // The given node is contained by a current node + // if hide of a current node contains the given node + // then assert + // if show of a current node contains the given node + // then ignore the given node + // otherwise ignore the given node, but not its show and hide events + if (matchNode) { + uint32_t eventType = 0; + uint32_t count = matchNode->mDependentEvents.Length(); + for (uint32_t idx = count - 1; idx < count; idx--) { + if (matchNode->mDependentEvents[idx]->mAccessible == matchParent) { + eventType = matchNode->mDependentEvents[idx]->mEventType; + } + } + MOZ_ASSERT(eventType != nsIAccessibleEvent::EVENT_HIDE, + "Accessible tree was modified after it was removed"); + + // If contained by show event target then no events are required. + if (eventType == nsIAccessibleEvent::EVENT_SHOW) { + return nullptr; + } + + node->mFirst = new EventTree(aContainer); + node->mFirst->mFireReorder = false; + return node->mFirst; + } + + // If the given node 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; + nsAutoPtr& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst; + nsAutoPtr newNode(new EventTree(aContainer)); + 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; + node = nodeOwnerRef->mNext; + nsAutoPtr* nodeRef = &nodeOwnerRef->mNext; + EventTree* insNode = nodeOwnerRef->mFirst; + 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; + + prevNode->mNext = Move(node->mNext); + node = prevNode; + break; + } + + prevNode = node; + nodeRef = &node->mNext; + node = node->mNext; + } + + return nodeOwnerRef; + } + + prevNode = node; + } while ((node = node->mNext)); + + MOZ_ASSERT(prevNode, "Nowhere to insert"); + return prevNode->mNext = new EventTree(aContainer); +} + +const EventTree* +EventTree::Find(const Accessible* aContainer) const +{ + const EventTree* et = this; + while (et) { + if (et->mContainer == aContainer) { + return et; + } + + if (et->mFirst) { + et = et->mFirst; + const EventTree* cet = et->Find(aContainer); + if (cet) { + return cet; + } + } + + et = et->mNext; + const EventTree* cet = et->Find(aContainer); + if (cet) { + return cet; + } + } + + return nullptr; +} + +#ifdef A11Y_LOG +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; i++) { + printf(" "); + } + logging::AccessibleInfo("shown", ev->mAccessible); + } + else { + for (uint32_t i = 0; i < aLevel; i++) { + printf(" "); + } + logging::AccessibleInfo("hidden", ev->mAccessible); + } + } + + if (mFirst) { + mFirst->Log(aLevel + 1); + } + + if (mNext) { + mNext->Log(aLevel); + } +} +#endif + +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. + EventTree* node = mFirst; + while (node) { + if (node->mContainer == aEv->mAccessible) { + node->Clear(); + break; + } + 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); +} diff --git a/accessible/base/EventTree.h b/accessible/base/EventTree.h new file mode 100644 index 000000000000..d90e3a469c6f --- /dev/null +++ b/accessible/base/EventTree.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_EventTree_h_ +#define mozilla_a11y_EventTree_h_ + +#include "AccEvent.h" +#include "Accessible.h" + +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +/** + * This class makes sure required tasks are done before and after tree + * mutations. Currently this only includes group info invalidation. You must + * have an object of this class on the stack when calling methods that mutate + * the accessible tree. + */ +class TreeMutation final +{ +public: + static const bool kNoEvents = true; + static const bool kNoShutdown = true; + + explicit TreeMutation(Accessible* aParent, bool aNoEvents = false); + ~TreeMutation(); + + void AfterInsertion(Accessible* aChild); + void BeforeRemoval(Accessible* aChild, bool aNoShutdown = false); + void Done(); + +private: + NotificationController* Controller() const + { return mParent->Document()->Controller(); } + + static EventTree* const kNoEventTree; + + Accessible* mParent; + uint32_t mStartIdx; + uint32_t mStateFlagsCopy; + EventTree* mEventTree; + +#ifdef DEBUG + bool mIsDone; +#endif +}; + + +/** + * A mutation events coalescence structure. + */ +class EventTree final { +public: + EventTree() : + mFirst(nullptr), mNext(nullptr), mContainer(nullptr), mFireReorder(true) { } + explicit EventTree(Accessible* aContainer) : + mFirst(nullptr), mNext(nullptr), mContainer(aContainer), mFireReorder(true) { } + ~EventTree() { Clear(); } + + void Shown(Accessible* aChild) + { + RefPtr ev = new AccShowEvent(aChild); + Mutated(ev); + } + + void Hidden(Accessible* aChild, bool aNeedsShutdown = true) + { + RefPtr ev = new AccHideEvent(aChild, aNeedsShutdown); + Mutated(ev); + } + + /** + * Return an event tree node for the given accessible. + */ + const EventTree* Find(const Accessible* aContainer) const; + +#ifdef A11Y_LOG + void Log(uint32_t aLevel = UINT32_MAX) const; +#endif + +private: + /** + * Processes the event queue and fires events. + */ + void Process(); + + /** + * Return an event subtree for the given accessible. + */ + EventTree* FindOrInsert(Accessible* aContainer); + + void Mutated(AccMutationEvent* aEv); + void Clear() { mFirst = nullptr; mNext = nullptr; mContainer = nullptr; } + + nsAutoPtr mFirst; + nsAutoPtr mNext; + + Accessible* mContainer; + nsTArray> mDependentEvents; + bool mFireReorder; + + friend class NotificationController; +}; + + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_EventQueue_h_ diff --git a/accessible/base/Logging.cpp b/accessible/base/Logging.cpp index 233873c92889..1de1ff7d9769 100644 --- a/accessible/base/Logging.cpp +++ b/accessible/base/Logging.cpp @@ -44,6 +44,7 @@ static ModuleRep sModuleMap[] = { { "doclifecycle", logging::eDocLifeCycle }, { "events", logging::eEvents }, + { "eventTree", logging::eEventTree }, { "platforms", logging::ePlatforms }, { "text", logging::eText }, { "tree", logging::eTree }, diff --git a/accessible/base/Logging.h b/accessible/base/Logging.h index 43d0ed63c232..12d4749e6685 100644 --- a/accessible/base/Logging.h +++ b/accessible/base/Logging.h @@ -34,18 +34,19 @@ enum EModules { eDocLifeCycle = eDocLoad | eDocCreate | eDocDestroy, eEvents = 1 << 3, - ePlatforms = 1 << 4, - eText = 1 << 5, - eTree = 1 << 6, + eEventTree = 1 << 4, + ePlatforms = 1 << 5, + eText = 1 << 6, + eTree = 1 << 7, - eDOMEvents = 1 << 7, - eFocus = 1 << 8, - eSelection = 1 << 9, + eDOMEvents = 1 << 8, + eFocus = 1 << 9, + eSelection = 1 << 10, eNotifications = eDOMEvents | eSelection | eFocus, // extras - eStack = 1 << 10, - eVerbose = 1 << 11 + eStack = 1 << 11, + eVerbose = 1 << 12 }; /** diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp index db1831e8d5ff..9e67b2285dc2 100644 --- a/accessible/base/NotificationController.cpp +++ b/accessible/base/NotificationController.cpp @@ -97,6 +97,17 @@ NotificationController::Shutdown() mNotifications.Clear(); mEvents.Clear(); mRelocations.Clear(); + mEventTree.Clear(); +} + +EventTree* +NotificationController::QueueMutation(Accessible* aContainer) +{ + EventTree* tree = mEventTree.FindOrInsert(aContainer); + if (tree) { + ScheduleProcessing(); + } + return tree; } void @@ -388,6 +399,9 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime) // events causes script to run. mObservingState = eRefreshProcessing; + mEventTree.Process(); + mEventTree.Clear(); + ProcessEventQueue(); if (IPCAccessibilityActive()) { diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h index 666ddcc4a3f4..d4c0fe283762 100644 --- a/accessible/base/NotificationController.h +++ b/accessible/base/NotificationController.h @@ -7,6 +7,7 @@ #define mozilla_a11y_NotificationController_h_ #include "EventQueue.h" +#include "EventTree.h" #include "mozilla/IndexSequence.h" #include "mozilla/Tuple.h" @@ -104,14 +105,37 @@ public: void Shutdown(); /** - * Put an accessible event into the queue to process it later. + * Add an accessible event into the queue to process it later. */ void QueueEvent(AccEvent* aEvent) { - if (PushEvent(aEvent)) + if (PushEvent(aEvent)) { ScheduleProcessing(); + } } + /** + * Creates and adds a name change event into the queue for a container of + * the given accessible, if the accessible is a part of name computation of + * the container. + */ + void QueueNameChange(Accessible* aChangeTarget) + { + if (PushNameChange(aChangeTarget)) { + ScheduleProcessing(); + } + } + + /** + * Returns existing event tree for the given the accessible or creates one if + * it doesn't exists yet. + */ + EventTree* QueueMutation(Accessible* aContainer); + +#ifdef A11Y_LOG + const EventTree& RootEventTree() const { return mEventTree; }; +#endif + /** * Schedule binding the child document to the tree of this document. */ @@ -291,6 +315,11 @@ private: * Holds all scheduled relocations. */ nsTArray > mRelocations; + + /** + * Holds all mutation events. + */ + EventTree mEventTree; }; } // namespace a11y diff --git a/accessible/base/moz.build b/accessible/base/moz.build index c8c6c2f22098..e637c9bea93b 100644 --- a/accessible/base/moz.build +++ b/accessible/base/moz.build @@ -35,6 +35,7 @@ UNIFIED_SOURCES += [ 'Asserts.cpp', 'DocManager.cpp', 'EventQueue.cpp', + 'EventTree.cpp', 'Filters.cpp', 'FocusManager.cpp', 'NotificationController.cpp', diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp index 27f0effc8452..bea30f35a6b8 100644 --- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -580,8 +580,9 @@ nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell, #ifdef A11Y_LOG if (logging::IsEnabled(logging::eTree)) { logging::MsgBegin("TREE", "content removed"); - logging::Node("container", aChildNode->GetFlattenedTreeParent()); - logging::Node("content", aChildNode); + logging::Node("container node", aChildNode->GetFlattenedTreeParent()); + logging::Node("content node", aChildNode); + logging::MsgEnd(); } #endif diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index 83c6d50d0533..a2cdea5634fc 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -13,9 +13,11 @@ #include "nsAccUtils.h" #include "nsAccessibilityService.h" #include "ApplicationAccessible.h" +#include "NotificationController.h" #include "nsEventShell.h" #include "nsTextEquivUtils.h" #include "DocAccessibleChild.h" +#include "EventTree.h" #include "Logging.h" #include "Relation.h" #include "Role.h" @@ -2119,6 +2121,11 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) "No move, same index"); MOZ_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given"); + EventTree* eventTree = mDoc->Controller()->QueueMutation(this); + if (eventTree) { + eventTree->Hidden(aChild, false); + } + mEmbeddedObjCollector = nullptr; mChildren.RemoveElementAt(aChild->mIndexInParent); @@ -2127,7 +2134,6 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) // If the child is moved after its current position. if (static_cast(aChild->mIndexInParent) < aNewIndex) { startIdx = aChild->mIndexInParent; - if (aNewIndex == mChildren.Length() + 1) { // The child is moved to the end. mChildren.AppendElement(aChild); @@ -2148,6 +2154,11 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) mChildren[idx]->mStateFlags |= eGroupInfoDirty; mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1; } + + if (eventTree) { + eventTree->Shown(aChild); + mDoc->Controller()->QueueNameChange(aChild); + } } Accessible* @@ -2791,34 +2802,3 @@ KeyBinding::ToAtkFormat(nsAString& aValue) const aValue.Append(mKey); } - - -//////////////////////////////////////////////////////////////////////////////// -// AutoTreeMutation class - -void -AutoTreeMutation::Done() -{ - MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating, - "The parent is not in mutating state."); - mParent->mStateFlags &= ~Accessible::eKidsMutating; - - uint32_t length = mParent->mChildren.Length(); -#ifdef DEBUG - for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) { - MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast(idx), - "Wrong index detected"); - } -#endif - - for (uint32_t idx = mStartIdx; idx < length; idx++) { - mParent->mChildren[idx]->mIndexInParent = idx; - mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty; - } - - if (mStartIdx < mParent->mChildren.Length() - 1) { - mParent->mEmbeddedObjCollector = nullptr; - } - - mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating; -} diff --git a/accessible/generic/Accessible.h b/accessible/generic/Accessible.h index 119527858a26..8857030ebda5 100644 --- a/accessible/generic/Accessible.h +++ b/accessible/generic/Accessible.h @@ -33,6 +33,7 @@ class AccGroupInfo; class ApplicationAccessible; class DocAccessible; class EmbeddedObjCollector; +class EventTree; class HTMLImageMapAccessible; class HTMLLIAccessible; class HyperTextAccessible; @@ -1112,7 +1113,7 @@ protected: friend class DocAccessible; friend class xpcAccessible; - friend class AutoTreeMutation; + friend class TreeMutation; nsAutoPtr mEmbeddedObjCollector; union { @@ -1204,43 +1205,6 @@ private: uint32_t mModifierMask; }; - -/** - * This class makes sure required tasks are done before and after tree - * mutations. Currently this only includes group info invalidation. You must - * have an object of this class on the stack when calling methods that mutate - * the accessible tree. - */ -class AutoTreeMutation -{ -public: - explicit AutoTreeMutation(Accessible* aParent) : - mParent(aParent), mStartIdx(UINT32_MAX), - mStateFlagsCopy(mParent->mStateFlags) - { - mParent->mStateFlags |= Accessible::eKidsMutating; - } - - void AfterInsertion(const Accessible* aChild) { - if (static_cast(aChild->IndexInParent()) < mStartIdx) { - mStartIdx = aChild->IndexInParent() + 1; - } - } - - void BeforeRemoval(const Accessible* aChild) { - if (static_cast(aChild->IndexInParent()) < mStartIdx) { - mStartIdx = aChild->IndexInParent(); - } - } - - void Done(); - -private: - Accessible* mParent; - uint32_t mStartIdx; - uint32_t mStateFlagsCopy; -}; - } // namespace a11y } // namespace mozilla diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index 7cf0ac402312..48e0f204a0af 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -1751,17 +1751,19 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, UpdateRootElIfNeeded(); } - uint32_t updateFlags = 0; - AutoTreeMutation mt(aContainer); - RefPtr reorderEvent = new AccReorderEvent(aContainer); + InsertIterator iter(aContainer, aNodes); + if (!iter.Next()) { + return; + } #ifdef A11Y_LOG logging::TreeInfo("children before insertion", logging::eVerbose, aContainer); #endif - InsertIterator iter(aContainer, aNodes); - while (iter.Next()) { + uint32_t updateFlags = 0; + TreeMutation mt(aContainer); + do { Accessible* parent = iter.Child()->Parent(); if (parent) { if (parent != aContainer) { @@ -1788,12 +1790,13 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, #endif mt.AfterInsertion(iter.Child()); - updateFlags |= UpdateTreeInternal(iter.Child(), true, reorderEvent); + updateFlags |= UpdateTreeInternal(iter.Child(), true); continue; } MOZ_ASSERT_UNREACHABLE("accessible was rejected"); - } + } while (iter.Next()); + mt.Done(); #ifdef A11Y_LOG @@ -1801,7 +1804,7 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, aContainer); #endif - FireEventsOnInsertion(aContainer, reorderEvent, updateFlags); + FireEventsOnInsertion(aContainer, updateFlags); } void @@ -1819,22 +1822,19 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode) } if (child) { - RefPtr reorderEvent = new AccReorderEvent(aContainer); - - AutoTreeMutation mt(aContainer); + TreeMutation mt(aContainer); aContainer->InsertAfter(child, walker.Prev()); mt.AfterInsertion(child); mt.Done(); - uint32_t flags = UpdateTreeInternal(child, true, reorderEvent); - FireEventsOnInsertion(aContainer, reorderEvent, flags); + uint32_t flags = UpdateTreeInternal(child, true); + FireEventsOnInsertion(aContainer, flags); } } } void DocAccessible::FireEventsOnInsertion(Accessible* aContainer, - AccReorderEvent* aReorderEvent, uint32_t aUpdateFlags) { // Content insertion did not cause an accessible tree change. @@ -1857,7 +1857,6 @@ DocAccessible::FireEventsOnInsertion(Accessible* aContainer, } MaybeNotifyOfValueChange(aContainer); - FireDelayedEvent(aReorderEvent); } void @@ -1880,32 +1879,33 @@ DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNod #endif uint32_t updateFlags = eNoAccessible; - RefPtr reorderEvent = new AccReorderEvent(aContainer); - AutoTreeMutation mt(aContainer); + TreeMutation mt(aContainer); if (child) { mt.BeforeRemoval(child); - updateFlags |= UpdateTreeInternal(child, false, reorderEvent); - } else { + updateFlags |= UpdateTreeInternal(child, false); + } + else { TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache); - while (Accessible* child = walker.Next()) { - mt.BeforeRemoval(child); - updateFlags |= UpdateTreeInternal(child, false, reorderEvent); + Accessible* child = walker.Next(); + if (child) { + do { + mt.BeforeRemoval(child); + updateFlags |= UpdateTreeInternal(child, false); + } + while ((child = walker.Next())); } } mt.Done(); // Content insertion/removal is not cause of accessible tree change. - if (updateFlags == eNoAccessible) - return; - - MaybeNotifyOfValueChange(aContainer); - FireDelayedEvent(reorderEvent); + if (updateFlags != eNoAccessible) { + MaybeNotifyOfValueChange(aContainer); + } } uint32_t -DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert, - AccReorderEvent* aReorderEvent) +DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert) { uint32_t updateFlags = eAccessible; @@ -1918,31 +1918,8 @@ DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert, if (aIsInsert) { // Create accessible tree for shown accessible. CacheChildrenInSubtree(aChild, &focusedAcc); - - } else { - // Fire menupopup end event before 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 (aChild->ARIARole() == roles::MENUPOPUP) - FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild); } - // Fire show/hide event. - RefPtr event; - if (aIsInsert) - event = new AccShowEvent(aChild); - else - event = new AccHideEvent(aChild); - - FireDelayedEvent(event); - aReorderEvent->AddSubMutationEvent(event); - if (aIsInsert) { roles::Role ariaRole = aChild->ARIARole(); if (ariaRole == roles::MENUPOPUP) { @@ -2065,7 +2042,7 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) if (aOwner->IsAcceptableChild(childEl)) { child = GetAccService()->CreateAccessible(childEl, aOwner); if (child) { - AutoTreeMutation imut(aOwner); + TreeMutation imut(aOwner); aOwner->InsertChildAt(insertIdx, child); imut.AfterInsertion(child); imut.Done(); @@ -2076,9 +2053,8 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) insertIdx = child->IndexInParent() + 1; arrayIdx++; - RefPtr reorderEvent = new AccReorderEvent(aOwner); - uint32_t flags = UpdateTreeInternal(child, true, reorderEvent); - FireEventsOnInsertion(aOwner, reorderEvent, flags); + uint32_t flags = UpdateTreeInternal(child, true); + FireEventsOnInsertion(aOwner, flags); } } continue; @@ -2196,19 +2172,8 @@ DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent, if (curParent == aNewParent) { MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case"); - RefPtr reorderEvent = new AccReorderEvent(curParent); - RefPtr hideEvent = new AccHideEvent(aChild, false); - reorderEvent->AddSubMutationEvent(hideEvent); - FireDelayedEvent(hideEvent); - curParent->MoveChild(aIdxInParent, aChild); - - RefPtr showEvent = new AccShowEvent(aChild); - reorderEvent->AddSubMutationEvent(showEvent); - FireDelayedEvent(showEvent); - MaybeNotifyOfValueChange(curParent); - FireDelayedEvent(reorderEvent); #ifdef A11Y_LOG logging::TreeInfo("move child: parent tree after", @@ -2221,36 +2186,24 @@ DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent, return false; } - RefPtr reorderEvent = new AccReorderEvent(curParent); - RefPtr hideEvent = new AccHideEvent(aChild, false); - reorderEvent->AddSubMutationEvent(hideEvent); - FireDelayedEvent(hideEvent); - - AutoTreeMutation rmut(curParent); - rmut.BeforeRemoval(aChild); + TreeMutation rmut(curParent); + rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown); curParent->RemoveChild(aChild); rmut.Done(); MaybeNotifyOfValueChange(curParent); - FireDelayedEvent(reorderEvent); // No insertion point for the child. if (aIdxInParent == -1) { return true; } - AutoTreeMutation imut(aNewParent); + TreeMutation imut(aNewParent); aNewParent->InsertChildAt(aIdxInParent, aChild); imut.AfterInsertion(aChild); imut.Done(); - reorderEvent = new AccReorderEvent(aNewParent); - RefPtr showEvent = new AccShowEvent(aChild); - reorderEvent->AddSubMutationEvent(showEvent); - FireDelayedEvent(showEvent); - MaybeNotifyOfValueChange(aNewParent); - FireDelayedEvent(reorderEvent); #ifdef A11Y_LOG logging::TreeInfo("move child: old parent tree after", @@ -2275,7 +2228,10 @@ DocAccessible::CacheChildrenInSubtree(Accessible* aRoot, Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot; if (root->KidsFromDOM()) { - AutoTreeMutation mt(root); +#ifdef A11Y_LOG + logging::TreeInfo("caching children", logging::eVerbose, aRoot); +#endif + TreeMutation mt(root, TreeMutation::kNoEvents); TreeWalker walker(root); while (Accessible* child = walker.Next()) { if (child->IsBoundToParent()) { @@ -2291,10 +2247,6 @@ DocAccessible::CacheChildrenInSubtree(Accessible* aRoot, mt.Done(); } -#ifdef A11Y_LOG - logging::TreeInfo("cached children", logging::eVerbose, aRoot); -#endif - // Fire document load complete on ARIA documents. // XXX: we should delay an event if the ARIA document has aria-busy. if (aRoot->HasARIARole() && !aRoot->IsDoc()) { diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h index 5021f9605362..5f03bb208311 100644 --- a/accessible/generic/DocAccessible.h +++ b/accessible/generic/DocAccessible.h @@ -8,8 +8,8 @@ #include "nsIAccessiblePivot.h" -#include "AccEvent.h" #include "HyperTextAccessibleWrap.h" +#include "AccEvent.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" @@ -187,9 +187,7 @@ public: */ void FireDelayedEvent(AccEvent* aEvent); void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget); - void FireEventsOnInsertion(Accessible* aContainer, - AccReorderEvent* aReorderEvent, - uint32_t aUpdateFlags); + void FireEventsOnInsertion(Accessible* aContainer, uint32_t aUpdateFlags); /** * Fire value change event on the given accessible if applicable. @@ -372,6 +370,11 @@ public: */ bool RelocateARIAOwnedIfNeeded(nsIContent* aEl); + /** + * Return a notification controller associated with the document. + */ + NotificationController* Controller() const { return mNotificationController; } + /** * If this document is in a content process return the object responsible for * communicating with the main process for it. @@ -522,9 +525,7 @@ protected: eAccessible = 1, eAlertAccessible = 2 }; - - uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert, - AccReorderEvent* aReorderEvent); + uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert); /** * Validates all aria-owns connections and updates the tree accordingly. @@ -703,7 +704,7 @@ protected: * Used to process notification from core and accessible events. */ RefPtr mNotificationController; - friend class EventQueue; + friend class EventTree; friend class NotificationController; private: diff --git a/accessible/html/HTMLImageMapAccessible.cpp b/accessible/html/HTMLImageMapAccessible.cpp index 5bda4171c46c..d6acbeba6a37 100644 --- a/accessible/html/HTMLImageMapAccessible.cpp +++ b/accessible/html/HTMLImageMapAccessible.cpp @@ -87,9 +87,7 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) if (!imageMapObj) return; - bool treeChanged = false; - AutoTreeMutation mt(this); - RefPtr reorderEvent = new AccReorderEvent(this); + TreeMutation mt(this, TreeMutation::kNoEvents & !aDoFireEvents); // Remove areas that are not a valid part of the image map anymore. for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) { @@ -97,15 +95,8 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) if (area->GetContent()->GetPrimaryFrame()) continue; - if (aDoFireEvents) { - RefPtr event = new AccHideEvent(area, area->GetContent()); - mDoc->FireDelayedEvent(event); - reorderEvent->AddSubMutationEvent(event); - } - mt.BeforeRemoval(area); RemoveChild(area); - treeChanged = true; } // Insert new areas into the tree. @@ -123,22 +114,10 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) } mt.AfterInsertion(area); - - if (aDoFireEvents) { - RefPtr event = new AccShowEvent(area); - mDoc->FireDelayedEvent(event); - reorderEvent->AddSubMutationEvent(event); - } - - treeChanged = true; } } mt.Done(); - - // Fire reorder event if needed. - if (treeChanged && aDoFireEvents) - mDoc->FireDelayedEvent(reorderEvent); } Accessible* diff --git a/accessible/html/HTMLListAccessible.cpp b/accessible/html/HTMLListAccessible.cpp index 6b2bbafed032..c419a630a90e 100644 --- a/accessible/html/HTMLListAccessible.cpp +++ b/accessible/html/HTMLListAccessible.cpp @@ -103,31 +103,19 @@ HTMLLIAccessible::UpdateBullet(bool aHasBullet) return; } - RefPtr reorderEvent = new AccReorderEvent(this); - AutoTreeMutation mt(this); - - DocAccessible* document = Document(); + TreeMutation mt(this); if (aHasBullet) { mBullet = new HTMLListBulletAccessible(mContent, mDoc); - document->BindToDocument(mBullet, nullptr); + mDoc->BindToDocument(mBullet, nullptr); InsertChildAt(0, mBullet); mt.AfterInsertion(mBullet); - - RefPtr event = new AccShowEvent(mBullet); - mDoc->FireDelayedEvent(event); - reorderEvent->AddSubMutationEvent(event); - } else { - RefPtr event = new AccHideEvent(mBullet, mBullet->GetContent()); - mDoc->FireDelayedEvent(event); - reorderEvent->AddSubMutationEvent(event); - + } + else { mt.BeforeRemoval(mBullet); RemoveChild(mBullet); mBullet = nullptr; } mt.Done(); - - mDoc->FireDelayedEvent(reorderEvent); } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/tests/mochitest/actions/test_link.html b/accessible/tests/mochitest/actions/test_link.html index b3683f95024e..dc96951d193e 100644 --- a/accessible/tests/mochitest/actions/test_link.html +++ b/accessible/tests/mochitest/actions/test_link.html @@ -54,9 +54,8 @@ } } - //gA11yEventDumpID = "eventdump"; // debug stuff //gA11yEventDumpToConsole = true; - //enableLogging("tree"); + //enableLogging("tree,eventTree,verbose"); function doTest() { @@ -144,7 +143,5 @@ - -
diff --git a/accessible/tests/mochitest/events/test_aria_alert.html b/accessible/tests/mochitest/events/test_aria_alert.html index 85e413c3ff53..2dab35723734 100644 --- a/accessible/tests/mochitest/events/test_aria_alert.html +++ b/accessible/tests/mochitest/events/test_aria_alert.html @@ -56,10 +56,10 @@ //////////////////////////////////////////////////////////////////////////// // Do tests + //gA11yEventDumpToConsole = true; // debuging + //enableLogging("tree,events,verbose"); + var gQueue = null; - - //gA11yEventDumpID = "eventdump"; - function doTests() { gQueue = new eventQueue(nsIAccessibleEvent.EVENT_ALERT); @@ -87,7 +87,6 @@
   
-
diff --git a/accessible/tests/mochitest/events/test_aria_menu.html b/accessible/tests/mochitest/events/test_aria_menu.html index 2dd00478e5a3..4cd1199f551c 100644 --- a/accessible/tests/mochitest/events/test_aria_menu.html +++ b/accessible/tests/mochitest/events/test_aria_menu.html @@ -81,8 +81,8 @@ this.menu = null; this.eventSeq = [ - new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this), new invokerChecker(EVENT_HIDE, getMenu, this), + new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this), new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)) ]; @@ -150,11 +150,10 @@ //////////////////////////////////////////////////////////////////////////// // Do tests - var gQueue = null; - //gA11yEventDumpToConsole = true; // debuging - //enableLogging("tree,verbose"); + //enableLogging("tree,events,verbose"); + var gQueue = null; function doTests() { gQueue = new eventQueue(); @@ -257,7 +256,8 @@ + +
+
+
+
+
+
diff --git a/accessible/tests/mochitest/events/test_mutation.html b/accessible/tests/mochitest/events/test_mutation.html index 9154fa9ae9cd..b05ac00883f4 100644 --- a/accessible/tests/mochitest/events/test_mutation.html +++ b/accessible/tests/mochitest/events/test_mutation.html @@ -361,8 +361,8 @@ function showHiddenParentOfVisibleChild() { this.eventSeq = [ - new invokerChecker(EVENT_HIDE, getNode("c4_child")), new invokerChecker(EVENT_SHOW, getNode("c4_middle")), + new invokerChecker(EVENT_HIDE, getNode("c4_child")), new invokerChecker(EVENT_REORDER, getNode("c4")) ]; @@ -415,7 +415,7 @@ } //gA11yEventDumpToConsole = true; // debug stuff - //enableLogging("tree,verbose"); + //enableLogging("events,verbose"); /** * Do tests. diff --git a/accessible/tests/mochitest/events/test_namechange.html b/accessible/tests/mochitest/events/test_namechange.html index 15bbbfd36823..935d865e9f6e 100644 --- a/accessible/tests/mochitest/events/test_namechange.html +++ b/accessible/tests/mochitest/events/test_namechange.html @@ -38,6 +38,27 @@ } } + /** + * No name change on an accessible, because the accessible is recreated. + */ + function setAttr_recreate(aID, aAttr, aValue) + { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible(aID)), + new invokerChecker(EVENT_SHOW, aID) + ]; + this.invoke = function setAttr_recreate_invoke() + { + todo(false, "No accessible recreation should happen, just name change event"); + getNode(aID).setAttribute(aAttr, aValue); + } + + this.getID = function setAttr_recreate_getID() + { + return "set attr '" + aAttr + "', value '" + aValue + "'"; + } + } + //////////////////////////////////////////////////////////////////////////// // Do tests @@ -64,8 +85,7 @@ gQueue.push(new setAttr("tst2", "title", "title", new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2"))); - gQueue.push(new setAttr("tst3", "alt", "alt", - new invokerChecker(EVENT_NAME_CHANGE, "tst3"))); + gQueue.push(new setAttr_recreate("tst3", "alt", "alt")); gQueue.push(new setAttr("tst3", "title", "title", new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst3"))); diff --git a/accessible/tests/mochitest/events/test_selection.html b/accessible/tests/mochitest/events/test_selection.html index 7581ff06ef8c..de25fedc3132 100644 --- a/accessible/tests/mochitest/events/test_selection.html +++ b/accessible/tests/mochitest/events/test_selection.html @@ -114,7 +114,5 @@