/* -*- Mode: C++; tab-width: 2; 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/. */

#ifndef mozilla_BasicEvents_h__
#define mozilla_BasicEvents_h__

#include <stdint.h>
#include <type_traits>

#include "mozilla/EventForwards.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/layers/LayersTypes.h"
#include "nsCOMPtr.h"
#include "nsAtom.h"
#include "nsISupportsImpl.h"
#include "nsIWidget.h"
#include "nsString.h"
#include "Units.h"

#ifdef DEBUG
#  include "nsXULAppAPI.h"
#endif  // #ifdef DEBUG

class nsIPrincipal;

namespace IPC {
template <typename T>
struct ParamTraits;
}  // namespace IPC

namespace mozilla {

class EventTargetChainItem;

enum class CrossProcessForwarding {
  // eStop prevents the event to be sent to remote process.
  eStop,
  // eAllow keeps current state of the event whether it's sent to remote
  // process.  In other words, eAllow does NOT mean that making the event
  // sent to remote process when IsCrossProcessForwardingStopped() returns
  // true.
  eAllow,
};

/******************************************************************************
 * mozilla::BaseEventFlags
 *
 * BaseEventFlags must be a POD struct for safe to use memcpy (including
 * in ParamTraits<BaseEventFlags>).  So don't make virtual methods, constructor,
 * destructor and operators.
 * This is necessary for VC which is NOT C++0x compiler.
 ******************************************************************************/

struct BaseEventFlags {
 public:
  // If mIsTrusted is true, the event is a trusted event.  Otherwise, it's
  // an untrusted event.
  bool mIsTrusted : 1;
  // If mInBubblingPhase is true, the event is in bubbling phase or target
  // phase.
  bool mInBubblingPhase : 1;
  // If mInCapturePhase is true, the event is in capture phase or target phase.
  bool mInCapturePhase : 1;
  // If mInTargetPhase is true, the event is in target phase.
  bool mInTargetPhase : 1;
  // If mInSystemGroup is true, the event is being dispatched in system group.
  bool mInSystemGroup : 1;
  // If mCancelable is true, the event can be consumed.  I.e., calling
  // dom::Event::PreventDefault() can prevent the default action.
  bool mCancelable : 1;
  // If mBubbles is true, the event can bubble.  Otherwise, cannot be handled
  // in bubbling phase.
  bool mBubbles : 1;
  // If mPropagationStopped is true, dom::Event::StopPropagation() or
  // dom::Event::StopImmediatePropagation() has been called.
  bool mPropagationStopped : 1;
  // If mImmediatePropagationStopped is true,
  // dom::Event::StopImmediatePropagation() has been called.
  // Note that mPropagationStopped must be true when this is true.
  bool mImmediatePropagationStopped : 1;
  // If mDefaultPrevented is true, the event has been consumed.
  // E.g., dom::Event::PreventDefault() has been called or
  // the default action has been performed.
  bool mDefaultPrevented : 1;
  // If mDefaultPreventedByContent is true, the event has been
  // consumed by content.
  // Note that mDefaultPrevented must be true when this is true.
  bool mDefaultPreventedByContent : 1;
  // If mDefaultPreventedByChrome is true, the event has been
  // consumed by chrome.
  // Note that mDefaultPrevented must be true when this is true.
  bool mDefaultPreventedByChrome : 1;
  // mMultipleActionsPrevented may be used when default handling don't want to
  // be prevented, but only one of the event targets should handle the event.
  // For example, when a <label> element is in another <label> element and
  // the first <label> element is clicked, that one may set this true.
  // Then, the second <label> element won't handle the event.
  bool mMultipleActionsPrevented : 1;
  // Similar to above but expected to be used during PreHandleEvent phase.
  bool mMultiplePreActionsPrevented : 1;
  // If mIsBeingDispatched is true, the DOM event created from the event is
  // dispatching into the DOM tree and not completed.
  bool mIsBeingDispatched : 1;
  // If mDispatchedAtLeastOnce is true, the event has been dispatched
  // as a DOM event and the dispatch has been completed in the process.
  // So, this is false even if the event has already been dispatched
  // in another process.
  bool mDispatchedAtLeastOnce : 1;
  // If mIsSynthesizedForTests is true, the event has been synthesized for
  // automated tests or something hacky approach of an add-on.
  bool mIsSynthesizedForTests : 1;
  // If mExceptionWasRaised is true, one of the event handlers has raised an
  // exception.
  bool mExceptionWasRaised : 1;
  // If mRetargetToNonNativeAnonymous is true and the target is in a non-native
  // native anonymous subtree, the event target is set to mOriginalTarget.
  bool mRetargetToNonNativeAnonymous : 1;
  // If mNoContentDispatch is true, the event is never dispatched to the
  // event handlers which are added to the contents, onfoo attributes and
  // properties.  Note that this flag is ignored when
  // EventChainPreVisitor::mForceContentDispatch is set true.  For exapmle,
  // window and document object sets it true.  Therefore, web applications
  // can handle the event if they add event listeners to the window or the
  // document.
  // XXX This is an ancient and broken feature, don't use this for new bug
  //     as far as possible.
  bool mNoContentDispatch : 1;
  // If mOnlyChromeDispatch is true, the event is dispatched to only chrome.
  bool mOnlyChromeDispatch : 1;
  // Indicates if the key combination is reserved by chrome.  This is set by
  // MarkAsReservedByChrome().
  bool mIsReservedByChrome : 1;
  // If mOnlySystemGroupDispatchInContent is true, event listeners added to
  // the default group for non-chrome EventTarget won't be called.
  // Be aware, if this is true, EventDispatcher needs to check if each event
  // listener is added to chrome node, so, don't set this to true for the
  // events which are fired a lot of times like eMouseMove.
  bool mOnlySystemGroupDispatchInContent : 1;
  // If mOnlySystemGroupDispatch is true, the event will be dispatched only to
  // event listeners added in the system group.
  bool mOnlySystemGroupDispatch : 1;
  // The event's action will be handled by APZ. The main thread should not
  // perform its associated action.
  bool mHandledByAPZ : 1;
  // True if the event is currently being handled by an event listener that
  // was registered as a passive listener.
  bool mInPassiveListener : 1;
  // If mComposed is true, the event fired by nodes in shadow DOM can cross the
  // boundary of shadow DOM and light DOM.
  bool mComposed : 1;
  // Similar to mComposed. Set it to true to allow events cross the boundary
  // between native non-anonymous content and native anonymouse content
  bool mComposedInNativeAnonymousContent : 1;
  // Set to true for events which are suppressed or delayed so that later a
  // DelayedEvent of it is dispatched. This is used when parent side process
  // the key event after content side, and may drop the event if the event
  // was suppressed or delayed in contents side.
  // It is also set to true for the events (in a DelayedInputEvent), which will
  // be dispatched afterwards.
  bool mIsSuppressedOrDelayed : 1;
  // Certain mouse events can be marked as positionless to return 0 from
  // coordinate related getters.
  bool mIsPositionless : 1;

  // Flags managing state of propagation between processes.
  // Note the the following flags shouldn't be referred directly.  Use utility
  // methods instead.

  // If mNoRemoteProcessDispatch is true, the event is not allowed to be sent
  // to remote process.
  bool mNoRemoteProcessDispatch : 1;
  // If mWantReplyFromContentProcess is true, the event will be redispatched
  // in the parent process after the content process has handled it. Useful
  // for when the parent process need the know first how the event was used
  // by content before handling it itself.
  bool mWantReplyFromContentProcess : 1;
  // If mPostedToRemoteProcess is true, the event has been posted to the
  // remote process (but it's not handled yet if it's not a duplicated event
  // instance).
  bool mPostedToRemoteProcess : 1;
  // If mCameFromAnotherProcess is true, the event came from another process.
  bool mCameFromAnotherProcess : 1;

  /**
   * Helper methods for methods of DOM Event.
   */
  inline void StopPropagation() { mPropagationStopped = true; }
  inline void StopImmediatePropagation() {
    StopPropagation();
    mImmediatePropagationStopped = true;
  }
  inline void PreventDefault(bool aCalledByDefaultHandler = true) {
    if (!mCancelable) {
      return;
    }
    mDefaultPrevented = true;
    // Note that even if preventDefault() has already been called by chrome,
    // a call of preventDefault() by content needs to overwrite
    // mDefaultPreventedByContent to true because in such case, defaultPrevented
    // must be true when web apps check it after they call preventDefault().
    if (aCalledByDefaultHandler) {
      StopCrossProcessForwarding();
      mDefaultPreventedByChrome = true;
    } else {
      mDefaultPreventedByContent = true;
    }
  }
  // This should be used only before dispatching events into the DOM tree.
  inline void PreventDefaultBeforeDispatch(
      CrossProcessForwarding aCrossProcessForwarding) {
    if (!mCancelable) {
      return;
    }
    mDefaultPrevented = true;
    if (aCrossProcessForwarding == CrossProcessForwarding::eStop) {
      StopCrossProcessForwarding();
    }
  }
  inline bool DefaultPrevented() const { return mDefaultPrevented; }
  inline bool DefaultPreventedByContent() const {
    MOZ_ASSERT(!mDefaultPreventedByContent || DefaultPrevented());
    return mDefaultPreventedByContent;
  }
  inline bool IsTrusted() const { return mIsTrusted; }
  inline bool PropagationStopped() const { return mPropagationStopped; }

  // Helper methods to access flags managing state of propagation between
  // processes.

  /**
   * Prevent to be dispatched to remote process.
   */
  inline void StopCrossProcessForwarding() {
    MOZ_ASSERT(!mPostedToRemoteProcess);
    mNoRemoteProcessDispatch = true;
    mWantReplyFromContentProcess = false;
  }
  /**
   * Return true if the event shouldn't be dispatched to remote process.
   */
  inline bool IsCrossProcessForwardingStopped() const {
    return mNoRemoteProcessDispatch;
  }
  /**
   * Mark the event as waiting reply from remote process.
   * If the caller needs to win other keyboard event handlers in chrome,
   * the caller should call StopPropagation() too.
   * Otherwise, if the caller just needs to know if the event is consumed by
   * either content or chrome, it should just call this because the event
   * may be reserved by chrome and it needs to be dispatched into the DOM
   * tree in chrome for checking if it's reserved before being sent to any
   * remote processes.
   */
  inline void MarkAsWaitingReplyFromRemoteProcess() {
    MOZ_ASSERT(!mPostedToRemoteProcess);
    mNoRemoteProcessDispatch = false;
    mWantReplyFromContentProcess = true;
  }
  /**
   * Reset "waiting reply from remote process" state.  This is useful when
   * you dispatch a copy of an event coming from different process.
   */
  inline void ResetWaitingReplyFromRemoteProcessState() {
    if (IsWaitingReplyFromRemoteProcess()) {
      // FYI: mWantReplyFromContentProcess is also used for indicating
      //      "handled in remote process" state.  Therefore, only when
      //      IsWaitingReplyFromRemoteProcess() returns true, this should
      //      reset the flag.
      mWantReplyFromContentProcess = false;
    }
  }
  /**
   * Return true if the event handler should wait reply event.  I.e., if this
   * returns true, any event handler should do nothing with the event.
   */
  inline bool IsWaitingReplyFromRemoteProcess() const {
    return !mNoRemoteProcessDispatch && mWantReplyFromContentProcess;
  }
  /**
   * Mark the event as already handled in the remote process.  This should be
   * called when initializing reply events.
   */
  inline void MarkAsHandledInRemoteProcess() {
    mNoRemoteProcessDispatch = true;
    mWantReplyFromContentProcess = true;
    mPostedToRemoteProcess = false;
  }
  /**
   * Return true if the event has already been handled in the remote process.
   */
  inline bool IsHandledInRemoteProcess() const {
    return mNoRemoteProcessDispatch && mWantReplyFromContentProcess;
  }
  /**
   * Return true if the event should be sent back to its parent process.
   */
  inline bool WantReplyFromContentProcess() const {
    MOZ_ASSERT(!XRE_IsParentProcess());
    return IsWaitingReplyFromRemoteProcess();
  }
  /**
   * Mark the event has already posted to a remote process.
   */
  inline void MarkAsPostedToRemoteProcess() {
    MOZ_ASSERT(!IsCrossProcessForwardingStopped());
    mPostedToRemoteProcess = true;
  }
  /**
   * Reset the cross process dispatching state.  This should be used when a
   * process receives the event because the state is in the sender.
   */
  inline void ResetCrossProcessDispatchingState() {
    MOZ_ASSERT(!IsCrossProcessForwardingStopped());
    mPostedToRemoteProcess = false;
    // Ignore propagation state in the remote process if it's marked as
    // "waiting reply from remote process" because the process needs to
    // stop propagation in the process until receiving a reply event.
    // Note that the propagation stopped flag is important for the reply event
    // handler in the main process because it's used for making whether it's
    // ignored by the remote process or not.
    if (!XRE_IsParentProcess() && IsWaitingReplyFromRemoteProcess()) {
      mPropagationStopped = mImmediatePropagationStopped = false;
    }
    // mDispatchedAtLeastOnce indicates the state in current process.
    mDispatchedAtLeastOnce = false;
  }
  /**
   * Return true if the event has been posted to a remote process.
   * Note that MarkAsPostedToRemoteProcess() is called by
   * ParamTraits<mozilla::WidgetEvent>.  Therefore, it *might* be possible
   * that posting the event failed even if this returns true.  But that must
   * really rare.  If that'd be problem for you, you should unmark this in
   * BrowserParent or somewhere.
   */
  inline bool HasBeenPostedToRemoteProcess() const {
    return mPostedToRemoteProcess;
  }
  /**
   * Return true if the event came from another process.
   */
  inline bool CameFromAnotherProcess() const { return mCameFromAnotherProcess; }
  /**
   * Mark the event as coming from another process.
   */
  inline void MarkAsComingFromAnotherProcess() {
    mCameFromAnotherProcess = true;
  }
  /**
   * Mark the event is reserved by chrome.  I.e., shouldn't be dispatched to
   * content because it shouldn't be cancelable.
   */
  inline void MarkAsReservedByChrome() {
    MOZ_ASSERT(!mPostedToRemoteProcess);
    mIsReservedByChrome = true;
    // For reserved commands (such as Open New Tab), we don't need to wait for
    // the content to answer, neither to give a chance for content to override
    // its behavior.
    StopCrossProcessForwarding();
    // If the event is reserved by chrome, we shouldn't expose the event to
    // web contents because such events shouldn't be cancelable.  So, it's not
    // good behavior to fire such events but to ignore the defaultPrevented
    // attribute value in chrome.
    mOnlySystemGroupDispatchInContent = true;
  }
  /**
   * Return true if the event is reserved by chrome.
   */
  inline bool IsReservedByChrome() const {
    MOZ_ASSERT(!mIsReservedByChrome || (IsCrossProcessForwardingStopped() &&
                                        mOnlySystemGroupDispatchInContent));
    return mIsReservedByChrome;
  }

  inline void Clear() { SetRawFlags(0); }
  // Get if either the instance's bit or the aOther's bit is true, the
  // instance's bit becomes true.  In other words, this works like:
  // eventFlags |= aOther;
  inline void Union(const BaseEventFlags& aOther) {
    RawFlags rawFlags = GetRawFlags() | aOther.GetRawFlags();
    SetRawFlags(rawFlags);
  }

 private:
  typedef uint64_t RawFlags;

  inline void SetRawFlags(RawFlags aRawFlags) {
    static_assert(sizeof(BaseEventFlags) <= sizeof(RawFlags),
                  "mozilla::EventFlags must not be bigger than the RawFlags");
    memcpy(this, &aRawFlags, sizeof(BaseEventFlags));
  }
  inline RawFlags GetRawFlags() const {
    RawFlags result = 0;
    memcpy(&result, this, sizeof(BaseEventFlags));
    return result;
  }
};

/******************************************************************************
 * mozilla::EventFlags
 ******************************************************************************/

struct EventFlags : public BaseEventFlags {
  EventFlags() { Clear(); }
};

/******************************************************************************
 * mozilla::WidgetEventTime
 ******************************************************************************/

class WidgetEventTime {
 public:
  // Timestamp when the message was created.
  TimeStamp mTimeStamp;

  WidgetEventTime() : mTimeStamp(TimeStamp::Now()) {}

  explicit WidgetEventTime(const WidgetEventTime* aTime)
      : mTimeStamp(aTime ? aTime->mTimeStamp : TimeStamp::Now()) {
    MOZ_ASSERT(aTime != this);
    MOZ_ASSERT_IF(aTime, !aTime->mTimeStamp.IsNull());
  }

  explicit WidgetEventTime(TimeStamp aTimeStamp) : mTimeStamp(aTimeStamp) {}

  void AssignEventTime(const WidgetEventTime& aOther) {
    mTimeStamp = aOther.mTimeStamp;
  }
};

/******************************************************************************
 * mozilla::WidgetEvent
 ******************************************************************************/

class WidgetEvent : public WidgetEventTime {
 private:
  void SetDefaultCancelableAndBubbles() {
    switch (mClass) {
      case eEditorInputEventClass:
        mFlags.mCancelable = false;
        mFlags.mBubbles = mFlags.mIsTrusted;
        break;
      case eMouseEventClass:
        mFlags.mCancelable =
            (mMessage != eMouseEnter && mMessage != eMouseLeave);
        mFlags.mBubbles = (mMessage != eMouseEnter && mMessage != eMouseLeave);
        break;
      case ePointerEventClass:
        mFlags.mCancelable =
            (mMessage != ePointerEnter && mMessage != ePointerLeave &&
             mMessage != ePointerCancel && mMessage != ePointerGotCapture &&
             mMessage != ePointerLostCapture);
        mFlags.mBubbles =
            (mMessage != ePointerEnter && mMessage != ePointerLeave);
        break;
      case eDragEventClass:
        mFlags.mCancelable = (mMessage != eDragExit && mMessage != eDragLeave &&
                              mMessage != eDragEnd);
        mFlags.mBubbles = true;
        break;
      case eSMILTimeEventClass:
        mFlags.mCancelable = false;
        mFlags.mBubbles = false;
        break;
      case eTransitionEventClass:
      case eAnimationEventClass:
        mFlags.mCancelable = false;
        mFlags.mBubbles = true;
        break;
      case eCompositionEventClass:
        // XXX compositionstart is cancelable in draft of DOM3 Events.
        //     However, it doesn't make sense for us, we cannot cancel
        //     composition when we send compositionstart event.
        mFlags.mCancelable = false;
        mFlags.mBubbles = true;
        break;
      default:
        if (mMessage == eResize || mMessage == eMozVisualResize ||
            mMessage == eMozVisualScroll || mMessage == eEditorInput ||
            mMessage == eFormSelect) {
          mFlags.mCancelable = false;
        } else {
          mFlags.mCancelable = true;
        }
        mFlags.mBubbles = true;
        break;
    }
  }

 protected:
  WidgetEvent(bool aIsTrusted, EventMessage aMessage,
              EventClassID aEventClassID,
              const WidgetEventTime* aTime = nullptr)
      : WidgetEventTime(aTime),
        mClass(aEventClassID),
        mMessage(aMessage),
        mRefPoint(0, 0),
        mLastRefPoint(0, 0),
        mFocusSequenceNumber(0),
        mSpecifiedEventType(nullptr),
        mPath(nullptr),
        mLayersId(layers::LayersId{0}) {
    MOZ_COUNT_CTOR(WidgetEvent);
    mFlags.Clear();
    mFlags.mIsTrusted = aIsTrusted;
    SetDefaultCancelableAndBubbles();
    SetDefaultComposed();
    SetDefaultComposedInNativeAnonymousContent();
  }

  WidgetEvent() : WidgetEventTime(), mPath(nullptr) {
    MOZ_COUNT_CTOR(WidgetEvent);
  }

 public:
  WidgetEvent(bool aIsTrusted, EventMessage aMessage,
              const WidgetEventTime* aTime = nullptr)
      : WidgetEvent(aIsTrusted, aMessage, eBasicEventClass, aTime) {}

  MOZ_COUNTED_DTOR_VIRTUAL(WidgetEvent)

  WidgetEvent(const WidgetEvent& aOther) : WidgetEventTime() {
    MOZ_COUNT_CTOR(WidgetEvent);
    *this = aOther;
  }
  WidgetEvent& operator=(const WidgetEvent& aOther) = default;

  WidgetEvent(WidgetEvent&& aOther)
      : WidgetEventTime(std::move(aOther)),
        mClass(aOther.mClass),
        mMessage(aOther.mMessage),
        mRefPoint(std::move(aOther.mRefPoint)),
        mLastRefPoint(std::move(aOther.mLastRefPoint)),
        mFocusSequenceNumber(aOther.mFocusSequenceNumber),
        mFlags(std::move(aOther.mFlags)),
        mSpecifiedEventType(std::move(aOther.mSpecifiedEventType)),
        mSpecifiedEventTypeString(std::move(aOther.mSpecifiedEventTypeString)),
        mTarget(std::move(aOther.mTarget)),
        mCurrentTarget(std::move(aOther.mCurrentTarget)),
        mOriginalTarget(std::move(aOther.mOriginalTarget)),
        mRelatedTarget(std::move(aOther.mRelatedTarget)),
        mOriginalRelatedTarget(std::move(aOther.mOriginalRelatedTarget)),
        mPath(std::move(aOther.mPath)) {
    MOZ_COUNT_CTOR(WidgetEvent);
  }
  WidgetEvent& operator=(WidgetEvent&& aOther) = default;

  virtual WidgetEvent* Duplicate() const {
    MOZ_ASSERT(mClass == eBasicEventClass,
               "Duplicate() must be overridden by sub class");
    WidgetEvent* result = new WidgetEvent(false, mMessage, this);
    result->AssignEventData(*this, true);
    result->mFlags = mFlags;
    return result;
  }

  EventClassID mClass;
  EventMessage mMessage;
  // Relative to the widget of the event, or if there is no widget then it is
  // in screen coordinates. Not modified by layout code.
  // This is in visual coordinates, i.e. the correct RelativeTo value that
  // expresses what this is relative to is `{viewportFrame, Visual}`, where
  // `viewportFrame` is the viewport frame of the widget's root document.
  LayoutDeviceIntPoint mRefPoint;
  // The previous mRefPoint, if known, used to calculate mouse movement deltas.
  LayoutDeviceIntPoint mLastRefPoint;
  // The sequence number of the last potentially focus changing event handled
  // by APZ. This is used to track when that event has been processed by
  // content, and focus can be reconfirmed for async keyboard scrolling.
  uint64_t mFocusSequenceNumber;
  // See BaseEventFlags definition for the detail.
  BaseEventFlags mFlags;

  // If JS creates an event with unknown event type or known event type but
  // for different event interface, the event type is stored to this.
  // NOTE: This is always used if the instance is a WidgetCommandEvent instance
  //       or "input" event is dispatched with dom::Event class.
  RefPtr<nsAtom> mSpecifiedEventType;

  // nsAtom isn't available on non-main thread due to unsafe.  Therefore,
  // mSpecifiedEventTypeString is used instead of mSpecifiedEventType if
  // the event is created in non-main thread.
  nsString mSpecifiedEventTypeString;

  // Event targets, needed by DOM Events
  // Note that when you need event target for DOM event, you should use
  // Get*DOMEventTarget() instead of accessing these members directly.
  nsCOMPtr<dom::EventTarget> mTarget;
  nsCOMPtr<dom::EventTarget> mCurrentTarget;
  nsCOMPtr<dom::EventTarget> mOriginalTarget;

  /// The possible related target
  nsCOMPtr<dom::EventTarget> mRelatedTarget;
  nsCOMPtr<dom::EventTarget> mOriginalRelatedTarget;

  nsTArray<EventTargetChainItem>* mPath;

  // The LayersId of the content process that this event should be
  // dispatched to. This field is only used in the chrome process
  // and doesn't get remoted to child processes.
  layers::LayersId mLayersId;

  dom::EventTarget* GetDOMEventTarget() const;
  dom::EventTarget* GetCurrentDOMEventTarget() const;
  dom::EventTarget* GetOriginalDOMEventTarget() const;

  void AssignEventData(const WidgetEvent& aEvent, bool aCopyTargets) {
    // mClass should be initialized with the constructor.
    // mMessage should be initialized with the constructor.
    mRefPoint = aEvent.mRefPoint;
    // mLastRefPoint doesn't need to be copied.
    mFocusSequenceNumber = aEvent.mFocusSequenceNumber;
    // mLayersId intentionally not copied, since it's not used within content
    AssignEventTime(aEvent);
    // mFlags should be copied manually if it's necessary.
    mSpecifiedEventType = aEvent.mSpecifiedEventType;
    // mSpecifiedEventTypeString should be copied manually if it's necessary.
    mTarget = aCopyTargets ? aEvent.mTarget : nullptr;
    mCurrentTarget = aCopyTargets ? aEvent.mCurrentTarget : nullptr;
    mOriginalTarget = aCopyTargets ? aEvent.mOriginalTarget : nullptr;
    mRelatedTarget = aCopyTargets ? aEvent.mRelatedTarget : nullptr;
    mOriginalRelatedTarget =
        aCopyTargets ? aEvent.mOriginalRelatedTarget : nullptr;
  }

  /**
   * Helper methods for methods of DOM Event.
   */
  void StopPropagation() { mFlags.StopPropagation(); }
  void StopImmediatePropagation() { mFlags.StopImmediatePropagation(); }
  void PreventDefault(bool aCalledByDefaultHandler = true,
                      nsIPrincipal* aPrincipal = nullptr);

  void PreventDefaultBeforeDispatch(
      CrossProcessForwarding aCrossProcessForwarding) {
    mFlags.PreventDefaultBeforeDispatch(aCrossProcessForwarding);
  }
  bool DefaultPrevented() const { return mFlags.DefaultPrevented(); }
  bool DefaultPreventedByContent() const {
    return mFlags.DefaultPreventedByContent();
  }
  bool IsTrusted() const { return mFlags.IsTrusted(); }
  bool PropagationStopped() const { return mFlags.PropagationStopped(); }

  /**
   * Prevent to be dispatched to remote process.
   */
  inline void StopCrossProcessForwarding() {
    mFlags.StopCrossProcessForwarding();
  }
  /**
   * Return true if the event shouldn't be dispatched to remote process.
   */
  inline bool IsCrossProcessForwardingStopped() const {
    return mFlags.IsCrossProcessForwardingStopped();
  }
  /**
   * Mark the event as waiting reply from remote process.
   * Note that this also stops immediate propagation in current process.
   */
  inline void MarkAsWaitingReplyFromRemoteProcess() {
    mFlags.MarkAsWaitingReplyFromRemoteProcess();
  }
  /**
   * Reset "waiting reply from remote process" state.  This is useful when
   * you dispatch a copy of an event coming from different process.
   */
  inline void ResetWaitingReplyFromRemoteProcessState() {
    mFlags.ResetWaitingReplyFromRemoteProcessState();
  }
  /**
   * Return true if the event handler should wait reply event.  I.e., if this
   * returns true, any event handler should do nothing with the event.
   */
  inline bool IsWaitingReplyFromRemoteProcess() const {
    return mFlags.IsWaitingReplyFromRemoteProcess();
  }
  /**
   * Mark the event as already handled in the remote process.  This should be
   * called when initializing reply events.
   */
  inline void MarkAsHandledInRemoteProcess() {
    mFlags.MarkAsHandledInRemoteProcess();
  }
  /**
   * Return true if the event has already been handled in the remote process.
   * I.e., if this returns true, the event is a reply event.
   */
  inline bool IsHandledInRemoteProcess() const {
    return mFlags.IsHandledInRemoteProcess();
  }
  /**
   * Return true if the event should be sent back to its parent process.
   * So, usual event handlers shouldn't call this.
   */
  inline bool WantReplyFromContentProcess() const {
    return mFlags.WantReplyFromContentProcess();
  }
  /**
   * Mark the event has already posted to a remote process.
   */
  inline void MarkAsPostedToRemoteProcess() {
    mFlags.MarkAsPostedToRemoteProcess();
  }
  /**
   * Reset the cross process dispatching state.  This should be used when a
   * process receives the event because the state is in the sender.
   */
  inline void ResetCrossProcessDispatchingState() {
    mFlags.ResetCrossProcessDispatchingState();
  }
  /**
   * Return true if the event has been posted to a remote process.
   */
  inline bool HasBeenPostedToRemoteProcess() const {
    return mFlags.HasBeenPostedToRemoteProcess();
  }
  /**
   * Return true if the event came from another process.
   */
  inline bool CameFromAnotherProcess() const {
    return mFlags.CameFromAnotherProcess();
  }
  /**
   * Mark the event as coming from another process.
   */
  inline void MarkAsComingFromAnotherProcess() {
    mFlags.MarkAsComingFromAnotherProcess();
  }
  /**
   * Mark the event is reserved by chrome.  I.e., shouldn't be dispatched to
   * content because it shouldn't be cancelable.
   */
  inline void MarkAsReservedByChrome() { mFlags.MarkAsReservedByChrome(); }
  /**
   * Return true if the event is reserved by chrome.
   */
  inline bool IsReservedByChrome() const { return mFlags.IsReservedByChrome(); }

  /**
   * Utils for checking event types
   */

  /**
   * As*Event() returns the pointer of the instance only when the instance is
   * the class or one of its derived class.
   */
#define NS_ROOT_EVENT_CLASS(aPrefix, aName)
#define NS_EVENT_CLASS(aPrefix, aName) \
  virtual aPrefix##aName* As##aName(); \
  const aPrefix##aName* As##aName() const;

#include "mozilla/EventClassList.h"

#undef NS_EVENT_CLASS
#undef NS_ROOT_EVENT_CLASS

  /**
   * Returns true if the event is a query content event.
   */
  bool IsQueryContentEvent() const;
  /**
   * Returns true if the event is a selection event.
   */
  bool IsSelectionEvent() const;
  /**
   * Returns true if the event is a content command event.
   */
  bool IsContentCommandEvent() const;

  /**
   * Returns true if the event mMessage is one of mouse events.
   */
  bool HasMouseEventMessage() const;
  /**
   * Returns true if the event mMessage is one of drag events.
   */
  bool HasDragEventMessage() const;
  /**
   * Returns true if aMessage or mMessage is one of key events.
   */
  static bool IsKeyEventMessage(EventMessage aMessage);
  bool HasKeyEventMessage() const { return IsKeyEventMessage(mMessage); }
  /**
   * Returns true if the event mMessage is one of composition events or text
   * event.
   */
  bool HasIMEEventMessage() const;

  /**
   * Returns true if the event can be sent to remote process.
   */
  bool CanBeSentToRemoteProcess() const;
  /**
   * Returns true if the original target is a remote process and the event
   * will be posted to the remote process later.
   */
  bool WillBeSentToRemoteProcess() const;
  /**
   * Returns true if the event is related to IME handling.  It includes
   * IME events, query content events and selection events.
   * Be careful when you use this.
   */
  bool IsIMERelatedEvent() const;

  /**
   * Whether the event should be handled by the frame of the mouse cursor
   * position or not.  When it should be handled there (e.g., the mouse events),
   * this returns true.
   */
  bool IsUsingCoordinates() const;
  /**
   * Whether the event should be handled by the focused DOM window in the
   * same top level window's or not.  E.g., key events, IME related events
   * (including the query content events, they are used in IME transaction)
   * should be handled by the (last) focused window rather than the dispatched
   * window.
   *
   * NOTE: Even if this returns true, the event isn't going to be handled by the
   * application level active DOM window which is on another top level window.
   * So, when the event is fired on a deactive window, the event is going to be
   * handled by the last focused DOM window in the last focused window.
   */
  bool IsTargetedAtFocusedWindow() const;
  /**
   * Whether the event should be handled by the focused content or not.  E.g.,
   * key events, IME related events and other input events which are not handled
   * by the frame of the mouse cursor position.
   *
   * NOTE: Even if this returns true, the event isn't going to be handled by the
   * application level active DOM window which is on another top level window.
   * So, when the event is fired on a deactive window, the event is going to be
   * handled by the last focused DOM element of the last focused DOM window in
   * the last focused window.
   */
  bool IsTargetedAtFocusedContent() const;
  /**
   * Whether the event should cause a DOM event.
   */
  bool IsAllowedToDispatchDOMEvent() const;
  /**
   * Whether the event should be dispatched in system group.
   */
  bool IsAllowedToDispatchInSystemGroup() const;
  /**
   * Whether the event should be blocked for fingerprinting resistance.
   */
  bool IsBlockedForFingerprintingResistance() const;
  /**
   * Whether the event handler can flush pending notifications or not.
   */
  bool AllowFlushingPendingNotifications() const;
  /**
   * Initialize mComposed
   */
  void SetDefaultComposed() {
    switch (mClass) {
      case eClipboardEventClass:
        mFlags.mComposed = true;
        break;
      case eCompositionEventClass:
        mFlags.mComposed =
            mMessage == eCompositionStart || mMessage == eCompositionUpdate ||
            mMessage == eCompositionChange || mMessage == eCompositionEnd;
        break;
      case eDragEventClass:
        // All drag & drop events are composed
        mFlags.mComposed = mMessage == eDrag || mMessage == eDragEnd ||
                           mMessage == eDragEnter || mMessage == eDragExit ||
                           mMessage == eDragLeave || mMessage == eDragOver ||
                           mMessage == eDragStart || mMessage == eDrop;
        break;
      case eEditorInputEventClass:
        mFlags.mComposed =
            mMessage == eEditorInput || mMessage == eEditorBeforeInput;
        break;
      case eFocusEventClass:
        mFlags.mComposed = mMessage == eBlur || mMessage == eFocus ||
                           mMessage == eFocusOut || mMessage == eFocusIn;
        break;
      case eKeyboardEventClass:
        mFlags.mComposed =
            mMessage == eKeyDown || mMessage == eKeyUp || mMessage == eKeyPress;
        break;
      case eMouseEventClass:
        mFlags.mComposed =
            mMessage == eMouseClick || mMessage == eMouseDoubleClick ||
            mMessage == eMouseAuxClick || mMessage == eMouseDown ||
            mMessage == eMouseUp || mMessage == eMouseOver ||
            mMessage == eMouseOut || mMessage == eMouseMove ||
            mMessage == eContextMenu || mMessage == eXULPopupShowing ||
            mMessage == eXULPopupHiding || mMessage == eXULPopupShown ||
            mMessage == eXULPopupHidden;
        break;
      case ePointerEventClass:
        // All pointer events are composed
        mFlags.mComposed =
            mMessage == ePointerDown || mMessage == ePointerMove ||
            mMessage == ePointerUp || mMessage == ePointerCancel ||
            mMessage == ePointerOver || mMessage == ePointerOut ||
            mMessage == ePointerGotCapture || mMessage == ePointerLostCapture;
        break;
      case eTouchEventClass:
        // All touch events are composed
        mFlags.mComposed = mMessage == eTouchStart || mMessage == eTouchEnd ||
                           mMessage == eTouchMove || mMessage == eTouchCancel;
        break;
      case eUIEventClass:
        mFlags.mComposed = mMessage == eLegacyDOMFocusIn ||
                           mMessage == eLegacyDOMFocusOut ||
                           mMessage == eLegacyDOMActivate;
        break;
      case eWheelEventClass:
        // All wheel events are composed
        mFlags.mComposed = mMessage == eWheel;
        break;
      case eMouseScrollEventClass:
        // Legacy mouse scroll events are composed too, for consistency with
        // wheel.
        mFlags.mComposed = mMessage == eLegacyMouseLineOrPageScroll ||
                           mMessage == eLegacyMousePixelScroll;
        break;
      default:
        mFlags.mComposed = false;
        break;
    }
  }

  void SetComposed(const nsAString& aEventTypeArg) {
    mFlags.mComposed =  // composition events
        aEventTypeArg.EqualsLiteral("compositionstart") ||
        aEventTypeArg.EqualsLiteral("compositionupdate") ||
        aEventTypeArg.EqualsLiteral("compositionend") ||
        aEventTypeArg.EqualsLiteral("text") ||
        // drag and drop events
        aEventTypeArg.EqualsLiteral("dragstart") ||
        aEventTypeArg.EqualsLiteral("drag") ||
        aEventTypeArg.EqualsLiteral("dragenter") ||
        aEventTypeArg.EqualsLiteral("dragexit") ||
        aEventTypeArg.EqualsLiteral("dragleave") ||
        aEventTypeArg.EqualsLiteral("dragover") ||
        aEventTypeArg.EqualsLiteral("drop") ||
        aEventTypeArg.EqualsLiteral("dropend") ||
        // editor input events
        aEventTypeArg.EqualsLiteral("input") ||
        aEventTypeArg.EqualsLiteral("beforeinput") ||
        // focus events
        aEventTypeArg.EqualsLiteral("blur") ||
        aEventTypeArg.EqualsLiteral("focus") ||
        aEventTypeArg.EqualsLiteral("focusin") ||
        aEventTypeArg.EqualsLiteral("focusout") ||
        // keyboard events
        aEventTypeArg.EqualsLiteral("keydown") ||
        aEventTypeArg.EqualsLiteral("keyup") ||
        aEventTypeArg.EqualsLiteral("keypress") ||
        // mouse events
        aEventTypeArg.EqualsLiteral("click") ||
        aEventTypeArg.EqualsLiteral("dblclick") ||
        aEventTypeArg.EqualsLiteral("mousedown") ||
        aEventTypeArg.EqualsLiteral("mouseup") ||
        aEventTypeArg.EqualsLiteral("mouseenter") ||
        aEventTypeArg.EqualsLiteral("mouseleave") ||
        aEventTypeArg.EqualsLiteral("mouseover") ||
        aEventTypeArg.EqualsLiteral("mouseout") ||
        aEventTypeArg.EqualsLiteral("mousemove") ||
        aEventTypeArg.EqualsLiteral("contextmenu") ||
        // pointer events
        aEventTypeArg.EqualsLiteral("pointerdown") ||
        aEventTypeArg.EqualsLiteral("pointermove") ||
        aEventTypeArg.EqualsLiteral("pointerup") ||
        aEventTypeArg.EqualsLiteral("pointercancel") ||
        aEventTypeArg.EqualsLiteral("pointerover") ||
        aEventTypeArg.EqualsLiteral("pointerout") ||
        aEventTypeArg.EqualsLiteral("pointerenter") ||
        aEventTypeArg.EqualsLiteral("pointerleave") ||
        aEventTypeArg.EqualsLiteral("gotpointercapture") ||
        aEventTypeArg.EqualsLiteral("lostpointercapture") ||
        // touch events
        aEventTypeArg.EqualsLiteral("touchstart") ||
        aEventTypeArg.EqualsLiteral("touchend") ||
        aEventTypeArg.EqualsLiteral("touchmove") ||
        aEventTypeArg.EqualsLiteral("touchcancel") ||
        // UI legacy events
        aEventTypeArg.EqualsLiteral("DOMFocusIn") ||
        aEventTypeArg.EqualsLiteral("DOMFocusOut") ||
        aEventTypeArg.EqualsLiteral("DOMActivate") ||
        // wheel events
        aEventTypeArg.EqualsLiteral("wheel");
  }

  void SetComposed(bool aComposed) { mFlags.mComposed = aComposed; }

  void SetDefaultComposedInNativeAnonymousContent() {
    // For compatibility concerns, we set mComposedInNativeAnonymousContent to
    // false for those events we want to stop propagation.
    //
    // nsVideoFrame may create anonymous image element which fires eLoad,
    // eLoadStart, eLoadEnd, eLoadError. We don't want these events cross
    // the boundary of NAC
    mFlags.mComposedInNativeAnonymousContent =
        mMessage != eLoad && mMessage != eLoadStart && mMessage != eLoadEnd &&
        mMessage != eLoadError;
  }

  bool IsUserAction() const;
};

/******************************************************************************
 * mozilla::WidgetGUIEvent
 ******************************************************************************/

class WidgetGUIEvent : public WidgetEvent {
 protected:
  WidgetGUIEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
                 EventClassID aEventClassID,
                 const WidgetEventTime* aTime = nullptr)
      : WidgetEvent(aIsTrusted, aMessage, aEventClassID, aTime),
        mWidget(aWidget) {}

  WidgetGUIEvent() = default;

 public:
  virtual WidgetGUIEvent* AsGUIEvent() override { return this; }

  WidgetGUIEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
                 const WidgetEventTime* aTime = nullptr)
      : WidgetEvent(aIsTrusted, aMessage, eGUIEventClass, aTime),
        mWidget(aWidget) {}

  virtual WidgetEvent* Duplicate() const override {
    MOZ_ASSERT(mClass == eGUIEventClass,
               "Duplicate() must be overridden by sub class");
    // Not copying widget, it is a weak reference.
    WidgetGUIEvent* result = new WidgetGUIEvent(false, mMessage, nullptr, this);
    result->AssignGUIEventData(*this, true);
    result->mFlags = mFlags;
    return result;
  }

  // Originator of the event
  nsCOMPtr<nsIWidget> mWidget;

  void AssignGUIEventData(const WidgetGUIEvent& aEvent, bool aCopyTargets) {
    AssignEventData(aEvent, aCopyTargets);
    // widget should be initialized with the constructor.
  }
};

/******************************************************************************
 * mozilla::Modifier
 *
 * All modifier keys should be defined here.  This is used for managing
 * modifier states for DOM Level 3 or later.
 ******************************************************************************/

enum Modifier {
  MODIFIER_NONE = 0x0000,
  MODIFIER_ALT = 0x0001,
  MODIFIER_ALTGRAPH = 0x0002,
  MODIFIER_CAPSLOCK = 0x0004,
  MODIFIER_CONTROL = 0x0008,
  MODIFIER_FN = 0x0010,
  MODIFIER_FNLOCK = 0x0020,
  MODIFIER_META = 0x0040,
  MODIFIER_NUMLOCK = 0x0080,
  MODIFIER_SCROLLLOCK = 0x0100,
  MODIFIER_SHIFT = 0x0200,
  MODIFIER_SYMBOL = 0x0400,
  MODIFIER_SYMBOLLOCK = 0x0800,
};

/******************************************************************************
 * Modifier key names.
 ******************************************************************************/

#define NS_DOM_KEYNAME_ALT "Alt"
#define NS_DOM_KEYNAME_ALTGRAPH "AltGraph"
#define NS_DOM_KEYNAME_CAPSLOCK "CapsLock"
#define NS_DOM_KEYNAME_CONTROL "Control"
#define NS_DOM_KEYNAME_FN "Fn"
#define NS_DOM_KEYNAME_FNLOCK "FnLock"
#define NS_DOM_KEYNAME_META "Meta"
#define NS_DOM_KEYNAME_NUMLOCK "NumLock"
#define NS_DOM_KEYNAME_SCROLLLOCK "ScrollLock"
#define NS_DOM_KEYNAME_SHIFT "Shift"
#define NS_DOM_KEYNAME_SYMBOL "Symbol"
#define NS_DOM_KEYNAME_SYMBOLLOCK "SymbolLock"
#define NS_DOM_KEYNAME_OS "OS"

/******************************************************************************
 * mozilla::Modifiers
 ******************************************************************************/

typedef uint16_t Modifiers;

class MOZ_STACK_CLASS GetModifiersName final : public nsAutoCString {
 public:
  explicit GetModifiersName(Modifiers aModifiers) {
    if (aModifiers & MODIFIER_ALT) {
      AssignLiteral(NS_DOM_KEYNAME_ALT);
    }
    if (aModifiers & MODIFIER_ALTGRAPH) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_ALTGRAPH);
    }
    if (aModifiers & MODIFIER_CAPSLOCK) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_CAPSLOCK);
    }
    if (aModifiers & MODIFIER_CONTROL) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_CONTROL);
    }
    if (aModifiers & MODIFIER_FN) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_FN);
    }
    if (aModifiers & MODIFIER_FNLOCK) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_FNLOCK);
    }
    if (aModifiers & MODIFIER_META) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_META);
    }
    if (aModifiers & MODIFIER_NUMLOCK) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_NUMLOCK);
    }
    if (aModifiers & MODIFIER_SCROLLLOCK) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_SCROLLLOCK);
    }
    if (aModifiers & MODIFIER_SHIFT) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_SHIFT);
    }
    if (aModifiers & MODIFIER_SYMBOL) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_SYMBOL);
    }
    if (aModifiers & MODIFIER_SYMBOLLOCK) {
      MaybeAppendSeparator();
      AppendLiteral(NS_DOM_KEYNAME_SYMBOLLOCK);
    }
    if (IsEmpty()) {
      AssignLiteral("none");
    }
  }

 private:
  void MaybeAppendSeparator() {
    if (!IsEmpty()) {
      AppendLiteral(" | ");
    }
  }
};

/******************************************************************************
 * mozilla::WidgetInputEvent
 ******************************************************************************/

class WidgetInputEvent : public WidgetGUIEvent {
 protected:
  WidgetInputEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
                   EventClassID aEventClassID,
                   const WidgetEventTime* aTime = nullptr)
      : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, aEventClassID, aTime),
        mModifiers(0) {}

  WidgetInputEvent() : mModifiers(0) {}

 public:
  virtual WidgetInputEvent* AsInputEvent() override { return this; }

  WidgetInputEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
                   const WidgetEventTime* aTime = nullptr)
      : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, eInputEventClass, aTime),
        mModifiers(0) {}

  virtual WidgetEvent* Duplicate() const override {
    MOZ_ASSERT(mClass == eInputEventClass,
               "Duplicate() must be overridden by sub class");
    // Not copying widget, it is a weak reference.
    WidgetInputEvent* result =
        new WidgetInputEvent(false, mMessage, nullptr, this);
    result->AssignInputEventData(*this, true);
    result->mFlags = mFlags;
    return result;
  }

  /**
   * Returns a modifier of "Accel" virtual modifier which is used for shortcut
   * key.
   */
  static Modifier AccelModifier();

  /**
   * GetModifier() returns a modifier flag which is activated by aDOMKeyName.
   */
  static Modifier GetModifier(const nsAString& aDOMKeyName);

  // true indicates the accel key on the environment is down
  bool IsAccel() const { return ((mModifiers & AccelModifier()) != 0); }

  // true indicates the shift key is down
  bool IsShift() const { return ((mModifiers & MODIFIER_SHIFT) != 0); }
  // true indicates the control key is down
  bool IsControl() const { return ((mModifiers & MODIFIER_CONTROL) != 0); }
  // true indicates the alt key is down
  bool IsAlt() const { return ((mModifiers & MODIFIER_ALT) != 0); }
  // true indicates the meta key is down (Command key on macOS, Windows logo key
  // on Windows, Super/Hyper key on Linux, Meta key on Android).
  bool IsMeta() const { return ((mModifiers & MODIFIER_META) != 0); }
  // true indicates the alt graph key is down
  // NOTE: on Mac, the option key press causes both IsAlt() and IsAltGrpah()
  //       return true.
  bool IsAltGraph() const { return ((mModifiers & MODIFIER_ALTGRAPH) != 0); }
  // true indicates the CapLock LED is turn on.
  bool IsCapsLocked() const { return ((mModifiers & MODIFIER_CAPSLOCK) != 0); }
  // true indicates the NumLock LED is turn on.
  bool IsNumLocked() const { return ((mModifiers & MODIFIER_NUMLOCK) != 0); }
  // true indicates the ScrollLock LED is turn on.
  bool IsScrollLocked() const {
    return ((mModifiers & MODIFIER_SCROLLLOCK) != 0);
  }

  // true indicates the Fn key is down, but this is not supported by native
  // key event on any platform.
  bool IsFn() const { return ((mModifiers & MODIFIER_FN) != 0); }
  // true indicates the FnLock LED is turn on, but we don't know such
  // keyboards nor platforms.
  bool IsFnLocked() const { return ((mModifiers & MODIFIER_FNLOCK) != 0); }
  // true indicates the Symbol is down, but this is not supported by native
  // key event on any platforms.
  bool IsSymbol() const { return ((mModifiers & MODIFIER_SYMBOL) != 0); }
  // true indicates the SymbolLock LED is turn on, but we don't know such
  // keyboards nor platforms.
  bool IsSymbolLocked() const {
    return ((mModifiers & MODIFIER_SYMBOLLOCK) != 0);
  }

  void InitBasicModifiers(bool aCtrlKey, bool aAltKey, bool aShiftKey,
                          bool aMetaKey) {
    mModifiers = 0;
    if (aCtrlKey) {
      mModifiers |= MODIFIER_CONTROL;
    }
    if (aAltKey) {
      mModifiers |= MODIFIER_ALT;
    }
    if (aShiftKey) {
      mModifiers |= MODIFIER_SHIFT;
    }
    if (aMetaKey) {
      mModifiers |= MODIFIER_META;
    }
  }

  Modifiers mModifiers;

  void AssignInputEventData(const WidgetInputEvent& aEvent, bool aCopyTargets) {
    AssignGUIEventData(aEvent, aCopyTargets);

    mModifiers = aEvent.mModifiers;
  }
};

/******************************************************************************
 * mozilla::InternalUIEvent
 *
 * XXX Why this inherits WidgetGUIEvent rather than WidgetEvent?
 ******************************************************************************/

class InternalUIEvent : public WidgetGUIEvent {
 protected:
  InternalUIEvent() : mDetail(0), mCausedByUntrustedEvent(false) {}

  InternalUIEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget,
                  EventClassID aEventClassID,
                  const WidgetEventTime* aTime = nullptr)
      : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, aEventClassID, aTime),
        mDetail(0),
        mCausedByUntrustedEvent(false) {}

  InternalUIEvent(bool aIsTrusted, EventMessage aMessage,
                  EventClassID aEventClassID,
                  const WidgetEventTime* aTime = nullptr)
      : WidgetGUIEvent(aIsTrusted, aMessage, nullptr, aEventClassID, aTime),
        mDetail(0),
        mCausedByUntrustedEvent(false) {}

 public:
  virtual InternalUIEvent* AsUIEvent() override { return this; }

  /**
   * If the UIEvent is caused by another event (e.g., click event),
   * aEventCausesThisEvent should be the event.  If there is no such event,
   * this should be nullptr.
   */
  InternalUIEvent(bool aIsTrusted, EventMessage aMessage,
                  const WidgetEvent* aEventCausesThisEvent,
                  const WidgetEventTime* aTime = nullptr)
      : WidgetGUIEvent(aIsTrusted, aMessage, nullptr, eUIEventClass, aTime),
        mDetail(0),
        mCausedByUntrustedEvent(aEventCausesThisEvent &&
                                !aEventCausesThisEvent->IsTrusted()) {}

  virtual WidgetEvent* Duplicate() const override {
    MOZ_ASSERT(mClass == eUIEventClass,
               "Duplicate() must be overridden by sub class");
    InternalUIEvent* result =
        new InternalUIEvent(false, mMessage, nullptr, this);
    result->AssignUIEventData(*this, true);
    result->mFlags = mFlags;
    return result;
  }

  int32_t mDetail;
  // mCausedByUntrustedEvent is true if the event is caused by untrusted event.
  bool mCausedByUntrustedEvent;

  // If you check the event is a trusted event and NOT caused by an untrusted
  // event, IsTrustable() returns what you expected.
  bool IsTrustable() const { return IsTrusted() && !mCausedByUntrustedEvent; }

  void AssignUIEventData(const InternalUIEvent& aEvent, bool aCopyTargets) {
    AssignGUIEventData(aEvent, aCopyTargets);

    mDetail = aEvent.mDetail;
    mCausedByUntrustedEvent = aEvent.mCausedByUntrustedEvent;
  }
};

}  // namespace mozilla

#endif  // mozilla_BasicEvents_h__
