Bug 812767 - move out event processing logging from NotificationController, r=tbsaunde

--HG--
rename : accessible/src/base/NotificationController.cpp => accessible/src/base/EventQueue.cpp
rename : accessible/src/base/NotificationController.h => accessible/src/base/EventQueue.h
This commit is contained in:
Alexander Surkov 2013-01-29 19:00:58 +09:00
parent 2f218c35f1
commit 4d57ff6887
7 changed files with 604 additions and 543 deletions

View File

@ -123,7 +123,7 @@ protected:
EEventRule mEventRule;
nsRefPtr<Accessible> mAccessible;
friend class NotificationController;
friend class EventQueue;
friend class AccReorderEvent;
};
@ -163,7 +163,7 @@ private:
uint64_t mState;
bool mIsEnabled;
friend class NotificationController;
friend class EventQueue;
};
@ -198,7 +198,7 @@ private:
bool mIsInserted;
nsString mModifiedText;
friend class NotificationController;
friend class EventQueue;
friend class AccReorderEvent;
};
@ -235,7 +235,7 @@ protected:
nsRefPtr<Accessible> mParent;
nsRefPtr<AccTextChangeEvent> mTextChangeEvent;
friend class NotificationController;
friend class EventQueue;
};
@ -265,7 +265,7 @@ protected:
nsRefPtr<Accessible> mNextSibling;
nsRefPtr<Accessible> mPrevSibling;
friend class NotificationController;
friend class EventQueue;
};
@ -333,7 +333,7 @@ protected:
*/
nsTArray<AccMutationEvent*> mDependentEvents;
friend class NotificationController;
friend class EventQueue;
};
@ -363,7 +363,7 @@ public:
private:
int32_t mCaretOffset;
friend class NotificationController;
friend class EventQueue;
};
@ -400,7 +400,7 @@ private:
uint32_t mPreceedingCount;
AccSelChangeEvent* mPackedEvent;
friend class NotificationController;
friend class EventQueue;
};

View File

@ -0,0 +1,491 @@
/* -*- 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 "EventQueue.h"
#include "Accessible-inl.h"
#include "DocAccessible-inl.h"
#include "nsEventShell.h"
using namespace mozilla;
using namespace mozilla::a11y;
// Defines the number of selection add/remove events in the queue when they
// aren't packed into single selection within event.
const unsigned int kSelChangeCountToPack = 5;
////////////////////////////////////////////////////////////////////////////////
// EventQueue
////////////////////////////////////////////////////////////////////////////////
bool
EventQueue::PushEvent(AccEvent* aEvent)
{
NS_ASSERTION((aEvent->mAccessible && aEvent->mAccessible->IsApplication()) ||
aEvent->GetDocAccessible() == mDocument,
"Queued event belongs to another document!");
if (!mEvents.AppendElement(aEvent))
return false;
// Filter events.
CoalesceEvents();
// 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;
}
////////////////////////////////////////////////////////////////////////////////
// EventQueue: private
void
EventQueue::CoalesceEvents()
{
NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!");
uint32_t tail = mEvents.Length() - 1;
AccEvent* tailEvent = mEvents[tail];
switch(tailEvent->mEventRule) {
case AccEvent::eCoalesceReorder:
CoalesceReorderEvents(tailEvent);
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.
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule) {
accEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
}
} break; // case eCoalesceOfSameType
case AccEvent::eCoalesceSelectionChange:
{
AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule == tailEvent->mEventRule) {
AccSelChangeEvent* thisSelChangeEvent =
downcast_accEvent(thisEvent);
// Coalesce selection change events within same control.
if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
return;
}
}
}
} break; // eCoalesceSelectionChange
case AccEvent::eCoalesceStateChange:
{
// If state change event is duped then ignore previous event. If state
// change event is opposite to previous event then no event is emitted
// (accessible state wasn't changed).
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
thisEvent->mEventType == tailEvent->mEventType &&
thisEvent->mAccessible == tailEvent->mAccessible) {
AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent);
AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent);
if (thisSCEvent->mState == tailSCEvent->mState) {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled)
tailEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
}
break; // eCoalesceStateChange
}
case AccEvent::eRemoveDupes:
{
// Check for repeat events, coalesce newly appended event by more older
// event.
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule &&
accEvent->mAccessible == tailEvent->mAccessible) {
tailEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
}
} break; // case eRemoveDupes
default:
break; // case eAllowDupes, eDoNotEmit
} // 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) {
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
EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
AccSelChangeEvent* aThisEvent,
uint32_t aThisIndex)
{
aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
// Pack all preceding events into single selection within event
// when we receive too much selection add/remove events.
if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
aTailEvent->mAccessible = aTailEvent->mWidget;
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
// Do not emit any preceding selection events for same widget if they
// weren't coalesced yet.
if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
AccEvent* prevEvent = mEvents[jdx];
if (prevEvent->mEventRule == aTailEvent->mEventRule) {
AccSelChangeEvent* prevSelChangeEvent =
downcast_accEvent(prevEvent);
if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
}
return;
}
// Pack sequential selection remove and selection add events into
// single selection change event.
if (aTailEvent->mPreceedingCount == 1 &&
aTailEvent->mItem != aThisEvent->mItem) {
if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aTailEvent->mPackedEvent = aThisEvent;
return;
}
if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aTailEvent->mEventRule = AccEvent::eDoNotEmit;
aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aThisEvent->mPackedEvent = aThisEvent;
return;
}
}
// Unpack the packed selection change event because we've got one
// more selection add/remove.
if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
if (aThisEvent->mPackedEvent) {
aThisEvent->mPackedEvent->mEventType =
aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
aThisEvent->mPackedEvent->mEventRule =
AccEvent::eCoalesceSelectionChange;
aThisEvent->mPackedEvent = nullptr;
}
aThisEvent->mEventType =
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
return;
}
// Convert into selection add since control has single selection but other
// selection events for this control are queued.
if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
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
void
EventQueue::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) {
AccCaretMoveEvent* caretMoveEvent = downcast_accEvent(event);
HyperTextAccessible* hyperText = target->AsHyperText();
if (hyperText &&
NS_SUCCEEDED(hyperText->GetCaretOffset(&caretMoveEvent->mCaretOffset))) {
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;
}
}

View File

@ -0,0 +1,90 @@
/* -*- 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_EventQueue_h_
#define mozilla_a11y_EventQueue_h_
#include "AccEvent.h"
class nsIContent;
namespace mozilla {
namespace a11y {
class DocAccessible;
/**
* Used to organize and coalesce pending events.
*/
class EventQueue
{
protected:
EventQueue(DocAccessible* aDocument) : mDocument(aDocument) { }
/**
* Put an accessible event into the queue to process it later.
*/
bool PushEvent(AccEvent* aEvent);
/**
* Process events from the queue and fires events.
*/
void ProcessEventQueue();
private:
EventQueue(const EventQueue&) MOZ_DELETE;
EventQueue& operator = (const EventQueue&) MOZ_DELETE;
// Event queue processing
/**
* Coalesce redundant events from the queue.
*/
void CoalesceEvents();
/**
* Coalesce events from the same subtree.
*/
void CoalesceReorderEvents(AccEvent* aTailEvent);
/**
* Coalesce two selection change events within the same select control.
*/
void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
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.
*/
nsRefPtr<DocAccessible> mDocument;
/**
* Pending events array. Don't make this an nsAutoTArray; we use
* SwapElements() on it.
*/
nsTArray<nsRefPtr<AccEvent> > mEvents;
};
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_EventQueue_h_

View File

@ -22,6 +22,7 @@ CPPSRCS = \
Filters.cpp \
ARIAStateMap.cpp \
DocManager.cpp \
EventQueue.cpp \
FocusManager.cpp \
NotificationController.cpp \
nsAccessNode.cpp \

View File

@ -6,20 +6,10 @@
#include "NotificationController.h"
#include "Accessible-inl.h"
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "DocAccessible-inl.h"
#include "nsEventShell.h"
#include "FocusManager.h"
#include "Role.h"
#include "TextLeafAccessible.h"
#include "TextUpdater.h"
#ifdef A11Y_LOG
#include "Logging.h"
#endif
#include "mozilla/dom/Element.h"
#include "mozilla/Telemetry.h"
@ -36,7 +26,7 @@ const unsigned int kSelChangeCountToPack = 5;
NotificationController::NotificationController(DocAccessible* aDocument,
nsIPresShell* aPresShell) :
mObservingState(eNotObservingRefresh), mDocument(aDocument),
EventQueue(aDocument), mObservingState(eNotObservingRefresh),
mPresShell(aPresShell)
{
mTextHash.Init();
@ -102,28 +92,6 @@ NotificationController::Shutdown()
mEvents.Clear();
}
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;
// Filter events.
CoalesceEvents();
// 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);
ScheduleProcessing();
}
void
NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
{
@ -316,451 +284,6 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
}
}
void
NotificationController::CoalesceEvents()
{
NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!");
uint32_t tail = mEvents.Length() - 1;
AccEvent* tailEvent = mEvents[tail];
switch(tailEvent->mEventRule) {
case AccEvent::eCoalesceReorder:
CoalesceReorderEvents(tailEvent);
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.
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule) {
accEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
}
} break; // case eCoalesceOfSameType
case AccEvent::eCoalesceSelectionChange:
{
AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule == tailEvent->mEventRule) {
AccSelChangeEvent* thisSelChangeEvent =
downcast_accEvent(thisEvent);
// Coalesce selection change events within same control.
if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
return;
}
}
}
} break; // eCoalesceSelectionChange
case AccEvent::eCoalesceStateChange:
{
// If state change event is duped then ignore previous event. If state
// change event is opposite to previous event then no event is emitted
// (accessible state wasn't changed).
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
thisEvent->mEventType == tailEvent->mEventType &&
thisEvent->mAccessible == tailEvent->mAccessible) {
AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent);
AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent);
if (thisSCEvent->mState == tailSCEvent->mState) {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled)
tailEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
}
break; // eCoalesceStateChange
}
case AccEvent::eRemoveDupes:
{
// Check for repeat events, coalesce newly appended event by more older
// event.
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule &&
accEvent->mAccessible == tailEvent->mAccessible) {
tailEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
}
} break; // case eRemoveDupes
default:
break; // case eAllowDupes, eDoNotEmit
} // switch
}
void
NotificationController::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) {
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
NotificationController::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
AccSelChangeEvent* aThisEvent,
uint32_t aThisIndex)
{
aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
// Pack all preceding events into single selection within event
// when we receive too much selection add/remove events.
if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
aTailEvent->mAccessible = aTailEvent->mWidget;
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
// Do not emit any preceding selection events for same widget if they
// weren't coalesced yet.
if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
AccEvent* prevEvent = mEvents[jdx];
if (prevEvent->mEventRule == aTailEvent->mEventRule) {
AccSelChangeEvent* prevSelChangeEvent =
downcast_accEvent(prevEvent);
if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
}
return;
}
// Pack sequential selection remove and selection add events into
// single selection change event.
if (aTailEvent->mPreceedingCount == 1 &&
aTailEvent->mItem != aThisEvent->mItem) {
if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aTailEvent->mPackedEvent = aThisEvent;
return;
}
if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aTailEvent->mEventRule = AccEvent::eDoNotEmit;
aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aThisEvent->mPackedEvent = aThisEvent;
return;
}
}
// Unpack the packed selection change event because we've got one
// more selection add/remove.
if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
if (aThisEvent->mPackedEvent) {
aThisEvent->mPackedEvent->mEventType =
aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
aThisEvent->mPackedEvent->mEventRule =
AccEvent::eCoalesceSelectionChange;
aThisEvent->mPackedEvent = nullptr;
}
aThisEvent->mEventType =
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
return;
}
// Convert into selection add since control has single selection but other
// selection events for this control are queued.
if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
}
void
NotificationController::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
NotificationController::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
NotificationController::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);
}
////////////////////////////////////////////////////////////////////////////////
// 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) {
AccCaretMoveEvent* caretMoveEvent = downcast_accEvent(event);
HyperTextAccessible* hyperText = target->AsHyperText();
if (hyperText &&
NS_SUCCEEDED(hyperText->GetCaretOffset(&caretMoveEvent->mCaretOffset))) {
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

@ -3,10 +3,11 @@
* 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 NotificationController_h_
#define NotificationController_h_
#ifndef mozilla_a11y_NotificationController_h_
#define mozilla_a11y_NotificationController_h_
#include "EventQueue.h"
#include "AccEvent.h"
#include "nsCycleCollectionParticipant.h"
#include "nsRefreshDriver.h"
@ -84,7 +85,8 @@ private:
/**
* Used to process notifications from core for the document accessible.
*/
class NotificationController : public nsARefreshObserver
class NotificationController : public EventQueue,
public nsARefreshObserver
{
public:
NotificationController(DocAccessible* aDocument, nsIPresShell* aPresShell);
@ -103,7 +105,11 @@ public:
/**
* Put an accessible event into the queue to process it later.
*/
void QueueEvent(AccEvent* aEvent);
void QueueEvent(AccEvent* aEvent)
{
if (PushEvent(aEvent))
ScheduleProcessing();
}
/**
* Schedule binding the child document to the tree of this document.
@ -198,46 +204,6 @@ private:
// nsARefreshObserver
virtual void WillRefresh(mozilla::TimeStamp aTime);
// Event queue processing
/**
* Coalesce redundant events from the queue.
*/
void CoalesceEvents();
/**
* Coalesce events from the same subtree.
*/
void CoalesceReorderEvents(AccEvent* aTailEvent);
/**
* Coalesce two selection change events within the same select control.
*/
void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
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);
// Event queue processing
/**
* Process events from the queue and fires events.
*/
void ProcessEventQueue();
private:
/**
* Indicates whether we're waiting on an event queue processing from our
@ -250,11 +216,6 @@ private:
};
eObservingState mObservingState;
/**
* The document accessible reference owning this queue.
*/
nsRefPtr<DocAccessible> mDocument;
/**
* The presshell of the document accessible.
*/
@ -343,15 +304,9 @@ private:
* use SwapElements() on it.
*/
nsTArray<nsRefPtr<Notification> > mNotifications;
/**
* Pending events array. Don't make this an nsAutoTArray; we use
* SwapElements() on it.
*/
nsTArray<nsRefPtr<AccEvent> > mEvents;
};
} // namespace a11y
} // namespace mozilla
#endif
#endif // mozilla_a11y_NotificationController_h_

View File

@ -581,6 +581,7 @@ protected:
* Used to process notification from core and accessible events.
*/
nsRefPtr<NotificationController> mNotificationController;
friend class EventQueue;
friend class NotificationController;
private: