Mozilla/mozilla/widget/timer/src/mac/nsTimerPeriodical.cpp
danm%netscape.com d66fbc869b guard against extra release of timer object when one-shot timers are cleared out during execution of a timer. bug 35074.
git-svn-id: svn://10.0.0.236/trunk@67551 18797224-902f-48f8-a5cc-f745e15eee43
2000-04-28 20:28:13 +00:00

337 lines
9.5 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
#include "nsTimerPeriodical.h"
#include "nsCOMPtr.h"
#include "prlog.h"
// this is also in the windows code. Prolly needs to be in nsITimer.h
#define NS_PRIORITY_IMMEDIATE NS_PRIORITY_HIGHEST
nsTimerPeriodical * nsTimerPeriodical::gPeriodical = nsnull;
//----------------------------------------------------------------------------------------
nsTimerPeriodical * nsTimerPeriodical::GetPeriodical()
//----------------------------------------------------------------------------------------
{
if (gPeriodical == NULL)
gPeriodical = new nsTimerPeriodical();
return gPeriodical;
}
#pragma mark -
//----------------------------------------------------------------------------------------
nsTimerPeriodical::nsTimerPeriodical()
//----------------------------------------------------------------------------------------
: mTimers(nsnull)
, mReadyTimers(nsnull)
, mFiringTimer(nsnull)
{
}
//----------------------------------------------------------------------------------------
nsTimerPeriodical::~nsTimerPeriodical()
//----------------------------------------------------------------------------------------
{
PR_ASSERT(!mTimers && !mReadyTimers);
}
#pragma mark -
//----------------------------------------------------------------------------------------
nsresult nsTimerPeriodical::AddTimer(nsTimerImpl * aTimer)
//----------------------------------------------------------------------------------------
{
NS_ADDREF(aTimer); // we hold a ref to the timer until it fires
AddTimerToList(aTimer, mTimers);
if (mTimers)
StartRepeating();
return NS_OK;
}
//----------------------------------------------------------------------------------------
nsresult nsTimerPeriodical::RemoveTimer(nsTimerImpl * aTimer)
//----------------------------------------------------------------------------------------
{
// if someone tries to cancel a timer that is firing now, we just ignore them.
if (aTimer == mFiringTimer) return NS_OK;
RemoveTimerFromList(aTimer, mTimers);
RemoveTimerFromList(aTimer, mReadyTimers);
if (aTimer && !aTimer->mTimerSpent) { // if we haven't fired the timer, release it
aTimer->mTimerSpent = PR_TRUE;
NS_RELEASE(aTimer);
}
return NS_OK;
}
// Called through every event loop
// Loops through the list of available timers, and
// fires off the appropriate ones
//----------------------------------------------------------------------------------------
void nsTimerPeriodical::RepeatAction(const EventRecord &inMacEvent)
//----------------------------------------------------------------------------------------
{
ProcessTimers(inMacEvent.when);
if (!mTimers)
StopRepeating();
// ProcessTimers can put timers in the ready queue, so start idling
// if we have any
if (mReadyTimers)
StartIdling();
}
//----------------------------------------------------------------------------------------
void nsTimerPeriodical::IdleAction(const EventRecord &inMacEvent)
//----------------------------------------------------------------------------------------
{
FireNextReadyTimer();
if (!mReadyTimers)
StopIdling();
}
#pragma mark -
//----------------------------------------------------------------------------------------
void nsTimerPeriodical::ProcessTimers(UInt32 currentTicks)
// This only fires one timer per call right now.
//----------------------------------------------------------------------------------------
{
PRBool done = PR_FALSE;
while (!done)
{
nsTimerImpl* curTimer = mTimers;
while (curTimer)
{
NS_ASSERTION(curTimer->IsGoodTimer(), "Bad timer!");
nsTimerImpl* nextTimer = curTimer->mNext;
if (curTimer->GetFireTime() <= currentTicks)
{
ProcessExpiredTimer(curTimer); // this changes the list
break;
}
curTimer = nextTimer;
}
done = (curTimer == nsnull); // we got to the end
}
}
//----------------------------------------------------------------------------------------
void nsTimerPeriodical::FireAndReprimeTimer(nsTimerImpl* aTimer)
//----------------------------------------------------------------------------------------
{
nsCOMPtr<nsITimer> kungFuDeathGrip(aTimer);
// if this timer is a precise timer, set the next fire time
// before we execute the callback
if (aTimer->GetType() == NS_TYPE_REPEATING_PRECISE)
aTimer->SetDelay(aTimer->GetDelay());
mFiringTimer = aTimer;
aTimer->Fire();
mFiringTimer = nsnull;
// if this is a slack timer, set the delay now
if (aTimer->GetType() == NS_TYPE_REPEATING_SLACK)
aTimer->SetDelay(aTimer->GetDelay());
// if this is a repeating timer, put it back in the list
if (aTimer->GetType() != NS_TYPE_ONE_SHOT)
{
AddTimerToList(aTimer, mTimers);
}
else
{
if (!aTimer->mTimerSpent)
{
aTimer->mTimerSpent = PR_TRUE;
NS_RELEASE(aTimer); // this timer is dead
}
}
}
//----------------------------------------------------------------------------------------
void nsTimerPeriodical::FireNextReadyTimer()
//----------------------------------------------------------------------------------------
{
if (mReadyTimers)
{
nsTimerImpl* aTimer = mReadyTimers;
RemoveTimerFromList(aTimer, mReadyTimers);
FireAndReprimeTimer(aTimer);
}
}
//----------------------------------------------------------------------------------------
void nsTimerPeriodical::AddToReadyQueue(nsTimerImpl* aTimer)
//----------------------------------------------------------------------------------------
{
PRUint32 priority = aTimer->GetPriority();
NS_ASSERTION(aTimer && priority < NS_PRIORITY_IMMEDIATE, "Should have non-immediate timer here");
// do an insertion sort by priority
if (mReadyTimers)
{
if (priority > mReadyTimers->GetPriority())
{
mReadyTimers->mPrev = aTimer;
aTimer->mNext = mReadyTimers;
mReadyTimers = aTimer;
}
else
{
nsTimerImpl *readyTimer = mReadyTimers;
nsTimerImpl *prevTimer;
// we know we will enter the while loop at least the first
// time, and thus prevt will be initialized
while (readyTimer && (readyTimer->GetPriority() >= priority))
{
NS_ASSERTION(readyTimer->IsGoodTimer(), "Bad timer!");
prevTimer = readyTimer;
readyTimer = readyTimer->mNext;
}
aTimer->mPrev = prevTimer;
aTimer->mNext = prevTimer->mNext;
prevTimer->mNext = aTimer;
if (aTimer->mNext) aTimer->mNext->mPrev = aTimer;
}
}
else
{
mReadyTimers = aTimer;
}
}
//----------------------------------------------------------------------------------------
void nsTimerPeriodical::ProcessExpiredTimer(nsTimerImpl* aTimer)
//----------------------------------------------------------------------------------------
{
RemoveTimerFromList(aTimer, mTimers);
if (aTimer->GetPriority() >= NS_PRIORITY_IMMEDIATE)
{
FireAndReprimeTimer(aTimer);
}
else
{
// add to the ready queue
AddToReadyQueue(aTimer);
}
}
#pragma mark -
//----------------------------------------------------------------------------------------
nsresult nsTimerPeriodical::AddTimerToList(nsTimerImpl* aTimer, nsTimerImpl*& timerList)
//----------------------------------------------------------------------------------------
{
// make sure it's not already there
RemoveTimerFromList(aTimer, timerList);
// keep list sorted by fire time
if (timerList)
{
if (aTimer->GetFireTime() < timerList->GetFireTime())
{
timerList->mPrev = aTimer;
aTimer->mNext = timerList;
timerList = aTimer;
}
else
{
nsTimerImpl *t = timerList;
nsTimerImpl *prevt;
// we know we will enter the while loop at least the first
// time, and thus prevt will be initialized
while (t && (t->GetFireTime() <= aTimer->GetFireTime()))
{
prevt = t;
t = t->mNext;
}
aTimer->mPrev = prevt;
aTimer->mNext = prevt->mNext;
prevt->mNext = aTimer;
if (aTimer->mNext) aTimer->mNext->mPrev = aTimer;
}
}
else
{
timerList = aTimer;
}
return NS_OK;
}
//----------------------------------------------------------------------------------------
nsresult nsTimerPeriodical::RemoveTimerFromList(nsTimerImpl* aTimer, nsTimerImpl*& timerList)
//----------------------------------------------------------------------------------------
{
nsTimerImpl* t = timerList;
nsTimerImpl* next_t = nsnull;
if (t) next_t = t->mNext;
while (t)
{
if (t == aTimer)
{
if (timerList == t) timerList = t->mNext;
if (t->mPrev) t->mPrev->mNext = t->mNext;
if (t->mNext) t->mNext->mPrev = t->mPrev;
t->mNext = nsnull;
t->mPrev = nsnull;
}
t = next_t;
if (t) next_t = t->mNext;
}
return NS_OK;
}