summaryrefslogtreecommitdiff
path: root/widget/nsBaseAppShell.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/nsBaseAppShell.cpp')
-rw-r--r--widget/nsBaseAppShell.cpp349
1 files changed, 349 insertions, 0 deletions
diff --git a/widget/nsBaseAppShell.cpp b/widget/nsBaseAppShell.cpp
new file mode 100644
index 0000000000..1557498b71
--- /dev/null
+++ b/widget/nsBaseAppShell.cpp
@@ -0,0 +1,349 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/message_loop.h"
+
+#include "nsBaseAppShell.h"
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#endif
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+// When processing the next thread event, the appshell may process native
+// events (if not in performance mode), which can result in suppressing the
+// next thread event for at most this many ticks:
+#define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(10)
+
+NS_IMPL_ISUPPORTS(nsBaseAppShell, nsIAppShell, nsIThreadObserver, nsIObserver)
+
+nsBaseAppShell::nsBaseAppShell()
+ : mSuspendNativeCount(0)
+ , mEventloopNestingLevel(0)
+ , mBlockedWait(nullptr)
+ , mFavorPerf(0)
+ , mNativeEventPending(false)
+ , mStarvationDelay(0)
+ , mSwitchTime(0)
+ , mLastNativeEventTime(0)
+ , mEventloopNestingState(eEventloopNone)
+ , mRunning(false)
+ , mExiting(false)
+ , mBlockNativeEvent(false)
+{
+}
+
+nsBaseAppShell::~nsBaseAppShell()
+{
+}
+
+nsresult
+nsBaseAppShell::Init()
+{
+ // Configure ourselves as an observer for the current thread:
+
+ nsCOMPtr<nsIThreadInternal> threadInt =
+ do_QueryInterface(NS_GetCurrentThread());
+ NS_ENSURE_STATE(threadInt);
+
+ threadInt->SetObserver(this);
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc)
+ obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ return NS_OK;
+}
+
+// Called by nsAppShell's native event callback
+void
+nsBaseAppShell::NativeEventCallback()
+{
+ if (!mNativeEventPending.exchange(false))
+ return;
+
+ // If DoProcessNextNativeEvent is on the stack, then we assume that we can
+ // just unwind and let nsThread::ProcessNextEvent process the next event.
+ // However, if we are called from a nested native event loop (maybe via some
+ // plug-in or library function), then go ahead and process Gecko events now.
+ if (mEventloopNestingState == eEventloopXPCOM) {
+ mEventloopNestingState = eEventloopOther;
+ // XXX there is a tiny risk we will never get a new NativeEventCallback,
+ // XXX see discussion in bug 389931.
+ return;
+ }
+
+ // nsBaseAppShell::Run is not being used to pump events, so this may be
+ // our only opportunity to process pending gecko events.
+
+ nsIThread *thread = NS_GetCurrentThread();
+ bool prevBlockNativeEvent = mBlockNativeEvent;
+ if (mEventloopNestingState == eEventloopOther) {
+ if (!NS_HasPendingEvents(thread))
+ return;
+ // We're in a nested native event loop and have some gecko events to
+ // process. While doing that we block processing native events from the
+ // appshell - instead, we want to get back to the nested native event
+ // loop ASAP (bug 420148).
+ mBlockNativeEvent = true;
+ }
+
+ IncrementEventloopNestingLevel();
+ EventloopNestingState prevVal = mEventloopNestingState;
+ NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
+ mProcessedGeckoEvents = true;
+ mEventloopNestingState = prevVal;
+ mBlockNativeEvent = prevBlockNativeEvent;
+
+ // Continue processing pending events later (we don't want to starve the
+ // embedders event loop).
+ if (NS_HasPendingEvents(thread))
+ DoProcessMoreGeckoEvents();
+
+ DecrementEventloopNestingLevel();
+}
+
+// Note, this is currently overidden on windows, see comments in nsAppShell for
+// details.
+void
+nsBaseAppShell::DoProcessMoreGeckoEvents()
+{
+ OnDispatchedEvent(nullptr);
+}
+
+
+// Main thread via OnProcessNextEvent below
+bool
+nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
+{
+ // The next native event to be processed may trigger our NativeEventCallback,
+ // in which case we do not want it to process any thread events since we'll
+ // do that when this function returns.
+ //
+ // If the next native event is not our NativeEventCallback, then we may end
+ // up recursing into this function.
+ //
+ // However, if the next native event is not our NativeEventCallback, but it
+ // results in another native event loop, then our NativeEventCallback could
+ // fire and it will see mEventloopNestingState as eEventloopOther.
+ //
+ EventloopNestingState prevVal = mEventloopNestingState;
+ mEventloopNestingState = eEventloopXPCOM;
+
+ IncrementEventloopNestingLevel();
+ bool result = ProcessNextNativeEvent(mayWait);
+ DecrementEventloopNestingLevel();
+
+ mEventloopNestingState = prevVal;
+ return result;
+}
+
+//-------------------------------------------------------------------------
+// nsIAppShell methods:
+
+NS_IMETHODIMP
+nsBaseAppShell::Run(void)
+{
+ NS_ENSURE_STATE(!mRunning); // should not call Run twice
+ mRunning = true;
+
+ nsIThread *thread = NS_GetCurrentThread();
+
+ MessageLoop::current()->Run();
+
+ NS_ProcessPendingEvents(thread);
+
+ mRunning = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::Exit(void)
+{
+ if (mRunning && !mExiting) {
+ MessageLoop::current()->Quit();
+ }
+ mExiting = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::FavorPerformanceHint(bool favorPerfOverStarvation,
+ uint32_t starvationDelay)
+{
+ mStarvationDelay = PR_MillisecondsToInterval(starvationDelay);
+ if (favorPerfOverStarvation) {
+ ++mFavorPerf;
+ } else {
+ --mFavorPerf;
+ mSwitchTime = PR_IntervalNow();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::SuspendNative()
+{
+ ++mSuspendNativeCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::ResumeNative()
+{
+ --mSuspendNativeCount;
+ NS_ASSERTION(mSuspendNativeCount >= 0, "Unbalanced call to nsBaseAppShell::ResumeNative!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::GetEventloopNestingLevel(uint32_t* aNestingLevelResult)
+{
+ NS_ENSURE_ARG_POINTER(aNestingLevelResult);
+
+ *aNestingLevelResult = mEventloopNestingLevel;
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+// nsIThreadObserver methods:
+
+// Called from any thread
+NS_IMETHODIMP
+nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
+{
+ if (mBlockNativeEvent)
+ return NS_OK;
+
+ if (mNativeEventPending.exchange(true))
+ return NS_OK;
+
+ // Returns on the main thread in NativeEventCallback above
+ ScheduleNativeEventCallback();
+ return NS_OK;
+}
+
+// Called from the main thread
+NS_IMETHODIMP
+nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait)
+{
+ if (mBlockNativeEvent) {
+ if (!mayWait)
+ return NS_OK;
+ // Hmm, we're in a nested native event loop and would like to get
+ // back to it ASAP, but it seems a gecko event has caused us to
+ // spin up a nested XPCOM event loop (eg. modal window), so we
+ // really must start processing native events here again.
+ mBlockNativeEvent = false;
+ if (NS_HasPendingEvents(thr))
+ OnDispatchedEvent(thr); // in case we blocked it earlier
+ }
+
+ PRIntervalTime start = PR_IntervalNow();
+ PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;
+
+ // Unblock outer nested wait loop (below).
+ if (mBlockedWait)
+ *mBlockedWait = false;
+
+ bool *oldBlockedWait = mBlockedWait;
+ mBlockedWait = &mayWait;
+
+ // When mayWait is true, we need to make sure that there is an event in the
+ // thread's event queue before we return. Otherwise, the thread will block
+ // on its event queue waiting for an event.
+ bool needEvent = mayWait;
+ // Reset prior to invoking DoProcessNextNativeEvent which might cause
+ // NativeEventCallback to process gecko events.
+ mProcessedGeckoEvents = false;
+
+ if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
+ // Favor pending native events
+ PRIntervalTime now = start;
+ bool keepGoing;
+ do {
+ mLastNativeEventTime = now;
+ keepGoing = DoProcessNextNativeEvent(false);
+ } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
+ } else {
+ // Avoid starving native events completely when in performance mode
+ if (start - mLastNativeEventTime > limit) {
+ mLastNativeEventTime = start;
+ DoProcessNextNativeEvent(false);
+ }
+ }
+
+ while (!NS_HasPendingEvents(thr) && !mProcessedGeckoEvents) {
+ // If we have been asked to exit from Run, then we should not wait for
+ // events to process. Note that an inner nested event loop causes
+ // 'mayWait' to become false too, through 'mBlockedWait'.
+ if (mExiting)
+ mayWait = false;
+
+ mLastNativeEventTime = PR_IntervalNow();
+ if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
+ break;
+ }
+
+ mBlockedWait = oldBlockedWait;
+
+ // Make sure that the thread event queue does not block on its monitor, as
+ // it normally would do if it did not have any pending events. To avoid
+ // that, we simply insert a dummy event into its queue during shutdown.
+ if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
+ DispatchDummyEvent(thr);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ if (!mDummyEvent)
+ mDummyEvent = new mozilla::Runnable();
+
+ return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
+}
+
+void
+nsBaseAppShell::IncrementEventloopNestingLevel()
+{
+ ++mEventloopNestingLevel;
+#if defined(MOZ_CRASHREPORTER)
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+#endif
+}
+
+void
+nsBaseAppShell::DecrementEventloopNestingLevel()
+{
+ --mEventloopNestingLevel;
+#if defined(MOZ_CRASHREPORTER)
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+#endif
+}
+
+// Called from the main thread
+NS_IMETHODIMP
+nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
+ bool eventWasProcessed)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data)
+{
+ NS_ASSERTION(!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID), "oops");
+ Exit();
+ return NS_OK;
+}