Source code

Revision control

Other Tools

/* -*- 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 "mozilla/Logging.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/TextEvents.h"
#include "nsAlgorithm.h"
#include "nsExceptionHandler.h"
#include "nsGkAtoms.h"
#include "nsIUserIdleServiceInternal.h"
#include "nsIWindowsRegKey.h"
#include "nsMemory.h"
#include "nsPrintfCString.h"
#include "nsQuickSort.h"
#include "nsReadableUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsToolkit.h"
#include "nsUnicharUtils.h"
#include "nsWindowDbg.h"
#include "KeyboardLayout.h"
#include "WidgetUtils.h"
#include "WinUtils.h"
#include "npapi.h"
#include <windows.h>
#include <winuser.h>
#include <algorithm>
#ifndef WINABLEAPI
# include <winable.h>
#endif
// In WinUser.h, MAPVK_VK_TO_VSC_EX is defined only when WINVER >= 0x0600
#ifndef MAPVK_VK_TO_VSC_EX
# define MAPVK_VK_TO_VSC_EX (4)
#endif
namespace mozilla {
namespace widget {
static const char* const kVirtualKeyName[] = {
"NULL",
"VK_LBUTTON",
"VK_RBUTTON",
"VK_CANCEL",
"VK_MBUTTON",
"VK_XBUTTON1",
"VK_XBUTTON2",
"0x07",
"VK_BACK",
"VK_TAB",
"0x0A",
"0x0B",
"VK_CLEAR",
"VK_RETURN",
"0x0E",
"0x0F",
"VK_SHIFT",
"VK_CONTROL",
"VK_MENU",
"VK_PAUSE",
"VK_CAPITAL",
"VK_KANA, VK_HANGUL",
"0x16",
"VK_JUNJA",
"VK_FINAL",
"VK_HANJA, VK_KANJI",
"0x1A",
"VK_ESCAPE",
"VK_CONVERT",
"VK_NONCONVERT",
"VK_ACCEPT",
"VK_MODECHANGE",
"VK_SPACE",
"VK_PRIOR",
"VK_NEXT",
"VK_END",
"VK_HOME",
"VK_LEFT",
"VK_UP",
"VK_RIGHT",
"VK_DOWN",
"VK_SELECT",
"VK_PRINT",
"VK_EXECUTE",
"VK_SNAPSHOT",
"VK_INSERT",
"VK_DELETE",
"VK_HELP",
"VK_0",
"VK_1",
"VK_2",
"VK_3",
"VK_4",
"VK_5",
"VK_6",
"VK_7",
"VK_8",
"VK_9",
"0x3A",
"0x3B",
"0x3C",
"0x3D",
"0x3E",
"0x3F",
"0x40",
"VK_A",
"VK_B",
"VK_C",
"VK_D",
"VK_E",
"VK_F",
"VK_G",
"VK_H",
"VK_I",
"VK_J",
"VK_K",
"VK_L",
"VK_M",
"VK_N",
"VK_O",
"VK_P",
"VK_Q",
"VK_R",
"VK_S",
"VK_T",
"VK_U",
"VK_V",
"VK_W",
"VK_X",
"VK_Y",
"VK_Z",
"VK_LWIN",
"VK_RWIN",
"VK_APPS",
"0x5E",
"VK_SLEEP",
"VK_NUMPAD0",
"VK_NUMPAD1",
"VK_NUMPAD2",
"VK_NUMPAD3",
"VK_NUMPAD4",
"VK_NUMPAD5",
"VK_NUMPAD6",
"VK_NUMPAD7",
"VK_NUMPAD8",
"VK_NUMPAD9",
"VK_MULTIPLY",
"VK_ADD",
"VK_SEPARATOR",
"VK_SUBTRACT",
"VK_DECIMAL",
"VK_DIVIDE",
"VK_F1",
"VK_F2",
"VK_F3",
"VK_F4",
"VK_F5",
"VK_F6",
"VK_F7",
"VK_F8",
"VK_F9",
"VK_F10",
"VK_F11",
"VK_F12",
"VK_F13",
"VK_F14",
"VK_F15",
"VK_F16",
"VK_F17",
"VK_F18",
"VK_F19",
"VK_F20",
"VK_F21",
"VK_F22",
"VK_F23",
"VK_F24",
"0x88",
"0x89",
"0x8A",
"0x8B",
"0x8C",
"0x8D",
"0x8E",
"0x8F",
"VK_NUMLOCK",
"VK_SCROLL",
"VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO",
"VK_OEM_FJ_MASSHOU",
"VK_OEM_FJ_TOUROKU",
"VK_OEM_FJ_LOYA",
"VK_OEM_FJ_ROYA",
"0x97",
"0x98",
"0x99",
"0x9A",
"0x9B",
"0x9C",
"0x9D",
"0x9E",
"0x9F",
"VK_LSHIFT",
"VK_RSHIFT",
"VK_LCONTROL",
"VK_RCONTROL",
"VK_LMENU",
"VK_RMENU",
"VK_BROWSER_BACK",
"VK_BROWSER_FORWARD",
"VK_BROWSER_REFRESH",
"VK_BROWSER_STOP",
"VK_BROWSER_SEARCH",
"VK_BROWSER_FAVORITES",
"VK_BROWSER_HOME",
"VK_VOLUME_MUTE",
"VK_VOLUME_DOWN",
"VK_VOLUME_UP",
"VK_MEDIA_NEXT_TRACK",
"VK_MEDIA_PREV_TRACK",
"VK_MEDIA_STOP",
"VK_MEDIA_PLAY_PAUSE",
"VK_LAUNCH_MAIL",
"VK_LAUNCH_MEDIA_SELECT",
"VK_LAUNCH_APP1",
"VK_LAUNCH_APP2",
"0xB8",
"0xB9",
"VK_OEM_1",
"VK_OEM_PLUS",
"VK_OEM_COMMA",
"VK_OEM_MINUS",
"VK_OEM_PERIOD",
"VK_OEM_2",
"VK_OEM_3",
"VK_ABNT_C1",
"VK_ABNT_C2",
"0xC3",
"0xC4",
"0xC5",
"0xC6",
"0xC7",
"0xC8",
"0xC9",
"0xCA",
"0xCB",
"0xCC",
"0xCD",
"0xCE",
"0xCF",
"0xD0",
"0xD1",
"0xD2",
"0xD3",
"0xD4",
"0xD5",
"0xD6",
"0xD7",
"0xD8",
"0xD9",
"0xDA",
"VK_OEM_4",
"VK_OEM_5",
"VK_OEM_6",
"VK_OEM_7",
"VK_OEM_8",
"0xE0",
"VK_OEM_AX",
"VK_OEM_102",
"VK_ICO_HELP",
"VK_ICO_00",
"VK_PROCESSKEY",
"VK_ICO_CLEAR",
"VK_PACKET",
"0xE8",
"VK_OEM_RESET",
"VK_OEM_JUMP",
"VK_OEM_PA1",
"VK_OEM_PA2",
"VK_OEM_PA3",
"VK_OEM_WSCTRL",
"VK_OEM_CUSEL",
"VK_OEM_ATTN",
"VK_OEM_FINISH",
"VK_OEM_COPY",
"VK_OEM_AUTO",
"VK_OEM_ENLW",
"VK_OEM_BACKTAB",
"VK_ATTN",
"VK_CRSEL",
"VK_EXSEL",
"VK_EREOF",
"VK_PLAY",
"VK_ZOOM",
"VK_NONAME",
"VK_PA1",
"VK_OEM_CLEAR",
"0xFF"};
static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100,
"The virtual key name must be defined just 256 keys");
static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
static const nsCString GetCharacterCodeName(WPARAM aCharCode) {
switch (aCharCode) {
case 0x0000:
return "NULL (0x0000)"_ns;
case 0x0008:
return "BACKSPACE (0x0008)"_ns;
case 0x0009:
return "CHARACTER TABULATION (0x0009)"_ns;
case 0x000A:
return "LINE FEED (0x000A)"_ns;
case 0x000B:
return "LINE TABULATION (0x000B)"_ns;
case 0x000C:
return "FORM FEED (0x000C)"_ns;
case 0x000D:
return "CARRIAGE RETURN (0x000D)"_ns;
case 0x0018:
return "CANCEL (0x0018)"_ns;
case 0x001B:
return "ESCAPE (0x001B)"_ns;
case 0x0020:
return "SPACE (0x0020)"_ns;
case 0x007F:
return "DELETE (0x007F)"_ns;
case 0x00A0:
return "NO-BREAK SPACE (0x00A0)"_ns;
case 0x00AD:
return "SOFT HYPHEN (0x00AD)"_ns;
case 0x2000:
return "EN QUAD (0x2000)"_ns;
case 0x2001:
return "EM QUAD (0x2001)"_ns;
case 0x2002:
return "EN SPACE (0x2002)"_ns;
case 0x2003:
return "EM SPACE (0x2003)"_ns;
case 0x2004:
return "THREE-PER-EM SPACE (0x2004)"_ns;
case 0x2005:
return "FOUR-PER-EM SPACE (0x2005)"_ns;
case 0x2006:
return "SIX-PER-EM SPACE (0x2006)"_ns;
case 0x2007:
return "FIGURE SPACE (0x2007)"_ns;
case 0x2008:
return "PUNCTUATION SPACE (0x2008)"_ns;
case 0x2009:
return "THIN SPACE (0x2009)"_ns;
case 0x200A:
return "HAIR SPACE (0x200A)"_ns;
case 0x200B:
return "ZERO WIDTH SPACE (0x200B)"_ns;
case 0x200C:
return "ZERO WIDTH NON-JOINER (0x200C)"_ns;
case 0x200D:
return "ZERO WIDTH JOINER (0x200D)"_ns;
case 0x200E:
return "LEFT-TO-RIGHT MARK (0x200E)"_ns;
case 0x200F:
return "RIGHT-TO-LEFT MARK (0x200F)"_ns;
case 0x2029:
return "PARAGRAPH SEPARATOR (0x2029)"_ns;
case 0x202A:
return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns;
case 0x202B:
return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns;
case 0x202D:
return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns;
case 0x202E:
return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns;
case 0x202F:
return "NARROW NO-BREAK SPACE (0x202F)"_ns;
case 0x205F:
return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns;
case 0x2060:
return "WORD JOINER (0x2060)"_ns;
case 0x2066:
return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns;
case 0x2067:
return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns;
case 0x3000:
return "IDEOGRAPHIC SPACE (0x3000)"_ns;
case 0xFEFF:
return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns;
default: {
if (aCharCode < ' ' || (aCharCode >= 0x80 && aCharCode < 0xA0)) {
return nsPrintfCString("control (0x%04X)", aCharCode);
}
if (NS_IS_HIGH_SURROGATE(aCharCode)) {
return nsPrintfCString("high surrogate (0x%04X)", aCharCode);
}
if (NS_IS_LOW_SURROGATE(aCharCode)) {
return nsPrintfCString("low surrogate (0x%04X)", aCharCode);
}
return IS_IN_BMP(aCharCode)
? nsPrintfCString(
"'%s' (0x%04X)",
NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
aCharCode)
: nsPrintfCString(
"'%s' (0x%08X)",
NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
aCharCode);
}
}
}
static const nsCString GetKeyLocationName(uint32_t aLocation) {
switch (aLocation) {
case eKeyLocationLeft:
return "KEY_LOCATION_LEFT"_ns;
case eKeyLocationRight:
return "KEY_LOCATION_RIGHT"_ns;
case eKeyLocationStandard:
return "KEY_LOCATION_STANDARD"_ns;
case eKeyLocationNumpad:
return "KEY_LOCATION_NUMPAD"_ns;
default:
return nsPrintfCString("Unknown (0x%04X)", aLocation);
}
}
static const nsCString GetCharacterCodeNames(const char16_t* aChars,
uint32_t aLength) {
if (!aLength) {
return ""_ns;
}
nsCString result;
result.AssignLiteral("\"");
StringJoinAppend(result, ", "_ns, Span{aChars, aLength},
[](nsACString& dest, const char16_t charValue) {
dest.Append(GetCharacterCodeName(charValue));
});
result.AppendLiteral("\"");
return result;
}
static const nsCString GetCharacterCodeNames(
const UniCharsAndModifiers& aUniCharsAndModifiers) {
if (aUniCharsAndModifiers.IsEmpty()) {
return ""_ns;
}
nsCString result;
result.AssignLiteral("\"");
StringJoinAppend(result, ", "_ns, Span{aUniCharsAndModifiers.ToString()},
[](nsACString& dest, const char16_t charValue) {
dest.Append(GetCharacterCodeName(charValue));
});
result.AppendLiteral("\"");
return result;
}
class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString {
public:
explicit GetShiftStateName(VirtualKey::ShiftState aShiftState) {
if (!aShiftState) {
AssignLiteral("none");
return;
}
if (aShiftState & VirtualKey::STATE_SHIFT) {
AssignLiteral("Shift");
aShiftState &= ~VirtualKey::STATE_SHIFT;
}
if (aShiftState & VirtualKey::STATE_CONTROL) {
MaybeAppendSeparator();
AssignLiteral("Ctrl");
aShiftState &= ~VirtualKey::STATE_CONTROL;
}
if (aShiftState & VirtualKey::STATE_ALT) {
MaybeAppendSeparator();
AssignLiteral("Alt");
aShiftState &= ~VirtualKey::STATE_ALT;
}
if (aShiftState & VirtualKey::STATE_CAPSLOCK) {
MaybeAppendSeparator();
AssignLiteral("CapsLock");
aShiftState &= ~VirtualKey::STATE_CAPSLOCK;
}
MOZ_ASSERT(!aShiftState);
}
private:
void MaybeAppendSeparator() {
if (!IsEmpty()) {
AppendLiteral(" | ");
}
}
};
static const nsCString GetMessageName(UINT aMessage) {
switch (aMessage) {
case WM_NULL:
return "WM_NULL"_ns;
case WM_KEYDOWN:
return "WM_KEYDOWN"_ns;
case WM_KEYUP:
return "WM_KEYUP"_ns;
case WM_SYSKEYDOWN:
return "WM_SYSKEYDOWN"_ns;
case WM_SYSKEYUP:
return "WM_SYSKEYUP"_ns;
case WM_CHAR:
return "WM_CHAR"_ns;
case WM_UNICHAR:
return "WM_UNICHAR"_ns;
case WM_SYSCHAR:
return "WM_SYSCHAR"_ns;
case WM_DEADCHAR:
return "WM_DEADCHAR"_ns;
case WM_SYSDEADCHAR:
return "WM_SYSDEADCHAR"_ns;
case WM_APPCOMMAND:
return "WM_APPCOMMAND"_ns;
case WM_QUIT:
return "WM_QUIT"_ns;
default:
return nsPrintfCString("Unknown Message (0x%04X)", aMessage);
}
}
static const nsCString GetVirtualKeyCodeName(WPARAM aVK) {
if (aVK >= ArrayLength(kVirtualKeyName)) {
return nsPrintfCString("Invalid (0x%08X)", aVK);
}
return nsCString(kVirtualKeyName[aVK]);
}
static const nsCString GetAppCommandName(WPARAM aCommand) {
switch (aCommand) {
case APPCOMMAND_BASS_BOOST:
return "APPCOMMAND_BASS_BOOST"_ns;
case APPCOMMAND_BASS_DOWN:
return "APPCOMMAND_BASS_DOWN"_ns;
case APPCOMMAND_BASS_UP:
return "APPCOMMAND_BASS_UP"_ns;
case APPCOMMAND_BROWSER_BACKWARD:
return "APPCOMMAND_BROWSER_BACKWARD"_ns;
case APPCOMMAND_BROWSER_FAVORITES:
return "APPCOMMAND_BROWSER_FAVORITES"_ns;
case APPCOMMAND_BROWSER_FORWARD:
return "APPCOMMAND_BROWSER_FORWARD"_ns;
case APPCOMMAND_BROWSER_HOME:
return "APPCOMMAND_BROWSER_HOME"_ns;
case APPCOMMAND_BROWSER_REFRESH:
return "APPCOMMAND_BROWSER_REFRESH"_ns;
case APPCOMMAND_BROWSER_SEARCH:
return "APPCOMMAND_BROWSER_SEARCH"_ns;
case APPCOMMAND_BROWSER_STOP:
return "APPCOMMAND_BROWSER_STOP"_ns;
case APPCOMMAND_CLOSE:
return "APPCOMMAND_CLOSE"_ns;
case APPCOMMAND_COPY:
return "APPCOMMAND_COPY"_ns;
case APPCOMMAND_CORRECTION_LIST:
return "APPCOMMAND_CORRECTION_LIST"_ns;
case APPCOMMAND_CUT:
return "APPCOMMAND_CUT"_ns;
case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
return "APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE"_ns;
case APPCOMMAND_FIND:
return "APPCOMMAND_FIND"_ns;
case APPCOMMAND_FORWARD_MAIL:
return "APPCOMMAND_FORWARD_MAIL"_ns;
case APPCOMMAND_HELP:
return "APPCOMMAND_HELP"_ns;
case APPCOMMAND_LAUNCH_APP1:
return "APPCOMMAND_LAUNCH_APP1"_ns;
case APPCOMMAND_LAUNCH_APP2:
return "APPCOMMAND_LAUNCH_APP2"_ns;
case APPCOMMAND_LAUNCH_MAIL:
return "APPCOMMAND_LAUNCH_MAIL"_ns;
case APPCOMMAND_LAUNCH_MEDIA_SELECT:
return "APPCOMMAND_LAUNCH_MEDIA_SELECT"_ns;
case APPCOMMAND_MEDIA_CHANNEL_DOWN:
return "APPCOMMAND_MEDIA_CHANNEL_DOWN"_ns;
case APPCOMMAND_MEDIA_CHANNEL_UP:
return "APPCOMMAND_MEDIA_CHANNEL_UP"_ns;
case APPCOMMAND_MEDIA_FAST_FORWARD:
return "APPCOMMAND_MEDIA_FAST_FORWARD"_ns;
case APPCOMMAND_MEDIA_NEXTTRACK:
return "APPCOMMAND_MEDIA_NEXTTRACK"_ns;
case APPCOMMAND_MEDIA_PAUSE:
return "APPCOMMAND_MEDIA_PAUSE"_ns;
case APPCOMMAND_MEDIA_PLAY:
return "APPCOMMAND_MEDIA_PLAY"_ns;
case APPCOMMAND_MEDIA_PLAY_PAUSE:
return "APPCOMMAND_MEDIA_PLAY_PAUSE"_ns;
case APPCOMMAND_MEDIA_PREVIOUSTRACK:
return "APPCOMMAND_MEDIA_PREVIOUSTRACK"_ns;
case APPCOMMAND_MEDIA_RECORD:
return "APPCOMMAND_MEDIA_RECORD"_ns;
case APPCOMMAND_MEDIA_REWIND:
return "APPCOMMAND_MEDIA_REWIND"_ns;
case APPCOMMAND_MEDIA_STOP:
return "APPCOMMAND_MEDIA_STOP"_ns;
case APPCOMMAND_MIC_ON_OFF_TOGGLE:
return "APPCOMMAND_MIC_ON_OFF_TOGGLE"_ns;
case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
return "APPCOMMAND_MICROPHONE_VOLUME_DOWN"_ns;
case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
return "APPCOMMAND_MICROPHONE_VOLUME_MUTE"_ns;
case APPCOMMAND_MICROPHONE_VOLUME_UP:
return "APPCOMMAND_MICROPHONE_VOLUME_UP"_ns;
case APPCOMMAND_NEW:
return "APPCOMMAND_NEW"_ns;
case APPCOMMAND_OPEN:
return "APPCOMMAND_OPEN"_ns;
case APPCOMMAND_PASTE:
return "APPCOMMAND_PASTE"_ns;
case APPCOMMAND_PRINT:
return "APPCOMMAND_PRINT"_ns;
case APPCOMMAND_REDO:
return "APPCOMMAND_REDO"_ns;
case APPCOMMAND_REPLY_TO_MAIL:
return "APPCOMMAND_REPLY_TO_MAIL"_ns;
case APPCOMMAND_SAVE:
return "APPCOMMAND_SAVE"_ns;
case APPCOMMAND_SEND_MAIL:
return "APPCOMMAND_SEND_MAIL"_ns;
case APPCOMMAND_SPELL_CHECK:
return "APPCOMMAND_SPELL_CHECK"_ns;
case APPCOMMAND_TREBLE_DOWN:
return "APPCOMMAND_TREBLE_DOWN"_ns;
case APPCOMMAND_TREBLE_UP:
return "APPCOMMAND_TREBLE_UP"_ns;
case APPCOMMAND_UNDO:
return "APPCOMMAND_UNDO"_ns;
case APPCOMMAND_VOLUME_DOWN:
return "APPCOMMAND_VOLUME_DOWN"_ns;
case APPCOMMAND_VOLUME_MUTE:
return "APPCOMMAND_VOLUME_MUTE"_ns;
case APPCOMMAND_VOLUME_UP:
return "APPCOMMAND_VOLUME_UP"_ns;
default:
return nsPrintfCString("Unknown app command (0x%08X)", aCommand);
}
}
static const nsCString GetAppCommandDeviceName(LPARAM aDevice) {
switch (aDevice) {
case FAPPCOMMAND_KEY:
return "FAPPCOMMAND_KEY"_ns;
case FAPPCOMMAND_MOUSE:
return "FAPPCOMMAND_MOUSE"_ns;
case FAPPCOMMAND_OEM:
return "FAPPCOMMAND_OEM"_ns;
default:
return nsPrintfCString("Unknown app command device (0x%04X)", aDevice);
}
};
class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString {
public:
explicit GetAppCommandKeysName(WPARAM aKeys) {
if (aKeys & MK_CONTROL) {
AppendLiteral("MK_CONTROL");
aKeys &= ~MK_CONTROL;
}
if (aKeys & MK_LBUTTON) {
MaybeAppendSeparator();
AppendLiteral("MK_LBUTTON");
aKeys &= ~MK_LBUTTON;
}
if (aKeys & MK_MBUTTON) {
MaybeAppendSeparator();
AppendLiteral("MK_MBUTTON");
aKeys &= ~MK_MBUTTON;
}
if (aKeys & MK_RBUTTON) {
MaybeAppendSeparator();
AppendLiteral("MK_RBUTTON");
aKeys &= ~MK_RBUTTON;
}
if (aKeys & MK_SHIFT) {
MaybeAppendSeparator();
AppendLiteral("MK_SHIFT");
aKeys &= ~MK_SHIFT;
}
if (aKeys & MK_XBUTTON1) {
MaybeAppendSeparator();
AppendLiteral("MK_XBUTTON1");
aKeys &= ~MK_XBUTTON1;
}
if (aKeys & MK_XBUTTON2) {
MaybeAppendSeparator();
AppendLiteral("MK_XBUTTON2");
aKeys &= ~MK_XBUTTON2;
}
if (aKeys) {
MaybeAppendSeparator();
AppendPrintf("Unknown Flags (0x%04X)", aKeys);
}
if (IsEmpty()) {
AssignLiteral("none (0x0000)");
}
}
private:
void MaybeAppendSeparator() {
if (!IsEmpty()) {
AppendLiteral(" | ");
}
}
};
static const nsCString ToString(const MSG& aMSG) {
nsCString result;
result.AssignLiteral("{ message=");
result.Append(GetMessageName(aMSG.message).get());
result.AppendLiteral(", ");
switch (aMSG.message) {
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
result.AppendPrintf(
"virtual keycode=%s, repeat count=%d, "
"scancode=0x%02X, extended key=%s, "
"context code=%s, previous key state=%s, "
"transition state=%s",
GetVirtualKeyCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
WinUtils::GetScanCode(aMSG.lParam),
GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
GetBoolName((aMSG.lParam & (1 << 29)) != 0),
GetBoolName((aMSG.lParam & (1 << 30)) != 0),
GetBoolName((aMSG.lParam & (1 << 31)) != 0));
break;
case WM_CHAR:
case WM_DEADCHAR:
case WM_SYSCHAR:
case WM_SYSDEADCHAR:
result.AppendPrintf(
"character code=%s, repeat count=%d, "
"scancode=0x%02X, extended key=%s, "
"context code=%s, previous key state=%s, "
"transition state=%s",
GetCharacterCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
WinUtils::GetScanCode(aMSG.lParam),
GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
GetBoolName((aMSG.lParam & (1 << 29)) != 0),
GetBoolName((aMSG.lParam & (1 << 30)) != 0),
GetBoolName((aMSG.lParam & (1 << 31)) != 0));
break;
case WM_APPCOMMAND:
result.AppendPrintf(
"window handle=0x%p, app command=%s, device=%s, dwKeys=%s",
aMSG.wParam,
GetAppCommandName(GET_APPCOMMAND_LPARAM(aMSG.lParam)).get(),
GetAppCommandDeviceName(GET_DEVICE_LPARAM(aMSG.lParam)).get(),
GetAppCommandKeysName(GET_KEYSTATE_LPARAM(aMSG.lParam)).get());
break;
default:
result.AppendPrintf("wParam=%u, lParam=%u", aMSG.wParam, aMSG.lParam);
break;
}
result.AppendPrintf(", hwnd=0x%p", aMSG.hwnd);
return result;
}
static const nsCString ToString(
const UniCharsAndModifiers& aUniCharsAndModifiers) {
if (aUniCharsAndModifiers.IsEmpty()) {
return "{}"_ns;
}
nsCString result;
result.AssignLiteral("{ ");
result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(0)));
for (size_t i = 1; i < aUniCharsAndModifiers.Length(); ++i) {
if (aUniCharsAndModifiers.ModifiersAt(i - 1) !=
aUniCharsAndModifiers.ModifiersAt(i)) {
result.AppendLiteral(" [");
result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(0)));
result.AppendLiteral("]");
}
result.AppendLiteral(", ");
result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(i)));
}
result.AppendLiteral(" [");
uint32_t lastIndex = aUniCharsAndModifiers.Length() - 1;
result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(lastIndex)));
result.AppendLiteral("] }");
return result;
}
const nsCString ToString(const ModifierKeyState& aModifierKeyState) {
nsCString result;
result.AssignLiteral("{ ");
result.Append(GetModifiersName(aModifierKeyState.GetModifiers()).get());
result.AppendLiteral(" }");
return result;
}
// Unique id counter associated with a keydown / keypress events. Used in
// identifing keypress events for removal from async event dispatch queue
// in metrofx after preventDefault is called on keydown events.
static uint32_t sUniqueKeyEventId = 0;
/*****************************************************************************
* mozilla::widget::ModifierKeyState
*****************************************************************************/
ModifierKeyState::ModifierKeyState() { Update(); }
ModifierKeyState::ModifierKeyState(Modifiers aModifiers)
: mModifiers(aModifiers) {
MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
"Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
"if MODIFIER_ALTGRAPH is set");
}
void ModifierKeyState::Update() {
mModifiers = 0;
if (IS_VK_DOWN(VK_SHIFT)) {
mModifiers |= MODIFIER_SHIFT;
}
// If AltGr key (i.e., VK_RMENU on some keyboard layout) is pressed, only
// MODIFIER_ALTGRAPH should be set. Otherwise, i.e., if both Ctrl and Alt
// keys are pressed to emulate AltGr key, MODIFIER_CONTROL and MODIFIER_ALT
// keys should be set separately.
if (KeyboardLayout::GetInstance()->HasAltGr() && IS_VK_DOWN(VK_RMENU)) {
mModifiers |= MODIFIER_ALTGRAPH;
} else {
if (IS_VK_DOWN(VK_CONTROL)) {
mModifiers |= MODIFIER_CONTROL;
}
if (IS_VK_DOWN(VK_MENU)) {
mModifiers |= MODIFIER_ALT;
}
}
if (IS_VK_DOWN(VK_LWIN) || IS_VK_DOWN(VK_RWIN)) {
mModifiers |= MODIFIER_OS;
}
if (::GetKeyState(VK_CAPITAL) & 1) {
mModifiers |= MODIFIER_CAPSLOCK;
}
if (::GetKeyState(VK_NUMLOCK) & 1) {
mModifiers |= MODIFIER_NUMLOCK;
}
if (::GetKeyState(VK_SCROLL) & 1) {
mModifiers |= MODIFIER_SCROLLLOCK;
}
}
void ModifierKeyState::Unset(Modifiers aRemovingModifiers) {
mModifiers &= ~aRemovingModifiers;
}
void ModifierKeyState::Set(Modifiers aAddingModifiers) {
mModifiers |= aAddingModifiers;
MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
"Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
"if MODIFIER_ALTGRAPH is set");
}
void ModifierKeyState::InitInputEvent(WidgetInputEvent& aInputEvent) const {
aInputEvent.mModifiers = mModifiers;
switch (aInputEvent.mClass) {
case eMouseEventClass:
case eMouseScrollEventClass:
case eWheelEventClass:
case eDragEventClass:
case eSimpleGestureEventClass:
InitMouseEvent(aInputEvent);
break;
default:
break;
}
}
void ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const {
NS_ASSERTION(aMouseEvent.mClass == eMouseEventClass ||
aMouseEvent.mClass == eWheelEventClass ||
aMouseEvent.mClass == eDragEventClass ||
aMouseEvent.mClass == eSimpleGestureEventClass,
"called with non-mouse event");
WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase();
mouseEvent.mButtons = 0;
if (::GetKeyState(VK_LBUTTON) < 0) {
mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag;
}
if (::GetKeyState(VK_RBUTTON) < 0) {
mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag;
}
if (::GetKeyState(VK_MBUTTON) < 0) {
mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag;
}
if (::GetKeyState(VK_XBUTTON1) < 0) {
mouseEvent.mButtons |= MouseButtonsFlag::e4thFlag;
}
if (::GetKeyState(VK_XBUTTON2) < 0) {
mouseEvent.mButtons |= MouseButtonsFlag::e5thFlag;
}
}
bool ModifierKeyState::IsShift() const {
return (mModifiers & MODIFIER_SHIFT) != 0;
}
bool ModifierKeyState::IsControl() const {
return (mModifiers & MODIFIER_CONTROL) != 0;
}
bool ModifierKeyState::IsAlt() const {
return (mModifiers & MODIFIER_ALT) != 0;
}
bool ModifierKeyState::IsWin() const { return (mModifiers & MODIFIER_OS) != 0; }
bool ModifierKeyState::MaybeMatchShortcutKey() const {
// If Windows key is pressed, even if both Ctrl key and Alt key are pressed,
// it's possible to match a shortcut key.
if (IsWin()) {
return true;
}
// Otherwise, when both Ctrl key and Alt key are pressed, it shouldn't be
// a shortcut key for Windows since it means pressing AltGr key on
// some keyboard layouts.
if (IsControl() ^ IsAlt()) {
return true;
}
// If no modifier key is active except a lockable modifier nor Shift key,
// the key shouldn't match any shortcut keys (there are Space and
// Shift+Space, though, let's ignore these special case...).
return false;
}
bool ModifierKeyState::IsCapsLocked() const {
return (mModifiers & MODIFIER_CAPSLOCK) != 0;
}
bool ModifierKeyState::IsNumLocked() const {
return (mModifiers & MODIFIER_NUMLOCK) != 0;
}
bool ModifierKeyState::IsScrollLocked() const {
return (mModifiers & MODIFIER_SCROLLLOCK) != 0;
}
/*****************************************************************************
* mozilla::widget::UniCharsAndModifiers
*****************************************************************************/
void UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers) {
mChars.Append(aUniChar);
mModifiers.AppendElement(aModifiers);
}
void UniCharsAndModifiers::FillModifiers(Modifiers aModifiers) {
for (size_t i = 0; i < Length(); i++) {
mModifiers[i] = aModifiers;
}
}
void UniCharsAndModifiers::OverwriteModifiersIfBeginsWith(
const UniCharsAndModifiers& aOther) {
if (!BeginsWith(aOther)) {
return;
}
for (size_t i = 0; i < aOther.Length(); ++i) {
mModifiers[i] = aOther.mModifiers[i];
}
}
bool UniCharsAndModifiers::UniCharsEqual(
const UniCharsAndModifiers& aOther) const {
return mChars.Equals(aOther.mChars);
}
bool UniCharsAndModifiers::UniCharsCaseInsensitiveEqual(
const UniCharsAndModifiers& aOther) const {
return mChars.Equals(aOther.mChars, nsCaseInsensitiveStringComparator);
}
bool UniCharsAndModifiers::BeginsWith(
const UniCharsAndModifiers& aOther) const {
return StringBeginsWith(mChars, aOther.mChars);
}
UniCharsAndModifiers& UniCharsAndModifiers::operator+=(
const UniCharsAndModifiers& aOther) {
mChars.Append(aOther.mChars);
mModifiers.AppendElements(aOther.mModifiers);
return *this;
}
UniCharsAndModifiers UniCharsAndModifiers::operator+(
const UniCharsAndModifiers& aOther) const {
UniCharsAndModifiers result(*this);
result += aOther;
return result;
}
/*****************************************************************************
* mozilla::widget::VirtualKey
*****************************************************************************/
// static
VirtualKey::ShiftState VirtualKey::ModifiersToShiftState(Modifiers aModifiers) {
ShiftState state = 0;
if (aModifiers & MODIFIER_SHIFT) {
state |= STATE_SHIFT;
}
if (aModifiers & MODIFIER_ALTGRAPH) {
state |= STATE_ALTGRAPH;
} else {
if (aModifiers & MODIFIER_CONTROL) {
state |= STATE_CONTROL;
}
if (aModifiers & MODIFIER_ALT) {
state |= STATE_ALT;
}
}
if (aModifiers & MODIFIER_CAPSLOCK) {
state |= STATE_CAPSLOCK;
}
return state;
}
// static
Modifiers VirtualKey::ShiftStateToModifiers(ShiftState aShiftState) {
Modifiers modifiers = 0;
if (aShiftState & STATE_SHIFT) {
modifiers |= MODIFIER_SHIFT;
}
if (aShiftState & STATE_ALTGRAPH) {
modifiers |= MODIFIER_ALTGRAPH;
} else {
if (aShiftState & STATE_CONTROL) {
modifiers |= MODIFIER_CONTROL;
}
if (aShiftState & STATE_ALT) {
modifiers |= MODIFIER_ALT;
}
}
if (aShiftState & STATE_CAPSLOCK) {
modifiers |= MODIFIER_CAPSLOCK;
}
return modifiers;
}
const DeadKeyTable* VirtualKey::MatchingDeadKeyTable(
const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const {
if (!mIsDeadKey) {
return nullptr;
}
for (ShiftState shiftState = 0; shiftState < 16; shiftState++) {
if (!IsDeadKey(shiftState)) {
continue;
}
const DeadKeyTable* dkt = mShiftStates[shiftState].DeadKey.Table;
if (dkt && dkt->IsEqual(aDeadKeyArray, aEntries)) {
return dkt;
}
}
return nullptr;
}
void VirtualKey::SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
uint32_t aNumOfChars) {
MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
SetDeadKey(aShiftState, false);
for (uint32_t index = 0; index < aNumOfChars; index++) {
// Ignore legacy non-printable control characters
mShiftStates[aShiftState].Normal.Chars[index] =
(aChars[index] >= 0x20) ? aChars[index] : 0;
}
uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
for (uint32_t index = aNumOfChars; index < len; index++) {
mShiftStates[aShiftState].Normal.Chars[index] = 0;
}
}
void VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar) {
MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
SetDeadKey(aShiftState, true);
mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar;
mShiftStates[aShiftState].DeadKey.Table = nullptr;
}
UniCharsAndModifiers VirtualKey::GetUniChars(ShiftState aShiftState) const {
UniCharsAndModifiers result = GetNativeUniChars(aShiftState);
const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
if (!(kShiftStateIndex & STATE_CONTROL_ALT)) {
// If neither Alt nor Ctrl key is pressed, just return stored data
// for the key.
return result;
}
if (result.IsEmpty()) {
// If Alt and/or Control are pressed and the key produces no
// character, return characters which is produced by the key without
// Alt and Control, and return given modifiers as is.
result = GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
result.FillModifiers(ShiftStateToModifiers(aShiftState));
return result;
}
if (IsAltGrIndex(kShiftStateIndex)) {
// If AltGr or both Ctrl and Alt are pressed and the key produces
// character(s), we need to clear MODIFIER_ALT and MODIFIER_CONTROL
// since TextEditor won't handle eKeyPress event whose mModifiers
// has MODIFIER_ALT or MODIFIER_CONTROL. Additionally, we need to
// use MODIFIER_ALTGRAPH when a key produces character(s) with
// AltGr or both Ctrl and Alt on Windows. See following spec issue:
Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
finalModifiers |= MODIFIER_ALTGRAPH;
result.FillModifiers(finalModifiers);
return result;
}
// Otherwise, i.e., Alt or Ctrl is pressed and it produces character(s),
// check if different character(s) is produced by the key without Alt/Ctrl.
// If it produces different character, we need to consume the Alt and
// Ctrl modifier for TextEditor. Otherwise, the key does not produces the
// character actually. So, keep setting Alt and Ctrl modifiers.
UniCharsAndModifiers unmodifiedReslt =
GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
if (!result.UniCharsEqual(unmodifiedReslt)) {
Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
result.FillModifiers(finalModifiers);
}
return result;
}
UniCharsAndModifiers VirtualKey::GetNativeUniChars(
ShiftState aShiftState) const {
const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
UniCharsAndModifiers result;
Modifiers modifiers = ShiftStateToModifiers(aShiftState);
if (IsDeadKey(aShiftState)) {
result.Append(mShiftStates[kShiftStateIndex].DeadKey.DeadChar, modifiers);
return result;
}
uint32_t len = ArrayLength(mShiftStates[kShiftStateIndex].Normal.Chars);
for (uint32_t i = 0;
i < len && mShiftStates[kShiftStateIndex].Normal.Chars[i]; i++) {
result.Append(mShiftStates[kShiftStateIndex].Normal.Chars[i], modifiers);
}
return result;
}
// static
void VirtualKey::FillKbdState(PBYTE aKbdState, const ShiftState aShiftState) {
if (aShiftState & STATE_SHIFT) {
aKbdState[VK_SHIFT] |= 0x80;
} else {
aKbdState[VK_SHIFT] &= ~0x80;
aKbdState[VK_LSHIFT] &= ~0x80;
aKbdState[VK_RSHIFT] &= ~0x80;
}
if (aShiftState & STATE_ALTGRAPH) {
aKbdState[VK_CONTROL] |= 0x80;
aKbdState[VK_LCONTROL] |= 0x80;
aKbdState[VK_RCONTROL] &= ~0x80;
aKbdState[VK_MENU] |= 0x80;
aKbdState[VK_LMENU] &= ~0x80;
aKbdState[VK_RMENU] |= 0x80;
} else {
if (aShiftState & STATE_CONTROL) {
aKbdState[VK_CONTROL] |= 0x80;
} else {
aKbdState[VK_CONTROL] &= ~0x80;
aKbdState[VK_LCONTROL] &= ~0x80;
aKbdState[VK_RCONTROL] &= ~0x80;
}
if (aShiftState & STATE_ALT) {
aKbdState[VK_MENU] |= 0x80;
} else {
aKbdState[VK_MENU] &= ~0x80;
aKbdState[VK_LMENU] &= ~0x80;
aKbdState[VK_RMENU] &= ~0x80;
}
}
if (aShiftState & STATE_CAPSLOCK) {
aKbdState[VK_CAPITAL] |= 0x01;
} else {
aKbdState[VK_CAPITAL] &= ~0x01;
}
}
/*****************************************************************************
* mozilla::widget::NativeKey
*****************************************************************************/
uint8_t NativeKey::sDispatchedKeyOfAppCommand = 0;
NativeKey* NativeKey::sLatestInstance = nullptr;
const MSG NativeKey::sEmptyMSG = {};
MSG NativeKey::sLastKeyOrCharMSG = {};
MSG NativeKey::sLastKeyMSG = {};
LazyLogModule sNativeKeyLogger("NativeKeyWidgets");
NativeKey::NativeKey(nsWindowBase* aWidget, const MSG& aMessage,
const ModifierKeyState& aModKeyState,
HKL aOverrideKeyboardLayout,
nsTArray<FakeCharMsg>* aFakeCharMsgs)
: mLastInstance(sLatestInstance),
mRemovingMsg(sEmptyMSG),
mReceivedMsg(sEmptyMSG),
mWidget(aWidget),
mDispatcher(aWidget->GetTextEventDispatcher()),
mMsg(aMessage),
mFocusedWndBeforeDispatch(::GetFocus()),
mDOMKeyCode(0),
mKeyNameIndex(KEY_NAME_INDEX_Unidentified),
mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN),
mModKeyState(aModKeyState),
mVirtualKeyCode(0),
mOriginalVirtualKeyCode(0),
mShiftedLatinChar(0),
mUnshiftedLatinChar(0),
mScanCode(0),
mIsExtended(false),
mIsRepeat(false),
mIsDeadKey(false),
mIsPrintableKey(false),
mIsSkippableInRemoteProcess(false),
mCharMessageHasGone(false),
mCanIgnoreModifierStateAtKeyPress(true),
mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ? aFakeCharMsgs
: nullptr) {
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::NativeKey(aWidget=0x%p { GetWindowHandle()=0x%p }, "
"aMessage=%s, aModKeyState=%s), sLatestInstance=0x%p",
this, aWidget, aWidget->GetWindowHandle(), ToString(aMessage).get(),
ToString(aModKeyState).get(), sLatestInstance));
MOZ_ASSERT(aWidget);
MOZ_ASSERT(mDispatcher);
sLatestInstance = this;
KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
mKeyboardLayout = keyboardLayout->GetLayout();
if (aOverrideKeyboardLayout && mKeyboardLayout != aOverrideKeyboardLayout) {
keyboardLayout->OverrideLayout(aOverrideKeyboardLayout);
mKeyboardLayout = keyboardLayout->GetLayout();
MOZ_ASSERT(mKeyboardLayout == aOverrideKeyboardLayout);
mIsOverridingKeyboardLayout = true;
} else {
mIsOverridingKeyboardLayout = false;
sLastKeyOrCharMSG = aMessage;
}
if (mMsg.message == WM_APPCOMMAND) {
InitWithAppCommand();
} else {
InitWithKeyOrChar();
}
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::NativeKey(), mKeyboardLayout=0x%08X, "
"mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=%s, "
"mKeyNameIndex=%s, mCodeNameIndex=%s, mModKeyState=%s, "
"mVirtualKeyCode=%s, mOriginalVirtualKeyCode=%s, "
"mCommittedCharsAndModifiers=%s, mInputtingStringAndModifiers=%s, "
"mShiftedString=%s, mUnshiftedString=%s, mShiftedLatinChar=%s, "
"mUnshiftedLatinChar=%s, mScanCode=0x%04X, mIsExtended=%s, "
"mIsRepeat=%s, mIsDeadKey=%s, mIsPrintableKey=%s, "
"mIsSkippableInRemoteProcess=%s, mCharMessageHasGone=%s, "
"mIsOverridingKeyboardLayout=%s",
this, mKeyboardLayout, mFocusedWndBeforeDispatch,
GetDOMKeyCodeName(mDOMKeyCode).get(), ToString(mKeyNameIndex).get(),
ToString(mCodeNameIndex).get(), ToString(mModKeyState).get(),
GetVirtualKeyCodeName(mVirtualKeyCode).get(),
GetVirtualKeyCodeName(mOriginalVirtualKeyCode).get(),
ToString(mCommittedCharsAndModifiers).get(),
ToString(mInputtingStringAndModifiers).get(),
ToString(mShiftedString).get(), ToString(mUnshiftedString).get(),
GetCharacterCodeName(mShiftedLatinChar).get(),
GetCharacterCodeName(mUnshiftedLatinChar).get(), mScanCode,
GetBoolName(mIsExtended), GetBoolName(mIsRepeat),
GetBoolName(mIsDeadKey), GetBoolName(mIsPrintableKey),
GetBoolName(mIsSkippableInRemoteProcess),
GetBoolName(mCharMessageHasGone),
GetBoolName(mIsOverridingKeyboardLayout)));
}
void NativeKey::InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG) {
mIsSkippableInRemoteProcess = false;
if (!mIsRepeat) {
// If the message is not repeated key message, the event should be always
// handled in remote process even if it's too old.
return;
}
// Keyboard utilities may send us some generated messages and such messages
// may be marked as "repeated", e.g., SendInput() calls with
// KEYEVENTF_UNICODE but without KEYEVENTF_KEYUP. However, key sequence
// comes from such utilities may be really important. For example, utilities
// may send WM_KEYDOWN for VK_BACK to remove previous character and send
// WM_KEYDOWN for VK_PACKET to insert a composite character. Therefore, we
// should check if current message and previous key message are caused by
// same physical key. If not, the message may be generated by such
// utility.
// XXX With this approach, if VK_BACK messages are generated with known
// scancode, we cannot distinguish whether coming VK_BACK message is
// actually repeated by the auto-repeat feature. Currently, we need
// this hack only for "SinhalaTamil IME" and fortunately, it generates
// VK_BACK messages with odd scancode. So, we don't need to handle
// VK_BACK specially at least for now.
if (mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
// If current event is not caused by physical key operation, it may be
// caused by a keyboard utility. If so, the event shouldn't be ignored by
// BrowserChild since it want to insert the character, delete a character or
// move caret.
return;
}
if (mOriginalVirtualKeyCode == VK_PACKET) {
// If the message is VK_PACKET, that means that a keyboard utility
// tries to insert a character.
return;
}
switch (mMsg.message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_CHAR:
case WM_SYSCHAR:
case WM_DEADCHAR:
case WM_SYSDEADCHAR:
// However, some keyboard layouts may send some keyboard messages with
// activating the bit. If we dispatch repeated keyboard events, they
// may be ignored by BrowserChild due to performance reason. So, we need
// to check if actually a physical key is repeated by the auto-repeat
// feature.
switch (aLastKeyMSG.message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
if (aLastKeyMSG.wParam == VK_PACKET) {
// If the last message was VK_PACKET, that means that a keyboard
// utility tried to insert a character. So, current message is
// not repeated key event of the previous event.
return;
}
// Let's check whether current message and previous message are
// caused by same physical key.
mIsSkippableInRemoteProcess =
mScanCode == WinUtils::GetScanCode(aLastKeyMSG.lParam) &&
mIsExtended == WinUtils::IsExtendedScanCode(aLastKeyMSG.lParam);
return;
default:
// If previous message is not a keydown, this must not be generated
// by the auto-repeat feature.
return;
}
return;
case WM_APPCOMMAND:
MOZ_ASSERT_UNREACHABLE(
"WM_APPCOMMAND should be handled in "
"InitWithAppCommand()");
return;
default:
// keyup message shouldn't be repeated by the auto-repeat feature.
return;
}
}
void NativeKey::InitWithKeyOrChar() {
MSG lastKeyMSG = sLastKeyMSG;
mScanCode = WinUtils::GetScanCode(mMsg.lParam);
mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam);
switch (mMsg.message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP: {
// Modify sLastKeyMSG now since retrieving following char messages may
// cause sending another key message if odd tool hooks GetMessage(),
// PeekMessage().
sLastKeyMSG = mMsg;
// Note that we don't need to compute raw virtual keycode here even when
// it's VK_PROCESS (i.e., already handled by IME) because we need to
// export it as DOM_VK_PROCESS and KEY_NAME_INDEX_Process.
mOriginalVirtualKeyCode = static_cast<uint8_t>(mMsg.wParam);
// If the key message is sent from other application like a11y tools, the
// scancode value might not be set proper value. Then, probably the value
// is 0.
// NOTE: If the virtual keycode can be caused by both non-extended key
// and extended key, the API returns the non-extended key's
// scancode. E.g., VK_LEFT causes "4" key on numpad.
if (!mScanCode && mOriginalVirtualKeyCode != VK_PACKET) {
uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mMsg.wParam);
if (scanCodeEx) {
mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF);
uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8);
mIsExtended = (extended == 0xE0) || (extended == 0xE1);
}
}
// Most keys are not distinguished as left or right keys.
bool isLeftRightDistinguishedKey = false;
// mOriginalVirtualKeyCode must not distinguish left or right of
// Shift, Control or Alt.
switch (mOriginalVirtualKeyCode) {
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
isLeftRightDistinguishedKey = true;
break;
case VK_LSHIFT:
case VK_RSHIFT:
mVirtualKeyCode = mOriginalVirtualKeyCode;
mOriginalVirtualKeyCode = VK_SHIFT;
isLeftRightDistinguishedKey = true;
break;
case VK_LCONTROL:
case VK_RCONTROL:
mVirtualKeyCode = mOriginalVirtualKeyCode;
mOriginalVirtualKeyCode = VK_CONTROL;
isLeftRightDistinguishedKey = true;
break;
case VK_LMENU:
case VK_RMENU:
mVirtualKeyCode = mOriginalVirtualKeyCode;
mOriginalVirtualKeyCode = VK_MENU;
isLeftRightDistinguishedKey = true;
break;
}
// If virtual keycode (left-right distinguished keycode) is already
// computed, we don't need to do anymore.
if (mVirtualKeyCode) {
break;
}
// If the keycode doesn't have LR distinguished keycode, we just set
// mOriginalVirtualKeyCode to mVirtualKeyCode. Note that don't compute
// it from MapVirtualKeyEx() because the scan code might be wrong if
// the message is sent/posted by other application. Then, we will compute
// unexpected keycode from the scan code.
if (!isLeftRightDistinguishedKey) {
break;
}
NS_ASSERTION(!mVirtualKeyCode,
"mVirtualKeyCode has been computed already");
// Otherwise, compute the virtual keycode with MapVirtualKeyEx().
mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx();
// Following code shouldn't be used now because we compute scancode value
// if we detect that the sender doesn't set proper scancode.
// However, the detection might fail. Therefore, let's keep using this.
switch (mOriginalVirtualKeyCode) {
case VK_CONTROL:
if (mVirtualKeyCode != VK_LCONTROL &&
mVirtualKeyCode != VK_RCONTROL) {
mVirtualKeyCode = mIsExtended ? VK_RCONTROL : VK_LCONTROL;
}
break;
case VK_MENU:
if (mVirtualKeyCode != VK_LMENU && mVirtualKeyCode != VK_RMENU) {
mVirtualKeyCode = mIsExtended ? VK_RMENU : VK_LMENU;
}
break;
case VK_SHIFT:
if (mVirtualKeyCode != VK_LSHIFT && mVirtualKeyCode != VK_RSHIFT) {
// Neither left shift nor right shift is an extended key,
// let's use VK_LSHIFT for unknown mapping.
mVirtualKeyCode = VK_LSHIFT;
}
break;
default:
MOZ_CRASH("Unsupported mOriginalVirtualKeyCode");
}
break;
}
case WM_CHAR:
case WM_UNICHAR:
case WM_SYSCHAR:
// If there is another instance and it is trying to remove a char message
// from the queue, this message should be handled in the old instance.
if (IsAnotherInstanceRemovingCharMessage()) {
// XXX Do we need to make mReceivedMsg an array?
MOZ_ASSERT(IsEmptyMSG(mLastInstance->mReceivedMsg));
MOZ_LOG(
sNativeKeyLogger, LogLevel::Warning,
("%p NativeKey::InitWithKeyOrChar(), WARNING, detecting another "
"instance is trying to remove a char message, so, this instance "
"should do nothing, mLastInstance=0x%p, mRemovingMsg=%s, "
"mReceivedMsg=%s",
this, mLastInstance, ToString(mLastInstance->mRemovingMsg).get(),
ToString(mLastInstance->mReceivedMsg).get()));
mLastInstance->mReceivedMsg = mMsg;
return;
}
// NOTE: If other applications like a11y tools sends WM_*CHAR without
// scancode, we cannot compute virtual keycode. I.e., with such
// applications, we cannot generate proper KeyboardEvent.code value.
mVirtualKeyCode = mOriginalVirtualKeyCode =
ComputeVirtualKeyCodeFromScanCodeEx();
NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode");
break;
default: {
MOZ_CRASH_UNSAFE_PRINTF("Unsupported message: 0x%04X", mMsg.message);
break;
}
}
if (!mVirtualKeyCode) {
mVirtualKeyCode = mOriginalVirtualKeyCode;
}
KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
mDOMKeyCode =
keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mVirtualKeyCode);
// Be aware, keyboard utilities can change non-printable keys to printable
// keys. In such case, we should make the key value as a printable key.
// FYI: IsFollowedByPrintableCharMessage() returns true only when it's
// handling a keydown message.
mKeyNameIndex =
IsFollowedByPrintableCharMessage()
? KEY_NAME_INDEX_USE_STRING
: keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mVirtualKeyCode);
mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex(
GetScanCodeWithExtendedFlag());
// If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key
// combination is not reserved by the system, let's consume it now.
// TODO: We cannot initialize mCommittedCharsAndModifiers for VK_PACKET
// if the message is WM_KEYUP because we don't have preceding
// WM_CHAR message.
// TODO: Like Edge, we shouldn't dispatch two sets of keyboard events
// for a Unicode character in non-BMP because its key value looks
// broken and not good thing for our editor if only one keydown or
// keypress event's default is prevented. I guess, we should store
// key message information globally and we should wait following
// WM_KEYDOWN if following WM_CHAR is a part of a Unicode character.
if ((mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN) &&
!IsReservedBySystem()) {
MSG charMsg;
while (GetFollowingCharMessage(charMsg)) {
// Although, got message shouldn't be WM_NULL in desktop apps,
// we should keep checking this. FYI: This was added for Metrofox.
if (charMsg.message == WM_NULL) {
continue;
}
MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
("%p NativeKey::InitWithKeyOrChar(), removed char message, %s",
this, ToString(charMsg).get()));
Unused << NS_WARN_IF(charMsg.hwnd != mMsg.hwnd);
mFollowingCharMsgs.AppendElement(charMsg);
}