diff --git a/accessible/src/base/nsAccessibleEventData.cpp b/accessible/src/base/nsAccessibleEventData.cpp index 04edaac31407..d03b1af9c053 100644 --- a/accessible/src/base/nsAccessibleEventData.cpp +++ b/accessible/src/base/nsAccessibleEventData.cpp @@ -308,9 +308,9 @@ nsAccEvent::GetAccessibleByNode() /* static */ void -nsAccEvent::ApplyEventRules(nsCOMArray &aEventsToFire) +nsAccEvent::ApplyEventRules(nsTArray > &aEventsToFire) { - PRUint32 numQueuedEvents = aEventsToFire.Count(); + PRUint32 numQueuedEvents = aEventsToFire.Length(); PRInt32 tail = numQueuedEvents - 1; nsRefPtr tailEvent = GetAccEventPtr(aEventsToFire[tail]); @@ -398,7 +398,7 @@ nsAccEvent::ApplyEventRules(nsCOMArray &aEventsToFire) /* static */ void -nsAccEvent::ApplyToSiblings(nsCOMArray &aEventsToFire, +nsAccEvent::ApplyToSiblings(nsTArray > &aEventsToFire, PRUint32 aStart, PRUint32 aEnd, PRUint32 aEventType, nsIDOMNode* aDOMNode, EEventRule aEventRule) diff --git a/accessible/src/base/nsAccessibleEventData.h b/accessible/src/base/nsAccessibleEventData.h index b33dd14f883b..a021856deda0 100644 --- a/accessible/src/base/nsAccessibleEventData.h +++ b/accessible/src/base/nsAccessibleEventData.h @@ -164,7 +164,7 @@ public: * Event rule of filtered events will be set to eDoNotEmit. * Events with other event rule are good to emit. */ - static void ApplyEventRules(nsCOMArray &aEventsToFire); + static void ApplyEventRules(nsTArray > &aEventsToFire); private: static already_AddRefed GetAccEventPtr(nsIAccessibleEvent *aAccEvent) { @@ -183,7 +183,7 @@ private: * @param aEventRule the event rule to be applied * (should be eDoNotEmit or eAllowDupes) */ - static void ApplyToSiblings(nsCOMArray &aEventsToFire, + static void ApplyToSiblings(nsTArray > &aEventsToFire, PRUint32 aStart, PRUint32 aEnd, PRUint32 aEventType, nsIDOMNode* aDOMNode, EEventRule aEventRule); diff --git a/accessible/src/base/nsDocAccessible.cpp b/accessible/src/base/nsDocAccessible.cpp index 085bb6f5a70e..0514c842383d 100644 --- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -86,7 +86,8 @@ 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) + mIsLoadCompleteFired(PR_FALSE), mInFlushPendingEvents(PR_FALSE), + mFireEventTimerStarted(PR_FALSE) { // XXX aaronl should we use an algorithm for the initial cache size? mAccessNodeCache.Init(kDefaultCacheSize); @@ -153,12 +154,17 @@ ElementTraverser(const void *aKey, nsIAccessNode *aAccessNode, NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mEventsToFire) - tmp->mAccessNodeCache.EnumerateRead(ElementTraverser, &cb); + 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()); + } + + 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_NSCOMARRAY(mEventsToFire) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mEventsToFire) tmp->ClearCache(tmp->mAccessNodeCache); NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -652,17 +658,17 @@ nsDocAccessible::Shutdown() nsHyperTextAccessibleWrap::Shutdown(); if (mFireEventTimer) { - // Doc being shut down before events fired, + // Doc being shut down before delayed events were processed. mFireEventTimer->Cancel(); mFireEventTimer = nsnull; - if (mEventsToFire.Count() > 0 ) { - mEventsToFire.Clear(); - // Make sure we release the kung fu death grip which is always - // there when there are still events left to be fired - // If FlushPendingEvents() is in call stack, - // kung fu death grip will be released there. - if (!mInFlushPendingEvents) - NS_RELEASE_THIS(); + 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(); } } @@ -1609,35 +1615,54 @@ nsDocAccessible::FireDelayedAccessibleEvent(PRUint32 aEventType, nsresult nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent) { - NS_ENSURE_TRUE(aEvent, NS_ERROR_FAILURE); + NS_ENSURE_ARG(aEvent); - if (!mFireEventTimer) { - // Do not yet have a timer going for firing another event. - mFireEventTimer = do_CreateInstance("@mozilla.org/timer;1"); - NS_ENSURE_TRUE(mFireEventTimer, NS_ERROR_OUT_OF_MEMORY); - } - - mEventsToFire.AppendObject(aEvent); + mEventsToFire.AppendElement(aEvent); // Filter events. nsAccEvent::ApplyEventRules(mEventsToFire); - if (mEventsToFire.Count() == 1) { - // This is be the first delayed event in queue, start timer - // so that event gets fired via FlushEventsCallback - NS_ADDREF_THIS(); // Kung fu death grip to prevent crash in callback - mFireEventTimer->InitWithFuncCallback(FlushEventsCallback, - this, 0, nsITimer::TYPE_ONE_SHOT); + // 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); } - return NS_OK; + // 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; } void nsDocAccessible::FlushPendingEvents() { mInFlushPendingEvents = PR_TRUE; - PRUint32 length = mEventsToFire.Count(); + + PRUint32 length = mEventsToFire.Length(); NS_ASSERTION(length, "How did we get here without events to fire?"); nsCOMPtr presShell = GetPresShell(); if (!presShell) @@ -1650,10 +1675,17 @@ nsDocAccessible::FlushPendingEvents() // painting. If no flush is necessary the method will simple return. presShell->FlushPendingNotifications(Flush_Layout); } - + + // Process only currently queued events. In the meantime, newly appended + // events will not be processed. for (PRUint32 index = 0; index < length; index ++) { - nsCOMPtr accessibleEvent( - do_QueryInterface(mEventsToFire[index])); + + // No presshell means the document was shut down duiring event handling + // by AT. + if (!mWeakShell) + break; + + nsCOMPtr accessibleEvent(mEventsToFire[index]); if (nsAccEvent::EventRule(accessibleEvent) == nsAccEvent::eDoNotEmit) continue; @@ -1807,12 +1839,24 @@ nsDocAccessible::FlushPendingEvents() } } } - mEventsToFire.Clear(); // Clear out array - mInFlushPendingEvents = PR_FALSE; - NS_RELEASE_THIS(); // Release kung fu death grip - // After a flood of events, reset so that user input flag is off + // 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(); + } + + // After a flood of events, reset so that user input flag is off. nsAccEvent::ResetLastInputState(); + + mInFlushPendingEvents = PR_FALSE; + NS_RELEASE_THIS(); // Release kung fu death grip. } void nsDocAccessible::FlushEventsCallback(nsITimer *aTimer, void *aClosure) diff --git a/accessible/src/base/nsDocAccessible.h b/accessible/src/base/nsDocAccessible.h index 77b6b6aef72f..765f7134c219 100644 --- a/accessible/src/base/nsDocAccessible.h +++ b/accessible/src/base/nsDocAccessible.h @@ -165,11 +165,6 @@ public: */ void RemoveAccessNodeFromCache(nsIAccessNode *aAccessNode); - /** - * Fires pending events. - */ - void FlushPendingEvents(); - /** * Fire document load events. * @@ -225,6 +220,16 @@ 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. @@ -278,12 +283,15 @@ protected: PRUint16 mScrollPositionChangedTicks; // Used for tracking scroll events PRPackedBool mIsContentLoaded; PRPackedBool mIsLoadCompleteFired; - nsCOMArray mEventsToFire; protected: PRBool mIsAnchor; PRBool mIsAnchorJumped; - PRBool mInFlushPendingEvents; + + PRBool mInFlushPendingEvents; + PRBool mFireEventTimerStarted; + nsTArray > mEventsToFire; + static PRUint32 gLastFocusedAccessiblesState; static nsIAtom *gLastFocusedFrameType; }; diff --git a/accessible/tests/mochitest/Makefile.in b/accessible/tests/mochitest/Makefile.in index d1bb2044ae69..d5589436ee3f 100644 --- a/accessible/tests/mochitest/Makefile.in +++ b/accessible/tests/mochitest/Makefile.in @@ -96,6 +96,7 @@ _TEST_FILES =\ test_events_caretmove.html \ test_events_doc.html \ test_events_draganddrop.html \ + test_events_flush.html \ test_events_focus.xul \ test_events_mutation.html \ test_events_mutation_coalesce.html \ diff --git a/accessible/tests/mochitest/test_events_flush.html b/accessible/tests/mochitest/test_events_flush.html new file mode 100644 index 000000000000..cc7218187ee5 --- /dev/null +++ b/accessible/tests/mochitest/test_events_flush.html @@ -0,0 +1,78 @@ + + + + Flush delayed events testing + + + + + + + + + + + + + + + + + Mozilla Bug 477551 + + +

+ +
+  
+ + + + +