michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "MessagePump.h" michael@0: michael@0: #include "nsIRunnable.h" michael@0: #include "nsIThread.h" michael@0: #include "nsITimer.h" michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "base/logging.h" michael@0: #include "base/scoped_nsautorelease_pool.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsDebug.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsTimerImpl.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "prthread.h" michael@0: michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: #include "AndroidBridge.h" michael@0: #endif michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: #include "ipc/Nuwa.h" michael@0: #endif michael@0: michael@0: using base::TimeTicks; michael@0: using namespace mozilla::ipc; michael@0: michael@0: NS_DEFINE_NAMED_CID(NS_TIMER_CID); michael@0: michael@0: static mozilla::DebugOnly gFirstDelegate; michael@0: michael@0: namespace mozilla { michael@0: namespace ipc { michael@0: michael@0: class DoWorkRunnable MOZ_FINAL : public nsIRunnable, michael@0: public nsITimerCallback michael@0: { michael@0: public: michael@0: DoWorkRunnable(MessagePump* aPump) michael@0: : mPump(aPump) michael@0: { michael@0: MOZ_ASSERT(aPump); michael@0: } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIRUNNABLE michael@0: NS_DECL_NSITIMERCALLBACK michael@0: michael@0: private: michael@0: ~DoWorkRunnable() michael@0: { } michael@0: michael@0: MessagePump* mPump; michael@0: }; michael@0: michael@0: } /* namespace ipc */ michael@0: } /* namespace mozilla */ michael@0: michael@0: MessagePump::MessagePump() michael@0: : mThread(nullptr) michael@0: { michael@0: mDoWorkEvent = new DoWorkRunnable(this); michael@0: } michael@0: michael@0: MessagePump::~MessagePump() michael@0: { michael@0: } michael@0: michael@0: void michael@0: MessagePump::Run(MessagePump::Delegate* aDelegate) michael@0: { michael@0: MOZ_ASSERT(keep_running_); michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "Use mozilla::ipc::MessagePumpForNonMainThreads instead!"); michael@0: michael@0: mThread = NS_GetCurrentThread(); michael@0: MOZ_ASSERT(mThread); michael@0: michael@0: mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); michael@0: MOZ_ASSERT(mDelayedWorkTimer); michael@0: michael@0: base::ScopedNSAutoreleasePool autoReleasePool; michael@0: michael@0: for (;;) { michael@0: autoReleasePool.Recycle(); michael@0: michael@0: bool did_work = NS_ProcessNextEvent(mThread, false) ? true : false; michael@0: if (!keep_running_) michael@0: break; michael@0: michael@0: // NB: it is crucial *not* to directly call |aDelegate->DoWork()| michael@0: // here. To ensure that MessageLoop tasks and XPCOM events have michael@0: // equal priority, we sensitively rely on processing exactly one michael@0: // Task per DoWorkRunnable XPCOM event. michael@0: michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: // This processes messages in the Android Looper. Note that we only michael@0: // get here if the normal Gecko event loop has been awoken above. michael@0: // Bug 750713 michael@0: if (MOZ_LIKELY(AndroidBridge::HasEnv())) { michael@0: did_work |= mozilla::widget::android::GeckoAppShell::PumpMessageLoop(); michael@0: } michael@0: #endif michael@0: michael@0: did_work |= aDelegate->DoDelayedWork(&delayed_work_time_); michael@0: michael@0: if (did_work && delayed_work_time_.is_null() michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: && (!IsNuwaReady() || !IsNuwaProcess()) michael@0: #endif michael@0: ) michael@0: mDelayedWorkTimer->Cancel(); michael@0: michael@0: if (!keep_running_) michael@0: break; michael@0: michael@0: if (did_work) michael@0: continue; michael@0: michael@0: did_work = aDelegate->DoIdleWork(); michael@0: if (!keep_running_) michael@0: break; michael@0: michael@0: if (did_work) michael@0: continue; michael@0: michael@0: // This will either sleep or process an event. michael@0: NS_ProcessNextEvent(mThread, true); michael@0: } michael@0: michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: if (!IsNuwaReady() || !IsNuwaProcess()) michael@0: #endif michael@0: mDelayedWorkTimer->Cancel(); michael@0: michael@0: keep_running_ = true; michael@0: } michael@0: michael@0: void michael@0: MessagePump::ScheduleWork() michael@0: { michael@0: // Make sure the event loop wakes up. michael@0: if (mThread) { michael@0: mThread->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: else { michael@0: // Some things (like xpcshell) don't use the app shell and so Run hasn't michael@0: // been called. We still need to wake up the main thread. michael@0: NS_DispatchToMainThread(mDoWorkEvent, NS_DISPATCH_NORMAL); michael@0: } michael@0: event_.Signal(); michael@0: } michael@0: michael@0: void michael@0: MessagePump::ScheduleWorkForNestedLoop() michael@0: { michael@0: // This method is called when our MessageLoop has just allowed michael@0: // nested tasks. In our setup, whenever that happens we know that michael@0: // DoWork() will be called "soon", so there's no need to pay the michael@0: // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent). michael@0: } michael@0: michael@0: void michael@0: MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) michael@0: { michael@0: #ifdef MOZ_NUWA_PROCESS michael@0: if (IsNuwaReady() && IsNuwaProcess()) michael@0: return; michael@0: #endif michael@0: michael@0: if (!mDelayedWorkTimer) { michael@0: mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); michael@0: if (!mDelayedWorkTimer) { michael@0: // Called before XPCOM has started up? We can't do this correctly. michael@0: NS_WARNING("Delayed task might not run!"); michael@0: delayed_work_time_ = aDelayedTime; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (!delayed_work_time_.is_null()) { michael@0: mDelayedWorkTimer->Cancel(); michael@0: } michael@0: michael@0: delayed_work_time_ = aDelayedTime; michael@0: michael@0: // TimeDelta's constructor initializes to 0 michael@0: base::TimeDelta delay; michael@0: if (aDelayedTime > base::TimeTicks::Now()) michael@0: delay = aDelayedTime - base::TimeTicks::Now(); michael@0: michael@0: uint32_t delayMS = uint32_t(delay.InMilliseconds()); michael@0: mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: void michael@0: MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) michael@0: { michael@0: aDelegate->DoDelayedWork(&delayed_work_time_); michael@0: if (!delayed_work_time_.is_null()) { michael@0: ScheduleDelayedWork(delayed_work_time_); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(DoWorkRunnable, nsIRunnable, nsITimerCallback) michael@0: michael@0: NS_IMETHODIMP michael@0: DoWorkRunnable::Run() michael@0: { michael@0: MessageLoop* loop = MessageLoop::current(); michael@0: MOZ_ASSERT(loop); michael@0: michael@0: bool nestableTasksAllowed = loop->NestableTasksAllowed(); michael@0: michael@0: // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will michael@0: // always dispatch DoWork() below from what looks to MessageLoop like a nested michael@0: // context. So we unconditionally allow nesting here. michael@0: loop->SetNestableTasksAllowed(true); michael@0: loop->DoWork(); michael@0: loop->SetNestableTasksAllowed(nestableTasksAllowed); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DoWorkRunnable::Notify(nsITimer* aTimer) michael@0: { michael@0: MessageLoop* loop = MessageLoop::current(); michael@0: MOZ_ASSERT(loop); michael@0: michael@0: mPump->DoDelayedWork(loop); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate) michael@0: { michael@0: if (mFirstRun) { michael@0: MOZ_ASSERT(aDelegate && !gFirstDelegate); michael@0: gFirstDelegate = aDelegate; michael@0: michael@0: mFirstRun = false; michael@0: if (NS_FAILED(XRE_RunAppShell())) { michael@0: NS_WARNING("Failed to run app shell?!"); michael@0: } michael@0: michael@0: MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); michael@0: gFirstDelegate = nullptr; michael@0: michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); michael@0: michael@0: // We can get to this point in startup with Tasks in our loop's michael@0: // incoming_queue_ or pending_queue_, but without a matching michael@0: // DoWorkRunnable(). In MessagePump::Run() above, we sensitively michael@0: // depend on *not* directly calling delegate->DoWork(), because that michael@0: // prioritizes Tasks above XPCOM events. However, from this point michael@0: // forward, any Task posted to our loop is guaranteed to have a michael@0: // DoWorkRunnable enqueued for it. michael@0: // michael@0: // So we just flush the pending work here and move on. michael@0: MessageLoop* loop = MessageLoop::current(); michael@0: bool nestableTasksAllowed = loop->NestableTasksAllowed(); michael@0: loop->SetNestableTasksAllowed(true); michael@0: michael@0: while (aDelegate->DoWork()); michael@0: michael@0: loop->SetNestableTasksAllowed(nestableTasksAllowed); michael@0: michael@0: // Really run. michael@0: mozilla::ipc::MessagePump::Run(aDelegate); michael@0: } michael@0: michael@0: void michael@0: MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate) michael@0: { michael@0: MOZ_ASSERT(keep_running_); michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Use mozilla::ipc::MessagePump instead!"); michael@0: michael@0: mThread = NS_GetCurrentThread(); michael@0: MOZ_ASSERT(mThread); michael@0: michael@0: mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID); michael@0: MOZ_ASSERT(mDelayedWorkTimer); michael@0: michael@0: if (NS_FAILED(mDelayedWorkTimer->SetTarget(mThread))) { michael@0: MOZ_CRASH("Failed to set timer target!"); michael@0: } michael@0: michael@0: base::ScopedNSAutoreleasePool autoReleasePool; michael@0: michael@0: for (;;) { michael@0: autoReleasePool.Recycle(); michael@0: michael@0: bool didWork = NS_ProcessNextEvent(mThread, false) ? true : false; michael@0: if (!keep_running_) { michael@0: break; michael@0: } michael@0: michael@0: didWork |= aDelegate->DoDelayedWork(&delayed_work_time_); michael@0: michael@0: if (didWork && delayed_work_time_.is_null()) { michael@0: mDelayedWorkTimer->Cancel(); michael@0: } michael@0: michael@0: if (!keep_running_) { michael@0: break; michael@0: } michael@0: michael@0: if (didWork) { michael@0: continue; michael@0: } michael@0: michael@0: didWork = aDelegate->DoIdleWork(); michael@0: if (!keep_running_) { michael@0: break; michael@0: } michael@0: michael@0: if (didWork) { michael@0: continue; michael@0: } michael@0: michael@0: // This will either sleep or process an event. michael@0: NS_ProcessNextEvent(mThread, true); michael@0: } michael@0: michael@0: mDelayedWorkTimer->Cancel(); michael@0: michael@0: keep_running_ = true; michael@0: }