summaryrefslogtreecommitdiffstats
path: root/widget/android/AndroidUiThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/android/AndroidUiThread.cpp')
-rw-r--r--widget/android/AndroidUiThread.cpp377
1 files changed, 377 insertions, 0 deletions
diff --git a/widget/android/AndroidUiThread.cpp b/widget/android/AndroidUiThread.cpp
new file mode 100644
index 0000000000..dc8d56b163
--- /dev/null
+++ b/widget/android/AndroidUiThread.cpp
@@ -0,0 +1,377 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "base/message_loop.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/java/GeckoThreadWrappers.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "GeckoProfiler.h"
+#include "nsThread.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+
+#include <android/api-level.h>
+#include <pthread.h>
+
+using namespace mozilla;
+
+namespace {
+
+class AndroidUiThread;
+class AndroidUiTask;
+
+StaticAutoPtr<LinkedList<AndroidUiTask> > sTaskQueue;
+StaticAutoPtr<mozilla::Mutex> sTaskQueueLock;
+StaticRefPtr<AndroidUiThread> sThread;
+static bool sThreadDestroyed;
+static MessageLoop* sMessageLoop;
+static Atomic<Monitor*> sMessageLoopAccessMonitor;
+
+void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs);
+
+/*
+ * The AndroidUiThread is derived from nsThread so that nsIRunnable objects that
+ * get dispatched may be intercepted. Only nsIRunnable objects that need to be
+ * synchronously executed are passed into the nsThread to be queued. All other
+ * nsIRunnable object are immediately dispatched to the Android UI thread.
+ * AndroidUiThread is derived from nsThread instead of being an nsIEventTarget
+ * wrapper that contains an nsThread object because if nsIRunnable objects with
+ * a delay were dispatch directly to an nsThread object, such as obtained from
+ * nsThreadManager::GetCurrentThread(), the nsIRunnable could get stuck in the
+ * nsThread nsIRunnable queue. This is due to the fact that Android controls the
+ * event loop in the Android UI thread and has no knowledge of when the nsThread
+ * needs to be drained.
+ */
+
+class AndroidUiThread : public nsThread {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(AndroidUiThread, nsThread)
+ AndroidUiThread()
+ : nsThread(
+ MakeNotNull<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()),
+ nsThread::NOT_MAIN_THREAD, {.stackSize = 0}) {}
+
+ nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) override;
+ nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) override;
+
+ private:
+ ~AndroidUiThread() {}
+};
+
+NS_IMETHODIMP
+AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aFlags) {
+ EnqueueTask(std::move(aEvent), 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ EnqueueTask(std::move(aEvent), aDelayMs);
+ return NS_OK;
+}
+
+static void PumpEvents() { NS_ProcessPendingEvents(sThread.get()); }
+
+class ThreadObserver : public nsIThreadObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADOBSERVER
+
+ ThreadObserver() {}
+
+ private:
+ virtual ~ThreadObserver() {}
+};
+
+NS_IMPL_ISUPPORTS(ThreadObserver, nsIThreadObserver)
+
+NS_IMETHODIMP
+ThreadObserver::OnDispatchedEvent() {
+ EnqueueTask(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThreadObserver::OnProcessNextEvent(nsIThreadInternal* thread, bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) {
+ return NS_OK;
+}
+
+class AndroidUiTask : public LinkedListElement<AndroidUiTask> {
+ using TimeStamp = mozilla::TimeStamp;
+ using TimeDuration = mozilla::TimeDuration;
+
+ public:
+ explicit AndroidUiTask(already_AddRefed<nsIRunnable> aTask)
+ : mTask(aTask),
+ mRunTime() // Null timestamp representing no delay.
+ {}
+
+ AndroidUiTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
+ : mTask(aTask),
+ mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs)) {}
+
+ bool IsEarlierThan(const AndroidUiTask& aOther) const {
+ if (mRunTime) {
+ return aOther.mRunTime ? mRunTime < aOther.mRunTime : false;
+ }
+ // In the case of no delay, we're earlier if aOther has a delay.
+ // Otherwise, we're not earlier, to maintain task order.
+ return !!aOther.mRunTime;
+ }
+
+ int64_t MillisecondsToRunTime() const {
+ if (mRunTime) {
+ return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds());
+ }
+ return 0;
+ }
+
+ already_AddRefed<nsIRunnable> TakeTask() { return mTask.forget(); }
+
+ private:
+ nsCOMPtr<nsIRunnable> mTask;
+ const TimeStamp mRunTime;
+};
+
+class CreateOnUiThread : public Runnable {
+ public:
+ CreateOnUiThread() : Runnable("CreateOnUiThread") {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!sThreadDestroyed);
+ MOZ_ASSERT(sMessageLoopAccessMonitor);
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ sThread = new AndroidUiThread();
+ sThread->InitCurrentThread();
+ sThread->SetObserver(new ThreadObserver());
+ RegisterThreadWithProfiler();
+ sMessageLoop =
+ new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get());
+ lock.NotifyAll();
+ return NS_OK;
+ }
+
+ private:
+ static void RegisterThreadWithProfiler() {
+#if defined(MOZ_GECKO_PROFILER)
+ // We don't use the PROFILER_REGISTER_THREAD macro here because by this
+ // point the Android UI thread is already quite a ways into its stack;
+ // the profiler's sampler thread will ignore a lot of frames if we do not
+ // provide a better value for the stack top. We'll manually obtain that
+ // info via pthreads.
+
+ // Fallback address if any pthread calls fail
+ char fallback;
+ char* stackTop = &fallback;
+
+ auto regOnExit = MakeScopeExit(
+ [&stackTop]() { profiler_register_thread("AndroidUI", stackTop); });
+
+ // Bionic does not properly support pthread_attr_getstack for the UI thread
+ // until Lollipop (API 21).
+# if __ANDROID_API__ >= __ANDROID_API_L__
+ pthread_attr_t attrs;
+ if (pthread_getattr_np(pthread_self(), &attrs)) {
+ return;
+ }
+
+ void* stackBase;
+ size_t stackSize;
+ if (pthread_attr_getstack(&attrs, &stackBase, &stackSize)) {
+ return;
+ }
+
+ stackTop = static_cast<char*>(stackBase) + stackSize - 1;
+# endif // __ANDROID_API__ >= __ANDROID_API_L__
+#endif // defined(MOZ_GECKO_PROFILER)
+ }
+};
+
+class DestroyOnUiThread : public Runnable {
+ public:
+ DestroyOnUiThread() : Runnable("DestroyOnUiThread"), mDestroyed(false) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!sThreadDestroyed);
+ MOZ_ASSERT(sMessageLoopAccessMonitor);
+ MOZ_ASSERT(sTaskQueue);
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ sThreadDestroyed = true;
+
+ {
+ // Flush the queue
+ MutexAutoLock lock(*sTaskQueueLock);
+ while (AndroidUiTask* task = sTaskQueue->getFirst()) {
+ delete task;
+ }
+ }
+
+ delete sMessageLoop;
+ sMessageLoop = nullptr;
+ MOZ_ASSERT(sThread);
+ PROFILER_UNREGISTER_THREAD();
+ nsThreadManager::get().UnregisterCurrentThread(*sThread);
+ sThread = nullptr;
+ mDestroyed = true;
+ lock.NotifyAll();
+ return NS_OK;
+ }
+
+ void WaitForDestruction() {
+ MOZ_ASSERT(sMessageLoopAccessMonitor);
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ while (!mDestroyed) {
+ lock.Wait();
+ }
+ }
+
+ private:
+ bool mDestroyed;
+};
+
+void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs) {
+ if (sThreadDestroyed) {
+ return;
+ }
+
+ // add the new task into the sTaskQueue, sorted with
+ // the earliest task first in the queue
+ AndroidUiTask* newTask =
+ (aDelayMs ? new AndroidUiTask(std::move(aTask), aDelayMs)
+ : new AndroidUiTask(std::move(aTask)));
+
+ bool headOfList = false;
+ {
+ MOZ_ASSERT(sTaskQueue);
+ MOZ_ASSERT(sTaskQueueLock);
+ MutexAutoLock lock(*sTaskQueueLock);
+
+ AndroidUiTask* task = sTaskQueue->getFirst();
+
+ while (task) {
+ if (newTask->IsEarlierThan(*task)) {
+ task->setPrevious(newTask);
+ break;
+ }
+ task = task->getNext();
+ }
+
+ if (!newTask->isInList()) {
+ sTaskQueue->insertBack(newTask);
+ }
+ headOfList = !newTask->getPrevious();
+ }
+
+ if (headOfList) {
+ // if we're inserting it at the head of the queue, notify Java because
+ // we need to get a callback at an earlier time than the last scheduled
+ // callback
+ java::GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
+ }
+}
+
+} // namespace
+
+namespace mozilla {
+
+void CreateAndroidUiThread() {
+ MOZ_ASSERT(!sThread);
+ MOZ_ASSERT(!sMessageLoopAccessMonitor);
+ sTaskQueue = new LinkedList<AndroidUiTask>();
+ sTaskQueueLock = new Mutex("AndroidUiThreadTaskQueueLock");
+ sMessageLoopAccessMonitor =
+ new Monitor("AndroidUiThreadMessageLoopAccessMonitor");
+ sThreadDestroyed = false;
+ RefPtr<CreateOnUiThread> runnable = new CreateOnUiThread;
+ EnqueueTask(do_AddRef(runnable), 0);
+}
+
+void DestroyAndroidUiThread() {
+ MOZ_ASSERT(sThread);
+ RefPtr<DestroyOnUiThread> runnable = new DestroyOnUiThread;
+ EnqueueTask(do_AddRef(runnable), 0);
+ runnable->WaitForDestruction();
+ delete sMessageLoopAccessMonitor;
+ sMessageLoopAccessMonitor = nullptr;
+}
+
+MessageLoop* GetAndroidUiThreadMessageLoop() {
+ if (!sMessageLoopAccessMonitor) {
+ return nullptr;
+ }
+
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ while (!sMessageLoop) {
+ lock.Wait();
+ }
+
+ return sMessageLoop;
+}
+
+RefPtr<nsThread> GetAndroidUiThread() {
+ if (!sMessageLoopAccessMonitor) {
+ return nullptr;
+ }
+
+ MonitorAutoLock lock(*sMessageLoopAccessMonitor);
+ while (!sThread) {
+ lock.Wait();
+ }
+
+ return sThread;
+}
+
+int64_t RunAndroidUiTasks() {
+ MutexAutoLock lock(*sTaskQueueLock);
+
+ if (sThreadDestroyed) {
+ return -1;
+ }
+
+ while (!sTaskQueue->isEmpty()) {
+ AndroidUiTask* task = sTaskQueue->getFirst();
+ const int64_t timeLeft = task->MillisecondsToRunTime();
+ if (timeLeft > 0) {
+ // this task (and therefore all remaining tasks)
+ // have not yet reached their runtime. return the
+ // time left until we should be called again
+ return timeLeft;
+ }
+
+ // Retrieve task before unlocking/running.
+ nsCOMPtr<nsIRunnable> runnable(task->TakeTask());
+ // LinkedListElements auto remove from list upon destruction
+ delete task;
+
+ // Unlock to allow posting new tasks reentrantly.
+ MutexAutoUnlock unlock(*sTaskQueueLock);
+ runnable->Run();
+ if (sThreadDestroyed) {
+ return -1;
+ }
+ }
+ return -1;
+}
+
+} // namespace mozilla