/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "MessagePump.h"

#include "nsIThread.h"
#include "nsITimer.h"
#include "nsICancelableRunnable.h"

#include "base/basictypes.h"
#include "base/logging.h"
#include "base/scoped_nsautorelease_pool.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "nsComponentManagerUtils.h"
#include "nsDebug.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsTimerImpl.h"
#include "nsXULAppAPI.h"
#include "prthread.h"

using base::TimeTicks;
using namespace mozilla::ipc;

#ifdef DEBUG
static MessagePump::Delegate* gFirstDelegate;
#endif

namespace mozilla {
namespace ipc {

class DoWorkRunnable final : public CancelableRunnable,
                             public nsITimerCallback {
 public:
  explicit DoWorkRunnable(MessagePump* aPump)
      : CancelableRunnable("ipc::DoWorkRunnable"), mPump(aPump) {
    MOZ_ASSERT(aPump);
  }

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIRUNNABLE
  NS_DECL_NSITIMERCALLBACK
  nsresult Cancel() override;

 private:
  ~DoWorkRunnable() = default;

  MessagePump* mPump;
  // DoWorkRunnable is designed as a stateless singleton.  Do not add stateful
  // members here!
};

} /* namespace ipc */
} /* namespace mozilla */

MessagePump::MessagePump(nsISerialEventTarget* aEventTarget)
    : mEventTarget(aEventTarget) {
  mDoWorkEvent = new DoWorkRunnable(this);
}

MessagePump::~MessagePump() = default;

void MessagePump::Run(MessagePump::Delegate* aDelegate) {
  MOZ_ASSERT(keep_running_);
  MOZ_RELEASE_ASSERT(NS_IsMainThread(),
                     "Use mozilla::ipc::MessagePumpForNonMainThreads instead!");
  MOZ_RELEASE_ASSERT(!mEventTarget);

  nsIThread* thisThread = NS_GetCurrentThread();
  MOZ_ASSERT(thisThread);

  mDelayedWorkTimer = NS_NewTimer();
  MOZ_ASSERT(mDelayedWorkTimer);

  base::ScopedNSAutoreleasePool autoReleasePool;

  for (;;) {
    autoReleasePool.Recycle();

    bool did_work = NS_ProcessNextEvent(thisThread, false) ? true : false;
    if (!keep_running_) break;

    // NB: it is crucial *not* to directly call |aDelegate->DoWork()|
    // here.  To ensure that MessageLoop tasks and XPCOM events have
    // equal priority, we sensitively rely on processing exactly one
    // Task per DoWorkRunnable XPCOM event.

    did_work |= aDelegate->DoDelayedWork(&delayed_work_time_);

    if (did_work && delayed_work_time_.is_null()) mDelayedWorkTimer->Cancel();

    if (!keep_running_) break;

    if (did_work) continue;

    did_work = aDelegate->DoIdleWork();
    if (!keep_running_) break;

    if (did_work) continue;

    // This will either sleep or process an event.
    NS_ProcessNextEvent(thisThread, true);
  }

  mDelayedWorkTimer->Cancel();

  keep_running_ = true;
}

void MessagePump::ScheduleWork() {
  // Make sure the event loop wakes up.
  if (mEventTarget) {
    mEventTarget->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL);
  } else {
    // Some things (like xpcshell) don't use the app shell and so Run hasn't
    // been called. We still need to wake up the main thread.
    NS_DispatchToMainThread(mDoWorkEvent);
  }
  event_.Signal();
}

void MessagePump::ScheduleWorkForNestedLoop() {
  // This method is called when our MessageLoop has just allowed
  // nested tasks.  In our setup, whenever that happens we know that
  // DoWork() will be called "soon", so there's no need to pay the
  // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent).
}

void MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) {
  // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as
  // ::Run().
  MOZ_RELEASE_ASSERT((!mEventTarget && NS_IsMainThread()) ||
                     mEventTarget->IsOnCurrentThread());

  if (!mDelayedWorkTimer) {
    mDelayedWorkTimer = NS_NewTimer();
    if (!mDelayedWorkTimer) {
      // Called before XPCOM has started up? We can't do this correctly.
      NS_WARNING("Delayed task might not run!");
      delayed_work_time_ = aDelayedTime;
      return;
    }
  }

  if (!delayed_work_time_.is_null()) {
    mDelayedWorkTimer->Cancel();
  }

  delayed_work_time_ = aDelayedTime;

  // TimeDelta's constructor initializes to 0
  base::TimeDelta delay;
  if (aDelayedTime > base::TimeTicks::Now())
    delay = aDelayedTime - base::TimeTicks::Now();

  uint32_t delayMS = uint32_t(delay.InMilliseconds());
  mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS,
                                      nsITimer::TYPE_ONE_SHOT);
}

nsISerialEventTarget* MessagePump::GetXPCOMThread() {
  if (mEventTarget) {
    return mEventTarget;
  }

  // Main thread
  return GetMainThreadSerialEventTarget();
}

void MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) {
  aDelegate->DoDelayedWork(&delayed_work_time_);
  if (!delayed_work_time_.is_null()) {
    ScheduleDelayedWork(delayed_work_time_);
  }
}

NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable,
                            nsITimerCallback)

NS_IMETHODIMP
DoWorkRunnable::Run() {
  MessageLoop* loop = MessageLoop::current();
  MOZ_ASSERT(loop);

  bool nestableTasksAllowed = loop->NestableTasksAllowed();

  // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will
  // always dispatch DoWork() below from what looks to MessageLoop like a nested
  // context.  So we unconditionally allow nesting here.
  loop->SetNestableTasksAllowed(true);
  loop->DoWork();
  loop->SetNestableTasksAllowed(nestableTasksAllowed);

  return NS_OK;
}

NS_IMETHODIMP
DoWorkRunnable::Notify(nsITimer* aTimer) {
  MessageLoop* loop = MessageLoop::current();
  MOZ_ASSERT(loop);

  bool nestableTasksAllowed = loop->NestableTasksAllowed();
  loop->SetNestableTasksAllowed(true);
  mPump->DoDelayedWork(loop);
  loop->SetNestableTasksAllowed(nestableTasksAllowed);

  return NS_OK;
}

nsresult DoWorkRunnable::Cancel() {
  // Workers require cancelable runnables, but we can't really cancel cleanly
  // here.  If we don't process this runnable then we will leave something
  // unprocessed in the message_loop.  Therefore, eagerly complete our work
  // instead by immediately calling Run().  Run() should be called separately
  // after this.  Unfortunately we cannot use flags to verify this because
  // DoWorkRunnable is a stateless singleton that can be in the event queue
  // multiple times simultaneously.
  MOZ_ALWAYS_SUCCEEDS(Run());
  return NS_OK;
}

void MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate) {
  if (mFirstRun) {
    MOZ_ASSERT(aDelegate && !gFirstDelegate);
#ifdef DEBUG
    gFirstDelegate = aDelegate;
#endif

    mFirstRun = false;
    if (NS_FAILED(XRE_RunAppShell())) {
      NS_WARNING("Failed to run app shell?!");
    }

    MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);
#ifdef DEBUG
    gFirstDelegate = nullptr;
#endif

    return;
  }

  MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate);

  // We can get to this point in startup with Tasks in our loop's
  // incoming_queue_ or pending_queue_, but without a matching
  // DoWorkRunnable().  In MessagePump::Run() above, we sensitively
  // depend on *not* directly calling delegate->DoWork(), because that
  // prioritizes Tasks above XPCOM events.  However, from this point
  // forward, any Task posted to our loop is guaranteed to have a
  // DoWorkRunnable enqueued for it.
  //
  // So we just flush the pending work here and move on.
  MessageLoop* loop = MessageLoop::current();
  bool nestableTasksAllowed = loop->NestableTasksAllowed();
  loop->SetNestableTasksAllowed(true);

  while (aDelegate->DoWork());

  loop->SetNestableTasksAllowed(nestableTasksAllowed);

  // Really run.
  mozilla::ipc::MessagePump::Run(aDelegate);
}

void MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate) {
  MOZ_ASSERT(keep_running_);
  MOZ_RELEASE_ASSERT(!NS_IsMainThread(),
                     "Use mozilla::ipc::MessagePump instead!");

  nsIThread* thread = NS_GetCurrentThread();
  MOZ_RELEASE_ASSERT(mEventTarget->IsOnCurrentThread());

  mDelayedWorkTimer = NS_NewTimer(mEventTarget);
  MOZ_ASSERT(mDelayedWorkTimer);

  // Chromium event notifications to be processed will be received by this
  // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that
  // were received before our thread is valid, however, will not generate
  // runnable wrappers. We must process any of these before we enter this
  // loop, or we will forever have unprocessed chromium messages in our queue.
  //
  // Note we would like to request a flush of the chromium event queue
  // using a runnable on the xpcom side, but some thread implementations
  // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork
  // calls dispatch on mEventTarget) before the thread processes an event. As
  // such, clear the queue manually.
  while (aDelegate->DoWork()) {
  }

  base::ScopedNSAutoreleasePool autoReleasePool;
  for (;;) {
    autoReleasePool.Recycle();

    bool didWork = NS_ProcessNextEvent(thread, false) ? true : false;
    if (!keep_running_) {
      break;
    }

    didWork |= aDelegate->DoDelayedWork(&delayed_work_time_);

    if (didWork && delayed_work_time_.is_null()) {
      mDelayedWorkTimer->Cancel();
    }

    if (!keep_running_) {
      break;
    }

    if (didWork) {
      continue;
    }

    DebugOnly<bool> didIdleWork = aDelegate->DoIdleWork();
    MOZ_ASSERT(!didIdleWork);
    if (!keep_running_) {
      break;
    }

    if (didWork) {
      continue;
    }

    // This will either sleep or process an event.
    NS_ProcessNextEvent(thread, true);
  }

  mDelayedWorkTimer->Cancel();

  keep_running_ = true;
}
