Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/widget/cocoa/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 204 kB image not shown  

SSL TextInputHandler.mm   Interaktion und
Portierbarkeitunbekannt

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TextInputHandler.h"

#include "mozilla/Logging.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/MacStringHelpers.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/StaticPrefs_intl.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/glean/WidgetCocoaMetrics.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/ToString.h"

#include "nsChildView.h"
#include "nsObjCExceptions.h"
#include "nsBidiUtils.h"
#include "nsToolkit.h"
#include "nsCocoaUtils.h"
#include "WidgetUtils.h"
#include "nsPrintfCString.h"

using namespace mozilla;
using namespace mozilla::widget;

// For collecting other people's log, tell them `MOZ_LOG=IMEHandler:4,sync`
// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
// big file.
// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
mozilla::LazyLogModule gIMELog("IMEHandler");

// For collecting other people's log, tell them `MOZ_LOG=KeyboardHandler:4,sync`
// rather than `MOZ_LOG=KeyboardHandler:5,sync` since using `5` may create too
// big file.
// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
mozilla::LazyLogModule gKeyLog("KeyboardHandler");

// The behavior of `TextInputHandler` class is important both for logging
// keyboard handler and IME handler.  Therefore, the behavior is logged when
// either `IMEHandler` or `KeyboardHandler` is set to `MOZ_LOG`.  Therefore,
// you may not need to tell people
// `MOZ_LOG=IMEHandler:4,KeyboardHandler:4,sync`.
#define MOZ_LOG_KEY_OR_IME(aLogLevel, aArgs)                               \
  MOZ_LOG(MOZ_LOG_TEST(gIMELog, aLogLevel) ? gIMELog : gKeyLog, aLogLevel, \
          aArgs)

static const char* OnOrOff(bool aBool) { return aBool ? "ON" : "off"; }

static const char* TrueOrFalse(bool aBool) { return aBool ? "TRUE" : "FALSE"; }

static const char* GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode) {
  switch (aNativeKeyCode) {
    case kVK_Escape:
      return "Escape";
    case kVK_RightCommand:
      return "Right-Command";
    case kVK_Command:
      return "Command";
    case kVK_Shift:
      return "Shift";
    case kVK_CapsLock:
      return "CapsLock";
    case kVK_Option:
      return "Option";
    case kVK_Control:
      return "Control";
    case kVK_RightShift:
      return "Right-Shift";
    case kVK_RightOption:
      return "Right-Option";
    case kVK_RightControl:
      return "Right-Control";
    case kVK_ANSI_KeypadClear:
      return "Clear";

    case kVK_F1:
      return "F1";
    case kVK_F2:
      return "F2";
    case kVK_F3:
      return "F3";
    case kVK_F4:
      return "F4";
    case kVK_F5:
      return "F5";
    case kVK_F6:
      return "F6";
    case kVK_F7:
      return "F7";
    case kVK_F8:
      return "F8";
    case kVK_F9:
      return "F9";
    case kVK_F10:
      return "F10";
    case kVK_F11:
      return "F11";
    case kVK_F12:
      return "F12";
    case kVK_F13:
      return "F13/PrintScreen";
    case kVK_F14:
      return "F14/ScrollLock";
    case kVK_F15:
      return "F15/Pause";

    case kVK_ANSI_Keypad0:
      return "NumPad-0";
    case kVK_ANSI_Keypad1:
      return "NumPad-1";
    case kVK_ANSI_Keypad2:
      return "NumPad-2";
    case kVK_ANSI_Keypad3:
      return "NumPad-3";
    case kVK_ANSI_Keypad4:
      return "NumPad-4";
    case kVK_ANSI_Keypad5:
      return "NumPad-5";
    case kVK_ANSI_Keypad6:
      return "NumPad-6";
    case kVK_ANSI_Keypad7:
      return "NumPad-7";
    case kVK_ANSI_Keypad8:
      return "NumPad-8";
    case kVK_ANSI_Keypad9:
      return "NumPad-9";

    case kVK_ANSI_KeypadMultiply:
      return "NumPad-*";
    case kVK_ANSI_KeypadPlus:
      return "NumPad-+";
    case kVK_ANSI_KeypadMinus:
      return "NumPad--";
    case kVK_ANSI_KeypadDecimal:
      return "NumPad-.";
    case kVK_ANSI_KeypadDivide:
      return "NumPad-/";
    case kVK_ANSI_KeypadEquals:
      return "NumPad-=";
    case kVK_ANSI_KeypadEnter:
      return "NumPad-Enter";
    case kVK_Return:
      return "Return";
    case kVK_Powerbook_KeypadEnter:
      return "NumPad-EnterOnPowerBook";

    case kVK_PC_Insert:
      return "Insert/Help";
    case kVK_PC_Delete:
      return "Delete";
    case kVK_Tab:
      return "Tab";
    case kVK_PC_Backspace:
      return "Backspace";
    case kVK_Home:
      return "Home";
    case kVK_End:
      return "End";
    case kVK_PageUp:
      return "PageUp";
    case kVK_PageDown:
      return "PageDown";
    case kVK_LeftArrow:
      return "LeftArrow";
    case kVK_RightArrow:
      return "RightArrow";
    case kVK_UpArrow:
      return "UpArrow";
    case kVK_DownArrow:
      return "DownArrow";
    case kVK_PC_ContextMenu:
      return "ContextMenu";

    case kVK_Function:
      return "Function";
    case kVK_VolumeUp:
      return "VolumeUp";
    case kVK_VolumeDown:
      return "VolumeDown";
    case kVK_Mute:
      return "Mute";

    case kVK_ISO_Section:
      return "ISO_Section";

    case kVK_JIS_Yen:
      return "JIS_Yen";
    case kVK_JIS_Underscore:
      return "JIS_Underscore";
    case kVK_JIS_KeypadComma:
      return "JIS_KeypadComma";
    case kVK_JIS_Eisu:
      return "JIS_Eisu";
    case kVK_JIS_Kana:
      return "JIS_Kana";

    case kVK_ANSI_A:
      return "A";
    case kVK_ANSI_B:
      return "B";
    case kVK_ANSI_C:
      return "C";
    case kVK_ANSI_D:
      return "D";
    case kVK_ANSI_E:
      return "E";
    case kVK_ANSI_F:
      return "F";
    case kVK_ANSI_G:
      return "G";
    case kVK_ANSI_H:
      return "H";
    case kVK_ANSI_I:
      return "I";
    case kVK_ANSI_J:
      return "J";
    case kVK_ANSI_K:
      return "K";
    case kVK_ANSI_L:
      return "L";
    case kVK_ANSI_M:
      return "M";
    case kVK_ANSI_N:
      return "N";
    case kVK_ANSI_O:
      return "O";
    case kVK_ANSI_P:
      return "P";
    case kVK_ANSI_Q:
      return "Q";
    case kVK_ANSI_R:
      return "R";
    case kVK_ANSI_S:
      return "S";
    case kVK_ANSI_T:
      return "T";
    case kVK_ANSI_U:
      return "U";
    case kVK_ANSI_V:
      return "V";
    case kVK_ANSI_W:
      return "W";
    case kVK_ANSI_X:
      return "X";
    case kVK_ANSI_Y:
      return "Y";
    case kVK_ANSI_Z:
      return "Z";

    case kVK_ANSI_1:
      return "1";
    case kVK_ANSI_2:
      return "2";
    case kVK_ANSI_3:
      return "3";
    case kVK_ANSI_4:
      return "4";
    case kVK_ANSI_5:
      return "5";
    case kVK_ANSI_6:
      return "6";
    case kVK_ANSI_7:
      return "7";
    case kVK_ANSI_8:
      return "8";
    case kVK_ANSI_9:
      return "9";
    case kVK_ANSI_0:
      return "0";
    case kVK_ANSI_Equal:
      return "Equal";
    case kVK_ANSI_Minus:
      return "Minus";
    case kVK_ANSI_RightBracket:
      return "RightBracket";
    case kVK_ANSI_LeftBracket:
      return "LeftBracket";
    case kVK_ANSI_Quote:
      return "Quote";
    case kVK_ANSI_Semicolon:
      return "Semicolon";
    case kVK_ANSI_Backslash:
      return "Backslash";
    case kVK_ANSI_Comma:
      return "Comma";
    case kVK_ANSI_Slash:
      return "Slash";
    case kVK_ANSI_Period:
      return "Period";
    case kVK_ANSI_Grave:
      return "Grave";

    default:
      return "undefined";
  }
}

static const char* GetCharacters(const nsAString& aString) {
  if (aString.IsEmpty()) {
    return "";
  }
  nsAutoString escapedStr;
  for (uint32_t i = 0; i < aString.Length(); i++) {
    char16_t ch = aString.CharAt(i);
    if (ch < 0x20) {
      nsPrintfCString utf8str("(U+%04X)", ch);
      escapedStr += NS_ConvertUTF8toUTF16(utf8str);
    } else if (ch <= 0x7E) {
      escapedStr += ch;
    } else {
      nsPrintfCString utf8str("(U+%04X)", ch);
      escapedStr += ch;
      escapedStr += NS_ConvertUTF8toUTF16(utf8str);
    }
  }

  // the result will be freed automatically by cocoa.
  NSString* result = XPCOMStringToNSString(escapedStr);
  return [result UTF8String];
}

static const char* GetCharacters(const NSString* aString) {
  nsAutoString str;
  CopyNSStringToXPCOMString(aString, str);
  return GetCharacters(str);
}

static const char* GetCharacters(const CFStringRef aString) {
  const NSString* str = reinterpret_cast<const NSString*>(aString);
  return GetCharacters(str);
}

static const char* GetNativeKeyEventType(NSEvent* aNativeEvent) {
  switch ([aNativeEvent type]) {
    case NSEventTypeKeyDown:
      return "NSEventTypeKeyDown";
    case NSEventTypeKeyUp:
      return "NSEventTypeKeyUp";
    case NSEventTypeFlagsChanged:
      return "NSEventTypeFlagsChanged";
    default:
      return "not key event";
  }
}

static const char* GetGeckoKeyEventType(const WidgetEvent& aEvent) {
  switch (aEvent.mMessage) {
    case eKeyDown:
      return "eKeyDown";
    case eKeyUp:
      return "eKeyUp";
    case eKeyPress:
      return "eKeyPress";
    default:
      return "not key event";
  }
}

static const char* GetWindowLevelName(NSInteger aWindowLevel) {
  switch (aWindowLevel) {
    case kCGBaseWindowLevelKey:
      return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
    case kCGMinimumWindowLevelKey:
      return "kCGMinimumWindowLevelKey";
    case kCGDesktopWindowLevelKey:
      return "kCGDesktopWindowLevelKey";
    case kCGBackstopMenuLevelKey:
      return "kCGBackstopMenuLevelKey";
    case kCGNormalWindowLevelKey:
      return "kCGNormalWindowLevelKey";
    case kCGFloatingWindowLevelKey:
      return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
    case kCGTornOffMenuWindowLevelKey:
      return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, "
             "NSTornOffMenuWindowLevel)";
    case kCGDockWindowLevelKey:
      return "kCGDockWindowLevelKey (NSDockWindowLevel)";
    case kCGMainMenuWindowLevelKey:
      return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
    case kCGStatusWindowLevelKey:
      return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
    case kCGModalPanelWindowLevelKey:
      return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
    case kCGPopUpMenuWindowLevelKey:
      return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
    case kCGDraggingWindowLevelKey:
      return "kCGDraggingWindowLevelKey";
    case kCGScreenSaverWindowLevelKey:
      return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
    case kCGMaximumWindowLevelKey:
      return "kCGMaximumWindowLevelKey";
    case kCGOverlayWindowLevelKey:
      return "kCGOverlayWindowLevelKey";
    case kCGHelpWindowLevelKey:
      return "kCGHelpWindowLevelKey";
    case kCGUtilityWindowLevelKey:
      return "kCGUtilityWindowLevelKey";
    case kCGDesktopIconWindowLevelKey:
      return "kCGDesktopIconWindowLevelKey";
    case kCGCursorWindowLevelKey:
      return "kCGCursorWindowLevelKey";
    case kCGNumberOfWindowLevelKeys:
      return "kCGNumberOfWindowLevelKeys";
    default:
      return "unknown window level";
  }
}

static bool IsControlChar(uint32_t aCharCode) {
  return aCharCode < ' ' || aCharCode == 0x7F;
}

static std::ostream& operator<<(std::ostream& aStream, const NSRange& aRange) {
  aStream << "{ location=";
  if (aRange.location == NSNotFound) {
    aStream << "NSNotFound";
  } else {
    aStream << aRange.location;
  }
  aStream << ", length=" << aRange.length << " }";
  return aStream;
}

static uint32_t gHandlerInstanceCount = 0;

static void EnsureToLogAllKeyboardLayoutsAndIMEs() {
  static bool sDone = false;
  if (!sDone) {
    sDone = true;
    TextInputHandler::DebugPrintAllKeyboardLayouts();
    IMEInputHandler::DebugPrintAllIMEModes();
  }
}

inline NSRange MakeNSRangeFrom(
    const Maybe<OffsetAndData<uint32_t>>& aOffsetAndData) {
  if (aOffsetAndData.isNothing()) {
    return NSMakeRange(NSNotFound, 0);
  }
  return NSMakeRange(aOffsetAndData->StartOffset(), aOffsetAndData->Length());
}

#pragma mark -

/******************************************************************************
 *
 *  TISInputSourceWrapper implementation
 *
 ******************************************************************************/

TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;

// static
TISInputSourceWrapper& TISInputSourceWrapper::CurrentInputSource() {
  if (!sCurrentInputSource) {
    sCurrentInputSource = new TISInputSourceWrapper();
  }
  if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
    sCurrentInputSource->InitByCurrentInputSource();
  }
  return *sCurrentInputSource;
}

// static
void TISInputSourceWrapper::Shutdown() {
  if (!sCurrentInputSource) {
    return;
  }
  sCurrentInputSource->Clear();
  delete sCurrentInputSource;
  sCurrentInputSource = nullptr;
}

bool TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode,
                                              UInt32 aModifiers, UInt32 aKbType,
                                              nsAString& aStr) {
  aStr.Truncate();

  const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
           "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n    "
           "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
           this, static_cast<unsigned int>(aKeyCode),
           static_cast<unsigned int>(aModifiers),
           static_cast<unsigned int>(aKbType), UCKey,
           OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
           OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
           OnOrOff(aModifiers & alphaLock),
           OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));

  NS_ENSURE_TRUE(UCKey, false);

  UInt32 deadKeyState = 0;
  UniCharCount len;
  UniChar chars[5];
  OSStatus err = ::UCKeyTranslate(
      UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8, aKbType,
      kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 5, &len, chars);

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::TranslateToString, err=0x%X, len=%zu",
           this, static_cast<int>(err), len));

  NS_ENSURE_TRUE(err == noErr, false);
  if (len == 0) {
    return true;
  }
  if (!aStr.SetLength(len, fallible)) {
    return false;
  }
  NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
               "size of char16_t and size of UniChar are different");
  memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::TranslateToString, aStr=\"%s\"", this,
           NS_ConvertUTF16toUTF8(aStr).get()));

  return true;
}

uint32_t TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode,
                                                UInt32 aModifiers,
                                                UInt32 aKbType) {
  nsAutoString str;
  if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
      str.Length() != 1) {
    return 0;
  }
  return static_cast<uint32_t>(str.CharAt(0));
}

bool TISInputSourceWrapper::IsDeadKey(NSEvent* aNativeKeyEvent) {
  if ([[aNativeKeyEvent characters] length]) {
    return false;
  }

  // Assume that if the control key, command key or Fn key is pressed, it's not
  // a dead key.
  NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
  if (cocoaState & (NSEventModifierFlagControl | NSEventModifierFlagCommand |
                    NSEventModifierFlagFunction)) {
    return false;
  }

  UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
  switch (nativeKeyCode) {
    case kVK_ANSI_A:
    case kVK_ANSI_B:
    case kVK_ANSI_C:
    case kVK_ANSI_D:
    case kVK_ANSI_E:
    case kVK_ANSI_F:
    case kVK_ANSI_G:
    case kVK_ANSI_H:
    case kVK_ANSI_I:
    case kVK_ANSI_J:
    case kVK_ANSI_K:
    case kVK_ANSI_L:
    case kVK_ANSI_M:
    case kVK_ANSI_N:
    case kVK_ANSI_O:
    case kVK_ANSI_P:
    case kVK_ANSI_Q:
    case kVK_ANSI_R:
    case kVK_ANSI_S:
    case kVK_ANSI_T:
    case kVK_ANSI_U:
    case kVK_ANSI_V:
    case kVK_ANSI_W:
    case kVK_ANSI_X:
    case kVK_ANSI_Y:
    case kVK_ANSI_Z:
    case kVK_ANSI_1:
    case kVK_ANSI_2:
    case kVK_ANSI_3:
    case kVK_ANSI_4:
    case kVK_ANSI_5:
    case kVK_ANSI_6:
    case kVK_ANSI_7:
    case kVK_ANSI_8:
    case kVK_ANSI_9:
    case kVK_ANSI_0:
    case kVK_ANSI_Equal:
    case kVK_ANSI_Minus:
    case kVK_ANSI_RightBracket:
    case kVK_ANSI_LeftBracket:
    case kVK_ANSI_Quote:
    case kVK_ANSI_Semicolon:
    case kVK_ANSI_Backslash:
    case kVK_ANSI_Comma:
    case kVK_ANSI_Slash:
    case kVK_ANSI_Period:
    case kVK_ANSI_Grave:
    case kVK_JIS_Yen:
    case kVK_JIS_Underscore:
      break;
    default:
      // Let's assume that dead key can be only a printable key in standard
      // position.
      return false;
  }

  // If TranslateToChar() returns non-zero value, that means that
  // the key may input a character with different dead key state.
  UInt32 kbType = GetKbdType();
  UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
  return IsDeadKey(nativeKeyCode, carbonState, kbType);
}

bool TISInputSourceWrapper::IsDeadKey(UInt32 aKeyCode, UInt32 aModifiers,
                                      UInt32 aKbType) {
  const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p TISInputSourceWrapper::IsDeadKey, aKeyCode=0x%X, "
           "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n    "
           "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
           this, static_cast<unsigned int>(aKeyCode),
           static_cast<unsigned int>(aModifiers),
           static_cast<unsigned int>(aKbType), UCKey,
           OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
           OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
           OnOrOff(aModifiers & alphaLock),
           OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));

  if (NS_WARN_IF(!UCKey)) {
    return false;
  }

  UInt32 deadKeyState = 0;
  UniCharCount len;
  UniChar chars[5];
  OSStatus err =
      ::UCKeyTranslate(UCKey, aKeyCode, kUCKeyActionDown, aModifiers >> 8,
                       aKbType, 0, &deadKeyState, 5, &len, chars);

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::IsDeadKey, err=0x%X, "
           "len=%zu, deadKeyState=%u",
           this, static_cast<int>(err), len, deadKeyState));

  if (NS_WARN_IF(err != noErr)) {
    return false;
  }

  return deadKeyState != 0;
}

void TISInputSourceWrapper::InitByInputSourceID(const char* aID) {
  Clear();
  if (!aID) return;

  CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
                                                  kCFStringEncodingASCII);
  InitByInputSourceID(idstr);
  ::CFRelease(idstr);
}

void TISInputSourceWrapper::InitByInputSourceID(const nsString& aID) {
  Clear();
  if (aID.IsEmpty()) return;
  CFStringRef idstr = ::CFStringCreateWithCharacters(
      kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aID.get()),
      aID.Length());
  InitByInputSourceID(idstr);
  ::CFRelease(idstr);
}

void TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID) {
  Clear();
  if (!aID) return;
  const void* keys[] = {kTISPropertyInputSourceID};
  const void* values[] = {aID};
  CFDictionaryRef filter =
      ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
  NS_ASSERTION(filter, "failed to create the filter");
  mInputSourceList = ::TISCreateInputSourceList(filter, true);
  ::CFRelease(filter);
  if (::CFArrayGetCount(mInputSourceList) > 0) {
    mInputSource = static_cast<TISInputSourceRef>(
        const_cast<void*>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
    if (IsKeyboardLayout()) {
      mKeyboardLayout = mInputSource;
    }
  }
}

void TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
                                           bool aOverrideKeyboard) {
  // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
  switch (aLayoutID) {
    case 0:
      InitByInputSourceID("com.apple.keylayout.US");
      break;
    case 1:
      InitByInputSourceID("com.apple.keylayout.Greek");
      break;
    case 2:
      InitByInputSourceID("com.apple.keylayout.German");
      break;
    case 3:
      InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
      break;
    case 4:
      InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
      break;
    case 5:
      InitByInputSourceID("com.apple.keylayout.Thai");
      break;
    case 6:
      InitByInputSourceID("com.apple.keylayout.Arabic");
      break;
    case 7:
      InitByInputSourceID("com.apple.keylayout.ArabicPC");
      break;
    case 8:
      InitByInputSourceID("com.apple.keylayout.French");
      break;
    case 9:
      InitByInputSourceID("com.apple.keylayout.Hebrew");
      break;
    case 10:
      InitByInputSourceID("com.apple.keylayout.Lithuanian");
      break;
    case 11:
      InitByInputSourceID("com.apple.keylayout.Norwegian");
      break;
    case 12:
      InitByInputSourceID("com.apple.keylayout.Spanish");
      break;
    case 13:
      InitByInputSourceID("com.apple.keylayout.French-PC");
      break;
    default:
      Clear();
      break;
  }
  mOverrideKeyboard = aOverrideKeyboard;
}

void TISInputSourceWrapper::InitByCurrentInputSource() {
  Clear();
  mInputSource = ::TISCopyCurrentKeyboardInputSource();
  mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
  if (!mKeyboardLayout) {
    mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
  }
  // If this causes composition, the current keyboard layout may input non-ASCII
  // characters such as Japanese Kana characters or Hangul characters.
  // However, we need to set ASCII characters to DOM key events for consistency
  // with other platforms.
  if (IsOpenedIMEMode()) {
    TISInputSourceWrapper tis(mKeyboardLayout);
    if (!tis.IsASCIICapable()) {
      mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
    }
  }
}

void TISInputSourceWrapper::InitByCurrentKeyboardLayout() {
  Clear();
  mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
  mKeyboardLayout = mInputSource;
}

void TISInputSourceWrapper::InitByCurrentASCIICapableInputSource() {
  Clear();
  mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
  mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
  if (mKeyboardLayout) {
    TISInputSourceWrapper tis(mKeyboardLayout);
    if (!tis.IsASCIICapable()) {
      mKeyboardLayout = nullptr;
    }
  }
  if (!mKeyboardLayout) {
    mKeyboardLayout = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
  }
}

void TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout() {
  Clear();
  mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
  mKeyboardLayout = mInputSource;
}

void TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride() {
  Clear();
  mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
  mKeyboardLayout = mInputSource;
}

void TISInputSourceWrapper::InitByTISInputSourceRef(
    TISInputSourceRef aInputSource) {
  Clear();
  mInputSource = aInputSource;
  if (IsKeyboardLayout()) {
    mKeyboardLayout = mInputSource;
  }
}

void TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage) {
  Clear();
  mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
  if (IsKeyboardLayout()) {
    mKeyboardLayout = mInputSource;
  }
}

const UCKeyboardLayout* TISInputSourceWrapper::GetUCKeyboardLayout() {
  NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
  if (mUCKeyboardLayout) {
    return mUCKeyboardLayout;
  }
  CFDataRef uchr = static_cast<CFDataRef>(::TISGetInputSourceProperty(
      mKeyboardLayout, kTISPropertyUnicodeKeyLayoutData));

  // We should be always able to get the layout here.
  NS_ENSURE_TRUE(uchr, nullptr);
  mUCKeyboardLayout =
      reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
  return mUCKeyboardLayout;
}

bool TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey) {
  CFBooleanRef ret = static_cast<CFBooleanRef>(
      ::TISGetInputSourceProperty(mInputSource, aKey));
  return ::CFBooleanGetValue(ret);
}

bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
                                              CFStringRef& aStr) {
  aStr =
      static_cast<CFStringRef>(::TISGetInputSourceProperty(mInputSource, aKey));
  return aStr != nullptr;
}

bool TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
                                              nsAString& aStr) {
  CFStringRef str;
  GetStringProperty(aKey, str);
  CopyNSStringToXPCOMString((const NSString*)str, aStr);
  return !aStr.IsEmpty();
}

bool TISInputSourceWrapper::IsOpenedIMEMode() {
  NS_ENSURE_TRUE(mInputSource, false);
  if (!IsIMEMode()) return false;
  return !IsASCIICapable();
}

bool TISInputSourceWrapper::IsIMEMode() {
  NS_ENSURE_TRUE(mInputSource, false);
  CFStringRef str;
  GetInputSourceType(str);
  NS_ENSURE_TRUE(str, false);
  return ::CFStringCompare(kTISTypeKeyboardInputMode, str, 0) ==
         kCFCompareEqualTo;
}

bool TISInputSourceWrapper::IsKeyboardLayout() {
  NS_ENSURE_TRUE(mInputSource, false);
  CFStringRef str;
  GetInputSourceType(str);
  NS_ENSURE_TRUE(str, false);
  return ::CFStringCompare(kTISTypeKeyboardLayout, str, 0) == kCFCompareEqualTo;
}

bool TISInputSourceWrapper::GetLanguageList(CFArrayRef& aLanguageList) {
  NS_ENSURE_TRUE(mInputSource, false);
  aLanguageList = static_cast<CFArrayRef>(::TISGetInputSourceProperty(
      mInputSource, kTISPropertyInputSourceLanguages));
  return aLanguageList != nullptr;
}

bool TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef& aPrimaryLanguage) {
  NS_ENSURE_TRUE(mInputSource, false);
  CFArrayRef langList;
  NS_ENSURE_TRUE(GetLanguageList(langList), false);
  if (::CFArrayGetCount(langList) == 0) return false;
  aPrimaryLanguage =
      static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
  return aPrimaryLanguage != nullptr;
}

bool TISInputSourceWrapper::GetPrimaryLanguage(nsAString& aPrimaryLanguage) {
  NS_ENSURE_TRUE(mInputSource, false);
  CFStringRef primaryLanguage;
  NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
  CopyNSStringToXPCOMString((const NSString*)primaryLanguage, aPrimaryLanguage);
  return !aPrimaryLanguage.IsEmpty();
}

bool TISInputSourceWrapper::IsForRTLLanguage() {
  if (mIsRTL < 0) {
    // Get the input character of the 'A' key of ANSI keyboard layout.
    nsAutoString str;
    bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
    NS_ENSURE_TRUE(ret, ret);
    char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
    mIsRTL = UTF16_CODE_UNIT_IS_BIDI(ch);
  }
  return mIsRTL != 0;
}

bool TISInputSourceWrapper::IsForJapaneseLanguage() {
  nsAutoString lang;
  GetPrimaryLanguage(lang);
  return lang.EqualsLiteral("ja");
}

bool TISInputSourceWrapper::IsInitializedByCurrentInputSource() {
  return mInputSource == ::TISCopyCurrentKeyboardInputSource();
}

void TISInputSourceWrapper::Select() {
  if (!mInputSource) return;
  ::TISSelectInputSource(mInputSource);
}

void TISInputSourceWrapper::Clear() {
  // Clear() is always called when TISInputSourceWrappper is created.
  EnsureToLogAllKeyboardLayoutsAndIMEs();

  if (mInputSourceList) {
    ::CFRelease(mInputSourceList);
  }
  mInputSourceList = nullptr;
  mInputSource = nullptr;
  mKeyboardLayout = nullptr;
  mIsRTL = -1;
  mUCKeyboardLayout = nullptr;
  mOverrideKeyboard = false;
}

bool TISInputSourceWrapper::IsPrintableKeyEvent(
    NSEvent* aNativeKeyEvent) const {
  UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];

  bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
  if (isPrintableKey && [aNativeKeyEvent type] != NSEventTypeKeyDown &&
      [aNativeKeyEvent type] != NSEventTypeKeyUp) {
    NS_WARNING("Why would a printable key not be an NSEventTypeKeyDown or "
               "NSEventTypeKeyUp event?");
    isPrintableKey = false;
  }
  return isPrintableKey;
}

UInt32 TISInputSourceWrapper::GetKbdType() const {
  // If a keyboard layout override is set, we also need to force the keyboard
  // type to something ANSI to avoid test failures on machines with JIS
  // keyboards (since the pair of keyboard layout and physical keyboard type
  // form the actual key layout).  This assumes that the test setting the
  // override was written assuming an ANSI keyboard.
  return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
}

void TISInputSourceWrapper::ComputeInsertStringForCharCode(
    NSEvent* aNativeKeyEvent, const WidgetKeyboardEvent& aKeyEvent,
    const nsAString* aInsertString, nsAString& aResult) {
  if (aInsertString) {
    // If the caller expects that the aInsertString will be input, we shouldn't
    // change it.
    aResult = *aInsertString;
  } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
    // If IME is open, [aNativeKeyEvent characters] may be a character
    // which will be appended to the composition string.  However, especially,
    // while IME is disabled, most users and developers expect the key event
    // works as IME closed.  So, we should compute the aResult with
    // the ASCII capable keyboard layout.
    // NOTE: Such keyboard layouts typically change the layout to its ASCII
    //       capable layout when Command key is pressed.  And we don't worry
    //       when Control key is pressed too because it causes inputting
    //       control characters.
    // Additionally, if the key event doesn't input any text, the event may be
    // dead key event.  In this case, the charCode value should be the dead
    // character.
    UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
    if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
        ![[aNativeKeyEvent characters] length]) {
      UInt32 state = nsCocoaUtils::ConvertToCarbonModifier(
          [aNativeKeyEvent modifierFlags]);
      uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
      if (ch) {
        aResult = ch;
      }
    } else {
      // If the caller isn't sure what string will be input, let's use
      // characters of NSEvent.
      CopyNSStringToXPCOMString([aNativeKeyEvent characters], aResult);
    }

    // If control key is pressed and the eventChars is a non-printable control
    // character, we should convert it to ASCII alphabet.
    if (aKeyEvent.IsControl() && !aResult.IsEmpty() &&
        aResult[0] <= char16_t(26)) {
      aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked())
                    ? static_cast<char16_t>(aResult[0] + ('A' - 1))
                    : static_cast<char16_t>(aResult[0] + ('a' - 1));
    }
    // If Meta key is pressed, it may cause to switch the keyboard layout like
    // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
    else if (aKeyEvent.IsMeta() &&
             !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
      UInt32 kbType = GetKbdType();
      UInt32 numLockState =
          aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
      UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
      UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
      uint32_t uncmdedChar =
          TranslateToChar(nativeKeyCode, numLockState, kbType);
      uint32_t cmdedChar =
          TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
      // If we can make a good guess at the characters that the user would
      // expect this key combination to produce (with and without Shift) then
      // use those characters.  This also corrects for CapsLock.
      uint32_t ch = 0;
      if (uncmdedChar == cmdedChar) {
        // The characters produced with Command seem similar to those without
        // Command.
        ch = TranslateToChar(nativeKeyCode,
                             shiftState | capsLockState | numLockState, kbType);
      } else {
        TISInputSourceWrapper USLayout("com.apple.keylayout.US");
        uint32_t uncmdedUSChar =
            USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
        // If it looks like characters from US keyboard layout when Command key
        // is pressed, we should compute a character in the layout.
        if (uncmdedUSChar == cmdedChar) {
          ch = USLayout.TranslateToChar(
              nativeKeyCode, shiftState | capsLockState | numLockState, kbType);
        }
      }

      // If there is a more preferred character for the commanded key event,
      // we should use it.
      if (ch) {
        aResult = ch;
      }
    }
  }

  // Remove control characters which shouldn't be inputted on editor.
  // XXX Currently, we don't find any cases inserting control characters with
  //     printable character.  So, just checking first character is enough.
  if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
    aResult.Truncate();
  }
}

void TISInputSourceWrapper::InitKeyEvent(NSEvent* aNativeKeyEvent,
                                         WidgetKeyboardEvent& aKeyEvent,
                                         bool aIsProcessedByIME,
                                         const nsAString* aInsertString) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  MOZ_ASSERT(!aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
             "eKeyPress event should not be marked as proccessed by IME");

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
           "aKeyEvent.mMessage=%s, aProcessedByIME=%s, aInsertString=%p, "
           "IsOpenedIMEMode()=%s",
           this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent),
           TrueOrFalse(aIsProcessedByIME), aInsertString,
           TrueOrFalse(IsOpenedIMEMode())));

  if (NS_WARN_IF(!aNativeKeyEvent)) {
    return;
  }

  nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);

  // This is used only while dispatching the event (which is a synchronous
  // call), so there is no need to retain and release this data.
  aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;

  aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);

  UInt32 kbType = GetKbdType();
  UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];

  // macOS handles dead key as IME.  If the key is first key press of dead
  // key, we should use KEY_NAME_INDEX_Dead for first (dead) key event.
  // So, if aIsProcessedByIME is true, it may be dead key.  Let's check
  // if current key event is a dead key's keydown event.
  bool isProcessedByIME =
      aIsProcessedByIME &&
      !TISInputSourceWrapper::CurrentInputSource().IsDeadKey(aNativeKeyEvent);

  aKeyEvent.mKeyCode =
      isProcessedByIME
          ? NS_VK_PROCESSKEY
          : ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());

  switch (nativeKeyCode) {
    case kVK_Command:
    case kVK_Shift:
    case kVK_Option:
    case kVK_Control:
      aKeyEvent.mLocation = eKeyLocationLeft;
      break;

    case kVK_RightCommand:
    case kVK_RightShift:
    case kVK_RightOption:
    case kVK_RightControl:
      aKeyEvent.mLocation = eKeyLocationRight;
      break;

    case kVK_ANSI_Keypad0:
    case kVK_ANSI_Keypad1:
    case kVK_ANSI_Keypad2:
    case kVK_ANSI_Keypad3:
    case kVK_ANSI_Keypad4:
    case kVK_ANSI_Keypad5:
    case kVK_ANSI_Keypad6:
    case kVK_ANSI_Keypad7:
    case kVK_ANSI_Keypad8:
    case kVK_ANSI_Keypad9:
    case kVK_ANSI_KeypadMultiply:
    case kVK_ANSI_KeypadPlus:
    case kVK_ANSI_KeypadMinus:
    case kVK_ANSI_KeypadDecimal:
    case kVK_ANSI_KeypadDivide:
    case kVK_ANSI_KeypadEquals:
    case kVK_ANSI_KeypadEnter:
    case kVK_JIS_KeypadComma:
    case kVK_Powerbook_KeypadEnter:
      aKeyEvent.mLocation = eKeyLocationNumpad;
      break;

    default:
      aKeyEvent.mLocation = eKeyLocationStandard;
      break;
  }

  aKeyEvent.mIsRepeat = ([aNativeKeyEvent type] == NSEventTypeKeyDown)
                            ? [aNativeKeyEvent isARepeat]
                            : false;

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::InitKeyEvent, "
           "shift=%s, ctrl=%s, alt=%s, meta=%s",
           this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
           OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));

  if (isProcessedByIME) {
    aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
  } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
    aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
    // If insertText calls this method, let's use the string.
    if (aInsertString && !aInsertString->IsEmpty() &&
        !IsControlChar((*aInsertString)[0])) {
      aKeyEvent.mKeyValue = *aInsertString;
    }
    // If meta key is pressed, the printable key layout may be switched from
    // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
    // KeyboardEvent.key value should be the switched layout's character.
    else if (aKeyEvent.IsMeta()) {
      CopyNSStringToXPCOMString([aNativeKeyEvent characters],
                                aKeyEvent.mKeyValue);
    }
    // If control key is pressed, some keys may produce printable character via
    // [aNativeKeyEvent characters].  Otherwise, translate input character of
    // the key without control key.
    else if (aKeyEvent.IsControl()) {
      NSUInteger cocoaState =
          [aNativeKeyEvent modifierFlags] & ~NSEventModifierFlagControl;
      UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
      if (IsDeadKey(nativeKeyCode, carbonState, kbType)) {
        aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
      } else {
        aKeyEvent.mKeyValue =
            TranslateToChar(nativeKeyCode, carbonState, kbType);
        if (!aKeyEvent.mKeyValue.IsEmpty() &&
            IsControlChar(aKeyEvent.mKeyValue[0])) {
          // Don't expose control character to the web.
          aKeyEvent.mKeyValue.Truncate();
        }
      }
    }
    // Otherwise, KeyboardEvent.key expose
    // [aNativeKeyEvent characters] value.  However, if IME is open and the
    // keyboard layout isn't ASCII capable, exposing the non-ASCII character
    // doesn't match with other platform's behavior.  For the compatibility
    // with other platform's Gecko, we need to set a translated character.
    else if (IsOpenedIMEMode()) {
      UInt32 state = nsCocoaUtils::ConvertToCarbonModifier(
          [aNativeKeyEvent modifierFlags]);
      aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
    } else {
      CopyNSStringToXPCOMString([aNativeKeyEvent characters],
                                aKeyEvent.mKeyValue);
      // If the key value is empty, the event may be a dead key event.
      // If TranslateToChar() returns non-zero value, that means that
      // the key may input a character with different dead key state.
      if (aKeyEvent.mKeyValue.IsEmpty()) {
        NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
        UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
        if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
          aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
        }
      }
    }

    // Last resort.  If .key value becomes empty string, we should use
    // charactersIgnoringModifiers, if it's available.
    if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
        (aKeyEvent.mKeyValue.IsEmpty() ||
         IsControlChar(aKeyEvent.mKeyValue[0]))) {
      CopyNSStringToXPCOMString([aNativeKeyEvent charactersIgnoringModifiers],
                                aKeyEvent.mKeyValue);
      // But don't expose it if it's a control character.
      if (!aKeyEvent.mKeyValue.IsEmpty() &&
          IsControlChar(aKeyEvent.mKeyValue[0])) {
        aKeyEvent.mKeyValue.Truncate();
      }
    }
  } else {
    // Compute the key for non-printable keys and some special printable keys.
    aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
  }

  aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode, kbType);
  MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);

  NS_OBJC_END_TRY_IGNORE_BLOCK
}

void TISInputSourceWrapper::WillDispatchKeyboardEvent(
    NSEvent* aNativeKeyEvent, const nsAString* aInsertString,
    uint32_t aIndexOfKeypress, WidgetKeyboardEvent& aKeyEvent) {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  // Nothing to do here if the native key event is neither NSEventTypeKeyDown
  // nor NSEventTypeKeyUp because accessing [aNativeKeyEvent characters] causes
  // throwing an exception.
  if ([aNativeKeyEvent type] != NSEventTypeKeyDown &&
      [aNativeKeyEvent type] != NSEventTypeKeyUp) {
    return;
  }

  UInt32 kbType = GetKbdType();

  if (MOZ_LOG_TEST(gKeyLog, LogLevel::Info)) {
    nsAutoString chars;
    CopyNSStringToXPCOMString([aNativeKeyEvent characters], chars);
    NS_ConvertUTF16toUTF8 utf8Chars(chars);
    char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
    MOZ_LOG(
        gKeyLog, LogLevel::Info,
        ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
         "aNativeKeyEvent=%p, aInsertString=%p (\"%s\"), "
         "aIndexOfKeypress=%u, [aNativeKeyEvent characters]=\"%s\", "
         "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
         "IsOpenedIMEMode()=%s",
         this, aNativeKeyEvent, aInsertString,
         aInsertString ? GetCharacters(*aInsertString) : "", aIndexOfKeypress,
         GetCharacters([aNativeKeyEvent characters]),
         GetGeckoKeyEventType(aKeyEvent), aKeyEvent.mCharCode,
         uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
         static_cast<unsigned int>(kbType), TrueOrFalse(IsOpenedIMEMode())));
  }

  nsAutoString insertStringForCharCode;
  ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
                                 insertStringForCharCode);

  // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
  // is pressed, its value should indicate an ASCII character for backward
  // compatibility rather than inputting character without the modifiers.
  // Therefore, we need to modify mCharCode value here.
  uint32_t charCode = 0;
  if (aIndexOfKeypress < insertStringForCharCode.Length()) {
    charCode = insertStringForCharCode[aIndexOfKeypress];
  }
  aKeyEvent.SetCharCode(charCode);

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
           "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
           this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));

  // If aInsertString is not nullptr (it means InsertText() is called)
  // and it acutally inputs a character, we don't need to append alternative
  // charCode values since such keyboard event shouldn't be handled as
  // a shortcut key.
  if (aInsertString && charCode) {
    return;
  }

  TISInputSourceWrapper USLayout("com.apple.keylayout.US");
  bool isRomanKeyboardLayout = IsASCIICapable();

  UInt32 key = [aNativeKeyEvent keyCode];

  // Caps lock and num lock modifier state:
  UInt32 lockState = 0;
  if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagCapsLock) {
    lockState |= alphaLock;
  }
  if ([aNativeKeyEvent modifierFlags] & NSEventModifierFlagNumericPad) {
    lockState |= kEventKeyModifierNumLockMask;
  }

  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
           "isRomanKeyboardLayout=%s, kbType=0x%X, key=0x%X",
           this, TrueOrFalse(isRomanKeyboardLayout),
           static_cast<unsigned int>(kbType), static_cast<unsigned int>(key)));

  nsString str;

  // normal chars
  uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
  UInt32 shiftLockMod = shiftKey | lockState;
  uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);

  // characters generated with Cmd key
  // XXX we should remove CapsLock state, which changes characters from
  //     Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
  //     is pressed.
  UInt32 numState = (lockState & ~alphaLock);  // only num lock state
  uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
  UInt32 shiftNumMod = numState | shiftKey;
  uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
  uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
  UInt32 cmdNumMod = cmdKey | numState;
  uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
  UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
  uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);

  // Is the keyboard layout changed by Cmd key?
  // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
  bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
  // Is the keyboard layout for Latin, but Cmd key switches the layout?
  // I.e., Dvorak-QWERTY
  bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;

  // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
  // we should append unshiftedChar and shiftedChar for handling the
  // normal characters.  These are the characters that the user is most
  // likely to associate with this key.
  if ((unshiftedChar || shiftedChar) &&
      (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
    AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
    aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
  }
  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
           "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
           "unshiftedChar=U+%X, shiftedChar=U+%X",
           this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
           unshiftedChar, shiftedChar));

  // Most keyboard layouts provide the same characters in the NSEvents
  // with Command+Shift as with Command.  However, with Command+Shift we
  // want the character on the second level.  e.g. With a US QWERTY
  // layout, we want "?" when the "/","?" key is pressed with
  // Command+Shift.

  // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
  // even though Cmd+SS is 'SS' and Shift+'SS' is '?'.  This '/' seems
  // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
  // event on a US keyboard.  The user thinks they are typing Cmd+"?", so
  // we'll prefer the "?" character, replacing mCharCode with shiftedChar
  // when Shift is pressed.  However, in case there is a layout where the
  // character unique to Cmd+Shift is the character that the user expects,
  // we'll send it as an alternative char.
  bool hasCmdShiftOnlyChar =
      cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
  uint32_t originalCmdedShiftChar = cmdedShiftChar;

  // If we can make a good guess at the characters that the user would
  // expect this key combination to produce (with and without Shift) then
  // use those characters.  This also corrects for CapsLock, which was
  // ignored above.
  if (!isCmdSwitchLayout) {
    // The characters produced with Command seem similar to those without
    // Command.
    if (unshiftedChar) {
      cmdedChar = unshiftedChar;
    }
    if (shiftedChar) {
      cmdedShiftChar = shiftedChar;
    }
  } else if (uncmdedUSChar == cmdedChar) {
    // It looks like characters from a US layout are provided when Command
    // is down.
    uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
    if (ch) {
      cmdedChar = ch;
    }
    ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
    if (ch) {
      cmdedShiftChar = ch;
    }
  }

  // If the current keyboard layout is switched by the Cmd key,
  // we should append cmdedChar and shiftedCmdChar that are
  // Latin char for the key.
  // If the keyboard layout is Dvorak-QWERTY, we should append them only when
  // command key is pressed because when command key isn't pressed, uncmded
  // chars have been appended already.
  if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
      (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
    AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
    aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
  }
  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
           "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
           "cmdedChar=U+%X, cmdedShiftChar=U+%X",
           this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
           TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
  // Special case for 'SS' key of German layout. See the comment of
  // hasCmdShiftOnlyChar definition for the detail.
  if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
    AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
    aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
  }
  MOZ_LOG(gKeyLog, LogLevel::Info,
          ("%p   TISInputSourceWrapper::WillDispatchKeyboardEvent, "
           "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
           this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));

  NS_OBJC_END_TRY_IGNORE_BLOCK
}

uint32_t TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
                                                    UInt32 aKbType,
                                                    bool aCmdIsPressed) {
  MOZ_LOG(
      gKeyLog, LogLevel::Info,
      ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
       "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
       "IsASCIICapable()=%s",
       this, static_cast<unsigned int>(aNativeKeyCode),
       static_cast<unsigned int>(aKbType), TrueOrFalse(aCmdIsPressed),
       TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));

  switch (aNativeKeyCode) {
    case kVK_Space:
      return NS_VK_SPACE;
    case kVK_Escape:
      return NS_VK_ESCAPE;

    // modifiers
    case kVK_RightCommand:
    case kVK_Command:
      return NS_VK_META;
    case kVK_RightShift:
    case kVK_Shift:
      return NS_VK_SHIFT;
    case kVK_CapsLock:
      return NS_VK_CAPS_LOCK;
    case kVK_RightControl:
    case kVK_Control:
      return NS_VK_CONTROL;
    case kVK_RightOption:
    case kVK_Option:
      return NS_VK_ALT;

    case kVK_ANSI_KeypadClear:
      return NS_VK_CLEAR;

    // function keys
    case kVK_F1:
      return NS_VK_F1;
    case kVK_F2:
      return NS_VK_F2;
    case kVK_F3:
      return NS_VK_F3;
    case kVK_F4:
      return NS_VK_F4;
    case kVK_F5:
      return NS_VK_F5;
    case kVK_F6:
      return NS_VK_F6;
    case kVK_F7:
      return NS_VK_F7;
    case kVK_F8:
      return NS_VK_F8;
    case kVK_F9:
      return NS_VK_F9;
    case kVK_F10:
      return NS_VK_F10;
    case kVK_F11:
      return NS_VK_F11;
    case kVK_F12:
      return NS_VK_F12;
    // case kVK_F13:               return NS_VK_F13;  // clash with the 3 below
    // case kVK_F14:               return NS_VK_F14;
    // case kVK_F15:               return NS_VK_F15;
    case kVK_F16:
      return NS_VK_F16;
    case kVK_F17:
      return NS_VK_F17;
    case kVK_F18:
      return NS_VK_F18;
    case kVK_F19:
      return NS_VK_F19;

    case kVK_PC_Pause:
      return NS_VK_PAUSE;
    case kVK_PC_ScrollLock:
      return NS_VK_SCROLL_LOCK;
    case kVK_PC_PrintScreen:
      return NS_VK_PRINTSCREEN;

    // keypad
    case kVK_ANSI_Keypad0:
      return NS_VK_NUMPAD0;
    case kVK_ANSI_Keypad1:
      return NS_VK_NUMPAD1;
    case kVK_ANSI_Keypad2:
      return NS_VK_NUMPAD2;
    case kVK_ANSI_Keypad3:
      return NS_VK_NUMPAD3;
    case kVK_ANSI_Keypad4:
      return NS_VK_NUMPAD4;
    case kVK_ANSI_Keypad5:
      return NS_VK_NUMPAD5;
    case kVK_ANSI_Keypad6:
      return NS_VK_NUMPAD6;
    case kVK_ANSI_Keypad7:
      return NS_VK_NUMPAD7;
    case kVK_ANSI_Keypad8:
      return NS_VK_NUMPAD8;
    case kVK_ANSI_Keypad9:
      return NS_VK_NUMPAD9;

    case kVK_ANSI_KeypadMultiply:
      return NS_VK_MULTIPLY;
    case kVK_ANSI_KeypadPlus:
      return NS_VK_ADD;
    case kVK_ANSI_KeypadMinus:
      return NS_VK_SUBTRACT;
    case kVK_ANSI_KeypadDecimal:
      return NS_VK_DECIMAL;
    case kVK_ANSI_KeypadDivide:
      return NS_VK_DIVIDE;

    case kVK_JIS_KeypadComma:
      return NS_VK_SEPARATOR;

    // IME keys
    case kVK_JIS_Eisu:
      return NS_VK_EISU;
    case kVK_JIS_Kana:
      return NS_VK_KANA;

    // these may clash with forward delete and help
    case kVK_PC_Insert:
      return NS_VK_INSERT;
    case kVK_PC_Delete:
      return NS_VK_DELETE;

    case kVK_PC_Backspace:
      return NS_VK_BACK;
    case kVK_Tab:
      return NS_VK_TAB;

    case kVK_Home:
      return NS_VK_HOME;
    case kVK_End:
      return NS_VK_END;

    case kVK_PageUp:
      return NS_VK_PAGE_UP;
    case kVK_PageDown:
      return NS_VK_PAGE_DOWN;

    case kVK_LeftArrow:
      return NS_VK_LEFT;
    case kVK_RightArrow:
      return NS_VK_RIGHT;
    case kVK_UpArrow:
      return NS_VK_UP;
    case kVK_DownArrow:
      return NS_VK_DOWN;

    case kVK_PC_ContextMenu:
      return NS_VK_CONTEXT_MENU;

    case kVK_ANSI_1:
      return NS_VK_1;
    case kVK_ANSI_2:
      return NS_VK_2;
    case kVK_ANSI_3:
      return NS_VK_3;
    case kVK_ANSI_4:
      return NS_VK_4;
    case kVK_ANSI_5:
      return NS_VK_5;
    case kVK_ANSI_6:
      return NS_VK_6;
    case kVK_ANSI_7:
      return NS_VK_7;
    case kVK_ANSI_8:
      return NS_VK_8;
    case kVK_ANSI_9:
      return NS_VK_9;
    case kVK_ANSI_0:
      return NS_VK_0;

    case kVK_ANSI_KeypadEnter:
    case kVK_Return:
    case kVK_Powerbook_KeypadEnter:
      return NS_VK_RETURN;
  }

  // If Cmd key is pressed, that causes switching keyboard layout temporarily.
  // E.g., Dvorak-QWERTY.  Therefore, if Cmd key is pressed, we should honor it.
  UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;

  uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);

  // Special case for Mac.  Mac inputs Yen sign (U+00A5) directly instead of
  // Back slash (U+005C).  We should return NS_VK_BACK_SLASH for compatibility
  // with other platforms.
  // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
  if (charCode == 0x00A5) {
    return NS_VK_BACK_SLASH;
  }

  uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
  if (keyCode) {
    return keyCode;
  }

  // If the unshifed char isn't an ASCII character, use shifted char.
  charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
  keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
  if (keyCode) {
    return keyCode;
  }

  if (!IsASCIICapable()) {
    // Retry with ASCII capable keyboard layout.
    TISInputSourceWrapper currentKeyboardLayout;
    currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
    NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
    keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
                                                        aCmdIsPressed);
    // We've returned 0 for long time if keyCode isn't for an alphabet keys or
    // a numeric key even in alternative ASCII capable keyboard layout because
    // we decided that we should avoid setting same keyCode value to 2 or
    // more keys since active keyboard layout may have a key to input the
    // punctuation with different key.  However, setting keyCode to 0 makes
    // some web applications which are aware of neither KeyboardEvent.key nor
    // KeyboardEvent.code not work with Firefox when user selects non-ASCII
    // capable keyboard layout such as Russian and Thai.  So, if alternative
    // ASCII capable keyboard layout has keyCode value for the key, we should
    // use it.  In other words, this behavior does that non-ASCII capable
    // keyboard layout overrides some keys' keyCode value only if the key
    // produces ASCII character by itself or with Shift key.
    if (keyCode) {
      return keyCode;
    }
  }

  // Otherwise, let's decide keyCode value from the native virtual keycode
  // value on major keyboard layout.
  CodeNameIndex code = ComputeGeckoCodeNameIndex(aNativeKeyCode, aKbType);
  return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
}

// static
KeyNameIndex TISInputSourceWrapper::ComputeGeckoKeyNameIndex(
    UInt32 aNativeKeyCode) {
  // NOTE:
  //   When unsupported keys like Convert, Nonconvert of Japanese keyboard is
  //   pressed:
  //     on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
  //     on 10.7.x, Nothing happens.
  //     on 10.8.x, Nothing happens.
  //     on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
  switch (aNativeKeyCode) {
#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
  case aNativeKey:                                                     \
    return aKeyNameIndex;

#include "NativeKeyToDOMKeyName.h"

#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX

    default:
      return KEY_NAME_INDEX_Unidentified;
  }
}

// static
CodeNameIndex TISInputSourceWrapper::ComputeGeckoCodeNameIndex(
    UInt32 aNativeKeyCode, UInt32 aKbType) {
  // macOS swaps native key code between Backquote key and IntlBackslash key
  // only when the keyboard type is ISO.  Let's treat the key code after
  // swapping them here because Chromium does so only when computing .code
  // value.
  if (::KBGetLayoutType(aKbType) == kKeyboardISO) {
    if (aNativeKeyCode == kVK_ISO_Section) {
      aNativeKeyCode = kVK_ANSI_Grave;
    } else if (aNativeKeyCode == kVK_ANSI_Grave) {
      aNativeKeyCode = kVK_ISO_Section;
    }
  }

  switch (aNativeKeyCode) {
#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
  case aNativeKey:                                                       \
    return aCodeNameIndex;

#include "NativeKeyToDOMCodeName.h"

#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX

    default:
      return CODE_NAME_INDEX_UNKNOWN;
  }
}

#pragma mark -

/******************************************************************************
 *
 *  TextInputHandler implementation (static methods)
 *
 ******************************************************************************/

NSUInteger TextInputHandler::sLastModifierState = 0;

// static
CFArrayRef TextInputHandler::CreateAllKeyboardLayoutList() {
  const void* keys[] = {kTISPropertyInputSourceType};
  const void* values[] = {kTISTypeKeyboardLayout};
  CFDictionaryRef filter =
      ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
  NS_ASSERTION(filter, "failed to create the filter");
  CFArrayRef list = ::TISCreateInputSourceList(filter, true);
  ::CFRelease(filter);
  return list;
}

// static
void TextInputHandler::DebugPrintAllKeyboardLayouts() {
  if (MOZ_LOG_TEST(gKeyLog, LogLevel::Info)) {
    CFArrayRef list = CreateAllKeyboardLayoutList();
    MOZ_LOG(gKeyLog, LogLevel::Info, ("Keyboard layout configuration:"));
    CFIndex idx = ::CFArrayGetCount(list);
    TISInputSourceWrapper tis;
    for (CFIndex i = 0; i < idx; ++i) {
      TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
          const_cast<void*>(::CFArrayGetValueAtIndex(list, i)));
      tis.InitByTISInputSourceRef(inputSource);
      nsAutoString name, isid;
      tis.GetLocalizedName(name);
      tis.GetInputSourceID(isid);
      MOZ_LOG(gKeyLog, LogLevel::Info,
              ("  %s\t<%s>%s%s\n", NS_ConvertUTF16toUTF8(name).get(),
               NS_ConvertUTF16toUTF8(isid).get(),
               tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
               tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout()
                   ? ""
                   : "\t(uchr is NOT AVAILABLE)"));
    }
    ::CFRelease(list);
  }
}

#pragma mark -

/******************************************************************************
 *
 *  TextInputHandler implementation
 *
 ******************************************************************************/

TextInputHandler::TextInputHandler(nsChildView* aWidget,
                                   NSView<mozView>* aNativeView)
    : IMEInputHandler(aWidget, aNativeView) {
  EnsureToLogAllKeyboardLayoutsAndIMEs();
  [mView installTextInputHandler:this];
}

TextInputHandler::~TextInputHandler() { [mView uninstallTextInputHandler]; }

bool TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent,
                                          uint32_t aUniqueId) {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  if (Destroyed()) {
    MOZ_LOG_KEY_OR_IME(LogLevel::Info,
                       ("%p TextInputHandler::HandleKeyDownEvent, "
                        "widget has been already destroyed",
                        this));
    return false;
  }

  // Insert empty line to the log for easier to read.
  MOZ_LOG_KEY_OR_IME(LogLevel::Info, (""));
  MOZ_LOG_KEY_OR_IME(
      LogLevel::Info,
      ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
       "type=%s, keyCode=%u (0x%X), modifierFlags=0x%lX, characters=\"%s\", "
       "charactersIgnoringModifiers=\"%s\"",
       this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
       [aNativeEvent keyCode], [aNativeEvent keyCode],
       static_cast<unsigned long>([aNativeEvent modifierFlags]),
       GetCharacters([aNativeEvent characters]),
       GetCharacters([aNativeEvent charactersIgnoringModifiers])));

  // We should hide the mouse cursor until the next mousemove, unless we aren't
  // dealing with editable content or the Command key was pressed. We hide the
  // mouse cursor even if the key event will be handled by IME (i.e., even
  // without dispatching eKeyPress events).
  if (IsEditableContent() &&
      !([aNativeEvent modifierFlags] & NSEventModifierFlagCommand)) {
    [NSCursor setHiddenUntilMouseMoves:YES];
  }

  RefPtr<nsChildView> widget(mWidget);

  KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent, aUniqueId);
  AutoKeyEventStateCleaner remover(this);

  RefPtr<TextInputHandler> kungFuDeathGrip(this);

  // When we're already in a composition, we need always to mark the eKeyDown
  // event as "processed by IME".  So, let's dispatch eKeyDown event here in
  // such case.
  if (IsIMEComposing() && !MaybeDispatchCurrentKeydownEvent(true)) {
    MOZ_LOG_KEY_OR_IME(LogLevel::Info,
                       ("%p   TextInputHandler::HandleKeyDownEvent, eKeyDown "
                        "caused focus move or "
                        "something and canceling the composition",
                        this));
    return false;
  }

  // macOS supports shortcut keys using the `Fn` key. Check first if this key
  // event is such a shortcut key.
  if (nsCocoaUtils::ModifiersForEvent(aNativeEvent) & MODIFIER_FN) {
    if (mWidget->SendEventToNativeMenuSystem(aNativeEvent)) {
      return true;
    }
  }

  // Let Cocoa interpret the key events, caching IsIMEComposing first.
  bool wasComposing = IsIMEComposing();
  bool interpretKeyEventsCalled = false;
  // Don't call interpretKeyEvents when a plugin has focus.  If we call it,
  // for example, a character is inputted twice during a composition in e10s
  // mode.
  if (IsIMEEnabled() || IsASCIICapableOnly()) {
    MOZ_LOG_KEY_OR_IME(LogLevel::Info,
                       ("%p   TextInputHandler::HandleKeyDownEvent, calling "
                        "interpretKeyEvents",
                        this));
    [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
    interpretKeyEventsCalled = true;
    MOZ_LOG_KEY_OR_IME(
        LogLevel::Info,
        ("%p   TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
         this));
  }

  if (Destroyed()) {
    MOZ_LOG_KEY_OR_IME(
        LogLevel::Info,
        ("%p   TextInputHandler::HandleKeyDownEvent, widget was destroyed",
         this));
    return currentKeyEvent->IsDefaultPrevented();
  }

  MOZ_LOG_KEY_OR_IME(
      LogLevel::Info,
      ("%p   TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
       "IsIMEComposing()=%s",
       this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));

  if (currentKeyEvent->CanDispatchKeyDownEvent()) {
    // Dispatch eKeyDown event if nobody has dispatched it yet.
    // NOTE: Although reaching here means that the native keydown event may
    //       not be handled by IME.  However, we cannot know if it is.
    //       For example, Japanese IME of Apple shows candidate window for
    //       typing window.  They, you can switch the sort order with Tab key.
    //       However, when you choose "Symbol" of the sort order, there may
    //       be no candiate words.  In this case, IME handles the Tab key
    //       actually, but we cannot know it because composition string is
    //       not updated.  So, let's mark eKeyDown event as "processed by IME"
    //       when there is composition string.  This is same as Chrome.
    MOZ_LOG_KEY_OR_IME(LogLevel::Info,
                       ("%p   TextInputHandler::HandleKeyDownEvent, trying to "
                        "dispatch eKeyDown "
                        "event since it's not yet dispatched",
                        this));
    if (!MaybeDispatchCurrentKeydownEvent(IsIMEComposing())) {
      return true;  // treat the eKeydDown event as consumed.
    }
    MOZ_LOG_KEY_OR_IME(
        LogLevel::Info,
--> --------------------

--> maximum size reached

--> --------------------

[ Verzeichnis aufwärts0.65unsichere Verbindung  Übersetzung europäischer Sprachen durch Browser  ]