widget/xpwidgets/nsBaseAppShell.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     6 #include "base/message_loop.h"
     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"
    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)
    22 NS_IMPL_ISUPPORTS(nsBaseAppShell, nsIAppShell, nsIThreadObserver, nsIObserver)
    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 }
    40 nsBaseAppShell::~nsBaseAppShell()
    41 {
    42   NS_ASSERTION(mSyncSections.IsEmpty(), "Must have run all sync sections");
    43 }
    45 nsresult
    46 nsBaseAppShell::Init()
    47 {
    48   // Configure ourselves as an observer for the current thread:
    50   nsCOMPtr<nsIThreadInternal> threadInt =
    51       do_QueryInterface(NS_GetCurrentThread());
    52   NS_ENSURE_STATE(threadInt);
    54   threadInt->SetObserver(this);
    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 }
    63 // Called by nsAppShell's native event callback
    64 void
    65 nsBaseAppShell::NativeEventCallback()
    66 {
    67   if (!mNativeEventPending.exchange(false))
    68     return;
    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   }
    81   // nsBaseAppShell::Run is not being used to pump events, so this may be
    82   // our only opportunity to process pending gecko events.
    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   }
    96   IncrementEventloopNestingLevel();
    97   EventloopNestingState prevVal = mEventloopNestingState;
    98   NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
    99   mProcessedGeckoEvents = true;
   100   mEventloopNestingState = prevVal;
   101   mBlockNativeEvent = prevBlockNativeEvent;
   103   // Continue processing pending events later (we don't want to starve the
   104   // embedders event loop).
   105   if (NS_HasPendingEvents(thread))
   106     DoProcessMoreGeckoEvents();
   108   DecrementEventloopNestingLevel();
   109 }
   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 }
   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;
   138   IncrementEventloopNestingLevel();
   140   bool result = ProcessNextNativeEvent(mayWait);
   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);
   147   DecrementEventloopNestingLevel();
   149   mEventloopNestingState = prevVal;
   150   return result;
   151 }
   153 //-------------------------------------------------------------------------
   154 // nsIAppShell methods:
   156 NS_IMETHODIMP
   157 nsBaseAppShell::Run(void)
   158 {
   159   NS_ENSURE_STATE(!mRunning);  // should not call Run twice
   160   mRunning = true;
   162   nsIThread *thread = NS_GetCurrentThread();
   164   MessageLoop::current()->Run();
   166   NS_ProcessPendingEvents(thread);
   168   mRunning = false;
   169   return NS_OK;
   170 }
   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 }
   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 }
   196 NS_IMETHODIMP
   197 nsBaseAppShell::SuspendNative()
   198 {
   199   ++mSuspendNativeCount;
   200   return NS_OK;
   201 }
   203 NS_IMETHODIMP
   204 nsBaseAppShell::ResumeNative()
   205 {
   206   --mSuspendNativeCount;
   207   NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!");
   208   return NS_OK;
   209 }
   211 NS_IMETHODIMP
   212 nsBaseAppShell::GetEventloopNestingLevel(uint32_t* aNestingLevelResult)
   213 {
   214   NS_ENSURE_ARG_POINTER(aNestingLevelResult);
   216   *aNestingLevelResult = mEventloopNestingLevel;
   218   return NS_OK;
   219 }
   221 //-------------------------------------------------------------------------
   222 // nsIThreadObserver methods:
   224 // Called from any thread
   225 NS_IMETHODIMP
   226 nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
   227 {
   228   if (mBlockNativeEvent)
   229     return NS_OK;
   231   if (mNativeEventPending.exchange(true))
   232     return NS_OK;
   234   // Returns on the main thread in NativeEventCallback above
   235   ScheduleNativeEventCallback();
   236   return NS_OK;
   237 }
   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   }
   256   PRIntervalTime start = PR_IntervalNow();
   257   PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;
   259   // Unblock outer nested wait loop (below).
   260   if (mBlockedWait)
   261     *mBlockedWait = false;
   263   bool *oldBlockedWait = mBlockedWait;
   264   mBlockedWait = &mayWait;
   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;
   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   }
   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;
   297     mLastNativeEventTime = PR_IntervalNow();
   298     if (!DoProcessNextNativeEvent(mayWait, recursionDepth) || !mayWait)
   299       break;
   300   }
   302   mBlockedWait = oldBlockedWait;
   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   }
   311   // We're about to run an event, so we're in a stable state.
   312   RunSyncSections(true, recursionDepth);
   314   return NS_OK;
   315 }
   317 bool
   318 nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget)
   319 {
   320   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   322   if (!mDummyEvent)
   323     mDummyEvent = new nsRunnable();
   325   return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
   326 }
   328 void
   329 nsBaseAppShell::IncrementEventloopNestingLevel()
   330 {
   331   ++mEventloopNestingLevel;
   332 #if defined(MOZ_CRASHREPORTER)
   333   CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
   334 #endif
   335 }
   337 void
   338 nsBaseAppShell::DecrementEventloopNestingLevel()
   339 {
   340   --mEventloopNestingLevel;
   341 #if defined(MOZ_CRASHREPORTER)
   342   CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
   343 #endif
   344 }
   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!");
   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.
   362   nsTArray<SyncSection> pendingSyncSections;
   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   }
   379   mSyncSections.SwapElements(pendingSyncSections);
   380 }
   382 void
   383 nsBaseAppShell::ScheduleSyncSection(nsIRunnable* aRunnable, bool aStable)
   384 {
   385   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   387   nsIThread* thread = NS_GetCurrentThread();
   389   // Add this runnable to our list of synchronous sections.
   390   SyncSection* section = mSyncSections.AppendElement();
   391   section->mStable = aStable;
   392   section->mRunnable = aRunnable;
   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;
   401     nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
   402     NS_ASSERTION(threadInternal, "This should never fail!");
   404     uint32_t recursionLevel;
   405     if (NS_FAILED(threadInternal->GetRecursionDepth(&recursionLevel))) {
   406       NS_ERROR("This should never fail!");
   407     }
   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   }
   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 }
   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 }
   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 }
   440 NS_IMETHODIMP
   441 nsBaseAppShell::RunInStableState(nsIRunnable* aRunnable)
   442 {
   443   ScheduleSyncSection(aRunnable, true);
   444   return NS_OK;
   445 }
   447 NS_IMETHODIMP
   448 nsBaseAppShell::RunBeforeNextEvent(nsIRunnable* aRunnable)
   449 {
   450   ScheduleSyncSection(aRunnable, false);
   451   return NS_OK;
   452 }

mercurial