/* -*- 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/. */

#include "BasicEvents.h"
#include "ContentEvents.h"
#include "MiscEvents.h"
#include "MouseEvents.h"
#include "NativeKeyBindingsType.h"
#include "TextEventDispatcher.h"
#include "TextEvents.h"
#include "TouchEvents.h"

#include "mozilla/EventStateManager.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/Maybe.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_mousewheel.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/WritingModes.h"
#include "mozilla/dom/KeyboardEventBinding.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "nsCommandParams.h"
#include "nsContentUtils.h"
#include "nsIContent.h"
#include "nsIDragSession.h"
#include "nsPrintfCString.h"

#if defined(XP_WIN)
#  include "windef.h"
#  include "winnetwk.h"
#  include "npapi.h"
#  include "WinUtils.h"
#endif  // #if defined (XP_WIN)

#if defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX)
#  include "NativeKeyBindings.h"
#endif  // #if defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX)

namespace mozilla {

/******************************************************************************
 * Global helper methods
 ******************************************************************************/

const char* ToChar(EventMessage aEventMessage) {
  switch (aEventMessage) {
#define NS_EVENT_MESSAGE(aMessage) \
  case aMessage:                   \
    return #aMessage;

#include "mozilla/EventMessageList.h"

#undef NS_EVENT_MESSAGE
    default:
      return "illegal event message";
  }
}

const char* ToChar(EventClassID aEventClassID) {
  switch (aEventClassID) {
#define NS_ROOT_EVENT_CLASS(aPrefix, aName) \
  case eBasic##aName##Class:                \
    return "eBasic" #aName "Class";

#define NS_EVENT_CLASS(aPrefix, aName) \
  case e##aName##Class:                \
    return "e" #aName "Class";

#include "mozilla/EventClassList.h"

#undef NS_EVENT_CLASS
#undef NS_ROOT_EVENT_CLASS
    default:
      return "illegal event class ID";
  }
}

const nsCString ToString(KeyNameIndex aKeyNameIndex) {
  if (aKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
    return "USE_STRING"_ns;
  }
  nsAutoString keyName;
  WidgetKeyboardEvent::GetDOMKeyName(aKeyNameIndex, keyName);
  return NS_ConvertUTF16toUTF8(keyName);
}

const nsCString ToString(CodeNameIndex aCodeNameIndex) {
  if (aCodeNameIndex == CODE_NAME_INDEX_USE_STRING) {
    return "USE_STRING"_ns;
  }
  nsAutoString codeName;
  WidgetKeyboardEvent::GetDOMCodeName(aCodeNameIndex, codeName);
  return NS_ConvertUTF16toUTF8(codeName);
}

const char* ToChar(Command aCommand) {
  if (aCommand == Command::DoNothing) {
    return "CommandDoNothing";
  }

  switch (aCommand) {
#define NS_DEFINE_COMMAND(aName, aCommandStr) \
  case Command::aName:                        \
    return "Command::" #aName;
#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam) \
  case Command::aName:                                           \
    return "Command::" #aName;
#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) \
  case Command::aName:                           \
    return "Command::" #aName;

#include "mozilla/CommandList.h"

#undef NS_DEFINE_COMMAND
#undef NS_DEFINE_COMMAND_WITH_PARAM
#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND

    default:
      return "illegal command value";
  }
}

const nsCString GetDOMKeyCodeName(uint32_t aKeyCode) {
  switch (aKeyCode) {
#define NS_DISALLOW_SAME_KEYCODE
#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
  case aDOMKeyCode:                            \
    return nsLiteralCString(#aDOMKeyName);

#include "mozilla/VirtualKeyCodeList.h"

#undef NS_DEFINE_VK
#undef NS_DISALLOW_SAME_KEYCODE

    default:
      return nsPrintfCString("Invalid DOM keyCode (0x%08X)", aKeyCode);
  }
}

bool IsValidRawTextRangeValue(RawTextRangeType aRawTextRangeType) {
  switch (static_cast<TextRangeType>(aRawTextRangeType)) {
    case TextRangeType::eUninitialized:
    case TextRangeType::eCaret:
    case TextRangeType::eRawClause:
    case TextRangeType::eSelectedRawClause:
    case TextRangeType::eConvertedClause:
    case TextRangeType::eSelectedClause:
      return true;
    default:
      return false;
  }
}

RawTextRangeType ToRawTextRangeType(TextRangeType aTextRangeType) {
  return static_cast<RawTextRangeType>(aTextRangeType);
}

TextRangeType ToTextRangeType(RawTextRangeType aRawTextRangeType) {
  MOZ_ASSERT(IsValidRawTextRangeValue(aRawTextRangeType));
  return static_cast<TextRangeType>(aRawTextRangeType);
}

const char* ToChar(TextRangeType aTextRangeType) {
  switch (aTextRangeType) {
    case TextRangeType::eUninitialized:
      return "TextRangeType::eUninitialized";
    case TextRangeType::eCaret:
      return "TextRangeType::eCaret";
    case TextRangeType::eRawClause:
      return "TextRangeType::eRawClause";
    case TextRangeType::eSelectedRawClause:
      return "TextRangeType::eSelectedRawClause";
    case TextRangeType::eConvertedClause:
      return "TextRangeType::eConvertedClause";
    case TextRangeType::eSelectedClause:
      return "TextRangeType::eSelectedClause";
    default:
      return "Invalid TextRangeType";
  }
}

SelectionType ToSelectionType(TextRangeType aTextRangeType) {
  switch (aTextRangeType) {
    case TextRangeType::eRawClause:
      return SelectionType::eIMERawClause;
    case TextRangeType::eSelectedRawClause:
      return SelectionType::eIMESelectedRawClause;
    case TextRangeType::eConvertedClause:
      return SelectionType::eIMEConvertedClause;
    case TextRangeType::eSelectedClause:
      return SelectionType::eIMESelectedClause;
    default:
      MOZ_CRASH("TextRangeType is invalid");
      return SelectionType::eNormal;
  }
}

/******************************************************************************
 * non class method implementation
 ******************************************************************************/

static nsTHashMap<nsDepCharHashKey, Command>* sCommandHashtable = nullptr;

Command GetInternalCommand(const char* aCommandName,
                           const nsCommandParams* aCommandParams) {
  if (!aCommandName) {
    return Command::DoNothing;
  }

  // Special cases for "cmd_align".  It's mapped to multiple internal commands
  // with additional param.  Therefore, we cannot handle it with the hashtable.
  if (!strcmp(aCommandName, "cmd_align")) {
    if (!aCommandParams) {
      // Note that if this is called by EditorCommand::IsCommandEnabled(),
      // it cannot set aCommandParams.  So, don't warn in this case even though
      // this is illegal case for DoCommandParams().
      return Command::FormatJustify;
    }
    nsAutoCString cValue;
    nsresult rv = aCommandParams->GetCString("state_attribute", cValue);
    if (NS_FAILED(rv)) {
      nsString value;  // Avoid copying the string buffer with using nsString.
      rv = aCommandParams->GetString("state_attribute", value);
      if (NS_FAILED(rv)) {
        return Command::FormatJustifyNone;
      }
      CopyUTF16toUTF8(value, cValue);
    }
    if (cValue.LowerCaseEqualsASCII("left")) {
      return Command::FormatJustifyLeft;
    }
    if (cValue.LowerCaseEqualsASCII("right")) {
      return Command::FormatJustifyRight;
    }
    if (cValue.LowerCaseEqualsASCII("center")) {
      return Command::FormatJustifyCenter;
    }
    if (cValue.LowerCaseEqualsASCII("justify")) {
      return Command::FormatJustifyFull;
    }
    if (cValue.IsEmpty()) {
      return Command::FormatJustifyNone;
    }
    return Command::DoNothing;
  }

  if (!sCommandHashtable) {
    sCommandHashtable = new nsTHashMap<nsDepCharHashKey, Command>();
#define NS_DEFINE_COMMAND(aName, aCommandStr) \
  sCommandHashtable->InsertOrUpdate(#aCommandStr, Command::aName);

#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam)

#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName)

#include "mozilla/CommandList.h"

#undef NS_DEFINE_COMMAND
#undef NS_DEFINE_COMMAND_WITH_PARAM
#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
  }
  Command command = Command::DoNothing;
  if (!sCommandHashtable->Get(aCommandName, &command)) {
    return Command::DoNothing;
  }
  return command;
}

/******************************************************************************
 * As*Event() implementation
 ******************************************************************************/

#define NS_ROOT_EVENT_CLASS(aPrefix, aName)
#define NS_EVENT_CLASS(aPrefix, aName)                         \
  aPrefix##aName* WidgetEvent::As##aName() { return nullptr; } \
                                                               \
  const aPrefix##aName* WidgetEvent::As##aName() const {       \
    return const_cast<WidgetEvent*>(this)->As##aName();        \
  }

#include "mozilla/EventClassList.h"

#undef NS_EVENT_CLASS
#undef NS_ROOT_EVENT_CLASS

/******************************************************************************
 * mozilla::WidgetEvent
 *
 * Event struct type checking methods.
 ******************************************************************************/

bool WidgetEvent::IsQueryContentEvent() const {
  return mClass == eQueryContentEventClass;
}

bool WidgetEvent::IsSelectionEvent() const {
  return mClass == eSelectionEventClass;
}

bool WidgetEvent::IsContentCommandEvent() const {
  return mClass == eContentCommandEventClass;
}

/******************************************************************************
 * mozilla::WidgetEvent
 *
 * Event message checking methods.
 ******************************************************************************/

bool WidgetEvent::HasMouseEventMessage() const {
  switch (mMessage) {
    case eMouseDown:
    case eMouseUp:
    case eMouseClick:
    case eMouseDoubleClick:
    case eMouseAuxClick:
    case eMouseEnterIntoWidget:
    case eMouseExitFromWidget:
    case eMouseActivate:
    case eMouseOver:
    case eMouseOut:
    case eMouseHitTest:
    case eMouseMove:
      return true;
    default:
      return false;
  }
}

bool WidgetEvent::HasDragEventMessage() const {
  switch (mMessage) {
    case eDragEnter:
    case eDragOver:
    case eDragExit:
    case eDrag:
    case eDragEnd:
    case eDragStart:
    case eDrop:
    case eDragLeave:
      return true;
    default:
      return false;
  }
}

/* static */
bool WidgetEvent::IsKeyEventMessage(EventMessage aMessage) {
  switch (aMessage) {
    case eKeyDown:
    case eKeyPress:
    case eKeyUp:
    case eAccessKeyNotFound:
      return true;
    default:
      return false;
  }
}

bool WidgetEvent::HasIMEEventMessage() const {
  switch (mMessage) {
    case eCompositionStart:
    case eCompositionEnd:
    case eCompositionUpdate:
    case eCompositionChange:
    case eCompositionCommitAsIs:
    case eCompositionCommit:
      return true;
    default:
      return false;
  }
}

/******************************************************************************
 * mozilla::WidgetEvent
 *
 * Specific event checking methods.
 ******************************************************************************/

bool WidgetEvent::CanBeSentToRemoteProcess() const {
  // If this event is explicitly marked as shouldn't be sent to remote process,
  // just return false.
  if (IsCrossProcessForwardingStopped()) {
    return false;
  }

  if (mClass == eKeyboardEventClass || mClass == eWheelEventClass) {
    return true;
  }

  switch (mMessage) {
    case eMouseDown:
    case eMouseUp:
    case eMouseMove:
    case eMouseExploreByTouch:
    case eContextMenu:
    case eMouseEnterIntoWidget:
    case eMouseExitFromWidget:
    case eMouseTouchDrag:
    case eTouchStart:
    case eTouchMove:
    case eTouchEnd:
    case eTouchCancel:
    case eDragOver:
    case eDragExit:
    case eDrop:
      return true;
    default:
      return false;
  }
}

bool WidgetEvent::WillBeSentToRemoteProcess() const {
  // This event won't be posted to remote process if it's already explicitly
  // stopped.
  if (IsCrossProcessForwardingStopped()) {
    return false;
  }

  // When mOriginalTarget is nullptr, this method shouldn't be used.
  if (NS_WARN_IF(!mOriginalTarget)) {
    return false;
  }

  return EventStateManager::IsTopLevelRemoteTarget(
      nsIContent::FromEventTarget(mOriginalTarget));
}

bool WidgetEvent::IsIMERelatedEvent() const {
  return HasIMEEventMessage() || IsQueryContentEvent() || IsSelectionEvent();
}

bool WidgetEvent::IsUsingCoordinates() const {
  const WidgetMouseEvent* mouseEvent = AsMouseEvent();
  if (mouseEvent) {
    return !mouseEvent->IsContextMenuKeyEvent();
  }
  return !HasKeyEventMessage() && !IsIMERelatedEvent() &&
         !IsContentCommandEvent();
}

bool WidgetEvent::IsTargetedAtFocusedWindow() const {
  const WidgetMouseEvent* mouseEvent = AsMouseEvent();
  if (mouseEvent) {
    return mouseEvent->IsContextMenuKeyEvent();
  }
  return HasKeyEventMessage() || IsIMERelatedEvent() || IsContentCommandEvent();
}

bool WidgetEvent::IsTargetedAtFocusedContent() const {
  const WidgetMouseEvent* mouseEvent = AsMouseEvent();
  if (mouseEvent) {
    return mouseEvent->IsContextMenuKeyEvent();
  }
  return HasKeyEventMessage() || IsIMERelatedEvent();
}

bool WidgetEvent::IsAllowedToDispatchDOMEvent() const {
  switch (mClass) {
    case eMouseEventClass:
      if (mMessage == eMouseTouchDrag) {
        return false;
      }
      [[fallthrough]];
    case ePointerEventClass:
      // We want synthesized mouse moves to cause mouseover and mouseout
      // DOM events (EventStateManager::PreHandleEvent), but not mousemove
      // DOM events.
      // Synthesized button up events also do not cause DOM events because they
      // do not have a reliable mRefPoint.
      return AsMouseEvent()->mReason == WidgetMouseEvent::eReal;

    case eWheelEventClass: {
      // wheel event whose all delta values are zero by user pref applied, it
      // shouldn't cause a DOM event.
      const WidgetWheelEvent* wheelEvent = AsWheelEvent();
      return wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0 ||
             wheelEvent->mDeltaZ != 0.0;
    }
    case eTouchEventClass:
      return mMessage != eTouchPointerCancel;
    // Following events are handled in EventStateManager, so, we don't need to
    // dispatch DOM event for them into the DOM tree.
    case eQueryContentEventClass:
    case eSelectionEventClass:
    case eContentCommandEventClass:
      return false;

    default:
      return true;
  }
}

bool WidgetEvent::IsAllowedToDispatchInSystemGroup() const {
  // We don't expect to implement default behaviors with pointer events because
  // if we do, prevent default on mouse events can't prevent default behaviors
  // anymore.
  return mClass != ePointerEventClass;
}

bool WidgetEvent::IsBlockedForFingerprintingResistance() const {
  switch (mClass) {
    case eKeyboardEventClass: {
      const WidgetKeyboardEvent* keyboardEvent = AsKeyboardEvent();

      return (keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_Alt ||
              keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_Shift ||
              keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_Control ||
              keyboardEvent->mKeyNameIndex == KEY_NAME_INDEX_AltGraph);
    }
    case ePointerEventClass: {
      const WidgetPointerEvent* pointerEvent = AsPointerEvent();

      // We suppress the pointer events if it is not primary for fingerprinting
      // resistance. It is because of that we want to spoof any pointer event
      // into a mouse pointer event and the mouse pointer event only has
      // isPrimary as true.
      return !pointerEvent->mIsPrimary;
    }
    default:
      return false;
  }
}

bool WidgetEvent::AllowFlushingPendingNotifications() const {
  if (mClass != eQueryContentEventClass) {
    return true;
  }
  // If the dispatcher does not want a flush of pending notifications, it may
  // be caused by that it's unsafe.  Therefore, we should allow handlers to
  // flush pending things only when the dispatcher requires the latest content
  // layout.
  return AsQueryContentEvent()->mNeedsToFlushLayout;
}

/******************************************************************************
 * mozilla::WidgetEvent
 *
 * Misc methods.
 ******************************************************************************/

static dom::EventTarget* GetTargetForDOMEvent(dom::EventTarget* aTarget) {
  return aTarget ? aTarget->GetTargetForDOMEvent() : nullptr;
}

dom::EventTarget* WidgetEvent::GetDOMEventTarget() const {
  return GetTargetForDOMEvent(mTarget);
}

dom::EventTarget* WidgetEvent::GetCurrentDOMEventTarget() const {
  return GetTargetForDOMEvent(mCurrentTarget);
}

dom::EventTarget* WidgetEvent::GetOriginalDOMEventTarget() const {
  if (mOriginalTarget) {
    return GetTargetForDOMEvent(mOriginalTarget);
  }
  return GetDOMEventTarget();
}

void WidgetEvent::PreventDefault(bool aCalledByDefaultHandler,
                                 nsIPrincipal* aPrincipal) {
  if (mMessage == ePointerDown) {
    if (aCalledByDefaultHandler) {
      // Shouldn't prevent default on pointerdown by default handlers to stop
      // firing legacy mouse events. Use MOZ_ASSERT to catch incorrect usages
      // in debug builds.
      MOZ_ASSERT(false);
      return;
    }
    if (aPrincipal) {
      nsAutoString addonId;
      Unused << NS_WARN_IF(NS_FAILED(aPrincipal->GetAddonId(addonId)));
      if (!addonId.IsEmpty()) {
        // Ignore the case that it's called by a web extension.
        return;
      }
    }
  }
  mFlags.PreventDefault(aCalledByDefaultHandler);
}

bool WidgetEvent::IsUserAction() const {
  if (!IsTrusted()) {
    return false;
  }
  // FYI: eMouseScrollEventClass and ePointerEventClass represent
  //      user action but they are synthesized events.
  switch (mClass) {
    case eKeyboardEventClass:
    case eCompositionEventClass:
    case eMouseScrollEventClass:
    case eWheelEventClass:
    case eGestureNotifyEventClass:
    case eSimpleGestureEventClass:
    case eTouchEventClass:
    case eCommandEventClass:
    case eContentCommandEventClass:
      return true;
    case eMouseEventClass:
    case eDragEventClass:
    case ePointerEventClass:
      return AsMouseEvent()->IsReal();
    default:
      return false;
  }
}

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

/* static */
Modifier WidgetInputEvent::GetModifier(const nsAString& aDOMKeyName) {
  if (aDOMKeyName.EqualsLiteral("Accel")) {
    return AccelModifier();
  }
  KeyNameIndex keyNameIndex = WidgetKeyboardEvent::GetKeyNameIndex(aDOMKeyName);
  return WidgetKeyboardEvent::GetModifierForKeyName(keyNameIndex);
}

/* static */
Modifier WidgetInputEvent::AccelModifier() {
  static Modifier sAccelModifier = MODIFIER_NONE;
  if (sAccelModifier == MODIFIER_NONE) {
    switch (StaticPrefs::ui_key_accelKey()) {
      case dom::KeyboardEvent_Binding::DOM_VK_META:
      case dom::KeyboardEvent_Binding::DOM_VK_WIN:
        sAccelModifier = MODIFIER_META;
        break;
      case dom::KeyboardEvent_Binding::DOM_VK_ALT:
        sAccelModifier = MODIFIER_ALT;
        break;
      case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
        sAccelModifier = MODIFIER_CONTROL;
        break;
      default:
#ifdef XP_MACOSX
        sAccelModifier = MODIFIER_META;
#else
        sAccelModifier = MODIFIER_CONTROL;
#endif
        break;
    }
  }
  return sAccelModifier;
}

/******************************************************************************
 * mozilla::WidgetMouseEvent (MouseEvents.h)
 ******************************************************************************/

/* static */
bool WidgetMouseEvent::IsMiddleClickPasteEnabled() {
  return Preferences::GetBool("middlemouse.paste", false);
}

#ifdef DEBUG
void WidgetMouseEvent::AssertContextMenuEventButtonConsistency() const {
  if (mMessage != eContextMenu) {
    return;
  }

  if (mContextMenuTrigger == eNormal) {
    NS_WARNING_ASSERTION(mButton == MouseButton::eSecondary,
                         "eContextMenu events with eNormal trigger should use "
                         "secondary mouse button");
  } else {
    NS_WARNING_ASSERTION(mButton == MouseButton::ePrimary,
                         "eContextMenu events with non-eNormal trigger should "
                         "use primary mouse button");
  }

  if (mContextMenuTrigger == eControlClick) {
    NS_WARNING_ASSERTION(IsControl(),
                         "eContextMenu events with eControlClick trigger "
                         "should return true from IsControl()");
  }
}
#endif

/******************************************************************************
 * mozilla::WidgetDragEvent (MouseEvents.h)
 ******************************************************************************/

void WidgetDragEvent::InitDropEffectForTests() {
  MOZ_ASSERT(mFlags.mIsSynthesizedForTests);

  nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
  if (NS_WARN_IF(!session)) {
    return;
  }

  uint32_t effectAllowed = session->GetEffectAllowedForTests();
  uint32_t desiredDropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
#ifdef XP_MACOSX
  if (IsAlt()) {
    desiredDropEffect = IsMeta() ? nsIDragService::DRAGDROP_ACTION_LINK
                                 : nsIDragService::DRAGDROP_ACTION_COPY;
  }
#else
  // On Linux, we know user's intention from API, but we should use
  // same modifiers as Windows for tests because GNOME on Ubuntu use
  // them and that makes each test simpler.
  if (IsControl()) {
    desiredDropEffect = IsShift() ? nsIDragService::DRAGDROP_ACTION_LINK
                                  : nsIDragService::DRAGDROP_ACTION_COPY;
  } else if (IsShift()) {
    desiredDropEffect = nsIDragService::DRAGDROP_ACTION_MOVE;
  }
#endif  // #ifdef XP_MACOSX #else
  // First, use modifier state for preferring action which is explicitly
  // specified by the synthesizer.
  if (!(desiredDropEffect &= effectAllowed)) {
    // Otherwise, use an action which is allowed at starting the session.
    desiredDropEffect = effectAllowed;
  }
  if (desiredDropEffect & nsIDragService::DRAGDROP_ACTION_MOVE) {
    session->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE);
  } else if (desiredDropEffect & nsIDragService::DRAGDROP_ACTION_COPY) {
    session->SetDragAction(nsIDragService::DRAGDROP_ACTION_COPY);
  } else if (desiredDropEffect & nsIDragService::DRAGDROP_ACTION_LINK) {
    session->SetDragAction(nsIDragService::DRAGDROP_ACTION_LINK);
  } else {
    session->SetDragAction(nsIDragService::DRAGDROP_ACTION_NONE);
  }
}

/******************************************************************************
 * mozilla::WidgetWheelEvent (MouseEvents.h)
 ******************************************************************************/

/* static */
double WidgetWheelEvent::ComputeOverriddenDelta(double aDelta,
                                                bool aIsForVertical) {
  if (!StaticPrefs::mousewheel_system_scroll_override_enabled()) {
    return aDelta;
  }
  int32_t intFactor =
      aIsForVertical
          ? StaticPrefs::mousewheel_system_scroll_override_vertical_factor()
          : StaticPrefs::mousewheel_system_scroll_override_horizontal_factor();
  // Making the scroll speed slower doesn't make sense. So, ignore odd factor
  // which is less than 1.0.
  if (intFactor <= 100) {
    return aDelta;
  }
  double factor = static_cast<double>(intFactor) / 100;
  return aDelta * factor;
}

double WidgetWheelEvent::OverriddenDeltaX() const {
  if (!mAllowToOverrideSystemScrollSpeed ||
      mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE ||
      mCustomizedByUserPrefs) {
    return mDeltaX;
  }
  return ComputeOverriddenDelta(mDeltaX, false);
}

double WidgetWheelEvent::OverriddenDeltaY() const {
  if (!mAllowToOverrideSystemScrollSpeed ||
      mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE ||
      mCustomizedByUserPrefs) {
    return mDeltaY;
  }
  return ComputeOverriddenDelta(mDeltaY, true);
}

/******************************************************************************
 * mozilla::WidgetKeyboardEvent (TextEvents.h)
 ******************************************************************************/

#define NS_DEFINE_KEYNAME(aCPPName, aDOMKeyName) (u"" aDOMKeyName),
const char16_t* const WidgetKeyboardEvent::kKeyNames[] = {
#include "mozilla/KeyNameList.h"
};
#undef NS_DEFINE_KEYNAME

#define NS_DEFINE_PHYSICAL_KEY_CODE_NAME(aCPPName, aDOMCodeName) \
  (u"" aDOMCodeName),
const char16_t* const WidgetKeyboardEvent::kCodeNames[] = {
#include "mozilla/PhysicalKeyCodeNameList.h"
};
#undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME

WidgetKeyboardEvent::KeyNameIndexHashtable*
    WidgetKeyboardEvent::sKeyNameIndexHashtable = nullptr;
WidgetKeyboardEvent::CodeNameIndexHashtable*
    WidgetKeyboardEvent::sCodeNameIndexHashtable = nullptr;

void WidgetKeyboardEvent::InitAllEditCommands(
    const Maybe<WritingMode>& aWritingMode) {
  // If this event is synthesized for tests, we don't need to retrieve the
  // command via the main process.  So, we don't need widget and can trust
  // the event.
  if (!mFlags.mIsSynthesizedForTests) {
    // If the event was created without widget, e.g., created event in chrome
    // script, this shouldn't execute native key bindings.
    if (NS_WARN_IF(!mWidget)) {
      return;
    }

    // This event should be trusted event here and we shouldn't expose native
    // key binding information to web contents with untrusted events.
    if (NS_WARN_IF(!IsTrusted())) {
      return;
    }

    MOZ_ASSERT(
        XRE_IsParentProcess(),
        "It's too expensive to retrieve all edit commands from remote process");
    MOZ_ASSERT(!AreAllEditCommandsInitialized(),
               "Shouldn't be called two or more times");
  }

  DebugOnly<bool> okIgnored = InitEditCommandsFor(
      NativeKeyBindingsType::SingleLineEditor, aWritingMode);
  NS_WARNING_ASSERTION(okIgnored,
                       "InitEditCommandsFor(NativeKeyBindingsType::"
                       "SingleLineEditor) failed, but ignored");
  okIgnored =
      InitEditCommandsFor(NativeKeyBindingsType::MultiLineEditor, aWritingMode);
  NS_WARNING_ASSERTION(okIgnored,
                       "InitEditCommandsFor(NativeKeyBindingsType::"
                       "MultiLineEditor) failed, but ignored");
  okIgnored =
      InitEditCommandsFor(NativeKeyBindingsType::RichTextEditor, aWritingMode);
  NS_WARNING_ASSERTION(okIgnored,
                       "InitEditCommandsFor(NativeKeyBindingsType::"
                       "RichTextEditor) failed, but ignored");
}

bool WidgetKeyboardEvent::InitEditCommandsFor(
    NativeKeyBindingsType aType, const Maybe<WritingMode>& aWritingMode) {
  bool& initialized = IsEditCommandsInitializedRef(aType);
  if (initialized) {
    return true;
  }
  nsTArray<CommandInt>& commands = EditCommandsRef(aType);

  // If this event is synthesized for tests, we shouldn't access customized
  // shortcut settings of the environment.  Therefore, we don't need to check
  // whether `widget` is set or not.  And we can treat synthesized events are
  // always trusted.
  if (mFlags.mIsSynthesizedForTests) {
    MOZ_DIAGNOSTIC_ASSERT(IsTrusted());
#if defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX)
    // TODO: We should implement `NativeKeyBindings` for Windows and Android
    //       too in bug 1301497 for getting rid of the #if.
    widget::NativeKeyBindings::GetEditCommandsForTests(aType, *this,
                                                       aWritingMode, commands);
#endif
    initialized = true;
    return true;
  }

  if (NS_WARN_IF(!mWidget) || NS_WARN_IF(!IsTrusted())) {
    return false;
  }
  // `nsIWidget::GetEditCommands()` will retrieve `WritingMode` at selection
  // again, but it should be almost zero-cost since `TextEventDispatcher`
  // caches the value.
  nsCOMPtr<nsIWidget> widget = mWidget;
  initialized = widget->GetEditCommands(aType, *this, commands);
  return initialized;
}

bool WidgetKeyboardEvent::ExecuteEditCommands(NativeKeyBindingsType aType,
                                              DoCommandCallback aCallback,
                                              void* aCallbackData) {
  // If the event was created without widget, e.g., created event in chrome
  // script, this shouldn't execute native key bindings.
  if (NS_WARN_IF(!mWidget)) {
    return false;
  }

  // This event should be trusted event here and we shouldn't expose native
  // key binding information to web contents with untrusted events.
  if (NS_WARN_IF(!IsTrusted())) {
    return false;
  }

  if (!IsEditCommandsInitializedRef(aType)) {
    Maybe<WritingMode> writingMode;
    if (RefPtr<widget::TextEventDispatcher> textEventDispatcher =
            mWidget->GetTextEventDispatcher()) {
      writingMode = textEventDispatcher->MaybeQueryWritingModeAtSelection();
    }
    if (NS_WARN_IF(!InitEditCommandsFor(aType, writingMode))) {
      return false;
    }
  }

  const nsTArray<CommandInt>& commands = EditCommandsRef(aType);
  if (commands.IsEmpty()) {
    return false;
  }

  for (CommandInt command : commands) {
    aCallback(static_cast<Command>(command), aCallbackData);
  }
  return true;
}

bool WidgetKeyboardEvent::ShouldCauseKeypressEvents() const {
  // Currently, we don't dispatch keypress events of modifier keys and
  // dead keys.
  switch (mKeyNameIndex) {
    case KEY_NAME_INDEX_Alt:
    case KEY_NAME_INDEX_AltGraph:
    case KEY_NAME_INDEX_CapsLock:
    case KEY_NAME_INDEX_Control:
    case KEY_NAME_INDEX_Fn:
    case KEY_NAME_INDEX_FnLock:
    // case KEY_NAME_INDEX_Hyper:
    case KEY_NAME_INDEX_Meta:
    case KEY_NAME_INDEX_NumLock:
    case KEY_NAME_INDEX_ScrollLock:
    case KEY_NAME_INDEX_Shift:
    // case KEY_NAME_INDEX_Super:
    case KEY_NAME_INDEX_Symbol:
    case KEY_NAME_INDEX_SymbolLock:
    case KEY_NAME_INDEX_Dead:
      return false;
    default:
      return true;
  }
}

static bool HasASCIIDigit(const ShortcutKeyCandidateArray& aCandidates) {
  for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
    uint32_t ch = aCandidates[i].mCharCode;
    if (ch >= '0' && ch <= '9') return true;
  }
  return false;
}

static bool CharsCaseInsensitiveEqual(uint32_t aChar1, uint32_t aChar2) {
  return aChar1 == aChar2 || (IS_IN_BMP(aChar1) && IS_IN_BMP(aChar2) &&
                              ToLowerCase(static_cast<char16_t>(aChar1)) ==
                                  ToLowerCase(static_cast<char16_t>(aChar2)));
}

static bool IsCaseChangeableChar(uint32_t aChar) {
  return IS_IN_BMP(aChar) && ToLowerCase(static_cast<char16_t>(aChar)) !=
                                 ToUpperCase(static_cast<char16_t>(aChar));
}

void WidgetKeyboardEvent::GetShortcutKeyCandidates(
    ShortcutKeyCandidateArray& aCandidates) const {
  MOZ_ASSERT(aCandidates.IsEmpty(), "aCandidates must be empty");

  using ShiftState = ShortcutKeyCandidate::ShiftState;
  using SkipIfEarlierHandlerDisabled =
      ShortcutKeyCandidate::SkipIfEarlierHandlerDisabled;

  // ShortcutKeyCandidate::mCharCode is a candidate charCode.
  // ShortcutKeyCandidate::mShiftState means the mCharCode should be tried to
  // execute a command with/without shift key state. If this is Ignorable,
  // the shifted key state should be ignored. Otherwise, don't ignore the state.
  // the priority of the charCodes are (shift key is not pressed):
  //   0: PseudoCharCode()/ShiftState::MatchExactly,
  //   1: unshiftedCharCodes[0]/ShiftState::MatchExactly,
  //   2: unshiftedCharCodes[1]/ShiftState::MatchExactly...
  // the priority of the charCodes are (shift key is pressed):
  //   0: PseudoCharCode()/ShiftState::MatchExactly,
  //   1: shiftedCharCodes[0]/ShiftState::MatchExactly,
  //   2: shiftedCharCodes[0]/ShiftState::Ignorable,
  //   3: shiftedCharCodes[1]/ShiftState::MatchExactly,
  //   4: shiftedCharCodes[1]/ShiftState::Ignorable...
  uint32_t pseudoCharCode = PseudoCharCode();
  if (pseudoCharCode) {
    ShortcutKeyCandidate key(pseudoCharCode, ShiftState::MatchExactly,
                             SkipIfEarlierHandlerDisabled::No);
    aCandidates.AppendElement(key);
  }

  uint32_t len = mAlternativeCharCodes.Length();
  if (!IsShift()) {
    for (uint32_t i = 0; i < len; ++i) {
      uint32_t ch = mAlternativeCharCodes[i].mUnshiftedCharCode;
      if (!ch || ch == pseudoCharCode) {
        continue;
      }
      ShortcutKeyCandidate key(ch, ShiftState::MatchExactly,
                               SkipIfEarlierHandlerDisabled::No);
      aCandidates.AppendElement(key);
    }
    // If unshiftedCharCodes doesn't have numeric but shiftedCharCode has it,
    // this keyboard layout is AZERTY or similar layout, probably.
    // In this case, Accel+[0-9] should be accessible without shift key.
    // However, the priority should be lowest.
    if (!HasASCIIDigit(aCandidates)) {
      for (uint32_t i = 0; i < len; ++i) {
        uint32_t ch = mAlternativeCharCodes[i].mShiftedCharCode;
        if (ch >= '0' && ch <= '9') {
          ShortcutKeyCandidate key(
              ch, ShiftState::MatchExactly,
              // Ctrl + `-` in the French keyboard layout should not match with
              // Ctrl + `6` shortcut when it's already fully zoomed out.
              SkipIfEarlierHandlerDisabled::Yes);
          aCandidates.AppendElement(key);
          break;
        }
      }
    }
  } else {
    for (uint32_t i = 0; i < len; ++i) {
      uint32_t ch = mAlternativeCharCodes[i].mShiftedCharCode;
      if (!ch) {
        continue;
      }

      if (ch != pseudoCharCode) {
        ShortcutKeyCandidate key(ch, ShiftState::MatchExactly,
                                 SkipIfEarlierHandlerDisabled::No);
        aCandidates.AppendElement(key);
      }

      // If the char is an alphabet, the shift key state should not be
      // ignored. E.g., Ctrl+Shift+C should not execute Ctrl+C.

      // And checking the charCode is same as unshiftedCharCode too.
      // E.g., for Ctrl+Shift+(Plus of Numpad) should not run Ctrl+Plus.
      uint32_t unshiftCh = mAlternativeCharCodes[i].mUnshiftedCharCode;
      if (CharsCaseInsensitiveEqual(ch, unshiftCh)) {
        continue;
      }

      // On the Hebrew keyboard layout on Windows, the unshifted char is a
      // localized character but the shifted char is a Latin alphabet,
      // then, we should not execute without the shift state. See bug 433192.
      if (IsCaseChangeableChar(ch)) {
        continue;
      }

      // Setting the alternative charCode candidates for retry without shift
      // key state only when the shift key is pressed.
      ShortcutKeyCandidate key(ch, ShiftState::Ignorable,
                               SkipIfEarlierHandlerDisabled::No);
      aCandidates.AppendElement(key);
    }
  }

  // Special case for "Space" key.  With some keyboard layouts, "Space" with
  // or without Shift key causes non-ASCII space.  For such keyboard layouts,
  // we should guarantee that the key press works as an ASCII white space key
  // press.  However, if the space key is assigned to a function key, it
  // shouldn't work as a space key.
  if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
      mCodeNameIndex == CODE_NAME_INDEX_Space && pseudoCharCode != ' ') {
    ShortcutKeyCandidate spaceKey(' ', ShiftState::MatchExactly,
                                  SkipIfEarlierHandlerDisabled::No);
    aCandidates.AppendElement(spaceKey);
  }
}

void WidgetKeyboardEvent::GetAccessKeyCandidates(
    nsTArray<uint32_t>& aCandidates) const {
  MOZ_ASSERT(aCandidates.IsEmpty(), "aCandidates must be empty");

  // return the lower cased charCode candidates for access keys.
  // the priority of the charCodes are:
  //   0: charCode, 1: unshiftedCharCodes[0], 2: shiftedCharCodes[0]
  //   3: unshiftedCharCodes[1], 4: shiftedCharCodes[1],...
  uint32_t pseudoCharCode = PseudoCharCode();
  if (pseudoCharCode) {
    uint32_t ch = pseudoCharCode;
    if (IS_IN_BMP(ch)) {
      ch = ToLowerCase(static_cast<char16_t>(ch));
    }
    aCandidates.AppendElement(ch);
  }
  for (uint32_t i = 0; i < mAlternativeCharCodes.Length(); ++i) {
    uint32_t ch[2] = {mAlternativeCharCodes[i].mUnshiftedCharCode,
                      mAlternativeCharCodes[i].mShiftedCharCode};
    for (uint32_t j = 0; j < 2; ++j) {
      if (!ch[j]) {
        continue;
      }
      if (IS_IN_BMP(ch[j])) {
        ch[j] = ToLowerCase(static_cast<char16_t>(ch[j]));
      }
      // Don't append the charcode that was already appended.
      if (aCandidates.IndexOf(ch[j]) == aCandidates.NoIndex) {
        aCandidates.AppendElement(ch[j]);
      }
    }
  }
  // Special case for "Space" key.  With some keyboard layouts, "Space" with
  // or without Shift key causes non-ASCII space.  For such keyboard layouts,
  // we should guarantee that the key press works as an ASCII white space key
  // press.  However, if the space key is assigned to a function key, it
  // shouldn't work as a space key.
  if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
      mCodeNameIndex == CODE_NAME_INDEX_Space && pseudoCharCode != ' ') {
    aCandidates.AppendElement(' ');
  }
}

// mask values for ui.key.chromeAccess and ui.key.contentAccess
#define NS_MODIFIER_SHIFT 1
#define NS_MODIFIER_CONTROL 2
#define NS_MODIFIER_ALT 4
#define NS_MODIFIER_META 8

static Modifiers PrefFlagsToModifiers(int32_t aPrefFlags) {
  Modifiers result = 0;
  if (aPrefFlags & NS_MODIFIER_SHIFT) {
    result |= MODIFIER_SHIFT;
  }
  if (aPrefFlags & NS_MODIFIER_CONTROL) {
    result |= MODIFIER_CONTROL;
  }
  if (aPrefFlags & NS_MODIFIER_ALT) {
    result |= MODIFIER_ALT;
  }
  if (aPrefFlags & NS_MODIFIER_META) {
    result |= MODIFIER_META;
  }
  return result;
}

bool WidgetKeyboardEvent::ModifiersMatchWithAccessKey(
    AccessKeyType aType) const {
  if (!ModifiersForAccessKeyMatching()) {
    return false;
  }
  return ModifiersForAccessKeyMatching() == AccessKeyModifiers(aType);
}

Modifiers WidgetKeyboardEvent::ModifiersForAccessKeyMatching() const {
  static const Modifiers kModifierMask =
      MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META;
  return mModifiers & kModifierMask;
}

/* static */
Modifiers WidgetKeyboardEvent::AccessKeyModifiers(AccessKeyType aType) {
  switch (StaticPrefs::ui_key_generalAccessKey()) {
    case -1:
      break;  // use the individual prefs
    case NS_VK_SHIFT:
      return MODIFIER_SHIFT;
    case NS_VK_CONTROL:
      return MODIFIER_CONTROL;
    case NS_VK_ALT:
      return MODIFIER_ALT;
    case NS_VK_META:
    case NS_VK_WIN:
      return MODIFIER_META;
    default:
      return MODIFIER_NONE;
  }

  switch (aType) {
    case AccessKeyType::eChrome:
      return PrefFlagsToModifiers(StaticPrefs::ui_key_chromeAccess());
    case AccessKeyType::eContent:
      return PrefFlagsToModifiers(StaticPrefs::ui_key_contentAccess());
    default:
      return MODIFIER_NONE;
  }
}

/* static */
void WidgetKeyboardEvent::Shutdown() {
  delete sKeyNameIndexHashtable;
  sKeyNameIndexHashtable = nullptr;
  delete sCodeNameIndexHashtable;
  sCodeNameIndexHashtable = nullptr;
  // Although sCommandHashtable is not a member of WidgetKeyboardEvent, but
  // let's delete it here since we need to do it at same time.
  delete sCommandHashtable;
  sCommandHashtable = nullptr;
}

/* static */
void WidgetKeyboardEvent::GetDOMKeyName(KeyNameIndex aKeyNameIndex,
                                        nsAString& aKeyName) {
  if (aKeyNameIndex >= KEY_NAME_INDEX_USE_STRING) {
    aKeyName.Truncate();
    return;
  }

  MOZ_RELEASE_ASSERT(
      static_cast<size_t>(aKeyNameIndex) < ArrayLength(kKeyNames),
      "Illegal key enumeration value");
  aKeyName = kKeyNames[aKeyNameIndex];
}

/* static */
void WidgetKeyboardEvent::GetDOMCodeName(CodeNameIndex aCodeNameIndex,
                                         nsAString& aCodeName) {
  if (aCodeNameIndex >= CODE_NAME_INDEX_USE_STRING) {
    aCodeName.Truncate();
    return;
  }

  MOZ_RELEASE_ASSERT(
      static_cast<size_t>(aCodeNameIndex) < ArrayLength(kCodeNames),
      "Illegal physical code enumeration value");

  // Generate some continuous runs of codes, rather than looking them up.
  if (aCodeNameIndex >= CODE_NAME_INDEX_KeyA &&
      aCodeNameIndex <= CODE_NAME_INDEX_KeyZ) {
    uint32_t index = aCodeNameIndex - CODE_NAME_INDEX_KeyA;
    aCodeName.AssignLiteral(u"Key");
    aCodeName.Append(u'A' + index);
    return;
  }
  if (aCodeNameIndex >= CODE_NAME_INDEX_Digit0 &&
      aCodeNameIndex <= CODE_NAME_INDEX_Digit9) {
    uint32_t index = aCodeNameIndex - CODE_NAME_INDEX_Digit0;
    aCodeName.AssignLiteral(u"Digit");
    aCodeName.AppendInt(index);
    return;
  }
  if (aCodeNameIndex >= CODE_NAME_INDEX_Numpad0 &&
      aCodeNameIndex <= CODE_NAME_INDEX_Numpad9) {
    uint32_t index = aCodeNameIndex - CODE_NAME_INDEX_Numpad0;
    aCodeName.AssignLiteral(u"Numpad");
    aCodeName.AppendInt(index);
    return;
  }
  if (aCodeNameIndex >= CODE_NAME_INDEX_F1 &&
      aCodeNameIndex <= CODE_NAME_INDEX_F24) {
    uint32_t index = aCodeNameIndex - CODE_NAME_INDEX_F1;
    aCodeName.Assign(u'F');
    aCodeName.AppendInt(index + 1);
    return;
  }

  aCodeName = kCodeNames[aCodeNameIndex];
}

/* static */
KeyNameIndex WidgetKeyboardEvent::GetKeyNameIndex(const nsAString& aKeyValue) {
  if (!sKeyNameIndexHashtable) {
    sKeyNameIndexHashtable = new KeyNameIndexHashtable(ArrayLength(kKeyNames));
    for (size_t i = 0; i < ArrayLength(kKeyNames); i++) {
      sKeyNameIndexHashtable->InsertOrUpdate(nsDependentString(kKeyNames[i]),
                                             static_cast<KeyNameIndex>(i));
    }
  }
  return sKeyNameIndexHashtable->MaybeGet(aKeyValue).valueOr(
      KEY_NAME_INDEX_USE_STRING);
}

/* static */
CodeNameIndex WidgetKeyboardEvent::GetCodeNameIndex(
    const nsAString& aCodeValue) {
  if (!sCodeNameIndexHashtable) {
    sCodeNameIndexHashtable =
        new CodeNameIndexHashtable(ArrayLength(kCodeNames));
    for (size_t i = 0; i < ArrayLength(kCodeNames); i++) {
      sCodeNameIndexHashtable->InsertOrUpdate(nsDependentString(kCodeNames[i]),
                                              static_cast<CodeNameIndex>(i));
    }
  }
  return sCodeNameIndexHashtable->MaybeGet(aCodeValue)
      .valueOr(CODE_NAME_INDEX_USE_STRING);
}

/* static */
uint32_t WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(
    CodeNameIndex aCodeNameIndex) {
  switch (aCodeNameIndex) {
    case CODE_NAME_INDEX_Semicolon:  // VK_OEM_1 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_SEMICOLON;
    case CODE_NAME_INDEX_Equal:  // VK_OEM_PLUS on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_EQUALS;
    case CODE_NAME_INDEX_Comma:  // VK_OEM_COMMA on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_COMMA;
    case CODE_NAME_INDEX_Minus:  // VK_OEM_MINUS on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_HYPHEN_MINUS;
    case CODE_NAME_INDEX_Period:  // VK_OEM_PERIOD on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_PERIOD;
    case CODE_NAME_INDEX_Slash:  // VK_OEM_2 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_SLASH;
    case CODE_NAME_INDEX_Backquote:  // VK_OEM_3 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_BACK_QUOTE;
    case CODE_NAME_INDEX_BracketLeft:  // VK_OEM_4 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_OPEN_BRACKET;
    case CODE_NAME_INDEX_Backslash:  // VK_OEM_5 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_BACK_SLASH;
    case CODE_NAME_INDEX_BracketRight:  // VK_OEM_6 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_CLOSE_BRACKET;
    case CODE_NAME_INDEX_Quote:  // VK_OEM_7 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_QUOTE;
    case CODE_NAME_INDEX_IntlBackslash:  // VK_OEM_5 on Windows (ABNT, etc)
    case CODE_NAME_INDEX_IntlYen:        // VK_OEM_5 on Windows (JIS)
    case CODE_NAME_INDEX_IntlRo:         // VK_OEM_102 on Windows
      return dom::KeyboardEvent_Binding::DOM_VK_BACK_SLASH;
    default:
      return 0;
  }
}

/* static */ const char* WidgetKeyboardEvent::GetCommandStr(Command aCommand) {
#define NS_DEFINE_COMMAND(aName, aCommandStr) , #aCommandStr
#define NS_DEFINE_COMMAND_WITH_PARAM(aName, aCommandStr, aParam) , #aCommandStr
#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) , ""
  static const char* const kCommands[] = {
      ""  // DoNothing
#include "mozilla/CommandList.h"
  };
#undef NS_DEFINE_COMMAND
#undef NS_DEFINE_COMMAND_WITH_PARAM
#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND

  MOZ_RELEASE_ASSERT(static_cast<size_t>(aCommand) < ArrayLength(kCommands),
                     "Illegal command enumeration value");
  return kCommands[static_cast<CommandInt>(aCommand)];
}

/* static */
uint32_t WidgetKeyboardEvent::ComputeLocationFromCodeValue(
    CodeNameIndex aCodeNameIndex) {
  // Following commented out cases are not defined in PhysicalKeyCodeNameList.h
  // but are defined by D3E spec.  So, they should be uncommented when the
  // code values are defined in the header.
  switch (aCodeNameIndex) {
    case CODE_NAME_INDEX_AltLeft:
    case CODE_NAME_INDEX_ControlLeft:
    case CODE_NAME_INDEX_MetaLeft:
    case CODE_NAME_INDEX_ShiftLeft:
      return eKeyLocationLeft;
    case CODE_NAME_INDEX_AltRight:
    case CODE_NAME_INDEX_ControlRight:
    case CODE_NAME_INDEX_MetaRight:
    case CODE_NAME_INDEX_ShiftRight:
      return eKeyLocationRight;
    case CODE_NAME_INDEX_Numpad0:
    case CODE_NAME_INDEX_Numpad1:
    case CODE_NAME_INDEX_Numpad2:
    case CODE_NAME_INDEX_Numpad3:
    case CODE_NAME_INDEX_Numpad4:
    case CODE_NAME_INDEX_Numpad5:
    case CODE_NAME_INDEX_Numpad6:
    case CODE_NAME_INDEX_Numpad7:
    case CODE_NAME_INDEX_Numpad8:
    case CODE_NAME_INDEX_Numpad9:
    case CODE_NAME_INDEX_NumpadAdd:
    case CODE_NAME_INDEX_NumpadBackspace:
    case CODE_NAME_INDEX_NumpadClear:
    case CODE_NAME_INDEX_NumpadClearEntry:
    case CODE_NAME_INDEX_NumpadComma:
    case CODE_NAME_INDEX_NumpadDecimal:
    case CODE_NAME_INDEX_NumpadDivide:
    case CODE_NAME_INDEX_NumpadEnter:
    case CODE_NAME_INDEX_NumpadEqual:
    case CODE_NAME_INDEX_NumpadMemoryAdd:
    case CODE_NAME_INDEX_NumpadMemoryClear:
    case CODE_NAME_INDEX_NumpadMemoryRecall:
    case CODE_NAME_INDEX_NumpadMemoryStore:
    case CODE_NAME_INDEX_NumpadMemorySubtract:
    case CODE_NAME_INDEX_NumpadMultiply:
    case CODE_NAME_INDEX_NumpadParenLeft:
    case CODE_NAME_INDEX_NumpadParenRight:
    case CODE_NAME_INDEX_NumpadSubtract:
      return eKeyLocationNumpad;
    default:
      return eKeyLocationStandard;
  }
}

/* static */
uint32_t WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(
    KeyNameIndex aKeyNameIndex) {
  switch (aKeyNameIndex) {
    case KEY_NAME_INDEX_Cancel:
      return dom::KeyboardEvent_Binding::DOM_VK_CANCEL;
    case KEY_NAME_INDEX_Help:
      return dom::KeyboardEvent_Binding::DOM_VK_HELP;
    case KEY_NAME_INDEX_Backspace:
      return dom::KeyboardEvent_Binding::DOM_VK_BACK_SPACE;
    case KEY_NAME_INDEX_Tab:
      return dom::KeyboardEvent_Binding::DOM_VK_TAB;
    case KEY_NAME_INDEX_Clear:
      return dom::KeyboardEvent_Binding::DOM_VK_CLEAR;
    case KEY_NAME_INDEX_Enter:
      return dom::KeyboardEvent_Binding::DOM_VK_RETURN;
    case KEY_NAME_INDEX_Shift:
      return dom::KeyboardEvent_Binding::DOM_VK_SHIFT;
    case KEY_NAME_INDEX_Control:
      return dom::KeyboardEvent_Binding::DOM_VK_CONTROL;
    case KEY_NAME_INDEX_Alt:
      return dom::KeyboardEvent_Binding::DOM_VK_ALT;
    case KEY_NAME_INDEX_Pause:
      return dom::KeyboardEvent_Binding::DOM_VK_PAUSE;
    case KEY_NAME_INDEX_CapsLock:
      return dom::KeyboardEvent_Binding::DOM_VK_CAPS_LOCK;
    case KEY_NAME_INDEX_Hiragana:
    case KEY_NAME_INDEX_Katakana:
    case KEY_NAME_INDEX_HiraganaKatakana:
    case KEY_NAME_INDEX_KanaMode:
      return dom::KeyboardEvent_Binding::DOM_VK_KANA;
    case KEY_NAME_INDEX_HangulMode:
      return dom::KeyboardEvent_Binding::DOM_VK_HANGUL;
    case KEY_NAME_INDEX_Eisu:
      return dom::KeyboardEvent_Binding::DOM_VK_EISU;
    case KEY_NAME_INDEX_JunjaMode:
      return dom::KeyboardEvent_Binding::DOM_VK_JUNJA;
    case KEY_NAME_INDEX_FinalMode:
      return dom::KeyboardEvent_Binding::DOM_VK_FINAL;
    case KEY_NAME_INDEX_HanjaMode:
      return dom::KeyboardEvent_Binding::DOM_VK_HANJA;
    case KEY_NAME_INDEX_KanjiMode:
      return dom::KeyboardEvent_Binding::DOM_VK_KANJI;
    case KEY_NAME_INDEX_Escape:
      return dom::KeyboardEvent_Binding::DOM_VK_ESCAPE;
    case KEY_NAME_INDEX_Convert:
      return dom::KeyboardEvent_Binding::DOM_VK_CONVERT;
    case KEY_NAME_INDEX_NonConvert:
      return dom::KeyboardEvent_Binding::DOM_VK_NONCONVERT;
    case KEY_NAME_INDEX_Accept:
      return dom::KeyboardEvent_Binding::DOM_VK_ACCEPT;
    case KEY_NAME_INDEX_ModeChange:
      return dom::KeyboardEvent_Binding::DOM_VK_MODECHANGE;
    case KEY_NAME_INDEX_PageUp:
      return dom::KeyboardEvent_Binding::DOM_VK_PAGE_UP;
    case KEY_NAME_INDEX_PageDown:
      return dom::KeyboardEvent_Binding::DOM_VK_PAGE_DOWN;
    case KEY_NAME_INDEX_End:
      return dom::KeyboardEvent_Binding::DOM_VK_END;
    case KEY_NAME_INDEX_Home:
      return dom::KeyboardEvent_Binding::DOM_VK_HOME;
    case KEY_NAME_INDEX_ArrowLeft:
      return dom::KeyboardEvent_Binding::DOM_VK_LEFT;
    case KEY_NAME_INDEX_ArrowUp:
      return dom::KeyboardEvent_Binding::DOM_VK_UP;
    case KEY_NAME_INDEX_ArrowRight:
      return dom::KeyboardEvent_Binding::DOM_VK_RIGHT;
    case KEY_NAME_INDEX_ArrowDown:
      return dom::KeyboardEvent_Binding::DOM_VK_DOWN;
    case KEY_NAME_INDEX_Select:
      return dom::KeyboardEvent_Binding::DOM_VK_SELECT;
    case KEY_NAME_INDEX_Print:
      return dom::KeyboardEvent_Binding::DOM_VK_PRINT;
    case KEY_NAME_INDEX_Execute:
      return dom::KeyboardEvent_Binding::DOM_VK_EXECUTE;
    case KEY_NAME_INDEX_PrintScreen:
      return dom::KeyboardEvent_Binding::DOM_VK_PRINTSCREEN;
    case KEY_NAME_INDEX_Insert:
      return dom::KeyboardEvent_Binding::DOM_VK_INSERT;
    case KEY_NAME_INDEX_Delete:
      return dom::KeyboardEvent_Binding::DOM_VK_DELETE;
    case KEY_NAME_INDEX_ContextMenu:
      return dom::KeyboardEvent_Binding::DOM_VK_CONTEXT_MENU;
    case KEY_NAME_INDEX_Standby:
      return dom::KeyboardEvent_Binding::DOM_VK_SLEEP;
    case KEY_NAME_INDEX_F1:
      return dom::KeyboardEvent_Binding::DOM_VK_F1;
    case KEY_NAME_INDEX_F2:
      return dom::KeyboardEvent_Binding::DOM_VK_F2;
    case KEY_NAME_INDEX_F3:
      return dom::KeyboardEvent_Binding::DOM_VK_F3;
    case KEY_NAME_INDEX_F4:
      return dom::KeyboardEvent_Binding::DOM_VK_F4;
    case KEY_NAME_INDEX_F5:
      return dom::KeyboardEvent_Binding::DOM_VK_F5;
    case KEY_NAME_INDEX_F6:
      return dom::KeyboardEvent_Binding::DOM_VK_F6;
    case KEY_NAME_INDEX_F7:
      return dom::KeyboardEvent_Binding::DOM_VK_F7;
    case KEY_NAME_INDEX_F8:
      return dom::KeyboardEvent_Binding::DOM_VK_F8;
    case KEY_NAME_INDEX_F9:
      return dom::KeyboardEvent_Binding::DOM_VK_F9;
    case KEY_NAME_INDEX_F10:
      return dom::KeyboardEvent_Binding::DOM_VK_F10;
    case KEY_NAME_INDEX_F11:
      return dom::KeyboardEvent_Binding::DOM_VK_F11;
    case KEY_NAME_INDEX_F12:
      return dom::KeyboardEvent_Binding::DOM_VK_F12;
    case KEY_NAME_INDEX_F13:
      return dom::KeyboardEvent_Binding::DOM_VK_F13;
    case KEY_NAME_INDEX_F14:
      return dom::KeyboardEvent_Binding::DOM_VK_F14;
    case KEY_NAME_INDEX_F15:
      return dom::KeyboardEvent_Binding::DOM_VK_F15;
    case KEY_NAME_INDEX_F16:
      return dom::KeyboardEvent_Binding::DOM_VK_F16;
    case KEY_NAME_INDEX_F17:
      return dom::KeyboardEvent_Binding::DOM_VK_F17;
    case KEY_NAME_INDEX_F18:
      return dom::KeyboardEvent_Binding::DOM_VK_F18;
    case KEY_NAME_INDEX_F19:
      return dom::KeyboardEvent_Binding::DOM_VK_F19;
    case KEY_NAME_INDEX_F20:
      return dom::KeyboardEvent_Binding::DOM_VK_F20;
    case KEY_NAME_INDEX_F21:
      return dom::KeyboardEvent_Binding::DOM_VK_F21;
    case KEY_NAME_INDEX_F22:
      return dom::KeyboardEvent_Binding::DOM_VK_F22;
    case KEY_NAME_INDEX_F23:
      return dom::KeyboardEvent_Binding::DOM_VK_F23;
    case KEY_NAME_INDEX_F24:
      return dom::KeyboardEvent_Binding::DOM_VK_F24;
    case KEY_NAME_INDEX_NumLock:
      return dom::KeyboardEvent_Binding::DOM_VK_NUM_LOCK;
    case KEY_NAME_INDEX_ScrollLock:
      return dom::KeyboardEvent_Binding::DOM_VK_SCROLL_LOCK;
    case KEY_NAME_INDEX_AudioVolumeMute:
      return dom::KeyboardEvent_Binding::DOM_VK_VOLUME_MUTE;
    case KEY_NAME_INDEX_AudioVolumeDown:
      return dom::KeyboardEvent_Binding::DOM_VK_VOLUME_DOWN;
    case KEY_NAME_INDEX_AudioVolumeUp:
      return dom::KeyboardEvent_Binding::DOM_VK_VOLUME_UP;
    case KEY_NAME_INDEX_Meta:
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
      return dom::KeyboardEvent_Binding::DOM_VK_WIN;
#else
      return dom::KeyboardEvent_Binding::DOM_VK_META;
#endif
    case KEY_NAME_INDEX_AltGraph:
      return dom::KeyboardEvent_Binding::DOM_VK_ALTGR;
    case KEY_NAME_INDEX_Process:
      return dom::KeyboardEvent_Binding::DOM_VK_PROCESSKEY;
    case KEY_NAME_INDEX_Attn:
      return dom::KeyboardEvent_Binding::DOM_VK_ATTN;
    case KEY_NAME_INDEX_CrSel:
      return dom::KeyboardEvent_Binding::DOM_VK_CRSEL;
    case KEY_NAME_INDEX_ExSel:
      return dom::KeyboardEvent_Binding::DOM_VK_EXSEL;
    case KEY_NAME_INDEX_EraseEof:
      return dom::KeyboardEvent_Binding::DOM_VK_EREOF;
    case KEY_NAME_INDEX_Play:
      return dom::KeyboardEvent_Binding::DOM_VK_PLAY;
    case KEY_NAME_INDEX_ZoomToggle:
    case KEY_NAME_INDEX_ZoomIn:
    case KEY_NAME_INDEX_ZoomOut:
      return dom::KeyboardEvent_Binding::DOM_VK_ZOOM;
    default:
      return 0;
  }
}

/* static */
CodeNameIndex WidgetKeyboardEvent::ComputeCodeNameIndexFromKeyNameIndex(
    KeyNameIndex aKeyNameIndex, const Maybe<uint32_t>& aLocation) {
  if (aLocation.isSome() &&
      aLocation.value() ==
          dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_NUMPAD) {
    // On macOS, NumLock is not supported.  Therefore, this handles
    // control key values except "Enter" only on non-macOS platforms.
    switch (aKeyNameIndex) {
#ifndef XP_MACOSX
      case KEY_NAME_INDEX_Insert:
        return CODE_NAME_INDEX_Numpad0;
      case KEY_NAME_INDEX_End:
        return CODE_NAME_INDEX_Numpad1;
      case KEY_NAME_INDEX_ArrowDown:
        return CODE_NAME_INDEX_Numpad2;
      case KEY_NAME_INDEX_PageDown:
        return CODE_NAME_INDEX_Numpad3;
      case KEY_NAME_INDEX_ArrowLeft:
        return CODE_NAME_INDEX_Numpad4;
      case KEY_NAME_INDEX_Clear:
        // FYI: "Clear" on macOS should be DOM_KEY_LOCATION_STANDARD.
        return CODE_NAME_INDEX_Numpad5;
      case KEY_NAME_INDEX_ArrowRight:
        return CODE_NAME_INDEX_Numpad6;
      case KEY_NAME_INDEX_Home:
        return CODE_NAME_INDEX_Numpad7;
      case KEY_NAME_INDEX_ArrowUp:
        return CODE_NAME_INDEX_Numpad8;
      case KEY_NAME_INDEX_PageUp:
        return CODE_NAME_INDEX_Numpad9;
      case KEY_NAME_INDEX_Delete:
        return CODE_NAME_INDEX_NumpadDecimal;
#endif  // #ifndef XP_MACOSX
      case KEY_NAME_INDEX_Enter:
        return CODE_NAME_INDEX_NumpadEnter;
      default:
        return CODE_NAME_INDEX_UNKNOWN;
    }
  }

  if (WidgetKeyboardEvent::IsLeftOrRightModiferKeyNameIndex(aKeyNameIndex)) {
    if (aLocation.isSome() &&
        (aLocation.value() !=
             dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_LEFT &&
         aLocation.value() !=
             dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_RIGHT)) {
      return CODE_NAME_INDEX_UNKNOWN;
    }
    bool isRight =
        aLocation.isSome() &&
        aLocation.value() == dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_RIGHT;
    switch (aKeyNameIndex) {
      case KEY_NAME_INDEX_Alt:
        return isRight ? CODE_NAME_INDEX_AltRight : CODE_NAME_INDEX_AltLeft;
      case KEY_NAME_INDEX_Control:
        return isRight ? CODE_NAME_INDEX_ControlRight
                       : CODE_NAME_INDEX_ControlLeft;
      case KEY_NAME_INDEX_Shift:
        return isRight ? CODE_NAME_INDEX_ShiftRight : CODE_NAME_INDEX_ShiftLeft;
      case KEY_NAME_INDEX_Meta:
        return isRight ? CODE_NAME_INDEX_MetaRight : CODE_NAME_INDEX_MetaLeft;
      default:
        return CODE_NAME_INDEX_UNKNOWN;
    }
  }

  if (aLocation.isSome() &&
      aLocation.value() !=
          dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_STANDARD) {
    return CODE_NAME_INDEX_UNKNOWN;
  }

  switch (aKeyNameIndex) {
    // Standard section:
    case KEY_NAME_INDEX_Escape:
      return CODE_NAME_INDEX_Escape;
    case KEY_NAME_INDEX_Tab:
      return CODE_NAME_INDEX_Tab;
    case KEY_NAME_INDEX_CapsLock:
      return CODE_NAME_INDEX_CapsLock;
    case KEY_NAME_INDEX_ContextMenu:
      return CODE_NAME_INDEX_ContextMenu;
    case KEY_NAME_INDEX_Backspace:
      return CODE_NAME_INDEX_Backspace;
    case KEY_NAME_INDEX_Enter:
      return CODE_NAME_INDEX_Enter;
#ifdef XP_MACOSX
    // Although, macOS does not fire native key event of "Fn" key, we support
    // Fn key event if it's sent by other apps directly.
    case KEY_NAME_INDEX_Fn:
      return CODE_NAME_INDEX_Fn;
#endif  // #ifdef

    // Arrow Pad section:
    case KEY_NAME_INDEX_ArrowLeft:
      return CODE_NAME_INDEX_ArrowLeft;
    case KEY_NAME_INDEX_ArrowUp:
      return CODE_NAME_INDEX_ArrowUp;
    case KEY_NAME_INDEX_ArrowDown:
      return CODE_NAME_INDEX_ArrowDown;
    case KEY_NAME_INDEX_ArrowRight:
      return CODE_NAME_INDEX_ArrowRight;

      // Control Pad section:
#ifndef XP_MACOSX
    case KEY_NAME_INDEX_Insert:
      return CODE_NAME_INDEX_Insert;
#else
    case KEY_NAME_INDEX_Help:
      return CODE_NAME_INDEX_Help;
#endif  // #ifndef XP_MACOSX #else
    case KEY_NAME_INDEX_Delete:
      return CODE_NAME_INDEX_Delete;
    case KEY_NAME_INDEX_Home:
      return CODE_NAME_INDEX_Home;
    case KEY_NAME_INDEX_End:
      return CODE_NAME_INDEX_End;
    case KEY_NAME_INDEX_PageUp:
      return CODE_NAME_INDEX_PageUp;
    case KEY_NAME_INDEX_PageDown:
      return CODE_NAME_INDEX_PageDown;

    // Function keys:
    case KEY_NAME_INDEX_F1:
      return CODE_NAME_INDEX_F1;
    case KEY_NAME_INDEX_F2:
      return CODE_NAME_INDEX_F2;
    case KEY_NAME_INDEX_F3:
      return CODE_NAME_INDEX_F3;
    case KEY_NAME_INDEX_F4:
      return CODE_NAME_INDEX_F4;
    case KEY_NAME_INDEX_F5:
      return CODE_NAME_INDEX_F5;
    case KEY_NAME_INDEX_F6:
      return CODE_NAME_INDEX_F6;
    case KEY_NAME_INDEX_F7:
      return CODE_NAME_INDEX_F7;
    case KEY_NAME_INDEX_F8:
      return CODE_NAME_INDEX_F8;
    case KEY_NAME_INDEX_F9:
      return CODE_NAME_INDEX_F9;
    case KEY_NAME_INDEX_F10:
      return CODE_NAME_INDEX_F10;
    case KEY_NAME_INDEX_F11:
      return CODE_NAME_INDEX_F11;
    case KEY_NAME_INDEX_F12:
      return CODE_NAME_INDEX_F12;
    case KEY_NAME_INDEX_F13:
      return CODE_NAME_INDEX_F13;
    case KEY_NAME_INDEX_F14:
      return CODE_NAME_INDEX_F14;
    case KEY_NAME_INDEX_F15:
      return CODE_NAME_INDEX_F15;
    case KEY_NAME_INDEX_F16:
      return CODE_NAME_INDEX_F16;
    case KEY_NAME_INDEX_F17:
      return CODE_NAME_INDEX_F17;
    case KEY_NAME_INDEX_F18:
      return CODE_NAME_INDEX_F18;
    case KEY_NAME_INDEX_F19:
      return CODE_NAME_INDEX_F19;
    case KEY_NAME_INDEX_F20:
      return CODE_NAME_INDEX_F20;
#ifndef XP_MACOSX
    case KEY_NAME_INDEX_F21:
      return CODE_NAME_INDEX_F21;
    case KEY_NAME_INDEX_F22:
      return CODE_NAME_INDEX_F22;
    case KEY_NAME_INDEX_F23:
      return CODE_NAME_INDEX_F23;
    case KEY_NAME_INDEX_F24:
      return CODE_NAME_INDEX_F24;
    case KEY_NAME_INDEX_Pause:
      return CODE_NAME_INDEX_Pause;
    case KEY_NAME_INDEX_PrintScreen:
      return CODE_NAME_INDEX_PrintScreen;
    case KEY_NAME_INDEX_ScrollLock:
      return CODE_NAME_INDEX_ScrollLock;
#endif  // #ifndef XP_MACOSX

      // NumLock key:
#ifndef XP_MACOSX
    case KEY_NAME_INDEX_NumLock:
      return CODE_NAME_INDEX_NumLock;
#else
    case KEY_NAME_INDEX_Clear:
      return CODE_NAME_INDEX_NumLock;
#endif  // #ifndef XP_MACOSX #else

    // Media keys:
    case KEY_NAME_INDEX_AudioVolumeDown:
      return CODE_NAME_INDEX_VolumeDown;
    case KEY_NAME_INDEX_AudioVolumeMute:
      return CODE_NAME_INDEX_VolumeMute;
    case KEY_NAME_INDEX_AudioVolumeUp:
      return CODE_NAME_INDEX_VolumeUp;
#ifndef XP_MACOSX
    case KEY_NAME_INDEX_BrowserBack:
      return CODE_NAME_INDEX_BrowserBack;
    case KEY_NAME_INDEX_BrowserFavorites:
      return CODE_NAME_INDEX_BrowserFavorites;
    case KEY_NAME_INDEX_BrowserForward:
      return CODE_NAME_INDEX_BrowserForward;
    case KEY_NAME_INDEX_BrowserRefresh:
      return CODE_NAME_INDEX_BrowserRefresh;
    case KEY_NAME_INDEX_BrowserSearch:
      return CODE_NAME_INDEX_BrowserSearch;
    case KEY_NAME_INDEX_BrowserStop:
      return CODE_NAME_INDEX_BrowserStop;
    case KEY_NAME_INDEX_MediaPlayPause:
      return CODE_NAME_INDEX_MediaPlayPause;
    case KEY_NAME_INDEX_MediaStop:
      return CODE_NAME_INDEX_MediaStop;
    case KEY_NAME_INDEX_MediaTrackNext:
      return CODE_NAME_INDEX_MediaTrackNext;
    case KEY_NAME_INDEX_MediaTrackPrevious:
      return CODE_NAME_INDEX_MediaTrackPrevious;
    case KEY_NAME_INDEX_LaunchApplication1:
      return CODE_NAME_INDEX_LaunchApp1;
#endif  // #ifndef XP_MACOSX

      // Only Windows and GTK supports the following multimedia keys.
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
    case KEY_NAME_INDEX_BrowserHome:
      return CODE_NAME_INDEX_BrowserHome;
    case KEY_NAME_INDEX_LaunchApplication2:
      return CODE_NAME_INDEX_LaunchApp2;
#endif  // #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)

      // Only GTK and Android supports the following multimedia keys.
#if defined(MOZ_WIDGET_GTK) || defined(ANDROID)
    case KEY_NAME_INDEX_Eject:
      return CODE_NAME_INDEX_Eject;
    case KEY_NAME_INDEX_WakeUp:
      return CODE_NAME_INDEX_WakeUp;
#endif  // #if defined(MOZ_WIDGET_GTK) || defined(ANDROID)

      // Only Windows does not support Help key (and macOS handled above).
#if !defined(XP_WIN) && !defined(XP_MACOSX)
    case KEY_NAME_INDEX_Help:
      return CODE_NAME_INDEX_Help;
#endif  // #if !defined(XP_WIN) && !defined(XP_MACOSX)

      // IME specific keys:
#ifdef XP_WIN
    case KEY_NAME_INDEX_Convert:
      return CODE_NAME_INDEX_Convert;
    case KEY_NAME_INDEX_NonConvert:
      return CODE_NAME_INDEX_NonConvert;
    case KEY_NAME_INDEX_Alphanumeric:
      return CODE_NAME_INDEX_CapsLock;
    case KEY_NAME_INDEX_KanaMode:
    case KEY_NAME_INDEX_Romaji:
    case KEY_NAME_INDEX_Katakana:
    case KEY_NAME_INDEX_Hiragana:
      return CODE_NAME_INDEX_KanaMode;
    case KEY_NAME_INDEX_Hankaku:
    case KEY_NAME_INDEX_Zenkaku:
    case KEY_NAME_INDEX_KanjiMode:
      return CODE_NAME_INDEX_Backquote;
    case KEY_NAME_INDEX_HanjaMode:
      return CODE_NAME_INDEX_Lang2;
    case KEY_NAME_INDEX_HangulMode:
      return CODE_NAME_INDEX_Lang1;
#endif  // #ifdef XP_WIN

#ifdef MOZ_WIDGET_GTK
    case KEY_NAME_INDEX_Convert:
      return CODE_NAME_INDEX_Convert;
    case KEY_NAME_INDEX_NonConvert:
      return CODE_NAME_INDEX_NonConvert;
    case KEY_NAME_INDEX_Alphanumeric:
      return CODE_NAME_INDEX_CapsLock;
    case KEY_NAME_INDEX_HiraganaKatakana:
      return CODE_NAME_INDEX_KanaMode;
    case KEY_NAME_INDEX_ZenkakuHankaku:
      return CODE_NAME_INDEX_Backquote;
#endif  // #ifdef MOZ_WIDGET_GTK

#ifdef ANDROID
    case KEY_NAME_INDEX_Convert:
      return CODE_NAME_INDEX_Convert;
    case KEY_NAME_INDEX_NonConvert:
      return CODE_NAME_INDEX_NonConvert;
    case KEY_NAME_INDEX_HiraganaKatakana:
      return CODE_NAME_INDEX_KanaMode;
    case KEY_NAME_INDEX_ZenkakuHankaku:
      return CODE_NAME_INDEX_Backquote;
    case KEY_NAME_INDEX_Eisu:
      return CODE_NAME_INDEX_Lang2;
    case KEY_NAME_INDEX_KanjiMode:
      return CODE_NAME_INDEX_Lang1;
#endif  // #ifdef ANDROID

#ifdef XP_MACOSX
    case KEY_NAME_INDEX_Eisu:
      return CODE_NAME_INDEX_Lang2;
    case KEY_NAME_INDEX_KanjiMode:
      return CODE_NAME_INDEX_Lang1;
#endif  // #ifdef XP_MACOSX

    default:
      return CODE_NAME_INDEX_UNKNOWN;
  }
}

/* static */
Modifier WidgetKeyboardEvent::GetModifierForKeyName(
    KeyNameIndex aKeyNameIndex) {
  switch (aKeyNameIndex) {
    case KEY_NAME_INDEX_Alt:
      return MODIFIER_ALT;
    case KEY_NAME_INDEX_AltGraph:
      return MODIFIER_ALTGRAPH;
    case KEY_NAME_INDEX_CapsLock:
      return MODIFIER_CAPSLOCK;
    case KEY_NAME_INDEX_Control:
      return MODIFIER_CONTROL;
    case KEY_NAME_INDEX_Fn:
      return MODIFIER_FN;
    case KEY_NAME_INDEX_FnLock:
      return MODIFIER_FNLOCK;
    // case KEY_NAME_INDEX_Hyper:
    case KEY_NAME_INDEX_Meta:
      return MODIFIER_META;
    case KEY_NAME_INDEX_NumLock:
      return MODIFIER_NUMLOCK;
    case KEY_NAME_INDEX_ScrollLock:
      return MODIFIER_SCROLLLOCK;
    case KEY_NAME_INDEX_Shift:
      return MODIFIER_SHIFT;
    // case KEY_NAME_INDEX_Super:
    case KEY_NAME_INDEX_Symbol:
      return MODIFIER_SYMBOL;
    case KEY_NAME_INDEX_SymbolLock:
      return MODIFIER_SYMBOLLOCK;
    default:
      return MODIFIER_NONE;
  }
}

/* static */
bool WidgetKeyboardEvent::IsLockableModifier(KeyNameIndex aKeyNameIndex) {
  switch (aKeyNameIndex) {
    case KEY_NAME_INDEX_CapsLock:
    case KEY_NAME_INDEX_FnLock:
    case KEY_NAME_INDEX_NumLock:
    case KEY_NAME_INDEX_ScrollLock:
    case KEY_NAME_INDEX_SymbolLock:
      return true;
    default:
      return false;
  }
}

/******************************************************************************
 * mozilla::InternalEditorInputEvent (TextEvents.h)
 ******************************************************************************/

#define NS_DEFINE_INPUTTYPE(aCPPName, aDOMName) (u"" aDOMName),
const char16_t* const InternalEditorInputEvent::kInputTypeNames[] = {
#include "mozilla/InputTypeList.h"
};
#undef NS_DEFINE_INPUTTYPE

InternalEditorInputEvent::InputTypeHashtable*
    InternalEditorInputEvent::sInputTypeHashtable = nullptr;

/* static */
void InternalEditorInputEvent::Shutdown() {
  delete sInputTypeHashtable;
  sInputTypeHashtable = nullptr;
}

/* static */
void InternalEditorInputEvent::GetDOMInputTypeName(EditorInputType aInputType,
                                                   nsAString& aInputTypeName) {
  if (static_cast<size_t>(aInputType) >=
      static_cast<size_t>(EditorInputType::eUnknown)) {
    aInputTypeName.Truncate();
    return;
  }

  MOZ_RELEASE_ASSERT(
      static_cast<size_t>(aInputType) < ArrayLength(kInputTypeNames),
      "Illegal input type enumeration value");
  aInputTypeName.Assign(kInputTypeNames[static_cast<size_t>(aInputType)]);
}

/* static */
EditorInputType InternalEditorInputEvent::GetEditorInputType(
    const nsAString& aInputType) {
  if (aInputType.IsEmpty()) {
    return EditorInputType::eUnknown;
  }

  if (!sInputTypeHashtable) {
    sInputTypeHashtable = new InputTypeHashtable(ArrayLength(kInputTypeNames));
    for (size_t i = 0; i < ArrayLength(kInputTypeNames); i++) {
      sInputTypeHashtable->InsertOrUpdate(nsDependentString(kInputTypeNames[i]),
                                          static_cast<EditorInputType>(i));
    }
  }
  return sInputTypeHashtable->MaybeGet(aInputType)
      .valueOr(EditorInputType::eUnknown);
}

}  // namespace mozilla
