summaryrefslogtreecommitdiffstats
path: root/widget/nsBaseAppShell.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/nsBaseAppShell.cpp313
1 files changed, 313 insertions, 0 deletions
diff --git a/widget/nsBaseAppShell.cpp b/widget/nsBaseAppShell.cpp
new file mode 100644
index 0000000000..632f38478c
--- /dev/null
+++ b/widget/nsBaseAppShell.cpp
@@ -0,0 +1,313 @@
+/* -*- 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"
+#include "nsExceptionHandler.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIAppShell.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsXULAppAPI.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),
+ mNativeEventPending(false),
+ mGeckoTaskBurstStartTime(0),
+ mLastNativeEventTime(0),
+ mEventloopNestingState(eEventloopNone),
+ mRunning(false),
+ mExiting(false),
+ mBlockNativeEvent(false),
+ mProcessedGeckoEvents(false) {}
+
+nsBaseAppShell::~nsBaseAppShell() = default;
+
+nsresult nsBaseAppShell::Init() {
+ // Configure ourselves as an observer for the current thread:
+
+ if (XRE_UseNativeEventProcessing()) {
+ 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();
+}
+
+void nsBaseAppShell::OnSystemTimezoneChange() {
+ nsJSUtils::ResetTimeZone();
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ // Timezone changed notification
+ obsSvc->NotifyObservers(nullptr, DEFAULT_TIMEZONE_CHANGED_OBSERVER_TOPIC,
+ nullptr);
+ }
+}
+
+// Note, this is currently overidden on windows, see comments in nsAppShell for
+// details.
+void nsBaseAppShell::DoProcessMoreGeckoEvents() { OnDispatchedEvent(); }
+
+// 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::GeckoTaskBurst() {
+ if (mGeckoTaskBurstStartTime == 0) {
+ mGeckoTaskBurstStartTime = 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() {
+ 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(); // 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;
+
+ // Content processes always priorize gecko events.
+ if (!XRE_IsContentProcess() && (start > (mGeckoTaskBurstStartTime + limit))) {
+ mGeckoTaskBurstStartTime = 0;
+ // 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("DummyEvent");
+
+ return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
+}
+
+void nsBaseAppShell::IncrementEventloopNestingLevel() {
+ ++mEventloopNestingLevel;
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+}
+
+void nsBaseAppShell::DecrementEventloopNestingLevel() {
+ --mEventloopNestingLevel;
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+}
+
+// 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;
+}