Bug 714717 - Part 2, Reschedule timer after non-idle event. r=MikeK
authorKan-Ru Chen <kanru@kanru.info>
Wed, 11 Jan 2012 15:38:16 +0100
changeset 85509 751ce658ce7397c9a2dedf5e6e84ac7877b28a0b
parent 85508 e44700517c2d414f416f6cab6f84fdffe9cddb67
child 85511 4f007aadbf3ed0488bd17be379765e246cd3de5d
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMikeK
bugs714717
milestone12.0a1
Bug 714717 - Part 2, Reschedule timer after non-idle event. r=MikeK
widget/xpwidgets/nsIdleService.cpp
widget/xpwidgets/nsIdleService.h
--- a/widget/xpwidgets/nsIdleService.cpp
+++ b/widget/xpwidgets/nsIdleService.cpp
@@ -195,17 +195,17 @@ nsIdleService::AddIdleObserver(nsIObserv
   // Create our timer callback if it's not there already.
   if (!mTimer) {
     nsresult rv;
     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Make sure our observer goes into 'idle' immediately if applicable.
-  CheckAwayState(false);
+  CheckAwayState(true);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, PRUint32 aTime)
 {
   NS_ENSURE_ARG_POINTER(aObserver);
@@ -223,28 +223,21 @@ nsIdleService::RemoveIdleObserver(nsIObs
 
   // If we get here, we haven't removed anything:
   return NS_ERROR_FAILURE;
 }
 
 void
 nsIdleService::ResetIdleTimeOut()
 {
-  // A zero in mLastIdleReset indicates that this function has never been
-  // called.
-  bool calledBefore = mLastIdleReset != 0;
   mLastIdleReset = PR_IntervalToSeconds(PR_IntervalNow());
   if (!mLastIdleReset) mLastIdleReset = 1;
 
   // Now check if this changes anything
-  // Note that if we have never been called before, we cannot do the
-  // optimization of passing true to CheckAwayState, which avoids
-  // calculating the timer (because if we have never been called before,
-  // we need to recalculate the timer and start it there).
-  CheckAwayState(calledBefore);
+  CheckAwayState(false);
 }
 
 NS_IMETHODIMP
 nsIdleService::GetIdleTime(PRUint32* idleTime)
 {
   // Check sanity of in parameter.
   if (!idleTime) {
     return NS_ERROR_NULL_POINTER;
@@ -301,83 +294,158 @@ nsIdleService::UsePollMode()
 
 void
 nsIdleService::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
 {
   static_cast<nsIdleService*>(aClosure)->CheckAwayState(false);
 }
 
 void
-nsIdleService::CheckAwayState(bool aNoTimeReset)
+nsIdleService::CheckAwayState(bool aNewObserver)
 {
   /**
    * 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;
+
+  /**
+   * Too short since last check.
+   * Bail out
+   */
+  if (!lastTime && !aNewObserver) {
+    return;
+  }
 
   // Get the idle time (in seconds).
   PRUint32 idleTime;
   if (NS_FAILED(GetIdleTime(&idleTime))) {
     return;
   }
 
   // If we have no valid data about the idle time, stop
   if (!mPolledIdleTimeIsValid && 0 == mLastIdleReset) {
     return;
   }
 
   // GetIdleTime returns the time in ms, internally we only calculate in s.
   idleTime /= 1000;
 
-  // We need a text string to send with any state change events.
-  nsAutoString timeStr;
-  timeStr.AppendInt(idleTime);
-
   // 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.
    */
-  nsCOMArray<nsIObserver> notifyList;
-
-  if (lastTime > idleTime) {
-    // Loop trough all listeners, and find any that have detected idle.
-    for (PRUint32 i = 0; i < mArrayListeners.Length(); i++) {
-      IdleListener& curListener = mArrayListeners.ElementAt(i);
+  bool userActivity = lastTime > idleTime;
 
-      if (curListener.isIdle) {
-        notifyList.AppendObject(curListener.observer);
-        curListener.isIdle = false;
-      }
-    }
-
-    // Send the "non-idle" events.
-    for (PRInt32 i = 0; i < notifyList.Count(); i++) {
-      notifyList[i]->Observe(this, OBSERVER_TOPIC_BACK, timeStr.get());
+  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.
    */
 
-  // Clean up the list, so it's ready for the next iteration.
-  notifyList.Clear();
+  nsCOMArray<nsIObserver> notifyList;
+
+  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 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
+        // notified.
+        notifyList.AppendObject(curListener.observer);
+        // This listener is now idle.
+        curListener.isIdle = true;
+      }
+    }
+  }
+
+  PRInt32 numberOfPendingNotifications = notifyList.Count();
 
-  // Bail out if we don't need to calculate new times.
-  if (aNoTimeReset) {
-    return;
+  // Bail if nothing to do
+  if(!numberOfPendingNotifications) {
+    return false;
   }
+
+  // We need a text string to send with any state change events.
+  nsAutoString timeStr;
+  timeStr.AppendInt(aIdleTime);
+
+  // Notify all listeners that just timed out.
+  while(numberOfPendingNotifications--) {
+    notifyList[numberOfPendingNotifications]->Observe(this,
+                                                      OBSERVER_TOPIC_IDLE,
+                                                      timeStr.get());
+  }
+
+  return true;
+}
+
+void
+nsIdleService::RescheduleIdleTimer(PRUint32 aIdleTime)
+{
   /**
    * 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;
 
   /**
@@ -387,44 +455,30 @@ nsIdleService::CheckAwayState(bool aNoTi
    */
   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 they have an idle time smaller than the actual idle time.
-      if (curListener.reqIdleTime <= idleTime) {
-        // 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 {
         // 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;
   }
 
   // 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 -= idleTime;
-  }
-
-  // Notify all listeners that just timed out.
-  for (PRInt32 i = 0; i < notifyList.Count(); i++) {
-    notifyList[i]->Observe(this, OBSERVER_TOPIC_IDLE, timeStr.get());
+    nextWaitTime -= aIdleTime;
   }
 
   // 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;
--- a/widget/xpwidgets/nsIdleService.h
+++ b/widget/xpwidgets/nsIdleService.h
@@ -156,20 +156,20 @@ protected:
    *
    * @return true if polling is supported, false otherwise.
    */
   virtual bool UsePollMode();
 
   /**
    * Send expired events and start timers.
    *
-   * @param aNoTimeReset
-   *        If true new times will not be calculated.
+   * @param aNewObserver
+   *        Whether there are new observers to check.
    */
-  void CheckAwayState(bool aNoTimeReset);
+  void CheckAwayState(bool aNewObserver);
 
 private:
   /**
    * Start the internal timer, restart it if it is allready running.
    *
    * @param aDelay
    *        The time in seconds that should pass before the next timeout.
    */
@@ -220,11 +220,41 @@ private:
    */
   static void IdleTimerCallback(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).
    */
   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);
 };
 
 #endif // nsIdleService_h__