1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/xpwidgets/nsBaseAppShell.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,452 @@ 1.4 +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "base/message_loop.h" 1.10 + 1.11 +#include "nsBaseAppShell.h" 1.12 +#if defined(MOZ_CRASHREPORTER) 1.13 +#include "nsExceptionHandler.h" 1.14 +#endif 1.15 +#include "nsThreadUtils.h" 1.16 +#include "nsIObserverService.h" 1.17 +#include "nsServiceManagerUtils.h" 1.18 +#include "mozilla/Services.h" 1.19 + 1.20 +// When processing the next thread event, the appshell may process native 1.21 +// events (if not in performance mode), which can result in suppressing the 1.22 +// next thread event for at most this many ticks: 1.23 +#define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(20) 1.24 + 1.25 +NS_IMPL_ISUPPORTS(nsBaseAppShell, nsIAppShell, nsIThreadObserver, nsIObserver) 1.26 + 1.27 +nsBaseAppShell::nsBaseAppShell() 1.28 + : mSuspendNativeCount(0) 1.29 + , mEventloopNestingLevel(0) 1.30 + , mBlockedWait(nullptr) 1.31 + , mFavorPerf(0) 1.32 + , mNativeEventPending(false) 1.33 + , mStarvationDelay(0) 1.34 + , mSwitchTime(0) 1.35 + , mLastNativeEventTime(0) 1.36 + , mEventloopNestingState(eEventloopNone) 1.37 + , mRunning(false) 1.38 + , mExiting(false) 1.39 + , mBlockNativeEvent(false) 1.40 +{ 1.41 +} 1.42 + 1.43 +nsBaseAppShell::~nsBaseAppShell() 1.44 +{ 1.45 + NS_ASSERTION(mSyncSections.IsEmpty(), "Must have run all sync sections"); 1.46 +} 1.47 + 1.48 +nsresult 1.49 +nsBaseAppShell::Init() 1.50 +{ 1.51 + // Configure ourselves as an observer for the current thread: 1.52 + 1.53 + nsCOMPtr<nsIThreadInternal> threadInt = 1.54 + do_QueryInterface(NS_GetCurrentThread()); 1.55 + NS_ENSURE_STATE(threadInt); 1.56 + 1.57 + threadInt->SetObserver(this); 1.58 + 1.59 + nsCOMPtr<nsIObserverService> obsSvc = 1.60 + mozilla::services::GetObserverService(); 1.61 + if (obsSvc) 1.62 + obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 1.63 + return NS_OK; 1.64 +} 1.65 + 1.66 +// Called by nsAppShell's native event callback 1.67 +void 1.68 +nsBaseAppShell::NativeEventCallback() 1.69 +{ 1.70 + if (!mNativeEventPending.exchange(false)) 1.71 + return; 1.72 + 1.73 + // If DoProcessNextNativeEvent is on the stack, then we assume that we can 1.74 + // just unwind and let nsThread::ProcessNextEvent process the next event. 1.75 + // However, if we are called from a nested native event loop (maybe via some 1.76 + // plug-in or library function), then go ahead and process Gecko events now. 1.77 + if (mEventloopNestingState == eEventloopXPCOM) { 1.78 + mEventloopNestingState = eEventloopOther; 1.79 + // XXX there is a tiny risk we will never get a new NativeEventCallback, 1.80 + // XXX see discussion in bug 389931. 1.81 + return; 1.82 + } 1.83 + 1.84 + // nsBaseAppShell::Run is not being used to pump events, so this may be 1.85 + // our only opportunity to process pending gecko events. 1.86 + 1.87 + nsIThread *thread = NS_GetCurrentThread(); 1.88 + bool prevBlockNativeEvent = mBlockNativeEvent; 1.89 + if (mEventloopNestingState == eEventloopOther) { 1.90 + if (!NS_HasPendingEvents(thread)) 1.91 + return; 1.92 + // We're in a nested native event loop and have some gecko events to 1.93 + // process. While doing that we block processing native events from the 1.94 + // appshell - instead, we want to get back to the nested native event 1.95 + // loop ASAP (bug 420148). 1.96 + mBlockNativeEvent = true; 1.97 + } 1.98 + 1.99 + IncrementEventloopNestingLevel(); 1.100 + EventloopNestingState prevVal = mEventloopNestingState; 1.101 + NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT); 1.102 + mProcessedGeckoEvents = true; 1.103 + mEventloopNestingState = prevVal; 1.104 + mBlockNativeEvent = prevBlockNativeEvent; 1.105 + 1.106 + // Continue processing pending events later (we don't want to starve the 1.107 + // embedders event loop). 1.108 + if (NS_HasPendingEvents(thread)) 1.109 + DoProcessMoreGeckoEvents(); 1.110 + 1.111 + DecrementEventloopNestingLevel(); 1.112 +} 1.113 + 1.114 +// Note, this is currently overidden on windows, see comments in nsAppShell for 1.115 +// details. 1.116 +void 1.117 +nsBaseAppShell::DoProcessMoreGeckoEvents() 1.118 +{ 1.119 + OnDispatchedEvent(nullptr); 1.120 +} 1.121 + 1.122 + 1.123 +// Main thread via OnProcessNextEvent below 1.124 +bool 1.125 +nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait, uint32_t recursionDepth) 1.126 +{ 1.127 + // The next native event to be processed may trigger our NativeEventCallback, 1.128 + // in which case we do not want it to process any thread events since we'll 1.129 + // do that when this function returns. 1.130 + // 1.131 + // If the next native event is not our NativeEventCallback, then we may end 1.132 + // up recursing into this function. 1.133 + // 1.134 + // However, if the next native event is not our NativeEventCallback, but it 1.135 + // results in another native event loop, then our NativeEventCallback could 1.136 + // fire and it will see mEventloopNestingState as eEventloopOther. 1.137 + // 1.138 + EventloopNestingState prevVal = mEventloopNestingState; 1.139 + mEventloopNestingState = eEventloopXPCOM; 1.140 + 1.141 + IncrementEventloopNestingLevel(); 1.142 + 1.143 + bool result = ProcessNextNativeEvent(mayWait); 1.144 + 1.145 + // Make sure that any sync sections registered during this most recent event 1.146 + // are run now. This is not considered a stable state because we're not back 1.147 + // to the event loop yet. 1.148 + RunSyncSections(false, recursionDepth); 1.149 + 1.150 + DecrementEventloopNestingLevel(); 1.151 + 1.152 + mEventloopNestingState = prevVal; 1.153 + return result; 1.154 +} 1.155 + 1.156 +//------------------------------------------------------------------------- 1.157 +// nsIAppShell methods: 1.158 + 1.159 +NS_IMETHODIMP 1.160 +nsBaseAppShell::Run(void) 1.161 +{ 1.162 + NS_ENSURE_STATE(!mRunning); // should not call Run twice 1.163 + mRunning = true; 1.164 + 1.165 + nsIThread *thread = NS_GetCurrentThread(); 1.166 + 1.167 + MessageLoop::current()->Run(); 1.168 + 1.169 + NS_ProcessPendingEvents(thread); 1.170 + 1.171 + mRunning = false; 1.172 + return NS_OK; 1.173 +} 1.174 + 1.175 +NS_IMETHODIMP 1.176 +nsBaseAppShell::Exit(void) 1.177 +{ 1.178 + if (mRunning && !mExiting) { 1.179 + MessageLoop::current()->Quit(); 1.180 + } 1.181 + mExiting = true; 1.182 + return NS_OK; 1.183 +} 1.184 + 1.185 +NS_IMETHODIMP 1.186 +nsBaseAppShell::FavorPerformanceHint(bool favorPerfOverStarvation, 1.187 + uint32_t starvationDelay) 1.188 +{ 1.189 + mStarvationDelay = PR_MillisecondsToInterval(starvationDelay); 1.190 + if (favorPerfOverStarvation) { 1.191 + ++mFavorPerf; 1.192 + } else { 1.193 + --mFavorPerf; 1.194 + mSwitchTime = PR_IntervalNow(); 1.195 + } 1.196 + return NS_OK; 1.197 +} 1.198 + 1.199 +NS_IMETHODIMP 1.200 +nsBaseAppShell::SuspendNative() 1.201 +{ 1.202 + ++mSuspendNativeCount; 1.203 + return NS_OK; 1.204 +} 1.205 + 1.206 +NS_IMETHODIMP 1.207 +nsBaseAppShell::ResumeNative() 1.208 +{ 1.209 + --mSuspendNativeCount; 1.210 + NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!"); 1.211 + return NS_OK; 1.212 +} 1.213 + 1.214 +NS_IMETHODIMP 1.215 +nsBaseAppShell::GetEventloopNestingLevel(uint32_t* aNestingLevelResult) 1.216 +{ 1.217 + NS_ENSURE_ARG_POINTER(aNestingLevelResult); 1.218 + 1.219 + *aNestingLevelResult = mEventloopNestingLevel; 1.220 + 1.221 + return NS_OK; 1.222 +} 1.223 + 1.224 +//------------------------------------------------------------------------- 1.225 +// nsIThreadObserver methods: 1.226 + 1.227 +// Called from any thread 1.228 +NS_IMETHODIMP 1.229 +nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr) 1.230 +{ 1.231 + if (mBlockNativeEvent) 1.232 + return NS_OK; 1.233 + 1.234 + if (mNativeEventPending.exchange(true)) 1.235 + return NS_OK; 1.236 + 1.237 + // Returns on the main thread in NativeEventCallback above 1.238 + ScheduleNativeEventCallback(); 1.239 + return NS_OK; 1.240 +} 1.241 + 1.242 +// Called from the main thread 1.243 +NS_IMETHODIMP 1.244 +nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait, 1.245 + uint32_t recursionDepth) 1.246 +{ 1.247 + if (mBlockNativeEvent) { 1.248 + if (!mayWait) 1.249 + return NS_OK; 1.250 + // Hmm, we're in a nested native event loop and would like to get 1.251 + // back to it ASAP, but it seems a gecko event has caused us to 1.252 + // spin up a nested XPCOM event loop (eg. modal window), so we 1.253 + // really must start processing native events here again. 1.254 + mBlockNativeEvent = false; 1.255 + if (NS_HasPendingEvents(thr)) 1.256 + OnDispatchedEvent(thr); // in case we blocked it earlier 1.257 + } 1.258 + 1.259 + PRIntervalTime start = PR_IntervalNow(); 1.260 + PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT; 1.261 + 1.262 + // Unblock outer nested wait loop (below). 1.263 + if (mBlockedWait) 1.264 + *mBlockedWait = false; 1.265 + 1.266 + bool *oldBlockedWait = mBlockedWait; 1.267 + mBlockedWait = &mayWait; 1.268 + 1.269 + // When mayWait is true, we need to make sure that there is an event in the 1.270 + // thread's event queue before we return. Otherwise, the thread will block 1.271 + // on its event queue waiting for an event. 1.272 + bool needEvent = mayWait; 1.273 + // Reset prior to invoking DoProcessNextNativeEvent which might cause 1.274 + // NativeEventCallback to process gecko events. 1.275 + mProcessedGeckoEvents = false; 1.276 + 1.277 + if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) { 1.278 + // Favor pending native events 1.279 + PRIntervalTime now = start; 1.280 + bool keepGoing; 1.281 + do { 1.282 + mLastNativeEventTime = now; 1.283 + keepGoing = DoProcessNextNativeEvent(false, recursionDepth); 1.284 + } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit); 1.285 + } else { 1.286 + // Avoid starving native events completely when in performance mode 1.287 + if (start - mLastNativeEventTime > limit) { 1.288 + mLastNativeEventTime = start; 1.289 + DoProcessNextNativeEvent(false, recursionDepth); 1.290 + } 1.291 + } 1.292 + 1.293 + while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) { 1.294 + // If we have been asked to exit from Run, then we should not wait for 1.295 + // events to process. Note that an inner nested event loop causes 1.296 + // 'mayWait' to become false too, through 'mBlockedWait'. 1.297 + if (mExiting) 1.298 + mayWait = false; 1.299 + 1.300 + mLastNativeEventTime = PR_IntervalNow(); 1.301 + if (!DoProcessNextNativeEvent(mayWait, recursionDepth) || !mayWait) 1.302 + break; 1.303 + } 1.304 + 1.305 + mBlockedWait = oldBlockedWait; 1.306 + 1.307 + // Make sure that the thread event queue does not block on its monitor, as 1.308 + // it normally would do if it did not have any pending events. To avoid 1.309 + // that, we simply insert a dummy event into its queue during shutdown. 1.310 + if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) { 1.311 + DispatchDummyEvent(thr); 1.312 + } 1.313 + 1.314 + // We're about to run an event, so we're in a stable state. 1.315 + RunSyncSections(true, recursionDepth); 1.316 + 1.317 + return NS_OK; 1.318 +} 1.319 + 1.320 +bool 1.321 +nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget) 1.322 +{ 1.323 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.324 + 1.325 + if (!mDummyEvent) 1.326 + mDummyEvent = new nsRunnable(); 1.327 + 1.328 + return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL)); 1.329 +} 1.330 + 1.331 +void 1.332 +nsBaseAppShell::IncrementEventloopNestingLevel() 1.333 +{ 1.334 + ++mEventloopNestingLevel; 1.335 +#if defined(MOZ_CRASHREPORTER) 1.336 + CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel); 1.337 +#endif 1.338 +} 1.339 + 1.340 +void 1.341 +nsBaseAppShell::DecrementEventloopNestingLevel() 1.342 +{ 1.343 + --mEventloopNestingLevel; 1.344 +#if defined(MOZ_CRASHREPORTER) 1.345 + CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel); 1.346 +#endif 1.347 +} 1.348 + 1.349 +void 1.350 +nsBaseAppShell::RunSyncSectionsInternal(bool aStable, 1.351 + uint32_t aThreadRecursionLevel) 1.352 +{ 1.353 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.354 + NS_ASSERTION(!mSyncSections.IsEmpty(), "Nothing to do!"); 1.355 + 1.356 + // We've got synchronous sections. Run all of them that are are awaiting a 1.357 + // stable state if aStable is true (i.e. we really are in a stable state). 1.358 + // Also run the synchronous sections that are simply waiting for the right 1.359 + // combination of event loop nesting level and thread recursion level. 1.360 + // Note that a synchronous section could add another synchronous section, so 1.361 + // we don't remove elements from mSyncSections until all sections have been 1.362 + // run, or else we'll screw up our iteration. Any sync sections that are not 1.363 + // ready to be run are saved for later. 1.364 + 1.365 + nsTArray<SyncSection> pendingSyncSections; 1.366 + 1.367 + for (uint32_t i = 0; i < mSyncSections.Length(); i++) { 1.368 + SyncSection& section = mSyncSections[i]; 1.369 + if ((aStable && section.mStable) || 1.370 + (!section.mStable && 1.371 + section.mEventloopNestingLevel == mEventloopNestingLevel && 1.372 + section.mThreadRecursionLevel == aThreadRecursionLevel)) { 1.373 + section.mRunnable->Run(); 1.374 + } 1.375 + else { 1.376 + // Add to pending list. 1.377 + SyncSection* pending = pendingSyncSections.AppendElement(); 1.378 + section.Forget(pending); 1.379 + } 1.380 + } 1.381 + 1.382 + mSyncSections.SwapElements(pendingSyncSections); 1.383 +} 1.384 + 1.385 +void 1.386 +nsBaseAppShell::ScheduleSyncSection(nsIRunnable* aRunnable, bool aStable) 1.387 +{ 1.388 + NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); 1.389 + 1.390 + nsIThread* thread = NS_GetCurrentThread(); 1.391 + 1.392 + // Add this runnable to our list of synchronous sections. 1.393 + SyncSection* section = mSyncSections.AppendElement(); 1.394 + section->mStable = aStable; 1.395 + section->mRunnable = aRunnable; 1.396 + 1.397 + // If aStable is false then this synchronous section is supposed to run before 1.398 + // the next event at the current nesting level. Record the event loop nesting 1.399 + // level and the thread recursion level so that the synchronous section will 1.400 + // run at the proper time. 1.401 + if (!aStable) { 1.402 + section->mEventloopNestingLevel = mEventloopNestingLevel; 1.403 + 1.404 + nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread); 1.405 + NS_ASSERTION(threadInternal, "This should never fail!"); 1.406 + 1.407 + uint32_t recursionLevel; 1.408 + if (NS_FAILED(threadInternal->GetRecursionDepth(&recursionLevel))) { 1.409 + NS_ERROR("This should never fail!"); 1.410 + } 1.411 + 1.412 + // Due to the weird way that the thread recursion counter is implemented we 1.413 + // subtract one from the recursion level if we have one. 1.414 + section->mThreadRecursionLevel = recursionLevel ? recursionLevel - 1 : 0; 1.415 + } 1.416 + 1.417 + // Ensure we've got a pending event, else the callbacks will never run. 1.418 + if (!NS_HasPendingEvents(thread) && !DispatchDummyEvent(thread)) { 1.419 + RunSyncSections(true, 0); 1.420 + } 1.421 +} 1.422 + 1.423 +// Called from the main thread 1.424 +NS_IMETHODIMP 1.425 +nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr, 1.426 + uint32_t recursionDepth, 1.427 + bool eventWasProcessed) 1.428 +{ 1.429 + // We've just finished running an event, so we're in a stable state. 1.430 + RunSyncSections(true, recursionDepth); 1.431 + return NS_OK; 1.432 +} 1.433 + 1.434 +NS_IMETHODIMP 1.435 +nsBaseAppShell::Observe(nsISupports *subject, const char *topic, 1.436 + const char16_t *data) 1.437 +{ 1.438 + NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops"); 1.439 + Exit(); 1.440 + return NS_OK; 1.441 +} 1.442 + 1.443 +NS_IMETHODIMP 1.444 +nsBaseAppShell::RunInStableState(nsIRunnable* aRunnable) 1.445 +{ 1.446 + ScheduleSyncSection(aRunnable, true); 1.447 + return NS_OK; 1.448 +} 1.449 + 1.450 +NS_IMETHODIMP 1.451 +nsBaseAppShell::RunBeforeNextEvent(nsIRunnable* aRunnable) 1.452 +{ 1.453 + ScheduleSyncSection(aRunnable, false); 1.454 + return NS_OK; 1.455 +}