Bug 807911 - whittle mutation events processing, r=tbsaunde

This commit is contained in:
Alexander Surkov 2012-11-13 15:29:22 +09:00
parent 7556e76ede
commit f15104b9ad
14 changed files with 408 additions and 291 deletions

View File

@ -214,7 +214,6 @@ AccStateChangeEvent::CreateXPCOMObject()
// we are ready to fire the event and so we will no longer assert at that point
// if the node was removed from the document. Either way, the AT won't work with
// a defunct accessible so the behaviour should be equivalent.
// XXX revisit this when coalescence is faster (eCoalesceFromSameSubtree)
AccTextChangeEvent::
AccTextChangeEvent(Accessible* aAccessible, int32_t aStart,
const nsAString& aModifiedText, bool aIsInserted,
@ -243,17 +242,23 @@ AccTextChangeEvent::CreateXPCOMObject()
////////////////////////////////////////////////////////////////////////////////
// AccMutationEvent
// AccReorderEvent
////////////////////////////////////////////////////////////////////////////////
AccMutationEvent::
AccMutationEvent(uint32_t aEventType, Accessible* aTarget,
nsINode* aTargetNode) :
AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceFromSameSubtree)
uint32_t
AccReorderEvent::IsShowHideEventTarget(const Accessible* aTarget) const
{
mNode = aTargetNode;
}
uint32_t count = mDependentEvents.Length();
for (uint32_t index = count - 1; index < count; index--) {
if (mDependentEvents[index]->mAccessible == aTarget &&
mDependentEvents[index]->mEventType == nsIAccessibleEvent::EVENT_SHOW ||
mDependentEvents[index]->mEventType == nsIAccessibleEvent::EVENT_HIDE) {
return mDependentEvents[index]->mEventType;
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// AccHideEvent
@ -263,7 +268,6 @@ AccHideEvent::
AccHideEvent(Accessible* aTarget, nsINode* aTargetNode) :
AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget, aTargetNode)
{
mParent = mAccessible->Parent();
mNextSibling = mAccessible->NextSibling();
mPrevSibling = mAccessible->PrevSibling();
}

View File

@ -38,10 +38,13 @@ public:
// This event will always be emitted.
eAllowDupes,
// eCoalesceFromSameSubtree : For events of the same type from the same
// subtree or the same node, only the umbrella event on the ancestor
// will be emitted.
eCoalesceFromSameSubtree,
// eCoalesceReorder : For reorder events from the same subtree or the same
// node, only the umbrella event on the ancestor will be emitted.
eCoalesceReorder,
// eCoalesceMutationTextChange : coalesce text change events caused by
// tree mutations of the same tree level.
eCoalesceMutationTextChange,
// eCoalesceOfSameType : For events of the same type, only the newest event
// will be processed.
@ -90,6 +93,7 @@ public:
eStateChangeEvent,
eTextChangeEvent,
eMutationEvent,
eReorderEvent,
eHideEvent,
eShowEvent,
eCaretMoveEvent,
@ -129,6 +133,7 @@ protected:
nsCOMPtr<nsINode> mNode;
friend class NotificationController;
friend class AccReorderEvent;
};
@ -197,6 +202,7 @@ private:
nsString mModifiedText;
friend class NotificationController;
friend class AccReorderEvent;
};
@ -207,7 +213,15 @@ class AccMutationEvent: public AccEvent
{
public:
AccMutationEvent(uint32_t aEventType, Accessible* aTarget,
nsINode* aTargetNode);
nsINode* aTargetNode) :
AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceMutationTextChange)
{
// Don't coalesce these since they are coalesced by reorder event. Coalesce
// contained text change events.
mNode = aTargetNode;
mParent = mAccessible->Parent();
}
virtual ~AccMutationEvent() { };
// Event
static const EventGroup kEventGroup = eMutationEvent;
@ -221,6 +235,7 @@ public:
bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; }
protected:
nsRefPtr<Accessible> mParent;
nsRefPtr<AccTextChangeEvent> mTextChangeEvent;
friend class NotificationController;
@ -250,7 +265,6 @@ public:
Accessible* TargetPrevSibling() const { return mPrevSibling; }
protected:
nsRefPtr<Accessible> mParent;
nsRefPtr<Accessible> mNextSibling;
nsRefPtr<Accessible> mPrevSibling;
@ -275,6 +289,57 @@ public:
};
/**
* Class for reorder accessible event. Takes care about
*/
class AccReorderEvent : public AccEvent
{
public:
AccReorderEvent(Accessible* aTarget) :
AccEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget,
eAutoDetect, eCoalesceReorder) { }
virtual ~AccReorderEvent() { };
// Event
static const EventGroup kEventGroup = eReorderEvent;
virtual unsigned int GetEventGroups() const
{
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 NotificationController;
};
/**
* Accessible caret move event.
*/

View File

@ -110,6 +110,10 @@ NotificationController::Shutdown()
void
NotificationController::QueueEvent(AccEvent* aEvent)
{
NS_ASSERTION(aEvent->mAccessible && aEvent->mAccessible->IsApplication() ||
aEvent->GetDocAccessible() == mDocument,
"Queued event belongs to another document!");
if (!mEvents.AppendElement(aEvent))
return;
@ -298,44 +302,9 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
// process it synchronously.
mObservingState = eRefreshObserving;
// Process only currently queued events.
nsTArray<nsRefPtr<AccEvent> > events;
events.SwapElements(mEvents);
uint32_t eventCount = events.Length();
#ifdef A11Y_LOG
if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
logging::MsgBegin("EVENTS", "events processing");
logging::Address("document", mDocument);
logging::MsgEnd();
}
#endif
for (uint32_t idx = 0; idx < eventCount; idx++) {
AccEvent* accEvent = events[idx];
if (accEvent->mEventRule != AccEvent::eDoNotEmit) {
Accessible* target = accEvent->GetAccessible();
if (!target || target->IsDefunct())
continue;
// Dispatch the focus event if target is still focused.
if (accEvent->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
FocusMgr()->ProcessFocusEvent(accEvent);
continue;
}
mDocument->ProcessPendingEvent(accEvent);
// Fire text change event caused by tree mutation.
AccMutationEvent* showOrHideEvent = downcast_accEvent(accEvent);
if (showOrHideEvent) {
if (showOrHideEvent->mTextChangeEvent)
mDocument->ProcessPendingEvent(showOrHideEvent->mTextChangeEvent);
}
}
if (!mDocument)
return;
}
ProcessEventQueue();
if (!mDocument)
return;
// Stop further processing if there are no new notifications of any kind or
// events and document load is processed.
@ -348,9 +317,6 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
}
}
////////////////////////////////////////////////////////////////////////////////
// NotificationController: event queue
void
NotificationController::CoalesceEvents()
{
@ -359,113 +325,54 @@ NotificationController::CoalesceEvents()
AccEvent* tailEvent = mEvents[tail];
switch(tailEvent->mEventRule) {
case AccEvent::eCoalesceFromSameSubtree:
case AccEvent::eCoalesceReorder:
CoalesceReorderEvents(tailEvent);
break; // case eCoalesceReorder
case AccEvent::eCoalesceMutationTextChange:
{
// No node means this is application accessible (which is a subject of
// reorder events), we do not coalesce events for it currently.
if (!tailEvent->mNode)
return;
for (int32_t index = tail - 1; index >= 0; index--) {
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventType != tailEvent->mEventType)
continue; // Different type
// Skip event for application accessible since no coalescence for it
// is supported. Ignore events from different documents since we don't
// coalesce them.
if (!thisEvent->mNode ||
thisEvent->mNode->OwnerDoc() != tailEvent->mNode->OwnerDoc())
if (thisEvent->mEventRule != tailEvent->mEventRule)
continue;
// Coalesce earlier event for the same target.
if (thisEvent->mNode == tailEvent->mNode) {
// 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;
return;
}
// If event queue contains an event of the same type and having target
// that is sibling of target of newly appended event then apply its
// event rule to the newly appended event.
AccMutationEvent* tailMutationEvent = downcast_accEvent(tailEvent);
AccMutationEvent* thisMutationEvent = downcast_accEvent(thisEvent);
if (tailMutationEvent->mParent != thisMutationEvent->mParent)
continue;
// Coalesce hide and show events for sibling targets.
if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE) {
// Coalesce text change events for hide and show events.
if (thisMutationEvent->IsHide()) {
AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent);
AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent);
if (thisHideEvent->mParent == tailHideEvent->mParent) {
tailEvent->mEventRule = thisEvent->mEventRule;
// Coalesce text change events for hide events.
if (tailEvent->mEventRule != AccEvent::eDoNotEmit)
CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
return;
}
} else if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_SHOW) {
if (thisEvent->mAccessible->Parent() ==
tailEvent->mAccessible->Parent()) {
tailEvent->mEventRule = thisEvent->mEventRule;
// Coalesce text change events for show events.
if (tailEvent->mEventRule != AccEvent::eDoNotEmit) {
AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent);
AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent);
CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent);
}
return;
}
CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent);
break;
}
// Ignore events unattached from DOM since we don't coalesce them.
if (!thisEvent->mNode->IsInDoc())
continue;
// Coalesce events by sibling targets (this is a case for reorder
// events).
if (thisEvent->mNode->GetParentNode() ==
tailEvent->mNode->GetParentNode()) {
tailEvent->mEventRule = thisEvent->mEventRule;
return;
}
// This and tail events can be anywhere in the tree, make assumptions
// for mutation events.
// Coalesce tail event if tail node is descendant of this node. Stop
// processing if tail event is coalesced since all possible descendants
// of this node was coalesced before.
// Note: more older hide event target (thisNode) can't contain recent
// hide event target (tailNode), i.e. be ancestor of tailNode. Skip
// this check for hide events.
if (tailEvent->mEventType != nsIAccessibleEvent::EVENT_HIDE &&
nsCoreUtils::IsAncestorOf(thisEvent->mNode, tailEvent->mNode)) {
tailEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
// If this node is a descendant of tail node then coalesce this event,
// check other events in the queue. Do not emit thisEvent, also apply
// this result to sibling nodes of thisNode.
if (nsCoreUtils::IsAncestorOf(tailEvent->mNode, thisEvent->mNode)) {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
ApplyToSiblings(0, index, thisEvent->mEventType,
thisEvent->mNode, AccEvent::eDoNotEmit);
continue;
}
} // for (index)
} break; // case eCoalesceFromSameSubtree
AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent);
AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent);
CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent);
break;
}
} break; // case eCoalesceMutationTextChange
case AccEvent::eCoalesceOfSameType:
{
// Coalesce old events by newer event.
for (int32_t index = tail - 1; index >= 0; index--) {
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule) {
accEvent->mEventRule == tailEvent->mEventRule) {
accEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
@ -476,7 +383,7 @@ NotificationController::CoalesceEvents()
{
// Check for repeat events, coalesce newly appended event by more older
// event.
for (int32_t index = tail - 1; index >= 0; index--) {
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule &&
@ -513,18 +420,92 @@ NotificationController::CoalesceEvents()
}
void
NotificationController::ApplyToSiblings(uint32_t aStart, uint32_t aEnd,
uint32_t aEventType, nsINode* aNode,
AccEvent::EEventRule aEventRule)
NotificationController::CoalesceReorderEvents(AccEvent* aTailEvent)
{
for (uint32_t index = aStart; index < aEnd; index ++) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == aEventType &&
accEvent->mEventRule != AccEvent::eDoNotEmit && accEvent->mNode &&
accEvent->mNode->GetParentNode() == aNode->GetParentNode()) {
accEvent->mEventRule = aEventRule;
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) {
if (thisEvent->mEventRule == AccEvent::eDoNotEmit) {
AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
tailReorder->DoNotEmitAll();
} else {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
}
return;
}
// If tailEvent contains thisEvent
// then
// if show of tailEvent contains a grand parent of thisEvent
// then assert
// else if 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);
if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
NS_ERROR("Accessible tree was created after it was modified! Huh?");
} else if (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;
return;
}
tailParent = tailParent->Parent();
}
} // for (index)
}
void
@ -544,7 +525,7 @@ NotificationController::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
// Do not emit any preceding selection events for same widget if they
// weren't coalesced yet.
if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
for (int32_t jdx = aThisIndex - 1; jdx >= 0; jdx--) {
for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
AccEvent* prevEvent = mEvents[jdx];
if (prevEvent->mEventRule == aTailEvent->mEventRule) {
AccSelChangeEvent* prevSelChangeEvent =
@ -692,6 +673,76 @@ NotificationController::CreateTextChangeEventFor(AccMutationEvent* aEvent)
aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
}
////////////////////////////////////////////////////////////////////////////////
// NotificationController: event queue
void
NotificationController::ProcessEventQueue()
{
// Process only currently queued events.
nsTArray<nsRefPtr<AccEvent> > events;
events.SwapElements(mEvents);
uint32_t eventCount = events.Length();
#ifdef A11Y_LOG
if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
logging::MsgBegin("EVENTS", "events processing");
logging::Address("document", mDocument);
logging::MsgEnd();
}
#endif
for (uint32_t idx = 0; idx < eventCount; idx++) {
AccEvent* event = events[idx];
if (event->mEventRule != AccEvent::eDoNotEmit) {
Accessible* target = event->GetAccessible();
if (!target || target->IsDefunct())
continue;
// Dispatch the focus event if target is still focused.
if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
FocusMgr()->ProcessFocusEvent(event);
continue;
}
// Dispatch caret moved and text selection change events.
if (event->mEventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
HyperTextAccessible* hyperText = target->AsHyperText();
int32_t caretOffset = -1;
if (hyperText &&
NS_SUCCEEDED(hyperText->GetCaretOffset(&caretOffset))) {
nsRefPtr<AccEvent> caretMoveEvent =
new AccCaretMoveEvent(hyperText, caretOffset);
nsEventShell::FireEvent(caretMoveEvent);
// There's a selection so fire selection change as well.
int32_t selectionCount;
hyperText->GetSelectionCount(&selectionCount);
if (selectionCount)
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
hyperText);
}
continue;
}
nsEventShell::FireEvent(event);
// Fire text change events.
AccMutationEvent* mutationEvent = downcast_accEvent(event);
if (mutationEvent) {
if (mutationEvent->mTextChangeEvent)
nsEventShell::FireEvent(mutationEvent->mTextChangeEvent);
}
}
if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE)
mDocument->ShutdownChildrenInSubtree(event->mAccessible);
if (!mDocument)
return;
}
}
////////////////////////////////////////////////////////////////////////////////
// Notification controller: text leaf accessible text update

View File

@ -201,18 +201,9 @@ private:
void CoalesceEvents();
/**
* Apply aEventRule to same type event that from sibling nodes of aDOMNode.
* @param aEventsToFire array of pending events
* @param aStart start index of pending events to be scanned
* @param aEnd end index to be scanned (not included)
* @param aEventType target event type
* @param aDOMNode target are siblings of this node
* @param aEventRule the event rule to be applied
* (should be eDoNotEmit or eAllowDupes)
* Coalesce events from the same subtree.
*/
void ApplyToSiblings(uint32_t aStart, uint32_t aEnd,
uint32_t aEventType, nsINode* aNode,
AccEvent::EEventRule aEventRule);
void CoalesceReorderEvents(AccEvent* aTailEvent);
/**
* Coalesce two selection change events within the same select control.
@ -230,11 +221,18 @@ private:
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.
* 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);
// Event queue processing
/**
* Process events from the queue and fires events.
*/
void CreateTextChangeEventFor(AccMutationEvent* aEvent);
void ProcessEventQueue();
private:
/**

View File

@ -398,9 +398,10 @@ nsAccDocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
// the tree. The reorder event is delivered after the document tree is
// constructed because event processing and tree construction are done by
// the same document.
// Note: don't use AccReorderEvent to avoid coalsecense and special reorder
// events processing.
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, ApplicationAcc(),
eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, ApplicationAcc());
docAcc->FireDelayedAccessibleEvent(reorderEvent);
} else {

View File

@ -49,7 +49,7 @@ DocAccessible::MaybeNotifyOfValueChange(Accessible* aAccessible)
new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible,
eAutoDetect, AccEvent::eRemoveDupes);
FireDelayedAccessibleEvent(valueChangeEvent);
}
}
}
#endif

View File

@ -1572,9 +1572,7 @@ DocAccessible::DoInitialUpdate()
// this document may be fired prior to this reorder event. If this is
// a problem then consider to keep event processing per tab document.
if (!IsRoot()) {
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, Parent(), eAutoDetect,
AccEvent::eCoalesceFromSameSubtree);
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
ParentDocument()->FireDelayedAccessibleEvent(reorderEvent);
}
}
@ -1785,75 +1783,53 @@ DocAccessible::FireDelayedAccessibleEvent(AccEvent* aEvent)
return NS_OK;
}
void
DocAccessible::ProcessPendingEvent(AccEvent* aEvent)
{
uint32_t eventType = aEvent->GetEventType();
if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
HyperTextAccessible* hyperText = aEvent->GetAccessible()->AsHyperText();
int32_t caretOffset;
if (hyperText &&
NS_SUCCEEDED(hyperText->GetCaretOffset(&caretOffset))) {
nsRefPtr<AccEvent> caretMoveEvent =
new AccCaretMoveEvent(hyperText, caretOffset);
nsEventShell::FireEvent(caretMoveEvent);
int32_t selectionCount;
hyperText->GetSelectionCount(&selectionCount);
if (selectionCount) { // There's a selection so fire selection change as well
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
hyperText);
}
}
}
else {
nsEventShell::FireEvent(aEvent);
// Post event processing
if (eventType == nsIAccessibleEvent::EVENT_HIDE)
ShutdownChildrenInSubtree(aEvent->GetAccessible());
}
}
void
DocAccessible::ProcessContentInserted(Accessible* aContainer,
const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent)
{
// Process the notification if the container accessible is still in tree.
// Process insertions if the container accessible is still in tree.
if (!HasAccessible(aContainer->GetNode()))
return;
if (aContainer == this) {
// If new root content has been inserted then update it.
nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocument);
if (rootContent != mContent)
mContent = rootContent;
bool containerNotUpdated = true;
// 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.
}
// XXX: Invalidate parent-child relations for container accessible and its
// children because there's no good way to find insertion point of new child
// accessibles into accessible tree. We need to invalidate children even
// there's no inserted accessibles in the end because accessible children
// are created while parent recaches child accessibles.
aContainer->UpdateChildren();
// 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). These cases result in
// double processing, however generated events are coalesced and we don't
// harm an AT.
// Theoretically the element might be not in tree at all at this point what
// means there's no container.
for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) {
Accessible* directContainer =
// 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* presentContainer =
GetContainerAccessible(aInsertedContent->ElementAt(idx));
if (directContainer)
UpdateTree(directContainer, aInsertedContent->ElementAt(idx), true);
if (presentContainer != aContainer)
continue;
if (containerNotUpdated) {
containerNotUpdated = false;
if (aContainer == this) {
// If new root content has been inserted then update it.
nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocument);
if (rootContent != mContent)
mContent = rootContent;
// 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.
}
// XXX: Invalidate parent-child relations for container accessible and its
// children because there's no good way to find insertion point of new child
// accessibles into accessible tree. We need to invalidate children even
// there's no inserted accessibles in the end because accessible children
// are created while parent recaches child accessibles.
aContainer->UpdateChildren();
}
UpdateTree(aContainer, aInsertedContent->ElementAt(idx), true);
}
}
@ -1880,15 +1856,17 @@ DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
}
#endif
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
if (child) {
updateFlags |= UpdateTreeInternal(child, aIsInsert);
updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
} else {
nsAccTreeWalker walker(this, aChildNode,
aContainer->CanHaveAnonChildren(), true);
while ((child = walker.NextChild()))
updateFlags |= UpdateTreeInternal(child, aIsInsert);
updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
}
// Content insertion/removal is not cause of accessible tree change.
@ -1903,8 +1881,9 @@ DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
Accessible* ancestor = aContainer;
while (ancestor) {
if (ancestor->ARIARole() == roles::ALERT) {
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT,
ancestor->GetNode());
nsRefPtr<AccEvent> alertEvent =
new AccEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
FireDelayedAccessibleEvent(alertEvent);
break;
}
@ -1920,15 +1899,12 @@ DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
// Fire reorder event so the MSAA clients know the children have changed. Also
// the event is used internally by MSAA layer.
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, aContainer->GetNode(),
eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
if (reorderEvent)
FireDelayedAccessibleEvent(reorderEvent);
FireDelayedAccessibleEvent(reorderEvent);
}
uint32_t
DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert)
DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
AccReorderEvent* aReorderEvent)
{
uint32_t updateFlags = eAccessible;
@ -1950,34 +1926,34 @@ DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert)
if (aChild->ARIARole() == roles::MENUPOPUP) {
nsRefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild);
if (event)
FireDelayedAccessibleEvent(event);
FireDelayedAccessibleEvent(event);
}
}
// Fire show/hide event.
nsRefPtr<AccEvent> event;
nsRefPtr<AccMutationEvent> event;
if (aIsInsert)
event = new AccShowEvent(aChild, node);
else
event = new AccHideEvent(aChild, node);
if (event)
FireDelayedAccessibleEvent(event);
FireDelayedAccessibleEvent(event);
aReorderEvent->AddSubMutationEvent(event);
if (aIsInsert) {
roles::Role ariaRole = aChild->ARIARole();
if (ariaRole == roles::MENUPOPUP) {
// Fire EVENT_MENUPOPUP_START if ARIA menu appears.
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
node, AccEvent::eRemoveDupes);
nsRefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild);
FireDelayedAccessibleEvent(event);
} else if (ariaRole == roles::ALERT) {
// Fire EVENT_ALERT if ARIA alert appears.
updateFlags = eAlertAccessible;
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, node,
AccEvent::eRemoveDupes);
nsRefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_ALERT, aChild);
FireDelayedAccessibleEvent(event);
}
// If focused node has been shown then it means its frame was recreated

View File

@ -427,12 +427,6 @@ protected:
*/
void ARIAActiveDescendantChanged(nsIContent* aElm);
/**
* Process the event when the queue of pending events is untwisted. Fire
* accessible events as result of the processing.
*/
void ProcessPendingEvent(AccEvent* aEvent);
/**
* Update the accessible tree for inserted content.
*/
@ -464,7 +458,8 @@ protected:
eAlertAccessible = 2
};
uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert);
uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
AccReorderEvent* aReorderEvent);
/**
* Create accessible tree.

View File

@ -85,6 +85,7 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents)
return;
bool doReorderEvent = false;
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
// Remove areas that are not a valid part of the image map anymore.
for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) {
@ -93,8 +94,9 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents)
continue;
if (aDoFireEvents) {
nsRefPtr<AccEvent> event = new AccHideEvent(area, area->GetContent());
nsRefPtr<AccHideEvent> event = new AccHideEvent(area, area->GetContent());
mDoc->FireDelayedAccessibleEvent(event);
reorderEvent->AddSubMutationEvent(event);
doReorderEvent = true;
}
@ -118,20 +120,17 @@ HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents)
}
if (aDoFireEvents) {
nsRefPtr<AccEvent> event = new AccShowEvent(area, areaContent);
nsRefPtr<AccShowEvent> event = new AccShowEvent(area, areaContent);
mDoc->FireDelayedAccessibleEvent(event);
reorderEvent->AddSubMutationEvent(event);
doReorderEvent = true;
}
}
}
// Fire reorder event if needed.
if (doReorderEvent) {
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, mContent,
eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
if (doReorderEvent)
mDoc->FireDelayedAccessibleEvent(reorderEvent);
}
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -662,11 +662,8 @@ XULTreeAccessible::TreeViewChanged(nsITreeView* aView)
// Fire reorder event on tree accessible on accessible tree (do not fire
// show/hide events on tree items because it can be expensive to fire them for
// each tree item.
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, this, eAutoDetect,
AccEvent::eCoalesceFromSameSubtree);
if (reorderEvent)
Document()->FireDelayedAccessibleEvent(reorderEvent);
nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
Document()->FireDelayedAccessibleEvent(reorderEvent);
// Clear cache.
ClearCache(mAccessibleCache);

View File

@ -503,9 +503,11 @@ function eventQueue(aEventType)
{
// Create unified event sequence concatenating expected and unexpected
// events.
this.mEventSeq = ("eventSeq" in aInvoker) ?
aInvoker.eventSeq :
[ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
this.mEventSeq = ("eventSeq" in aInvoker) ? aInvoker.eventSeq : [ ];
if (!this.mEventSeq.length && this.mDefEventType) {
this.mEventSeq.push(new invokerChecker(this.mDefEventType,
aInvoker.DOMNode));
}
var len = this.mEventSeq.length;
for (var idx = 0; idx < len; idx++) {

View File

@ -67,7 +67,7 @@
this.getID = function showMenu_getID()
{
return "Show ARIA menu " + aMenuID + " by " +
return "Show ARIA menu '" + aMenuID + "' by " +
(aHow == kViaDisplayStyle ? "display" : "visibility") +
" style tricks";
};
@ -150,7 +150,6 @@
var gQueue = null;
//gA11yEventDumpID = "eventdump"; // debuging
//gA11yEventDumpToConsole = true; // debuging
function doTests()

View File

@ -326,7 +326,7 @@
// Do tests.
var gQueue = null;
// var gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true; // debug stuff
function doTests()
{

View File

@ -242,8 +242,8 @@
}
/**
* Trigger content insertion, removal and insertion of the same element
* for the same parent.
* Trigger content insertion (flush layout), removal and insertion of
* the same element for the same parent.
*/
function test1(aContainerID)
{
@ -266,13 +266,14 @@
this.getID = function test1_getID()
{
return "test1";
return "fuzzy test #1: content insertion (flush layout), removal and" +
"reinsertion";
}
}
/**
* Trigger content insertion, removal and insertion of the same element
* for the different parents.
* Trigger content insertion (flush layout), removal and insertion of
* the same element for the different parents.
*/
function test2(aContainerID, aTmpContainerID)
{
@ -302,7 +303,36 @@
this.getID = function test2_getID()
{
return "test2";
return "fuzzy test #2: content insertion (flush layout), removal and" +
"reinsertion under another container";
}
}
/**
* Content insertion (flush layout) and then removal (nothing was changed).
*/
function test3(aContainerID)
{
this.divNode = document.createElement("div");
this.divNode.setAttribute("id", "div-test3");
this.containerNode = getNode(aContainerID);
this.unexpectedEventSeq = [
new invokerChecker(EVENT_SHOW, this.divNode),
new invokerChecker(EVENT_HIDE, this.divNode),
new invokerChecker(EVENT_REORDER, this.containerNode)
];
this.invoke = function test3_invoke()
{
this.containerNode.appendChild(this.divNode);
getComputedStyle(this.divNode, "").color;
this.containerNode.removeChild(this.divNode);
}
this.getID = function test3_getID()
{
return "fuzzy test #3: content insertion (flush layout) and removal";
}
}
@ -343,8 +373,7 @@
* Do tests.
*/
var gQueue = null;
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
//gA11yEventDumpToConsole = true; // debug stuff
function doTests()
{
@ -427,6 +456,7 @@
gQueue.push(new test1("testContainer"));
gQueue.push(new test2("testContainer", "testContainer2"));
gQueue.push(new test2("testContainer", "testNestedContainer"));
gQueue.push(new test3("testContainer"));
gQueue.invoke(); // Will call SimpleTest.finish();
}