From 8655c2747dc837b5364621deadf85d41fbf21f4d Mon Sep 17 00:00:00 2001 From: roytam1 Date: Tue, 14 May 2024 22:53:16 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1412825 - fix lz4 deprecated attribute with clang and c++14; r=RyanVM (059d86484b) - Bug 1245886 - Manually stop the profiler module at the end of all tests, r=me (1e00edd00c) - Bug 1262359 (part 1) - Remove unused |hashRef| parameter from nsDataHandler::ParseURI(). r=jduell. (dcae9e057a) - Bug 1262359 (part 2) - Make the filling in of two parameters optional in nsDataHandler::ParseURI(). r=jduell. (142ff6c86d) - Bug 1262359 (part 3) - Add a missing fallible nsTSubstring_CharT::Assign() variant. r=erahm. (df93f41b86) - Bug 1262359 (part 4) - Make data URL payload assignment fallible in nsDataHandler::ParseURI(). r=jduell. (05868a4269) - Bug 1262359 (part 5) - Add a missing rv check for call to nsDataHandler::ParseURI(). r=jduell. (67d8a9c642) - Bug 1263764 - Make the external string API's Truncate compatible with the internal API's Truncate. r=froydnj (b369693809) - bug 1262563 - stop passing an event to FireShowHideEvent() r=davidb (b2893a18c2) - bug 1262563 - fix how FireShowHideEvent gets the parent of a hide event target r=davidb (fbf7c39766) - bug 1262563 - make FireShowHideEvent() return void r=davidb (c77c6c1d57) - bug 1262563 - make FireShowHideEvent() a method of MaiAtkObject r=davidb (b0cc3aaf06) - Bug 1260237 - remove InvalidateChildren, r=yzen (1eecf43b01) - Bug 1251680 - get container accessible computation should take into account the HTML select, r=marcoz (553274c049) - Bug 1252857 - test value change events for closed HTML select, r=marcoz (e3248842f5) - Bug 1252857 - value change events for HTML:select have wrong target, r=marcoz (734ace8006) - Bug 1105611 - Add tests of nsIAccessibleEditableText with contentediable editors which have ::before or ::after, patch=nakano, surkov, r=yzen (3b423d91cd) - Bug 1249400 - add a test for missed hide events in case of accessible stealing, r=yzen (901c61e650) - Bug 1255009 - insert children into the tree on content insertion instead the recaching, r=yzen (8074d82484) - Bug 1255614 - make ProcessInvalidationList to insert accessibles instead the recaching, r=yzen (89a81d8b3f) - Bug 1255617 - make PutChildrenBack to insert accessibles instead the recaching, r=yzen (39548b5922) - Bug 1260187 - remove recaching version of DocAccessible::UpdateTreeOnInsertion, r=yzen (4bf8b09193) - Bug 1260277 - remove empty CacheChildren's, r=marcoz (4eabc70d60) - Bug 1256461 - merge MoveChild and SeizeChild methods, r=yzen (649b87dfad) - Bug 1260494 - rebuild child indexes by AutoTreeMutation guard, r=yzen (e49a381192) - Bug 1260862 - "remove Cache/EnsureChildren". r=mzehe (10751f0792) - Bug 1260860 - stop illicit accessible stealing, r=yzen (55621a1af3) - Bug 1260496 - get rid of CacheChildren for application accessible, r=marcoz (cde59765c3) - Bug 1250878 - add acceptable child check for HTML select, r=marcoz (6e70925079) - Bug 1252260 - get rid of HTML table CacheChildren, r=marcoz (7108ee2e06) - Bug 1261165 - remove Accessible::ChildrenFlags, r=yzen (6e6c4db99d) - Bug 1261167 - remove Accessible::TestChildCache, r=marcoz (69c9276da0) - Bug 1261170 - add a single node ProcessContentInserted method version, r=yzen (5385e407b8) - Bug 1261177 - split GetOrCreateAccessible method into two (Get and Create versions), r=yzen (ded9e7c0e5) - Bug 1261408 - detect ARIA owned children early to avoid tree moving, r=yzen (ffd090ff2c) - Bug 1261425 - coalesce mutation events by a tree structure, r=yzen (14ca8f3978) - bug 1261144 - rename AccCollector.{h,cpp} to EmbeddedObjCollector.{h,cpp} r=lsocks (386be7f834) - bug 1259023 - make nsIAccessible.parent work with proxies r=yzen (d611ef1fbf) --- accessible/atk/AccessibleWrap.cpp | 70 +- accessible/atk/AccessibleWrap.h | 2 - accessible/atk/nsMai.h | 7 + accessible/base/AccEvent.cpp | 22 - accessible/base/AccEvent.h | 40 +- accessible/base/AccessibleOrProxy.cpp | 27 + accessible/base/AccessibleOrProxy.h | 2 + accessible/base/DocManager.cpp | 3 - ...Collector.cpp => EmbeddedObjCollector.cpp} | 2 +- ...{AccCollector.h => EmbeddedObjCollector.h} | 4 +- accessible/base/EventQueue.cpp | 261 +------ 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 | 24 +- accessible/base/NotificationController.h | 33 +- accessible/base/TreeWalker.cpp | 30 +- accessible/base/moz.build | 4 +- accessible/base/nsAccessibilityService.cpp | 32 +- accessible/base/nsAccessibilityService.h | 7 +- accessible/generic/Accessible.cpp | 187 ++--- accessible/generic/Accessible.h | 131 +--- accessible/generic/ApplicationAccessible.cpp | 16 +- accessible/generic/ApplicationAccessible.h | 5 +- accessible/generic/BaseAccessibles.cpp | 10 +- accessible/generic/BaseAccessibles.h | 3 - accessible/generic/DocAccessible-inl.h | 12 + accessible/generic/DocAccessible.cpp | 670 ++++++++++-------- accessible/generic/DocAccessible.h | 58 +- accessible/generic/HyperTextAccessible.cpp | 5 +- accessible/generic/HyperTextAccessible.h | 2 +- accessible/generic/OuterDocAccessible.cpp | 15 - accessible/generic/OuterDocAccessible.h | 1 - accessible/generic/TextLeafAccessible.cpp | 7 +- accessible/generic/TextLeafAccessible.h | 4 - accessible/html/HTMLImageMapAccessible.cpp | 27 +- accessible/html/HTMLListAccessible.cpp | 37 +- accessible/html/HTMLListAccessible.h | 3 - accessible/html/HTMLSelectAccessible.cpp | 49 +- accessible/html/HTMLSelectAccessible.h | 9 +- accessible/html/HTMLTableAccessible.cpp | 18 +- accessible/html/HTMLTableAccessible.h | 5 +- accessible/mac/AccessibleWrap.h | 1 - accessible/mac/AccessibleWrap.mm | 12 - accessible/mac/mozAccessible.mm | 4 +- .../tests/mochitest/actions/test_general.xul | 3 + .../tests/mochitest/actions/test_link.html | 6 +- .../tests/mochitest/bounds/test_zoom.html | 1 + .../mochitest/editabletext/editabletext.js | 11 +- .../tests/mochitest/editabletext/test_1.html | 53 +- accessible/tests/mochitest/events.js | 32 +- .../mochitest/events/test_aria_alert.html | 7 +- .../mochitest/events/test_aria_menu.html | 10 +- .../mochitest/events/test_coalescence.html | 38 +- .../tests/mochitest/events/test_mutation.html | 29 +- .../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/events/test_valuechange.html | 34 +- .../mochitest/textcaret/test_browserui.xul | 6 +- .../tests/mochitest/tree/test_aria_owns.html | 4 +- .../tests/mochitest/tree/test_dockids.html | 2 + .../tests/mochitest/tree/test_tabbrowser.xul | 3 + .../tests/mochitest/tree/test_txtctrl.xul | 4 + .../tests/mochitest/treeupdate/a11y.ini | 1 + .../mochitest/treeupdate/test_ariadialog.html | 1 + .../mochitest/treeupdate/test_ariaowns.html | 24 +- .../mochitest/treeupdate/test_bug1189277.html | 5 +- .../mochitest/treeupdate/test_contextmenu.xul | 4 +- .../mochitest/treeupdate/test_optgroup.html | 2 +- .../mochitest/treeupdate/test_table.html | 81 +++ .../msaa/HTMLWin32ObjectAccessible.cpp | 11 +- .../windows/msaa/HTMLWin32ObjectAccessible.h | 4 - accessible/xpcom/xpcAccessible.cpp | 5 +- accessible/xul/XULElementAccessibles.cpp | 13 +- accessible/xul/XULElementAccessibles.h | 1 - accessible/xul/XULTreeAccessible.cpp | 9 +- accessible/xul/XULTreeAccessible.h | 3 - accessible/xul/XULTreeGridAccessible.cpp | 8 +- accessible/xul/XULTreeGridAccessible.h | 3 - mfbt/lz4.h | 6 +- netwerk/protocol/data/nsDataChannel.cpp | 8 +- netwerk/protocol/data/nsDataHandler.cpp | 48 +- netwerk/protocol/data/nsDataHandler.h | 10 +- xpcom/glue/nsStringAPI.h | 12 +- xpcom/string/nsTSubstring.cpp | 8 +- xpcom/string/nsTSubstring.h | 3 + 90 files changed, 1778 insertions(+), 1328 deletions(-) create mode 100644 accessible/base/AccessibleOrProxy.cpp rename accessible/base/{AccCollector.cpp => EmbeddedObjCollector.cpp} (98%) rename accessible/base/{AccCollector.h => EmbeddedObjCollector.h} (95%) create mode 100644 accessible/base/EventTree.cpp create mode 100644 accessible/base/EventTree.h create mode 100644 accessible/tests/mochitest/treeupdate/test_table.html diff --git a/accessible/atk/AccessibleWrap.cpp b/accessible/atk/AccessibleWrap.cpp index dd25b25d7b..0d6a504c15 100644 --- a/accessible/atk/AccessibleWrap.cpp +++ b/accessible/atk/AccessibleWrap.cpp @@ -793,24 +793,13 @@ getParentCB(AtkObject *aAtkObj) if (aAtkObj->accessible_parent) return aAtkObj->accessible_parent; - AtkObject* atkParent = nullptr; - if (AccessibleWrap* wrapper = GetAccessibleWrap(aAtkObj)) { - Accessible* parent = wrapper->Parent(); - atkParent = parent ? AccessibleWrap::GetAtkObject(parent) : nullptr; - } else if (ProxyAccessible* proxy = GetProxy(aAtkObj)) { - ProxyAccessible* parent = proxy->Parent(); - if (parent) { - atkParent = GetWrapperFor(parent); - } else { - // Otherwise this should be the proxy for the tab's top level document. - Accessible* outerDocParent = proxy->OuterDocOfRemoteBrowser(); - NS_ASSERTION(outerDocParent, "this document should have an outerDoc as a parent"); - if (outerDocParent) { - atkParent = AccessibleWrap::GetAtkObject(outerDocParent); - } - } + AccessibleOrProxy acc = GetInternalObj(aAtkObj); + if (acc.IsNull()) { + return nullptr; } + AccessibleOrProxy parent = acc.Parent(); + AtkObject* atkParent = !parent.IsNull() ? GetWrapperFor(parent) : nullptr; if (atkParent) atk_object_set_parent(aAtkObj, atkParent); @@ -1101,6 +1090,16 @@ GetWrapperFor(ProxyAccessible* aProxy) return reinterpret_cast(aProxy->GetWrapper() & ~IS_PROXY); } +AtkObject* +GetWrapperFor(AccessibleOrProxy aObj) +{ + if (aObj.IsProxy()) { + return GetWrapperFor(aObj.AsProxy()); + } + + return AccessibleWrap::GetAtkObject(aObj.AsAccessible()); +} + static uint16_t GetInterfacesForProxy(ProxyAccessible* aProxy, uint32_t aInterfaces) { @@ -1355,16 +1354,33 @@ AccessibleWrap::HandleAccEvent(AccEvent* aEvent) break; case nsIAccessibleEvent::EVENT_SHOW: - return FireAtkShowHideEvent(aEvent, atkObj, true); + { + AccMutationEvent* event = downcast_accEvent(aEvent); + Accessible* parentAcc = event ? event->Parent() : accessible->Parent(); + AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc); + NS_ENSURE_STATE(parent); + auto obj = reinterpret_cast(atkObj); + obj->FireAtkShowHideEvent(parent, true, aEvent->IsFromUserInput()); + return NS_OK; + } case nsIAccessibleEvent::EVENT_HIDE: + { // XXX - Handle native dialog accessibles. if (!accessible->IsRoot() && accessible->HasARIARole() && accessible->ARIARole() == roles::DIALOG) { guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } - return FireAtkShowHideEvent(aEvent, atkObj, false); + + AccMutationEvent* event = downcast_accEvent(aEvent); + Accessible* parentAcc = event ? event->Parent() : accessible->Parent(); + AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc); + NS_ENSURE_STATE(parent); + auto obj = reinterpret_cast(atkObj); + obj->FireAtkShowHideEvent(parent, false, aEvent->IsFromUserInput()); + return NS_OK; + } /* * Because dealing with menu is very different between nsIAccessible @@ -1577,19 +1593,13 @@ static const char *kMutationStrings[2][2] = { { HIDE_EVENT, ADD_EVENT }, }; -nsresult -AccessibleWrap::FireAtkShowHideEvent(AccEvent* aEvent, - AtkObject* aObject, bool aIsAdded) +void +MaiAtkObject::FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded, + bool aFromUser) { - int32_t indexInParent = getIndexInParentCB(aObject); - AtkObject *parentObject = getParentCB(aObject); - NS_ENSURE_STATE(parentObject); - - bool isFromUserInput = aEvent->IsFromUserInput(); - const char *signal_name = kMutationStrings[isFromUserInput][aIsAdded]; - g_signal_emit_by_name(parentObject, signal_name, indexInParent, aObject, nullptr); - - return NS_OK; + int32_t indexInParent = getIndexInParentCB(&this->parent); + const char *signal_name = kMutationStrings[aFromUser][aIsAdded]; + g_signal_emit_by_name(aParent, signal_name, indexInParent, this, nullptr); } // static diff --git a/accessible/atk/AccessibleWrap.h b/accessible/atk/AccessibleWrap.h index 9761c6f863..f73d2f0f48 100644 --- a/accessible/atk/AccessibleWrap.h +++ b/accessible/atk/AccessibleWrap.h @@ -79,8 +79,6 @@ protected: nsresult FireAtkStateChangeEvent(AccEvent* aEvent, AtkObject *aObject); nsresult FireAtkTextChangedEvent(AccEvent* aEvent, AtkObject *aObject); - nsresult FireAtkShowHideEvent(AccEvent* aEvent, AtkObject *aObject, - bool aIsAdded); AtkObject *mAtkObject; diff --git a/accessible/atk/nsMai.h b/accessible/atk/nsMai.h index 76b7f260dc..6f004d3f79 100644 --- a/accessible/atk/nsMai.h +++ b/accessible/atk/nsMai.h @@ -69,6 +69,7 @@ mozilla::a11y::AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj); mozilla::a11y::ProxyAccessible* GetProxy(AtkObject* aAtkObj); mozilla::a11y::AccessibleOrProxy GetInternalObj(AtkObject* aObj); AtkObject* GetWrapperFor(mozilla::a11y::ProxyAccessible* aProxy); +AtkObject* GetWrapperFor(mozilla::a11y::AccessibleOrProxy aObj); extern int atkMajorVersion, atkMinorVersion; @@ -120,6 +121,12 @@ struct MaiAtkObject void FireTextChangeEvent(const nsString& aStr, int32_t aStart, uint32_t aLen, bool aIsInsert, bool aIsFromUser); + /** + * Notify ATK of a shown or hidden subtree rooted at aObject whose parent is + * aParent + */ + void FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded, bool aFromUser); + private: /* * do we have text-remove and text-insert signals if not we need to use diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp index aa778d2744..5e5e9d15bb 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 0d99069d0f..58bad64196 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; }; @@ -307,37 +306,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/AccessibleOrProxy.cpp b/accessible/base/AccessibleOrProxy.cpp new file mode 100644 index 0000000000..77fb44a117 --- /dev/null +++ b/accessible/base/AccessibleOrProxy.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "AccessibleOrProxy.h" + +AccessibleOrProxy +AccessibleOrProxy::Parent() const +{ + if (IsAccessible()) { + return AsAccessible()->Parent(); + } + + ProxyAccessible* proxy = AsProxy(); + if (!proxy) { + return nullptr; + } + + if (ProxyAccessible* parent = proxy->Parent()) { + return parent; + } + + // Otherwise this should be the proxy for the tab's top level document. + return proxy->OuterDocOfRemoteBrowser(); +} diff --git a/accessible/base/AccessibleOrProxy.h b/accessible/base/AccessibleOrProxy.h index ce2b12ebf3..4b4ebfebdf 100644 --- a/accessible/base/AccessibleOrProxy.h +++ b/accessible/base/AccessibleOrProxy.h @@ -106,6 +106,8 @@ public: return AsAccessible()->Role(); } + AccessibleOrProxy Parent() const; + // XXX these are implementation details that ideally would not be exposed. uintptr_t Bits() const { return mBits; } void SetBits(uintptr_t aBits) { mBits = aBits; } diff --git a/accessible/base/DocManager.cpp b/accessible/base/DocManager.cpp index e74d3fdd03..a83586a816 100644 --- a/accessible/base/DocManager.cpp +++ b/accessible/base/DocManager.cpp @@ -61,9 +61,6 @@ DocManager::GetDocAccessible(nsIDocument* aDocument) if (!aDocument) return nullptr; - // Ensure CacheChildren is called before we query cache. - ApplicationAcc()->EnsureChildren(); - DocAccessible* docAcc = GetExistingDocAccessible(aDocument); if (docAcc) return docAcc; diff --git a/accessible/base/AccCollector.cpp b/accessible/base/EmbeddedObjCollector.cpp similarity index 98% rename from accessible/base/AccCollector.cpp rename to accessible/base/EmbeddedObjCollector.cpp index a4d52c0f86..f46387c214 100644 --- a/accessible/base/AccCollector.cpp +++ b/accessible/base/EmbeddedObjCollector.cpp @@ -2,7 +2,7 @@ * 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 "AccCollector.h" +#include "EmbeddedObjCollector.h" #include "Accessible.h" diff --git a/accessible/base/AccCollector.h b/accessible/base/EmbeddedObjCollector.h similarity index 95% rename from accessible/base/AccCollector.h rename to accessible/base/EmbeddedObjCollector.h index fde121eaea..35d3f4b6b7 100644 --- a/accessible/base/AccCollector.h +++ b/accessible/base/EmbeddedObjCollector.h @@ -2,8 +2,8 @@ * 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_AccCollector_h__ -#define mozilla_a11y_AccCollector_h__ +#ifndef mozilla_a11y_EmbeddedObjCollector_h__ +#define mozilla_a11y_EmbeddedObjCollector_h__ #include "Filters.h" diff --git a/accessible/base/EventQueue.cpp b/accessible/base/EventQueue.cpp index 2cc86de1e7..43da8f7093 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,95 +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); - - // Sometimes InvalidateChildren() and - // DocAccessible::CacheChildrenInSubtree() can conspire to reparent an - // accessible in this case no need for mutation events. Se bug 883708 - // for details. - 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, @@ -395,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 @@ -541,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 b80cddcbeb..57d1c236df 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 0000000000..b18b1aad7a --- /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 0000000000..d90e3a469c --- /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 46dc4c9f86..c8e7f92a15 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 43d0ed63c2..12d4749e66 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 ef024cb333..9e67b2285d 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 @@ -301,13 +312,11 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime) #endif // Make sure the text node is in accessible document still. - Accessible* container = mDocument->GetAccessibleOrContainer(containerNode); - NS_ASSERTION(container, - "Text node having rendered text hasn't accessible document!"); + Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode); + MOZ_ASSERT(container, + "Text node having rendered text hasn't accessible document!"); if (container) { - nsTArray > insertedContents; - insertedContents.AppendElement(textNode); - mDocument->ProcessContentInserted(container, &insertedContents); + mDocument->ProcessContentInserted(container, textNode); } } } @@ -390,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 666ddcc4a3..d4c0fe2837 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/TreeWalker.cpp b/accessible/base/TreeWalker.cpp index 79ad5c9269..dcd25ae08a 100644 --- a/accessible/base/TreeWalker.cpp +++ b/accessible/base/TreeWalker.cpp @@ -275,23 +275,27 @@ TreeWalker::Prev() Accessible* TreeWalker::AccessibleFor(nsIContent* aNode, uint32_t aFlags, bool* aSkipSubtree) { - Accessible* child = nullptr; - if (aFlags & eWalkCache) { - child = mDoc->GetAccessible(aNode); - } - else if (mContext->IsAcceptableChild(aNode)) { - child = GetAccService()-> - GetOrCreateAccessible(aNode, mContext, aSkipSubtree); - } - // Ignore the accessible and its subtree if it was repositioned by means // of aria-owns. - if (child && child->IsRelocated()) { - *aSkipSubtree = true; - return nullptr; + Accessible* child = mDoc->GetAccessible(aNode); + if (child) { + if (child->IsRelocated()) { + *aSkipSubtree = true; + return nullptr; + } + return child; } - return child; + // Create an accessible if allowed. + if (!(aFlags & eWalkCache) && mContext->IsAcceptableChild(aNode)) { + if (mDoc->RelocateARIAOwnedIfNeeded(aNode)) { + *aSkipSubtree = true; + return nullptr; + } + return GetAccService()->CreateAccessible(aNode, mContext, aSkipSubtree); + } + + return nullptr; } dom::AllChildrenIterator* diff --git a/accessible/base/moz.build b/accessible/base/moz.build index 4e5f6a904c..96f8fa6a54 100644 --- a/accessible/base/moz.build +++ b/accessible/base/moz.build @@ -26,7 +26,7 @@ if CONFIG['MOZ_DEBUG']: ] UNIFIED_SOURCES += [ - 'AccCollector.cpp', + 'AccessibleOrProxy.cpp', 'AccEvent.cpp', 'AccGroupInfo.cpp', 'AccIterator.cpp', @@ -34,7 +34,9 @@ UNIFIED_SOURCES += [ 'ARIAStateMap.cpp', 'Asserts.cpp', 'DocManager.cpp', + 'EmbeddedObjCollector.cpp', 'EventQueue.cpp', + 'EventTree.cpp', 'Filters.cpp', 'FocusManager.cpp', 'NotificationController.cpp', diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp index 45fbd968b7..d4551639a9 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 @@ -1010,26 +1011,20 @@ nsAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged) // nsAccessibilityService public Accessible* -nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode, - Accessible* aContext, - bool* aIsSubtreeHidden) +nsAccessibilityService::CreateAccessible(nsINode* aNode, + Accessible* aContext, + bool* aIsSubtreeHidden) { - NS_PRECONDITION(aContext && aNode && !gIsShutdown, - "Maybe let'd do a crash? Oh, yes, baby!"); + MOZ_ASSERT(aContext, "No context provided"); + MOZ_ASSERT(aNode, "No node to create an accessible for"); + MOZ_ASSERT(!gIsShutdown, "No creation after shutdown"); if (aIsSubtreeHidden) *aIsSubtreeHidden = false; DocAccessible* document = aContext->Document(); - - // Check to see if we already have an accessible for this node in the cache. - // XXX: we don't have context check here. It doesn't really necessary until - // we have in-law children adoption. - Accessible* cachedAccessible = document->GetAccessible(aNode); - if (cachedAccessible) - return cachedAccessible; - - // No cache entry, so we must create the accessible. + MOZ_ASSERT(!document->GetAccessible(aNode), + "We already have an accessible for this node."); if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) { // If it's document node then ask accessible document loader for @@ -1327,12 +1322,15 @@ nsAccessibilityService::Init() logging::CheckEnv(); #endif + gAccessibilityService = this; + if (XRE_IsParentProcess()) gApplicationAccessible = new ApplicationAccessibleWrap(); else gApplicationAccessible = new ApplicationAccessible(); NS_ADDREF(gApplicationAccessible); // will release in Shutdown() + gApplicationAccessible->Init(); #ifdef MOZ_CRASHREPORTER CrashReporter:: @@ -1828,9 +1826,7 @@ NS_GetAccessibilityService(nsIAccessibilityService** aResult) statistics::A11yInitialized(); - nsAccessibilityService::gAccessibilityService = service; NS_ADDREF(*aResult = service); - return NS_OK; } diff --git a/accessible/base/nsAccessibilityService.h b/accessible/base/nsAccessibilityService.h index aca7a3dcaf..0cfb4b82f3 100644 --- a/accessible/base/nsAccessibilityService.h +++ b/accessible/base/nsAccessibilityService.h @@ -180,16 +180,15 @@ public: static bool IsShutdown() { return gIsShutdown; } /** - * Return an accessible for the given DOM node from the cache or create new - * one. + * Creates an accessible for the given DOM node. * * @param aNode [in] the given node * @param aContext [in] context the accessible is created in * @param aIsSubtreeHidden [out, optional] indicates whether the node's * frame and its subtree is hidden */ - Accessible* GetOrCreateAccessible(nsINode* aNode, Accessible* aContext, - bool* aIsSubtreeHidden = nullptr); + Accessible* CreateAccessible(nsINode* aNode, Accessible* aContext, + bool* aIsSubtreeHidden = nullptr); mozilla::a11y::role MarkupRole(const nsIContent* aContent) const { diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index 2a7c4cd0d2..ce47ec7d4a 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -7,15 +7,18 @@ #include "nsIXBLAccessible.h" -#include "AccCollector.h" +#include "EmbeddedObjCollector.h" #include "AccGroupInfo.h" #include "AccIterator.h" #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" #include "RootAccessible.h" @@ -106,7 +109,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(Accessible, LastRelease()) Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) : mContent(aContent), mDoc(aDoc), - mParent(nullptr), mIndexInParent(-1), mChildrenFlags(eChildrenUninitialized), + mParent(nullptr), mIndexInParent(-1), mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0), mRoleMapEntry(nullptr) { @@ -1879,7 +1882,14 @@ Accessible::Shutdown() // other accessibles, also make sure none of its children point to this parent mStateFlags |= eIsDefunct; - InvalidateChildren(); + int32_t childCount = mChildren.Length(); + for (int32_t childIdx = 0; childIdx < childCount; childIdx++) { + mChildren.ElementAt(childIdx)->UnbindFromParent(); + } + mChildren.Clear(); + + mEmbeddedObjCollector = nullptr; + if (mParent) mParent->RemoveChild(this); @@ -1954,26 +1964,21 @@ Accessible::NativeName(nsString& aName) void Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) { - NS_PRECONDITION(aParent, "This method isn't used to set null parent!"); + MOZ_ASSERT(aParent, "This method isn't used to set null parent"); + MOZ_ASSERT(!mParent, "The child was expected to be moved"); +#ifdef A11Y_LOG if (mParent) { - if (mParent != aParent) { - NS_ERROR("Adopting child!"); - mParent->InvalidateChildrenGroupInfo(); - mParent->RemoveChild(this); - } else { - NS_ERROR("Binding to the same parent!"); - return; - } + logging::TreeInfo("BindToParent: stealing accessible", 0, + "old parent", mParent.get(), + "new parent", aParent, + "child", this, nullptr); } +#endif mParent = aParent; mIndexInParent = aIndexInParent; -#ifdef DEBUG - AssertInMutatingSubtree(); -#endif - // Note: this is currently only used for richlistitems and their children. if (mParent->HasNameDependentParent() || mParent->IsXULListItem()) mContextFlags |= eHasNameDependentParent; @@ -1992,9 +1997,6 @@ Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) void Accessible::UnbindFromParent() { -#ifdef DEBUG - AssertInMutatingSubtree(); -#endif mParent = nullptr; mIndexInParent = -1; mInt.mIndexOfEmbeddedChild = -1; @@ -2056,19 +2058,6 @@ Accessible::Language(nsAString& aLanguage) } } -void -Accessible::InvalidateChildren() -{ - int32_t childCount = mChildren.Length(); - for (int32_t childIdx = 0; childIdx < childCount; childIdx++) { - mChildren.ElementAt(childIdx)->UnbindFromParent(); - } - - mEmbeddedObjCollector = nullptr; - mChildren.Clear(); - SetChildrenFlag(eChildrenUninitialized); -} - bool Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) { @@ -2083,17 +2072,12 @@ Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) if (!mChildren.InsertElementAt(aIndex, aChild)) return false; - for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) { - NS_ASSERTION(static_cast(mChildren[idx]->mIndexInParent) == idx - 1, - "Accessible child index doesn't match"); - mChildren[idx]->mIndexInParent = idx; - } - - mEmbeddedObjCollector = nullptr; + MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change"); } - if (!nsAccUtils::IsEmbeddedObject(aChild)) - SetChildrenFlag(eMixedChildren); + if (!nsAccUtils::IsEmbeddedObject(aChild)) { + mStateFlags |= eHasTextKids; + } aChild->BindToParent(this, aIndex); return true; @@ -2108,23 +2092,21 @@ Accessible::RemoveChild(Accessible* aChild) if (aChild->mParent != this || aChild->mIndexInParent == -1) return false; - uint32_t index = static_cast(aChild->mIndexInParent); - if (index >= mChildren.Length() || mChildren[index] != aChild) { - NS_ERROR("Child is bound to parent but parent hasn't this child at its index!"); - aChild->UnbindFromParent(); - return false; - } + MOZ_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc(), + "Illicit children change"); - for (uint32_t idx = index + 1; idx < mChildren.Length(); idx++) { - NS_ASSERTION(static_cast(mChildren[idx]->mIndexInParent) == idx, - "Accessible child index doesn't match"); - mChildren[idx]->mIndexInParent = idx - 1; + int32_t index = static_cast(aChild->mIndexInParent); + + // If we adopt a child during a tree construction, then indexes might be not + // rebuilt yet. + if (mChildren.SafeElementAt(index) != aChild) { + index = mChildren.IndexOf(aChild); + MOZ_ASSERT(index != -1, + "Child is bound to parent but parent hasn't this child at its index."); } aChild->UnbindFromParent(); mChildren.RemoveElementAt(index); - mEmbeddedObjCollector = nullptr; - return true; } @@ -2138,10 +2120,10 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) "No move, same index"); MOZ_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given"); -#ifdef DEBUG - // AutoTreeMutation should update group info. - AssertInMutatingSubtree(); -#endif + EventTree* eventTree = mDoc->Controller()->QueueMutation(this); + if (eventTree) { + eventTree->Hidden(aChild, false); + } mEmbeddedObjCollector = nullptr; mChildren.RemoveElementAt(aChild->mIndexInParent); @@ -2151,7 +2133,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); @@ -2169,8 +2150,14 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) for (uint32_t idx = startIdx; idx <= endIdx; idx++) { mChildren[idx]->mIndexInParent = idx; + mChildren[idx]->mStateFlags |= eGroupInfoDirty; mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1; } + + if (eventTree) { + eventTree->Shown(aChild); + mDoc->Controller()->QueueNameChange(aChild); + } } Accessible* @@ -2204,7 +2191,7 @@ Accessible::IndexInParent() const uint32_t Accessible::EmbeddedChildCount() { - if (IsChildrenFlag(eMixedChildren)) { + if (mStateFlags & eHasTextKids) { if (!mEmbeddedObjCollector) mEmbeddedObjCollector = new EmbeddedObjCollector(this); return mEmbeddedObjCollector->Count(); @@ -2216,7 +2203,7 @@ Accessible::EmbeddedChildCount() Accessible* Accessible::GetEmbeddedChildAt(uint32_t aIndex) { - if (IsChildrenFlag(eMixedChildren)) { + if (mStateFlags & eHasTextKids) { if (!mEmbeddedObjCollector) mEmbeddedObjCollector = new EmbeddedObjCollector(this); return mEmbeddedObjCollector ? @@ -2229,7 +2216,7 @@ Accessible::GetEmbeddedChildAt(uint32_t aIndex) int32_t Accessible::GetIndexOfEmbeddedChild(Accessible* aChild) { - if (IsChildrenFlag(eMixedChildren)) { + if (mStateFlags & eHasTextKids) { if (!mEmbeddedObjCollector) mEmbeddedObjCollector = new EmbeddedObjCollector(this); return mEmbeddedObjCollector ? @@ -2542,53 +2529,6 @@ Accessible::LastRelease() delete this; } -void -Accessible::CacheChildren() -{ - NS_ENSURE_TRUE_VOID(Document()); - - TreeWalker walker(this); - - Accessible* child = nullptr; - while ((child = walker.Next()) && AppendChild(child)); -} - -void -Accessible::TestChildCache(Accessible* aCachedChild) const -{ -#ifdef DEBUG - int32_t childCount = mChildren.Length(); - if (childCount == 0) { - NS_ASSERTION(IsChildrenFlag(eChildrenUninitialized), - "No children but initialized!"); - return; - } - - Accessible* child = nullptr; - for (int32_t childIdx = 0; childIdx < childCount; childIdx++) { - child = mChildren[childIdx]; - if (child == aCachedChild) - break; - } - - NS_ASSERTION(child == aCachedChild, - "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!"); -#endif -} - -void -Accessible::EnsureChildren() -{ - NS_ASSERTION(!IsDefunct(), "Caching children for defunct accessible!"); - - if (!IsChildrenFlag(eChildrenUninitialized)) - return; - - // State is embedded children until text leaf accessible is appended. - SetChildrenFlag(eEmbeddedChildren); // Prevent reentry - CacheChildren(); -} - Accessible* Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const { @@ -2666,7 +2606,7 @@ Accessible::GetGroupInfo() if (mBits.groupInfo){ if (HasDirtyGroupInfo()) { mBits.groupInfo->Update(); - SetDirtyGroupInfo(false); + mStateFlags &= ~eGroupInfoDirty; } return mBits.groupInfo; @@ -2676,16 +2616,6 @@ Accessible::GetGroupInfo() return mBits.groupInfo; } -void -Accessible::InvalidateChildrenGroupInfo() -{ - uint32_t length = mChildren.Length(); - for (uint32_t i = 0; i < length; i++) { - Accessible* child = mChildren[i]; - child->SetDirtyGroupInfo(true); - } -} - void Accessible::GetPositionAndSizeInternal(int32_t *aPosInSet, int32_t *aSetSize) { @@ -2767,8 +2697,6 @@ Accessible::GetLevelInternal() void Accessible::StaticAsserts() const { - static_assert(eLastChildrenFlag <= (1 << kChildrenFlagsBits) - 1, - "Accessible::mChildrenFlags was oversized by eLastChildrenFlag!"); static_assert(eLastStateFlag <= (1 << kStateFlagsBits) - 1, "Accessible::mStateFlags was oversized by eLastStateFlag!"); static_assert(eLastAccType <= (1 << kTypeBits) - 1, @@ -2779,22 +2707,6 @@ Accessible::StaticAsserts() const "Accessible::mGenericType was oversized by eLastAccGenericType!"); } -void -Accessible::AssertInMutatingSubtree() const -{ - if (IsDoc() || IsApplication()) - return; - - const Accessible *acc = this; - while (!acc->IsDoc() && !(acc->mStateFlags & eSubtreeMutating)) { - acc = acc->Parent(); - if (!acc) - return; - } - - MOZ_ASSERT(acc->mStateFlags & eSubtreeMutating); -} - //////////////////////////////////////////////////////////////////////////////// // KeyBinding class @@ -2889,4 +2801,3 @@ KeyBinding::ToAtkFormat(nsAString& aValue) const aValue.Append(mKey); } - diff --git a/accessible/generic/Accessible.h b/accessible/generic/Accessible.h index f6992439d9..8857030ebd 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; @@ -375,25 +376,20 @@ public: void SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry) { mRoleMapEntry = aRoleMapEntry; } - /** - * Cache children if necessary. - */ - void EnsureChildren(); - - /** - * Set the child count to -1 (unknown) and null out cached child pointers. - * Should be called when accessible tree is changed because document has - * transformed. Note, if accessible cares about its parent relation chain - * itself should override this method to do nothing. - */ - virtual void InvalidateChildren(); - /** * Append/insert/remove a child. Return true if operation was successful. */ bool AppendChild(Accessible* aChild) { return InsertChildAt(mChildren.Length(), aChild); } virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild); + + bool InsertAfter(Accessible* aNewChild, Accessible* aRefChild) + { + MOZ_ASSERT(aNewChild, "No new child to insert"); + return InsertChildAt(aRefChild ? aRefChild->IndexInParent() + 1 : 0, + aNewChild); + } + virtual bool RemoveChild(Accessible* aChild); /** @@ -474,12 +470,6 @@ public: Accessible* ContentChildAt(uint32_t aIndex) const { return mChildren.ElementAt(aIndex); } - /** - * Return true if children were initialized. - */ - inline bool AreChildrenCached() const - { return !IsChildrenFlag(eChildrenUninitialized); } - /** * Return true if the accessible is attached to tree. */ @@ -513,12 +503,6 @@ public: virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0, uint32_t aLength = UINT32_MAX); - /** - * Assert if child not in parent's cache if the cache was initialized at this - * point. - */ - void TestChildCache(Accessible* aCachedChild) const; - /** * Return boundaries in screen coordinates. */ @@ -590,6 +574,7 @@ public: HyperTextAccessible* AsHyperText(); bool IsHTMLBr() const { return mType == eHTMLBRType; } + bool IsHTMLCaption() const { return mType == eHTMLCaptionType; } bool IsHTMLCombobox() const { return mType == eHTMLComboboxType; } bool IsHTMLFileInput() const { return mType == eHTMLFileInputType; } @@ -932,7 +917,13 @@ public: * Return true if the accessible doesn't allow accessible children from XBL * anonymous subtree. */ - bool NoXBLKids() { return mStateFlags & eNoXBLKids; } + bool NoXBLKids() const { return mStateFlags & eNoXBLKids; } + + /** + * Return true if the accessible allows accessible children from subtree of + * a DOM element of this accessible. + */ + bool KidsFromDOM() const { return !(mStateFlags & eNoKidsFromDOM); } /** * Return true if this accessible has a parent whose name depends on this @@ -976,11 +967,6 @@ protected: */ void LastRelease(); - /** - * Cache accessible children. - */ - virtual void CacheChildren(); - /** * Set accessible parent and index in parent. */ @@ -993,31 +979,8 @@ protected: virtual Accessible* GetSiblingAtOffset(int32_t aOffset, nsresult *aError = nullptr) const; - /** - * Flags used to describe the state and type of children. - */ - enum ChildrenFlags { - eChildrenUninitialized = 0, // children aren't initialized - eMixedChildren = 1 << 0, // text leaf children are presented - eEmbeddedChildren = 1 << 1, // all children are embedded objects - - eLastChildrenFlag = eEmbeddedChildren - }; - - /** - * Return true if the children flag is set. - */ - bool IsChildrenFlag(ChildrenFlags aFlag) const - { return static_cast(mChildrenFlags) == aFlag; } - - /** - * Set children flag. - */ - void SetChildrenFlag(ChildrenFlags aFlag) { mChildrenFlags = aFlag; } - /** * Flags used to describe the state of this accessible. - * @note keep these flags in sync with ChildrenFlags */ enum StateFlags { eIsDefunct = 1 << 0, // accessible is defunct @@ -1026,13 +989,15 @@ protected: eNotNodeMapEntry = 1 << 3, // accessible shouldn't be in document node map eHasNumericValue = 1 << 4, // accessible has a numeric value eGroupInfoDirty = 1 << 5, // accessible needs to update group info - eSubtreeMutating = 1 << 6, // subtree is being mutated + eKidsMutating = 1 << 6, // subtree is being mutated eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them eRelocated = 1 << 9, // accessible was moved in tree eNoXBLKids = 1 << 10, // accessible don't allows XBL children + eNoKidsFromDOM = 1 << 11, // accessible doesn't allow children from DOM + eHasTextKids = 1 << 12, // accessible have a text leaf in children - eLastStateFlag = eNoXBLKids + eLastStateFlag = eNoKidsFromDOM }; /** @@ -1123,22 +1088,6 @@ protected: */ AccGroupInfo* GetGroupInfo(); - /** - * Set dirty state of the accessible's group info. - */ - inline void SetDirtyGroupInfo(bool aIsDirty) - { - if (aIsDirty) - mStateFlags |= eGroupInfoDirty; - else - mStateFlags &= ~eGroupInfoDirty; - } - - /** - * Flag all children group info as needing to be updated. - */ - void InvalidateChildrenGroupInfo(); - // Data Members nsCOMPtr mContent; DocAccessible* mDoc; @@ -1147,27 +1096,24 @@ protected: nsTArray > mChildren; int32_t mIndexInParent; - static const uint8_t kChildrenFlagsBits = 2; - static const uint8_t kStateFlagsBits = 11; + static const uint8_t kStateFlagsBits = 13; static const uint8_t kContextFlagsBits = 3; static const uint8_t kTypeBits = 6; static const uint8_t kGenericTypesBits = 15; /** - * Keep in sync with ChildrenFlags, StateFlags, ContextFlags, and AccTypes. + * Keep in sync with StateFlags, ContextFlags, and AccTypes. */ - uint32_t mChildrenFlags : kChildrenFlagsBits; uint32_t mStateFlags : kStateFlagsBits; uint32_t mContextFlags : kContextFlagsBits; uint32_t mType : kTypeBits; uint32_t mGenericTypes : kGenericTypesBits; void StaticAsserts() const; - void AssertInMutatingSubtree() const; friend class DocAccessible; friend class xpcAccessible; - friend class AutoTreeMutation; + friend class TreeMutation; nsAutoPtr mEmbeddedObjCollector; union { @@ -1259,35 +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* aRoot, bool aInvalidationRequired = true) : - mInvalidationRequired(aInvalidationRequired), mRoot(aRoot) - { - MOZ_ASSERT(!(mRoot->mStateFlags & Accessible::eSubtreeMutating)); - mRoot->mStateFlags |= Accessible::eSubtreeMutating; - } - ~AutoTreeMutation() - { - if (mInvalidationRequired) - mRoot->InvalidateChildrenGroupInfo(); - - MOZ_ASSERT(mRoot->mStateFlags & Accessible::eSubtreeMutating); - mRoot->mStateFlags &= ~Accessible::eSubtreeMutating; - } - - bool mInvalidationRequired; -private: - Accessible* mRoot; -}; - } // namespace a11y } // namespace mozilla diff --git a/accessible/generic/ApplicationAccessible.cpp b/accessible/generic/ApplicationAccessible.cpp index 4a2f1a3875..3b17c598d0 100644 --- a/accessible/generic/ApplicationAccessible.cpp +++ b/accessible/generic/ApplicationAccessible.cpp @@ -150,29 +150,15 @@ ApplicationAccessible::NativeState() return 0; } -void -ApplicationAccessible::InvalidateChildren() -{ - // Do nothing because application children are kept updated by AppendChild() - // and RemoveChild() method calls. -} - KeyBinding ApplicationAccessible::AccessKey() const { return KeyBinding(); } -//////////////////////////////////////////////////////////////////////////////// -// Accessible protected methods - void -ApplicationAccessible::CacheChildren() +ApplicationAccessible::Init() { - // CacheChildren is called only once for application accessible when its - // children are requested because empty InvalidateChldren() prevents its - // repeated calls. - // Basically children are kept updated by Append/RemoveChild method calls. // However if there are open windows before accessibility was started // then we need to make sure root accessibles for open windows are created so diff --git a/accessible/generic/ApplicationAccessible.h b/accessible/generic/ApplicationAccessible.h index cee505134e..261529ffda 100644 --- a/accessible/generic/ApplicationAccessible.h +++ b/accessible/generic/ApplicationAccessible.h @@ -52,12 +52,12 @@ public: EWhichChildAtPoint aWhichChild) override; virtual Accessible* FocusedChild() override; - virtual void InvalidateChildren() override; - // ActionAccessible virtual KeyBinding AccessKey() const override; // ApplicationAccessible + void Init(); + void AppName(nsAString& aName) const { nsAutoCString cname; @@ -88,7 +88,6 @@ protected: virtual ~ApplicationAccessible() {} // Accessible - virtual void CacheChildren() override; virtual Accessible* GetSiblingAtOffset(int32_t aOffset, nsresult *aError = nullptr) const override; diff --git a/accessible/generic/BaseAccessibles.cpp b/accessible/generic/BaseAccessibles.cpp index 04c31d39bb..11d4f94a77 100644 --- a/accessible/generic/BaseAccessibles.cpp +++ b/accessible/generic/BaseAccessibles.cpp @@ -24,6 +24,7 @@ LeafAccessible:: LeafAccessible(nsIContent* aContent, DocAccessible* aDoc) : AccessibleWrap(aContent, aDoc) { + mStateFlags |= eNoKidsFromDOM; } NS_IMPL_ISUPPORTS_INHERITED0(LeafAccessible, Accessible) @@ -53,15 +54,6 @@ LeafAccessible::RemoveChild(Accessible* aChild) return false; } -//////////////////////////////////////////////////////////////////////////////// -// LeafAccessible: Accessible private - -void -LeafAccessible::CacheChildren() -{ - // No children for leaf accessible. -} - //////////////////////////////////////////////////////////////////////////////// // LinkableAccessible diff --git a/accessible/generic/BaseAccessibles.h b/accessible/generic/BaseAccessibles.h index 71e932949b..560093151f 100644 --- a/accessible/generic/BaseAccessibles.h +++ b/accessible/generic/BaseAccessibles.h @@ -40,9 +40,6 @@ public: protected: virtual ~LeafAccessible() {} - - // Accessible - virtual void CacheChildren() override; }; /** diff --git a/accessible/generic/DocAccessible-inl.h b/accessible/generic/DocAccessible-inl.h index 22226b2df9..44a4953d04 100644 --- a/accessible/generic/DocAccessible-inl.h +++ b/accessible/generic/DocAccessible-inl.h @@ -22,6 +22,18 @@ namespace mozilla { namespace a11y { +inline Accessible* +DocAccessible::AccessibleOrTrueContainer(nsINode* aNode) const +{ + // HTML comboboxes have no-content list accessible as an intermediate + // containing all options. + Accessible* container = GetAccessibleOrContainer(aNode); + if (container && container->IsHTMLCombobox()) { + return container->FirstChild(); + } + return container; +} + inline nsIAccessiblePivot* DocAccessible::VirtualCursor() { diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index ee297f4e98..1f23ed3153 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -387,33 +387,6 @@ DocAccessible::DocType(nsAString& aType) const docType->GetPublicId(aType); } -Accessible* -DocAccessible::GetAccessible(nsINode* aNode) const -{ - Accessible* accessible = mNodeToAccessibleMap.Get(aNode); - - // No accessible in the cache, check if the given ID is unique ID of this - // document accessible. - if (!accessible) { - if (GetNode() != aNode) - return nullptr; - - accessible = const_cast(this); - } - -#ifdef DEBUG - // All cached accessible nodes should be in the parent - // It will assert if not all the children were created - // when they were first cached, and no invalidation - // ever corrected parent accessible's child cache. - Accessible* parent = accessible->Parent(); - if (parent) - parent->TestChildCache(accessible); -#endif - - return accessible; -} - //////////////////////////////////////////////////////////////////////////////// // Accessible @@ -495,13 +468,7 @@ DocAccessible::Shutdown() mDependentIDsHash.Clear(); mNodeToAccessibleMap.Clear(); - - { - // We're about to get rid of all of our children so there won't be anything - // to invalidate. - AutoTreeMutation mut(this, false); - ClearCache(mAccessibleCache); - } + ClearCache(mAccessibleCache); HyperTextAccessibleWrap::Shutdown(); @@ -1296,8 +1263,6 @@ DocAccessible::BindToDocument(Accessible* aAccessible, if (el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_owns)) { mNotificationController->ScheduleRelocation(aAccessible); } - - RelocateARIAOwnedIfNeeded(el); } } @@ -1348,7 +1313,7 @@ DocAccessible::ContentInserted(nsIContent* aContainerNode, // Update the whole tree of this document accessible when the container is // null (document element is inserted or removed). Accessible* container = aContainerNode ? - GetAccessibleOrContainer(aContainerNode) : this; + AccessibleOrTrueContainer(aContainerNode) : this; if (container) { // Ignore notification if the container node is no longer in the DOM tree. mNotificationController->ScheduleContentInsertion(container, @@ -1389,8 +1354,9 @@ DocAccessible::ProcessInvalidationList() nsIContent* content = mInvalidationList[idx]; if (!HasAccessible(content)) { Accessible* container = GetContainerAccessible(content); - if (container) - UpdateTreeOnInsertion(container); + if (container) { + ProcessContentInserted(container, content); + } } } @@ -1461,9 +1427,7 @@ DocAccessible::DoInitialUpdate() // Set up a root element and ARIA role mapping. UpdateRootElIfNeeded(); - // Build initial tree. Since its the initial tree there's no group info to - // invalidate. - AutoTreeMutation mut(this, false); + // Build initial tree. CacheChildrenInSubtree(this); // Fire reorder event after the document tree is constructed. Note, since @@ -1668,95 +1632,218 @@ DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement, return false; } -void -DocAccessible::ProcessContentInserted(Accessible* aContainer, - const nsTArray >* aInsertedContent) +/** + * Content insertion helper. + */ +class InsertIterator final { - // Process insertions if the container accessible is still in tree. - if (!HasAccessible(aContainer->GetNode())) - return; +public: + InsertIterator(Accessible* aContext, + const nsTArray >* aNodes) : + mChild(nullptr), mChildBefore(nullptr), mWalker(aContext), + mStopNode(nullptr), mNodes(aNodes), mNodesIdx(0) + { + MOZ_ASSERT(aContext, "No context"); + MOZ_ASSERT(aNodes, "No nodes to search for accessible elements"); + MOZ_COUNT_CTOR(InsertIterator); + } + ~InsertIterator() { MOZ_COUNT_DTOR(InsertIterator); } + + Accessible* Context() const { return mWalker.Context(); } + Accessible* Child() const { return mChild; } + Accessible* ChildBefore() const { return mChildBefore; } + DocAccessible* Document() const { return mWalker.Document(); } + + /** + * Iterates to a next accessible within the inserted content. + */ + bool Next(); + +private: + Accessible* mChild; + Accessible* mChildBefore; + TreeWalker mWalker; + nsIContent* mStopNode; + + const nsTArray >* mNodes; + uint32_t mNodesIdx; +}; + +bool +InsertIterator::Next() +{ + if (mNodesIdx > 0) { + Accessible* nextChild = mWalker.Next(mStopNode); + if (nextChild) { + mChildBefore = mChild; + mChild = nextChild; + return true; + } + } + + while (mNodesIdx < mNodes->Length()) { + // Ignore nodes that are not contained by the container anymore. - for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) { // The container might be changed, for example, because of the subsequent // overlapping content insertion (i.e. other content was inserted between // this inserted content and its container or the content was reinserted // into different container of unrelated part of tree). To avoid a double // processing of the content insertion ignore this insertion notification. - // Note, the inserted content might be not in tree at all at this point what - // means there's no container. Ignore the insertion too. - - Accessible* container = - GetContainerAccessible(aInsertedContent->ElementAt(idx)); - if (container != aContainer) + // Note, the inserted content might be not in tree at all at this point + // what means there's no container. Ignore the insertion too. + nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1); + nsIContent* node = mNodes->ElementAt(mNodesIdx++); + Accessible* container = Document()->AccessibleOrTrueContainer(node); + if (container != Context()) { continue; - - if (container == this) { - // If new root content has been inserted then update it. - UpdateRootElIfNeeded(); - - // Continue to update the tree even if we don't have root content. - // For example, elements may be inserted under the document element while - // there is no HTML body element. } - // HTML comboboxes have no-content list accessible as an intermidiate + // HTML comboboxes have no-content list accessible as an intermediate // containing all options. - if (container->IsHTMLCombobox()) + if (container->IsHTMLCombobox()) { container = container->FirstChild(); + } - // We have a DOM/layout change under the container accessible, and its tree - // might need an update. Since DOM/layout change of the element may affect - // on the accessibleness of adjacent elements (for example, insertion of - // extra HTML:body make the old body accessible) then we have to recache - // children of the container, and then fire show/hide events for a change. - UpdateTreeOnInsertion(container); - break; + if (!container->IsAcceptableChild(node)) { + continue; + } + +#ifdef A11Y_LOG + logging::TreeInfo("traversing an inserted node", logging::eVerbose, + "container", container, "node", node); +#endif + + // If inserted nodes are siblings then just move the walker next. + if (prevNode && prevNode->GetNextSibling() == node) { + mStopNode = node; + Accessible* nextChild = mWalker.Next(mStopNode); + if (nextChild) { + mChildBefore = mChild; + mChild = nextChild; + return true; + } + } + else if (mWalker.Seek(node)) { + mStopNode = node; + mChildBefore = mWalker.Prev(); + mChild = mWalker.Next(mStopNode); + if (mChild) { + return true; + } + } + } + + return false; +} + +void +DocAccessible::ProcessContentInserted(Accessible* aContainer, + const nsTArray >* aNodes) +{ + // Process insertions if the container accessible is still in tree. + if (!aContainer->IsInDocument()) { + return; + } + + // If new root content has been inserted then update it. + if (aContainer == this) { + UpdateRootElIfNeeded(); + } + + InsertIterator iter(aContainer, aNodes); + if (!iter.Next()) { + return; + } + +#ifdef A11Y_LOG + logging::TreeInfo("children before insertion", logging::eVerbose, + aContainer); +#endif + + uint32_t updateFlags = 0; + TreeMutation mt(aContainer); + do { + Accessible* parent = iter.Child()->Parent(); + if (parent) { + if (parent != aContainer) { +#ifdef A11Y_LOG + logging::TreeInfo("stealing accessible", 0, + "old parent", parent, "new parent", + aContainer, "child", iter.Child(), nullptr); +#endif + MOZ_ASSERT_UNREACHABLE("stealing accessible"); + continue; + } + +#ifdef A11Y_LOG + logging::TreeInfo("binding to same parent", logging::eVerbose, + "parent", aContainer, "child", iter.Child(), nullptr); +#endif + continue; + } + + if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) { +#ifdef A11Y_LOG + logging::TreeInfo("accessible was inserted", 0, + "container", aContainer, "child", iter.Child(), nullptr); +#endif + + mt.AfterInsertion(iter.Child()); + updateFlags |= UpdateTreeInternal(iter.Child(), true); + continue; + } + + MOZ_ASSERT_UNREACHABLE("accessible was rejected"); + } while (iter.Next()); + + mt.Done(); + +#ifdef A11Y_LOG + logging::TreeInfo("children after insertion", logging::eVerbose, + aContainer); +#endif + + FireEventsOnInsertion(aContainer, updateFlags); +} + +void +DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode) +{ + if (!aContainer->IsInDocument()) { + return; + } + + TreeWalker walker(aContainer); + if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) { + Accessible* child = GetAccessible(aNode); + if (!child) { + child = GetAccService()->CreateAccessible(aNode, aContainer); + } + + if (child) { + TreeMutation mt(aContainer); + aContainer->InsertAfter(child, walker.Prev()); + mt.AfterInsertion(child); + mt.Done(); + + uint32_t flags = UpdateTreeInternal(child, true); + FireEventsOnInsertion(aContainer, flags); + } } } void -DocAccessible::UpdateTreeOnInsertion(Accessible* aContainer) +DocAccessible::FireEventsOnInsertion(Accessible* aContainer, + uint32_t aUpdateFlags) { - for (uint32_t idx = 0; idx < aContainer->ContentChildCount(); idx++) { - Accessible* child = aContainer->ContentChildAt(idx); - child->SetSurvivingInUpdate(true); - } - - AutoTreeMutation mut(aContainer); - aContainer->InvalidateChildren(); - aContainer->EnsureChildren(); - - RefPtr reorderEvent = new AccReorderEvent(aContainer); - - uint32_t updateFlags = eNoAccessible; - for (uint32_t idx = 0; idx < aContainer->ContentChildCount(); idx++) { - Accessible* child = aContainer->ContentChildAt(idx); - if (child->IsSurvivingInUpdate()) { - child->SetSurvivingInUpdate(false); - continue; - } - - // A new child has been created, update its tree. -#ifdef A11Y_LOG - if (logging::IsEnabled(logging::eTree)) { - logging::MsgBegin("TREE", "process content insertion"); - logging::Node("container", aContainer->GetNode()); - logging::Node("child", child->GetContent()); - logging::Address("child", child); - logging::MsgEnd(); - } -#endif - - updateFlags |= UpdateTreeInternal(child, true, reorderEvent); - } - - // Content insertion/removal is not cause of accessible tree change. - if (updateFlags == eNoAccessible) + // Content insertion did not cause an accessible tree change. + if (aUpdateFlags == eNoAccessible) { return; + } // Check to see if change occurred inside an alert, and fire an EVENT_ALERT // if it did. - if (!(updateFlags & eAlertAccessible) && + if (!(aUpdateFlags & eAlertAccessible) && (aContainer->IsAlert() || aContainer->IsInsideAlert())) { Accessible* ancestor = aContainer; do { @@ -1769,7 +1856,6 @@ DocAccessible::UpdateTreeOnInsertion(Accessible* aContainer) } MaybeNotifyOfValueChange(aContainer); - FireDelayedEvent(reorderEvent); } void @@ -1792,29 +1878,33 @@ DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNod #endif uint32_t updateFlags = eNoAccessible; - RefPtr reorderEvent = new AccReorderEvent(aContainer); - AutoTreeMutation mut(aContainer); + TreeMutation mt(aContainer); if (child) { - updateFlags |= UpdateTreeInternal(child, false, reorderEvent); - } else { + mt.BeforeRemoval(child); + updateFlags |= UpdateTreeInternal(child, false); + } + else { TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache); - while (Accessible* child = walker.Next()) { - 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; @@ -1827,31 +1917,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) { @@ -1886,11 +1953,11 @@ DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert, return updateFlags; } -void +bool DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement) { if (!aElement->HasID()) - return; + return false; AttrRelProviderArray* list = mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID())); @@ -1900,10 +1967,13 @@ DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement) Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent); if (owner) { mNotificationController->ScheduleRelocation(owner); + return true; } } } } + + return false; } void @@ -1957,11 +2027,43 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) MOZ_ASSERT(aOwner, "aOwner must be a valid pointer"); MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer"); - IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns); - Accessible* child = nullptr; +#ifdef A11Y_LOG + logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner); +#endif + IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns); uint32_t arrayIdx = 0, insertIdx = aOwner->ChildCount() - children->Length(); - while ((child = iter.Next())) { + while (nsIContent* childEl = iter.NextElem()) { + Accessible* child = GetAccessible(childEl); + + // Make an attempt to create an accessible if it wasn't created yet. + if (!child) { + if (aOwner->IsAcceptableChild(childEl)) { + child = GetAccService()->CreateAccessible(childEl, aOwner); + if (child) { + TreeMutation imut(aOwner); + aOwner->InsertChildAt(insertIdx, child); + imut.AfterInsertion(child); + imut.Done(); + + child->SetRelocated(true); + children->InsertElementAt(arrayIdx, child); + + insertIdx = child->IndexInParent() + 1; + arrayIdx++; + + uint32_t flags = UpdateTreeInternal(child, true); + FireEventsOnInsertion(aOwner, flags); + } + } + continue; + } + +#ifdef A11Y_LOG + logging::TreeInfo("aria owns traversal", logging::eVerbose, + "candidate", child, nullptr); +#endif + // Same child on same position, no change. if (child->Parent() == aOwner && child->IndexInParent() == static_cast(insertIdx)) { @@ -1989,18 +2091,11 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) } } - if (child->Parent() == aOwner) { - if (child->IsRelocated()) { - children->RemoveElement(child); - } - MoveChild(child, insertIdx); + if (MoveChild(child, aOwner, insertIdx)) { + child->SetRelocated(true); children->InsertElementAt(arrayIdx, child); arrayIdx++; insertIdx = child->IndexInParent() + 1; - - } else if (SeizeChild(aOwner, child, insertIdx)) { - children->InsertElementAt(arrayIdx, child); - insertIdx++; arrayIdx++; } } @@ -2011,106 +2106,6 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) } } -bool -DocAccessible::SeizeChild(Accessible* aNewParent, Accessible* aChild, - int32_t aIdxInParent) -{ - Accessible* oldParent = aChild->Parent(); - if (!oldParent) { - NS_ERROR("No parent? The tree is broken!"); - return false; - } - - int32_t oldIdxInParent = aChild->IndexInParent(); - -#ifdef A11Y_LOG - logging::TreeInfo("aria owns seize child", 0, - "old parent", oldParent, "new parent", aNewParent, - "child", aChild, nullptr); -#endif - - RefPtr reorderEvent = new AccReorderEvent(oldParent); - RefPtr hideEvent = new AccHideEvent(aChild, false); - reorderEvent->AddSubMutationEvent(hideEvent); - - { - AutoTreeMutation mut(oldParent); - oldParent->RemoveChild(aChild); - } - - bool isReinserted = false; - { - AutoTreeMutation mut(aNewParent); - isReinserted = aNewParent->InsertChildAt(aIdxInParent, aChild); - } - -#ifdef A11Y_LOG - logging::TreeInfo("aria owns seize child: new parent tree after", - logging::eVerbose, aNewParent); -#endif - - if (!isReinserted) { - AutoTreeMutation mut(oldParent); - oldParent->InsertChildAt(oldIdxInParent, aChild); - return false; - } - - // The child may be stolen from other ARIA owns element. - if (aChild->IsRelocated()) { - nsTArray >* children = mARIAOwnsHash.Get(oldParent); - children->RemoveElement(aChild); - } - - FireDelayedEvent(hideEvent); - MaybeNotifyOfValueChange(oldParent); - FireDelayedEvent(reorderEvent); - - reorderEvent = new AccReorderEvent(aNewParent); - RefPtr showEvent = new AccShowEvent(aChild); - reorderEvent->AddSubMutationEvent(showEvent); - - FireDelayedEvent(showEvent); - MaybeNotifyOfValueChange(aNewParent); - FireDelayedEvent(reorderEvent); - - aChild->SetRelocated(true); - return true; -} - -void -DocAccessible::MoveChild(Accessible* aChild, int32_t aIdxInParent) -{ - NS_PRECONDITION(aChild->Parent(), "No parent?"); - - Accessible* parent = aChild->Parent(); - RefPtr reorderEvent = new AccReorderEvent(parent); - RefPtr hideEvent = new AccHideEvent(aChild, false); - reorderEvent->AddSubMutationEvent(hideEvent); - -#ifdef A11Y_LOG - logging::TreeInfo("aria owns move child", 0, - "parent", parent, "child", aChild, nullptr); -#endif - - AutoTreeMutation mut(parent); - parent->MoveChild(aIdxInParent, aChild); - aChild->SetRelocated(true); - -#ifdef A11Y_LOG - logging::TreeInfo("aria owns move child: parent tree after", - logging::eVerbose, parent); -#endif - - FireDelayedEvent(hideEvent); - - RefPtr showEvent = new AccShowEvent(aChild); - reorderEvent->AddSubMutationEvent(showEvent); - FireDelayedEvent(showEvent); - - MaybeNotifyOfValueChange(parent); - FireDelayedEvent(reorderEvent); -} - void DocAccessible::PutChildrenBack(nsTArray >* aChildren, uint32_t aStartIdx) @@ -2118,47 +2113,108 @@ DocAccessible::PutChildrenBack(nsTArray >* aChildren, nsTArray > containers; for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) { Accessible* child = aChildren->ElementAt(idx); - - // If the child is in the tree then remove it from the owner. - if (child->IsInDocument()) { - Accessible* owner = child->Parent(); - if (!owner) { - NS_ERROR("Cannot put the child back. No parent, a broken tree."); - continue; - } - RefPtr reorderEvent = new AccReorderEvent(owner); - RefPtr hideEvent = new AccHideEvent(child, false); - reorderEvent->AddSubMutationEvent(hideEvent); - FireDelayedEvent(hideEvent); - - { - AutoTreeMutation mut(owner); - owner->RemoveChild(child); - child->SetRelocated(false); - } - - MaybeNotifyOfValueChange(owner); - FireDelayedEvent(reorderEvent); + if (!child->IsInDocument()) { + continue; } - Accessible* container = GetContainerAccessible(child->GetContent()); - if (container && - containers.IndexOf(container) == nsTArray::NoIndex) { - containers.AppendElement(container); + // Remove the child from the owner + Accessible* owner = child->Parent(); + if (!owner) { + NS_ERROR("Cannot put the child back. No parent, a broken tree."); + continue; } + +#ifdef A11Y_LOG + logging::TreeInfo("aria owns put child back", 0, + "old parent", owner, "child", child, nullptr); +#endif + + // Unset relocated flag to find an insertion point for the child. + child->SetRelocated(false); + + int32_t idxInParent = -1; + Accessible* origContainer = GetContainerAccessible(child->GetContent()); + if (origContainer) { + TreeWalker walker(origContainer); + if (walker.Seek(child->GetContent())) { + Accessible* prevChild = walker.Prev(); + idxInParent = prevChild ? prevChild->IndexInParent() + 1 : 0; + } + } + MoveChild(child, origContainer, idxInParent); } - // And put it back where it belongs to. aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx); - for (uint32_t idx = 0; idx < containers.Length(); idx++) { - NS_ASSERTION(containers[idx]->IsInDocument(), - "A container has been destroyed."); - if (containers[idx]->IsInDocument()) { - UpdateTreeOnInsertion(containers[idx]); - } - } } +bool +DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent, + int32_t aIdxInParent) +{ + MOZ_ASSERT(aChild, "No child"); + MOZ_ASSERT(aChild->Parent(), "No parent"); + + Accessible* curParent = aChild->Parent(); + +#ifdef A11Y_LOG + logging::TreeInfo("move child", 0, + "old parent", curParent, "new parent", aNewParent, + "child", aChild, nullptr); +#endif + + // If the child was taken from from an ARIA owns element. + if (aChild->IsRelocated()) { + nsTArray >* children = mARIAOwnsHash.Get(curParent); + children->RemoveElement(aChild); + } + + if (curParent == aNewParent) { + MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case"); + + curParent->MoveChild(aIdxInParent, aChild); + MaybeNotifyOfValueChange(curParent); + +#ifdef A11Y_LOG + logging::TreeInfo("move child: parent tree after", + logging::eVerbose, curParent); +#endif + return true; + } + + if (!aNewParent->IsAcceptableChild(aChild->GetContent())) { + return false; + } + + TreeMutation rmut(curParent); + rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown); + curParent->RemoveChild(aChild); + rmut.Done(); + + MaybeNotifyOfValueChange(curParent); + + // No insertion point for the child. + if (aIdxInParent == -1) { + return true; + } + + TreeMutation imut(aNewParent); + aNewParent->InsertChildAt(aIdxInParent, aChild); + imut.AfterInsertion(aChild); + imut.Done(); + + MaybeNotifyOfValueChange(aNewParent); + +#ifdef A11Y_LOG + logging::TreeInfo("move child: old parent tree after", + logging::eVerbose, curParent); + logging::TreeInfo("move child: new parent tree after", + logging::eVerbose, aNewParent); +#endif + + return true; +} + + void DocAccessible::CacheChildrenInSubtree(Accessible* aRoot, Accessible** aFocusedAcc) @@ -2169,18 +2225,25 @@ DocAccessible::CacheChildrenInSubtree(Accessible* aRoot, FocusMgr()->HasDOMFocus(aRoot->GetContent())) *aFocusedAcc = aRoot; - aRoot->EnsureChildren(); + Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot; + if (root->KidsFromDOM()) { +#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()) { + MoveChild(child, root, root->ChildCount()); + continue; + } + + root->AppendChild(child); + mt.AfterInsertion(child); - // Make sure we create accessible tree defined in DOM only, i.e. if accessible - // provides specific tree (like XUL trees) then tree creation is handled by - // this accessible. - uint32_t count = aRoot->ContentChildCount(); - for (uint32_t idx = 0; idx < count; idx++) { - Accessible* child = aRoot->ContentChildAt(idx); - NS_ASSERTION(child, "Illicit tree change while tree is created!"); - // Don't cross document boundaries. - if (child && child->IsContent()) CacheChildrenInSubtree(child, aFocusedAcc); + } + mt.Done(); } // Fire document load complete on ARIA documents. @@ -2259,4 +2322,3 @@ DocAccessible::IsLoadEventTarget() const // It's content (not chrome) root document. return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent); } - diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h index 5bd62e4f12..d3a77dd0fa 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,6 +187,7 @@ public: */ void FireDelayedEvent(AccEvent* aEvent); void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget); + void FireEventsOnInsertion(Accessible* aContainer, uint32_t aUpdateFlags); /** * Fire value change event on the given accessible if applicable. @@ -225,7 +226,11 @@ public: * * @return the accessible object */ - Accessible* GetAccessible(nsINode* aNode) const; + Accessible* GetAccessible(nsINode* aNode) const + { + return aNode == mDocumentNode ? + const_cast(this) : mNodeToAccessibleMap.Get(aNode); + } /** * Return an accessible for the given node even if the node is not in @@ -275,6 +280,12 @@ public: return aNode ? GetAccessibleOrContainer(aNode->GetParentNode()) : nullptr; } + /** + * Return an accessible for the given node if any, or an immediate accessible + * container for it. + */ + Accessible* AccessibleOrTrueContainer(nsINode* aNode) const; + /** * Return an accessible for the given node or its first accessible descendant. */ @@ -353,6 +364,17 @@ public: */ void RecreateAccessible(nsIContent* aContent); + /** + * Schedule ARIA owned element relocation if needed. Return true if relocation + * was scheduled. + */ + 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. @@ -477,6 +499,8 @@ protected: */ void ProcessContentInserted(Accessible* aContainer, const nsTArray >* aInsertedContent); + void ProcessContentInserted(Accessible* aContainer, + nsIContent* aInsertedContent); /** * Used to notify the document to make it process the invalidation list. @@ -487,11 +511,6 @@ protected: */ void ProcessInvalidationList(); - /** - * Update the tree on content insertion. - */ - void UpdateTreeOnInsertion(Accessible* aContainer); - /** * Update the accessible tree for content removal. */ @@ -506,14 +525,7 @@ protected: eAccessible = 1, eAlertAccessible = 2 }; - - uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert, - AccReorderEvent* aReorderEvent); - - /** - * Schedule ARIA owned element relocation if needed. - */ - void RelocateARIAOwnedIfNeeded(nsIContent* aEl); + uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert); /** * Validates all aria-owns connections and updates the tree accordingly. @@ -525,23 +537,15 @@ protected: */ void DoARIAOwnsRelocation(Accessible* aOwner); - /** - * Moves the child from old parent under new one. - */ - bool SeizeChild(Accessible* aNewParent, Accessible* aChild, - int32_t aIdxInParent); - - /** - * Move the child under same parent. - */ - void MoveChild(Accessible* aChild, int32_t aIdxInParent); - /** * Moves children back under their original parents. */ void PutChildrenBack(nsTArray >* aChildren, uint32_t aStartIdx); + bool MoveChild(Accessible* aChild, Accessible* aNewParent, + int32_t aIdxInParent); + /** * Create accessible tree. * @@ -700,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/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp index e6bc7e0830..381abaf6bf 100644 --- a/accessible/generic/HyperTextAccessible.cpp +++ b/accessible/generic/HyperTextAccessible.cpp @@ -1880,11 +1880,10 @@ HyperTextAccessible::NativeName(nsString& aName) } void -HyperTextAccessible::InvalidateChildren() +HyperTextAccessible::Shutdown() { mOffsets.Clear(); - - AccessibleWrap::InvalidateChildren(); + AccessibleWrap::Shutdown(); } bool diff --git a/accessible/generic/HyperTextAccessible.h b/accessible/generic/HyperTextAccessible.h index 25fcfb8154..9d365b28b3 100644 --- a/accessible/generic/HyperTextAccessible.h +++ b/accessible/generic/HyperTextAccessible.h @@ -60,7 +60,7 @@ public: virtual mozilla::a11y::role NativeRole() override; virtual uint64_t NativeState() override; - virtual void InvalidateChildren() override; + virtual void Shutdown() override; virtual bool RemoveChild(Accessible* aAccessible) override; virtual Relation RelationByType(RelationType aType) override; diff --git a/accessible/generic/OuterDocAccessible.cpp b/accessible/generic/OuterDocAccessible.cpp index 66305cf871..fb28b991db 100644 --- a/accessible/generic/OuterDocAccessible.cpp +++ b/accessible/generic/OuterDocAccessible.cpp @@ -110,21 +110,6 @@ OuterDocAccessible::Shutdown() AccessibleWrap::Shutdown(); } -void -OuterDocAccessible::InvalidateChildren() -{ - // Do not invalidate children because DocManager is responsible for - // document accessible lifetime when DOM document is created or destroyed. If - // DOM document isn't destroyed but its presshell is destroyed (for example, - // when DOM node of outerdoc accessible is hidden), then outerdoc accessible - // notifies DocManager about this. If presshell is created for existing - // DOM document (for example when DOM node of outerdoc accessible is shown) - // then allow DocManager to handle this case since the document - // accessible is created and appended as a child when it's requested. - - SetChildrenFlag(eChildrenUninitialized); -} - bool OuterDocAccessible::InsertChildAt(uint32_t aIdx, Accessible* aAccessible) { diff --git a/accessible/generic/OuterDocAccessible.h b/accessible/generic/OuterDocAccessible.h index 680ab60d87..dc4f0fb828 100644 --- a/accessible/generic/OuterDocAccessible.h +++ b/accessible/generic/OuterDocAccessible.h @@ -36,7 +36,6 @@ public: virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) override; - virtual void InvalidateChildren() override; virtual bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override; virtual bool RemoveChild(Accessible* aAccessible) override; diff --git a/accessible/generic/TextLeafAccessible.cpp b/accessible/generic/TextLeafAccessible.cpp index c7ba815591..e4760bc560 100644 --- a/accessible/generic/TextLeafAccessible.cpp +++ b/accessible/generic/TextLeafAccessible.cpp @@ -20,6 +20,7 @@ TextLeafAccessible:: LinkableAccessible(aContent, aDoc) { mType = eTextLeafType; + mStateFlags |= eNoKidsFromDOM; } TextLeafAccessible::~TextLeafAccessible() @@ -50,9 +51,3 @@ TextLeafAccessible::Name(nsString& aName) aName = mText; return eNameOK; } - -void -TextLeafAccessible::CacheChildren() -{ - // No children for text accessible. -} diff --git a/accessible/generic/TextLeafAccessible.h b/accessible/generic/TextLeafAccessible.h index 9bb1f64866..555929fbf2 100644 --- a/accessible/generic/TextLeafAccessible.h +++ b/accessible/generic/TextLeafAccessible.h @@ -30,10 +30,6 @@ public: void SetText(const nsAString& aText) { mText = aText; } const nsString& Text() const { return mText; } -protected: - // Accessible - virtual void CacheChildren() override; - protected: nsString mText; }; diff --git a/accessible/html/HTMLImageMapAccessible.cpp b/accessible/html/HTMLImageMapAccessible.cpp index 1284980f93..d6acbeba6a 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 mut(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,14 +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. @@ -121,22 +113,11 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) break; } - if (aDoFireEvents) { - RefPtr event = new AccShowEvent(area); - mDoc->FireDelayedEvent(event); - reorderEvent->AddSubMutationEvent(event); - } - - treeChanged = true; + mt.AfterInsertion(area); } } - // Fire reorder event if needed. - if (treeChanged && aDoFireEvents) - mDoc->FireDelayedEvent(reorderEvent); - - if (!treeChanged) - mut.mInvalidationRequired = false; + mt.Done(); } Accessible* diff --git a/accessible/html/HTMLListAccessible.cpp b/accessible/html/HTMLListAccessible.cpp index 36fa4c9194..c419a630a9 100644 --- a/accessible/html/HTMLListAccessible.cpp +++ b/accessible/html/HTMLListAccessible.cpp @@ -51,6 +51,7 @@ HTMLLIAccessible:: if (blockFrame && blockFrame->HasBullet()) { mBullet = new HTMLListBulletAccessible(mContent, mDoc); Document()->BindToDocument(mBullet, nullptr); + AppendChild(mBullet); } } @@ -102,41 +103,19 @@ HTMLLIAccessible::UpdateBullet(bool aHasBullet) return; } - RefPtr reorderEvent = new AccReorderEvent(this); - AutoTreeMutation mut(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); - - 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); - + mt.AfterInsertion(mBullet); + } + else { + mt.BeforeRemoval(mBullet); RemoveChild(mBullet); mBullet = nullptr; } - - mDoc->FireDelayedEvent(reorderEvent); -} - -//////////////////////////////////////////////////////////////////////////////// -// HTMLLIAccessible: Accessible protected - -void -HTMLLIAccessible::CacheChildren() -{ - if (mBullet) - AppendChild(mBullet); - - // Cache children from subtree. - AccessibleWrap::CacheChildren(); + mt.Done(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/html/HTMLListAccessible.h b/accessible/html/HTMLListAccessible.h index c22e028916..26de44d1cf 100644 --- a/accessible/html/HTMLListAccessible.h +++ b/accessible/html/HTMLListAccessible.h @@ -60,9 +60,6 @@ public: protected: virtual ~HTMLLIAccessible() { } - // Accessible - virtual void CacheChildren() override; - private: RefPtr mBullet; }; diff --git a/accessible/html/HTMLSelectAccessible.cpp b/accessible/html/HTMLSelectAccessible.cpp index 87c0b0f421..bdd840883d 100644 --- a/accessible/html/HTMLSelectAccessible.cpp +++ b/accessible/html/HTMLSelectAccessible.cpp @@ -114,35 +114,12 @@ HTMLSelectListAccessible::SetCurrentItem(Accessible* aItem) true); } -//////////////////////////////////////////////////////////////////////////////// -// HTMLSelectListAccessible: Accessible protected - -void -HTMLSelectListAccessible::CacheChildren() +bool +HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const { - // Cache accessibles for and s and