Bug 515141 - move events firing logic from nsDocAccessible and nsAccEvent into special classes, r=ginn, davidb

This commit is contained in:
Alexander Surkov 2010-01-27 19:42:08 +08:00
Родитель 545521f948
Коммит ed118021e7
7 изменённых файлов: 522 добавлений и 464 удалений

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

@ -254,160 +254,6 @@ nsAccEvent::CaptureIsFromUserInput(EIsFromUserInput aIsFromUserInput)
mIsFromUserInput = esm->IsHandlingUserInputExternal();
}
////////////////////////////////////////////////////////////////////////////////
// nsAccEvent: static methods
/* static */
void
nsAccEvent::ApplyEventRules(nsTArray<nsRefPtr<nsAccEvent> > &aEventsToFire)
{
PRUint32 numQueuedEvents = aEventsToFire.Length();
PRInt32 tail = numQueuedEvents - 1;
nsAccEvent* tailEvent = aEventsToFire[tail];
switch(tailEvent->mEventRule) {
case nsAccEvent::eCoalesceFromSameSubtree:
{
for (PRInt32 index = 0; index < tail; index ++) {
nsAccEvent* thisEvent = aEventsToFire[index];
if (thisEvent->mEventType != tailEvent->mEventType)
continue; // Different type
if (thisEvent->mEventRule == nsAccEvent::eAllowDupes ||
thisEvent->mEventRule == nsAccEvent::eDoNotEmit)
continue; // Do not need to check
if (thisEvent->mDOMNode == tailEvent->mDOMNode) {
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
CoalesceReorderEventsFromSameSource(thisEvent, tailEvent);
continue;
}
// Dupe
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
continue;
}
if (nsCoreUtils::IsAncestorOf(tailEvent->mDOMNode,
thisEvent->mDOMNode)) {
// thisDOMNode is a descendant of tailDOMNode
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
CoalesceReorderEventsFromSameTree(tailEvent, thisEvent);
continue;
}
// Do not emit thisEvent, also apply this result to sibling
// nodes of thisDOMNode.
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
ApplyToSiblings(aEventsToFire, 0, index, thisEvent->mEventType,
thisEvent->mDOMNode, nsAccEvent::eDoNotEmit);
continue;
}
if (nsCoreUtils::IsAncestorOf(thisEvent->mDOMNode,
tailEvent->mDOMNode)) {
// tailDOMNode is a descendant of thisDOMNode
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
CoalesceReorderEventsFromSameTree(thisEvent, tailEvent);
continue;
}
// Do not emit tailEvent, also apply this result to sibling
// nodes of tailDOMNode.
tailEvent->mEventRule = nsAccEvent::eDoNotEmit;
ApplyToSiblings(aEventsToFire, 0, tail, tailEvent->mEventType,
tailEvent->mDOMNode, nsAccEvent::eDoNotEmit);
break;
}
} // for (index)
if (tailEvent->mEventRule != nsAccEvent::eDoNotEmit) {
// Not in another event node's subtree, and no other event is in
// this event node's subtree.
// This event should be emitted
// Apply this result to sibling nodes of tailDOMNode
ApplyToSiblings(aEventsToFire, 0, tail, tailEvent->mEventType,
tailEvent->mDOMNode, nsAccEvent::eAllowDupes);
}
} break; // case eCoalesceFromSameSubtree
case nsAccEvent::eRemoveDupes:
{
// Check for repeat events.
for (PRInt32 index = 0; index < tail; index ++) {
nsAccEvent* accEvent = aEventsToFire[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule &&
accEvent->mDOMNode == tailEvent->mDOMNode) {
accEvent->mEventRule = nsAccEvent::eDoNotEmit;
}
}
} break; // case eRemoveDupes
default:
break; // case eAllowDupes, eDoNotEmit
} // switch
}
/* static */
void
nsAccEvent::ApplyToSiblings(nsTArray<nsRefPtr<nsAccEvent> > &aEventsToFire,
PRUint32 aStart, PRUint32 aEnd,
PRUint32 aEventType, nsIDOMNode* aDOMNode,
EEventRule aEventRule)
{
for (PRUint32 index = aStart; index < aEnd; index ++) {
nsAccEvent* accEvent = aEventsToFire[index];
if (accEvent->mEventType == aEventType &&
accEvent->mEventRule != nsAccEvent::eDoNotEmit &&
nsCoreUtils::AreSiblings(accEvent->mDOMNode, aDOMNode)) {
accEvent->mEventRule = aEventRule;
}
}
}
/* static */
void
nsAccEvent::CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1,
nsAccEvent *aAccEvent2)
{
// Do not emit event2 if event1 is unconditional.
nsCOMPtr<nsAccReorderEvent> reorderEvent1 = do_QueryInterface(aAccEvent1);
if (reorderEvent1->IsUnconditionalEvent()) {
aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit;
return;
}
// Do not emit event1 if event2 is unconditional.
nsCOMPtr<nsAccReorderEvent> reorderEvent2 = do_QueryInterface(aAccEvent2);
if (reorderEvent2->IsUnconditionalEvent()) {
aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit;
return;
}
// Do not emit event2 if event1 is valid, otherwise do not emit event1.
if (reorderEvent1->HasAccessibleInReasonSubtree())
aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit;
else
aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit;
}
void
nsAccEvent::CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent,
nsAccEvent *aDescendantAccEvent)
{
// Do not emit descendant event if this event is unconditional.
nsCOMPtr<nsAccReorderEvent> reorderEvent = do_QueryInterface(aAccEvent);
if (reorderEvent->IsUnconditionalEvent()) {
aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit;
return;
}
// Do not emit descendant event if this event is valid otherwise do not emit
// this event.
if (reorderEvent->HasAccessibleInReasonSubtree())
aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit;
else
aAccEvent->mEventRule = nsAccEvent::eDoNotEmit;
}
////////////////////////////////////////////////////////////////////////////////
// nsAccReorderEvent

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

@ -141,45 +141,7 @@ protected:
nsCOMPtr<nsIDOMNode> mDOMNode;
nsCOMPtr<nsIAccessibleDocument> mDocAccessible;
public:
/**
* Apply event rules to pending events, this method is called in
* FlushingPendingEvents().
* Result of this method:
* Event rule of filtered events will be set to eDoNotEmit.
* Events with other event rule are good to emit.
*/
static void ApplyEventRules(nsTArray<nsRefPtr<nsAccEvent> > &aEventsToFire);
private:
/**
* 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)
*/
static void ApplyToSiblings(nsTArray<nsRefPtr<nsAccEvent> > &aEventsToFire,
PRUint32 aStart, PRUint32 aEnd,
PRUint32 aEventType, nsIDOMNode* aDOMNode,
EEventRule aEventRule);
/**
* Do not emit one of two given reorder events fired for the same DOM node.
*/
static void CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1,
nsAccEvent *aAccEvent2);
/**
* Do not emit one of two given reorder events fired for DOM nodes in the case
* when one DOM node is in parent chain of second one.
*/
static void CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent,
nsAccEvent *aDescendantAccEvent);
friend class nsAccEventQueue;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsAccEvent, NS_ACCEVENT_IMPL_CID)

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

@ -100,11 +100,11 @@ NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x22)
PR_END_MACRO
#define NS_ACCESSNODE_IMPL_CID \
{ /* 13555f6e-0c0f-4002-84f6-558d47b8208e */ \
0x13555f6e, \
0xc0f, \
0x4002, \
{ 0x84, 0xf6, 0x55, 0x8d, 0x47, 0xb8, 0x20, 0x8e } \
{ /* 2b07e3d7-00b3-4379-aa0b-ea22e2c8ffda */ \
0x2b07e3d7, \
0x00b3, \
0x4379, \
{ 0xaa, 0x0b, 0xea, 0x22, 0xe2, 0xc8, 0xff, 0xda } \
}
class nsAccessNode: public nsIAccessNode
@ -168,9 +168,20 @@ class nsAccessNode: public nsIAccessNode
*/
virtual nsIFrame* GetFrame();
/**
* Return the corresponding press shell for this accessible.
*/
already_AddRefed<nsIPresShell> GetPresShell();
/**
* Return true if the accessible still has presentation shell. Light-weight
* version of IsDefunct() method.
*/
PRBool HasWeakShell() const { return !!mWeakShell; }
protected:
nsresult MakeAccessNode(nsIDOMNode *aNode, nsIAccessNode **aAccessNode);
already_AddRefed<nsIPresShell> GetPresShell();
nsPresContext* GetPresContext();
already_AddRefed<nsIAccessibleDocument> GetDocAccessible();
void LastRelease();

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

@ -85,8 +85,7 @@ nsIAtom *nsDocAccessible::gLastFocusedFrameType = nsnull;
nsDocAccessible::nsDocAccessible(nsIDOMNode *aDOMNode, nsIWeakReference* aShell):
nsHyperTextAccessibleWrap(aDOMNode, aShell), mWnd(nsnull),
mScrollPositionChangedTicks(0), mIsContentLoaded(PR_FALSE),
mIsLoadCompleteFired(PR_FALSE), mInFlushPendingEvents(PR_FALSE),
mFireEventTimerStarted(PR_FALSE)
mIsLoadCompleteFired(PR_FALSE)
{
// XXX aaronl should we use an algorithm for the initial cache size?
mAccessNodeCache.Init(kDefaultCacheSize);
@ -151,17 +150,14 @@ ElementTraverser(const void *aKey, nsIAccessNode *aAccessNode,
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocAccessible)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
PRUint32 i, length = tmp->mEventsToFire.Length();
for (i = 0; i < length; ++i) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEventsToFire[i]");
cb.NoteXPCOMChild(tmp->mEventsToFire[i].get());
}
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEventQueue");
cb.NoteXPCOMChild(tmp->mEventQueue.get());
tmp->mAccessNodeCache.EnumerateRead(ElementTraverser, &cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mEventsToFire)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventQueue)
tmp->ClearCache(tmp->mAccessNodeCache);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -609,6 +605,9 @@ nsDocAccessible::Init()
nsresult rv = nsHyperTextAccessibleWrap::Init();
NS_ENSURE_SUCCESS(rv, rv);
// Initialize event queue.
mEventQueue = new nsAccEventQueue(this);
// Fire reorder event to notify new accessible document has been created and
// attached to the tree.
nsCOMPtr<nsIAccessibleEvent> reorderEvent =
@ -626,6 +625,9 @@ nsDocAccessible::Shutdown()
return NS_OK; // Already shutdown
}
mEventQueue->Shutdown();
mEventQueue = nsnull;
nsCOMPtr<nsIDocShellTreeItem> treeItem =
nsCoreUtils::GetDocShellTreeItemFor(mDOMNode);
ShutdownChildDocuments(treeItem);
@ -641,21 +643,6 @@ nsDocAccessible::Shutdown()
nsHyperTextAccessibleWrap::Shutdown();
if (mFireEventTimer) {
// Doc being shut down before delayed events were processed.
mFireEventTimer->Cancel();
mFireEventTimer = nsnull;
mEventsToFire.Clear();
if (mFireEventTimerStarted && !mInFlushPendingEvents) {
// Make sure we release the kung fu death grip which is always there when
// fire event timer was started but FlushPendingEvents() callback wasn't
// triggered yet. If FlushPendingEvents() is in call stack, kung fu death
// grip will be released there.
NS_RELEASE_THIS();
}
}
// Remove from the cache after other parts of Shutdown(), so that Shutdown() procedures
// can find the doc or root accessible in the cache if they need it.
// We don't do this during ShutdownAccessibility() because that is already clearing the cache
@ -1615,257 +1602,166 @@ nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent)
NS_ENSURE_ARG(aEvent);
nsRefPtr<nsAccEvent> accEvent = nsAccUtils::QueryObject<nsAccEvent>(aEvent);
mEventsToFire.AppendElement(accEvent);
if (mEventQueue)
mEventQueue->Push(accEvent);
// Filter events.
nsAccEvent::ApplyEventRules(mEventsToFire);
// Process events.
return PreparePendingEventsFlush();
}
nsresult
nsDocAccessible::PreparePendingEventsFlush()
{
nsresult rv = NS_OK;
// Create timer if we don't have it yet.
if (!mFireEventTimer) {
mFireEventTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
// If there are delayed events in the queue and event timer wasn't started
// then initialize the timer so that delayed event will be processed in
// FlushPendingEvents.
if (mEventsToFire.Length() > 0 && !mFireEventTimerStarted) {
rv = mFireEventTimer->InitWithFuncCallback(FlushEventsCallback,
this, 0,
nsITimer::TYPE_ONE_SHOT);
if (NS_SUCCEEDED(rv)) {
// Kung fu death grip to prevent crash in callback.
NS_ADDREF_THIS();
mFireEventTimerStarted = PR_TRUE;
}
}
return rv;
return NS_OK;
}
void
nsDocAccessible::FlushPendingEvents()
{
mInFlushPendingEvents = PR_TRUE;
nsDocAccessible::ProcessPendingEvent(nsAccEvent *aEvent)
{
nsCOMPtr<nsIAccessible> accessible;
aEvent->GetAccessible(getter_AddRefs(accessible));
PRUint32 length = mEventsToFire.Length();
NS_ASSERTION(length, "How did we get here without events to fire?");
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
if (!presShell)
length = 0; // The doc is now shut down, don't fire events in it anymore
else {
// Flush layout so that all the frame construction, reflow, and styles are
// up-to-date. This will ensure we can get frames for the related nodes, as
// well as get the most current information for calculating things like
// visibility. We don't flush the display because we don't care about
// painting. If no flush is necessary the method will simple return.
presShell->FlushPendingNotifications(Flush_Layout);
nsCOMPtr<nsIDOMNode> domNode;
aEvent->GetDOMNode(getter_AddRefs(domNode));
PRUint32 eventType = aEvent->GetEventType();
EIsFromUserInput isFromUserInput =
aEvent->IsFromUserInput() ? eFromUserInput : eNoUserInput;
PRBool isAsync = aEvent->IsAsync();
if (domNode == gLastFocusedNode && isAsync &&
(eventType == nsIAccessibleEvent::EVENT_SHOW ||
eventType == nsIAccessibleEvent::EVENT_HIDE)) {
// If frame type didn't change for this event, then we don't actually need to invalidate
// However, we only keep track of the old frame type for the focus, where it's very
// important not to destroy and recreate the accessible for minor style changes,
// such as a:focus { overflow: scroll; }
nsCOMPtr<nsIContent> focusContent(do_QueryInterface(domNode));
if (focusContent) {
nsIFrame *focusFrame = focusContent->GetPrimaryFrame();
nsIAtom *newFrameType =
(focusFrame && focusFrame->GetStyleVisibility()->IsVisible()) ?
focusFrame->GetType() : nsnull;
if (newFrameType == gLastFocusedFrameType) {
// Don't need to invalidate this current accessible, but can
// just invalidate the children instead
FireShowHideEvents(domNode, PR_TRUE, eventType, eNormalEvent,
isAsync, isFromUserInput);
return;
}
gLastFocusedFrameType = newFrameType;
}
}
// Process only currently queued events. In the meantime, newly appended
// events will not be processed.
for (PRUint32 index = 0; index < length; index ++) {
// No presshell means the document was shut down duiring event handling
// by AT.
if (!mWeakShell)
break;
if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
nsAccEvent *accEvent = mEventsToFire[index];
nsCOMPtr<nsIAccessible> containerAccessible;
if (accessible)
accessible->GetParent(getter_AddRefs(containerAccessible));
if (accEvent->GetEventRule() == nsAccEvent::eDoNotEmit)
continue;
if (!containerAccessible) {
GetAccessibleInParentChain(domNode, PR_TRUE,
getter_AddRefs(containerAccessible));
if (!containerAccessible)
containerAccessible = this;
}
nsCOMPtr<nsIAccessible> accessible;
accEvent->GetAccessible(getter_AddRefs(accessible));
if (isAsync) {
// For asynch show, delayed invalidatation of parent's children
nsRefPtr<nsAccessible> containerAcc =
nsAccUtils::QueryAccessible(containerAccessible);
if (containerAcc)
containerAcc->InvalidateChildren();
nsCOMPtr<nsIDOMNode> domNode;
accEvent->GetDOMNode(getter_AddRefs(domNode));
// Some show events in the subtree may have been removed to
// avoid firing redundant events. But, we still need to make sure any
// accessibles parenting those shown nodes lose their child references.
InvalidateChildrenInSubtree(domNode);
}
PRUint32 eventType = accEvent->GetEventType();
EIsFromUserInput isFromUserInput =
accEvent->IsFromUserInput() ? eFromUserInput : eNoUserInput;
PRBool isAsync = accEvent->IsAsync();
if (domNode == gLastFocusedNode && isAsync &&
(eventType == nsIAccessibleEvent::EVENT_SHOW ||
eventType == nsIAccessibleEvent::EVENT_HIDE)) {
// If frame type didn't change for this event, then we don't actually need to invalidate
// However, we only keep track of the old frame type for the focus, where it's very
// important not to destroy and recreate the accessible for minor style changes,
// such as a:focus { overflow: scroll; }
nsCOMPtr<nsIContent> focusContent(do_QueryInterface(domNode));
if (focusContent) {
nsIFrame *focusFrame = focusContent->GetPrimaryFrame();
nsIAtom *newFrameType =
(focusFrame && focusFrame->GetStyleVisibility()->IsVisible()) ?
focusFrame->GetType() : nsnull;
if (newFrameType == gLastFocusedFrameType) {
// Don't need to invalidate this current accessible, but can
// just invalidate the children instead
FireShowHideEvents(domNode, PR_TRUE, eventType, eNormalEvent,
isAsync, isFromUserInput);
continue;
}
gLastFocusedFrameType = newFrameType;
// Also fire text changes if the node being created could affect the text in an nsIAccessibleText parent.
// When a node is being made visible or is inserted, the text in an ancestor hyper text will gain characters
// At this point we now have the frame and accessible for this node if there is one. That is why we
// wait to fire this here, instead of in InvalidateCacheSubtree(), where we wouldn't be able to calculate
// the offset, length and text for the text change.
if (domNode && domNode != mDOMNode) {
nsRefPtr<nsAccEvent> textChangeEvent =
CreateTextChangeEventForNode(containerAccessible, domNode, accessible,
PR_TRUE, PR_TRUE, isFromUserInput);
if (textChangeEvent) {
// XXX Queue them up and merge the text change events
// XXX We need a way to ignore SplitNode and JoinNode() when they
// do not affect the text within the hypertext
nsEventShell::FireEvent(textChangeEvent);
}
}
if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
// Fire show/create events for this node or first accessible descendants of it
FireShowHideEvents(domNode, PR_FALSE, eventType, eNormalEvent, isAsync,
isFromUserInput);
return;
}
nsCOMPtr<nsIAccessible> containerAccessible;
if (accessible)
accessible->GetParent(getter_AddRefs(containerAccessible));
if (accessible) {
if (eventType == nsIAccessibleEvent::EVENT_INTERNAL_LOAD) {
nsRefPtr<nsDocAccessible> docAcc =
nsAccUtils::QueryAccessibleDocument(accessible);
NS_ASSERTION(docAcc, "No doc accessible for doc load event");
if (!containerAccessible) {
GetAccessibleInParentChain(domNode, PR_TRUE,
getter_AddRefs(containerAccessible));
if (!containerAccessible)
containerAccessible = this;
}
if (isAsync) {
// For asynch show, delayed invalidatation of parent's children
nsRefPtr<nsAccessible> containerAcc =
nsAccUtils::QueryAccessible(containerAccessible);
if (containerAcc)
containerAcc->InvalidateChildren();
// Some show events in the subtree may have been removed to
// avoid firing redundant events. But, we still need to make sure any
// accessibles parenting those shown nodes lose their child references.
InvalidateChildrenInSubtree(domNode);
}
// Also fire text changes if the node being created could affect the text in an nsIAccessibleText parent.
// When a node is being made visible or is inserted, the text in an ancestor hyper text will gain characters
// At this point we now have the frame and accessible for this node if there is one. That is why we
// wait to fire this here, instead of in InvalidateCacheSubtree(), where we wouldn't be able to calculate
// the offset, length and text for the text change.
if (domNode && domNode != mDOMNode) {
nsRefPtr<nsAccEvent> textChangeEvent =
CreateTextChangeEventForNode(containerAccessible, domNode, accessible,
PR_TRUE, PR_TRUE, isFromUserInput);
if (textChangeEvent) {
// XXX Queue them up and merge the text change events
// XXX We need a way to ignore SplitNode and JoinNode() when they
// do not affect the text within the hypertext
nsEventShell::FireEvent(textChangeEvent);
}
}
// Fire show/create events for this node or first accessible descendants of it
FireShowHideEvents(domNode, PR_FALSE, eventType, eNormalEvent, isAsync,
isFromUserInput);
continue;
if (docAcc)
docAcc->FireDocLoadEvents(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
}
if (accessible) {
if (eventType == nsIAccessibleEvent::EVENT_INTERNAL_LOAD) {
nsRefPtr<nsDocAccessible> docAcc =
nsAccUtils::QueryAccessibleDocument(accessible);
NS_ASSERTION(docAcc, "No doc accessible for doc load event");
if (docAcc)
docAcc->FireDocLoadEvents(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
}
else if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
nsCOMPtr<nsIAccessibleText> accessibleText = do_QueryInterface(accessible);
PRInt32 caretOffset;
if (accessibleText && NS_SUCCEEDED(accessibleText->GetCaretOffset(&caretOffset))) {
else if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
nsCOMPtr<nsIAccessibleText> accessibleText = do_QueryInterface(accessible);
PRInt32 caretOffset;
if (accessibleText && NS_SUCCEEDED(accessibleText->GetCaretOffset(&caretOffset))) {
#ifdef DEBUG_A11Y
PRUnichar chAtOffset;
accessibleText->GetCharacterAtOffset(caretOffset, &chAtOffset);
printf("\nCaret moved to %d with char %c", caretOffset, chAtOffset);
PRUnichar chAtOffset;
accessibleText->GetCharacterAtOffset(caretOffset, &chAtOffset);
printf("\nCaret moved to %d with char %c", caretOffset, chAtOffset);
#endif
#ifdef DEBUG_CARET
// Test caret line # -- fire an EVENT_ALERT on the focused node so we can watch the
// line-number object attribute on it
nsCOMPtr<nsIAccessible> accForFocus;
GetAccService()->GetAccessibleFor(gLastFocusedNode, getter_AddRefs(accForFocus));
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accForFocus);
// Test caret line # -- fire an EVENT_ALERT on the focused node so we can watch the
// line-number object attribute on it
nsCOMPtr<nsIAccessible> accForFocus;
GetAccService()->GetAccessibleFor(gLastFocusedNode, getter_AddRefs(accForFocus));
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accForFocus);
#endif
nsRefPtr<nsAccEvent> caretMoveEvent =
new nsAccCaretMoveEvent(accessible, caretOffset);
if (!caretMoveEvent)
break; // Out of memory, break out to release kung fu death grip
nsRefPtr<nsAccEvent> caretMoveEvent =
new nsAccCaretMoveEvent(accessible, caretOffset);
if (!caretMoveEvent)
return;
nsEventShell::FireEvent(caretMoveEvent);
nsEventShell::FireEvent(caretMoveEvent);
PRInt32 selectionCount;
accessibleText->GetSelectionCount(&selectionCount);
if (selectionCount) { // There's a selection so fire selection change as well
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
accessible, PR_TRUE);
}
}
}
else if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
// Fire reorder event if it's unconditional (see InvalidateCacheSubtree
// method) or if changed node (that is the reason of this reorder event)
// is accessible or has accessible children.
nsCOMPtr<nsAccReorderEvent> reorderEvent = do_QueryInterface(accEvent);
if (reorderEvent->IsUnconditionalEvent() ||
reorderEvent->HasAccessibleInReasonSubtree()) {
nsEventShell::FireEvent(accEvent);
PRInt32 selectionCount;
accessibleText->GetSelectionCount(&selectionCount);
if (selectionCount) { // There's a selection so fire selection change as well
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
accessible, PR_TRUE);
}
}
}
else if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
// Fire reorder event if it's unconditional (see InvalidateCacheSubtree
// method) or if changed node (that is the reason of this reorder event)
// is accessible or has accessible children.
nsCOMPtr<nsAccReorderEvent> reorderEvent = do_QueryInterface(aEvent);
if (reorderEvent->IsUnconditionalEvent() ||
reorderEvent->HasAccessibleInReasonSubtree()) {
nsEventShell::FireEvent(aEvent);
}
else {
nsEventShell::FireEvent(accEvent);
}
else {
nsEventShell::FireEvent(aEvent);
// Post event processing
if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
// Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in
// this subtree.
nsCOMPtr<nsIDOMNode> hidingNode;
accEvent->GetDOMNode(getter_AddRefs(hidingNode));
if (hidingNode) {
RefreshNodes(hidingNode); // Will this bite us with asynch events
}
// Post event processing
if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
// Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in
// this subtree.
nsCOMPtr<nsIDOMNode> hidingNode;
aEvent->GetDOMNode(getter_AddRefs(hidingNode));
if (hidingNode) {
RefreshNodes(hidingNode); // Will this bite us with asynch events
}
}
}
}
// Mark we are ready to start event processing timer again.
mFireEventTimerStarted = PR_FALSE;
// If the document accessible is alive then remove processed events from the
// queue (otherwise they were removed on shutdown already) and reinitialize
// queue processing callback if necessary (new events might occur duiring
// delayed event processing).
if (mWeakShell) {
mEventsToFire.RemoveElementsAt(0, length);
PreparePendingEventsFlush();
}
mInFlushPendingEvents = PR_FALSE;
NS_RELEASE_THIS(); // Release kung fu death grip.
}
void nsDocAccessible::FlushEventsCallback(nsITimer *aTimer, void *aClosure)
{
nsDocAccessible *accessibleDoc = static_cast<nsDocAccessible*>(aClosure);
NS_ASSERTION(accessibleDoc, "How did we get here without an accessible document?");
if (accessibleDoc) {
// A lot of crashes were happening here, so now we're reffing the doc
// now until the events are flushed
accessibleDoc->FlushPendingEvents();
}
}
void nsDocAccessible::InvalidateChildrenInSubtree(nsIDOMNode *aStartNode)

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

@ -56,11 +56,11 @@ class nsIScrollableView;
const PRUint32 kDefaultCacheSize = 256;
#define NS_DOCACCESSIBLE_IMPL_CID \
{ /* 11d54d4f-f135-4b1b-80e4-6425a64f703c */ \
0x11d54d4f, \
0xf135, \
0x4b1b, \
{ 0x80, 0xe4, 0x64, 0x25, 0xa6, 0x4f, 0x70, 0x3c } \
{ /* 5559d4f2-4338-40eb-bfca-0fb7d73e958a */ \
0x5559d4f2, \
0x4338, \
0x40eb, \
{ 0xbf, 0xca, 0x0f, 0xb7, 0xd7, 0x3e, 0x95, 0x8a } \
}
class nsDocAccessible : public nsHyperTextAccessibleWrap,
@ -172,9 +172,10 @@ public:
virtual void FireDocLoadEvents(PRUint32 aEventType);
/**
* Used to flush pending events, called after timeout. See FlushPendingEvents.
* Process the event when the queue of pending events is untwisted. Fire
* accessible events as result of the processing.
*/
static void FlushEventsCallback(nsITimer *aTimer, void *aClosure);
void ProcessPendingEvent(nsAccEvent* aEvent);
protected:
/**
@ -214,16 +215,6 @@ protected:
*/
void ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute);
/**
* Process delayed (pending) events resulted in normal events firing.
*/
void FlushPendingEvents();
/**
* Start the timer to flush delayed (pending) events.
*/
nsresult PreparePendingEventsFlush();
/**
* Fire text changed event for character data changed. The method is used
* from nsIMutationObserver methods.
@ -293,16 +284,13 @@ protected:
void *mWnd;
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsITimer> mScrollWatchTimer;
nsCOMPtr<nsITimer> mFireEventTimer;
PRUint16 mScrollPositionChangedTicks; // Used for tracking scroll events
PRPackedBool mIsContentLoaded;
PRPackedBool mIsLoadCompleteFired;
protected:
PRBool mInFlushPendingEvents;
PRBool mFireEventTimerStarted;
nsTArray<nsRefPtr<nsAccEvent> > mEventsToFire;
nsRefPtr<nsAccEventQueue> mEventQueue;
static PRUint32 gLastFocusedAccessiblesState;
static nsIAtom *gLastFocusedFrameType;

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

@ -38,7 +38,7 @@
#include "nsEventShell.h"
#include "nsAccessible.h"
#include "nsDocAccessible.h"
////////////////////////////////////////////////////////////////////////////////
// nsEventShell
@ -95,3 +95,279 @@ nsEventShell::GetEventAttributes(nsIDOMNode *aNode,
PRBool nsEventShell::sEventFromUserInput = PR_FALSE;
nsCOMPtr<nsIDOMNode> nsEventShell::sEventTargetNode;
////////////////////////////////////////////////////////////////////////////////
// nsAccEventQueue
////////////////////////////////////////////////////////////////////////////////
nsAccEventQueue::nsAccEventQueue(nsDocAccessible *aDocument):
mProcessingStarted(PR_FALSE), mDocument(aDocument)
{
}
nsAccEventQueue::~nsAccEventQueue()
{
NS_ASSERTION(mDocument, "Queue wasn't shut down!");
}
////////////////////////////////////////////////////////////////////////////////
// nsAccEventQueue: nsISupports and cycle collection
NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccEventQueue)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccEventQueue)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAccEventQueue)
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocument");
cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mDocument.get()));
PRUint32 i, length = tmp->mEvents.Length();
for (i = 0; i < length; ++i) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvents[i]");
cb.NoteXPCOMChild(tmp->mEvents[i].get());
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAccEventQueue)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mEvents)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccEventQueue)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccEventQueue)
////////////////////////////////////////////////////////////////////////////////
// nsAccEventQueue: public
void
nsAccEventQueue::Push(nsAccEvent *aEvent)
{
mEvents.AppendElement(aEvent);
// Filter events.
CoalesceEvents();
// Process events.
PrepareFlush();
}
void
nsAccEventQueue::Shutdown()
{
mDocument = nsnull;
mEvents.Clear();
}
////////////////////////////////////////////////////////////////////////////////
// nsAccEventQueue: private
void
nsAccEventQueue::PrepareFlush()
{
// If there are pending events in the queue and events flush isn't planed
// yet start events flush asyncroniously.
if (mEvents.Length() > 0 && !mProcessingStarted) {
NS_DISPATCH_RUNNABLEMETHOD(Flush, this)
mProcessingStarted = PR_TRUE;
}
}
void
nsAccEventQueue::Flush()
{
// If the document accessible is now shut down, don't fire events in it
// anymore.
if (!mDocument)
return;
nsCOMPtr<nsIPresShell> presShell = mDocument->GetPresShell();
if (!presShell)
return;
// Flush layout so that all the frame construction, reflow, and styles are
// up-to-date. This will ensure we can get frames for the related nodes, as
// well as get the most current information for calculating things like
// visibility. We don't flush the display because we don't care about
// painting. If no flush is necessary the method will simple return.
presShell->FlushPendingNotifications(Flush_Layout);
// Process only currently queued events. Newly appended events duiring events
// flusing won't be processed.
PRUint32 length = mEvents.Length();
NS_ASSERTION(length, "How did we get here without events to fire?");
for (PRUint32 index = 0; index < length; index ++) {
// No presshell means the document was shut down duiring event handling
// by AT.
if (!mDocument || !mDocument->HasWeakShell())
break;
nsAccEvent *accEvent = mEvents[index];
if (accEvent->mEventRule != nsAccEvent::eDoNotEmit)
mDocument->ProcessPendingEvent(accEvent);
}
// Mark we are ready to start event processing again.
mProcessingStarted = PR_FALSE;
// If the document accessible is alive then remove processed events from the
// queue (otherwise they were removed on shutdown already) and reinitialize
// queue processing callback if necessary (new events might occur duiring
// delayed event processing).
if (mDocument && mDocument->HasWeakShell()) {
mEvents.RemoveElementsAt(0, length);
PrepareFlush();
}
}
void
nsAccEventQueue::CoalesceEvents()
{
PRUint32 numQueuedEvents = mEvents.Length();
PRInt32 tail = numQueuedEvents - 1;
nsAccEvent* tailEvent = mEvents[tail];
switch(tailEvent->mEventRule) {
case nsAccEvent::eCoalesceFromSameSubtree:
{
for (PRInt32 index = 0; index < tail; index ++) {
nsAccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventType != tailEvent->mEventType)
continue; // Different type
if (thisEvent->mEventRule == nsAccEvent::eAllowDupes ||
thisEvent->mEventRule == nsAccEvent::eDoNotEmit)
continue; // Do not need to check
if (thisEvent->mDOMNode == tailEvent->mDOMNode) {
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
CoalesceReorderEventsFromSameSource(thisEvent, tailEvent);
continue;
}
// Dupe
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
continue;
}
if (nsCoreUtils::IsAncestorOf(tailEvent->mDOMNode,
thisEvent->mDOMNode)) {
// thisDOMNode is a descendant of tailDOMNode
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
CoalesceReorderEventsFromSameTree(tailEvent, thisEvent);
continue;
}
// Do not emit thisEvent, also apply this result to sibling
// nodes of thisDOMNode.
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
ApplyToSiblings(0, index, thisEvent->mEventType,
thisEvent->mDOMNode, nsAccEvent::eDoNotEmit);
continue;
}
if (nsCoreUtils::IsAncestorOf(thisEvent->mDOMNode,
tailEvent->mDOMNode)) {
// tailDOMNode is a descendant of thisDOMNode
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
CoalesceReorderEventsFromSameTree(thisEvent, tailEvent);
continue;
}
// Do not emit tailEvent, also apply this result to sibling
// nodes of tailDOMNode.
tailEvent->mEventRule = nsAccEvent::eDoNotEmit;
ApplyToSiblings(0, tail, tailEvent->mEventType,
tailEvent->mDOMNode, nsAccEvent::eDoNotEmit);
break;
}
} // for (index)
if (tailEvent->mEventRule != nsAccEvent::eDoNotEmit) {
// Not in another event node's subtree, and no other event is in
// this event node's subtree.
// This event should be emitted
// Apply this result to sibling nodes of tailDOMNode
ApplyToSiblings(0, tail, tailEvent->mEventType,
tailEvent->mDOMNode, nsAccEvent::eAllowDupes);
}
} break; // case eCoalesceFromSameSubtree
case nsAccEvent::eRemoveDupes:
{
// Check for repeat events.
for (PRInt32 index = 0; index < tail; index ++) {
nsAccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule &&
accEvent->mDOMNode == tailEvent->mDOMNode) {
accEvent->mEventRule = nsAccEvent::eDoNotEmit;
}
}
} break; // case eRemoveDupes
default:
break; // case eAllowDupes, eDoNotEmit
} // switch
}
void
nsAccEventQueue::ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd,
PRUint32 aEventType, nsIDOMNode* aDOMNode,
nsAccEvent::EEventRule aEventRule)
{
for (PRUint32 index = aStart; index < aEnd; index ++) {
nsAccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == aEventType &&
accEvent->mEventRule != nsAccEvent::eDoNotEmit &&
nsCoreUtils::AreSiblings(accEvent->mDOMNode, aDOMNode)) {
accEvent->mEventRule = aEventRule;
}
}
}
void
nsAccEventQueue::CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1,
nsAccEvent *aAccEvent2)
{
// Do not emit event2 if event1 is unconditional.
nsCOMPtr<nsAccReorderEvent> reorderEvent1 = do_QueryInterface(aAccEvent1);
if (reorderEvent1->IsUnconditionalEvent()) {
aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit;
return;
}
// Do not emit event1 if event2 is unconditional.
nsCOMPtr<nsAccReorderEvent> reorderEvent2 = do_QueryInterface(aAccEvent2);
if (reorderEvent2->IsUnconditionalEvent()) {
aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit;
return;
}
// Do not emit event2 if event1 is valid, otherwise do not emit event1.
if (reorderEvent1->HasAccessibleInReasonSubtree())
aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit;
else
aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit;
}
void
nsAccEventQueue::CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent,
nsAccEvent *aDescendantAccEvent)
{
// Do not emit descendant event if this event is unconditional.
nsCOMPtr<nsAccReorderEvent> reorderEvent = do_QueryInterface(aAccEvent);
if (reorderEvent->IsUnconditionalEvent()) {
aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit;
return;
}
// Do not emit descendant event if this event is valid otherwise do not emit
// this event.
if (reorderEvent->HasAccessibleInReasonSubtree())
aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit;
else
aAccEvent->mEventRule = nsAccEvent::eDoNotEmit;
}

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

@ -39,8 +39,12 @@
#ifndef _nsEventShell_H_
#define _nsEventShell_H_
#include "nsCoreUtils.h"
#include "nsAccEvent.h"
/**
* Used for everything about events.
*/
class nsEventShell
{
public:
@ -77,4 +81,79 @@ private:
static PRBool sEventFromUserInput;
};
/**
* Event queue.
*/
class nsAccEventQueue : public nsISupports
{
public:
nsAccEventQueue(nsDocAccessible *aDocument);
~nsAccEventQueue();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(nsAccEventQueue)
/**
* Push event to queue, coalesce it if necessary. Start pending processing.
*/
void Push(nsAccEvent *aEvent);
/**
* Shutdown the queue.
*/
void Shutdown();
private:
/**
* Start pending events procesing asyncroniously.
*/
void PrepareFlush();
/**
* Process pending events. It calls nsDocAccessible::ProcessPendingEvent()
* where the real event processing is happen.
*/
void Flush();
NS_DECL_RUNNABLEMETHOD(nsAccEventQueue, Flush)
/**
* Coalesce redurant events from the queue.
*/
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)
*/
void ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd,
PRUint32 aEventType, nsIDOMNode* aDOMNode,
nsAccEvent::EEventRule aEventRule);
/**
* Do not emit one of two given reorder events fired for the same DOM node.
*/
void CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1,
nsAccEvent *aAccEvent2);
/**
* Do not emit one of two given reorder events fired for DOM nodes in the case
* when one DOM node is in parent chain of second one.
*/
void CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent,
nsAccEvent *aDescendantAccEvent);
PRBool mProcessingStarted;
nsRefPtr<nsDocAccessible> mDocument;
nsTArray<nsRefPtr<nsAccEvent> > mEvents;
};
#endif