forked from mirrors/gecko-dev
		
	 d57c9498f6
			
		
	
	
		d57c9498f6
		
	
	
	
	
		
			
			This flag is not supported by most event targets and can have unexpected side effects (namely spinning a nested event loop). All consumers have been replaced with a new function which is more explicit about this side effect. Differential Revision: https://phabricator.services.mozilla.com/D173985
		
			
				
	
	
		
			377 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			377 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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
 |