mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Bug 477551 - nsDocAccessible::FlushPendingEvents isn't robust, r=marcoz, davidb, ginn, sr=neil
This commit is contained in:
parent
ec25eeae77
commit
8ee71c28f5
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 \
|
||||
|
78
accessible/tests/mochitest/test_events_flush.html
Normal file
78
accessible/tests/mochitest/test_events_flush.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user