Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "MessagePump.h"
7 #include "nsIRunnable.h"
8 #include "nsIThread.h"
9 #include "nsITimer.h"
11 #include "base/basictypes.h"
12 #include "base/logging.h"
13 #include "base/scoped_nsautorelease_pool.h"
14 #include "mozilla/Assertions.h"
15 #include "mozilla/DebugOnly.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsDebug.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsString.h"
20 #include "nsThreadUtils.h"
21 #include "nsTimerImpl.h"
22 #include "nsXULAppAPI.h"
23 #include "prthread.h"
25 #ifdef MOZ_WIDGET_ANDROID
26 #include "AndroidBridge.h"
27 #endif
29 #ifdef MOZ_NUWA_PROCESS
30 #include "ipc/Nuwa.h"
31 #endif
33 using base::TimeTicks;
34 using namespace mozilla::ipc;
36 NS_DEFINE_NAMED_CID(NS_TIMER_CID);
38 static mozilla::DebugOnly<MessagePump::Delegate*> gFirstDelegate;
40 namespace mozilla {
41 namespace ipc {
43 class DoWorkRunnable MOZ_FINAL : public nsIRunnable,
44 public nsITimerCallback
45 {
46 public:
47 DoWorkRunnable(MessagePump* aPump)
48 : mPump(aPump)
49 {
50 MOZ_ASSERT(aPump);
51 }
53 NS_DECL_THREADSAFE_ISUPPORTS
54 NS_DECL_NSIRUNNABLE
55 NS_DECL_NSITIMERCALLBACK
57 private:
58 ~DoWorkRunnable()
59 { }
61 MessagePump* mPump;
62 };
64 } /* namespace ipc */
65 } /* namespace mozilla */
67 MessagePump::MessagePump()
68 : mThread(nullptr)
69 {
70 mDoWorkEvent = new DoWorkRunnable(this);
71 }
73 MessagePump::~MessagePump()
74 {
75 }
77 void
78 MessagePump::Run(MessagePump::Delegate* aDelegate)
79 {
80 MOZ_ASSERT(keep_running_);
81 MOZ_ASSERT(NS_IsMainThread(),
82 "Use mozilla::ipc::MessagePumpForNonMainThreads instead!");
84 mThread = NS_GetCurrentThread();
85 MOZ_ASSERT(mThread);
87 mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
88 MOZ_ASSERT(mDelayedWorkTimer);
90 base::ScopedNSAutoreleasePool autoReleasePool;
92 for (;;) {
93 autoReleasePool.Recycle();
95 bool did_work = NS_ProcessNextEvent(mThread, false) ? true : false;
96 if (!keep_running_)
97 break;
99 // NB: it is crucial *not* to directly call |aDelegate->DoWork()|
100 // here. To ensure that MessageLoop tasks and XPCOM events have
101 // equal priority, we sensitively rely on processing exactly one
102 // Task per DoWorkRunnable XPCOM event.
104 #ifdef MOZ_WIDGET_ANDROID
105 // This processes messages in the Android Looper. Note that we only
106 // get here if the normal Gecko event loop has been awoken above.
107 // Bug 750713
108 if (MOZ_LIKELY(AndroidBridge::HasEnv())) {
109 did_work |= mozilla::widget::android::GeckoAppShell::PumpMessageLoop();
110 }
111 #endif
113 did_work |= aDelegate->DoDelayedWork(&delayed_work_time_);
115 if (did_work && delayed_work_time_.is_null()
116 #ifdef MOZ_NUWA_PROCESS
117 && (!IsNuwaReady() || !IsNuwaProcess())
118 #endif
119 )
120 mDelayedWorkTimer->Cancel();
122 if (!keep_running_)
123 break;
125 if (did_work)
126 continue;
128 did_work = aDelegate->DoIdleWork();
129 if (!keep_running_)
130 break;
132 if (did_work)
133 continue;
135 // This will either sleep or process an event.
136 NS_ProcessNextEvent(mThread, true);
137 }
139 #ifdef MOZ_NUWA_PROCESS
140 if (!IsNuwaReady() || !IsNuwaProcess())
141 #endif
142 mDelayedWorkTimer->Cancel();
144 keep_running_ = true;
145 }
147 void
148 MessagePump::ScheduleWork()
149 {
150 // Make sure the event loop wakes up.
151 if (mThread) {
152 mThread->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL);
153 }
154 else {
155 // Some things (like xpcshell) don't use the app shell and so Run hasn't
156 // been called. We still need to wake up the main thread.
157 NS_DispatchToMainThread(mDoWorkEvent, NS_DISPATCH_NORMAL);
158 }
159 event_.Signal();
160 }
162 void
163 MessagePump::ScheduleWorkForNestedLoop()
164 {
165 // This method is called when our MessageLoop has just allowed
166 // nested tasks. In our setup, whenever that happens we know that
167 // DoWork() will be called "soon", so there's no need to pay the
168 // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent).
169 }
171 void
172 MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime)
173 {
174 #ifdef MOZ_NUWA_PROCESS
175 if (IsNuwaReady() && IsNuwaProcess())
176 return;
177 #endif
179 if (!mDelayedWorkTimer) {
180 mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
181 if (!mDelayedWorkTimer) {
182 // Called before XPCOM has started up? We can't do this correctly.
183 NS_WARNING("Delayed task might not run!");
184 delayed_work_time_ = aDelayedTime;
185 return;
186 }
187 }
189 if (!delayed_work_time_.is_null()) {
190 mDelayedWorkTimer->Cancel();
191 }
193 delayed_work_time_ = aDelayedTime;
195 // TimeDelta's constructor initializes to 0
196 base::TimeDelta delay;
197 if (aDelayedTime > base::TimeTicks::Now())
198 delay = aDelayedTime - base::TimeTicks::Now();
200 uint32_t delayMS = uint32_t(delay.InMilliseconds());
201 mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS,
202 nsITimer::TYPE_ONE_SHOT);
203 }
205 void
206 MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate)
207 {
208 aDelegate->DoDelayedWork(&delayed_work_time_);
209 if (!delayed_work_time_.is_null()) {
210 ScheduleDelayedWork(delayed_work_time_);
211 }
212 }
214 NS_IMPL_ISUPPORTS(DoWorkRunnable, nsIRunnable, nsITimerCallback)
216 NS_IMETHODIMP
217 DoWorkRunnable::Run()
218 {
219 MessageLoop* loop = MessageLoop::current();
220 MOZ_ASSERT(loop);
222 bool nestableTasksAllowed = loop->NestableTasksAllowed();
224 // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
225 // always dispatch DoWork() below from what looks to MessageLoop like a nested
226 // context. So we unconditionally allow nesting here.
227 loop->SetNestableTasksAllowed(true);
228 loop->DoWork();
229 loop->SetNestableTasksAllowed(nestableTasksAllowed);
231 return NS_OK;
232 }
234 NS_IMETHODIMP
235 DoWorkRunnable::Notify(nsITimer* aTimer)
236 {
237 MessageLoop* loop = MessageLoop::current();
238 MOZ_ASSERT(loop);
240 mPump->DoDelayedWork(loop);
242 return NS_OK;
243 }
245 void
246 MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate)
247 {
248 if (mFirstRun) {
249 MOZ_ASSERT(aDelegate && !gFirstDelegate);
250 gFirstDelegate = aDelegate;
252 mFirstRun = false;
253 if (NS_FAILED(XRE_RunAppShell())) {
254 NS_WARNING("Failed to run app shell?!");
255 }
257 MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
258 gFirstDelegate = nullptr;
260 return;
261 }
263 MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
265 // We can get to this point in startup with Tasks in our loop's
266 // incoming_queue_ or pending_queue_, but without a matching
267 // DoWorkRunnable(). In MessagePump::Run() above, we sensitively
268 // depend on *not* directly calling delegate->DoWork(), because that
269 // prioritizes Tasks above XPCOM events. However, from this point
270 // forward, any Task posted to our loop is guaranteed to have a
271 // DoWorkRunnable enqueued for it.
272 //
273 // So we just flush the pending work here and move on.
274 MessageLoop* loop = MessageLoop::current();
275 bool nestableTasksAllowed = loop->NestableTasksAllowed();
276 loop->SetNestableTasksAllowed(true);
278 while (aDelegate->DoWork());
280 loop->SetNestableTasksAllowed(nestableTasksAllowed);
282 // Really run.
283 mozilla::ipc::MessagePump::Run(aDelegate);
284 }
286 void
287 MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate)
288 {
289 MOZ_ASSERT(keep_running_);
290 MOZ_ASSERT(!NS_IsMainThread(), "Use mozilla::ipc::MessagePump instead!");
292 mThread = NS_GetCurrentThread();
293 MOZ_ASSERT(mThread);
295 mDelayedWorkTimer = do_CreateInstance(kNS_TIMER_CID);
296 MOZ_ASSERT(mDelayedWorkTimer);
298 if (NS_FAILED(mDelayedWorkTimer->SetTarget(mThread))) {
299 MOZ_CRASH("Failed to set timer target!");
300 }
302 base::ScopedNSAutoreleasePool autoReleasePool;
304 for (;;) {
305 autoReleasePool.Recycle();
307 bool didWork = NS_ProcessNextEvent(mThread, false) ? true : false;
308 if (!keep_running_) {
309 break;
310 }
312 didWork |= aDelegate->DoDelayedWork(&delayed_work_time_);
314 if (didWork && delayed_work_time_.is_null()) {
315 mDelayedWorkTimer->Cancel();
316 }
318 if (!keep_running_) {
319 break;
320 }
322 if (didWork) {
323 continue;
324 }
326 didWork = aDelegate->DoIdleWork();
327 if (!keep_running_) {
328 break;
329 }
331 if (didWork) {
332 continue;
333 }
335 // This will either sleep or process an event.
336 NS_ProcessNextEvent(mThread, true);
337 }
339 mDelayedWorkTimer->Cancel();
341 keep_running_ = true;
342 }