зеркало из https://github.com/mozilla/gecko-dev.git
369 строки
9.1 KiB
C++
369 строки
9.1 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 "GeneratedJNIWrappers.h"
|
|
#include "mozilla/Atomics.h"
|
|
#include "mozilla/EventQueue.h"
|
|
#include "mozilla/LinkedList.h"
|
|
#include "mozilla/Monitor.h"
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/ThreadEventQueue.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsThread.h"
|
|
#include "nsThreadManager.h"
|
|
#include "nsThreadUtils.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_DECL_ISUPPORTS_INHERITED
|
|
AndroidUiThread()
|
|
: nsThread(WrapNotNull(new ThreadEventQueue<mozilla::EventQueue>(MakeUnique<mozilla::EventQueue>())),
|
|
nsThread::NOT_MAIN_THREAD, 0)
|
|
{}
|
|
|
|
nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override;
|
|
nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs) override;
|
|
|
|
private:
|
|
~AndroidUiThread()
|
|
{}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(AndroidUiThread, nsThread)
|
|
|
|
NS_IMETHODIMP
|
|
AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
|
|
{
|
|
if (aFlags & NS_DISPATCH_SYNC) {
|
|
return nsThread::Dispatch(Move(aEvent), aFlags);
|
|
} else {
|
|
EnqueueTask(Move(aEvent), 0);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs)
|
|
{
|
|
EnqueueTask(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:
|
|
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());
|
|
sMessageLoop = new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get());
|
|
lock.NotifyAll();
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
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);
|
|
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(mozilla::Move(aTask), aDelayMs)
|
|
: new AndroidUiTask(mozilla::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
|
|
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
|