|
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(20) |
|
21 |
|
22 NS_IMPL_ISUPPORTS(nsBaseAppShell, nsIAppShell, nsIThreadObserver, nsIObserver) |
|
23 |
|
24 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 , mBlockNativeEvent(false) |
|
37 { |
|
38 } |
|
39 |
|
40 nsBaseAppShell::~nsBaseAppShell() |
|
41 { |
|
42 NS_ASSERTION(mSyncSections.IsEmpty(), "Must have run all sync sections"); |
|
43 } |
|
44 |
|
45 nsresult |
|
46 nsBaseAppShell::Init() |
|
47 { |
|
48 // Configure ourselves as an observer for the current thread: |
|
49 |
|
50 nsCOMPtr<nsIThreadInternal> threadInt = |
|
51 do_QueryInterface(NS_GetCurrentThread()); |
|
52 NS_ENSURE_STATE(threadInt); |
|
53 |
|
54 threadInt->SetObserver(this); |
|
55 |
|
56 nsCOMPtr<nsIObserverService> obsSvc = |
|
57 mozilla::services::GetObserverService(); |
|
58 if (obsSvc) |
|
59 obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); |
|
60 return NS_OK; |
|
61 } |
|
62 |
|
63 // Called by nsAppShell's native event callback |
|
64 void |
|
65 nsBaseAppShell::NativeEventCallback() |
|
66 { |
|
67 if (!mNativeEventPending.exchange(false)) |
|
68 return; |
|
69 |
|
70 // If DoProcessNextNativeEvent is on the stack, then we assume that we can |
|
71 // just unwind and let nsThread::ProcessNextEvent process the next event. |
|
72 // However, if we are called from a nested native event loop (maybe via some |
|
73 // plug-in or library function), then go ahead and process Gecko events now. |
|
74 if (mEventloopNestingState == eEventloopXPCOM) { |
|
75 mEventloopNestingState = eEventloopOther; |
|
76 // XXX there is a tiny risk we will never get a new NativeEventCallback, |
|
77 // XXX see discussion in bug 389931. |
|
78 return; |
|
79 } |
|
80 |
|
81 // nsBaseAppShell::Run is not being used to pump events, so this may be |
|
82 // our only opportunity to process pending gecko events. |
|
83 |
|
84 nsIThread *thread = NS_GetCurrentThread(); |
|
85 bool prevBlockNativeEvent = mBlockNativeEvent; |
|
86 if (mEventloopNestingState == eEventloopOther) { |
|
87 if (!NS_HasPendingEvents(thread)) |
|
88 return; |
|
89 // We're in a nested native event loop and have some gecko events to |
|
90 // process. While doing that we block processing native events from the |
|
91 // appshell - instead, we want to get back to the nested native event |
|
92 // loop ASAP (bug 420148). |
|
93 mBlockNativeEvent = true; |
|
94 } |
|
95 |
|
96 IncrementEventloopNestingLevel(); |
|
97 EventloopNestingState prevVal = mEventloopNestingState; |
|
98 NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT); |
|
99 mProcessedGeckoEvents = true; |
|
100 mEventloopNestingState = prevVal; |
|
101 mBlockNativeEvent = prevBlockNativeEvent; |
|
102 |
|
103 // Continue processing pending events later (we don't want to starve the |
|
104 // embedders event loop). |
|
105 if (NS_HasPendingEvents(thread)) |
|
106 DoProcessMoreGeckoEvents(); |
|
107 |
|
108 DecrementEventloopNestingLevel(); |
|
109 } |
|
110 |
|
111 // Note, this is currently overidden on windows, see comments in nsAppShell for |
|
112 // details. |
|
113 void |
|
114 nsBaseAppShell::DoProcessMoreGeckoEvents() |
|
115 { |
|
116 OnDispatchedEvent(nullptr); |
|
117 } |
|
118 |
|
119 |
|
120 // Main thread via OnProcessNextEvent below |
|
121 bool |
|
122 nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth) |
|
123 { |
|
124 // The next native event to be processed may trigger our NativeEventCallback, |
|
125 // in which case we do not want it to process any thread events since we'll |
|
126 // do that when this function returns. |
|
127 // |
|
128 // If the next native event is not our NativeEventCallback, then we may end |
|
129 // up recursing into this function. |
|
130 // |
|
131 // However, if the next native event is not our NativeEventCallback, but it |
|
132 // results in another native event loop, then our NativeEventCallback could |
|
133 // fire and it will see mEventloopNestingState as eEventloopOther. |
|
134 // |
|
135 EventloopNestingState prevVal = mEventloopNestingState; |
|
136 mEventloopNestingState = eEventloopXPCOM; |
|
137 |
|
138 IncrementEventloopNestingLevel(); |
|
139 |
|
140 bool result = ProcessNextNativeEvent(mayWait); |
|
141 |
|
142 // Make sure that any sync sections registered during this most recent event |
|
143 // are run now. This is not considered a stable state because we're not back |
|
144 // to the event loop yet. |
|
145 RunSyncSections(false, recursionDepth); |
|
146 |
|
147 DecrementEventloopNestingLevel(); |
|
148 |
|
149 mEventloopNestingState = prevVal; |
|
150 return result; |
|
151 } |
|
152 |
|
153 //------------------------------------------------------------------------- |
|
154 // nsIAppShell methods: |
|
155 |
|
156 NS_IMETHODIMP |
|
157 nsBaseAppShell::Run(void) |
|
158 { |
|
159 NS_ENSURE_STATE(!mRunning); // should not call Run twice |
|
160 mRunning = true; |
|
161 |
|
162 nsIThread *thread = NS_GetCurrentThread(); |
|
163 |
|
164 MessageLoop::current()->Run(); |
|
165 |
|
166 NS_ProcessPendingEvents(thread); |
|
167 |
|
168 mRunning = false; |
|
169 return NS_OK; |
|
170 } |
|
171 |
|
172 NS_IMETHODIMP |
|
173 nsBaseAppShell::Exit(void) |
|
174 { |
|
175 if (mRunning && !mExiting) { |
|
176 MessageLoop::current()->Quit(); |
|
177 } |
|
178 mExiting = true; |
|
179 return NS_OK; |
|
180 } |
|
181 |
|
182 NS_IMETHODIMP |
|
183 nsBaseAppShell::FavorPerformanceHint(bool favorPerfOverStarvation, |
|
184 uint32_t starvationDelay) |
|
185 { |
|
186 mStarvationDelay = PR_MillisecondsToInterval(starvationDelay); |
|
187 if (favorPerfOverStarvation) { |
|
188 ++mFavorPerf; |
|
189 } else { |
|
190 --mFavorPerf; |
|
191 mSwitchTime = PR_IntervalNow(); |
|
192 } |
|
193 return NS_OK; |
|
194 } |
|
195 |
|
196 NS_IMETHODIMP |
|
197 nsBaseAppShell::SuspendNative() |
|
198 { |
|
199 ++mSuspendNativeCount; |
|
200 return NS_OK; |
|
201 } |
|
202 |
|
203 NS_IMETHODIMP |
|
204 nsBaseAppShell::ResumeNative() |
|
205 { |
|
206 --mSuspendNativeCount; |
|
207 NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!"); |
|
208 return NS_OK; |
|
209 } |
|
210 |
|
211 NS_IMETHODIMP |
|
212 nsBaseAppShell::GetEventloopNestingLevel(uint32_t* aNestingLevelResult) |
|
213 { |
|
214 NS_ENSURE_ARG_POINTER(aNestingLevelResult); |
|
215 |
|
216 *aNestingLevelResult = mEventloopNestingLevel; |
|
217 |
|
218 return NS_OK; |
|
219 } |
|
220 |
|
221 //------------------------------------------------------------------------- |
|
222 // nsIThreadObserver methods: |
|
223 |
|
224 // Called from any thread |
|
225 NS_IMETHODIMP |
|
226 nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr) |
|
227 { |
|
228 if (mBlockNativeEvent) |
|
229 return NS_OK; |
|
230 |
|
231 if (mNativeEventPending.exchange(true)) |
|
232 return NS_OK; |
|
233 |
|
234 // Returns on the main thread in NativeEventCallback above |
|
235 ScheduleNativeEventCallback(); |
|
236 return NS_OK; |
|
237 } |
|
238 |
|
239 // Called from the main thread |
|
240 NS_IMETHODIMP |
|
241 nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait, |
|
242 uint32_t recursionDepth) |
|
243 { |
|
244 if (mBlockNativeEvent) { |
|
245 if (!mayWait) |
|
246 return NS_OK; |
|
247 // Hmm, we're in a nested native event loop and would like to get |
|
248 // back to it ASAP, but it seems a gecko event has caused us to |
|
249 // spin up a nested XPCOM event loop (eg. modal window), so we |
|
250 // really must start processing native events here again. |
|
251 mBlockNativeEvent = false; |
|
252 if (NS_HasPendingEvents(thr)) |
|
253 OnDispatchedEvent(thr); // in case we blocked it earlier |
|
254 } |
|
255 |
|
256 PRIntervalTime start = PR_IntervalNow(); |
|
257 PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT; |
|
258 |
|
259 // Unblock outer nested wait loop (below). |
|
260 if (mBlockedWait) |
|
261 *mBlockedWait = false; |
|
262 |
|
263 bool *oldBlockedWait = mBlockedWait; |
|
264 mBlockedWait = &mayWait; |
|
265 |
|
266 // When mayWait is true, we need to make sure that there is an event in the |
|
267 // thread's event queue before we return. Otherwise, the thread will block |
|
268 // on its event queue waiting for an event. |
|
269 bool needEvent = mayWait; |
|
270 // Reset prior to invoking DoProcessNextNativeEvent which might cause |
|
271 // NativeEventCallback to process gecko events. |
|
272 mProcessedGeckoEvents = false; |
|
273 |
|
274 if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) { |
|
275 // Favor pending native events |
|
276 PRIntervalTime now = start; |
|
277 bool keepGoing; |
|
278 do { |
|
279 mLastNativeEventTime = now; |
|
280 keepGoing = DoProcessNextNativeEvent(false, recursionDepth); |
|
281 } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit); |
|
282 } else { |
|
283 // Avoid starving native events completely when in performance mode |
|
284 if (start - mLastNativeEventTime > limit) { |
|
285 mLastNativeEventTime = start; |
|
286 DoProcessNextNativeEvent(false, recursionDepth); |
|
287 } |
|
288 } |
|
289 |
|
290 while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) { |
|
291 // If we have been asked to exit from Run, then we should not wait for |
|
292 // events to process. Note that an inner nested event loop causes |
|
293 // 'mayWait' to become false too, through 'mBlockedWait'. |
|
294 if (mExiting) |
|
295 mayWait = false; |
|
296 |
|
297 mLastNativeEventTime = PR_IntervalNow(); |
|
298 if (!DoProcessNextNativeEvent(mayWait, recursionDepth) || !mayWait) |
|
299 break; |
|
300 } |
|
301 |
|
302 mBlockedWait = oldBlockedWait; |
|
303 |
|
304 // Make sure that the thread event queue does not block on its monitor, as |
|
305 // it normally would do if it did not have any pending events. To avoid |
|
306 // that, we simply insert a dummy event into its queue during shutdown. |
|
307 if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) { |
|
308 DispatchDummyEvent(thr); |
|
309 } |
|
310 |
|
311 // We're about to run an event, so we're in a stable state. |
|
312 RunSyncSections(true, recursionDepth); |
|
313 |
|
314 return NS_OK; |
|
315 } |
|
316 |
|
317 bool |
|
318 nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget) |
|
319 { |
|
320 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
321 |
|
322 if (!mDummyEvent) |
|
323 mDummyEvent = new nsRunnable(); |
|
324 |
|
325 return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL)); |
|
326 } |
|
327 |
|
328 void |
|
329 nsBaseAppShell::IncrementEventloopNestingLevel() |
|
330 { |
|
331 ++mEventloopNestingLevel; |
|
332 #if defined(MOZ_CRASHREPORTER) |
|
333 CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel); |
|
334 #endif |
|
335 } |
|
336 |
|
337 void |
|
338 nsBaseAppShell::DecrementEventloopNestingLevel() |
|
339 { |
|
340 --mEventloopNestingLevel; |
|
341 #if defined(MOZ_CRASHREPORTER) |
|
342 CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel); |
|
343 #endif |
|
344 } |
|
345 |
|
346 void |
|
347 nsBaseAppShell::RunSyncSectionsInternal(bool aStable, |
|
348 uint32_t aThreadRecursionLevel) |
|
349 { |
|
350 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
351 NS_ASSERTION(!mSyncSections.IsEmpty(), "Nothing to do!"); |
|
352 |
|
353 // We've got synchronous sections. Run all of them that are are awaiting a |
|
354 // stable state if aStable is true (i.e. we really are in a stable state). |
|
355 // Also run the synchronous sections that are simply waiting for the right |
|
356 // combination of event loop nesting level and thread recursion level. |
|
357 // Note that a synchronous section could add another synchronous section, so |
|
358 // we don't remove elements from mSyncSections until all sections have been |
|
359 // run, or else we'll screw up our iteration. Any sync sections that are not |
|
360 // ready to be run are saved for later. |
|
361 |
|
362 nsTArray<SyncSection> pendingSyncSections; |
|
363 |
|
364 for (uint32_t i = 0; i < mSyncSections.Length(); i++) { |
|
365 SyncSection& section = mSyncSections[i]; |
|
366 if ((aStable && section.mStable) || |
|
367 (!section.mStable && |
|
368 section.mEventloopNestingLevel == mEventloopNestingLevel && |
|
369 section.mThreadRecursionLevel == aThreadRecursionLevel)) { |
|
370 section.mRunnable->Run(); |
|
371 } |
|
372 else { |
|
373 // Add to pending list. |
|
374 SyncSection* pending = pendingSyncSections.AppendElement(); |
|
375 section.Forget(pending); |
|
376 } |
|
377 } |
|
378 |
|
379 mSyncSections.SwapElements(pendingSyncSections); |
|
380 } |
|
381 |
|
382 void |
|
383 nsBaseAppShell::ScheduleSyncSection(nsIRunnable* aRunnable, bool aStable) |
|
384 { |
|
385 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); |
|
386 |
|
387 nsIThread* thread = NS_GetCurrentThread(); |
|
388 |
|
389 // Add this runnable to our list of synchronous sections. |
|
390 SyncSection* section = mSyncSections.AppendElement(); |
|
391 section->mStable = aStable; |
|
392 section->mRunnable = aRunnable; |
|
393 |
|
394 // If aStable is false then this synchronous section is supposed to run before |
|
395 // the next event at the current nesting level. Record the event loop nesting |
|
396 // level and the thread recursion level so that the synchronous section will |
|
397 // run at the proper time. |
|
398 if (!aStable) { |
|
399 section->mEventloopNestingLevel = mEventloopNestingLevel; |
|
400 |
|
401 nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread); |
|
402 NS_ASSERTION(threadInternal, "This should never fail!"); |
|
403 |
|
404 uint32_t recursionLevel; |
|
405 if (NS_FAILED(threadInternal->GetRecursionDepth(&recursionLevel))) { |
|
406 NS_ERROR("This should never fail!"); |
|
407 } |
|
408 |
|
409 // Due to the weird way that the thread recursion counter is implemented we |
|
410 // subtract one from the recursion level if we have one. |
|
411 section->mThreadRecursionLevel = recursionLevel ? recursionLevel - 1 : 0; |
|
412 } |
|
413 |
|
414 // Ensure we've got a pending event, else the callbacks will never run. |
|
415 if (!NS_HasPendingEvents(thread) && !DispatchDummyEvent(thread)) { |
|
416 RunSyncSections(true, 0); |
|
417 } |
|
418 } |
|
419 |
|
420 // Called from the main thread |
|
421 NS_IMETHODIMP |
|
422 nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr, |
|
423 uint32_t recursionDepth, |
|
424 bool eventWasProcessed) |
|
425 { |
|
426 // We've just finished running an event, so we're in a stable state. |
|
427 RunSyncSections(true, recursionDepth); |
|
428 return NS_OK; |
|
429 } |
|
430 |
|
431 NS_IMETHODIMP |
|
432 nsBaseAppShell::Observe(nsISupports *subject, const char *topic, |
|
433 const char16_t *data) |
|
434 { |
|
435 NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops"); |
|
436 Exit(); |
|
437 return NS_OK; |
|
438 } |
|
439 |
|
440 NS_IMETHODIMP |
|
441 nsBaseAppShell::RunInStableState(nsIRunnable* aRunnable) |
|
442 { |
|
443 ScheduleSyncSection(aRunnable, true); |
|
444 return NS_OK; |
|
445 } |
|
446 |
|
447 NS_IMETHODIMP |
|
448 nsBaseAppShell::RunBeforeNextEvent(nsIRunnable* aRunnable) |
|
449 { |
|
450 ScheduleSyncSection(aRunnable, false); |
|
451 return NS_OK; |
|
452 } |