nsIdleService.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* vim:expandtab:shiftwidth=2:tabstop=2:
  3. */
  4. /* This Source Code Form is subject to the terms of the Mozilla Public
  5. * License, v. 2.0. If a copy of the MPL was not distributed with this
  6. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  7. #include "nsIdleService.h"
  8. #include "nsString.h"
  9. #include "nsIObserverService.h"
  10. #include "nsIServiceManager.h"
  11. #include "nsDebug.h"
  12. #include "nsCOMArray.h"
  13. #include "nsXULAppAPI.h"
  14. #include "prinrval.h"
  15. #include "mozilla/Logging.h"
  16. #include "prtime.h"
  17. #include "mozilla/dom/ContentChild.h"
  18. #include "mozilla/Services.h"
  19. #include "mozilla/Preferences.h"
  20. #include "mozilla/Telemetry.h"
  21. #include <algorithm>
  22. using namespace mozilla;
  23. // interval in milliseconds between internal idle time requests.
  24. #define MIN_IDLE_POLL_INTERVAL_MSEC (5 * PR_MSEC_PER_SEC) /* 5 sec */
  25. // After the twenty four hour period expires for an idle daily, this is the
  26. // amount of idle time we wait for before actually firing the idle-daily
  27. // event.
  28. #define DAILY_SIGNIFICANT_IDLE_SERVICE_SEC (3 * 60)
  29. // In cases where it's been longer than twenty four hours since the last
  30. // idle-daily, this is the shortend amount of idle time we wait for before
  31. // firing the idle-daily event.
  32. #define DAILY_SHORTENED_IDLE_SERVICE_SEC 60
  33. // Pref for last time (seconds since epoch) daily notification was sent.
  34. #define PREF_LAST_DAILY "idle.lastDailyNotification"
  35. // Number of seconds in a day.
  36. #define SECONDS_PER_DAY 86400
  37. // MAX_DELTA_SEC * 1000 should be less than UINT32_MAX to prevent overflow on
  38. // converting from sec to msec
  39. #define MAX_DELTA_SEC (SECONDS_PER_DAY * 10)
  40. static PRLogModuleInfo *sLog = nullptr;
  41. #define LOG_TAG "GeckoIdleService"
  42. // Use this to find previously added observers in our array:
  43. class IdleListenerComparator
  44. {
  45. public:
  46. bool Equals(IdleListener a, IdleListener b) const
  47. {
  48. return (a.observer == b.observer) &&
  49. (a.reqIdleTime == b.reqIdleTime);
  50. }
  51. };
  52. ////////////////////////////////////////////////////////////////////////////////
  53. //// nsIdleServiceDaily
  54. NS_IMPL_ISUPPORTS(nsIdleServiceDaily, nsIObserver, nsISupportsWeakReference)
  55. NS_IMETHODIMP
  56. nsIdleServiceDaily::Observe(nsISupports *,
  57. const char *aTopic,
  58. const char16_t *)
  59. {
  60. MOZ_LOG(sLog, LogLevel::Debug,
  61. ("nsIdleServiceDaily: Observe '%s' (%d)",
  62. aTopic, mShutdownInProgress));
  63. if (strcmp(aTopic, "profile-after-change") == 0) {
  64. // We are back. Start sending notifications again.
  65. mShutdownInProgress = false;
  66. return NS_OK;
  67. }
  68. if (strcmp(aTopic, "xpcom-will-shutdown") == 0 ||
  69. strcmp(aTopic, "profile-change-teardown") == 0) {
  70. mShutdownInProgress = true;
  71. }
  72. if (mShutdownInProgress || strcmp(aTopic, OBSERVER_TOPIC_ACTIVE) == 0) {
  73. return NS_OK;
  74. }
  75. MOZ_ASSERT(strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0);
  76. MOZ_LOG(sLog, LogLevel::Debug,
  77. ("nsIdleServiceDaily: Notifying idle-daily observers"));
  78. // Send the idle-daily observer event
  79. nsCOMPtr<nsIObserverService> observerService =
  80. mozilla::services::GetObserverService();
  81. NS_ENSURE_STATE(observerService);
  82. (void)observerService->NotifyObservers(nullptr,
  83. OBSERVER_TOPIC_IDLE_DAILY,
  84. nullptr);
  85. // Notify the category observers.
  86. nsCOMArray<nsIObserver> entries;
  87. mCategoryObservers.GetEntries(entries);
  88. for (int32_t i = 0; i < entries.Count(); ++i) {
  89. (void)entries[i]->Observe(nullptr, OBSERVER_TOPIC_IDLE_DAILY, nullptr);
  90. }
  91. // Stop observing idle for today.
  92. (void)mIdleService->RemoveIdleObserver(this, mIdleDailyTriggerWait);
  93. // Set the last idle-daily time pref.
  94. int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
  95. Preferences::SetInt(PREF_LAST_DAILY, nowSec);
  96. // Force that to be stored so we don't retrigger twice a day under
  97. // any circumstances.
  98. nsIPrefService* prefs = Preferences::GetService();
  99. if (prefs) {
  100. prefs->SavePrefFile(nullptr);
  101. }
  102. MOZ_LOG(sLog, LogLevel::Debug,
  103. ("nsIdleServiceDaily: Storing last idle time as %d sec.", nowSec));
  104. // Note the moment we expect to get the next timer callback
  105. mExpectedTriggerTime = PR_Now() + ((PRTime)SECONDS_PER_DAY *
  106. (PRTime)PR_USEC_PER_SEC);
  107. MOZ_LOG(sLog, LogLevel::Debug,
  108. ("nsIdleServiceDaily: Restarting daily timer"));
  109. // Start timer for the next check in one day.
  110. (void)mTimer->InitWithFuncCallback(DailyCallback,
  111. this,
  112. SECONDS_PER_DAY * PR_MSEC_PER_SEC,
  113. nsITimer::TYPE_ONE_SHOT);
  114. return NS_OK;
  115. }
  116. nsIdleServiceDaily::nsIdleServiceDaily(nsIIdleService* aIdleService)
  117. : mIdleService(aIdleService)
  118. , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
  119. , mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY)
  120. , mShutdownInProgress(false)
  121. , mExpectedTriggerTime(0)
  122. , mIdleDailyTriggerWait(DAILY_SIGNIFICANT_IDLE_SERVICE_SEC)
  123. {
  124. }
  125. void
  126. nsIdleServiceDaily::Init()
  127. {
  128. // First check the time of the last idle-daily event notification. If it
  129. // has been 24 hours or higher, or if we have never sent an idle-daily,
  130. // get ready to send an idle-daily event. Otherwise set a timer targeted
  131. // at 24 hours past the last idle-daily we sent.
  132. int32_t nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
  133. int32_t lastDaily = Preferences::GetInt(PREF_LAST_DAILY, 0);
  134. if (lastDaily < 0 || lastDaily > nowSec) {
  135. // The time is bogus, use default.
  136. lastDaily = 0;
  137. }
  138. int32_t secondsSinceLastDaily = nowSec - lastDaily;
  139. MOZ_LOG(sLog, LogLevel::Debug,
  140. ("nsIdleServiceDaily: Init: seconds since last daily: %d",
  141. secondsSinceLastDaily));
  142. // If it has been twenty four hours or more or if we have never sent an
  143. // idle-daily event get ready to send it during the next idle period.
  144. if (secondsSinceLastDaily > SECONDS_PER_DAY) {
  145. // Check for a "long wait", e.g. 48-hours or more.
  146. bool hasBeenLongWait = (lastDaily &&
  147. (secondsSinceLastDaily > (SECONDS_PER_DAY * 2)));
  148. MOZ_LOG(sLog, LogLevel::Debug,
  149. ("nsIdleServiceDaily: has been long wait? %d",
  150. hasBeenLongWait));
  151. // StageIdleDaily sets up a wait for the user to become idle and then
  152. // sends the idle-daily event.
  153. StageIdleDaily(hasBeenLongWait);
  154. } else {
  155. MOZ_LOG(sLog, LogLevel::Debug,
  156. ("nsIdleServiceDaily: Setting timer a day from now"));
  157. // According to our last idle-daily pref, the last idle-daily was fired
  158. // less then 24 hours ago. Set a wait for the amount of time remaining.
  159. int32_t milliSecLeftUntilDaily = (SECONDS_PER_DAY - secondsSinceLastDaily)
  160. * PR_MSEC_PER_SEC;
  161. MOZ_LOG(sLog, LogLevel::Debug,
  162. ("nsIdleServiceDaily: Seconds till next timeout: %d",
  163. (SECONDS_PER_DAY - secondsSinceLastDaily)));
  164. // Mark the time at which we expect this to fire. On systems with faulty
  165. // timers, we need to be able to cross check that the timer fired at the
  166. // expected time.
  167. mExpectedTriggerTime = PR_Now() +
  168. (milliSecLeftUntilDaily * PR_USEC_PER_MSEC);
  169. (void)mTimer->InitWithFuncCallback(DailyCallback,
  170. this,
  171. milliSecLeftUntilDaily,
  172. nsITimer::TYPE_ONE_SHOT);
  173. }
  174. // Register for when we should terminate/pause
  175. nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  176. if (obs) {
  177. MOZ_LOG(sLog, LogLevel::Debug,
  178. ("nsIdleServiceDaily: Registering for system event observers."));
  179. obs->AddObserver(this, "xpcom-will-shutdown", true);
  180. obs->AddObserver(this, "profile-change-teardown", true);
  181. obs->AddObserver(this, "profile-after-change", true);
  182. }
  183. }
  184. nsIdleServiceDaily::~nsIdleServiceDaily()
  185. {
  186. if (mTimer) {
  187. mTimer->Cancel();
  188. mTimer = nullptr;
  189. }
  190. }
  191. void
  192. nsIdleServiceDaily::StageIdleDaily(bool aHasBeenLongWait)
  193. {
  194. NS_ASSERTION(mIdleService, "No idle service available?");
  195. MOZ_LOG(sLog, LogLevel::Debug,
  196. ("nsIdleServiceDaily: Registering Idle observer callback "
  197. "(short wait requested? %d)", aHasBeenLongWait));
  198. mIdleDailyTriggerWait = (aHasBeenLongWait ?
  199. DAILY_SHORTENED_IDLE_SERVICE_SEC :
  200. DAILY_SIGNIFICANT_IDLE_SERVICE_SEC);
  201. (void)mIdleService->AddIdleObserver(this, mIdleDailyTriggerWait);
  202. }
  203. // static
  204. void
  205. nsIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure)
  206. {
  207. MOZ_LOG(sLog, LogLevel::Debug,
  208. ("nsIdleServiceDaily: DailyCallback running"));
  209. nsIdleServiceDaily* self = static_cast<nsIdleServiceDaily*>(aClosure);
  210. // Check to be sure the timer didn't fire early.
  211. PRTime now = PR_Now();
  212. if (self->mExpectedTriggerTime && now < self->mExpectedTriggerTime) {
  213. // Timer returned early, reschedule to the appropriate time.
  214. PRTime delayTime = self->mExpectedTriggerTime - now;
  215. // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
  216. delayTime += 10 * PR_USEC_PER_MSEC;
  217. MOZ_LOG(sLog, LogLevel::Debug, ("nsIdleServiceDaily: DailyCallback resetting timer to %lld msec",
  218. delayTime / PR_USEC_PER_MSEC));
  219. (void)self->mTimer->InitWithFuncCallback(DailyCallback,
  220. self,
  221. delayTime / PR_USEC_PER_MSEC,
  222. nsITimer::TYPE_ONE_SHOT);
  223. return;
  224. }
  225. // Register for a short term wait for idle event. When this fires we fire
  226. // our idle-daily event.
  227. self->StageIdleDaily(false);
  228. }
  229. /**
  230. * The idle services goal is to notify subscribers when a certain time has
  231. * passed since the last user interaction with the system.
  232. *
  233. * On some platforms this is defined as the last time user events reached this
  234. * application, on other platforms it is a system wide thing - the preferred
  235. * implementation is to use the system idle time, rather than the application
  236. * idle time, as the things depending on the idle service are likely to use
  237. * significant resources (network, disk, memory, cpu, etc.).
  238. *
  239. * When the idle service needs to use the system wide idle timer, it typically
  240. * needs to poll the idle time value by the means of a timer. It needs to
  241. * poll fast when it is in active idle mode (when it has a listener in the idle
  242. * mode) as it needs to detect if the user is active in other applications.
  243. *
  244. * When the service is waiting for the first listener to become idle, or when
  245. * it is only monitoring application idle time, it only needs to have the timer
  246. * expire at the time the next listener goes idle.
  247. *
  248. * The core state of the service is determined by:
  249. *
  250. * - A list of listeners.
  251. *
  252. * - A boolean that tells if any listeners are in idle mode.
  253. *
  254. * - A delta value that indicates when, measured from the last non-idle time,
  255. * the next listener should switch to idle mode.
  256. *
  257. * - An absolute time of the last time idle mode was detected (this is used to
  258. * judge if we have been out of idle mode since the last invocation of the
  259. * service.
  260. *
  261. * There are four entry points into the system:
  262. *
  263. * - A new listener is registered.
  264. *
  265. * - An existing listener is deregistered.
  266. *
  267. * - User interaction is detected.
  268. *
  269. * - The timer expires.
  270. *
  271. * When a new listener is added its idle timeout, is compared with the next idle
  272. * timeout, and if lower, that time is stored as the new timeout, and the timer
  273. * is reconfigured to ensure a timeout around the time the new listener should
  274. * timeout.
  275. *
  276. * If the next idle time is above the idle time requested by the new listener
  277. * it won't be informed until the timer expires, this is to avoid recursive
  278. * behavior and to simplify the code. In this case the timer will be set to
  279. * about 10 ms.
  280. *
  281. * When an existing listener is deregistered, it is just removed from the list
  282. * of active listeners, we don't stop the timer, we just let it expire.
  283. *
  284. * When user interaction is detected, either because it was directly detected or
  285. * because we polled the system timer and found it to be unexpected low, then we
  286. * check the flag that tells us if any listeners are in idle mode, if there are
  287. * they are removed from idle mode and told so, and we reset our state
  288. * caculating the next timeout and restart the timer if needed.
  289. *
  290. * ---- Build in logic
  291. *
  292. * In order to avoid restarting the timer endlessly, the timer function has
  293. * logic that will only restart the timer, if the requested timeout is before
  294. * the current timeout.
  295. *
  296. */
  297. ////////////////////////////////////////////////////////////////////////////////
  298. //// nsIdleService
  299. namespace {
  300. nsIdleService* gIdleService;
  301. } // namespace
  302. already_AddRefed<nsIdleService>
  303. nsIdleService::GetInstance()
  304. {
  305. RefPtr<nsIdleService> instance(gIdleService);
  306. return instance.forget();
  307. }
  308. nsIdleService::nsIdleService() : mCurrentlySetToTimeoutAt(TimeStamp()),
  309. mIdleObserverCount(0),
  310. mDeltaToNextIdleSwitchInS(MAX_DELTA_SEC),
  311. mLastUserInteraction(TimeStamp::Now())
  312. {
  313. if (sLog == nullptr)
  314. sLog = PR_NewLogModule("idleService");
  315. MOZ_ASSERT(!gIdleService);
  316. gIdleService = this;
  317. if (XRE_IsParentProcess()) {
  318. mDailyIdle = new nsIdleServiceDaily(this);
  319. mDailyIdle->Init();
  320. }
  321. }
  322. nsIdleService::~nsIdleService()
  323. {
  324. if(mTimer) {
  325. mTimer->Cancel();
  326. }
  327. MOZ_ASSERT(gIdleService == this);
  328. gIdleService = nullptr;
  329. }
  330. NS_IMPL_ISUPPORTS(nsIdleService, nsIIdleService, nsIIdleServiceInternal)
  331. NS_IMETHODIMP
  332. nsIdleService::AddIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS)
  333. {
  334. NS_ENSURE_ARG_POINTER(aObserver);
  335. // We don't accept idle time at 0, and we can't handle idle time that are too
  336. // high either - no more than ~136 years.
  337. NS_ENSURE_ARG_RANGE(aIdleTimeInS, 1, (UINT32_MAX / 10) - 1);
  338. if (XRE_IsContentProcess()) {
  339. dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
  340. cpc->AddIdleObserver(aObserver, aIdleTimeInS);
  341. return NS_OK;
  342. }
  343. MOZ_LOG(sLog, LogLevel::Debug,
  344. ("idleService: Register idle observer %p for %d seconds",
  345. aObserver, aIdleTimeInS));
  346. // Put the time + observer in a struct we can keep:
  347. IdleListener listener(aObserver, aIdleTimeInS);
  348. if (!mArrayListeners.AppendElement(listener)) {
  349. return NS_ERROR_OUT_OF_MEMORY;
  350. }
  351. // Create our timer callback if it's not there already.
  352. if (!mTimer) {
  353. nsresult rv;
  354. mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
  355. NS_ENSURE_SUCCESS(rv, rv);
  356. }
  357. // Check if the newly added observer has a smaller wait time than what we
  358. // are waiting for now.
  359. if (mDeltaToNextIdleSwitchInS > aIdleTimeInS) {
  360. // If it is, then this is the next to move to idle (at this point we
  361. // don't care if it should have switched already).
  362. MOZ_LOG(sLog, LogLevel::Debug,
  363. ("idleService: Register: adjusting next switch from %d to %d seconds",
  364. mDeltaToNextIdleSwitchInS, aIdleTimeInS));
  365. mDeltaToNextIdleSwitchInS = aIdleTimeInS;
  366. }
  367. // Ensure timer is running.
  368. ReconfigureTimer();
  369. return NS_OK;
  370. }
  371. NS_IMETHODIMP
  372. nsIdleService::RemoveIdleObserver(nsIObserver* aObserver, uint32_t aTimeInS)
  373. {
  374. NS_ENSURE_ARG_POINTER(aObserver);
  375. NS_ENSURE_ARG(aTimeInS);
  376. if (XRE_IsContentProcess()) {
  377. dom::ContentChild* cpc = dom::ContentChild::GetSingleton();
  378. cpc->RemoveIdleObserver(aObserver, aTimeInS);
  379. return NS_OK;
  380. }
  381. IdleListener listener(aObserver, aTimeInS);
  382. // Find the entry and remove it, if it was the last entry, we just let the
  383. // existing timer run to completion (there might be a new registration in a
  384. // little while.
  385. IdleListenerComparator c;
  386. nsTArray<IdleListener>::index_type listenerIndex = mArrayListeners.IndexOf(listener, 0, c);
  387. if (listenerIndex != mArrayListeners.NoIndex) {
  388. if (mArrayListeners.ElementAt(listenerIndex).isIdle)
  389. mIdleObserverCount--;
  390. mArrayListeners.RemoveElementAt(listenerIndex);
  391. MOZ_LOG(sLog, LogLevel::Debug,
  392. ("idleService: Remove observer %p (%d seconds), %d remain idle",
  393. aObserver, aTimeInS, mIdleObserverCount));
  394. return NS_OK;
  395. }
  396. // If we get here, we haven't removed anything:
  397. MOZ_LOG(sLog, LogLevel::Warning,
  398. ("idleService: Failed to remove idle observer %p (%d seconds)",
  399. aObserver, aTimeInS));
  400. return NS_ERROR_FAILURE;
  401. }
  402. NS_IMETHODIMP
  403. nsIdleService::ResetIdleTimeOut(uint32_t idleDeltaInMS)
  404. {
  405. MOZ_LOG(sLog, LogLevel::Debug,
  406. ("idleService: Reset idle timeout (last interaction %u msec)",
  407. idleDeltaInMS));
  408. // Store the time
  409. mLastUserInteraction = TimeStamp::Now() -
  410. TimeDuration::FromMilliseconds(idleDeltaInMS);
  411. // If no one is idle, then we are done, any existing timers can keep running.
  412. if (mIdleObserverCount == 0) {
  413. MOZ_LOG(sLog, LogLevel::Debug,
  414. ("idleService: Reset idle timeout: no idle observers"));
  415. return NS_OK;
  416. }
  417. // Mark all idle services as non-idle, and calculate the next idle timeout.
  418. nsCOMArray<nsIObserver> notifyList;
  419. mDeltaToNextIdleSwitchInS = MAX_DELTA_SEC;
  420. // Loop through all listeners, and find any that have detected idle.
  421. for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
  422. IdleListener& curListener = mArrayListeners.ElementAt(i);
  423. // If the listener was idle, then he shouldn't be any longer.
  424. if (curListener.isIdle) {
  425. notifyList.AppendObject(curListener.observer);
  426. curListener.isIdle = false;
  427. }
  428. // Check if the listener is the next one to timeout.
  429. mDeltaToNextIdleSwitchInS = std::min(mDeltaToNextIdleSwitchInS,
  430. curListener.reqIdleTime);
  431. }
  432. // When we are done, then we wont have anyone idle.
  433. mIdleObserverCount = 0;
  434. // Restart the idle timer, and do so before anyone can delay us.
  435. ReconfigureTimer();
  436. int32_t numberOfPendingNotifications = notifyList.Count();
  437. // Bail if nothing to do.
  438. if (!numberOfPendingNotifications) {
  439. return NS_OK;
  440. }
  441. // Now send "active" events to all, if any should have timed out already,
  442. // then they will be reawaken by the timer that is already running.
  443. // We need a text string to send with any state change events.
  444. nsAutoString timeStr;
  445. timeStr.AppendInt((int32_t)(idleDeltaInMS / PR_MSEC_PER_SEC));
  446. // Send the "non-idle" events.
  447. while (numberOfPendingNotifications--) {
  448. MOZ_LOG(sLog, LogLevel::Debug,
  449. ("idleService: Reset idle timeout: tell observer %p user is back",
  450. notifyList[numberOfPendingNotifications]));
  451. notifyList[numberOfPendingNotifications]->Observe(this,
  452. OBSERVER_TOPIC_ACTIVE,
  453. timeStr.get());
  454. }
  455. return NS_OK;
  456. }
  457. NS_IMETHODIMP
  458. nsIdleService::GetIdleTime(uint32_t* idleTime)
  459. {
  460. // Check sanity of in parameter.
  461. if (!idleTime) {
  462. return NS_ERROR_NULL_POINTER;
  463. }
  464. // Polled idle time in ms.
  465. uint32_t polledIdleTimeMS;
  466. bool polledIdleTimeIsValid = PollIdleTime(&polledIdleTimeMS);
  467. MOZ_LOG(sLog, LogLevel::Debug,
  468. ("idleService: Get idle time: polled %u msec, valid = %d",
  469. polledIdleTimeMS, polledIdleTimeIsValid));
  470. // timeSinceReset is in milliseconds.
  471. TimeDuration timeSinceReset = TimeStamp::Now() - mLastUserInteraction;
  472. uint32_t timeSinceResetInMS = timeSinceReset.ToMilliseconds();
  473. MOZ_LOG(sLog, LogLevel::Debug,
  474. ("idleService: Get idle time: time since reset %u msec",
  475. timeSinceResetInMS));
  476. // If we did't get pulled data, return the time since last idle reset.
  477. if (!polledIdleTimeIsValid) {
  478. // We need to convert to ms before returning the time.
  479. *idleTime = timeSinceResetInMS;
  480. return NS_OK;
  481. }
  482. // Otherwise return the shortest time detected (in ms).
  483. *idleTime = std::min(timeSinceResetInMS, polledIdleTimeMS);
  484. return NS_OK;
  485. }
  486. bool
  487. nsIdleService::PollIdleTime(uint32_t* /*aIdleTime*/)
  488. {
  489. // Default behavior is not to have the ability to poll an idle time.
  490. return false;
  491. }
  492. bool
  493. nsIdleService::UsePollMode()
  494. {
  495. uint32_t dummy;
  496. return PollIdleTime(&dummy);
  497. }
  498. void
  499. nsIdleService::StaticIdleTimerCallback(nsITimer* aTimer, void* aClosure)
  500. {
  501. static_cast<nsIdleService*>(aClosure)->IdleTimerCallback();
  502. }
  503. void
  504. nsIdleService::IdleTimerCallback(void)
  505. {
  506. // Remember that we no longer have a timer running.
  507. mCurrentlySetToTimeoutAt = TimeStamp();
  508. // Find the last detected idle time.
  509. uint32_t lastIdleTimeInMS = static_cast<uint32_t>((TimeStamp::Now() -
  510. mLastUserInteraction).ToMilliseconds());
  511. // Get the current idle time.
  512. uint32_t currentIdleTimeInMS;
  513. if (NS_FAILED(GetIdleTime(&currentIdleTimeInMS))) {
  514. MOZ_LOG(sLog, LogLevel::Info,
  515. ("idleService: Idle timer callback: failed to get idle time"));
  516. return;
  517. }
  518. MOZ_LOG(sLog, LogLevel::Debug,
  519. ("idleService: Idle timer callback: current idle time %u msec",
  520. currentIdleTimeInMS));
  521. // Check if we have had some user interaction we didn't handle previously
  522. // we do the calculation in ms to lessen the chance for rounding errors to
  523. // trigger wrong results.
  524. if (lastIdleTimeInMS > currentIdleTimeInMS)
  525. {
  526. // We had user activity, so handle that part first (to ensure the listeners
  527. // don't risk getting an non-idle after they get a new idle indication.
  528. ResetIdleTimeOut(currentIdleTimeInMS);
  529. // NOTE: We can't bail here, as we might have something already timed out.
  530. }
  531. // Find the idle time in S.
  532. uint32_t currentIdleTimeInS = currentIdleTimeInMS / PR_MSEC_PER_SEC;
  533. // Restart timer and bail if no-one are expected to be in idle
  534. if (mDeltaToNextIdleSwitchInS > currentIdleTimeInS) {
  535. // If we didn't expect anyone to be idle, then just re-start the timer.
  536. ReconfigureTimer();
  537. return;
  538. }
  539. // We need to initialise the time to the next idle switch.
  540. mDeltaToNextIdleSwitchInS = MAX_DELTA_SEC;
  541. // Create list of observers that should be notified.
  542. nsCOMArray<nsIObserver> notifyList;
  543. for (uint32_t i = 0; i < mArrayListeners.Length(); i++) {
  544. IdleListener& curListener = mArrayListeners.ElementAt(i);
  545. // We are only interested in items, that are not in the idle state.
  546. if (!curListener.isIdle) {
  547. // If they have an idle time smaller than the actual idle time.
  548. if (curListener.reqIdleTime <= currentIdleTimeInS) {
  549. // Then add the listener to the list of listeners that should be
  550. // notified.
  551. notifyList.AppendObject(curListener.observer);
  552. // This listener is now idle.
  553. curListener.isIdle = true;
  554. // Remember we have someone idle.
  555. mIdleObserverCount++;
  556. } else {
  557. // Listeners that are not timed out yet are candidates for timing out.
  558. mDeltaToNextIdleSwitchInS = std::min(mDeltaToNextIdleSwitchInS,
  559. curListener.reqIdleTime);
  560. }
  561. }
  562. }
  563. // Restart the timer before any notifications that could slow us down are
  564. // done.
  565. ReconfigureTimer();
  566. int32_t numberOfPendingNotifications = notifyList.Count();
  567. // Bail if nothing to do.
  568. if (!numberOfPendingNotifications) {
  569. MOZ_LOG(sLog, LogLevel::Debug,
  570. ("idleService: **** Idle timer callback: no observers to message."));
  571. return;
  572. }
  573. // We need a text string to send with any state change events.
  574. nsAutoString timeStr;
  575. timeStr.AppendInt(currentIdleTimeInS);
  576. // Notify all listeners that just timed out.
  577. while (numberOfPendingNotifications--) {
  578. MOZ_LOG(sLog, LogLevel::Debug,
  579. ("idleService: **** Idle timer callback: tell observer %p user is idle",
  580. notifyList[numberOfPendingNotifications]));
  581. notifyList[numberOfPendingNotifications]->Observe(this,
  582. OBSERVER_TOPIC_IDLE,
  583. timeStr.get());
  584. }
  585. }
  586. void
  587. nsIdleService::SetTimerExpiryIfBefore(TimeStamp aNextTimeout)
  588. {
  589. TimeDuration nextTimeoutDuration = aNextTimeout - TimeStamp::Now();
  590. MOZ_LOG(sLog, LogLevel::Debug,
  591. ("idleService: SetTimerExpiryIfBefore: next timeout %0.f msec from now",
  592. nextTimeoutDuration.ToMilliseconds()));
  593. // Bail if we don't have a timer service.
  594. if (!mTimer) {
  595. return;
  596. }
  597. // If the new timeout is before the old one or we don't have a timer running,
  598. // then restart the timer.
  599. if (mCurrentlySetToTimeoutAt.IsNull() ||
  600. mCurrentlySetToTimeoutAt > aNextTimeout) {
  601. mCurrentlySetToTimeoutAt = aNextTimeout;
  602. // Stop the current timer (it's ok to try'n stop it, even it isn't running).
  603. mTimer->Cancel();
  604. // Check that the timeout is actually in the future, otherwise make it so.
  605. TimeStamp currentTime = TimeStamp::Now();
  606. if (currentTime > mCurrentlySetToTimeoutAt) {
  607. mCurrentlySetToTimeoutAt = currentTime;
  608. }
  609. // Add 10 ms to ensure we don't undershoot, and never get a "0" timer.
  610. mCurrentlySetToTimeoutAt += TimeDuration::FromMilliseconds(10);
  611. TimeDuration deltaTime = mCurrentlySetToTimeoutAt - currentTime;
  612. MOZ_LOG(sLog, LogLevel::Debug,
  613. ("idleService: IdleService reset timer expiry to %0.f msec from now",
  614. deltaTime.ToMilliseconds()));
  615. // Start the timer
  616. mTimer->InitWithFuncCallback(StaticIdleTimerCallback,
  617. this,
  618. deltaTime.ToMilliseconds(),
  619. nsITimer::TYPE_ONE_SHOT);
  620. }
  621. }
  622. void
  623. nsIdleService::ReconfigureTimer(void)
  624. {
  625. // Check if either someone is idle, or someone will become idle.
  626. if ((mIdleObserverCount == 0) && MAX_DELTA_SEC == mDeltaToNextIdleSwitchInS) {
  627. // If not, just let any existing timers run to completion
  628. // And bail out.
  629. MOZ_LOG(sLog, LogLevel::Debug,
  630. ("idleService: ReconfigureTimer: no idle or waiting observers"));
  631. return;
  632. }
  633. // Find the next timeout value, assuming we are not polling.
  634. // We need to store the current time, so we don't get artifacts from the time
  635. // ticking while we are processing.
  636. TimeStamp curTime = TimeStamp::Now();
  637. TimeStamp nextTimeoutAt = mLastUserInteraction +
  638. TimeDuration::FromSeconds(mDeltaToNextIdleSwitchInS);
  639. TimeDuration nextTimeoutDuration = nextTimeoutAt - curTime;
  640. MOZ_LOG(sLog, LogLevel::Debug,
  641. ("idleService: next timeout %0.f msec from now",
  642. nextTimeoutDuration.ToMilliseconds()));
  643. // Check if we should correct the timeout time because we should poll before.
  644. if ((mIdleObserverCount > 0) && UsePollMode()) {
  645. TimeStamp pollTimeout =
  646. curTime + TimeDuration::FromMilliseconds(MIN_IDLE_POLL_INTERVAL_MSEC);
  647. if (nextTimeoutAt > pollTimeout) {
  648. MOZ_LOG(sLog, LogLevel::Debug,
  649. ("idleService: idle observers, reducing timeout to %lu msec from now",
  650. MIN_IDLE_POLL_INTERVAL_MSEC));
  651. nextTimeoutAt = pollTimeout;
  652. }
  653. }
  654. SetTimerExpiryIfBefore(nextTimeoutAt);
  655. }