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