nsBaseAppShell.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "base/message_loop.h"
  6. #include "nsBaseAppShell.h"
  7. #include "nsThreadUtils.h"
  8. #include "nsIObserverService.h"
  9. #include "nsServiceManagerUtils.h"
  10. #include "mozilla/Services.h"
  11. // When processing the next thread event, the appshell may process native
  12. // events (if not in performance mode), which can result in suppressing the
  13. // next thread event for at most this many ticks:
  14. #define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(10)
  15. NS_IMPL_ISUPPORTS(nsBaseAppShell, nsIAppShell, nsIThreadObserver, nsIObserver)
  16. nsBaseAppShell::nsBaseAppShell()
  17. : mSuspendNativeCount(0)
  18. , mEventloopNestingLevel(0)
  19. , mBlockedWait(nullptr)
  20. , mFavorPerf(0)
  21. , mNativeEventPending(false)
  22. , mStarvationDelay(0)
  23. , mSwitchTime(0)
  24. , mLastNativeEventTime(0)
  25. , mEventloopNestingState(eEventloopNone)
  26. , mRunning(false)
  27. , mExiting(false)
  28. , mBlockNativeEvent(false)
  29. {
  30. }
  31. nsBaseAppShell::~nsBaseAppShell()
  32. {
  33. }
  34. nsresult
  35. nsBaseAppShell::Init()
  36. {
  37. // Configure ourselves as an observer for the current thread:
  38. nsCOMPtr<nsIThreadInternal> threadInt =
  39. do_QueryInterface(NS_GetCurrentThread());
  40. NS_ENSURE_STATE(threadInt);
  41. threadInt->SetObserver(this);
  42. nsCOMPtr<nsIObserverService> obsSvc =
  43. mozilla::services::GetObserverService();
  44. if (obsSvc)
  45. obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
  46. return NS_OK;
  47. }
  48. // Called by nsAppShell's native event callback
  49. void
  50. nsBaseAppShell::NativeEventCallback()
  51. {
  52. if (!mNativeEventPending.exchange(false))
  53. return;
  54. // If DoProcessNextNativeEvent is on the stack, then we assume that we can
  55. // just unwind and let nsThread::ProcessNextEvent process the next event.
  56. // However, if we are called from a nested native event loop (maybe via some
  57. // plug-in or library function), then go ahead and process Gecko events now.
  58. if (mEventloopNestingState == eEventloopXPCOM) {
  59. mEventloopNestingState = eEventloopOther;
  60. // XXX there is a tiny risk we will never get a new NativeEventCallback,
  61. // XXX see discussion in bug 389931.
  62. return;
  63. }
  64. // nsBaseAppShell::Run is not being used to pump events, so this may be
  65. // our only opportunity to process pending gecko events.
  66. nsIThread *thread = NS_GetCurrentThread();
  67. bool prevBlockNativeEvent = mBlockNativeEvent;
  68. if (mEventloopNestingState == eEventloopOther) {
  69. if (!NS_HasPendingEvents(thread))
  70. return;
  71. // We're in a nested native event loop and have some gecko events to
  72. // process. While doing that we block processing native events from the
  73. // appshell - instead, we want to get back to the nested native event
  74. // loop ASAP (bug 420148).
  75. mBlockNativeEvent = true;
  76. }
  77. IncrementEventloopNestingLevel();
  78. EventloopNestingState prevVal = mEventloopNestingState;
  79. NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
  80. mProcessedGeckoEvents = true;
  81. mEventloopNestingState = prevVal;
  82. mBlockNativeEvent = prevBlockNativeEvent;
  83. // Continue processing pending events later (we don't want to starve the
  84. // embedders event loop).
  85. if (NS_HasPendingEvents(thread))
  86. DoProcessMoreGeckoEvents();
  87. DecrementEventloopNestingLevel();
  88. }
  89. // Note, this is currently overidden on windows, see comments in nsAppShell for
  90. // details.
  91. void
  92. nsBaseAppShell::DoProcessMoreGeckoEvents()
  93. {
  94. OnDispatchedEvent(nullptr);
  95. }
  96. // Main thread via OnProcessNextEvent below
  97. bool
  98. nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
  99. {
  100. // The next native event to be processed may trigger our NativeEventCallback,
  101. // in which case we do not want it to process any thread events since we'll
  102. // do that when this function returns.
  103. //
  104. // If the next native event is not our NativeEventCallback, then we may end
  105. // up recursing into this function.
  106. //
  107. // However, if the next native event is not our NativeEventCallback, but it
  108. // results in another native event loop, then our NativeEventCallback could
  109. // fire and it will see mEventloopNestingState as eEventloopOther.
  110. //
  111. EventloopNestingState prevVal = mEventloopNestingState;
  112. mEventloopNestingState = eEventloopXPCOM;
  113. IncrementEventloopNestingLevel();
  114. bool result = ProcessNextNativeEvent(mayWait);
  115. DecrementEventloopNestingLevel();
  116. mEventloopNestingState = prevVal;
  117. return result;
  118. }
  119. //-------------------------------------------------------------------------
  120. // nsIAppShell methods:
  121. NS_IMETHODIMP
  122. nsBaseAppShell::Run(void)
  123. {
  124. NS_ENSURE_STATE(!mRunning); // should not call Run twice
  125. mRunning = true;
  126. nsIThread *thread = NS_GetCurrentThread();
  127. MessageLoop::current()->Run();
  128. NS_ProcessPendingEvents(thread);
  129. mRunning = false;
  130. return NS_OK;
  131. }
  132. NS_IMETHODIMP
  133. nsBaseAppShell::Exit(void)
  134. {
  135. if (mRunning && !mExiting) {
  136. MessageLoop::current()->Quit();
  137. }
  138. mExiting = true;
  139. return NS_OK;
  140. }
  141. NS_IMETHODIMP
  142. nsBaseAppShell::FavorPerformanceHint(bool favorPerfOverStarvation,
  143. uint32_t starvationDelay)
  144. {
  145. mStarvationDelay = PR_MillisecondsToInterval(starvationDelay);
  146. if (favorPerfOverStarvation) {
  147. ++mFavorPerf;
  148. } else {
  149. --mFavorPerf;
  150. mSwitchTime = PR_IntervalNow();
  151. }
  152. return NS_OK;
  153. }
  154. NS_IMETHODIMP
  155. nsBaseAppShell::SuspendNative()
  156. {
  157. ++mSuspendNativeCount;
  158. return NS_OK;
  159. }
  160. NS_IMETHODIMP
  161. nsBaseAppShell::ResumeNative()
  162. {
  163. --mSuspendNativeCount;
  164. NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!");
  165. return NS_OK;
  166. }
  167. NS_IMETHODIMP
  168. nsBaseAppShell::GetEventloopNestingLevel(uint32_t* aNestingLevelResult)
  169. {
  170. NS_ENSURE_ARG_POINTER(aNestingLevelResult);
  171. *aNestingLevelResult = mEventloopNestingLevel;
  172. return NS_OK;
  173. }
  174. //-------------------------------------------------------------------------
  175. // nsIThreadObserver methods:
  176. // Called from any thread
  177. NS_IMETHODIMP
  178. nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
  179. {
  180. if (mBlockNativeEvent)
  181. return NS_OK;
  182. if (mNativeEventPending.exchange(true))
  183. return NS_OK;
  184. // Returns on the main thread in NativeEventCallback above
  185. ScheduleNativeEventCallback();
  186. return NS_OK;
  187. }
  188. // Called from the main thread
  189. NS_IMETHODIMP
  190. nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait)
  191. {
  192. if (mBlockNativeEvent) {
  193. if (!mayWait)
  194. return NS_OK;
  195. // Hmm, we're in a nested native event loop and would like to get
  196. // back to it ASAP, but it seems a gecko event has caused us to
  197. // spin up a nested XPCOM event loop (eg. modal window), so we
  198. // really must start processing native events here again.
  199. mBlockNativeEvent = false;
  200. if (NS_HasPendingEvents(thr))
  201. OnDispatchedEvent(thr); // in case we blocked it earlier
  202. }
  203. PRIntervalTime start = PR_IntervalNow();
  204. PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;
  205. // Unblock outer nested wait loop (below).
  206. if (mBlockedWait)
  207. *mBlockedWait = false;
  208. bool *oldBlockedWait = mBlockedWait;
  209. mBlockedWait = &mayWait;
  210. // When mayWait is true, we need to make sure that there is an event in the
  211. // thread's event queue before we return. Otherwise, the thread will block
  212. // on its event queue waiting for an event.
  213. bool needEvent = mayWait;
  214. // Reset prior to invoking DoProcessNextNativeEvent which might cause
  215. // NativeEventCallback to process gecko events.
  216. mProcessedGeckoEvents = false;
  217. if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
  218. // Favor pending native events
  219. PRIntervalTime now = start;
  220. bool keepGoing;
  221. do {
  222. mLastNativeEventTime = now;
  223. keepGoing = DoProcessNextNativeEvent(false);
  224. } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
  225. } else {
  226. // Avoid starving native events completely when in performance mode
  227. if (start - mLastNativeEventTime > limit) {
  228. mLastNativeEventTime = start;
  229. DoProcessNextNativeEvent(false);
  230. }
  231. }
  232. while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) {
  233. // If we have been asked to exit from Run, then we should not wait for
  234. // events to process. Note that an inner nested event loop causes
  235. // 'mayWait' to become false too, through 'mBlockedWait'.
  236. if (mExiting)
  237. mayWait = false;
  238. mLastNativeEventTime = PR_IntervalNow();
  239. if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
  240. break;
  241. }
  242. mBlockedWait = oldBlockedWait;
  243. // Make sure that the thread event queue does not block on its monitor, as
  244. // it normally would do if it did not have any pending events. To avoid
  245. // that, we simply insert a dummy event into its queue during shutdown.
  246. if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
  247. DispatchDummyEvent(thr);
  248. }
  249. return NS_OK;
  250. }
  251. bool
  252. nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget)
  253. {
  254. NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
  255. if (!mDummyEvent)
  256. mDummyEvent = new mozilla::Runnable();
  257. return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
  258. }
  259. void
  260. nsBaseAppShell::IncrementEventloopNestingLevel()
  261. {
  262. ++mEventloopNestingLevel;
  263. }
  264. void
  265. nsBaseAppShell::DecrementEventloopNestingLevel()
  266. {
  267. --mEventloopNestingLevel;
  268. }
  269. // Called from the main thread
  270. NS_IMETHODIMP
  271. nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
  272. bool eventWasProcessed)
  273. {
  274. return NS_OK;
  275. }
  276. NS_IMETHODIMP
  277. nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
  278. const char16_t *data)
  279. {
  280. NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops");
  281. Exit();
  282. return NS_OK;
  283. }