/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "nsThreadManager.h" #include "nsThread.h" #include "nsThreadPool.h" #include "nsThreadUtils.h" #include "nsIClassInfoImpl.h" #include "nsExceptionHandler.h" #include "nsTArray.h" #include "nsXULAppAPI.h" #include "nsExceptionHandler.h" #include "mozilla/AbstractThread.h" #include "mozilla/AppShutdown.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask #include "mozilla/EventQueue.h" #include "mozilla/InputTaskManager.h" #include "mozilla/Mutex.h" #include "mozilla/Preferences.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/SpinEventLoopUntil.h" #include "mozilla/StaticPtr.h" #include "mozilla/TaskQueue.h" #include "mozilla/ThreadEventQueue.h" #include "mozilla/ThreadLocal.h" #include "TaskController.h" #include "ThreadEventTarget.h" #ifdef MOZ_CANARY # include # include #endif #include "MainThreadIdlePeriod.h" using namespace mozilla; static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread; bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); } class BackgroundEventTarget final : public nsIEventTarget, public TaskQueueTracker { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIEVENTTARGET_FULL BackgroundEventTarget() = default; nsresult Init(); already_AddRefed CreateBackgroundTaskQueue( const char* aName); void BeginShutdown(nsTArray>&); void FinishShutdown(); private: ~BackgroundEventTarget() = default; nsCOMPtr mPool; nsCOMPtr mIOPool; }; NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget, TaskQueueTracker) nsresult BackgroundEventTarget::Init() { nsCOMPtr pool(new nsThreadPool()); NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); nsresult rv = pool->SetName("BackgroundThreadPool"_ns); NS_ENSURE_SUCCESS(rv, rv); // Use potentially more conservative stack size. rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); NS_ENSURE_SUCCESS(rv, rv); // Thread limit of 2 makes deadlock during synchronous dispatch less likely. rv = pool->SetThreadLimit(2); NS_ENSURE_SUCCESS(rv, rv); rv = pool->SetIdleThreadLimit(1); NS_ENSURE_SUCCESS(rv, rv); // Leave threads alive for up to 5 minutes rv = pool->SetIdleThreadTimeout(300000); NS_ENSURE_SUCCESS(rv, rv); // Initialize the background I/O event target. nsCOMPtr ioPool(new nsThreadPool()); NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); rv = ioPool->SetName("BgIOThreadPool"_ns); NS_ENSURE_SUCCESS(rv, rv); // Use potentially more conservative stack size. rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); NS_ENSURE_SUCCESS(rv, rv); // Thread limit of 4 makes deadlock during synchronous dispatch less likely. rv = ioPool->SetThreadLimit(4); NS_ENSURE_SUCCESS(rv, rv); rv = ioPool->SetIdleThreadLimit(1); NS_ENSURE_SUCCESS(rv, rv); // Leave threads alive for up to 5 minutes rv = ioPool->SetIdleThreadTimeout(300000); NS_ENSURE_SUCCESS(rv, rv); pool.swap(mPool); ioPool.swap(mIOPool); return NS_OK; } NS_IMETHODIMP_(bool) BackgroundEventTarget::IsOnCurrentThreadInfallible() { return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread(); } NS_IMETHODIMP BackgroundEventTarget::IsOnCurrentThread(bool* aValue) { bool value = false; if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) { *aValue = value; return NS_OK; } return mIOPool->IsOnCurrentThread(aValue); } NS_IMETHODIMP BackgroundEventTarget::Dispatch(already_AddRefed aRunnable, uint32_t aFlags) { // We need to be careful here, because if an event is getting dispatched here // from within TaskQueue::Runner::Run, it will be dispatched with // NS_DISPATCH_AT_END, but we might not be running the event on the same // pool, depending on which pool we were on and the dispatch flags. If we // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool // may not process the event in a timely fashion, which can lead to deadlock. uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK; bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK); nsCOMPtr& pool = mayBlock ? mIOPool : mPool; // If we're already running on the pool we want to dispatch to, we can // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin // up a new thread. // // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues // like those in the above comment. if (pool->IsOnCurrentThread()) { flags |= NS_DISPATCH_AT_END; } else { flags &= ~NS_DISPATCH_AT_END; } return pool->Dispatch(std::move(aRunnable), flags); } NS_IMETHODIMP BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) { nsCOMPtr runnable(aRunnable); return Dispatch(runnable.forget(), aFlags); } NS_IMETHODIMP BackgroundEventTarget::DelayedDispatch(already_AddRefed aRunnable, uint32_t) { nsCOMPtr dropRunnable(aRunnable); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP BackgroundEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP BackgroundEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { return NS_ERROR_NOT_IMPLEMENTED; } void BackgroundEventTarget::BeginShutdown( nsTArray>& promises) { auto queues = GetAllTrackedTaskQueues(); for (auto& queue : queues) { promises.AppendElement(queue->BeginShutdown()); } } void BackgroundEventTarget::FinishShutdown() { mPool->Shutdown(); mIOPool->Shutdown(); } already_AddRefed BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) { return TaskQueue::Create(do_AddRef(this), aName).forget(); } extern "C" { // This uses the C language linkage because it's exposed to Rust // via the xpcom/rust/moz_task crate. bool NS_IsMainThread() { return sTLSIsMainThread.get(); } } void NS_SetMainThread() { if (!sTLSIsMainThread.init()) { MOZ_CRASH(); } sTLSIsMainThread.set(true); MOZ_ASSERT(NS_IsMainThread()); // We initialize the SerialEventTargetGuard's TLS here for simplicity as it // needs to be initialized around the same time you would initialize // sTLSIsMainThread. SerialEventTargetGuard::InitTLS(); } #ifdef DEBUG namespace mozilla { void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); } } // namespace mozilla #endif typedef nsTArray>> nsThreadArray; //----------------------------------------------------------------------------- /* static */ void nsThreadManager::ReleaseThread(void* aData) { static_cast(aData)->Release(); } // statically allocated instance NS_IMETHODIMP_(MozExternalRefCountType) nsThreadManager::AddRef() { return 2; } NS_IMETHODIMP_(MozExternalRefCountType) nsThreadManager::Release() { return 1; } NS_IMPL_CLASSINFO(nsThreadManager, nullptr, nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, NS_THREADMANAGER_CID) NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) //----------------------------------------------------------------------------- /*static*/ nsThreadManager& nsThreadManager::get() { static nsThreadManager sInstance; return sInstance; } nsThreadManager::nsThreadManager() : mCurThreadIndex(0), mMainPRThread(nullptr), mInitialized(false) {} nsThreadManager::~nsThreadManager() = default; nsresult nsThreadManager::Init() { // Child processes need to initialize the thread manager before they // initialize XPCOM in order to set up the crash reporter. This leads to // situations where we get initialized twice. if (mInitialized) { return NS_OK; } if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseThread) == PR_FAILURE) { return NS_ERROR_FAILURE; } #ifdef MOZ_CANARY const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; char* env_var_flag = getenv("MOZ_KILL_CANARIES"); sCanaryOutputFD = env_var_flag ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO) : 0; #endif TaskController::Initialize(); // Initialize idle handling. nsCOMPtr idlePeriod = new MainThreadIdlePeriod(); TaskController::Get()->SetIdleTaskManager( new IdleTaskManager(idlePeriod.forget())); // Create main thread queue that forwards events to TaskController and // construct main thread. UniquePtr queue = MakeUnique(true); RefPtr synchronizedQueue = new ThreadEventQueue(std::move(queue), true); mMainThread = new nsThread(WrapNotNull(synchronizedQueue), nsThread::MAIN_THREAD, {.stackSize = 0}); nsresult rv = mMainThread->InitCurrentThread(); if (NS_FAILED(rv)) { mMainThread = nullptr; return rv; } // We need to keep a pointer to the current thread, so we can satisfy // GetIsMainThread calls that occur post-Shutdown. mMainThread->GetPRThread(&mMainPRThread); // Init AbstractThread. AbstractThread::InitTLS(); AbstractThread::InitMainThread(); // Initialize the background event target. RefPtr target(new BackgroundEventTarget()); rv = target->Init(); NS_ENSURE_SUCCESS(rv, rv); mBackgroundEventTarget = std::move(target); mInitialized = true; return NS_OK; } void nsThreadManager::ShutdownNonMainThreads() { MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); // Prevent further access to the thread manager (no more new threads!) // // What happens if shutdown happens before NewThread completes? // We Shutdown() the new thread, and return error if we've started Shutdown // between when NewThread started, and when the thread finished initializing // and registering with ThreadManager. // mInitialized = false; // Empty the main thread event queue before we begin shutting down threads. NS_ProcessPendingEvents(mMainThread); mMainThread->mEvents->RunShutdownTasks(); nsTArray> promises; mBackgroundEventTarget->BeginShutdown(promises); bool taskQueuesShutdown = false; // It's fine to capture everything by reference in the Then handler since it // runs before we exit the nested event loop, thanks to the SpinEventLoopUntil // below. ShutdownPromise::All(mMainThread, promises)->Then(mMainThread, __func__, [&] { mBackgroundEventTarget->FinishShutdown(); taskQueuesShutdown = true; }); // Wait for task queues to shutdown, so we don't shut down the underlying // threads of the background event target in the block below, thereby // preventing the task queues from emptying, preventing the shutdown promises // from resolving, and prevent anything checking `taskQueuesShutdown` from // working. mozilla::SpinEventLoopUntil( "nsThreadManager::Shutdown"_ns, [&]() { return taskQueuesShutdown; }, mMainThread); { // We gather the threads into a list, so that we avoid holding the // enumerator lock while calling nsIThread::Shutdown. nsTArray> threadsToShutdown; for (auto* thread : nsThread::Enumerate()) { if (thread->ShutdownRequired()) { threadsToShutdown.AppendElement(thread); } } // It's tempting to walk the list of threads here and tell them each to stop // accepting new events, but that could lead to badness if one of those // threads is stuck waiting for a response from another thread. To do it // right, we'd need some way to interrupt the threads. // // Instead, we process events on the current thread while waiting for // threads to shutdown. This means that we have to preserve a mostly // functioning world until such time as the threads exit. // Shutdown all threads that require it (join with threads that we created). for (auto& thread : threadsToShutdown) { thread->Shutdown(); } } // NB: It's possible that there are events in the queue that want to *start* // an asynchronous shutdown. But we have already shutdown the threads above, // so there's no need to worry about them. We only have to wait for all // in-flight asynchronous thread shutdowns to complete. mMainThread->WaitForAllAsynchronousShutdowns(); // There are no more background threads at this point. } void nsThreadManager::ShutdownMainThread() { MOZ_ASSERT(!mInitialized, "Must have called BeginShutdown"); // Do NS_ProcessPendingEvents but with special handling to set // mEventsAreDoomed atomically with the removal of the last event. This means // that PutEvent cannot succeed if the event would be left in the main thread // queue after our final call to NS_ProcessPendingEvents. // See comments in `nsThread::ThreadFunc` for a more detailed explanation. while (true) { if (mMainThread->mEvents->ShutdownIfNoPendingEvents()) { break; } NS_ProcessPendingEvents(mMainThread); } // Normally thread shutdown clears the observer for the thread, but since the // main thread is special we do it manually here after we're sure all events // have been processed. mMainThread->SetObserver(nullptr); mBackgroundEventTarget = nullptr; } void nsThreadManager::ReleaseMainThread() { MOZ_ASSERT(!mInitialized, "Must have called BeginShutdown"); MOZ_ASSERT(!mBackgroundEventTarget, "Must have called ShutdownMainThread"); MOZ_ASSERT(mMainThread); // Release main thread object. mMainThread = nullptr; // Remove the TLS entry for the main thread. PR_SetThreadPrivate(mCurThreadIndex, nullptr); } void nsThreadManager::RegisterCurrentThread(nsThread& aThread) { MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); aThread.AddRef(); // for TLS entry PR_SetThreadPrivate(mCurThreadIndex, &aThread); } void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) { MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); PR_SetThreadPrivate(mCurThreadIndex, nullptr); // Ref-count balanced via ReleaseThread } nsThread* nsThreadManager::CreateCurrentThread( SynchronizedEventQueue* aQueue, nsThread::MainThreadFlag aMainThread) { // Make sure we don't have an nsThread yet. MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex)); if (!mInitialized) { return nullptr; } RefPtr thread = new nsThread(WrapNotNull(aQueue), aMainThread, {.stackSize = 0}); if (!thread || NS_FAILED(thread->InitCurrentThread())) { return nullptr; } return thread.get(); // reference held in TLS } nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent, uint32_t aDispatchFlags) { if (!mInitialized) { return NS_ERROR_FAILURE; } nsCOMPtr backgroundTarget(mBackgroundEventTarget); return backgroundTarget->Dispatch(aEvent, aDispatchFlags); } already_AddRefed nsThreadManager::CreateBackgroundTaskQueue(const char* aName) { if (!mInitialized) { return nullptr; } return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName); } nsThread* nsThreadManager::GetCurrentThread() { // read thread local storage void* data = PR_GetThreadPrivate(mCurThreadIndex); if (data) { return static_cast(data); } if (!mInitialized) { return nullptr; } // OK, that's fine. We'll dynamically create one :-) // // We assume that if we're implicitly creating a thread here that it doesn't // want an event queue. Any thread which wants an event queue should // explicitly create its nsThread wrapper. RefPtr thread = new nsThread(); if (!thread || NS_FAILED(thread->InitCurrentThread())) { return nullptr; } return thread.get(); // reference held in TLS } bool nsThreadManager::IsNSThread() const { if (!mInitialized) { return false; } if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) { return thread->EventQueue(); } return false; } NS_IMETHODIMP nsThreadManager::NewNamedThread( const nsACString& aName, nsIThreadManager::ThreadCreationOptions aOptions, nsIThread** aResult) { // Note: can be called from arbitrary threads // No new threads during Shutdown if (NS_WARN_IF(!mInitialized)) { return NS_ERROR_NOT_INITIALIZED; } [[maybe_unused]] TimeStamp startTime = TimeStamp::Now(); RefPtr queue = new ThreadEventQueue(MakeUnique()); RefPtr thr = new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aOptions); nsresult rv = thr->Init(aName); // Note: blocks until the new thread has been set up if (NS_FAILED(rv)) { return rv; } // At this point, we expect that the thread has been registered in // mThreadByPRThread; however, it is possible that it could have also been // replaced by now, so we cannot really assert that it was added. Instead, // kill it if we entered Shutdown() during/before Init() if (NS_WARN_IF(!mInitialized)) { if (thr->ShutdownRequired()) { thr->Shutdown(); // ok if it happens multiple times } return NS_ERROR_NOT_INITIALIZED; } PROFILER_MARKER_TEXT( "NewThread", OTHER, MarkerOptions(MarkerStack::Capture(), MarkerTiming::IntervalUntilNowFrom(startTime)), aName); if (!NS_IsMainThread()) { PROFILER_MARKER_TEXT( "NewThread (non-main thread)", OTHER, MarkerOptions(MarkerStack::Capture(), MarkerThreadId::MainThread(), MarkerTiming::IntervalUntilNowFrom(startTime)), aName); } thr.forget(aResult); return NS_OK; } NS_IMETHODIMP nsThreadManager::GetMainThread(nsIThread** aResult) { // Keep this functioning during Shutdown if (!mMainThread) { if (!NS_IsMainThread()) { NS_WARNING( "Called GetMainThread but there isn't a main thread and " "we're not the main thread."); } return NS_ERROR_NOT_INITIALIZED; } NS_ADDREF(*aResult = mMainThread); return NS_OK; } NS_IMETHODIMP nsThreadManager::GetCurrentThread(nsIThread** aResult) { // Keep this functioning during Shutdown if (!mMainThread) { return NS_ERROR_NOT_INITIALIZED; } *aResult = GetCurrentThread(); if (!*aResult) { return NS_ERROR_OUT_OF_MEMORY; } NS_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsThreadManager::SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis, nsINestedEventLoopCondition* aCondition) { return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, ShutdownPhase::NotInShutdown); } NS_IMETHODIMP nsThreadManager::SpinEventLoopUntilOrQuit( const nsACString& aVeryGoodReasonToDoThis, nsINestedEventLoopCondition* aCondition) { return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, ShutdownPhase::AppShutdownConfirmed); } // statics from SpinEventLoopUntil.h AutoNestedEventLoopAnnotation* AutoNestedEventLoopAnnotation::sCurrent = nullptr; StaticMutex AutoNestedEventLoopAnnotation::sStackMutex; // static from SpinEventLoopUntil.h void AutoNestedEventLoopAnnotation::AnnotateXPCOMSpinEventLoopStack( const nsACString& aStack) { if (aStack.Length() > 0) { nsCString prefixedStack(XRE_GetProcessTypeString()); prefixedStack += ": "_ns + aStack; CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::XPCOMSpinEventLoopStack, prefixedStack); } else { CrashReporter::AnnotateCrashReport( CrashReporter::Annotation::XPCOMSpinEventLoopStack, ""_ns); } } nsresult nsThreadManager::SpinEventLoopUntilInternal( const nsACString& aVeryGoodReasonToDoThis, nsINestedEventLoopCondition* aCondition, ShutdownPhase aShutdownPhaseToCheck) { // XXX: We would want to AssertIsOnMainThread(); but that breaks some GTest. nsCOMPtr condition(aCondition); nsresult rv = NS_OK; if (!mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, [&]() -> bool { // Check if an ongoing shutdown reached our limits. if (aShutdownPhaseToCheck > ShutdownPhase::NotInShutdown && AppShutdown::GetCurrentShutdownPhase() >= aShutdownPhaseToCheck) { return true; } bool isDone = false; rv = condition->IsDone(&isDone); // JS failure should be unusual, but we need to stop and propagate // the error back to the caller. if (NS_FAILED(rv)) { return true; } return isDone; })) { // We stopped early for some reason, which is unexpected. return NS_ERROR_UNEXPECTED; } // If we exited when the condition told us to, we need to return whether // the condition encountered failure when executing. return rv; } NS_IMETHODIMP nsThreadManager::SpinEventLoopUntilEmpty() { nsIThread* thread = NS_GetCurrentThread(); while (NS_HasPendingEvents(thread)) { (void)NS_ProcessNextEvent(thread, false); } return NS_OK; } NS_IMETHODIMP nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) { nsCOMPtr target = GetMainThreadSerialEventTarget(); target.forget(aTarget); return NS_OK; } NS_IMETHODIMP nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority, uint8_t aArgc) { // Note: C++ callers should instead use NS_DispatchToMainThread. MOZ_ASSERT(NS_IsMainThread()); // Keep this functioning during Shutdown if (NS_WARN_IF(!mMainThread)) { return NS_ERROR_NOT_INITIALIZED; } // If aPriority wasn't explicitly passed, that means it should be treated as // PRIORITY_NORMAL. if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) { nsCOMPtr event(aEvent); return mMainThread->DispatchFromScript( new PrioritizableRunnable(event.forget(), aPriority), 0); } return mMainThread->DispatchFromScript(aEvent, 0); } class AutoMicroTaskWrapperRunnable final : public Runnable { public: explicit AutoMicroTaskWrapperRunnable(nsIRunnable* aEvent) : Runnable("AutoMicroTaskWrapperRunnable"), mEvent(aEvent) { MOZ_ASSERT(aEvent); } private: ~AutoMicroTaskWrapperRunnable() = default; NS_IMETHOD Run() override { nsAutoMicroTask mt; return mEvent->Run(); } RefPtr mEvent; }; NS_IMETHODIMP nsThreadManager::DispatchToMainThreadWithMicroTask(nsIRunnable* aEvent, uint32_t aPriority, uint8_t aArgc) { RefPtr runnable = new AutoMicroTaskWrapperRunnable(aEvent); return DispatchToMainThread(runnable, aPriority, aArgc); } void nsThreadManager::EnableMainThreadEventPrioritization() { MOZ_ASSERT(NS_IsMainThread()); InputTaskManager::Get()->EnableInputEventPrioritization(); } void nsThreadManager::FlushInputEventPrioritization() { MOZ_ASSERT(NS_IsMainThread()); InputTaskManager::Get()->FlushInputEventPrioritization(); } void nsThreadManager::SuspendInputEventPrioritization() { MOZ_ASSERT(NS_IsMainThread()); InputTaskManager::Get()->SuspendInputEventPrioritization(); } void nsThreadManager::ResumeInputEventPrioritization() { MOZ_ASSERT(NS_IsMainThread()); InputTaskManager::Get()->ResumeInputEventPrioritization(); } // static bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() { MOZ_ASSERT(NS_IsMainThread()); bool retVal = false; if (get().mMainThread) { get().mMainThread->HasPendingHighPriorityEvents(&retVal); } return retVal; } NS_IMETHODIMP nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent, uint32_t aTimeout) { // Note: C++ callers should instead use NS_DispatchToThreadQueue or // NS_DispatchToCurrentThreadQueue. MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr event(aEvent); if (aTimeout) { return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread, EventQueuePriority::Idle); } return NS_DispatchToThreadQueue(event.forget(), mMainThread, EventQueuePriority::Idle); } NS_IMETHODIMP nsThreadManager::DispatchDirectTaskToCurrentThread(nsIRunnable* aEvent) { NS_ENSURE_STATE(aEvent); nsCOMPtr runnable = aEvent; return GetCurrentThread()->DispatchDirectTask(runnable.forget()); }