Files
palemoon27/accessible/base/EventTree.cpp
T
roytam1 8655c2747d 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)
2024-05-14 22:53:16 +08:00

537 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* -*- 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<EventTree*>(-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<int32_t>(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<int32_t>(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<uint32_t>(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<uint32_t>(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<int32_t>(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<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
nsAutoPtr<EventTree> 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<EventTree>* 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);
}