Bug 477551 - nsDocAccessible::FlushPendingEvents isn't robust, r=marcoz, davidb, ginn, sr=neil

This commit is contained in:
Alexander Surkov 2009-09-08 00:46:56 +08:00
parent ec25eeae77
commit 8ee71c28f5
6 changed files with 179 additions and 48 deletions

View File

@ -308,9 +308,9 @@ nsAccEvent::GetAccessibleByNode()
/* static */
void
nsAccEvent::ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire)
nsAccEvent::ApplyEventRules(nsTArray<nsCOMPtr<nsIAccessibleEvent> > &aEventsToFire)
{
PRUint32 numQueuedEvents = aEventsToFire.Count();
PRUint32 numQueuedEvents = aEventsToFire.Length();
PRInt32 tail = numQueuedEvents - 1;
nsRefPtr<nsAccEvent> tailEvent = GetAccEventPtr(aEventsToFire[tail]);
@ -398,7 +398,7 @@ nsAccEvent::ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire)
/* static */
void
nsAccEvent::ApplyToSiblings(nsCOMArray<nsIAccessibleEvent> &aEventsToFire,
nsAccEvent::ApplyToSiblings(nsTArray<nsCOMPtr<nsIAccessibleEvent> > &aEventsToFire,
PRUint32 aStart, PRUint32 aEnd,
PRUint32 aEventType, nsIDOMNode* aDOMNode,
EEventRule aEventRule)

View File

@ -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<nsIAccessibleEvent> &aEventsToFire);
static void ApplyEventRules(nsTArray<nsCOMPtr<nsIAccessibleEvent> > &aEventsToFire);
private:
static already_AddRefed<nsAccEvent> 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<nsIAccessibleEvent> &aEventsToFire,
static void ApplyToSiblings(nsTArray<nsCOMPtr<nsIAccessibleEvent> > &aEventsToFire,
PRUint32 aStart, PRUint32 aEnd,
PRUint32 aEventType, nsIDOMNode* aDOMNode,
EEventRule aEventRule);

View File

@ -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<nsIPresShell> 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<nsIAccessibleEvent> accessibleEvent(
do_QueryInterface(mEventsToFire[index]));
// No presshell means the document was shut down duiring event handling
// by AT.
if (!mWeakShell)
break;
nsCOMPtr<nsIAccessibleEvent> 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)

View File

@ -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<nsIAccessibleEvent> mEventsToFire;
protected:
PRBool mIsAnchor;
PRBool mIsAnchorJumped;
PRBool mInFlushPendingEvents;
PRBool mInFlushPendingEvents;
PRBool mFireEventTimerStarted;
nsTArray<nsCOMPtr<nsIAccessibleEvent> > mEventsToFire;
static PRUint32 gLastFocusedAccessiblesState;
static nsIAtom *gLastFocusedFrameType;
};

View File

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

View File

@ -0,0 +1,78 @@
<html>
<head>
<title>Flush delayed events testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/events.js"></script>
<script type="application/javascript">
var gFocusHandler = {
handleEvent: function(aEvent) {
switch (this.count) {
case 0:
is(aEvent.DOMNode, getNode("input1"),
"Focus event for input1 was expected!");
getAccessible("input2").takeFocus();
break;
case 1:
is(aEvent.DOMNode, getNode("input2"),
"Focus event for input2 was expected!");
unregisterA11yEventListener(EVENT_FOCUS, gFocusHandler);
SimpleTest.finish();
break;
default:
ok(false, "Wrong focus event!");
}
this.count++;
},
count: 0
};
function doTests()
{
registerA11yEventListener(EVENT_FOCUS, gFocusHandler);
getAccessible("input1").takeFocus();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=477551"
title="nsDocAccessible::FlushPendingEvents isn't robust">
Mozilla Bug 477551
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<input id="input1">
<input id="input2">
</body>
</html>