1 : /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Widget code.
16 : *
17 : * The Initial Developer of the Original Code is Google Inc.
18 : * Portions created by the Initial Developer are Copyright (C) 2006
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Darin Fisher <darin@meer.net> (original author)
23 : * Mats Palmgren <mats.palmgren@bredband.net>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "base/message_loop.h"
40 :
41 : #include "nsBaseAppShell.h"
42 : #include "nsThreadUtils.h"
43 : #include "nsIObserverService.h"
44 : #include "nsServiceManagerUtils.h"
45 : #include "mozilla/Services.h"
46 :
47 : // When processing the next thread event, the appshell may process native
48 : // events (if not in performance mode), which can result in suppressing the
49 : // next thread event for at most this many ticks:
50 : #define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(20)
51 :
52 1180423 : NS_IMPL_THREADSAFE_ISUPPORTS3(nsBaseAppShell, nsIAppShell, nsIThreadObserver,
53 : nsIObserver)
54 :
55 1387 : nsBaseAppShell::nsBaseAppShell()
56 : : mSuspendNativeCount(0)
57 : , mEventloopNestingLevel(0)
58 : , mBlockedWait(nsnull)
59 : , mFavorPerf(0)
60 : , mNativeEventPending(0)
61 : , mStarvationDelay(0)
62 : , mSwitchTime(0)
63 : , mLastNativeEventTime(0)
64 : , mEventloopNestingState(eEventloopNone)
65 : , mRunning(false)
66 : , mExiting(false)
67 1387 : , mBlockNativeEvent(false)
68 : {
69 1387 : }
70 :
71 2774 : nsBaseAppShell::~nsBaseAppShell()
72 : {
73 1387 : NS_ASSERTION(mSyncSections.Count() == 0, "Must have run all sync sections");
74 2774 : }
75 :
76 : nsresult
77 1387 : nsBaseAppShell::Init()
78 : {
79 : // Configure ourselves as an observer for the current thread:
80 :
81 : nsCOMPtr<nsIThreadInternal> threadInt =
82 2774 : do_QueryInterface(NS_GetCurrentThread());
83 1387 : NS_ENSURE_STATE(threadInt);
84 :
85 1387 : threadInt->SetObserver(this);
86 :
87 : nsCOMPtr<nsIObserverService> obsSvc =
88 2774 : mozilla::services::GetObserverService();
89 1387 : if (obsSvc)
90 1387 : obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
91 1387 : return NS_OK;
92 : }
93 :
94 : // Called by nsAppShell's native event callback
95 : void
96 81592 : nsBaseAppShell::NativeEventCallback()
97 : {
98 81592 : PRInt32 hasPending = PR_ATOMIC_SET(&mNativeEventPending, 0);
99 81592 : if (hasPending == 0)
100 0 : return;
101 :
102 : // If DoProcessNextNativeEvent is on the stack, then we assume that we can
103 : // just unwind and let nsThread::ProcessNextEvent process the next event.
104 : // However, if we are called from a nested native event loop (maybe via some
105 : // plug-in or library function), then go ahead and process Gecko events now.
106 81592 : if (mEventloopNestingState == eEventloopXPCOM) {
107 81592 : mEventloopNestingState = eEventloopOther;
108 : // XXX there is a tiny risk we will never get a new NativeEventCallback,
109 : // XXX see discussion in bug 389931.
110 81592 : return;
111 : }
112 :
113 : // nsBaseAppShell::Run is not being used to pump events, so this may be
114 : // our only opportunity to process pending gecko events.
115 :
116 0 : nsIThread *thread = NS_GetCurrentThread();
117 0 : bool prevBlockNativeEvent = mBlockNativeEvent;
118 0 : if (mEventloopNestingState == eEventloopOther) {
119 0 : if (!NS_HasPendingEvents(thread))
120 0 : return;
121 : // We're in a nested native event loop and have some gecko events to
122 : // process. While doing that we block processing native events from the
123 : // appshell - instead, we want to get back to the nested native event
124 : // loop ASAP (bug 420148).
125 0 : mBlockNativeEvent = true;
126 : }
127 :
128 0 : ++mEventloopNestingLevel;
129 0 : EventloopNestingState prevVal = mEventloopNestingState;
130 0 : NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
131 0 : mProcessedGeckoEvents = true;
132 0 : mEventloopNestingState = prevVal;
133 0 : mBlockNativeEvent = prevBlockNativeEvent;
134 :
135 : // Continue processing pending events later (we don't want to starve the
136 : // embedders event loop).
137 0 : if (NS_HasPendingEvents(thread))
138 0 : DoProcessMoreGeckoEvents();
139 :
140 0 : --mEventloopNestingLevel;
141 : }
142 :
143 : // Note, this is currently overidden on windows, see comments in nsAppShell for
144 : // details.
145 : void
146 0 : nsBaseAppShell::DoProcessMoreGeckoEvents()
147 : {
148 0 : OnDispatchedEvent(nsnull);
149 0 : }
150 :
151 :
152 : // Main thread via OnProcessNextEvent below
153 : bool
154 271073 : nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
155 : {
156 : // The next native event to be processed may trigger our NativeEventCallback,
157 : // in which case we do not want it to process any thread events since we'll
158 : // do that when this function returns.
159 : //
160 : // If the next native event is not our NativeEventCallback, then we may end
161 : // up recursing into this function.
162 : //
163 : // However, if the next native event is not our NativeEventCallback, but it
164 : // results in another native event loop, then our NativeEventCallback could
165 : // fire and it will see mEventloopNestingState as eEventloopOther.
166 : //
167 271073 : EventloopNestingState prevVal = mEventloopNestingState;
168 271073 : mEventloopNestingState = eEventloopXPCOM;
169 :
170 271073 : ++mEventloopNestingLevel;
171 271073 : bool result = ProcessNextNativeEvent(mayWait);
172 271073 : --mEventloopNestingLevel;
173 :
174 271073 : mEventloopNestingState = prevVal;
175 271073 : return result;
176 : }
177 :
178 : //-------------------------------------------------------------------------
179 : // nsIAppShell methods:
180 :
181 : NS_IMETHODIMP
182 0 : nsBaseAppShell::Run(void)
183 : {
184 0 : NS_ENSURE_STATE(!mRunning); // should not call Run twice
185 0 : mRunning = true;
186 :
187 0 : nsIThread *thread = NS_GetCurrentThread();
188 :
189 0 : MessageLoop::current()->Run();
190 :
191 0 : NS_ProcessPendingEvents(thread);
192 :
193 0 : mRunning = false;
194 0 : return NS_OK;
195 : }
196 :
197 : NS_IMETHODIMP
198 1387 : nsBaseAppShell::Exit(void)
199 : {
200 1387 : if (mRunning && !mExiting) {
201 0 : MessageLoop::current()->Quit();
202 : }
203 1387 : mExiting = true;
204 1387 : return NS_OK;
205 : }
206 :
207 : NS_IMETHODIMP
208 0 : nsBaseAppShell::FavorPerformanceHint(bool favorPerfOverStarvation,
209 : PRUint32 starvationDelay)
210 : {
211 0 : mStarvationDelay = PR_MillisecondsToInterval(starvationDelay);
212 0 : if (favorPerfOverStarvation) {
213 0 : ++mFavorPerf;
214 : } else {
215 0 : --mFavorPerf;
216 0 : mSwitchTime = PR_IntervalNow();
217 : }
218 0 : return NS_OK;
219 : }
220 :
221 : NS_IMETHODIMP
222 0 : nsBaseAppShell::SuspendNative()
223 : {
224 0 : ++mSuspendNativeCount;
225 0 : return NS_OK;
226 : }
227 :
228 : NS_IMETHODIMP
229 0 : nsBaseAppShell::ResumeNative()
230 : {
231 0 : --mSuspendNativeCount;
232 0 : NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!");
233 0 : return NS_OK;
234 : }
235 :
236 : NS_IMETHODIMP
237 0 : nsBaseAppShell::GetEventloopNestingLevel(PRUint32* aNestingLevelResult)
238 : {
239 0 : NS_ENSURE_ARG_POINTER(aNestingLevelResult);
240 :
241 0 : *aNestingLevelResult = mEventloopNestingLevel;
242 :
243 0 : return NS_OK;
244 : }
245 :
246 : //-------------------------------------------------------------------------
247 : // nsIThreadObserver methods:
248 :
249 : // Called from any thread
250 : NS_IMETHODIMP
251 162753 : nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
252 : {
253 162753 : if (mBlockNativeEvent)
254 0 : return NS_OK;
255 :
256 162753 : PRInt32 lastVal = PR_ATOMIC_SET(&mNativeEventPending, 1);
257 162753 : if (lastVal == 1)
258 81161 : return NS_OK;
259 :
260 : // Returns on the main thread in NativeEventCallback above
261 81592 : ScheduleNativeEventCallback();
262 81592 : return NS_OK;
263 : }
264 :
265 : // Called from the main thread
266 : NS_IMETHODIMP
267 174261 : nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
268 : PRUint32 recursionDepth)
269 : {
270 174261 : if (mBlockNativeEvent) {
271 0 : if (!mayWait)
272 0 : return NS_OK;
273 : // Hmm, we're in a nested native event loop and would like to get
274 : // back to it ASAP, but it seems a gecko event has caused us to
275 : // spin up a nested XPCOM event loop (eg. modal window), so we
276 : // really must start processing native events here again.
277 0 : mBlockNativeEvent = false;
278 0 : if (NS_HasPendingEvents(thr))
279 0 : OnDispatchedEvent(thr); // in case we blocked it earlier
280 : }
281 :
282 174261 : PRIntervalTime start = PR_IntervalNow();
283 174261 : PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;
284 :
285 : // Unblock outer nested wait loop (below).
286 174261 : if (mBlockedWait)
287 0 : *mBlockedWait = false;
288 :
289 174261 : bool *oldBlockedWait = mBlockedWait;
290 174261 : mBlockedWait = &mayWait;
291 :
292 : // When mayWait is true, we need to make sure that there is an event in the
293 : // thread's event queue before we return. Otherwise, the thread will block
294 : // on its event queue waiting for an event.
295 174261 : bool needEvent = mayWait;
296 : // Reset prior to invoking DoProcessNextNativeEvent which might cause
297 : // NativeEventCallback to process gecko events.
298 174261 : mProcessedGeckoEvents = false;
299 :
300 174261 : if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
301 : // Favor pending native events
302 174261 : PRIntervalTime now = start;
303 : bool keepGoing;
304 243747 : do {
305 243747 : mLastNativeEventTime = now;
306 243747 : keepGoing = DoProcessNextNativeEvent(false);
307 174261 : } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
308 : } else {
309 : // Avoid starving native events completely when in performance mode
310 0 : if (start - mLastNativeEventTime > limit) {
311 0 : mLastNativeEventTime = start;
312 0 : DoProcessNextNativeEvent(false);
313 : }
314 : }
315 :
316 362192 : while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) {
317 : // If we have been asked to exit from Run, then we should not wait for
318 : // events to process. Note that an inner nested event loop causes
319 : // 'mayWait' to become false too, through 'mBlockedWait'.
320 27326 : if (mExiting)
321 10319 : mayWait = false;
322 :
323 27326 : mLastNativeEventTime = PR_IntervalNow();
324 27326 : if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
325 13656 : break;
326 : }
327 :
328 174261 : mBlockedWait = oldBlockedWait;
329 :
330 : // Make sure that the thread event queue does not block on its monitor, as
331 : // it normally would do if it did not have any pending events. To avoid
332 : // that, we simply insert a dummy event into its queue during shutdown.
333 174261 : if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
334 0 : if (!mDummyEvent)
335 0 : mDummyEvent = new nsRunnable();
336 0 : thr->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL);
337 : }
338 :
339 : // We're about to run an event, so we're in a stable state.
340 174261 : RunSyncSections();
341 :
342 174261 : return NS_OK;
343 : }
344 :
345 : void
346 348522 : nsBaseAppShell::RunSyncSections()
347 : {
348 348522 : if (mSyncSections.Count() == 0) {
349 348522 : return;
350 : }
351 : // We've got synchronous sections awaiting a stable state. Run
352 : // all the synchronous sections. Note that a synchronous section could
353 : // add another synchronous section, so we don't remove elements from
354 : // mSyncSections until all sections have been run, else we'll screw up
355 : // our iteration.
356 0 : for (PRInt32 i = 0; i < mSyncSections.Count(); i++) {
357 0 : mSyncSections[i]->Run();
358 : }
359 0 : mSyncSections.Clear();
360 : }
361 :
362 : // Called from the main thread
363 : NS_IMETHODIMP
364 174261 : nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
365 : PRUint32 recursionDepth)
366 : {
367 : // We've just finished running an event, so we're in a stable state.
368 174261 : RunSyncSections();
369 174261 : return NS_OK;
370 : }
371 :
372 : NS_IMETHODIMP
373 1387 : nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
374 : const PRUnichar *data)
375 : {
376 1387 : NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops");
377 1387 : Exit();
378 1387 : return NS_OK;
379 : }
380 :
381 : NS_IMETHODIMP
382 0 : nsBaseAppShell::RunInStableState(nsIRunnable* aRunnable)
383 : {
384 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
385 : // Record the synchronous section, and run it with any others once
386 : // we reach a stable state.
387 0 : mSyncSections.AppendObject(aRunnable);
388 :
389 : // Ensure we've got a pending event, else the callbacks will never run.
390 0 : nsIThread* thread = NS_GetCurrentThread();
391 0 : if (!NS_HasPendingEvents(thread) &&
392 0 : NS_FAILED(thread->Dispatch(new nsRunnable(), NS_DISPATCH_NORMAL)))
393 : {
394 : // Failed to dispatch dummy event to cause sync sections to run, thread
395 : // is probably done processing events, just run the sync sections now.
396 0 : RunSyncSections();
397 : }
398 0 : return NS_OK;
399 : }
400 :
|