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