зеркало из https://github.com/mozilla/pjs.git
Bug 720493 - nsIdleService sometimes fails to restart its idle time detection timer. r=bbondy
This commit is contained in:
Родитель
00a3c3851b
Коммит
728c4964ae
|
@ -24,7 +24,7 @@
|
|||
* Contributor(s):
|
||||
* Gijs Kruitbosch <gijskruitbosch@gmail.com>
|
||||
* Edward Lee <edward.lee@engineering.uiuc.edu>
|
||||
* Mike Kristoffersen <moz@mikek.dk>
|
||||
* Mike Kristoffersen <mozstuff@mikek.dk>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -56,9 +56,11 @@ using namespace mozilla;
|
|||
#define OBSERVER_TOPIC_IDLE "idle"
|
||||
#define OBSERVER_TOPIC_BACK "back"
|
||||
#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
|
||||
// interval in seconds between internal idle time requests.
|
||||
#define MIN_IDLE_POLL_INTERVAL 5
|
||||
#define MAX_IDLE_POLL_INTERVAL 300 /* 5 min */
|
||||
// interval in milliseconds between internal idle time requests.
|
||||
#define MIN_IDLE_POLL_INTERVAL_MSEC (5 * PR_MSEC_PER_SEC) /* 5 sec */
|
||||
|
||||
// Time used by the daily idle serivce to determine a significant idle time.
|
||||
#define DAILY_SIGNIFICANT_IDLE_SERVICE_SEC 300 /* 5 min */
|
||||
// Pref for last time (seconds since epoch) daily notification was sent.
|
||||
#define PREF_LAST_DAILY "idle.lastDailyNotification"
|
||||
// Number of seconds in a day.
|
||||
|
@ -100,14 +102,17 @@ nsIdleServiceDaily::Observe(nsISupports *,
|
|||
}
|
||||
|
||||
// Stop observing idle for today.
|
||||
(void)mIdleService->RemoveIdleObserver(this, MAX_IDLE_POLL_INTERVAL);
|
||||
(void)mIdleService->RemoveIdleObserver(this,
|
||||
DAILY_SIGNIFICANT_IDLE_SERVICE_SEC);
|
||||
|
||||
// Set the last idle-daily time pref.
|
||||
PRInt32 nowSec = static_cast<PRInt32>(PR_Now() / PR_USEC_PER_SEC);
|
||||
Preferences::SetInt(PREF_LAST_DAILY, nowSec);
|
||||
|
||||
// Start timer for the next check in one day.
|
||||
(void)mTimer->InitWithFuncCallback(DailyCallback, this, SECONDS_PER_DAY * 1000,
|
||||
(void)mTimer->InitWithFuncCallback(DailyCallback,
|
||||
this,
|
||||
SECONDS_PER_DAY * PR_MSEC_PER_SEC,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
|
||||
return NS_OK;
|
||||
|
@ -139,7 +144,9 @@ nsIdleServiceDaily::Init()
|
|||
}
|
||||
else {
|
||||
// Start timer for the next check in one day.
|
||||
(void)mTimer->InitWithFuncCallback(DailyCallback, this, SECONDS_PER_DAY * 1000,
|
||||
(void)mTimer->InitWithFuncCallback(DailyCallback,
|
||||
this,
|
||||
SECONDS_PER_DAY * PR_MSEC_PER_SEC,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
|
@ -160,15 +167,88 @@ nsIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure)
|
|||
|
||||
// The one thing we do every day is to start waiting for the user to "have
|
||||
// a significant idle time".
|
||||
(void)me->mIdleService->AddIdleObserver(me, MAX_IDLE_POLL_INTERVAL);
|
||||
(void)me->mIdleService->AddIdleObserver(me,
|
||||
DAILY_SIGNIFICANT_IDLE_SERVICE_SEC);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The idle services goal is to notify subscribers when a certain time has
|
||||
* passed since the last user interaction with the system.
|
||||
*
|
||||
* On some platforms this is defined as the last time user events reached this
|
||||
* application, on other platforms it is a system wide thing - the preferred
|
||||
* implementation is to use the system idle time, rather than the application
|
||||
* idle time, as the things depending on the idle service are likely to use
|
||||
* significant resources (network, disk, memory, cpu, etc.).
|
||||
*
|
||||
* When the idle service needs to use the system wide idle timer, it typically
|
||||
* needs to poll the idle time value by the means of a timer. It needs to
|
||||
* poll fast when it is in active idle mode (when it has a listener in the idle
|
||||
* mode) as it needs to detect if the user is active in other applications.
|
||||
*
|
||||
* When the service is waiting for the first listener to become idle, or when
|
||||
* it is only monitoring application idle time, it only needs to have the timer
|
||||
* expire at the time the next listener goes idle.
|
||||
*
|
||||
* The core state of the service is determined by:
|
||||
*
|
||||
* - A list of listeners.
|
||||
*
|
||||
* - A boolean that tells if any listeners are in idle mode.
|
||||
*
|
||||
* - A delta value that indicates when, measured from the last non-idle time,
|
||||
* the next listener should switch to idle mode.
|
||||
*
|
||||
* - An absolute time of the last time idle mode was detected (this is used to
|
||||
* judge if we have been out of idle mode since the last invocation of the
|
||||
* service.
|
||||
*
|
||||
* There are four entry points into the system:
|
||||
*
|
||||
* - A new listener is registered.
|
||||
*
|
||||
* - An existing listener is deregistered.
|
||||
*
|
||||
* - User interaction is detected.
|
||||
*
|
||||
* - The timer expires.
|
||||
*
|
||||
* When a new listener is added its idle timeout, is compared with the next idle
|
||||
* timeout, and if lower, that time is stored as the new timeout, and the timer
|
||||
* is reconfigured to ensure a timeout around the time the new listener should
|
||||
* timeout.
|
||||
*
|
||||
* If the next idle time is above the idle time requested by the new listener
|
||||
* it won't be informed until the timer expires, this is to avoid recursive
|
||||
* behavior and to simplify the code. In this case the timer will be set to
|
||||
* about 10 ms.
|
||||
*
|
||||
* When an existing listener is deregistered, it is just removed from the list
|
||||
* of active listeners, we don't stop the timer, we just let it expire.
|
||||
*
|
||||
* When user interaction is detected, either because it was directly detected or
|
||||
* because we polled the system timer and found it to be unexpected low, then we
|
||||
* check the flag that tells us if any listeners are in idle mode, if there are
|
||||
* they are removed from idle mode and told so, and we reset our state
|
||||
* caculating the next timeout and restart the timer if needed.
|
||||
*
|
||||
* ---- Build in logic
|
||||
*
|
||||
* In order to avoid restarting the timer endlessly, the timer function has
|
||||
* logic that will only restart the timer, if the requested timeout is before
|
||||
* the current timeout.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// nsIdleService
|
||||
|
||||
nsIdleService::nsIdleService() : mLastIdleReset(0)
|
||||
, mLastHandledActivity(0)
|
||||
, mPolledIdleTimeIsValid(false)
|
||||
nsIdleService::nsIdleService() : mCurrentlySetToTimeoutAtInPR(0),
|
||||
mAnyObserverIdle(false),
|
||||
mDeltaToNextIdleSwitchInS(PR_UINT32_MAX),
|
||||
mLastUserInteractionInPR(0)
|
||||
{
|
||||
mDailyIdle = new nsIdleServiceDaily(this);
|
||||
mDailyIdle->Init();
|
||||
|
@ -176,17 +256,21 @@ nsIdleService::nsIdleService() : mLastIdleReset(0)
|
|||
|
||||
nsIdleService::~nsIdleService()
|
||||
{
|
||||
StopTimer();
|
||||
if(mTimer) {
|
||||
mTimer->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIdleService::AddIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime)
|
||||
nsIdleService::AddIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTimeInS)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aObserver);
|
||||
NS_ENSURE_ARG(aIdleTime);
|
||||
// We don't accept idle time at 0, and we can't handle idle time that are too
|
||||
// high either - no more than ~136 years.
|
||||
NS_ENSURE_ARG_RANGE(aIdleTimeInS, 1, (PR_UINT32_MAX / 10) - 1);
|
||||
|
||||
// Put the time + observer in a struct we can keep:
|
||||
IdleListener listener(aObserver, aIdleTime);
|
||||
IdleListener listener(aObserver, aIdleTimeInS);
|
||||
|
||||
if (!mArrayListeners.AppendElement(listener)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
@ -199,25 +283,33 @@ nsIdleService::AddIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Make sure our observer goes into 'idle' immediately if applicable.
|
||||
CheckAwayState(true);
|
||||
// Check if the newly added observer has a smaller wait time than what we
|
||||
// are wating for now.
|
||||
if (mDeltaToNextIdleSwitchInS > aIdleTimeInS) {
|
||||
// If it is, then this is the next to move to idle (at this point we
|
||||
// don't care if it should have switched already).
|
||||
mDeltaToNextIdleSwitchInS = aIdleTimeInS;
|
||||
}
|
||||
|
||||
// Ensure timer is running.
|
||||
ReconfigureTimer();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aTime)
|
||||
nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aTimeInS)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aObserver);
|
||||
NS_ENSURE_ARG(aTime);
|
||||
IdleListener listener(aObserver, aTime);
|
||||
|
||||
// Find the entry and remove it:
|
||||
NS_ENSURE_ARG_POINTER(aObserver);
|
||||
NS_ENSURE_ARG(aTimeInS);
|
||||
IdleListener listener(aObserver, aTimeInS);
|
||||
|
||||
// Find the entry and remove it, if it was the last entry, we just let the
|
||||
// existing timer run to completion (there might be a new registration in a
|
||||
// little while.
|
||||
IdleListenerComparator c;
|
||||
if (mArrayListeners.RemoveElement(listener, c)) {
|
||||
if (mArrayListeners.IsEmpty()) {
|
||||
StopTimer();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -226,13 +318,63 @@ nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aTime)
|
|||
}
|
||||
|
||||
void
|
||||
nsIdleService::ResetIdleTimeOut()
|
||||
nsIdleService::ResetIdleTimeOut(PRUint32 idleDeltaInMS)
|
||||
{
|
||||
mLastIdleReset = PR_IntervalToSeconds(PR_IntervalNow());
|
||||
if (!mLastIdleReset) mLastIdleReset = 1;
|
||||
// Store the time
|
||||
mLastUserInteractionInPR = PR_Now() - (idleDeltaInMS * PR_USEC_PER_MSEC);
|
||||
|
||||
// If no one is idle, then we are done, any existing timers can keep running.
|
||||
if (!mAnyObserverIdle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark all idle services as non-idle, and calculate the next idle timeout.
|
||||
nsCOMArray<nsIObserver> notifyList;
|
||||
mDeltaToNextIdleSwitchInS = PR_UINT32_MAX;
|
||||
|
||||
// Loop trough all listeners, and find any that have detected idle.
|
||||
for (PRUint32 i = 0; i < mArrayListeners.Length(); i++) {
|
||||
IdleListener& curListener = mArrayListeners.ElementAt(i);
|
||||
|
||||
// If the listener was idle, then he shouldn't be any longer.
|
||||
if (curListener.isIdle) {
|
||||
notifyList.AppendObject(curListener.observer);
|
||||
curListener.isIdle = false;
|
||||
}
|
||||
|
||||
// Check if the listener is the next one to timeout.
|
||||
mDeltaToNextIdleSwitchInS = PR_MIN(mDeltaToNextIdleSwitchInS,
|
||||
curListener.reqIdleTime);
|
||||
}
|
||||
|
||||
// When we are done, then we wont have anyone idle.
|
||||
mAnyObserverIdle = false;
|
||||
|
||||
// Restart the idle timer, and do so before anyone can delay us.
|
||||
ReconfigureTimer();
|
||||
|
||||
PRInt32 numberOfPendingNotifications = notifyList.Count();
|
||||
|
||||
// Bail if nothing to do.
|
||||
if (!numberOfPendingNotifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now send "back" events to all, if any should have timed out allready, then
|
||||
// they will be reawaken by the timer that is already running.
|
||||
|
||||
// We need a text string to send with any state change events.
|
||||
nsAutoString timeStr;
|
||||
|
||||
timeStr.AppendInt((PRInt32)(idleDeltaInMS / PR_MSEC_PER_SEC));
|
||||
|
||||
// Send the "non-idle" events.
|
||||
while (numberOfPendingNotifications--) {
|
||||
notifyList[numberOfPendingNotifications]->Observe(this,
|
||||
OBSERVER_TOPIC_BACK,
|
||||
timeStr.get());
|
||||
}
|
||||
|
||||
// Now check if this changes anything
|
||||
CheckAwayState(false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -243,36 +385,36 @@ nsIdleService::GetIdleTime(PRUint32* idleTime)
|
|||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
// Polled idle time in ms
|
||||
// Polled idle time in ms.
|
||||
PRUint32 polledIdleTimeMS;
|
||||
|
||||
mPolledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS);
|
||||
bool polledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS);
|
||||
|
||||
// If we don't have any valid data, then we are not in idle - pr. definition.
|
||||
if (!mPolledIdleTimeIsValid && 0 == mLastIdleReset) {
|
||||
if (!polledIdleTimeIsValid && 0 == mLastUserInteractionInPR) {
|
||||
*idleTime = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If we never got a reset, just return the pulled time.
|
||||
if (0 == mLastIdleReset) {
|
||||
if (0 == mLastUserInteractionInPR) {
|
||||
*idleTime = polledIdleTimeMS;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// timeSinceReset is in seconds.
|
||||
PRUint32 timeSinceReset =
|
||||
PR_IntervalToSeconds(PR_IntervalNow()) - mLastIdleReset;
|
||||
// timeSinceReset is in milliseconds.
|
||||
PRUint32 timeSinceResetInMS = (PR_Now() - mLastUserInteractionInPR) /
|
||||
PR_USEC_PER_MSEC;
|
||||
|
||||
// If we did't get pulled data, return the time since last idle reset.
|
||||
if (!mPolledIdleTimeIsValid) {
|
||||
if (!polledIdleTimeIsValid) {
|
||||
// We need to convert to ms before returning the time.
|
||||
*idleTime = timeSinceReset * 1000;
|
||||
*idleTime = timeSinceResetInMS;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Otherwise return the shortest time detected (in ms).
|
||||
*idleTime = NS_MIN(timeSinceReset * 1000, polledIdleTimeMS);
|
||||
*idleTime = NS_MIN(timeSinceResetInMS, polledIdleTimeMS);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -293,112 +435,54 @@ nsIdleService::UsePollMode()
|
|||
}
|
||||
|
||||
void
|
||||
nsIdleService::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
|
||||
nsIdleService::StaticIdleTimerCallback(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
static_cast<nsIdleService*>(aClosure)->CheckAwayState(false);
|
||||
static_cast<nsIdleService*>(aClosure)->IdleTimerCallback();
|
||||
}
|
||||
|
||||
void
|
||||
nsIdleService::CheckAwayState(bool aNewObserver)
|
||||
nsIdleService::IdleTimerCallback(void)
|
||||
{
|
||||
/**
|
||||
* Find our last detected idle time (it's important this happens before the
|
||||
* call below to GetIdleTime, as we use the two values to detect if there
|
||||
* has been user activity since the last time we were here).
|
||||
*/
|
||||
PRUint32 curTime = static_cast<PRUint32>(PR_Now() / PR_USEC_PER_SEC);
|
||||
PRUint32 lastTime = curTime - mLastHandledActivity;
|
||||
bool bootstrapTimer = mLastHandledActivity == 0;
|
||||
// Remember that we no longer have a timer running.
|
||||
mCurrentlySetToTimeoutAtInPR = 0;
|
||||
|
||||
/**
|
||||
* Too short since last check.
|
||||
* Bail out
|
||||
*/
|
||||
if (!lastTime && !aNewObserver) {
|
||||
// Get the current idle time.
|
||||
PRUint32 currentIdleTimeInMS;
|
||||
|
||||
if (NS_FAILED(GetIdleTime(¤tIdleTimeInMS))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the idle time (in seconds).
|
||||
PRUint32 idleTime;
|
||||
if (NS_FAILED(GetIdleTime(&idleTime))) {
|
||||
// Check if we have had some user interaction we didn't handle previously
|
||||
// we do the caluculation in ms to lessen the chance for rounding errors to
|
||||
// trigger wrong results, it is also very important that we call PR_Now AFTER
|
||||
// the call to GetIdleTime().
|
||||
if (((PR_Now() - mLastUserInteractionInPR) / PR_USEC_PER_MSEC) >
|
||||
currentIdleTimeInMS)
|
||||
{
|
||||
// We had user activity, so handle that part first (to ensure the listeners
|
||||
// don't risk getting an non-idle after they get a new idle indication.
|
||||
ResetIdleTimeOut(currentIdleTimeInMS);
|
||||
|
||||
// NOTE: We can't bail here, as we might have something already timed out.
|
||||
}
|
||||
|
||||
// Find the idle time in S.
|
||||
PRUint32 currentIdleTimeInS = currentIdleTimeInMS / PR_MSEC_PER_SEC;
|
||||
|
||||
// Restart timer and bail if no-one are expected to be in idle
|
||||
if (mDeltaToNextIdleSwitchInS > currentIdleTimeInS) {
|
||||
// If we didn't expect anyone to be idle, then just re-start the timer.
|
||||
ReconfigureTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have no valid data about the idle time, stop
|
||||
if (!mPolledIdleTimeIsValid && 0 == mLastIdleReset) {
|
||||
return;
|
||||
}
|
||||
// Tell expired listeners they are expired,and find the next timeout
|
||||
|
||||
// GetIdleTime returns the time in ms, internally we only calculate in s.
|
||||
idleTime /= 1000;
|
||||
|
||||
// Set the time for last user activity.
|
||||
mLastHandledActivity = curTime - idleTime;
|
||||
|
||||
/**
|
||||
* Now, if the idle time, is less than what we expect, it means the
|
||||
* user was active since last time that we checked.
|
||||
*/
|
||||
bool userActivity = lastTime > idleTime;
|
||||
|
||||
if (userActivity) {
|
||||
if (TryNotifyBackState(idleTime) || idleTime) {
|
||||
RescheduleIdleTimer(idleTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (!userActivity || aNewObserver || bootstrapTimer) {
|
||||
TryNotifyIdleState(idleTime);
|
||||
RescheduleIdleTimer(idleTime);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nsIdleService::TryNotifyBackState(PRUint32 aIdleTime)
|
||||
{
|
||||
nsCOMArray<nsIObserver> notifyList;
|
||||
|
||||
// Loop trough all listeners, and find any that have detected idle.
|
||||
for (PRUint32 i = 0; i < mArrayListeners.Length(); i++) {
|
||||
IdleListener& curListener = mArrayListeners.ElementAt(i);
|
||||
|
||||
if (curListener.isIdle) {
|
||||
notifyList.AppendObject(curListener.observer);
|
||||
curListener.isIdle = false;
|
||||
}
|
||||
}
|
||||
|
||||
PRInt32 numberOfPendingNotifications = notifyList.Count();
|
||||
|
||||
// Bail id nothing to do
|
||||
if(!numberOfPendingNotifications) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need a text string to send with any state change events.
|
||||
nsAutoString timeStr;
|
||||
timeStr.AppendInt(aIdleTime);
|
||||
|
||||
// Send the "non-idle" events.
|
||||
while(numberOfPendingNotifications--) {
|
||||
notifyList[numberOfPendingNotifications]->Observe(this,
|
||||
OBSERVER_TOPIC_BACK,
|
||||
timeStr.get());
|
||||
}
|
||||
|
||||
// We found something so return true
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
nsIdleService::TryNotifyIdleState(PRUint32 aIdleTime)
|
||||
{
|
||||
/**
|
||||
* Now we need to check for listeners that have expired, and while we are
|
||||
* looping through all the elements, we will also calculate when, if ever
|
||||
* the next one will need to be notified.
|
||||
*/
|
||||
// We need to initialise the time to the next idle switch.
|
||||
mDeltaToNextIdleSwitchInS = PR_UINT32_MAX;
|
||||
|
||||
// Create list of observers that should be notified.
|
||||
nsCOMArray<nsIObserver> notifyList;
|
||||
|
||||
for (PRUint32 i = 0; i < mArrayListeners.Length(); i++) {
|
||||
|
@ -407,106 +491,114 @@ nsIdleService::TryNotifyIdleState(PRUint32 aIdleTime)
|
|||
// We are only interested in items, that are not in the idle state.
|
||||
if (!curListener.isIdle) {
|
||||
// If they have an idle time smaller than the actual idle time.
|
||||
if (curListener.reqIdleTime <= aIdleTime) {
|
||||
// then add the listener to the list of listeners that should be
|
||||
if (curListener.reqIdleTime <= currentIdleTimeInS) {
|
||||
// Then add the listener to the list of listeners that should be
|
||||
// notified.
|
||||
notifyList.AppendObject(curListener.observer);
|
||||
// This listener is now idle.
|
||||
curListener.isIdle = true;
|
||||
} else {
|
||||
// Listeners that are not timed out yet are candidates for timing out.
|
||||
mDeltaToNextIdleSwitchInS = PR_MIN(mDeltaToNextIdleSwitchInS,
|
||||
curListener.reqIdleTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restart the timer before any notifications that could slow us down are
|
||||
// done.
|
||||
ReconfigureTimer();
|
||||
|
||||
PRInt32 numberOfPendingNotifications = notifyList.Count();
|
||||
|
||||
// Bail if nothing to do
|
||||
if(!numberOfPendingNotifications) {
|
||||
return false;
|
||||
// Bail if nothing to do.
|
||||
if (!numberOfPendingNotifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remember we have someone idle.
|
||||
mAnyObserverIdle = true;
|
||||
|
||||
// We need a text string to send with any state change events.
|
||||
nsAutoString timeStr;
|
||||
timeStr.AppendInt(aIdleTime);
|
||||
timeStr.AppendInt(currentIdleTimeInS);
|
||||
|
||||
// Notify all listeners that just timed out.
|
||||
while(numberOfPendingNotifications--) {
|
||||
while (numberOfPendingNotifications--) {
|
||||
notifyList[numberOfPendingNotifications]->Observe(this,
|
||||
OBSERVER_TOPIC_IDLE,
|
||||
timeStr.get());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
nsIdleService::RescheduleIdleTimer(PRUint32 aIdleTime)
|
||||
nsIdleService::SetTimerExpiryIfBefore(PRTime aNextTimeoutInPR)
|
||||
{
|
||||
/**
|
||||
* Placet to store the wait time to the next notification, note that
|
||||
* PR_UINT32_MAX means no-one are listening (or that they have such a big
|
||||
* delay that it doesn't matter).
|
||||
*/
|
||||
PRUint32 nextWaitTime = PR_UINT32_MAX;
|
||||
|
||||
/**
|
||||
* Place to remember if there are any listeners that are in the idle state,
|
||||
* if there are, we need to poll frequently in a polling environment to detect
|
||||
* when the user becomes active again.
|
||||
*/
|
||||
bool anyOneIdle = false;
|
||||
|
||||
for (PRUint32 i = 0; i < mArrayListeners.Length(); i++) {
|
||||
IdleListener& curListener = mArrayListeners.ElementAt(i);
|
||||
|
||||
// We are only interested in items, that are not in the idle state.
|
||||
if (!curListener.isIdle) {
|
||||
// If it hasn't expired yet, then we should note the time when it should
|
||||
// expire.
|
||||
nextWaitTime = NS_MIN(nextWaitTime, curListener.reqIdleTime);
|
||||
}
|
||||
|
||||
// Remember if anyone becomes idle (it's safe to do this as a binary compare
|
||||
// as we are or'ing).
|
||||
anyOneIdle |= curListener.isIdle;
|
||||
// Bail if we don't have a timer service.
|
||||
if (!mTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In order to find when the next idle event should time out, we need to
|
||||
// subtract the time we should wait, from the time that has already passed.
|
||||
if (PR_UINT32_MAX != nextWaitTime) {
|
||||
nextWaitTime -= aIdleTime;
|
||||
}
|
||||
// If the new timeout is before the old one or we don't have a timer running,
|
||||
// then restart the timer.
|
||||
if (mCurrentlySetToTimeoutAtInPR > aNextTimeoutInPR ||
|
||||
!mCurrentlySetToTimeoutAtInPR) {
|
||||
|
||||
// If we are in poll mode, we need to poll for activity if anyone are idle,
|
||||
// otherwise we can wait polling until they would expire.
|
||||
if (UsePollMode() &&
|
||||
anyOneIdle &&
|
||||
nextWaitTime > MIN_IDLE_POLL_INTERVAL) {
|
||||
nextWaitTime = MIN_IDLE_POLL_INTERVAL;
|
||||
}
|
||||
mCurrentlySetToTimeoutAtInPR = aNextTimeoutInPR ;
|
||||
|
||||
// Start the timer if there is anything to wait for.
|
||||
if (PR_UINT32_MAX != nextWaitTime) {
|
||||
StartTimer(nextWaitTime);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsIdleService::StartTimer(PRUint32 aDelay)
|
||||
{
|
||||
if (mTimer) {
|
||||
StopTimer();
|
||||
if (aDelay) {
|
||||
mTimer->InitWithFuncCallback(IdleTimerCallback, this, aDelay*1000,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsIdleService::StopTimer()
|
||||
{
|
||||
if (mTimer) {
|
||||
// Stop the current timer (it's ok to try'n stop it, even it isn't running).
|
||||
mTimer->Cancel();
|
||||
|
||||
// Check that the timeout is actually in the future, otherwise make it so.
|
||||
PRTime currentTimeInPR = PR_Now();
|
||||
if (currentTimeInPR > mCurrentlySetToTimeoutAtInPR) {
|
||||
mCurrentlySetToTimeoutAtInPR = currentTimeInPR;
|
||||
}
|
||||
|
||||
// Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
|
||||
mCurrentlySetToTimeoutAtInPR += 10 * PR_USEC_PER_MSEC;
|
||||
|
||||
// Start the timer
|
||||
mTimer->InitWithFuncCallback(StaticIdleTimerCallback,
|
||||
this,
|
||||
(mCurrentlySetToTimeoutAtInPR -
|
||||
currentTimeInPR) / PR_USEC_PER_MSEC,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
nsIdleService::ReconfigureTimer(void)
|
||||
{
|
||||
// Check if either someone is idle, or someone will become idle.
|
||||
if (!mAnyObserverIdle && PR_UINT32_MAX == mDeltaToNextIdleSwitchInS) {
|
||||
// If not, just let any existing timers run to completion
|
||||
// And bail out.
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the next timeout value, assuming we are not polling.
|
||||
|
||||
// We need to store the current time, so we don't get artifacts from the time
|
||||
// ticking while we are processing.
|
||||
PRTime curTimeInPR = PR_Now();
|
||||
|
||||
PRTime nextTimeoutAtInPR = mLastUserInteractionInPR +
|
||||
(((PRTime)mDeltaToNextIdleSwitchInS) *
|
||||
PR_USEC_PER_SEC);
|
||||
|
||||
// Check if we should correct the timeout time because we should poll before.
|
||||
if (mAnyObserverIdle && UsePollMode()) {
|
||||
PRTime pollTimeout = curTimeInPR +
|
||||
MIN_IDLE_POLL_INTERVAL_MSEC * PR_USEC_PER_SEC;
|
||||
|
||||
if (nextTimeoutAtInPR > pollTimeout) {
|
||||
nextTimeoutAtInPR = pollTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
SetTimerExpiryIfBefore(nextTimeoutAtInPR);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Gijs Kruitbosch <gijskruitbosch@gmail.com>
|
||||
* Mike Kristoffersen <moz@mikek.dk>
|
||||
* Mike Kristoffersen <mozstuff@mikek.dk>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -128,10 +128,17 @@ public:
|
|||
NS_IMETHOD RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aIdleTime);
|
||||
NS_IMETHOD GetIdleTime(PRUint32* idleTime);
|
||||
|
||||
void ResetIdleTimeOut();
|
||||
/**
|
||||
* Function that resets the idle time in the service, in other words it
|
||||
* sets the time for the last user interaction to now, or now-idleDelta
|
||||
*
|
||||
* @param idleDelta the time (in milliseconds) since the last user inter
|
||||
* action
|
||||
**/
|
||||
void ResetIdleTimeOut(PRUint32 idleDeltaInMS = 0);
|
||||
|
||||
protected:
|
||||
~nsIdleService();
|
||||
virtual ~nsIdleService();
|
||||
|
||||
/**
|
||||
* If there is a platform specific function to poll the system idel time
|
||||
|
@ -158,29 +165,21 @@ protected:
|
|||
*/
|
||||
virtual bool UsePollMode();
|
||||
|
||||
/**
|
||||
* Send expired events and start timers.
|
||||
*
|
||||
* @param aNewObserver
|
||||
* Whether there are new observers to check.
|
||||
*/
|
||||
void CheckAwayState(bool aNewObserver);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Start the internal timer, restart it if it is allready running.
|
||||
* Ensure that the timer is expiring at least at the given time
|
||||
*
|
||||
* @param aDelay
|
||||
* The time in seconds that should pass before the next timeout.
|
||||
* The function might not restart the timer if there is one running currently
|
||||
*
|
||||
* @param aNextTimeoutInPR
|
||||
* The last absolute time the timer should expire
|
||||
*/
|
||||
|
||||
void StartTimer(PRUint32 aDelay);
|
||||
void SetTimerExpiryIfBefore(PRTime aNextTimeoutInPR);
|
||||
|
||||
/**
|
||||
* Stop the internal timer, it is safe to call this function, even when
|
||||
* there are no timers running.
|
||||
* Stores the next timeout time, 0 means timer not running
|
||||
*/
|
||||
void StopTimer();
|
||||
PRTime mCurrentlySetToTimeoutAtInPR;
|
||||
|
||||
/**
|
||||
* mTimer holds the internal timer used by this class to detect when to poll
|
||||
|
@ -199,62 +198,43 @@ private:
|
|||
nsRefPtr<nsIdleServiceDaily> mDailyIdle;
|
||||
|
||||
/**
|
||||
* Contains the time of the last idle reset or 0 if there haven't been a
|
||||
* reset.
|
||||
* <p>
|
||||
* Time is kept in seconds since the epoch at midnight, January 1, 1970 UTC.
|
||||
* Boolean indicating if any observers are in idle mode
|
||||
*/
|
||||
PRUint32 mLastIdleReset;
|
||||
bool mAnyObserverIdle;
|
||||
|
||||
/**
|
||||
* The time since the last handled activity (which might be different than
|
||||
* mLastIdleReset, since the activity that reset the idle timer could just
|
||||
* have happend, and not handled yet).
|
||||
* <p>
|
||||
* Time is kept in seconds since the epoch at midnight, January 1, 1970 UTC.
|
||||
* Delta time from last non idle time to when the next observer should switch
|
||||
* to idle mode
|
||||
*
|
||||
* Time in seconds
|
||||
*
|
||||
* If this value is 0 it means there are no active observers
|
||||
*/
|
||||
PRUint32 mLastHandledActivity;
|
||||
PRUint32 mDeltaToNextIdleSwitchInS;
|
||||
|
||||
/**
|
||||
* Absolute value for when the last user interaction took place.
|
||||
*/
|
||||
PRTime mLastUserInteractionInPR;
|
||||
|
||||
|
||||
/**
|
||||
* Function that ensures the timer is running with at least the minimum time
|
||||
* needed. It will kill the timer if there are no active observers.
|
||||
*/
|
||||
void ReconfigureTimer(void);
|
||||
|
||||
/**
|
||||
* Callback function that is called when the internal timer expires.
|
||||
*
|
||||
* This in turn calls the IdleTimerCallback that does the real processing
|
||||
*/
|
||||
static void IdleTimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
static void StaticIdleTimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
|
||||
/**
|
||||
* Whether the idle time calculated in the last call to GetIdleTime is
|
||||
* actually valid (see nsIdleService.idl - we return 0 when it isn't).
|
||||
* Function that handles when a timer has expired
|
||||
*/
|
||||
bool mPolledIdleTimeIsValid;
|
||||
|
||||
/**
|
||||
* Check if any listeners were in idle state, notify them that user
|
||||
* is back and change them to non-idle state.
|
||||
*
|
||||
* @param aIdleTime
|
||||
* The idle time in seconds.
|
||||
*
|
||||
* @return true if any listeners were notified.
|
||||
*/
|
||||
bool TryNotifyBackState(PRUint32 aIdleTime);
|
||||
|
||||
/**
|
||||
* Check if any listeners were in non-idle state, notify them that user
|
||||
* is idle and change them to idle state.
|
||||
*
|
||||
* @param aIdleTime
|
||||
* The idle time in seconds.
|
||||
*
|
||||
* @return true if any listeners were notified.
|
||||
*/
|
||||
bool TryNotifyIdleState(PRUint32 aIdleTime);
|
||||
|
||||
/**
|
||||
* Reschedule the idle timer to fire on next checkpoint.
|
||||
*
|
||||
* @param aIdleTime
|
||||
* The idle time has passed in seconds.
|
||||
*/
|
||||
void RescheduleIdleTimer(PRUint32 aIdleTime);
|
||||
void IdleTimerCallback(void);
|
||||
};
|
||||
|
||||
#endif // nsIdleService_h__
|
||||
|
|
Загрузка…
Ссылка в новой задаче