/* 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 "nsISupports.idl"
#include "nsIRunnable.idl"
%{C++
#include "nsCOMPtr.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Atomics.h"
#include "mozilla/TypedEnumBits.h"

class nsITargetShutdownTask;
%}

native alreadyAddRefed_nsIRunnable(already_AddRefed<nsIRunnable>);
[ptr] native nsITargetShutdownTask(nsITargetShutdownTask);

[builtinclass, scriptable, rust_sync, uuid(a03b8b63-af8b-4164-b0e5-c41e8b2b7cfa)]
interface nsIEventTarget : nsISupports
{
  /**
   * Flags used to customize the behaviour of a `Dispatch` call.
   *
   * WARNING: These flags are generally hints, and are not universally supported
   * by all nsIEventTarget implementations. An implementation may ignore some or
   * all of these flags.
   *
   * HISTORIC NOTE: There used to be a `DISPATCH_SYNC` flag which would spin a
   * nested event loop while dispatching (and could not be safely ignored by
   * event target implementations), but this has since been removed.
   */
  cenum DispatchFlags : 32 {
    /**
     * DISPATCH_NORMAL corresponds to the absense of other flags. This is used
     * for dispatching async events to an event target which will be queued for
     * later processing.
     */
    DISPATCH_NORMAL = 0,

    // NOTE: 1 << 0 used to be DISPATCH_SYNC

    /**
     * This flag specifies that the dispatch is occurring from a running event
     * that was dispatched to the same event target, and that event is about to
     * finish.
     *
     * A thread pool can use this as an optimization hint to not spin up
     * another thread, since the current thread is about to become idle.
    */
    DISPATCH_AT_END = 1 << 1,

    /**
     * This flag specifies that the dispatched event may block the thread on
     * which it executes, usually by doing some sort of I/O.  This information
     * may be used by the event target to execute the job on a thread
     * specifically dedicated to doing I/O, leaving other threads available for
     * CPU-intensive work.
     */
    DISPATCH_EVENT_MAY_BLOCK = 1 << 2,

    /**
     * This flag specifies that the dispatched event should be delivered to the
     * target thread even if the thread has been configured to block dispatching
     * of runnables. This is generally done for threads which have their own
     * internal event loop, such as thread pools or the timer thread, and will not
     * service runnables dispatched to them until shutdown.
     */
    DISPATCH_IGNORE_BLOCK_DISPATCH = 1 << 3,

    /**
     * This flag specifies that the caller of the given dispatch is prepared to
     * handle dispatch failures itself.
     *
     * Some nsIEventTarget implementations, such as nsThread, leak the passed-in
     * runnable if there is a failure when dispatching (e.g. due to the thread
     * being destroyed). This is done as many callers are unprepared to handle
     * calls to Dispatch failing, and could otherwise e.g. destroy a object on
     * the wrong thread.
     *
     * By specifying DISPATCH_FALLIBLE, you communicate to the nsIEventTarget
     * implementation that you are prepared to handle the consequences of the
     * dispatch failing. nsIEventTarget implementations should neither leak
     * passed-in runnables nor fire assertions on state-related error codepaths
     * (such as late shutdown dispatch assertions) when DISPATCH_FALLIBLE is
     * specified.
     *
     * When using DISPATCH_FALLIBLE, the caller must be careful that the event
     * object is safe to discard on the calling thread, and that other systems
     * do not depend on the runnable completing on error cases.
     *
     * NOTE: Many nsIEventTarget implementations (including TaskQueue and
     * nsThreadPool) never leak the passed-in runnable on failure, whether or
     * not this flag is specified. Dispatched events may always be released by
     * the dispatcher on error for those types.
     *
     * See bug 803174, bug 1237213 and bug 1824189 for historical context.
     */
    DISPATCH_FALLIBLE = 1 << 4,
  };

  /**
   * IsOnCurrentThread() should return true if events dispatched to this target
   * can possibly run on the current thread, and false otherwise. In the case
   * of an nsIEventTarget for a thread pool, it should return true on all
   * threads in the pool. In the case of a non-thread nsIEventTarget such as
   * ThrottledEventQueue, it should return true on the thread where events are
   * expected to be processed, even if no events from the queue are actually
   * being processed right now.
   *
   * When called on an nsISerialEventTarget, IsOnCurrentThread can be used to
   * ensure that no other thread has "ownership" of the event target. As such,
   * it's useful for asserting that an object is only used on a particular
   * thread. IsOnCurrentThread can't guarantee that the current event has been
   * dispatched through a particular event target.
   *
   * The infallible version of IsOnCurrentThread() is optimized to avoid a
   * virtual call for non-thread event targets. Thread targets should set
   * mThread to their virtual PRThread. Non-thread targets should leave
   * mThread null and implement IsOnCurrentThreadInfallible() to
   * return the correct answer.
   *
   * The fallible version of IsOnCurrentThread may return errors, such as during
   * shutdown. If it does not return an error, it should return the same result
   * as the infallible version. The infallible method should return the correct
   * result regardless of whether the fallible method returns an error.
   */
  %{C++
public:
  // Infallible. Defined in nsThreadUtils.cpp. Delegates to
  // IsOnCurrentThreadInfallible when mThread is null.
  bool IsOnCurrentThread();

protected:
  mozilla::Atomic<PRThread*> mThread;

  nsIEventTarget() : mThread(nullptr) {}
  %}
  // Note that this method is protected.  We define it through IDL, rather than
  // in a %{C++ block, to ensure that the correct method indices are recorded
  // for XPConnect purposes.
  [noscript,notxpcom] boolean isOnCurrentThreadInfallible();
  %{C++
public:
  %}

  // Fallible version of IsOnCurrentThread.
  boolean isOnCurrentThread();

  /* until we can get rid of all uses, keep the non-alreadyAddRefed<> version */
%{C++
    nsresult Dispatch(nsIRunnable* aEvent, DispatchFlags aFlags) {
      return Dispatch(nsCOMPtr<nsIRunnable>(aEvent).forget(), aFlags);
    }
%}

  /**
   * Dispatch an event to this event target.  This function may be called from
   * any thread, and it may be called re-entrantly.
   *
   * @param event
   *   The alreadyAddRefed<> event to dispatch.
   *   NOTE that the event should be leaked if it fails to dispatch unless
   *   NS_DISPATCH_FALLIBLE is passed.
   * @param flags
   *   The flags modifying event dispatch.  The flags are described in detail
   *   above.
   *
   * @throws NS_ERROR_INVALID_ARG
   *   Indicates that event is null.
   * @throws NS_ERROR_UNEXPECTED
   *   Indicates that the thread is shutting down and has finished processing
   * events, so this event would never run and has not been dispatched.
   */
  [noscript, binaryname(Dispatch)] void dispatchFromC(in alreadyAddRefed_nsIRunnable event,
                                                      [default(DISPATCH_NORMAL)] in nsIEventTarget_DispatchFlags flags);

  /**
   * Version of Dispatch to expose to JS, which doesn't require an alreadyAddRefed<>
   * (it will be converted to that internally)
   *
   * @param event
   *   The (raw) event to dispatch.
   * @param flags
   *   The flags modifying event dispatch.  The flags are described in detail
   *   above.
   *
   * @throws NS_ERROR_INVALID_ARG
   *   Indicates that event is null.
   * @throws NS_ERROR_UNEXPECTED
   *   Indicates that the thread is shutting down and has finished processing
   * events, so this event would never run and has not been dispatched.
   */
  [binaryname(DispatchFromScript)] void dispatch(in nsIRunnable event, in nsIEventTarget_DispatchFlags flags);

  /**
   * Dispatch an event to this event target, but do not run it before delay
   * milliseconds have passed.  This function may be called from any thread.
   *
   * @param event
   *   The alreadyAddrefed<> event to dispatch.
   * @param delay
   *   The delay (in ms) before running the event.  If event does not rise to
   *   the top of the event queue before the delay has passed, it will be set
   *   aside to execute once the delay has passed.  Otherwise, it will be
   *   executed immediately.
   *
   * @throws NS_ERROR_INVALID_ARG
   *   Indicates that event is null.
   * @throws NS_ERROR_UNEXPECTED
   *   Indicates that the thread is shutting down and has finished processing
   *   events, so this event would never run and has not been dispatched, or
   *   that delay is zero.
   */
  [noscript] void delayedDispatch(in alreadyAddRefed_nsIRunnable event, in unsigned long delay);

  /**
   * Register an task to be run on this event target when it begins shutting
   * down.  Shutdown tasks may be run in any order, and this function may be
   * called from any thread.
   *
   * The event target may or may not continue accepting events during or after
   * the shutdown task. The precise behaviour here depends on the event target.
   *
   * @param task
   *   The task to be registered to the target thread.
   *   NOTE that unlike `dispatch`, this will not leak the task if it fails.
   *
   * @throws NS_ERROR_INVALID_ARG
   *   Indicates that task is null.
   * @throws NS_ERROR_NOT_IMPLEMENTED
   *   Indicates that this event target doesn't support shutdown tasks.
   * @throws NS_ERROR_UNEXPECTED
   *   Indicates that the thread is already shutting down, and no longer
   *   accepting events.
   */
  [noscript] void registerShutdownTask(in nsITargetShutdownTask task);

  /**
   * Unregisters an task previously registered with registerShutdownTask.  This
   * function may be called from any thread.
   *
   * @param task
   *   The task previously registered with registerShutdownTask
   *
   * @throws NS_ERROR_INVALID_ARG
   *   Indicates that task is null.
   * @throws NS_ERROR_NOT_IMPLEMENTED
   *   Indicates that this event target doesn't support shutdown tasks.
   * @throws NS_ERROR_UNEXPECTED
   *   Indicates that the thread is already shutting down, and no longer
   *   accepting events, or that the shutdown task cannot be found.
   */
  [noscript] void unregisterShutdownTask(in nsITargetShutdownTask task);

  cenum FeatureFlags : 32 {
      SUPPORTS_BASE = 0,
      // Indicates that the event target supports shutdown tasks.
      SUPPORTS_SHUTDOWN_TASKS = 1 << 0,
      // Indicates whether or not it is possible to dispatch events to this
      // event target while its shutdown tasks are executed from those.
      SUPPORTS_SHUTDOWN_TASK_DISPATCH = 1 << 1,
      // Indicates whether the event target supports prioritization.
      // Events implementing nsIRunnablePriority can get run in a different
      // order than they were dispatched.
      SUPPORTS_PRIORITIZATION = 1 << 2,
  };

  /**
   * Returns a FeatureFlags bitmask indicating supported features.
   */
  [noscript, notxpcom] nsIEventTarget_FeatureFlags getFeatures();

};

%{C++
// convenient aliases:
#define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL
#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END
#define NS_DISPATCH_EVENT_MAY_BLOCK nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK
#define NS_DISPATCH_IGNORE_BLOCK_DISPATCH nsIEventTarget::DISPATCH_IGNORE_BLOCK_DISPATCH
#define NS_DISPATCH_FALLIBLE nsIEventTarget::DISPATCH_FALLIBLE

// Convenience bitwise operators for the flag cenum
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsIEventTarget::DispatchFlags)
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsIEventTarget::FeatureFlags)

// Convenient NS_DECL variant that includes some C++-only methods.
#define NS_DECL_NSIEVENTTARGET_FULL                                   \
    NS_DECL_NSIEVENTTARGET                                            \
    /* Avoid hiding these methods */                                  \
    using nsIEventTarget::Dispatch;                                   \
    using nsIEventTarget::IsOnCurrentThread;
%}
