1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "base/message_loop.h"
7 #include "mozilla/Atomics.h"
8 #include "mozilla/EventQueue.h"
9 #include "mozilla/java/GeckoThreadWrappers.h"
10 #include "mozilla/LinkedList.h"
11 #include "mozilla/Monitor.h"
12 #include "mozilla/Mutex.h"
13 #include "mozilla/RefPtr.h"
14 #include "mozilla/StaticPtr.h"
15 #include "mozilla/ThreadEventQueue.h"
16 #include "mozilla/TimeStamp.h"
17 #include "mozilla/UniquePtr.h"
18 #include "nsThread.h"
19 #include "nsThreadManager.h"
20 #include "nsThreadUtils.h"
21 
22 using namespace mozilla;
23 
24 namespace {
25 
26 class AndroidUiThread;
27 class AndroidUiTask;
28 
29 StaticAutoPtr<LinkedList<AndroidUiTask> > sTaskQueue;
30 StaticAutoPtr<mozilla::Mutex> sTaskQueueLock;
31 StaticRefPtr<AndroidUiThread> sThread;
32 static bool sThreadDestroyed;
33 static MessageLoop* sMessageLoop;
34 static Atomic<Monitor*> sMessageLoopAccessMonitor;
35 
36 void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs);
37 
38 /*
39  * The AndroidUiThread is derived from nsThread so that nsIRunnable objects that
40  * get dispatched may be intercepted. Only nsIRunnable objects that need to be
41  * synchronously executed are passed into the nsThread to be queued. All other
42  * nsIRunnable object are immediately dispatched to the Android UI thread.
43  * AndroidUiThread is derived from nsThread instead of being an nsIEventTarget
44  * wrapper that contains an nsThread object because if nsIRunnable objects with
45  * a delay were dispatch directly to an nsThread object, such as obtained from
46  * nsThreadManager::GetCurrentThread(), the nsIRunnable could get stuck in the
47  * nsThread nsIRunnable queue. This is due to the fact that Android controls the
48  * event loop in the Android UI thread and has no knowledge of when the nsThread
49  * needs to be drained.
50  */
51 
52 class AndroidUiThread : public nsThread {
53  public:
NS_INLINE_DECL_REFCOUNTING_INHERITED(AndroidUiThread,nsThread)54   NS_INLINE_DECL_REFCOUNTING_INHERITED(AndroidUiThread, nsThread)
55   AndroidUiThread()
56       : nsThread(MakeNotNull<ThreadEventQueue<mozilla::EventQueue>*>(
57                      MakeUnique<mozilla::EventQueue>()),
58                  nsThread::NOT_MAIN_THREAD, 0) {}
59 
60   nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent,
61                     uint32_t aFlags) override;
62   nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
63                            uint32_t aDelayMs) override;
64 
65  private:
~AndroidUiThread()66   ~AndroidUiThread() {}
67 };
68 
69 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aFlags)70 AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
71                           uint32_t aFlags) {
72   if (aFlags & NS_DISPATCH_SYNC) {
73     return nsThread::Dispatch(std::move(aEvent), aFlags);
74   } else {
75     EnqueueTask(std::move(aEvent), 0);
76     return NS_OK;
77   }
78 }
79 
80 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aDelayMs)81 AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
82                                  uint32_t aDelayMs) {
83   EnqueueTask(std::move(aEvent), aDelayMs);
84   return NS_OK;
85 }
86 
PumpEvents()87 static void PumpEvents() { NS_ProcessPendingEvents(sThread.get()); }
88 
89 class ThreadObserver : public nsIThreadObserver {
90  public:
91   NS_DECL_THREADSAFE_ISUPPORTS
92   NS_DECL_NSITHREADOBSERVER
93 
ThreadObserver()94   ThreadObserver() {}
95 
96  private:
~ThreadObserver()97   virtual ~ThreadObserver() {}
98 };
99 
NS_IMPL_ISUPPORTS(ThreadObserver,nsIThreadObserver)100 NS_IMPL_ISUPPORTS(ThreadObserver, nsIThreadObserver)
101 
102 NS_IMETHODIMP
103 ThreadObserver::OnDispatchedEvent() {
104   EnqueueTask(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0);
105   return NS_OK;
106 }
107 
108 NS_IMETHODIMP
OnProcessNextEvent(nsIThreadInternal * thread,bool mayWait)109 ThreadObserver::OnProcessNextEvent(nsIThreadInternal* thread, bool mayWait) {
110   return NS_OK;
111 }
112 
113 NS_IMETHODIMP
AfterProcessNextEvent(nsIThreadInternal * thread,bool eventWasProcessed)114 ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* thread,
115                                       bool eventWasProcessed) {
116   return NS_OK;
117 }
118 
119 class AndroidUiTask : public LinkedListElement<AndroidUiTask> {
120   using TimeStamp = mozilla::TimeStamp;
121   using TimeDuration = mozilla::TimeDuration;
122 
123  public:
AndroidUiTask(already_AddRefed<nsIRunnable> aTask)124   explicit AndroidUiTask(already_AddRefed<nsIRunnable> aTask)
125       : mTask(aTask),
126         mRunTime()  // Null timestamp representing no delay.
127   {}
128 
AndroidUiTask(already_AddRefed<nsIRunnable> aTask,int aDelayMs)129   AndroidUiTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
130       : mTask(aTask),
131         mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs)) {}
132 
IsEarlierThan(const AndroidUiTask & aOther) const133   bool IsEarlierThan(const AndroidUiTask& aOther) const {
134     if (mRunTime) {
135       return aOther.mRunTime ? mRunTime < aOther.mRunTime : false;
136     }
137     // In the case of no delay, we're earlier if aOther has a delay.
138     // Otherwise, we're not earlier, to maintain task order.
139     return !!aOther.mRunTime;
140   }
141 
MillisecondsToRunTime() const142   int64_t MillisecondsToRunTime() const {
143     if (mRunTime) {
144       return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds());
145     }
146     return 0;
147   }
148 
TakeTask()149   already_AddRefed<nsIRunnable> TakeTask() { return mTask.forget(); }
150 
151  private:
152   nsCOMPtr<nsIRunnable> mTask;
153   const TimeStamp mRunTime;
154 };
155 
156 class CreateOnUiThread : public Runnable {
157  public:
CreateOnUiThread()158   CreateOnUiThread() : Runnable("CreateOnUiThread") {}
159 
Run()160   NS_IMETHOD Run() override {
161     MOZ_ASSERT(!sThreadDestroyed);
162     MOZ_ASSERT(sMessageLoopAccessMonitor);
163     MonitorAutoLock lock(*sMessageLoopAccessMonitor);
164     sThread = new AndroidUiThread();
165     sThread->InitCurrentThread();
166     sThread->SetObserver(new ThreadObserver());
167     sMessageLoop =
168         new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get());
169     lock.NotifyAll();
170     return NS_OK;
171   }
172 };
173 
174 class DestroyOnUiThread : public Runnable {
175  public:
DestroyOnUiThread()176   DestroyOnUiThread() : Runnable("DestroyOnUiThread"), mDestroyed(false) {}
177 
Run()178   NS_IMETHOD Run() override {
179     MOZ_ASSERT(!sThreadDestroyed);
180     MOZ_ASSERT(sMessageLoopAccessMonitor);
181     MOZ_ASSERT(sTaskQueue);
182     MonitorAutoLock lock(*sMessageLoopAccessMonitor);
183     sThreadDestroyed = true;
184 
185     {
186       // Flush the queue
187       MutexAutoLock lock(*sTaskQueueLock);
188       while (AndroidUiTask* task = sTaskQueue->getFirst()) {
189         delete task;
190       }
191     }
192 
193     delete sMessageLoop;
194     sMessageLoop = nullptr;
195     MOZ_ASSERT(sThread);
196     nsThreadManager::get().UnregisterCurrentThread(*sThread);
197     sThread = nullptr;
198     mDestroyed = true;
199     lock.NotifyAll();
200     return NS_OK;
201   }
202 
WaitForDestruction()203   void WaitForDestruction() {
204     MOZ_ASSERT(sMessageLoopAccessMonitor);
205     MonitorAutoLock lock(*sMessageLoopAccessMonitor);
206     while (!mDestroyed) {
207       lock.Wait();
208     }
209   }
210 
211  private:
212   bool mDestroyed;
213 };
214 
EnqueueTask(already_AddRefed<nsIRunnable> aTask,int aDelayMs)215 void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs) {
216   if (sThreadDestroyed) {
217     return;
218   }
219 
220   // add the new task into the sTaskQueue, sorted with
221   // the earliest task first in the queue
222   AndroidUiTask* newTask =
223       (aDelayMs ? new AndroidUiTask(std::move(aTask), aDelayMs)
224                 : new AndroidUiTask(std::move(aTask)));
225 
226   bool headOfList = false;
227   {
228     MOZ_ASSERT(sTaskQueue);
229     MOZ_ASSERT(sTaskQueueLock);
230     MutexAutoLock lock(*sTaskQueueLock);
231 
232     AndroidUiTask* task = sTaskQueue->getFirst();
233 
234     while (task) {
235       if (newTask->IsEarlierThan(*task)) {
236         task->setPrevious(newTask);
237         break;
238       }
239       task = task->getNext();
240     }
241 
242     if (!newTask->isInList()) {
243       sTaskQueue->insertBack(newTask);
244     }
245     headOfList = !newTask->getPrevious();
246   }
247 
248   if (headOfList) {
249     // if we're inserting it at the head of the queue, notify Java because
250     // we need to get a callback at an earlier time than the last scheduled
251     // callback
252     java::GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
253   }
254 }
255 
256 }  // namespace
257 
258 namespace mozilla {
259 
CreateAndroidUiThread()260 void CreateAndroidUiThread() {
261   MOZ_ASSERT(!sThread);
262   MOZ_ASSERT(!sMessageLoopAccessMonitor);
263   sTaskQueue = new LinkedList<AndroidUiTask>();
264   sTaskQueueLock = new Mutex("AndroidUiThreadTaskQueueLock");
265   sMessageLoopAccessMonitor =
266       new Monitor("AndroidUiThreadMessageLoopAccessMonitor");
267   sThreadDestroyed = false;
268   RefPtr<CreateOnUiThread> runnable = new CreateOnUiThread;
269   EnqueueTask(do_AddRef(runnable), 0);
270 }
271 
DestroyAndroidUiThread()272 void DestroyAndroidUiThread() {
273   MOZ_ASSERT(sThread);
274   RefPtr<DestroyOnUiThread> runnable = new DestroyOnUiThread;
275   EnqueueTask(do_AddRef(runnable), 0);
276   runnable->WaitForDestruction();
277   delete sMessageLoopAccessMonitor;
278   sMessageLoopAccessMonitor = nullptr;
279 }
280 
GetAndroidUiThreadMessageLoop()281 MessageLoop* GetAndroidUiThreadMessageLoop() {
282   if (!sMessageLoopAccessMonitor) {
283     return nullptr;
284   }
285 
286   MonitorAutoLock lock(*sMessageLoopAccessMonitor);
287   while (!sMessageLoop) {
288     lock.Wait();
289   }
290 
291   return sMessageLoop;
292 }
293 
GetAndroidUiThread()294 RefPtr<nsThread> GetAndroidUiThread() {
295   if (!sMessageLoopAccessMonitor) {
296     return nullptr;
297   }
298 
299   MonitorAutoLock lock(*sMessageLoopAccessMonitor);
300   while (!sThread) {
301     lock.Wait();
302   }
303 
304   return sThread;
305 }
306 
RunAndroidUiTasks()307 int64_t RunAndroidUiTasks() {
308   MutexAutoLock lock(*sTaskQueueLock);
309 
310   if (sThreadDestroyed) {
311     return -1;
312   }
313 
314   while (!sTaskQueue->isEmpty()) {
315     AndroidUiTask* task = sTaskQueue->getFirst();
316     const int64_t timeLeft = task->MillisecondsToRunTime();
317     if (timeLeft > 0) {
318       // this task (and therefore all remaining tasks)
319       // have not yet reached their runtime. return the
320       // time left until we should be called again
321       return timeLeft;
322     }
323 
324     // Retrieve task before unlocking/running.
325     nsCOMPtr<nsIRunnable> runnable(task->TakeTask());
326     // LinkedListElements auto remove from list upon destruction
327     delete task;
328 
329     // Unlock to allow posting new tasks reentrantly.
330     MutexAutoUnlock unlock(*sTaskQueueLock);
331     runnable->Run();
332     if (sThreadDestroyed) {
333       return -1;
334     }
335   }
336   return -1;
337 }
338 
339 }  // namespace mozilla
340