Bug 1261425 - coalesce mutation events by a tree structure, r=yzen

This commit is contained in:
Alexander Surkov 2016-04-07 09:30:22 -04:00
Родитель 6a9e630b92
Коммит 51947ead2c
29 изменённых файлов: 896 добавлений и 562 удалений

Просмотреть файл

@ -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
////////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -130,7 +130,7 @@ protected:
RefPtr<Accessible> 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<Accessible> mParent;
RefPtr<AccTextChangeEvent> mTextChangeEvent;
friend class EventQueue;
friend class EventTree;
};
@ -269,7 +268,7 @@ protected:
RefPtr<Accessible> mNextSibling;
RefPtr<Accessible> mPrevSibling;
friend class EventQueue;
friend class EventTree;
};
@ -312,37 +311,6 @@ public:
{
return AccEvent::GetEventGroups() | (1U << eReorderEvent);
}
/**
* Get connected with mutation event.
*/
void AddSubMutationEvent(AccMutationEvent* aEvent)
{ mDependentEvents.AppendElement(aEvent); }
/**
* Do not emit the reorder event and its connected mutation events.
*/
void DoNotEmitAll()
{
mEventRule = AccEvent::eDoNotEmit;
uint32_t eventsCount = mDependentEvents.Length();
for (uint32_t idx = 0; idx < eventsCount; idx++)
mDependentEvents[idx]->mEventRule = AccEvent::eDoNotEmit;
}
/**
* Return true if the given accessible is a target of connected mutation
* event.
*/
uint32_t IsShowHideEventTarget(const Accessible* aTarget) const;
protected:
/**
* Show and hide events causing this reorder event.
*/
nsTArray<AccMutationEvent*> mDependentEvents;
friend class EventQueue;
};

Просмотреть файл

@ -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<AccEvent> nameChangeEvent =
new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
PushEvent(nameChangeEvent);
return PushEvent(nameChangeEvent);
}
break;
}
parent = parent->Parent();
}
}
// Associate text change with hide event if it wasn't stolen from hiding
// siblings during coalescence.
AccMutationEvent* showOrHideEvent = downcast_accEvent(aEvent);
if (showOrHideEvent && !showOrHideEvent->mTextChangeEvent)
CreateTextChangeEventFor(showOrHideEvent);
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
@ -92,46 +90,12 @@ EventQueue::CoalesceEvents()
switch(tailEvent->mEventRule) {
case AccEvent::eCoalesceReorder:
CoalesceReorderEvents(tailEvent);
MOZ_ASSERT(tailEvent->mAccessible->IsApplication() ||
tailEvent->mAccessible->IsOuterDoc() ||
tailEvent->mAccessible->IsXULTree(),
"Only app or outerdoc accessible reorder events are in the queue");
break; // case eCoalesceReorder
case AccEvent::eCoalesceMutationTextChange:
{
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule != tailEvent->mEventRule)
continue;
// We don't currently coalesce text change events from show/hide events.
if (thisEvent->mEventType != tailEvent->mEventType)
continue;
// Show events may be duped because of reinsertion (removal is ignored
// because initial insertion is not processed). Ignore initial
// insertion.
if (thisEvent->mAccessible == tailEvent->mAccessible)
thisEvent->mEventRule = AccEvent::eDoNotEmit;
AccMutationEvent* tailMutationEvent = downcast_accEvent(tailEvent);
AccMutationEvent* thisMutationEvent = downcast_accEvent(thisEvent);
if (tailMutationEvent->mParent != thisMutationEvent->mParent)
continue;
// Coalesce text change events for hide and show events.
if (thisMutationEvent->IsHide()) {
AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent);
AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent);
CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
break;
}
AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent);
AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent);
CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent);
break;
}
} break; // case eCoalesceMutationTextChange
case AccEvent::eCoalesceOfSameType:
{
// Coalesce old events by newer event.
@ -226,93 +190,6 @@ EventQueue::CoalesceEvents()
} // switch
}
void
EventQueue::CoalesceReorderEvents(AccEvent* aTailEvent)
{
uint32_t count = mEvents.Length();
for (uint32_t index = count - 2; index < count; index--) {
AccEvent* thisEvent = mEvents[index];
// Skip events of different types and targeted to application accessible.
if (thisEvent->mEventType != aTailEvent->mEventType ||
thisEvent->mAccessible->IsApplication())
continue;
// If thisEvent target is not in document longer, i.e. if it was
// removed from the tree then do not emit the event.
if (!thisEvent->mAccessible->IsDoc() &&
!thisEvent->mAccessible->IsInDocument()) {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
continue;
}
// Coalesce earlier event of the same target.
if (thisEvent->mAccessible == aTailEvent->mAccessible) {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
// If tailEvent contains thisEvent
// then
// if show or hide of tailEvent contains a grand parent of thisEvent
// then ignore thisEvent and its show and hide events
// otherwise ignore thisEvent but not its show and hide events
Accessible* thisParent = thisEvent->mAccessible;
while (thisParent && thisParent != mDocument) {
if (thisParent->Parent() == aTailEvent->mAccessible) {
AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
uint32_t eventType = tailReorder->IsShowHideEventTarget(thisParent);
// It can be either hide or show events which may occur because of
// accessible reparenting.
if (eventType == nsIAccessibleEvent::EVENT_SHOW ||
eventType == nsIAccessibleEvent::EVENT_HIDE) {
AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
thisReorder->DoNotEmitAll();
} else {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
}
return;
}
thisParent = thisParent->Parent();
}
// If tailEvent is contained by thisEvent
// then
// if show of thisEvent contains the tailEvent
// then ignore tailEvent
// if hide of thisEvent contains the tailEvent
// then assert
// otherwise ignore tailEvent but not its show and hide events
Accessible* tailParent = aTailEvent->mAccessible;
while (tailParent && tailParent != mDocument) {
if (tailParent->Parent() == thisEvent->mAccessible) {
AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent);
if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
tailReorder->DoNotEmitAll();
}
else if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
NS_ERROR("Accessible tree was modified after it was removed! Huh?");
}
else {
aTailEvent->mEventRule = AccEvent::eDoNotEmit;
mEvents[index].swap(mEvents[count - 1]);
}
return;
}
tailParent = tailParent->Parent();
}
} // for (index)
}
void
EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
AccSelChangeEvent* aThisEvent,
@ -393,90 +270,6 @@ EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
}
void
EventQueue::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
AccHideEvent* aThisEvent)
{
// XXX: we need a way to ignore SplitNode and JoinNode() when they do not
// affect the text within the hypertext.
AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
if (!textEvent)
return;
if (aThisEvent->mNextSibling == aTailEvent->mAccessible) {
aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
} else if (aThisEvent->mPrevSibling == aTailEvent->mAccessible) {
uint32_t oldLen = textEvent->GetLength();
aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
textEvent->mStart -= textEvent->GetLength() - oldLen;
}
aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent);
}
void
EventQueue::CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent,
AccShowEvent* aThisEvent)
{
AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent;
if (!textEvent)
return;
if (aTailEvent->mAccessible->IndexInParent() ==
aThisEvent->mAccessible->IndexInParent() + 1) {
// If tail target was inserted after this target, i.e. tail target is next
// sibling of this target.
aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText);
} else if (aTailEvent->mAccessible->IndexInParent() ==
aThisEvent->mAccessible->IndexInParent() -1) {
// If tail target was inserted before this target, i.e. tail target is
// previous sibling of this target.
nsAutoString startText;
aTailEvent->mAccessible->AppendTextTo(startText);
textEvent->mModifiedText = startText + textEvent->mModifiedText;
textEvent->mStart -= startText.Length();
}
aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent);
}
void
EventQueue::CreateTextChangeEventFor(AccMutationEvent* aEvent)
{
Accessible* container = aEvent->mAccessible->Parent();
if (!container)
return;
HyperTextAccessible* textAccessible = container->AsHyperText();
if (!textAccessible)
return;
// Don't fire event for the first html:br in an editor.
if (aEvent->mAccessible->Role() == roles::WHITESPACE) {
nsCOMPtr<nsIEditor> editor = textAccessible->GetEditor();
if (editor) {
bool isEmpty = false;
editor->GetDocumentIsEmpty(&isEmpty);
if (isEmpty)
return;
}
}
int32_t offset = textAccessible->GetChildOffset(aEvent->mAccessible);
nsAutoString text;
aEvent->mAccessible->AppendTextTo(text);
if (text.IsEmpty())
return;
aEvent->mTextChangeEvent =
new AccTextChangeEvent(textAccessible, offset, text, aEvent->IsShow(),
aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
}
////////////////////////////////////////////////////////////////////////////////
// EventQueue: event queue
@ -539,18 +332,6 @@ EventQueue::ProcessEventQueue()
}
nsEventShell::FireEvent(event);
// Fire text change events.
AccMutationEvent* mutationEvent = downcast_accEvent(event);
if (mutationEvent) {
if (mutationEvent->mTextChangeEvent)
nsEventShell::FireEvent(mutationEvent->mTextChangeEvent);
}
}
AccHideEvent* hideEvent = downcast_accEvent(event);
if (hideEvent && hideEvent->NeedsShutdown()) {
mDocument->ShutdownChildrenInSubtree(event->mAccessible);
}
if (!mDocument)

Просмотреть файл

@ -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<RefPtr<AccEvent> > mEvents;
nsTArray<RefPtr<AccEvent>> mEvents;
};
} // namespace a11y

Просмотреть файл

@ -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<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);
}

113
accessible/base/EventTree.h Normal file
Просмотреть файл

@ -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<AccShowEvent> ev = new AccShowEvent(aChild);
Mutated(ev);
}
void Hidden(Accessible* aChild, bool aNeedsShutdown = true)
{
RefPtr<AccHideEvent> 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<EventTree> mFirst;
nsAutoPtr<EventTree> mNext;
Accessible* mContainer;
nsTArray<RefPtr<AccMutationEvent>> mDependentEvents;
bool mFireReorder;
friend class NotificationController;
};
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_EventQueue_h_

Просмотреть файл

@ -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 },

Просмотреть файл

@ -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
};
/**

Просмотреть файл

@ -97,6 +97,17 @@ NotificationController::Shutdown()
mNotifications.Clear();
mEvents.Clear();
mRelocations.Clear();
mEventTree.Clear();
}
EventTree*
NotificationController::QueueMutation(Accessible* aContainer)
{
EventTree* tree = mEventTree.FindOrInsert(aContainer);
if (tree) {
ScheduleProcessing();
}
return tree;
}
void
@ -388,6 +399,9 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
// events causes script to run.
mObservingState = eRefreshProcessing;
mEventTree.Process();
mEventTree.Clear();
ProcessEventQueue();
if (IPCAccessibilityActive()) {

Просмотреть файл

@ -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<RefPtr<Accessible> > mRelocations;
/**
* Holds all mutation events.
*/
EventTree mEventTree;
};
} // namespace a11y

Просмотреть файл

@ -35,6 +35,7 @@ UNIFIED_SOURCES += [
'Asserts.cpp',
'DocManager.cpp',
'EventQueue.cpp',
'EventTree.cpp',
'Filters.cpp',
'FocusManager.cpp',
'NotificationController.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

Просмотреть файл

@ -13,9 +13,11 @@
#include "nsAccUtils.h"
#include "nsAccessibilityService.h"
#include "ApplicationAccessible.h"
#include "NotificationController.h"
#include "nsEventShell.h"
#include "nsTextEquivUtils.h"
#include "DocAccessibleChild.h"
#include "EventTree.h"
#include "Logging.h"
#include "Relation.h"
#include "Role.h"
@ -2119,6 +2121,11 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild)
"No move, same index");
MOZ_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given");
EventTree* eventTree = mDoc->Controller()->QueueMutation(this);
if (eventTree) {
eventTree->Hidden(aChild, false);
}
mEmbeddedObjCollector = nullptr;
mChildren.RemoveElementAt(aChild->mIndexInParent);
@ -2127,7 +2134,6 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild)
// If the child is moved after its current position.
if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
startIdx = aChild->mIndexInParent;
if (aNewIndex == mChildren.Length() + 1) {
// The child is moved to the end.
mChildren.AppendElement(aChild);
@ -2148,6 +2154,11 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild)
mChildren[idx]->mStateFlags |= eGroupInfoDirty;
mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
}
if (eventTree) {
eventTree->Shown(aChild);
mDoc->Controller()->QueueNameChange(aChild);
}
}
Accessible*
@ -2791,34 +2802,3 @@ KeyBinding::ToAtkFormat(nsAString& aValue) const
aValue.Append(mKey);
}
////////////////////////////////////////////////////////////////////////////////
// AutoTreeMutation class
void
AutoTreeMutation::Done()
{
MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating,
"The parent is not in mutating state.");
mParent->mStateFlags &= ~Accessible::eKidsMutating;
uint32_t length = mParent->mChildren.Length();
#ifdef DEBUG
for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast<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;
}

Просмотреть файл

@ -33,6 +33,7 @@ class AccGroupInfo;
class ApplicationAccessible;
class DocAccessible;
class EmbeddedObjCollector;
class EventTree;
class HTMLImageMapAccessible;
class HTMLLIAccessible;
class HyperTextAccessible;
@ -1112,7 +1113,7 @@ protected:
friend class DocAccessible;
friend class xpcAccessible;
friend class AutoTreeMutation;
friend class TreeMutation;
nsAutoPtr<mozilla::a11y::EmbeddedObjCollector> mEmbeddedObjCollector;
union {
@ -1204,43 +1205,6 @@ private:
uint32_t mModifierMask;
};
/**
* This class makes sure required tasks are done before and after tree
* mutations. Currently this only includes group info invalidation. You must
* have an object of this class on the stack when calling methods that mutate
* the accessible tree.
*/
class AutoTreeMutation
{
public:
explicit AutoTreeMutation(Accessible* aParent) :
mParent(aParent), mStartIdx(UINT32_MAX),
mStateFlagsCopy(mParent->mStateFlags)
{
mParent->mStateFlags |= Accessible::eKidsMutating;
}
void AfterInsertion(const Accessible* aChild) {
if (static_cast<uint32_t>(aChild->IndexInParent()) < mStartIdx) {
mStartIdx = aChild->IndexInParent() + 1;
}
}
void BeforeRemoval(const Accessible* aChild) {
if (static_cast<uint32_t>(aChild->IndexInParent()) < mStartIdx) {
mStartIdx = aChild->IndexInParent();
}
}
void Done();
private:
Accessible* mParent;
uint32_t mStartIdx;
uint32_t mStateFlagsCopy;
};
} // namespace a11y
} // namespace mozilla

Просмотреть файл

@ -1751,17 +1751,19 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer,
UpdateRootElIfNeeded();
}
uint32_t updateFlags = 0;
AutoTreeMutation mt(aContainer);
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
InsertIterator iter(aContainer, aNodes);
if (!iter.Next()) {
return;
}
#ifdef A11Y_LOG
logging::TreeInfo("children before insertion", logging::eVerbose,
aContainer);
#endif
InsertIterator iter(aContainer, aNodes);
while (iter.Next()) {
uint32_t updateFlags = 0;
TreeMutation mt(aContainer);
do {
Accessible* parent = iter.Child()->Parent();
if (parent) {
if (parent != aContainer) {
@ -1788,12 +1790,13 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer,
#endif
mt.AfterInsertion(iter.Child());
updateFlags |= UpdateTreeInternal(iter.Child(), true, reorderEvent);
updateFlags |= UpdateTreeInternal(iter.Child(), true);
continue;
}
MOZ_ASSERT_UNREACHABLE("accessible was rejected");
}
} while (iter.Next());
mt.Done();
#ifdef A11Y_LOG
@ -1801,7 +1804,7 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer,
aContainer);
#endif
FireEventsOnInsertion(aContainer, reorderEvent, updateFlags);
FireEventsOnInsertion(aContainer, updateFlags);
}
void
@ -1819,22 +1822,19 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode)
}
if (child) {
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
AutoTreeMutation mt(aContainer);
TreeMutation mt(aContainer);
aContainer->InsertAfter(child, walker.Prev());
mt.AfterInsertion(child);
mt.Done();
uint32_t flags = UpdateTreeInternal(child, true, reorderEvent);
FireEventsOnInsertion(aContainer, reorderEvent, flags);
uint32_t flags = UpdateTreeInternal(child, true);
FireEventsOnInsertion(aContainer, flags);
}
}
}
void
DocAccessible::FireEventsOnInsertion(Accessible* aContainer,
AccReorderEvent* aReorderEvent,
uint32_t aUpdateFlags)
{
// Content insertion did not cause an accessible tree change.
@ -1857,7 +1857,6 @@ DocAccessible::FireEventsOnInsertion(Accessible* aContainer,
}
MaybeNotifyOfValueChange(aContainer);
FireDelayedEvent(aReorderEvent);
}
void
@ -1880,32 +1879,33 @@ DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNod
#endif
uint32_t updateFlags = eNoAccessible;
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
AutoTreeMutation mt(aContainer);
TreeMutation mt(aContainer);
if (child) {
mt.BeforeRemoval(child);
updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
} else {
updateFlags |= UpdateTreeInternal(child, false);
}
else {
TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
while (Accessible* child = walker.Next()) {
mt.BeforeRemoval(child);
updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
Accessible* child = walker.Next();
if (child) {
do {
mt.BeforeRemoval(child);
updateFlags |= UpdateTreeInternal(child, false);
}
while ((child = walker.Next()));
}
}
mt.Done();
// Content insertion/removal is not cause of accessible tree change.
if (updateFlags == eNoAccessible)
return;
MaybeNotifyOfValueChange(aContainer);
FireDelayedEvent(reorderEvent);
if (updateFlags != eNoAccessible) {
MaybeNotifyOfValueChange(aContainer);
}
}
uint32_t
DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
AccReorderEvent* aReorderEvent)
DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert)
{
uint32_t updateFlags = eAccessible;
@ -1918,31 +1918,8 @@ DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
if (aIsInsert) {
// Create accessible tree for shown accessible.
CacheChildrenInSubtree(aChild, &focusedAcc);
} else {
// Fire menupopup end event before hide event if a menu goes away.
// XXX: We don't look into children of hidden subtree to find hiding
// menupopup (as we did prior bug 570275) because we don't do that when
// menu is showing (and that's impossible until bug 606924 is fixed).
// Nevertheless we should do this at least because layout coalesces
// the changes before our processing and we may miss some menupopup
// events. Now we just want to be consistent in content insertion/removal
// handling.
if (aChild->ARIARole() == roles::MENUPOPUP)
FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild);
}
// Fire show/hide event.
RefPtr<AccMutationEvent> event;
if (aIsInsert)
event = new AccShowEvent(aChild);
else
event = new AccHideEvent(aChild);
FireDelayedEvent(event);
aReorderEvent->AddSubMutationEvent(event);
if (aIsInsert) {
roles::Role ariaRole = aChild->ARIARole();
if (ariaRole == roles::MENUPOPUP) {
@ -2065,7 +2042,7 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
if (aOwner->IsAcceptableChild(childEl)) {
child = GetAccService()->CreateAccessible(childEl, aOwner);
if (child) {
AutoTreeMutation imut(aOwner);
TreeMutation imut(aOwner);
aOwner->InsertChildAt(insertIdx, child);
imut.AfterInsertion(child);
imut.Done();
@ -2076,9 +2053,8 @@ DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
insertIdx = child->IndexInParent() + 1;
arrayIdx++;
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aOwner);
uint32_t flags = UpdateTreeInternal(child, true, reorderEvent);
FireEventsOnInsertion(aOwner, reorderEvent, flags);
uint32_t flags = UpdateTreeInternal(child, true);
FireEventsOnInsertion(aOwner, flags);
}
}
continue;
@ -2196,19 +2172,8 @@ DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
if (curParent == aNewParent) {
MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(curParent);
RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(aChild, false);
reorderEvent->AddSubMutationEvent(hideEvent);
FireDelayedEvent(hideEvent);
curParent->MoveChild(aIdxInParent, aChild);
RefPtr<AccMutationEvent> showEvent = new AccShowEvent(aChild);
reorderEvent->AddSubMutationEvent(showEvent);
FireDelayedEvent(showEvent);
MaybeNotifyOfValueChange(curParent);
FireDelayedEvent(reorderEvent);
#ifdef A11Y_LOG
logging::TreeInfo("move child: parent tree after",
@ -2221,36 +2186,24 @@ DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
return false;
}
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(curParent);
RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(aChild, false);
reorderEvent->AddSubMutationEvent(hideEvent);
FireDelayedEvent(hideEvent);
AutoTreeMutation rmut(curParent);
rmut.BeforeRemoval(aChild);
TreeMutation rmut(curParent);
rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown);
curParent->RemoveChild(aChild);
rmut.Done();
MaybeNotifyOfValueChange(curParent);
FireDelayedEvent(reorderEvent);
// No insertion point for the child.
if (aIdxInParent == -1) {
return true;
}
AutoTreeMutation imut(aNewParent);
TreeMutation imut(aNewParent);
aNewParent->InsertChildAt(aIdxInParent, aChild);
imut.AfterInsertion(aChild);
imut.Done();
reorderEvent = new AccReorderEvent(aNewParent);
RefPtr<AccMutationEvent> showEvent = new AccShowEvent(aChild);
reorderEvent->AddSubMutationEvent(showEvent);
FireDelayedEvent(showEvent);
MaybeNotifyOfValueChange(aNewParent);
FireDelayedEvent(reorderEvent);
#ifdef A11Y_LOG
logging::TreeInfo("move child: old parent tree after",
@ -2275,7 +2228,10 @@ DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
if (root->KidsFromDOM()) {
AutoTreeMutation mt(root);
#ifdef A11Y_LOG
logging::TreeInfo("caching children", logging::eVerbose, aRoot);
#endif
TreeMutation mt(root, TreeMutation::kNoEvents);
TreeWalker walker(root);
while (Accessible* child = walker.Next()) {
if (child->IsBoundToParent()) {
@ -2291,10 +2247,6 @@ DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
mt.Done();
}
#ifdef A11Y_LOG
logging::TreeInfo("cached children", logging::eVerbose, aRoot);
#endif
// Fire document load complete on ARIA documents.
// XXX: we should delay an event if the ARIA document has aria-busy.
if (aRoot->HasARIARole() && !aRoot->IsDoc()) {

Просмотреть файл

@ -8,8 +8,8 @@
#include "nsIAccessiblePivot.h"
#include "AccEvent.h"
#include "HyperTextAccessibleWrap.h"
#include "AccEvent.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
@ -187,9 +187,7 @@ public:
*/
void FireDelayedEvent(AccEvent* aEvent);
void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget);
void FireEventsOnInsertion(Accessible* aContainer,
AccReorderEvent* aReorderEvent,
uint32_t aUpdateFlags);
void FireEventsOnInsertion(Accessible* aContainer, uint32_t aUpdateFlags);
/**
* Fire value change event on the given accessible if applicable.
@ -372,6 +370,11 @@ public:
*/
bool RelocateARIAOwnedIfNeeded(nsIContent* aEl);
/**
* Return a notification controller associated with the document.
*/
NotificationController* Controller() const { return mNotificationController; }
/**
* If this document is in a content process return the object responsible for
* communicating with the main process for it.
@ -522,9 +525,7 @@ protected:
eAccessible = 1,
eAlertAccessible = 2
};
uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
AccReorderEvent* aReorderEvent);
uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert);
/**
* Validates all aria-owns connections and updates the tree accordingly.
@ -703,7 +704,7 @@ protected:
* Used to process notification from core and accessible events.
*/
RefPtr<NotificationController> mNotificationController;
friend class EventQueue;
friend class EventTree;
friend class NotificationController;
private:

Просмотреть файл

@ -87,9 +87,7 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents)
if (!imageMapObj)
return;
bool treeChanged = false;
AutoTreeMutation mt(this);
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
TreeMutation mt(this, TreeMutation::kNoEvents & !aDoFireEvents);
// Remove areas that are not a valid part of the image map anymore.
for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) {
@ -97,15 +95,8 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents)
if (area->GetContent()->GetPrimaryFrame())
continue;
if (aDoFireEvents) {
RefPtr<AccHideEvent> event = new AccHideEvent(area, area->GetContent());
mDoc->FireDelayedEvent(event);
reorderEvent->AddSubMutationEvent(event);
}
mt.BeforeRemoval(area);
RemoveChild(area);
treeChanged = true;
}
// Insert new areas into the tree.
@ -123,22 +114,10 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents)
}
mt.AfterInsertion(area);
if (aDoFireEvents) {
RefPtr<AccShowEvent> event = new AccShowEvent(area);
mDoc->FireDelayedEvent(event);
reorderEvent->AddSubMutationEvent(event);
}
treeChanged = true;
}
}
mt.Done();
// Fire reorder event if needed.
if (treeChanged && aDoFireEvents)
mDoc->FireDelayedEvent(reorderEvent);
}
Accessible*

Просмотреть файл

@ -103,31 +103,19 @@ HTMLLIAccessible::UpdateBullet(bool aHasBullet)
return;
}
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
AutoTreeMutation mt(this);
DocAccessible* document = Document();
TreeMutation mt(this);
if (aHasBullet) {
mBullet = new HTMLListBulletAccessible(mContent, mDoc);
document->BindToDocument(mBullet, nullptr);
mDoc->BindToDocument(mBullet, nullptr);
InsertChildAt(0, mBullet);
mt.AfterInsertion(mBullet);
RefPtr<AccShowEvent> event = new AccShowEvent(mBullet);
mDoc->FireDelayedEvent(event);
reorderEvent->AddSubMutationEvent(event);
} else {
RefPtr<AccHideEvent> event = new AccHideEvent(mBullet, mBullet->GetContent());
mDoc->FireDelayedEvent(event);
reorderEvent->AddSubMutationEvent(event);
}
else {
mt.BeforeRemoval(mBullet);
RemoveChild(mBullet);
mBullet = nullptr;
}
mt.Done();
mDoc->FireDelayedEvent(reorderEvent);
}
////////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -54,9 +54,8 @@
}
}
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
//enableLogging("tree");
//enableLogging("tree,eventTree,verbose");
function doTest()
{
@ -144,7 +143,5 @@
<a id="link4" onmouseup="">
<img src="../moz.png" id="img4">
</a>
<div id="eventdump"></div>
</body>
</html>

Просмотреть файл

@ -56,10 +56,10 @@
////////////////////////////////////////////////////////////////////////////
// Do tests
//gA11yEventDumpToConsole = true; // debuging
//enableLogging("tree,events,verbose");
var gQueue = null;
//gA11yEventDumpID = "eventdump";
function doTests()
{
gQueue = new eventQueue(nsIAccessibleEvent.EVENT_ALERT);
@ -87,7 +87,6 @@
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="eventdump"></div>
</body>
</html>

Просмотреть файл

@ -81,8 +81,8 @@
this.menu = null;
this.eventSeq = [
new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this),
new invokerChecker(EVENT_HIDE, getMenu, this),
new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this),
new invokerChecker(EVENT_REORDER, getNode(aParentMenuID))
];
@ -150,11 +150,10 @@
////////////////////////////////////////////////////////////////////////////
// Do tests
var gQueue = null;
//gA11yEventDumpToConsole = true; // debuging
//enableLogging("tree,verbose");
//enableLogging("tree,events,verbose");
var gQueue = null;
function doTests()
{
gQueue = new eventQueue();
@ -257,7 +256,8 @@
<div id="mb3-mi-outside" role="menuitem" tabindex="0">Outside</div>
<div id="menubar4" role="menubar">
<div role="menuitem" aria-haspopup="true" aria-owns="mb4-menu">Item</div>
<div id="mb4_topitem" role="menuitem" aria-haspopup="true"
aria-owns="mb4-menu">Item</div>
</div>
<div id="mb4-menu" role="menu" style="display:none;">
<div role="menuitem" id="mb4-item1" tabindex="0">Item 1.1</div>

Просмотреть файл

@ -323,13 +323,39 @@
this.initSequence();
}
/**
* Remove children and parent
*/
function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId)
{
this.child1 = getNode(aChild1Id);
this.child2 = getNode(aChild2Id);
this.parent = getNode(aParentId);
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getAccessible(aParentId)),
new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode)
];
this.invoke = function removeGrandChildrenNHideParent_invoke()
{
this.child1.parentNode.removeChild(this.child1);
this.child2.parentNode.removeChild(this.child2);
this.parent.hidden = true;
}
this.getID = function removeGrandChildrenNHideParent_getID() {
return "remove grand children of different parents and then hide their grand parent";
}
}
////////////////////////////////////////////////////////////////////////////
// Do tests.
var gQueue = null;
//gA11yEventDumpToConsole = true; // debug stuff
//enableLogging("tree");
//enableLogging("events,tree,eventTree,verbose");
var gQueue = null;
function doTests()
{
gQueue = new eventQueue();
@ -350,6 +376,8 @@
gQueue.push(new showParentNAddChild("select11", false));
gQueue.push(new showParentNAddChild("select12", true));
gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -412,5 +440,12 @@
<select id="select11" style="display: none"></select>
<select id="select12" style="display: none"></select>
</div>
<div id="testContainer2">
<div id="t1_parent">
<div id="t1_mid1"><div id="t1_child1"></div></div>
<div id="t1_mid2"><div id="t1_child2"></div></div>
</div>
</div>
</body>
</html>

Просмотреть файл

@ -361,8 +361,8 @@
function showHiddenParentOfVisibleChild()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("c4_child")),
new invokerChecker(EVENT_SHOW, getNode("c4_middle")),
new invokerChecker(EVENT_HIDE, getNode("c4_child")),
new invokerChecker(EVENT_REORDER, getNode("c4"))
];
@ -415,7 +415,7 @@
}
//gA11yEventDumpToConsole = true; // debug stuff
//enableLogging("tree,verbose");
//enableLogging("events,verbose");
/**
* Do tests.

Просмотреть файл

@ -38,6 +38,27 @@
}
}
/**
* No name change on an accessible, because the accessible is recreated.
*/
function setAttr_recreate(aID, aAttr, aValue)
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getAccessible(aID)),
new invokerChecker(EVENT_SHOW, aID)
];
this.invoke = function setAttr_recreate_invoke()
{
todo(false, "No accessible recreation should happen, just name change event");
getNode(aID).setAttribute(aAttr, aValue);
}
this.getID = function setAttr_recreate_getID()
{
return "set attr '" + aAttr + "', value '" + aValue + "'";
}
}
////////////////////////////////////////////////////////////////////////////
// Do tests
@ -64,8 +85,7 @@
gQueue.push(new setAttr("tst2", "title", "title",
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
gQueue.push(new setAttr("tst3", "alt", "alt",
new invokerChecker(EVENT_NAME_CHANGE, "tst3")));
gQueue.push(new setAttr_recreate("tst3", "alt", "alt"));
gQueue.push(new setAttr("tst3", "title", "title",
new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst3")));

Просмотреть файл

@ -114,7 +114,5 @@
<option id="lb2_item4" value="tomatoes">tomatoes
<option id="lb2_item5" value="olives">olives
</select>
<div id="eventdump"></div>
</body>
</html>

Просмотреть файл

@ -25,12 +25,20 @@
<script type="application/javascript">
function advanceTab(aTabsID, aDirection, aNextTabID)
{
this.eventSeq = [
var eventSeq1 = [
new invokerChecker(EVENT_SELECTION, aNextTabID)
]
defineScenario(this, eventSeq1);
var eventSeq2 = [
new invokerChecker(EVENT_HIDE, getAccessible(aNextTabID)),
new invokerChecker(EVENT_SHOW, aNextTabID)
];
defineScenario(this, eventSeq2);
this.invoke = function advanceTab_invoke()
{
todo(false, "No accessible recreation should happen, just selection event");
getNode(aTabsID).advanceSelectedTab(aDirection, true);
}
@ -148,6 +156,7 @@
*/
var gQueue = null;
//enableLogging("events");
//gA11yEventDumpToConsole = true; // debuggin
function doTests()

Просмотреть файл

@ -254,9 +254,9 @@
////////////////////////////////////////////////////////////////////////////
// Do tests
var gQueue = null;
//gA11yEventDumpID = "eventdump"; // debug stuff
gA11yEventDumpToConsole = true; // debugging
var gQueue = null;
function doTests()
{
gQueue = new eventQueue();
@ -327,7 +327,6 @@
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="eventdump"></div>
<p id="p"><span><span>333</span><span>22</span></span>1111</p>
<div id="div">hello<div>hello</div>hello</div>

Просмотреть файл

@ -28,8 +28,8 @@
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t1_button")),
new invokerChecker(EVENT_SHOW, getNode("t1_button")),
new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
// no hide for t1_subdiv because it is contained by hidden t1_checkbox
new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")),
new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")),
new invokerChecker(EVENT_REORDER, getNode("t1_container"))
@ -73,10 +73,10 @@
function removeARIAOwns()
{
this.eventSeq = [
new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
new invokerChecker(EVENT_HIDE, getNode("t1_button")),
new invokerChecker(EVENT_SHOW, getNode("t1_button")),
new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
new invokerChecker(EVENT_REORDER, getNode("t1_container"))
];
@ -107,9 +107,9 @@
function setARIAOwns()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
new invokerChecker(EVENT_HIDE, getNode("t1_button")),
new invokerChecker(EVENT_SHOW, getNode("t1_button")),
new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
new invokerChecker(EVENT_REORDER, getNode("t1_container"))
];
@ -142,8 +142,8 @@
function addIdToARIAOwns()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t1_group")),
new invokerChecker(EVENT_SHOW, getNode("t1_group")),
new invokerChecker(EVENT_HIDE, getNode("t1_group")),
new invokerChecker(EVENT_REORDER, document)
];
@ -275,8 +275,8 @@
function setId()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")),
new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
new invokerChecker(EVENT_REORDER, document)
];
@ -515,7 +515,7 @@
////////////////////////////////////////////////////////////////////////////
//gA11yEventDumpToConsole = true;
//enableLogging("tree,verbose"); // debug stuff
//enableLogging("tree,eventTree,verbose"); // debug stuff
var gQueue = null;

Просмотреть файл

@ -21,9 +21,9 @@
this.containerNode = getNode("outerDiv");
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("child")),
new invokerChecker(EVENT_HIDE, getNode("childDoc")),
new invokerChecker(EVENT_SHOW, "newChildDoc"),
new invokerChecker(EVENT_HIDE, getNode("child")),
new invokerChecker(EVENT_REORDER, this.containerNode)
];
@ -46,7 +46,8 @@
}
}
gA11yEventDumpToConsole = true;
//enableLogging("tree");
//gA11yEventDumpToConsole = true;
var gQueue = null;
function doTest()
{