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