summaryrefslogtreecommitdiffstats
path: root/widget/windows/KeyboardLayout.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/KeyboardLayout.cpp')
-rw-r--r--widget/windows/KeyboardLayout.cpp5232
1 files changed, 5232 insertions, 0 deletions
diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp
new file mode 100644
index 000000000..11f657874
--- /dev/null
+++ b/widget/windows/KeyboardLayout.cpp
@@ -0,0 +1,5232 @@
+/* -*- 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/DebugOnly.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "nsAlgorithm.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+#include "nsGkAtoms.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIIdleServiceInternal.h"
+#include "nsIWindowsRegKey.h"
+#include "nsMemory.h"
+#include "nsPrintfCString.h"
+#include "nsQuickSort.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* 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 NS_LITERAL_CSTRING("NULL (0x0000)");
+ case 0x0008:
+ return NS_LITERAL_CSTRING("BACKSPACE (0x0008)");
+ case 0x0009:
+ return NS_LITERAL_CSTRING("CHARACTER TABULATION (0x0009)");
+ case 0x000A:
+ return NS_LITERAL_CSTRING("LINE FEED (0x000A)");
+ case 0x000B:
+ return NS_LITERAL_CSTRING("LINE TABULATION (0x000B)");
+ case 0x000C:
+ return NS_LITERAL_CSTRING("FORM FEED (0x000C)");
+ case 0x000D:
+ return NS_LITERAL_CSTRING("CARRIAGE RETURN (0x000D)");
+ case 0x0018:
+ return NS_LITERAL_CSTRING("CANCEL (0x0018)");
+ case 0x001B:
+ return NS_LITERAL_CSTRING("ESCAPE (0x001B)");
+ case 0x0020:
+ return NS_LITERAL_CSTRING("SPACE (0x0020)");
+ case 0x007F:
+ return NS_LITERAL_CSTRING("DELETE (0x007F)");
+ case 0x00A0:
+ return NS_LITERAL_CSTRING("NO-BREAK SPACE (0x00A0)");
+ case 0x00AD:
+ return NS_LITERAL_CSTRING("SOFT HYPHEN (0x00AD)");
+ case 0x2000:
+ return NS_LITERAL_CSTRING("EN QUAD (0x2000)");
+ case 0x2001:
+ return NS_LITERAL_CSTRING("EM QUAD (0x2001)");
+ case 0x2002:
+ return NS_LITERAL_CSTRING("EN SPACE (0x2002)");
+ case 0x2003:
+ return NS_LITERAL_CSTRING("EM SPACE (0x2003)");
+ case 0x2004:
+ return NS_LITERAL_CSTRING("THREE-PER-EM SPACE (0x2004)");
+ case 0x2005:
+ return NS_LITERAL_CSTRING("FOUR-PER-EM SPACE (0x2005)");
+ case 0x2006:
+ return NS_LITERAL_CSTRING("SIX-PER-EM SPACE (0x2006)");
+ case 0x2007:
+ return NS_LITERAL_CSTRING("FIGURE SPACE (0x2007)");
+ case 0x2008:
+ return NS_LITERAL_CSTRING("PUNCTUATION SPACE (0x2008)");
+ case 0x2009:
+ return NS_LITERAL_CSTRING("THIN SPACE (0x2009)");
+ case 0x200A:
+ return NS_LITERAL_CSTRING("HAIR SPACE (0x200A)");
+ case 0x200B:
+ return NS_LITERAL_CSTRING("ZERO WIDTH SPACE (0x200B)");
+ case 0x200C:
+ return NS_LITERAL_CSTRING("ZERO WIDTH NON-JOINER (0x200C)");
+ case 0x200D:
+ return NS_LITERAL_CSTRING("ZERO WIDTH JOINER (0x200D)");
+ case 0x200E:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT MARK (0x200E)");
+ case 0x200F:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT MARK (0x200F)");
+ case 0x2029:
+ return NS_LITERAL_CSTRING("PARAGRAPH SEPARATOR (0x2029)");
+ case 0x202A:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT EMBEDDING (0x202A)");
+ case 0x202B:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT EMBEDDING (0x202B)");
+ case 0x202D:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT OVERRIDE (0x202D)");
+ case 0x202E:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT OVERRIDE (0x202E)");
+ case 0x202F:
+ return NS_LITERAL_CSTRING("NARROW NO-BREAK SPACE (0x202F)");
+ case 0x205F:
+ return NS_LITERAL_CSTRING("MEDIUM MATHEMATICAL SPACE (0x205F)");
+ case 0x2060:
+ return NS_LITERAL_CSTRING("WORD JOINER (0x2060)");
+ case 0x2066:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT ISOLATE (0x2066)");
+ case 0x2067:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT ISOLATE (0x2067)");
+ case 0x3000:
+ return NS_LITERAL_CSTRING("IDEOGRAPHIC SPACE (0x3000)");
+ case 0xFEFF:
+ return NS_LITERAL_CSTRING("ZERO WIDTH NO-BREAK SPACE (0xFEFF)");
+ 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_LEFT");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_RIGHT");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_STANDARD");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_NUMPAD");
+ default:
+ return nsPrintfCString("Unknown (0x%04X)", aLocation);
+ }
+}
+
+static const nsCString
+GetCharacterCodeName(char16_t* aChars, uint32_t aLength)
+{
+ if (!aLength) {
+ return NS_LITERAL_CSTRING("");
+ }
+ nsAutoCString result;
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (!result.IsEmpty()) {
+ result.AppendLiteral(", ");
+ } else {
+ result.AssignLiteral("\"");
+ }
+ result.Append(GetCharacterCodeName(aChars[i]));
+ }
+ 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 NS_LITERAL_CSTRING("WM_NULL");
+ case WM_KEYDOWN:
+ return NS_LITERAL_CSTRING("WM_KEYDOWN");
+ case WM_KEYUP:
+ return NS_LITERAL_CSTRING("WM_KEYUP");
+ case WM_SYSKEYDOWN:
+ return NS_LITERAL_CSTRING("WM_SYSKEYDOWN");
+ case WM_SYSKEYUP:
+ return NS_LITERAL_CSTRING("WM_SYSKEYUP");
+ case WM_CHAR:
+ return NS_LITERAL_CSTRING("WM_CHAR");
+ case WM_UNICHAR:
+ return NS_LITERAL_CSTRING("WM_UNICHAR");
+ case WM_SYSCHAR:
+ return NS_LITERAL_CSTRING("WM_SYSCHAR");
+ case WM_DEADCHAR:
+ return NS_LITERAL_CSTRING("WM_DEADCHAR");
+ case WM_SYSDEADCHAR:
+ return NS_LITERAL_CSTRING("WM_SYSDEADCHAR");
+ case MOZ_WM_KEYDOWN:
+ return NS_LITERAL_CSTRING("MOZ_WM_KEYDOWN");
+ case MOZ_WM_KEYUP:
+ return NS_LITERAL_CSTRING("MOZ_WM_KEYUP");
+ case WM_APPCOMMAND:
+ return NS_LITERAL_CSTRING("WM_APPCOMMAND");
+ case WM_QUIT:
+ return NS_LITERAL_CSTRING("WM_QUIT");
+ 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 NS_LITERAL_CSTRING("APPCOMMAND_BASS_BOOST");
+ case APPCOMMAND_BASS_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BASS_DOWN");
+ case APPCOMMAND_BASS_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BASS_UP");
+ case APPCOMMAND_BROWSER_BACKWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_BACKWARD");
+ case APPCOMMAND_BROWSER_FAVORITES:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FAVORITES");
+ case APPCOMMAND_BROWSER_FORWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FORWARD");
+ case APPCOMMAND_BROWSER_HOME:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_HOME");
+ case APPCOMMAND_BROWSER_REFRESH:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_REFRESH");
+ case APPCOMMAND_BROWSER_SEARCH:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_SEARCH");
+ case APPCOMMAND_BROWSER_STOP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_STOP");
+ case APPCOMMAND_CLOSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CLOSE");
+ case APPCOMMAND_COPY:
+ return NS_LITERAL_CSTRING("APPCOMMAND_COPY");
+ case APPCOMMAND_CORRECTION_LIST:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CORRECTION_LIST");
+ case APPCOMMAND_CUT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CUT");
+ case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE");
+ case APPCOMMAND_FIND:
+ return NS_LITERAL_CSTRING("APPCOMMAND_FIND");
+ case APPCOMMAND_FORWARD_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_FORWARD_MAIL");
+ case APPCOMMAND_HELP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_HELP");
+ case APPCOMMAND_LAUNCH_APP1:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP1");
+ case APPCOMMAND_LAUNCH_APP2:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP2");
+ case APPCOMMAND_LAUNCH_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MAIL");
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MEDIA_SELECT");
+ case APPCOMMAND_MEDIA_CHANNEL_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_DOWN");
+ case APPCOMMAND_MEDIA_CHANNEL_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_UP");
+ case APPCOMMAND_MEDIA_FAST_FORWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_FAST_FORWARD");
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_NEXTTRACK");
+ case APPCOMMAND_MEDIA_PAUSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PAUSE");
+ case APPCOMMAND_MEDIA_PLAY:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY");
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY_PAUSE");
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PREVIOUSTRACK");
+ case APPCOMMAND_MEDIA_RECORD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_RECORD");
+ case APPCOMMAND_MEDIA_REWIND:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_REWIND");
+ case APPCOMMAND_MEDIA_STOP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_STOP");
+ case APPCOMMAND_MIC_ON_OFF_TOGGLE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MIC_ON_OFF_TOGGLE");
+ case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_DOWN");
+ case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_MUTE");
+ case APPCOMMAND_MICROPHONE_VOLUME_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_UP");
+ case APPCOMMAND_NEW:
+ return NS_LITERAL_CSTRING("APPCOMMAND_NEW");
+ case APPCOMMAND_OPEN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_OPEN");
+ case APPCOMMAND_PASTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_PASTE");
+ case APPCOMMAND_PRINT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_PRINT");
+ case APPCOMMAND_REDO:
+ return NS_LITERAL_CSTRING("APPCOMMAND_REDO");
+ case APPCOMMAND_REPLY_TO_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_REPLY_TO_MAIL");
+ case APPCOMMAND_SAVE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SAVE");
+ case APPCOMMAND_SEND_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SEND_MAIL");
+ case APPCOMMAND_SPELL_CHECK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SPELL_CHECK");
+ case APPCOMMAND_TREBLE_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_DOWN");
+ case APPCOMMAND_TREBLE_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_UP");
+ case APPCOMMAND_UNDO:
+ return NS_LITERAL_CSTRING("APPCOMMAND_UNDO");
+ case APPCOMMAND_VOLUME_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_DOWN");
+ case APPCOMMAND_VOLUME_MUTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_MUTE");
+ case APPCOMMAND_VOLUME_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_UP");
+ default:
+ return nsPrintfCString("Unknown app command (0x%08X)", aCommand);
+ }
+}
+
+static const nsCString
+GetAppCommandDeviceName(LPARAM aDevice)
+{
+ switch (aDevice) {
+ case FAPPCOMMAND_KEY:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_KEY");
+ case FAPPCOMMAND_MOUSE:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_MOUSE");
+ case FAPPCOMMAND_OEM:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_OEM");
+ default:
+ return nsPrintfCString("Unknown app command device (0x%04X)", aDevice);
+ }
+};
+
+class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString
+{
+public:
+ 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)
+{
+ nsAutoCString 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:
+ case MOZ_WM_KEYDOWN:
+ case MOZ_WM_KEYUP:
+ 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_LITERAL_CSTRING("{}");
+ }
+ nsAutoCString 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)
+{
+ nsAutoCString 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;
+
+struct DeadKeyEntry
+{
+ char16_t BaseChar;
+ char16_t CompositeChar;
+};
+
+
+class DeadKeyTable
+{
+ friend class KeyboardLayout;
+
+ uint16_t mEntries;
+ // KeyboardLayout::AddDeadKeyTable() will allocate as many entries as
+ // required. It is the only way to create new DeadKeyTable instances.
+ DeadKeyEntry mTable[1];
+
+ void Init(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries)
+ {
+ mEntries = aEntries;
+ memcpy(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry));
+ }
+
+ static uint32_t SizeInBytes(uint32_t aEntries)
+ {
+ return offsetof(DeadKeyTable, mTable) + aEntries * sizeof(DeadKeyEntry);
+ }
+
+public:
+ uint32_t Entries() const
+ {
+ return mEntries;
+ }
+
+ bool IsEqual(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const
+ {
+ return (mEntries == aEntries &&
+ !memcmp(mTable, aDeadKeyArray,
+ aEntries * sizeof(DeadKeyEntry)));
+ }
+
+ char16_t GetCompositeChar(char16_t aBaseChar) const;
+};
+
+
+/*****************************************************************************
+ * mozilla::widget::ModifierKeyState
+ *****************************************************************************/
+
+ModifierKeyState::ModifierKeyState()
+{
+ Update();
+}
+
+ModifierKeyState::ModifierKeyState(bool aIsShiftDown,
+ bool aIsControlDown,
+ bool aIsAltDown)
+{
+ Update();
+ Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_ALTGRAPH);
+ Modifiers modifiers = 0;
+ if (aIsShiftDown) {
+ modifiers |= MODIFIER_SHIFT;
+ }
+ if (aIsControlDown) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aIsAltDown) {
+ modifiers |= MODIFIER_ALT;
+ }
+ if (modifiers) {
+ Set(modifiers);
+ }
+}
+
+ModifierKeyState::ModifierKeyState(Modifiers aModifiers) :
+ mModifiers(aModifiers)
+{
+ EnsureAltGr();
+}
+
+void
+ModifierKeyState::Update()
+{
+ mModifiers = 0;
+ if (IS_VK_DOWN(VK_SHIFT)) {
+ mModifiers |= MODIFIER_SHIFT;
+ }
+ 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;
+ }
+
+ EnsureAltGr();
+}
+
+void
+ModifierKeyState::Unset(Modifiers aRemovingModifiers)
+{
+ mModifiers &= ~aRemovingModifiers;
+ // Note that we don't need to unset AltGr flag here automatically.
+ // For EditorBase, we need to remove Alt and Control flags but AltGr isn't
+ // checked in EditorBase, so, it can be kept.
+}
+
+void
+ModifierKeyState::Set(Modifiers aAddingModifiers)
+{
+ mModifiers |= aAddingModifiers;
+ EnsureAltGr();
+}
+
+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.buttons = 0;
+ if (::GetKeyState(VK_LBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag;
+ }
+ if (::GetKeyState(VK_RBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag;
+ }
+ if (::GetKeyState(VK_MBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON1) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::e4thButtonFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON2) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::e5thButtonFlag;
+ }
+}
+
+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::IsAltGr() const
+{
+ return IsControl() && IsAlt();
+}
+
+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;
+}
+
+void
+ModifierKeyState::EnsureAltGr()
+{
+ // If both Control key and Alt key are pressed, it means AltGr is pressed.
+ // Ideally, we should check whether the current keyboard layout has AltGr
+ // or not. However, setting AltGr flags for keyboard which doesn't have
+ // AltGr must not be serious bug. So, it should be OK for now.
+ if (IsAltGr()) {
+ mModifiers |= MODIFIER_ALTGRAPH;
+ }
+}
+
+/*****************************************************************************
+ * 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
+{
+ nsCaseInsensitiveStringComparator comp;
+ return mChars.Equals(aOther.mChars, comp);
+}
+
+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_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_CONTROL) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aShiftState & STATE_ALT) {
+ modifiers |= MODIFIER_ALT;
+ }
+ if (aShiftState & STATE_CAPSLOCK) {
+ modifiers |= MODIFIER_CAPSLOCK;
+ }
+ if ((modifiers & (MODIFIER_ALT | MODIFIER_CONTROL)) ==
+ (MODIFIER_ALT | MODIFIER_CONTROL)) {
+ modifiers |= MODIFIER_ALTGRAPH;
+ }
+ return modifiers;
+}
+
+inline char16_t
+VirtualKey::GetCompositeChar(ShiftState aShiftState, char16_t aBaseChar) const
+{
+ return mShiftStates[aShiftState].DeadKey.Table->GetCompositeChar(aBaseChar);
+}
+
+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)
+{
+ NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+
+ 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)
+{
+ NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+
+ SetDeadKey(aShiftState, true);
+
+ mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar;
+ mShiftStates[aShiftState].DeadKey.Table = nullptr;
+}
+
+UniCharsAndModifiers
+VirtualKey::GetUniChars(ShiftState aShiftState) const
+{
+ UniCharsAndModifiers result = GetNativeUniChars(aShiftState);
+
+ const ShiftState STATE_ALT_CONTROL = (STATE_ALT | STATE_CONTROL);
+ if (!(aShiftState & STATE_ALT_CONTROL)) {
+ return result;
+ }
+
+ if (result.IsEmpty()) {
+ result = GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL);
+ result.FillModifiers(ShiftStateToModifiers(aShiftState));
+ return result;
+ }
+
+ if ((aShiftState & STATE_ALT_CONTROL) == STATE_ALT_CONTROL) {
+ // Even if the shifted chars and the unshifted chars are same, we
+ // should consume the Alt key state and the Ctrl key state when
+ // AltGr key is pressed. Because if we don't consume them, the input
+ // events are ignored on EditorBase. (I.e., Users cannot input the
+ // characters with this key combination.)
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ return result;
+ }
+
+ UniCharsAndModifiers unmodifiedReslt =
+ GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL);
+ if (!result.UniCharsEqual(unmodifiedReslt)) {
+ // Otherwise, we should consume the Alt key state and the Ctrl key state
+ // only when the shifted chars and unshifted chars are different.
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ }
+ return result;
+}
+
+
+UniCharsAndModifiers
+VirtualKey::GetNativeUniChars(ShiftState aShiftState) const
+{
+#ifdef DEBUG
+ if (aShiftState >= ArrayLength(mShiftStates)) {
+ nsPrintfCString warning("Shift state is out of range: "
+ "aShiftState=%d, ArrayLength(mShiftState)=%d",
+ aShiftState, ArrayLength(mShiftStates));
+ NS_WARNING(warning.get());
+ }
+#endif
+
+ UniCharsAndModifiers result;
+ Modifiers modifiers = ShiftStateToModifiers(aShiftState);
+ if (IsDeadKey(aShiftState)) {
+ result.Append(mShiftStates[aShiftState].DeadKey.DeadChar, modifiers);
+ return result;
+ }
+
+ uint32_t index;
+ uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
+ for (index = 0;
+ index < len && mShiftStates[aShiftState].Normal.Chars[index]; index++) {
+ result.Append(mShiftStates[aShiftState].Normal.Chars[index], modifiers);
+ }
+ return result;
+}
+
+// static
+void
+VirtualKey::FillKbdState(PBYTE aKbdState,
+ const ShiftState aShiftState)
+{
+ NS_ASSERTION(aShiftState < 16, "aShiftState out of range");
+
+ if (aShiftState & STATE_SHIFT) {
+ aKbdState[VK_SHIFT] |= 0x80;
+ } else {
+ aKbdState[VK_SHIFT] &= ~0x80;
+ aKbdState[VK_LSHIFT] &= ~0x80;
+ aKbdState[VK_RSHIFT] &= ~0x80;
+ }
+
+ 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 = {};
+
+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)
+ , mIsDeadKey(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;
+ }
+
+ if (mMsg.message == WM_APPCOMMAND) {
+ InitWithAppCommand();
+ } else {
+ InitWithKeyChar();
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%08X, "
+ "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=0x%04X, "
+ "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, "
+ "mIsDeadKey=%s, mIsPrintableKey=%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(mIsDeadKey),
+ GetBoolName(mIsPrintableKey), GetBoolName(mCharMessageHasGone),
+ GetBoolName(mIsOverridingKeyboardLayout)));
+}
+
+void
+NativeKey::InitWithKeyChar()
+{
+ 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:
+ case MOZ_WM_KEYDOWN:
+ case MOZ_WM_KEYUP: {
+ // First, resolve the IME converted virtual keycode to its original
+ // keycode.
+ if (mMsg.wParam == VK_PROCESSKEY) {
+ mOriginalVirtualKeyCode =
+ static_cast<uint8_t>(::ImmGetVirtualKey(mMsg.hwnd));
+ } else {
+ 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;
+ }
+
+ if (!CanComputeVirtualKeyCodeFromScanCode()) {
+ // The right control key and the right alt key are extended keys.
+ // Therefore, we never get VK_RCONTRL and VK_RMENU for the result of
+ // MapVirtualKeyEx() on WinXP or WinServer2003.
+ //
+ // If VK_SHIFT, VK_CONTROL or VK_MENU key message is caused by well
+ // known scan code, we should decide it as Right key. Otherwise,
+ // decide it as Left key.
+ switch (mOriginalVirtualKeyCode) {
+ case VK_CONTROL:
+ mVirtualKeyCode =
+ mIsExtended && mScanCode == 0x1D ? VK_RCONTROL : VK_LCONTROL;
+ break;
+ case VK_MENU:
+ mVirtualKeyCode =
+ mIsExtended && mScanCode == 0x38 ? VK_RMENU : VK_LMENU;
+ break;
+ case VK_SHIFT:
+ // 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;
+ }
+
+ 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::InitWithKeyChar(), 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.
+
+ // We cannot compute the virtual key code from WM_CHAR message on WinXP
+ // if it's caused by an extended key.
+ if (!CanComputeVirtualKeyCodeFromScanCode()) {
+ break;
+ }
+ mVirtualKeyCode = mOriginalVirtualKeyCode =
+ ComputeVirtualKeyCodeFromScanCodeEx();
+ NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode");
+ break;
+ default:
+ MOZ_CRASH("Unsupported message");
+ }
+
+ if (!mVirtualKeyCode) {
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ }
+
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ mDOMKeyCode =
+ keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode);
+ // 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(mOriginalVirtualKeyCode);
+ 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::InitWithKeyChar(), removed char message, %s",
+ this, ToString(charMsg).get()));
+ NS_WARN_IF(charMsg.hwnd != mMsg.hwnd);
+ mFollowingCharMsgs.AppendElement(charMsg);
+ }
+ }
+
+ keyboardLayout->InitNativeKey(*this, mModKeyState);
+
+ mIsDeadKey =
+ (IsFollowedByDeadCharMessage() ||
+ keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState));
+ mIsPrintableKey =
+ mKeyNameIndex == KEY_NAME_INDEX_USE_STRING ||
+ KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
+
+ if (IsKeyDownMessage()) {
+ // Compute some strings which may be inputted by the key with various
+ // modifier state if this key event won't cause text input actually.
+ // They will be used for setting mAlternativeCharCodes in the callback
+ // method which will be called by TextEventDispatcher.
+ if (!IsFollowedByPrintableCharMessage()) {
+ ComputeInputtingStringWithKeyboardLayout();
+ }
+ // Remove odd char messages if there are.
+ RemoveFollowingOddCharMessages();
+ }
+}
+
+void
+NativeKey::InitCommittedCharsAndModifiersWithFollowingCharMessages(
+ const ModifierKeyState& aModKeyState)
+{
+ mCommittedCharsAndModifiers.Clear();
+ // This should cause inputting text in focused editor. However, it
+ // ignores keypress events whose altKey or ctrlKey is true.
+ // Therefore, we need to remove these modifier state here.
+ Modifiers modifiers = aModKeyState.GetModifiers();
+ if (IsFollowedByPrintableCharMessage()) {
+ modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ // NOTE: This method assumes that WM_CHAR and WM_SYSCHAR are never retrieved
+ // at same time.
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ // Ignore non-printable char messages.
+ if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ continue;
+ }
+ char16_t ch = static_cast<char16_t>(mFollowingCharMsgs[i].wParam);
+ mCommittedCharsAndModifiers.Append(ch, modifiers);
+ }
+}
+
+NativeKey::~NativeKey()
+{
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::~NativeKey(), destroyed", this));
+ if (mIsOverridingKeyboardLayout) {
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ keyboardLayout->RestoreLayout();
+ }
+ sLatestInstance = mLastInstance;
+}
+
+void
+NativeKey::InitWithAppCommand()
+{
+ if (GET_DEVICE_LPARAM(mMsg.lParam) != FAPPCOMMAND_KEY) {
+ return;
+ }
+
+ uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam);
+ switch (GET_APPCOMMAND_LPARAM(mMsg.lParam)) {
+
+#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX
+#define NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX(aAppCommand, aKeyNameIndex) \
+ case aAppCommand: \
+ mKeyNameIndex = aKeyNameIndex; \
+ break;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ mKeyNameIndex = KEY_NAME_INDEX_Unidentified;
+ }
+
+ // Guess the virtual keycode which caused this message.
+ switch (appCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_BACK;
+ break;
+ case APPCOMMAND_BROWSER_FORWARD:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FORWARD;
+ break;
+ case APPCOMMAND_BROWSER_REFRESH:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_REFRESH;
+ break;
+ case APPCOMMAND_BROWSER_STOP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_STOP;
+ break;
+ case APPCOMMAND_BROWSER_SEARCH:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_SEARCH;
+ break;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FAVORITES;
+ break;
+ case APPCOMMAND_BROWSER_HOME:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_HOME;
+ break;
+ case APPCOMMAND_VOLUME_MUTE:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_MUTE;
+ break;
+ case APPCOMMAND_VOLUME_DOWN:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_DOWN;
+ break;
+ case APPCOMMAND_VOLUME_UP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_UP;
+ break;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_NEXT_TRACK;
+ break;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PREV_TRACK;
+ break;
+ case APPCOMMAND_MEDIA_STOP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_STOP;
+ break;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PLAY_PAUSE;
+ break;
+ case APPCOMMAND_LAUNCH_MAIL:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MAIL;
+ break;
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MEDIA_SELECT;
+ break;
+ case APPCOMMAND_LAUNCH_APP1:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP1;
+ break;
+ case APPCOMMAND_LAUNCH_APP2:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP2;
+ break;
+ default:
+ return;
+ }
+
+ uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mVirtualKeyCode);
+ mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF);
+ uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8);
+ mIsExtended = (extended == 0xE0) || (extended == 0xE1);
+ mDOMKeyCode =
+ KeyboardLayout::GetInstance()->
+ ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode);
+ mCodeNameIndex =
+ KeyboardLayout::ConvertScanCodeToCodeNameIndex(
+ GetScanCodeWithExtendedFlag());
+}
+
+// static
+bool
+NativeKey::IsControlChar(char16_t aChar)
+{
+ static const char16_t U_SPACE = 0x20;
+ static const char16_t U_DELETE = 0x7F;
+ return aChar < U_SPACE || aChar == U_DELETE;
+}
+
+bool
+NativeKey::IsFollowedByDeadCharMessage() const
+{
+ if (mFollowingCharMsgs.IsEmpty()) {
+ return false;
+ }
+ return IsDeadCharMessage(mFollowingCharMsgs[0]);
+}
+
+bool
+NativeKey::IsFollowedByPrintableCharMessage() const
+{
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (IsPrintableCharMessage(mFollowingCharMsgs[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+NativeKey::IsFollowedByPrintableCharOrSysCharMessage() const
+{
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+NativeKey::IsReservedBySystem() const
+{
+ // Alt+Space key is handled by OS, we shouldn't touch it.
+ if (mModKeyState.IsAlt() && !mModKeyState.IsControl() &&
+ mVirtualKeyCode == VK_SPACE) {
+ return true;
+ }
+
+ // XXX How about Alt+F4? We receive WM_SYSKEYDOWN for F4 before closing the
+ // window. Although, we don't prevent to close the window but the key
+ // event shouldn't be exposed to the web.
+
+ return false;
+}
+
+bool
+NativeKey::IsIMEDoingKakuteiUndo() const
+{
+ // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG:
+ // ---------------------------------------------------------------------------
+ // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1)
+ // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK
+ // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0)
+ // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF)
+ // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1)
+ // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001)
+ // ---------------------------------------------------------------------------
+ // This doesn't match usual key message pattern such as:
+ // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP
+ // See following bugs for the detail.
+ // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese)
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English)
+ MSG startCompositionMsg, compositionMsg, charMsg;
+ return WinUtils::PeekMessage(&startCompositionMsg, mMsg.hwnd,
+ WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ WinUtils::PeekMessage(&compositionMsg, mMsg.hwnd, WM_IME_COMPOSITION,
+ WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) &&
+ WinUtils::PeekMessage(&charMsg, mMsg.hwnd, WM_CHAR, WM_CHAR,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ startCompositionMsg.wParam == 0x0 &&
+ startCompositionMsg.lParam == 0x0 &&
+ compositionMsg.wParam == 0x0 &&
+ compositionMsg.lParam == 0x1BF &&
+ charMsg.wParam == VK_BACK && charMsg.lParam == 0x1 &&
+ startCompositionMsg.time <= compositionMsg.time &&
+ compositionMsg.time <= charMsg.time;
+}
+
+void
+NativeKey::RemoveFollowingOddCharMessages()
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ // If the keydown message is synthesized for automated tests, there is
+ // nothing to do here.
+ if (mFakeCharMsgs) {
+ return;
+ }
+
+ // If there are some following char messages before another key message,
+ // there is nothing to do here.
+ if (!mFollowingCharMsgs.IsEmpty()) {
+ return;
+ }
+
+ // If the handling key isn't Backspace, there is nothing to do here.
+ if (mOriginalVirtualKeyCode != VK_BACK) {
+ return;
+ }
+
+ // If we don't see the odd message pattern, there is nothing to do here.
+ if (!IsIMEDoingKakuteiUndo()) {
+ return;
+ }
+
+ // Otherwise, we need to remove odd WM_CHAR messages for ATOK or WXG (both
+ // of them are Japanese IME).
+ MSG msg;
+ while (WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_CHAR, WM_CHAR,
+ PM_REMOVE | PM_NOYIELD)) {
+ if (msg.message != WM_CHAR) {
+ MOZ_RELEASE_ASSERT(msg.message == WM_NULL,
+ "Unexpected message was removed");
+ continue;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::RemoveFollowingOddCharMessages(), removed odd char "
+ "message, %s",
+ this, ToString(msg).get()));
+ mRemovedOddCharMsgs.AppendElement(msg);
+ }
+}
+
+UINT
+NativeKey::GetScanCodeWithExtendedFlag() const
+{
+ if (!mIsExtended) {
+ return mScanCode;
+ }
+ return (0xE000 | mScanCode);
+}
+
+uint32_t
+NativeKey::GetKeyLocation() const
+{
+ switch (mVirtualKeyCode) {
+ case VK_LSHIFT:
+ case VK_LCONTROL:
+ case VK_LMENU:
+ case VK_LWIN:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+
+ case VK_RSHIFT:
+ case VK_RCONTROL:
+ case VK_RMENU:
+ case VK_RWIN:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+
+ case VK_RETURN:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return !mIsExtended ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD :
+ nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_END:
+ case VK_DOWN:
+ case VK_NEXT:
+ case VK_LEFT:
+ case VK_CLEAR:
+ case VK_RIGHT:
+ case VK_HOME:
+ case VK_UP:
+ case VK_PRIOR:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return mIsExtended ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD :
+ nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ // NumLock key isn't included due to IE9's behavior.
+ case VK_NUMPAD0:
+ case VK_NUMPAD1:
+ case VK_NUMPAD2:
+ case VK_NUMPAD3:
+ case VK_NUMPAD4:
+ case VK_NUMPAD5:
+ case VK_NUMPAD6:
+ case VK_NUMPAD7:
+ case VK_NUMPAD8:
+ case VK_NUMPAD9:
+ case VK_DECIMAL:
+ case VK_DIVIDE:
+ case VK_MULTIPLY:
+ case VK_SUBTRACT:
+ case VK_ADD:
+ // Separator key of Brazilian keyboard or JIS keyboard for Mac
+ case VK_ABNT_C2:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU:
+ NS_WARNING("Failed to decide the key location?");
+
+ default:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ }
+}
+
+bool
+NativeKey::CanComputeVirtualKeyCodeFromScanCode() const
+{
+ // Vista or later supports ScanCodeEx.
+ if (IsVistaOrLater()) {
+ return true;
+ }
+ // Otherwise, MapVirtualKeyEx() can compute virtual keycode only with
+ // non-extended key.
+ return !mIsExtended;
+}
+
+uint8_t
+NativeKey::ComputeVirtualKeyCodeFromScanCode() const
+{
+ return static_cast<uint8_t>(
+ ::MapVirtualKeyEx(mScanCode, MAPVK_VSC_TO_VK, mKeyboardLayout));
+}
+
+uint8_t
+NativeKey::ComputeVirtualKeyCodeFromScanCodeEx() const
+{
+ // MapVirtualKeyEx() has been improved for supporting extended keys since
+ // Vista. When we call it for mapping a scancode of an extended key and
+ // a virtual keycode, we need to add 0xE000 to the scancode.
+ // On the other hand, neither WinXP nor WinServer2003 doesn't support 0xE000.
+ // Therefore, we have no way to get virtual keycode from scan code of
+ // extended keys.
+ if (NS_WARN_IF(!CanComputeVirtualKeyCodeFromScanCode())) {
+ return 0;
+ }
+ return static_cast<uint8_t>(
+ ::MapVirtualKeyEx(GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX,
+ mKeyboardLayout));
+}
+
+uint16_t
+NativeKey::ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const
+{
+ return static_cast<uint16_t>(
+ ::MapVirtualKeyEx(aVirtualKeyCode,
+ IsVistaOrLater() ? MAPVK_VK_TO_VSC_EX :
+ MAPVK_VK_TO_VSC,
+ mKeyboardLayout));
+}
+
+char16_t
+NativeKey::ComputeUnicharFromScanCode() const
+{
+ return static_cast<char16_t>(
+ ::MapVirtualKeyEx(ComputeVirtualKeyCodeFromScanCode(),
+ MAPVK_VK_TO_CHAR, mKeyboardLayout));
+}
+
+nsEventStatus
+NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG* aMsgSentToPlugin) const
+{
+ return InitKeyEvent(aKeyEvent, mModKeyState, aMsgSentToPlugin);
+}
+
+nsEventStatus
+NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const ModifierKeyState& aModKeyState,
+ const MSG* aMsgSentToPlugin) const
+{
+ if (mWidget->Destroyed()) {
+ MOZ_CRASH("NativeKey tries to dispatch a key event on destroyed widget");
+ }
+
+ LayoutDeviceIntPoint point(0, 0);
+ mWidget->InitEvent(aKeyEvent, &point);
+
+ switch (aKeyEvent.mMessage) {
+ case eKeyDown:
+ // If it was followed by a char message but it was consumed by somebody,
+ // we should mark it as consumed because somebody must have handled it
+ // and we should prevent to do "double action" for the key operation.
+ if (mCharMessageHasGone) {
+ aKeyEvent.PreventDefaultBeforeDispatch();
+ }
+ MOZ_FALLTHROUGH;
+ case eKeyDownOnPlugin:
+ aKeyEvent.mKeyCode = mDOMKeyCode;
+ // Unique id for this keydown event and its associated keypress.
+ sUniqueKeyEventId++;
+ aKeyEvent.mUniqueId = sUniqueKeyEventId;
+ break;
+ case eKeyUp:
+ case eKeyUpOnPlugin:
+ aKeyEvent.mKeyCode = mDOMKeyCode;
+ // Set defaultPrevented of the key event if the VK_MENU is not a system
+ // key release, so that the menu bar does not trigger. This helps avoid
+ // triggering the menu bar for ALT key accelerators used in assistive
+ // technologies such as Window-Eyes and ZoomText or for switching open
+ // state of IME.
+ if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) {
+ aKeyEvent.PreventDefaultBeforeDispatch();
+ }
+ break;
+ case eKeyPress:
+ MOZ_ASSERT(!mCharMessageHasGone,
+ "If following char message was consumed by somebody, "
+ "keydown event should be consumed above");
+ aKeyEvent.mUniqueId = sUniqueKeyEventId;
+ break;
+ default:
+ MOZ_CRASH("Invalid event message");
+ }
+
+ aKeyEvent.mIsRepeat = IsRepeat();
+ aKeyEvent.mKeyNameIndex = mKeyNameIndex;
+ if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ aKeyEvent.mKeyValue = mCommittedCharsAndModifiers.ToString();
+ }
+ aKeyEvent.mCodeNameIndex = mCodeNameIndex;
+ MOZ_ASSERT(mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ aKeyEvent.mLocation = GetKeyLocation();
+ aModKeyState.InitInputEvent(aKeyEvent);
+
+ if (aMsgSentToPlugin) {
+ MaybeInitPluginEventOfKeyEvent(aKeyEvent, *aMsgSentToPlugin);
+ }
+
+ KeyboardLayout::NotifyIdleServiceOfUserActivity();
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::InitKeyEvent(), initialized, aKeyEvent={ "
+ "mMessage=%s, mKeyNameIndex=%s, mKeyValue=\"%s\", mCodeNameIndex=%s, "
+ "mKeyCode=%s, mLocation=%s, mModifiers=%s, DefaultPrevented()=%s }",
+ this, ToChar(aKeyEvent.mMessage),
+ ToString(aKeyEvent.mKeyNameIndex).get(),
+ NS_ConvertUTF16toUTF8(aKeyEvent.mKeyValue).get(),
+ ToString(aKeyEvent.mCodeNameIndex).get(),
+ GetDOMKeyCodeName(aKeyEvent.mKeyCode).get(),
+ GetKeyLocationName(aKeyEvent.mLocation).get(),
+ GetModifiersName(aKeyEvent.mModifiers).get(),
+ GetBoolName(aKeyEvent.DefaultPrevented())));
+
+ return aKeyEvent.DefaultPrevented() ? nsEventStatus_eConsumeNoDefault :
+ nsEventStatus_eIgnore;
+}
+
+void
+NativeKey::MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG& aMsgSentToPlugin) const
+{
+ if (mWidget->GetInputContext().mIMEState.mEnabled != IMEState::PLUGIN) {
+ return;
+ }
+ NPEvent pluginEvent;
+ pluginEvent.event = aMsgSentToPlugin.message;
+ pluginEvent.wParam = aMsgSentToPlugin.wParam;
+ pluginEvent.lParam = aMsgSentToPlugin.lParam;
+ aKeyEvent.mPluginEvent.Copy(pluginEvent);
+}
+
+bool
+NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const
+{
+ nsCOMPtr<nsIAtom> command;
+ switch (aEventCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ command = nsGkAtoms::Back;
+ break;
+ case APPCOMMAND_BROWSER_FORWARD:
+ command = nsGkAtoms::Forward;
+ break;
+ case APPCOMMAND_BROWSER_REFRESH:
+ command = nsGkAtoms::Reload;
+ break;
+ case APPCOMMAND_BROWSER_STOP:
+ command = nsGkAtoms::Stop;
+ break;
+ case APPCOMMAND_BROWSER_SEARCH:
+ command = nsGkAtoms::Search;
+ break;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ command = nsGkAtoms::Bookmarks;
+ break;
+ case APPCOMMAND_BROWSER_HOME:
+ command = nsGkAtoms::Home;
+ break;
+ case APPCOMMAND_CLOSE:
+ command = nsGkAtoms::Close;
+ break;
+ case APPCOMMAND_FIND:
+ command = nsGkAtoms::Find;
+ break;
+ case APPCOMMAND_HELP:
+ command = nsGkAtoms::Help;
+ break;
+ case APPCOMMAND_NEW:
+ command = nsGkAtoms::New;
+ break;
+ case APPCOMMAND_OPEN:
+ command = nsGkAtoms::Open;
+ break;
+ case APPCOMMAND_PRINT:
+ command = nsGkAtoms::Print;
+ break;
+ case APPCOMMAND_SAVE:
+ command = nsGkAtoms::Save;
+ break;
+ case APPCOMMAND_FORWARD_MAIL:
+ command = nsGkAtoms::ForwardMail;
+ break;
+ case APPCOMMAND_REPLY_TO_MAIL:
+ command = nsGkAtoms::ReplyToMail;
+ break;
+ case APPCOMMAND_SEND_MAIL:
+ command = nsGkAtoms::SendMail;
+ break;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ command = nsGkAtoms::NextTrack;
+ break;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ command = nsGkAtoms::PreviousTrack;
+ break;
+ case APPCOMMAND_MEDIA_STOP:
+ command = nsGkAtoms::MediaStop;
+ break;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ command = nsGkAtoms::PlayPause;
+ break;
+ default:
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), doesn't dispatch command "
+ "event", this));
+ return false;
+ }
+ WidgetCommandEvent commandEvent(true, nsGkAtoms::onAppCommand,
+ command, mWidget);
+
+ mWidget->InitEvent(commandEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatching %s command event...",
+ this, nsAtomCString(command).get()));
+ bool ok = mWidget->DispatchWindowEvent(&commandEvent) || mWidget->Destroyed();
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatched command event, "
+ "result=%s, mWidget->Destroyed()=%s",
+ this, GetBoolName(ok), GetBoolName(mWidget->Destroyed())));
+ return ok;
+}
+
+bool
+NativeKey::HandleAppCommandMessage() const
+{
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleAppCommandMessage(), WARNING, not handled due to "
+ "destroyed the widget", this));
+ return false;
+ }
+
+ // NOTE: Typical behavior of WM_APPCOMMAND caused by key is, WM_APPCOMMAND
+ // message is _sent_ first. Then, the DefaultWndProc will _post_
+ // WM_KEYDOWN message and WM_KEYUP message if the keycode for the
+ // command is available (i.e., mVirtualKeyCode is not 0).
+
+ // NOTE: IntelliType (Microsoft's keyboard utility software) always consumes
+ // WM_KEYDOWN and WM_KEYUP.
+
+ // Let's dispatch keydown message before our chrome handles the command
+ // when the message is caused by a keypress. This behavior makes handling
+ // WM_APPCOMMAND be a default action of the keydown event. This means that
+ // web applications can handle multimedia keys and prevent our default action.
+ // This allow web applications to provide better UX for multimedia keyboard
+ // users.
+ bool dispatchKeyEvent = (GET_DEVICE_LPARAM(mMsg.lParam) == FAPPCOMMAND_KEY);
+ if (dispatchKeyEvent) {
+ // If a plug-in window has focus but it didn't consume the message, our
+ // window receive WM_APPCOMMAND message. In this case, we shouldn't
+ // dispatch KeyboardEvents because an event handler may access the
+ // plug-in process synchronously.
+ dispatchKeyEvent =
+ WinUtils::IsOurProcessWindow(reinterpret_cast<HWND>(mMsg.wParam));
+ }
+
+ bool consumed = false;
+
+ if (dispatchKeyEvent) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keydown "
+ "event...", this));
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), tries to dispatch "
+ "keydown event...", this));
+ // NOTE: If the keydown event is consumed by web contents, we shouldn't
+ // continue to handle the command.
+ if (!mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
+ const_cast<NativeKey*>(this))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event isn't "
+ "dispatched", this));
+ // If keyboard event wasn't fired, there must be composition.
+ // So, we don't need to dispatch a command event.
+ return true;
+ }
+ consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event was "
+ "dispatched, consumed=%s",
+ this, GetBoolName(consumed)));
+ sDispatchedKeyOfAppCommand = mVirtualKeyCode;
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event caused "
+ "destroying the widget", this));
+ return true;
+ }
+ }
+
+ // Dispatch a command event or a content command event if the command is
+ // supported.
+ if (!consumed) {
+ uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam);
+ EventMessage contentCommandMessage = eVoidEvent;
+ switch (appCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ case APPCOMMAND_BROWSER_FORWARD:
+ case APPCOMMAND_BROWSER_REFRESH:
+ case APPCOMMAND_BROWSER_STOP:
+ case APPCOMMAND_BROWSER_SEARCH:
+ case APPCOMMAND_BROWSER_FAVORITES:
+ case APPCOMMAND_BROWSER_HOME:
+ case APPCOMMAND_CLOSE:
+ case APPCOMMAND_FIND:
+ case APPCOMMAND_HELP:
+ case APPCOMMAND_NEW:
+ case APPCOMMAND_OPEN:
+ case APPCOMMAND_PRINT:
+ case APPCOMMAND_SAVE:
+ case APPCOMMAND_FORWARD_MAIL:
+ case APPCOMMAND_REPLY_TO_MAIL:
+ case APPCOMMAND_SEND_MAIL:
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ case APPCOMMAND_MEDIA_STOP:
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ // We shouldn't consume the message always because if we don't handle
+ // the message, the sender (typically, utility of keyboard or mouse)
+ // may send other key messages which indicate well known shortcut key.
+ consumed = DispatchCommandEvent(appCommand);
+ break;
+
+ // Use content command for following commands:
+ case APPCOMMAND_COPY:
+ contentCommandMessage = eContentCommandCopy;
+ break;
+ case APPCOMMAND_CUT:
+ contentCommandMessage = eContentCommandCut;
+ break;
+ case APPCOMMAND_PASTE:
+ contentCommandMessage = eContentCommandPaste;
+ break;
+ case APPCOMMAND_REDO:
+ contentCommandMessage = eContentCommandRedo;
+ break;
+ case APPCOMMAND_UNDO:
+ contentCommandMessage = eContentCommandUndo;
+ break;
+ }
+
+ if (contentCommandMessage) {
+ MOZ_ASSERT(!mWidget->Destroyed());
+ WidgetContentCommandEvent contentCommandEvent(true, contentCommandMessage,
+ mWidget);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatching %s event...",
+ this, ToChar(contentCommandMessage)));
+ mWidget->DispatchWindowEvent(&contentCommandEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched %s event",
+ this, ToChar(contentCommandMessage)));
+ consumed = true;
+
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), %s event caused "
+ "destroying the widget",
+ this, ToChar(contentCommandMessage)));
+ return true;
+ }
+ } else {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), doesn't dispatch content "
+ "command event", this));
+ }
+ }
+
+ // Dispatch a keyup event if the command is caused by pressing a key and
+ // the key isn't mapped to a virtual keycode.
+ if (dispatchKeyEvent && !mVirtualKeyCode) {
+ MOZ_ASSERT(!mWidget->Destroyed());
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keyup "
+ "event...", this));
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatching keyup event...",
+ this));
+ // NOTE: Ignore if the keyup event is consumed because keyup event
+ // represents just a physical key event state change.
+ mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status,
+ const_cast<NativeKey*>(this));
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched keyup event",
+ this));
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), %s event caused "
+ "destroying the widget", this));
+ return true;
+ }
+ }
+
+ return consumed;
+}
+
+bool
+NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ if (sDispatchedKeyOfAppCommand &&
+ sDispatchedKeyOfAppCommand == mOriginalVirtualKeyCode) {
+ // The multimedia key event has already been dispatch from
+ // HandleAppCommandMessage().
+ sDispatchedKeyOfAppCommand = 0;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown "
+ "event due to already dispatched from HandleAppCommandMessage(), ",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return true;
+ }
+
+ if (IsReservedBySystem()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown "
+ "event because the key combination is reserved by the system", this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleKeyDownMessage(), WARNING, not handled due to "
+ "destroyed the widget", this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return false;
+ }
+
+ bool defaultPrevented = false;
+ if (mFakeCharMsgs || IsKeyMessageOnPlugin() ||
+ !RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleKeyDownMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+
+ bool isIMEEnabled = WinUtils::IsIMEEnabled(mWidget->GetInputContext());
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyDownMessage(), initializing keydown "
+ "event...", this));
+
+ EventMessage keyDownMessage =
+ IsKeyMessageOnPlugin() ? eKeyDownOnPlugin : eKeyDown;
+ WidgetKeyboardEvent keydownEvent(true, keyDownMessage, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), dispatching keydown event...",
+ this));
+ bool dispatched =
+ mDispatcher->DispatchKeyboardEvent(keyDownMessage, keydownEvent, status,
+ const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (!dispatched) {
+ // If the keydown event wasn't fired, there must be composition.
+ // we don't need to do anything anymore.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress "
+ "event(s) because keydown event isn't dispatched actually", this));
+ return false;
+ }
+ defaultPrevented = status == nsEventStatus_eConsumeNoDefault;
+
+ // We don't need to handle key messages on plugin for eKeyPress since
+ // eKeyDownOnPlugin is handled as both eKeyDown and eKeyPress.
+ if (IsKeyMessageOnPlugin()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress "
+ "event(s) because it's a keydown message on windowed plugin, "
+ "defaultPrevented=%s",
+ this, GetBoolName(defaultPrevented)));
+ return defaultPrevented;
+ }
+
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), keydown event caused "
+ "destroying the widget", this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), dispatched keydown event, "
+ "dispatched=%s, defaultPrevented=%s",
+ this, GetBoolName(dispatched), GetBoolName(defaultPrevented)));
+
+ // If IMC wasn't associated to the window but is associated it now (i.e.,
+ // focus is moved from a non-editable editor to an editor by keydown
+ // event handler), WM_CHAR and WM_SYSCHAR shouldn't cause first character
+ // inputting if IME is opened. But then, we should redirect the native
+ // keydown message to IME.
+ // However, note that if focus has been already moved to another
+ // application, we shouldn't redirect the message to it because the keydown
+ // message is processed by us, so, nobody shouldn't process it.
+ HWND focusedWnd = ::GetFocus();
+ if (!defaultPrevented && !mFakeCharMsgs && !IsKeyMessageOnPlugin() &&
+ focusedWnd && !mWidget->PluginHasFocus() && !isIMEEnabled &&
+ WinUtils::IsIMEEnabled(mWidget->GetInputContext())) {
+ RedirectedKeyDownMessageManager::RemoveNextCharMessage(focusedWnd);
+
+ INPUT keyinput;
+ keyinput.type = INPUT_KEYBOARD;
+ keyinput.ki.wVk = mOriginalVirtualKeyCode;
+ keyinput.ki.wScan = mScanCode;
+ keyinput.ki.dwFlags = KEYEVENTF_SCANCODE;
+ if (mIsExtended) {
+ keyinput.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+ }
+ keyinput.ki.time = 0;
+ keyinput.ki.dwExtraInfo = 0;
+
+ RedirectedKeyDownMessageManager::WillRedirect(mMsg, defaultPrevented);
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), redirecting %s...",
+ this, ToString(mMsg).get()));
+
+ ::SendInput(1, &keyinput, sizeof(keyinput));
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), redirected %s",
+ this, ToString(mMsg).get()));
+
+ // Return here. We shouldn't dispatch keypress event for this WM_KEYDOWN.
+ // If it's needed, it will be dispatched after next (redirected)
+ // WM_KEYDOWN.
+ return true;
+ }
+ } else {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), received a redirected %s",
+ this, ToString(mMsg).get()));
+
+ defaultPrevented = RedirectedKeyDownMessageManager::DefaultPrevented();
+ // If this is redirected keydown message, we have dispatched the keydown
+ // event already.
+ if (aEventDispatched) {
+ *aEventDispatched = true;
+ }
+ }
+
+ RedirectedKeyDownMessageManager::Forget();
+
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ // If the key was processed by IME and didn't cause WM_(SYS)CHAR messages, we
+ // shouldn't dispatch keypress event.
+ if (mOriginalVirtualKeyCode == VK_PROCESSKEY &&
+ !IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event because the key was already handled by IME, defaultPrevented=%s",
+ this, GetBoolName(defaultPrevented)));
+ return defaultPrevented;
+ }
+
+ if (defaultPrevented) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event because preceding keydown event was consumed",
+ this));
+ MaybeDispatchPluginEventsForRemovedCharMessages();
+ return true;
+ }
+
+ MOZ_ASSERT(!mCharMessageHasGone,
+ "If following char message was consumed by somebody, "
+ "keydown event should have been consumed before dispatch");
+
+ // If mCommittedCharsAndModifiers was initialized with following char
+ // messages, we should dispatch keypress events with its information.
+ if (IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events with retrieved char messages...", this));
+ return DispatchKeyPressEventsWithRetrievedCharMessages();
+ }
+
+ // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a
+ // keypress for almost all keys
+ if (NeedsToHandleWithoutFollowingCharMessages()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events...", this));
+ return (MaybeDispatchPluginEventsForRemovedCharMessages() ||
+ DispatchKeyPressEventsWithoutCharMessage());
+ }
+
+ // If WM_KEYDOWN of VK_PACKET isn't followed by WM_CHAR, we don't need to
+ // dispatch keypress events.
+ if (mVirtualKeyCode == VK_PACKET) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress event "
+ "because the key is VK_PACKET and there are no char messages",
+ this));
+ return false;
+ }
+
+ if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
+ !mModKeyState.IsWin() && mIsPrintableKey) {
+ // If this is simple KeyDown event but next message is not WM_CHAR,
+ // this event may not input text, so we should ignore this event.
+ // See bug 314130.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress event "
+ "because the key event is simple printable key's event but not followed "
+ "by char messages", this));
+ return false;
+ }
+
+ if (mIsDeadKey) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress event "
+ "because the key is a dead key and not followed by char messages",
+ this));
+ return false;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events due to no following char messages...", this));
+ return DispatchKeyPressEventsWithoutCharMessage();
+}
+
+bool
+NativeKey::HandleCharMessage(bool* aEventDispatched) const
+{
+ MOZ_ASSERT(IsCharOrSysCharMessage(mMsg));
+ return HandleCharMessage(mMsg, aEventDispatched);
+}
+
+bool
+NativeKey::HandleCharMessage(const MSG& aCharMsg,
+ bool* aEventDispatched) const
+{
+ MOZ_ASSERT(IsKeyDownMessage() || IsCharOrSysCharMessage(mMsg));
+ MOZ_ASSERT(IsCharOrSysCharMessage(aCharMsg.message));
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ if (IsCharOrSysCharMessage(mMsg) && IsAnotherInstanceRemovingCharMessage()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleCharMessage(), WARNING, does nothing because "
+ "the message should be handled in another instance removing this "
+ "message", this));
+ // Consume this for now because it will be handled by another instance.
+ return true;
+ }
+
+ // If the key combinations is reserved by the system, we shouldn't dispatch
+ // eKeyPress event for it and passes the message to next wndproc.
+ if (IsReservedBySystem()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress "
+ "event because the key combination is reserved by the system", this));
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleCharMessage(), WARNING, not handled due to "
+ "destroyed the widget", this));
+ return false;
+ }
+
+ // When a control key is inputted by a key, it should be handled without
+ // WM_*CHAR messages at receiving WM_*KEYDOWN message. So, when we receive
+ // WM_*CHAR message directly, we see a control character here.
+ if (IsControlCharMessage(aCharMsg)) {
+ // In this case, we don't need to dispatch eKeyPress event because:
+ // 1. We're the only browser which dispatches "keypress" event for
+ // non-printable characters (Although, both Chrome and Edge dispatch
+ // "keypress" event for some keys accidentally. For example, "IntlRo"
+ // key with Ctrl of Japanese keyboard layout).
+ // 2. Currently, we may handle shortcut keys with "keydown" event if
+ // it's reserved or something. So, we shouldn't dispatch "keypress"
+ // event without it.
+ // Note that this does NOT mean we stop dispatching eKeyPress event for
+ // key presses causes a control character when Ctrl is pressed. In such
+ // case, DispatchKeyPressEventsWithoutCharMessage() dispatches eKeyPress
+ // instead of this method.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress "
+ "event because received a control character input without WM_KEYDOWN",
+ this));
+ return false;
+ }
+
+ // XXXmnakano I think that if mMsg is WM_CHAR, i.e., it comes without
+ // preceding WM_KEYDOWN, we should should dispatch composition
+ // events instead of eKeyPress because they are not caused by
+ // actual keyboard operation.
+
+ // First, handle normal text input or non-printable key case here.
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleCharMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleCharMessage(), initializing keypress "
+ "event...", this));
+
+ ModifierKeyState modKeyState(mModKeyState);
+ // When AltGr is pressed, both Alt and Ctrl are active. However, when they
+ // are active, EditorBase won't treat the keypress event as inputting a
+ // character. Therefore, when AltGr is pressed and the key tries to input
+ // a character, let's set them to false.
+ if (modKeyState.IsAltGr() && IsPrintableCharMessage(aCharMsg)) {
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState, &aCharMsg);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), dispatching keypress event...",
+ this));
+ bool dispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), keypress event caused "
+ "destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), dispatched keypress event, "
+ "dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool
+NativeKey::HandleKeyUpMessage(bool* aEventDispatched) const
+{
+ MOZ_ASSERT(IsKeyUpMessage());
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ // If the key combinations is reserved by the system, we shouldn't dispatch
+ // eKeyUp event for it and passes the message to next wndproc.
+ if (IsReservedBySystem()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), doesn't dispatch keyup "
+ "event because the key combination is reserved by the system", this));
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::HandleKeyUpMessage(), WARNING, not handled due to "
+ "destroyed the widget", this));
+ return false;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleKeyUpMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyUpMessage(), initializing keyup event...",
+ this));
+ EventMessage keyUpMessage = IsKeyMessageOnPlugin() ? eKeyUpOnPlugin : eKeyUp;
+ WidgetKeyboardEvent keyupEvent(true, keyUpMessage, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), dispatching keyup event...",
+ this));
+ bool dispatched =
+ mDispatcher->DispatchKeyboardEvent(keyUpMessage, keyupEvent, status,
+ const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), keyup event caused "
+ "destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), dispatched keyup event, "
+ "dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool
+NativeKey::NeedsToHandleWithoutFollowingCharMessages() const
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ // We cannot know following char messages of key messages in a plugin
+ // process. So, let's compute the character to be inputted with every
+ // printable key should be computed with the keyboard layout.
+ if (IsKeyMessageOnPlugin()) {
+ return true;
+ }
+
+ // If the key combination is reserved by the system, the caller shouldn't
+ // do anything with following WM_*CHAR messages. So, let's return true here.
+ if (IsReservedBySystem()) {
+ return true;
+ }
+
+ // If the keydown message is generated for inputting some Unicode characters
+ // via SendInput() API, we need to handle it only with WM_*CHAR messages.
+ if (mVirtualKeyCode == VK_PACKET) {
+ return false;
+ }
+
+ // If following char message is for a control character, it should be handled
+ // without WM_CHAR message. This is typically Ctrl + [a-z].
+ if (mFollowingCharMsgs.Length() == 1 &&
+ IsControlCharMessage(mFollowingCharMsgs[0])) {
+ return true;
+ }
+
+ // If keydown message is followed by WM_CHAR or WM_SYSCHAR whose wParam isn't
+ // a control character, we should dispatch keypress event with the char
+ // message even with any modifier state.
+ if (IsFollowedByPrintableCharOrSysCharMessage()) {
+ return false;
+ }
+
+ // If any modifier keys which may cause printable keys becoming non-printable
+ // are not pressed, we don't need special handling for the key.
+ if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
+ !mModKeyState.IsWin()) {
+ return false;
+ }
+
+ // If the key event causes dead key event, we don't need to dispatch keypress
+ // event.
+ if (mIsDeadKey && mCommittedCharsAndModifiers.IsEmpty()) {
+ return false;
+ }
+
+ // Even if the key is a printable key, it might cause non-printable character
+ // input with modifier key(s).
+ return mIsPrintableKey;
+}
+
+#ifdef MOZ_CRASHREPORTER
+
+static nsCString
+GetResultOfInSendMessageEx()
+{
+ DWORD ret = ::InSendMessageEx(nullptr);
+ if (!ret) {
+ return NS_LITERAL_CSTRING("ISMEX_NOSEND");
+ }
+ nsAutoCString result;
+ if (ret & ISMEX_CALLBACK) {
+ result = "ISMEX_CALLBACK";
+ }
+ if (ret & ISMEX_NOTIFY) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_NOTIFY";
+ }
+ if (ret & ISMEX_REPLIED) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_REPLIED";
+ }
+ if (ret & ISMEX_SEND) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_SEND";
+ }
+ return result;
+}
+
+#endif // #ifdef MOZ_CRASHREPORTER
+
+bool
+NativeKey::MayBeSameCharMessage(const MSG& aCharMsg1,
+ const MSG& aCharMsg2) const
+{
+ // NOTE: Although, we don't know when this case occurs, the scan code value
+ // in lParam may be changed from 0 to something. The changed value
+ // is different from the scan code of handling keydown message.
+ static const LPARAM kScanCodeMask = 0x00FF0000;
+ return
+ aCharMsg1.message == aCharMsg2.message &&
+ aCharMsg1.wParam == aCharMsg2.wParam &&
+ (aCharMsg1.lParam & ~kScanCodeMask) == (aCharMsg2.lParam & ~kScanCodeMask);
+}
+
+bool
+NativeKey::IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1,
+ const MSG& aKeyOrCharMsg2) const
+{
+ if (NS_WARN_IF(aKeyOrCharMsg1.message < WM_KEYFIRST) ||
+ NS_WARN_IF(aKeyOrCharMsg1.message > WM_KEYLAST) ||
+ NS_WARN_IF(aKeyOrCharMsg2.message < WM_KEYFIRST) ||
+ NS_WARN_IF(aKeyOrCharMsg2.message > WM_KEYLAST)) {
+ return false;
+ }
+ return WinUtils::GetScanCode(aKeyOrCharMsg1.lParam) ==
+ WinUtils::GetScanCode(aKeyOrCharMsg2.lParam) &&
+ WinUtils::IsExtendedScanCode(aKeyOrCharMsg1.lParam) ==
+ WinUtils::IsExtendedScanCode(aKeyOrCharMsg2.lParam);
+}
+
+bool
+NativeKey::GetFollowingCharMessage(MSG& aCharMsg)
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!IsKeyMessageOnPlugin());
+
+ aCharMsg.message = WM_NULL;
+
+ if (mFakeCharMsgs) {
+ for (size_t i = 0; i < mFakeCharMsgs->Length(); i++) {
+ FakeCharMsg& fakeCharMsg = mFakeCharMsgs->ElementAt(i);
+ if (fakeCharMsg.mConsumed) {
+ continue;
+ }
+ MSG charMsg = fakeCharMsg.GetCharMsg(mMsg.hwnd);
+ fakeCharMsg.mConsumed = true;
+ if (!IsCharMessage(charMsg)) {
+ return false;
+ }
+ aCharMsg = charMsg;
+ return true;
+ }
+ return false;
+ }
+
+ // If next key message is not char message, we should give up to find a
+ // related char message for the handling keydown event for now.
+ // Note that it's possible other applications may send other key message
+ // after we call TranslateMessage(). That may cause PeekMessage() failing
+ // to get char message for the handling keydown message.
+ MSG nextKeyMsg;
+ if (!WinUtils::PeekMessage(&nextKeyMsg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD) ||
+ !IsCharMessage(nextKeyMsg)) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose,
+ ("%p NativeKey::GetFollowingCharMessage(), there are no char messages",
+ this));
+ return false;
+ }
+ const MSG kFoundCharMsg = nextKeyMsg;
+
+ AutoRestore<MSG> saveLastRemovingMsg(mRemovingMsg);
+ mRemovingMsg = nextKeyMsg;
+
+ mReceivedMsg = sEmptyMSG;
+ AutoRestore<MSG> ensureToClearRecivedMsg(mReceivedMsg);
+
+ // On Metrofox, PeekMessage() sometimes returns WM_NULL even if we specify
+ // the message range. So, if it returns WM_NULL, we should retry to get
+ // the following char message it was found above.
+ for (uint32_t i = 0; i < 50; i++) {
+ MSG removedMsg, nextKeyMsgInAllWindows;
+ bool doCrash = false;
+ if (!WinUtils::PeekMessage(&removedMsg, mMsg.hwnd,
+ nextKeyMsg.message, nextKeyMsg.message,
+ PM_REMOVE | PM_NOYIELD)) {
+ // We meets unexpected case. We should collect the message queue state
+ // and crash for reporting the bug.
+ doCrash = true;
+
+ // If another instance was created for the removing message during trying
+ // to remove a char message, the instance didn't handle it for preventing
+ // recursive handling. So, let's handle it in this instance.
+ if (!IsEmptyMSG(mReceivedMsg)) {
+ // If focus is moved to different window, we shouldn't handle it on
+ // the widget. Let's discard it for now.
+ if (mReceivedMsg.hwnd != nextKeyMsg.hwnd) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a "
+ "char message during removing it from the queue, but it's for "
+ "different window, mReceivedMsg=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ // There might still exist char messages, the loop of calling
+ // this method should be continued.
+ aCharMsg.message = WM_NULL;
+ return true;
+ }
+ // Even if the received message is different from what we tried to
+ // remove from the queue, let's take the received message as a part of
+ // the result of this key sequence.
+ if (mReceivedMsg.message != nextKeyMsg.message ||
+ mReceivedMsg.wParam != nextKeyMsg.wParam ||
+ mReceivedMsg.lParam != nextKeyMsg.lParam) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a "
+ "char message during removing it from the queue, but it's "
+ "differnt from what trying to remove from the queue, "
+ "aCharMsg=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ } else {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose,
+ ("%p NativeKey::GetFollowingCharMessage(), succeeded to retrieve "
+ "next char message via another instance, aCharMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ }
+ aCharMsg = mReceivedMsg;
+ return true;
+ }
+
+ // The char message is redirected to different thread's window by focus
+ // move or something or just cancelled by external application.
+ if (!WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0,
+ WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD)) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but it's already gone from all message "
+ "queues, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ return true; // XXX should return false in this case
+ }
+ // The next key message is redirected to different window created by our
+ // thread, we should do nothing because we must not have focus.
+ if (nextKeyMsgInAllWindows.hwnd != mMsg.hwnd) {
+ aCharMsg = nextKeyMsgInAllWindows;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but found in another message queue, "
+ "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ return true;
+ }
+ // If next key message becomes non-char message, this key operation
+ // may have already been consumed or canceled.
+ if (!IsCharMessage(nextKeyMsgInAllWindows)) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message and next key message becomes non-char "
+ "message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ // If next key message is still a char message but different key message,
+ // we should treat current key operation is consumed or canceled and
+ // next char message should be handled as an orphan char message later.
+ if (!IsSamePhysicalKeyMessage(nextKeyMsgInAllWindows, kFoundCharMsg)) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message and next key message becomes differnt key's "
+ "char message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ // If next key message is still a char message but the message is changed,
+ // we should retry to remove the new message with PeekMessage() again.
+ if (nextKeyMsgInAllWindows.message != nextKeyMsg.message) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message due to message change, let's retry to "
+ "remove the message with newly found char message, ",
+ "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ nextKeyMsg = nextKeyMsgInAllWindows;
+ continue;
+ }
+ // If there is still existing a char message caused by same physical key
+ // in the queue but PeekMessage(PM_REMOVE) failed to remove it from the
+ // queue, it might be possible that the odd keyboard layout or utility
+ // hooks only PeekMessage(PM_NOREMOVE) and GetMessage(). So, let's try
+ // remove the char message with GetMessage() again.
+ // FYI: The wParam might be different from the found message, but it's
+ // okay because we assume that odd keyboard layouts return actual
+ // inputting character at removing the char message.
+ if (WinUtils::GetMessage(&removedMsg, mMsg.hwnd,
+ nextKeyMsg.message, nextKeyMsg.message)) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but succeeded with GetMessage(), "
+ "removedMsg=%s, kFoundCharMsg=%s",
+ this, ToString(removedMsg).get(), ToString(kFoundCharMsg).get()));
+ // Cancel to crash, but we need to check the removed message value.
+ doCrash = false;
+ }
+ // If we've already removed some WM_NULL messages from the queue and
+ // the found message has already gone from the queue, let's treat the key
+ // as inputting no characters and already consumed.
+ else if (i > 0) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but removed %d WM_NULL messages",
+ this, i));
+ // If the key is a printable key or a control key but tried to input
+ // a character, mark mCharMessageHasGone true for handling the keydown
+ // event as inputting empty string.
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, lost target "
+ "message to remove, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+ }
+
+ if (doCrash) {
+#ifdef MOZ_CRASHREPORTER
+ nsPrintfCString info("\nPeekMessage() failed to remove char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nWM_NULL has been removed: %d, "
+ "\nNext key message in all windows: %s, "
+ "time=%d, ",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().get(),
+ ToString(mMsg).get(),
+ GetResultOfInSendMessageEx().get(),
+ ToString(kFoundCharMsg).get(), i,
+ ToString(nextKeyMsgInAllWindows).get(),
+ nextKeyMsgInAllWindows.time);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ MSG nextMsg;
+ if (WinUtils::PeekMessage(&nextMsg, 0, 0, 0,
+ PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info("\nNext message in all windows: %s, time=%d",
+ ToString(nextMsg).get(), nextMsg.time);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ NS_LITERAL_CSTRING("\nThere is no message in any window"));
+ }
+#endif // #ifdef MOZ_CRASHREPORTER
+ MOZ_CRASH("We lost the following char message");
+ }
+
+ // We're still not sure why ::PeekMessage() may return WM_NULL even with
+ // its first message and its last message are same message. However,
+ // at developing Metrofox, we met this case even with usual keyboard
+ // layouts. So, it might be possible in desktop application or it really
+ // occurs with some odd keyboard layouts which perhaps hook API.
+ if (removedMsg.message == WM_NULL) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, instead, removed WM_NULL message, ",
+ "removedMsg=%s",
+ this, ToString(removedMsg).get()));
+ // Check if there is the message which we're trying to remove.
+ MSG newNextKeyMsg;
+ if (!WinUtils::PeekMessage(&newNextKeyMsg, mMsg.hwnd,
+ WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD)) {
+ // If there is no key message, we should mark this keydown as consumed
+ // because the key operation may have already been handled or canceled.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message because it's gone during removing it from "
+ "the queue, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ if (!IsCharMessage(newNextKeyMsg)) {
+ // If next key message becomes a non-char message, we should mark this
+ // keydown as consumed because the key operation may have already been
+ // handled or canceled.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message because it's gone during removing it from "
+ "the queue, nextKeyMsg=%s, newNextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(newNextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::GetFollowingCharMessage(), there is the message "
+ "which is being tried to be removed from the queue, trying again...",
+ this));
+ continue;
+ }
+
+ // Typically, this case occurs with WM_DEADCHAR. If the removed message's
+ // wParam becomes 0, that means that the key event shouldn't cause text
+ // input. So, let's ignore the strange char message.
+ if (removedMsg.message == nextKeyMsg.message && !removedMsg.wParam) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message's wParam is 0, "
+ "removedMsg=%s",
+ this, ToString(removedMsg).get()));
+ return false;
+ }
+
+ // This is normal case.
+ if (MayBeSameCharMessage(removedMsg, nextKeyMsg)) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Verbose,
+ ("%p NativeKey::GetFollowingCharMessage(), succeeded to retrieve "
+ "next char message, aCharMsg=%s",
+ this, ToString(aCharMsg).get()));
+ return true;
+ }
+
+ // Even if removed message is different char message from the found char
+ // message, when the scan code is same, we can assume that the message
+ // is overwritten by somebody who hooks API. See bug 1336028 comment 0 for
+ // the possible scenarios.
+ if (IsCharMessage(removedMsg) &&
+ IsSamePhysicalKeyMessage(removedMsg, nextKeyMsg)) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message was changed from "
+ "the found message except their scancode, aCharMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ return true;
+ }
+
+ // When found message's wParam is 0 and its scancode is 0xFF, we may remove
+ // usual char message actually. In such case, we should use the removed
+ // char message.
+ if (IsCharMessage(removedMsg) && !nextKeyMsg.wParam &&
+ WinUtils::GetScanCode(nextKeyMsg.lParam) == 0xFF) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message was changed from "
+ "the found message but the found message was odd, so, ignoring the "
+ "odd found message and respecting the removed message, aCharMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ return true;
+ }
+
+ // NOTE: Although, we don't know when this case occurs, the scan code value
+ // in lParam may be changed from 0 to something. The changed value
+ // is different from the scan code of handling keydown message.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed message "
+ "is really different from what we have already found, removedMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(removedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+#ifdef MOZ_CRASHREPORTER
+ nsPrintfCString info("\nPeekMessage() removed unexpcted char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nRemoved message: %s, ",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().get(),
+ ToString(mMsg).get(),
+ GetResultOfInSendMessageEx().get(),
+ ToString(kFoundCharMsg).get(),
+ ToString(removedMsg).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ // What's the next key message?
+ MSG nextKeyMsgAfter;
+ if (WinUtils::PeekMessage(&nextKeyMsgAfter, mMsg.hwnd,
+ WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info("\nNext key message after unexpected char message "
+ "removed: %s, ",
+ ToString(nextKeyMsgAfter).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ NS_LITERAL_CSTRING("\nThere is no key message after unexpected char "
+ "message removed, "));
+ }
+ // Another window has a key message?
+ if (WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0,
+ WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info("\nNext key message in all windows: %s.",
+ ToString(nextKeyMsgInAllWindows).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ NS_LITERAL_CSTRING("\nThere is no key message in any windows."));
+ }
+#endif // #ifdef MOZ_CRASHREPORTER
+ MOZ_CRASH("PeekMessage() removed unexpected message");
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed messages "
+ "are all WM_NULL, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+#ifdef MOZ_CRASHREPORTER
+ nsPrintfCString info("\nWe lost following char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, \n"
+ "Found message: %s, removed a lot of WM_NULL",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().get(),
+ ToString(mMsg).get(),
+ GetResultOfInSendMessageEx().get(),
+ ToString(kFoundCharMsg).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+#endif // #ifdef MOZ_CRASHREPORTER
+ MOZ_CRASH("We lost the following char message");
+ return false;
+}
+
+bool
+NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages() const
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!IsKeyMessageOnPlugin());
+
+ for (size_t i = 0;
+ i < mFollowingCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent();
+ ++i) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatching %uth plugin event for %s...",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ MOZ_RELEASE_ASSERT(!mWidget->Destroyed(),
+ "NativeKey tries to dispatch a plugin event on destroyed widget");
+ mWidget->DispatchPluginEvent(mFollowingCharMsgs[i]);
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "%uth plugin event caused %s",
+ this, i + 1, mWidget->Destroyed() ? "destroying the widget" :
+ "focus change"));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatched %uth plugin event",
+ this, i + 1));
+ }
+
+ // Dispatch odd char messages which are caused by ATOK or WXG (both of them
+ // are Japanese IME) and removed by RemoveFollowingOddCharMessages().
+ for (size_t i = 0;
+ i < mRemovedOddCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent();
+ ++i) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatching %uth plugin event for odd char message, %s...",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ MOZ_RELEASE_ASSERT(!mWidget->Destroyed(),
+ "NativeKey tries to dispatch a plugin event on destroyed widget");
+ mWidget->DispatchPluginEvent(mRemovedOddCharMsgs[i]);
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "%uth plugin event for odd char message caused %s",
+ this, i + 1, mWidget->Destroyed() ? "destroying the widget" :
+ "focus change"));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatched %uth plugin event for odd char message",
+ this, i + 1));
+ }
+
+ return false;
+}
+
+void
+NativeKey::ComputeInputtingStringWithKeyboardLayout()
+{
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+
+ if (KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode) ||
+ mCharMessageHasGone) {
+ mInputtingStringAndModifiers = mCommittedCharsAndModifiers;
+ } else {
+ mInputtingStringAndModifiers.Clear();
+ }
+ mShiftedString.Clear();
+ mUnshiftedString.Clear();
+ mShiftedLatinChar = mUnshiftedLatinChar = 0;
+
+ // XXX How about when Win key is pressed?
+ if (mModKeyState.IsControl() == mModKeyState.IsAlt()) {
+ return;
+ }
+
+ ModifierKeyState capsLockState(
+ mModKeyState.GetModifiers() & MODIFIER_CAPSLOCK);
+
+ mUnshiftedString =
+ keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState);
+ capsLockState.Set(MODIFIER_SHIFT);
+ mShiftedString =
+ keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState);
+
+ // The current keyboard cannot input alphabets or numerics,
+ // we should append them for Shortcut/Access keys.
+ // E.g., for Cyrillic keyboard layout.
+ capsLockState.Unset(MODIFIER_SHIFT);
+ WidgetUtils::GetLatinCharCodeForKeyCode(mDOMKeyCode,
+ capsLockState.GetModifiers(),
+ &mUnshiftedLatinChar,
+ &mShiftedLatinChar);
+
+ // If the mShiftedLatinChar isn't 0, the key code is NS_VK_[A-Z].
+ if (mShiftedLatinChar) {
+ // If the produced characters of the key on current keyboard layout
+ // are same as computed Latin characters, we shouldn't append the
+ // Latin characters to alternativeCharCode.
+ if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) &&
+ mShiftedLatinChar == mShiftedString.CharAt(0)) {
+ mShiftedLatinChar = mUnshiftedLatinChar = 0;
+ }
+ } else if (mUnshiftedLatinChar) {
+ // If the mShiftedLatinChar is 0, the mKeyCode doesn't produce
+ // alphabet character. At that time, the character may be produced
+ // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT
+ // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without
+ // Shift key but with Shift key, it produces '%'.
+ // If the mUnshiftedLatinChar is produced by the key on current
+ // keyboard layout, we shouldn't append it to alternativeCharCode.
+ if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) ||
+ mUnshiftedLatinChar == mShiftedString.CharAt(0)) {
+ mUnshiftedLatinChar = 0;
+ }
+ }
+
+ if (!mModKeyState.IsControl()) {
+ return;
+ }
+
+ // If the mCharCode is not ASCII character, we should replace the
+ // mCharCode with ASCII character only when Ctrl is pressed.
+ // But don't replace the mCharCode when the mCharCode is not same as
+ // unmodified characters. In such case, Ctrl is sometimes used for a
+ // part of character inputting key combination like Shift.
+ uint32_t ch =
+ mModKeyState.IsShift() ? mShiftedLatinChar : mUnshiftedLatinChar;
+ if (!ch) {
+ return;
+ }
+ if (mInputtingStringAndModifiers.IsEmpty() ||
+ mInputtingStringAndModifiers.UniCharsCaseInsensitiveEqual(
+ mModKeyState.IsShift() ? mShiftedString : mUnshiftedString)) {
+ mInputtingStringAndModifiers.Clear();
+ mInputtingStringAndModifiers.Append(ch, mModKeyState.GetModifiers());
+ }
+}
+
+bool
+NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() const
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(IsFollowedByPrintableCharOrSysCharMessage());
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "FAILED due to BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "initializing keypress event...", this));
+ ModifierKeyState modKeyState(mModKeyState);
+ if (mCanIgnoreModifierStateAtKeyPress && IsFollowedByPrintableCharMessage()) {
+ // If eKeyPress event should cause inputting text in focused editor,
+ // we need to remove Alt and Ctrl state.
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ // We don't need to send char message here if there are two or more retrieved
+ // messages because we need to set each message to each eKeyPress event.
+ bool needsCallback = mFollowingCharMsgs.Length() > 1;
+ nsEventStatus status =
+ InitKeyEvent(keypressEvent, modKeyState,
+ !needsCallback ? &mFollowingCharMsgs[0] : nullptr);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "dispatching keypress event(s)...", this));
+ bool dispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ const_cast<NativeKey*>(this),
+ needsCallback);
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "keypress event(s) caused destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "dispatched keypress event(s), dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool
+NativeKey::DispatchKeyPressEventsWithoutCharMessage() const
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!mIsDeadKey || !mCommittedCharsAndModifiers.IsEmpty());
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), FAILED due "
+ "to BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ if (mInputtingStringAndModifiers.IsEmpty() &&
+ mShiftedString.IsEmpty() && mUnshiftedString.IsEmpty()) {
+ keypressEvent.mKeyCode = mDOMKeyCode;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), initializing "
+ "keypress event...", this));
+ nsEventStatus status = InitKeyEvent(keypressEvent, mModKeyState);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), dispatching "
+ "keypress event(s)...", this));
+ bool dispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ const_cast<NativeKey*>(this));
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "keypress event(s) caused destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), dispatched "
+ "keypress event(s), dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+void
+NativeKey::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndex)
+{
+ // If it's an eKeyPress event and it's generated from retrieved char message,
+ // we need to set raw message information for plugins.
+ if (aKeyboardEvent.mMessage == eKeyPress &&
+ IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_RELEASE_ASSERT(aIndex < mCommittedCharsAndModifiers.Length());
+ uint32_t foundPrintableCharMessages = 0;
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ // XXX Should we dispatch a plugin event for WM_*DEADCHAR messages and
+ // WM_CHAR with a control character here? But we're not sure
+ // how can we create such message queue (i.e., WM_CHAR or
+ // WM_SYSCHAR with a printable character and such message are
+ // generated by a keydown). So, let's ignore such case until
+ // we'd get some bug reports.
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), WARNING, "
+ "ignoring %uth message due to non-printable char message, %s",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ continue;
+ }
+ if (foundPrintableCharMessages++ == aIndex) {
+ // Found message which caused the eKeyPress event. Let's set the
+ // message for plugin if it's necessary.
+ MaybeInitPluginEventOfKeyEvent(aKeyboardEvent, mFollowingCharMsgs[i]);
+ break;
+ }
+ }
+ // Set modifier state from mCommittedCharsAndModifiers because some of them
+ // might be different. For example, Shift key was pressed at inputting
+ // dead char but Shift key was released before inputting next character.
+ if (mCanIgnoreModifierStateAtKeyPress) {
+ ModifierKeyState modKeyState(mModKeyState);
+ modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT |
+ MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK);
+ modKeyState.Set(mCommittedCharsAndModifiers.ModifiersAt(aIndex));
+ modKeyState.InitInputEvent(aKeyboardEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth modifier state to %s",
+ this, aIndex + 1, ToString(modKeyState).get()));
+ }
+ }
+ size_t longestLength =
+ std::max(mInputtingStringAndModifiers.Length(),
+ std::max(mShiftedString.Length(), mUnshiftedString.Length()));
+ size_t skipUniChars = longestLength - mInputtingStringAndModifiers.Length();
+ size_t skipShiftedChars = longestLength - mShiftedString.Length();
+ size_t skipUnshiftedChars = longestLength - mUnshiftedString.Length();
+ if (aIndex >= longestLength) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), does nothing for %uth "
+ "%s event",
+ this, aIndex + 1, ToChar(aKeyboardEvent.mMessage)));
+ return;
+ }
+
+ // Check if aKeyboardEvent is the last event for a key press.
+ // So, if it's not an eKeyPress event, it's always the last event.
+ // Otherwise, check if the index is the last character of
+ // mCommittedCharsAndModifiers.
+ bool isLastIndex =
+ aKeyboardEvent.mMessage != eKeyPress ||
+ mCommittedCharsAndModifiers.IsEmpty() ||
+ mCommittedCharsAndModifiers.Length() - 1 == aIndex;
+
+ nsTArray<AlternativeCharCode>& altArray =
+ aKeyboardEvent.mAlternativeCharCodes;
+
+ // Set charCode and adjust modifier state for every eKeyPress event.
+ // This is not necessary for the other keyboard events because the other
+ // keyboard events shouldn't have non-zero charCode value and should have
+ // current modifier state.
+ if (aKeyboardEvent.mMessage == eKeyPress && skipUniChars <= aIndex) {
+ // XXX Modifying modifier state of aKeyboardEvent is illegal, but no way
+ // to set different modifier state per keypress event except this
+ // hack. Note that ideally, dead key should cause composition events
+ // instead of keypress events, though.
+ if (aIndex - skipUniChars < mInputtingStringAndModifiers.Length()) {
+ ModifierKeyState modKeyState(mModKeyState);
+ // If key in combination with Alt and/or Ctrl produces a different
+ // character than without them then do not report these flags
+ // because it is separate keyboard layout shift state. If dead-key
+ // and base character does not produce a valid composite character
+ // then both produced dead-key character and following base
+ // character may have different modifier flags, too.
+ modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT |
+ MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK);
+ modKeyState.Set(
+ mInputtingStringAndModifiers.ModifiersAt(aIndex - skipUniChars));
+ modKeyState.InitInputEvent(aKeyboardEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth modifier state to %s",
+ this, aIndex + 1, ToString(modKeyState).get()));
+ }
+ uint16_t uniChar =
+ mInputtingStringAndModifiers.CharAt(aIndex - skipUniChars);
+
+ // 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.
+ aKeyboardEvent.SetCharCode(uniChar);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth charCode to %s",
+ this, aIndex + 1, GetCharacterCodeName(uniChar).get()));
+ }
+
+ // We need to append alterntaive charCode values:
+ // - if the event is eKeyPress, we need to append for the index because
+ // eKeyPress event is dispatched for every character inputted by a
+ // key press.
+ // - if the event is not eKeyPress, we need to append for all characters
+ // inputted by the key press because the other keyboard events (e.g.,
+ // eKeyDown are eKeyUp) are fired only once for a key press.
+ size_t count;
+ if (aKeyboardEvent.mMessage == eKeyPress) {
+ // Basically, append alternative charCode values only for the index.
+ count = 1;
+ // However, if it's the last eKeyPress event but different shift state
+ // can input longer string, the last eKeyPress event should have all
+ // remaining alternative charCode values.
+ if (isLastIndex) {
+ count = longestLength - aIndex;
+ }
+ } else {
+ count = longestLength;
+ }
+ for (size_t i = 0; i < count; ++i) {
+ uint16_t shiftedChar = 0, unshiftedChar = 0;
+ if (skipShiftedChars <= aIndex + i) {
+ shiftedChar = mShiftedString.CharAt(aIndex + i - skipShiftedChars);
+ }
+ if (skipUnshiftedChars <= aIndex + i) {
+ unshiftedChar = mUnshiftedString.CharAt(aIndex + i - skipUnshiftedChars);
+ }
+
+ if (shiftedChar || unshiftedChar) {
+ AlternativeCharCode chars(unshiftedChar, shiftedChar);
+ altArray.AppendElement(chars);
+ }
+
+ if (!isLastIndex) {
+ continue;
+ }
+
+ if (mUnshiftedLatinChar || mShiftedLatinChar) {
+ AlternativeCharCode chars(mUnshiftedLatinChar, mShiftedLatinChar);
+ altArray.AppendElement(chars);
+ }
+
+ // Typically, following virtual keycodes are used for a key which can
+ // input the character. However, these keycodes are also used for
+ // other keys on some keyboard layout. E.g., in spite of Shift+'1'
+ // inputs '+' on Thai keyboard layout, a key which is at '=/+'
+ // key on ANSI keyboard layout is VK_OEM_PLUS. Native applications
+ // handle it as '+' key if Ctrl key is pressed.
+ char16_t charForOEMKeyCode = 0;
+ switch (mVirtualKeyCode) {
+ case VK_OEM_PLUS: charForOEMKeyCode = '+'; break;
+ case VK_OEM_COMMA: charForOEMKeyCode = ','; break;
+ case VK_OEM_MINUS: charForOEMKeyCode = '-'; break;
+ case VK_OEM_PERIOD: charForOEMKeyCode = '.'; break;
+ }
+ if (charForOEMKeyCode &&
+ charForOEMKeyCode != mUnshiftedString.CharAt(0) &&
+ charForOEMKeyCode != mShiftedString.CharAt(0) &&
+ charForOEMKeyCode != mUnshiftedLatinChar &&
+ charForOEMKeyCode != mShiftedLatinChar) {
+ AlternativeCharCode OEMChars(charForOEMKeyCode, charForOEMKeyCode);
+ altArray.AppendElement(OEMChars);
+ }
+ }
+}
+
+/*****************************************************************************
+ * mozilla::widget::KeyboardLayout
+ *****************************************************************************/
+
+KeyboardLayout* KeyboardLayout::sInstance = nullptr;
+nsIIdleServiceInternal* KeyboardLayout::sIdleService = nullptr;
+
+// This log is very noisy if you don't want to retrieve the mapping table
+// of specific keyboard layout. LogLevel::Debug and LogLevel::Verbose are
+// used to log the layout mapping. If you need to log some behavior of
+// KeyboardLayout class, you should use LogLevel::Info or lower level.
+LazyLogModule sKeyboardLayoutLogger("KeyboardLayoutWidgets");
+
+// static
+KeyboardLayout*
+KeyboardLayout::GetInstance()
+{
+ if (!sInstance) {
+ sInstance = new KeyboardLayout();
+ nsCOMPtr<nsIIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/idleservice;1");
+ // The refcount will be decreased at shut down.
+ sIdleService = idleService.forget().take();
+ }
+ return sInstance;
+}
+
+// static
+void
+KeyboardLayout::Shutdown()
+{
+ delete sInstance;
+ sInstance = nullptr;
+ NS_IF_RELEASE(sIdleService);
+}
+
+// static
+void
+KeyboardLayout::NotifyIdleServiceOfUserActivity()
+{
+ sIdleService->ResetIdleTimeOut(0);
+}
+
+KeyboardLayout::KeyboardLayout()
+ : mKeyboardLayout(0)
+ , mIsOverridden(false)
+ , mIsPendingToRestoreKeyboardLayout(false)
+{
+ mDeadKeyTableListHead = nullptr;
+ // A dead key sequence should be made from up to 5 keys. Therefore, 4 is
+ // enough and makes sense because the item is uint8_t.
+ // (Although, even if it's possible to be 6 keys or more in a sequence,
+ // this array will be re-allocated).
+ mActiveDeadKeys.SetCapacity(4);
+ mDeadKeyShiftStates.SetCapacity(4);
+
+ // NOTE: LoadLayout() should be called via OnLayoutChange().
+}
+
+KeyboardLayout::~KeyboardLayout()
+{
+ ReleaseDeadKeyTables();
+}
+
+bool
+KeyboardLayout::IsPrintableCharKey(uint8_t aVirtualKey)
+{
+ return GetKeyIndex(aVirtualKey) >= 0;
+}
+
+WORD
+KeyboardLayout::ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const
+{
+ return static_cast<WORD>(
+ ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC, GetLayout()));
+}
+
+bool
+KeyboardLayout::IsDeadKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const
+{
+ int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey);
+
+ // XXX KeyboardLayout class doesn't support unusual keyboard layout which
+ // maps some function keys as dead keys.
+ if (virtualKeyIndex < 0) {
+ return false;
+ }
+
+ return mVirtualKeys[virtualKeyIndex].IsDeadKey(
+ VirtualKey::ModifiersToShiftState(aModKeyState.GetModifiers()));
+}
+
+bool
+KeyboardLayout::IsSysKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const
+{
+ // If Alt key is not pressed, it's never a system key combination.
+ // Additionally, if Ctrl key is pressed, it's never a system key combination
+ // too.
+ // FYI: Windows logo key state won't affect if it's a system key.
+ if (!aModKeyState.IsAlt() || aModKeyState.IsControl()) {
+ return false;
+ }
+
+ int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey);
+ if (virtualKeyIndex < 0) {
+ return true;
+ }
+
+ UniCharsAndModifiers inputCharsAndModifiers =
+ GetUniCharsAndModifiers(aVirtualKey, aModKeyState);
+ if (inputCharsAndModifiers.IsEmpty()) {
+ return true;
+ }
+
+ // If the Alt key state isn't consumed, that means that the key with Alt
+ // doesn't cause text input. So, the combination is a system key.
+ return !!(inputCharsAndModifiers.ModifiersAt(0) & MODIFIER_ALT);
+}
+
+void
+KeyboardLayout::InitNativeKey(NativeKey& aNativeKey,
+ const ModifierKeyState& aModKeyState)
+{
+ if (mIsPendingToRestoreKeyboardLayout) {
+ LoadLayout(::GetKeyboardLayout(0));
+ }
+
+ // If the aNativeKey is initialized with WM_CHAR, the key information
+ // should be discarded because mKeyValue should have the string to be
+ // inputted.
+ if (aNativeKey.mMsg.message == WM_CHAR) {
+ char16_t ch = static_cast<char16_t>(aNativeKey.mMsg.wParam);
+ // But don't set key value as printable key if the character is a control
+ // character such as 0x0D at pressing Enter key.
+ if (!NativeKey::IsControlChar(ch)) {
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ Modifiers modifiers =
+ aModKeyState.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers);
+ return;
+ }
+ }
+
+ // When it's followed by non-dead char message(s) for printable character(s),
+ // aNativeKey should dispatch eKeyPress events for them rather than
+ // information from keyboard layout because respecting WM_(SYS)CHAR messages
+ // guarantees that we can always input characters which is expected by
+ // the user even if the user uses odd keyboard layout.
+ // Or, when it was followed by non-dead char message for a printable character
+ // but it's gone at removing the message from the queue, let's treat it
+ // as a key inputting empty string.
+ if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage() ||
+ aNativeKey.mCharMessageHasGone) {
+ MOZ_ASSERT(!aNativeKey.IsCharMessage(aNativeKey.mMsg));
+ if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage()) {
+ // Initialize mCommittedCharsAndModifiers with following char messages.
+ aNativeKey.
+ InitCommittedCharsAndModifiersWithFollowingCharMessages(aModKeyState);
+ MOZ_ASSERT(!aNativeKey.mCommittedCharsAndModifiers.IsEmpty());
+
+ // Currently, we are doing a ugly hack to keypress events to cause
+ // inputting character even if Ctrl or Alt key is pressed, that is, we
+ // remove Ctrl and Alt modifier state from keypress event. However, for
+ // example, Ctrl+Space which causes ' ' of WM_CHAR message never causes
+ // keypress event whose ctrlKey is true. For preventing this problem,
+ // we should mark as not removable if Ctrl or Alt key does not cause
+ // changing inputting character.
+ if (IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode) &&
+ !aModKeyState.IsAltGr() &&
+ (aModKeyState.IsControl() || aModKeyState.IsAlt())) {
+ ModifierKeyState state = aModKeyState;
+ state.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ UniCharsAndModifiers charsWithoutModifier =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, state);
+ aNativeKey.mCanIgnoreModifierStateAtKeyPress =
+ !charsWithoutModifier.UniCharsEqual(
+ aNativeKey.mCommittedCharsAndModifiers);
+ }
+ } else {
+ aNativeKey.mCommittedCharsAndModifiers.Clear();
+ }
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+
+ // If it's not in dead key sequence, we don't need to do anymore here.
+ if (!IsInDeadKeySequence()) {
+ return;
+ }
+
+ // If it's in dead key sequence and dead char is inputted as is, we need to
+ // set the previous modifier state which is stored when preceding dead key
+ // is pressed.
+ UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
+ aNativeKey.mCommittedCharsAndModifiers.
+ OverwriteModifiersIfBeginsWith(deadChars);
+ // Finish the dead key sequence.
+ DeactivateDeadKeyState();
+ return;
+ }
+
+ // If it's a dead key, aNativeKey will be initialized by
+ // MaybeInitNativeKeyAsDeadKey().
+ if (MaybeInitNativeKeyAsDeadKey(aNativeKey, aModKeyState)) {
+ return;
+ }
+
+ // If the key is not a usual printable key, KeyboardLayout class assume that
+ // it's not cause dead char nor printable char. Therefore, there are nothing
+ // to do here fore such keys (e.g., function keys).
+ // However, this should keep dead key state even if non-printable key is
+ // pressed during a dead key sequence.
+ if (!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode)) {
+ return;
+ }
+
+ MOZ_ASSERT(aNativeKey.mOriginalVirtualKeyCode != VK_PACKET,
+ "At handling VK_PACKET, we shouldn't refer keyboard layout");
+ MOZ_ASSERT(aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING,
+ "Printable key's key name index must be KEY_NAME_INDEX_USE_STRING");
+
+ // If it's in dead key handling and the pressed key causes a composite
+ // character, aNativeKey will be initialized by
+ // MaybeInitNativeKeyWithCompositeChar().
+ if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) {
+ return;
+ }
+
+ UniCharsAndModifiers baseChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+
+ // If the key press isn't related to any dead keys, initialize aNativeKey
+ // with the characters which should be caused by the key.
+ if (!IsInDeadKeySequence()) {
+ aNativeKey.mCommittedCharsAndModifiers = baseChars;
+ return;
+ }
+
+ // If the key doesn't cause a composite character with preceding dead key,
+ // initialize aNativeKey with the dead-key character followed by current
+ // key's character.
+ UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
+ aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars;
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+}
+
+bool
+KeyboardLayout::MaybeInitNativeKeyAsDeadKey(
+ NativeKey& aNativeKey,
+ const ModifierKeyState& aModKeyState)
+{
+ // Only when it's not in dead key sequence, we can trust IsDeadKey() result.
+ if (!IsInDeadKeySequence() &&
+ !IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) {
+ return false;
+ }
+
+ // When keydown message is followed by a dead char message, it should be
+ // initialized as dead key.
+ bool isDeadKeyDownEvent =
+ aNativeKey.IsKeyDownMessage() &&
+ aNativeKey.IsFollowedByDeadCharMessage();
+
+ // When keyup message is received, let's check if it's one of preceding
+ // dead keys because keydown message order and keyup message order may be
+ // different.
+ bool isDeadKeyUpEvent =
+ !aNativeKey.IsKeyDownMessage() &&
+ mActiveDeadKeys.Contains(aNativeKey.mOriginalVirtualKeyCode);
+
+ if (isDeadKeyDownEvent || isDeadKeyUpEvent) {
+ ActivateDeadKeyState(aNativeKey, aModKeyState);
+ // Any dead key events don't generate characters. So, a dead key should
+ // cause only keydown event and keyup event whose KeyboardEvent.key
+ // values are "Dead".
+ aNativeKey.mCommittedCharsAndModifiers.Clear();
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_Dead;
+ return true;
+ }
+
+ // At keydown message handling, we need to forget the first dead key
+ // because there is no guarantee coming WM_KEYUP for the second dead
+ // key before next WM_KEYDOWN. E.g., due to auto key repeat or pressing
+ // another dead key before releasing current key. Therefore, we can
+ // set only a character for current key for keyup event.
+ if (!IsInDeadKeySequence()) {
+ aNativeKey.mCommittedCharsAndModifiers =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+ return true;
+ }
+
+ // When non-printable key event comes during a dead key sequence, that must
+ // be a modifier key event. So, such events shouldn't be handled as a part
+ // of the dead key sequence.
+ if (!IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) {
+ return false;
+ }
+
+ // FYI: Following code may run when the user doesn't input text actually
+ // but the key sequence is a dead key sequence. For example,
+ // ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this
+ // complicated code for now because this runs really rarely.
+
+ // Dead key followed by another dead key may cause a composed character
+ // (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c').
+ if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) {
+ return true;
+ }
+
+ // Otherwise, dead key followed by another dead key causes inputting both
+ // character.
+ UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers();
+ UniCharsAndModifiers newChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+ // But keypress events should be fired for each committed character.
+ aNativeKey.mCommittedCharsAndModifiers = prevDeadChars + newChars;
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+ return true;
+}
+
+bool
+KeyboardLayout::MaybeInitNativeKeyWithCompositeChar(
+ NativeKey& aNativeKey,
+ const ModifierKeyState& aModKeyState)
+{
+ if (!IsInDeadKeySequence()) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
+ return false;
+ }
+
+ UniCharsAndModifiers baseChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+ if (baseChars.IsEmpty() || !baseChars.CharAt(0)) {
+ return false;
+ }
+
+ char16_t compositeChar = GetCompositeChar(baseChars.CharAt(0));
+ if (!compositeChar) {
+ return false;
+ }
+
+ // Active dead-key and base character does produce exactly one composite
+ // character.
+ aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar,
+ baseChars.ModifiersAt(0));
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+ return true;
+}
+
+UniCharsAndModifiers
+KeyboardLayout::GetUniCharsAndModifiers(
+ uint8_t aVirtualKey,
+ VirtualKey::ShiftState aShiftState) const
+{
+ UniCharsAndModifiers result;
+ int32_t key = GetKeyIndex(aVirtualKey);
+ if (key < 0) {
+ return result;
+ }
+ return mVirtualKeys[key].GetUniChars(aShiftState);
+}
+
+UniCharsAndModifiers
+KeyboardLayout::GetNativeUniCharsAndModifiers(
+ uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const
+{
+ int32_t key = GetKeyIndex(aVirtualKey);
+ if (key < 0) {
+ return UniCharsAndModifiers();
+ }
+ VirtualKey::ShiftState shiftState =
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState);
+ return mVirtualKeys[key].GetNativeUniChars(shiftState);
+}
+
+UniCharsAndModifiers
+KeyboardLayout::GetDeadUniCharsAndModifiers() const
+{
+ MOZ_RELEASE_ASSERT(mActiveDeadKeys.Length() == mDeadKeyShiftStates.Length());
+
+ if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
+ return UniCharsAndModifiers();
+ }
+
+ UniCharsAndModifiers result;
+ for (size_t i = 0; i < mActiveDeadKeys.Length(); ++i) {
+ result +=
+ GetUniCharsAndModifiers(mActiveDeadKeys[i], mDeadKeyShiftStates[i]);
+ }
+ return result;
+}
+
+char16_t
+KeyboardLayout::GetCompositeChar(char16_t aBaseChar) const
+{
+ if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
+ return 0;
+ }
+ // XXX Currently, we don't support computing a composite character with
+ // two or more dead keys since it needs big table for supporting
+ // long chained dead keys. However, this should be a minor bug
+ // because this runs only when the latest keydown event does not cause
+ // WM_(SYS)CHAR messages. So, when user wants to input a character,
+ // this path never runs.
+ if (mActiveDeadKeys.Length() > 1) {
+ return 0;
+ }
+ int32_t key = GetKeyIndex(mActiveDeadKeys[0]);
+ if (key < 0) {
+ return 0;
+ }
+ return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar);
+}
+
+// static
+HKL
+KeyboardLayout::GetActiveLayout()
+{
+ return GetInstance()->mKeyboardLayout;
+}
+
+// static
+nsCString
+KeyboardLayout::GetActiveLayoutName()
+{
+ return GetInstance()->GetLayoutName(GetActiveLayout());
+}
+
+static bool IsValidKeyboardLayoutsChild(const nsAString& aChildName)
+{
+ if (aChildName.Length() != 8) {
+ return false;
+ }
+ for (size_t i = 0; i < aChildName.Length(); i++) {
+ if ((aChildName[i] >= '0' && aChildName[i] <= '9') ||
+ (aChildName[i] >= 'a' && aChildName[i] <= 'f') ||
+ (aChildName[i] >= 'A' && aChildName[i] <= 'F')) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+nsCString
+KeyboardLayout::GetLayoutName(HKL aLayout) const
+{
+ const wchar_t kKeyboardLayouts[] =
+ L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\";
+ uint16_t language = reinterpret_cast<uintptr_t>(aLayout) & 0xFFFF;
+ uint16_t layout = (reinterpret_cast<uintptr_t>(aLayout) >> 16) & 0xFFFF;
+ // If the layout is less than 0xA000XXXX (normal keyboard layout for the
+ // language) or 0xEYYYXXXX (IMM-IME), we can retrieve its name simply.
+ if (layout < 0xA000 || (layout & 0xF000) == 0xE000) {
+ nsAutoString key(kKeyboardLayouts);
+ key.AppendPrintf("%08X", layout < 0xA000 ?
+ layout : reinterpret_cast<uintptr_t>(aLayout));
+ wchar_t buf[256];
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ key.get(), L"Layout Text",
+ buf, sizeof(buf)))) {
+ return NS_LITERAL_CSTRING("No name or too long name");
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+
+ if (NS_WARN_IF((layout & 0xF000) != 0xF000)) {
+ nsAutoCString result;
+ result.AppendPrintf("Odd HKL: 0x%08X",
+ reinterpret_cast<uintptr_t>(aLayout));
+ return result;
+ }
+
+ // Otherwise, we need to walk the registry under "Keyboard Layouts".
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (NS_WARN_IF(!regKey)) {
+ return EmptyCString();
+ }
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ nsString(kKeyboardLayouts),
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EmptyCString();
+ }
+ uint32_t childCount = 0;
+ if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) ||
+ NS_WARN_IF(!childCount)) {
+ return EmptyCString();
+ }
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsAutoString childName;
+ if (NS_WARN_IF(NS_FAILED(regKey->GetChildName(i, childName))) ||
+ !IsValidKeyboardLayoutsChild(childName)) {
+ continue;
+ }
+ uint32_t childNum = static_cast<uint32_t>(childName.ToInteger64(&rv, 16));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ // Ignore normal keyboard layouts for each language.
+ if (childNum <= 0xFFFF) {
+ continue;
+ }
+ // If it doesn't start with 'A' nor 'a', language should be matched.
+ if ((childNum & 0xFFFF) != language &&
+ (childNum & 0xF0000000) != 0xA0000000) {
+ continue;
+ }
+ // Then, the child should have "Layout Id" which is "YYY" of 0xFYYYXXXX.
+ nsAutoString key(kKeyboardLayouts);
+ key += childName;
+ wchar_t buf[256];
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ key.get(), L"Layout Id",
+ buf, sizeof(buf)))) {
+ continue;
+ }
+ uint16_t layoutId = wcstol(buf, nullptr, 16);
+ if (layoutId != (layout & 0x0FFF)) {
+ continue;
+ }
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ key.get(), L"Layout Text",
+ buf, sizeof(buf)))) {
+ continue;
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+ return EmptyCString();
+}
+
+void
+KeyboardLayout::LoadLayout(HKL aLayout)
+{
+ mIsPendingToRestoreKeyboardLayout = false;
+
+ if (mKeyboardLayout == aLayout) {
+ return;
+ }
+
+ mKeyboardLayout = aLayout;
+
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
+ ("KeyboardLayout::LoadLayout(aLayout=0x%08X (%s))",
+ aLayout, GetLayoutName(aLayout).get()));
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ BYTE originalKbdState[256];
+ // Bitfield with all shift states that have at least one dead-key.
+ uint16_t shiftStatesWithDeadKeys = 0;
+ // Bitfield with all shift states that produce any possible dead-key base
+ // characters.
+ uint16_t shiftStatesWithBaseChars = 0;
+
+ mActiveDeadKeys.Clear();
+ mDeadKeyShiftStates.Clear();
+
+ ReleaseDeadKeyTables();
+
+ ::GetKeyboardState(originalKbdState);
+
+ // For each shift state gather all printable characters that are produced
+ // for normal case when no any dead-key is active.
+
+ for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ VirtualKey::FillKbdState(kbdState, shiftState);
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ if (vki < 0) {
+ continue;
+ }
+ NS_ASSERTION(uint32_t(vki) < ArrayLength(mVirtualKeys), "invalid index");
+ char16_t uniChars[5];
+ int32_t ret =
+ ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)uniChars,
+ ArrayLength(uniChars), 0, mKeyboardLayout);
+ // dead-key
+ if (ret < 0) {
+ shiftStatesWithDeadKeys |= (1 << shiftState);
+ // Repeat dead-key to deactivate it and get its character
+ // representation.
+ char16_t deadChar[2];
+ ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)deadChar,
+ ArrayLength(deadChar), 0, mKeyboardLayout);
+ NS_ASSERTION(ret == 2, "Expecting twice repeated dead-key character");
+ mVirtualKeys[vki].SetDeadChar(shiftState, deadChar[0]);
+
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
+ (" %s (%d): DeadChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeName(deadChar, 1).get(), ret));
+ } else {
+ if (ret == 1) {
+ // dead-key can pair only with exactly one base character.
+ shiftStatesWithBaseChars |= (1 << shiftState);
+ }
+ mVirtualKeys[vki].SetNormalChars(shiftState, uniChars, ret);
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+ (" %s (%d): NormalChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeName(uniChars, ret).get(), ret));
+ }
+ }
+ }
+
+ // Now process each dead-key to find all its base characters and resulting
+ // composite characters.
+ for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ if (!(shiftStatesWithDeadKeys & (1 << shiftState))) {
+ continue;
+ }
+
+ VirtualKey::FillKbdState(kbdState, shiftState);
+
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ if (vki >= 0 && mVirtualKeys[vki].IsDeadKey(shiftState)) {
+ DeadKeyEntry deadKeyArray[256];
+ int32_t n = GetDeadKeyCombinations(virtualKey, kbdState,
+ shiftStatesWithBaseChars,
+ deadKeyArray,
+ ArrayLength(deadKeyArray));
+ const DeadKeyTable* dkt =
+ mVirtualKeys[vki].MatchingDeadKeyTable(deadKeyArray, n);
+ if (!dkt) {
+ dkt = AddDeadKeyTable(deadKeyArray, n);
+ }
+ mVirtualKeys[vki].AttachDeadKeyTable(shiftState, dkt);
+ }
+ }
+ }
+
+ ::SetKeyboardState(originalKbdState);
+
+ if (MOZ_LOG_TEST(sKeyboardLayoutLogger, LogLevel::Verbose)) {
+ static const UINT kExtendedScanCode[] = { 0x0000, 0xE000 };
+ static const UINT kMapType =
+ IsVistaOrLater() ? MAPVK_VSC_TO_VK_EX : MAPVK_VSC_TO_VK;
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+ ("Logging virtual keycode values for scancode (0x%p)...",
+ mKeyboardLayout));
+ for (uint32_t i = 0; i < ArrayLength(kExtendedScanCode); i++) {
+ for (uint32_t j = 1; j <= 0xFF; j++) {
+ UINT scanCode = kExtendedScanCode[i] + j;
+ UINT virtualKeyCode =
+ ::MapVirtualKeyEx(scanCode, kMapType, mKeyboardLayout);
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+ ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode]));
+ }
+ // XP and Server 2003 don't support 0xE0 prefix of the scancode.
+ // Therefore, we don't need to continue on them.
+ if (!IsVistaOrLater()) {
+ break;
+ }
+ }
+ }
+}
+
+inline int32_t
+KeyboardLayout::GetKeyIndex(uint8_t aVirtualKey)
+{
+// Currently these 68 (NS_NUM_OF_KEYS) virtual keys are assumed
+// to produce visible representation:
+// 0x20 - VK_SPACE ' '
+// 0x30..0x39 '0'..'9'
+// 0x41..0x5A 'A'..'Z'
+// 0x60..0x69 '0'..'9' on numpad
+// 0x6A - VK_MULTIPLY '*' on numpad
+// 0x6B - VK_ADD '+' on numpad
+// 0x6D - VK_SUBTRACT '-' on numpad
+// 0x6E - VK_DECIMAL '.' on numpad
+// 0x6F - VK_DIVIDE '/' on numpad
+// 0x6E - VK_DECIMAL '.'
+// 0xBA - VK_OEM_1 ';:' for US
+// 0xBB - VK_OEM_PLUS '+' any country
+// 0xBC - VK_OEM_COMMA ',' any country
+// 0xBD - VK_OEM_MINUS '-' any country
+// 0xBE - VK_OEM_PERIOD '.' any country
+// 0xBF - VK_OEM_2 '/?' for US
+// 0xC0 - VK_OEM_3 '`~' for US
+// 0xC1 - VK_ABNT_C1 '/?' for Brazilian
+// 0xC2 - VK_ABNT_C2 separator key on numpad (Brazilian or JIS for Mac)
+// 0xDB - VK_OEM_4 '[{' for US
+// 0xDC - VK_OEM_5 '\|' for US
+// 0xDD - VK_OEM_6 ']}' for US
+// 0xDE - VK_OEM_7 ''"' for US
+// 0xDF - VK_OEM_8
+// 0xE1 - no name
+// 0xE2 - VK_OEM_102 '\_' for JIS
+// 0xE3 - no name
+// 0xE4 - no name
+
+ static const int8_t xlat[256] =
+ {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ //-----------------------------------------------------------------------
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10
+ 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, // 30
+ -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 40
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, // 50
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, -1, 49, 50, 51, // 60
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 70
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, 53, 54, 55, 56, 57, // B0
+ 58, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, 62, 63, 64, 65, // D0
+ -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // F0
+ };
+
+ return xlat[aVirtualKey];
+}
+
+int
+KeyboardLayout::CompareDeadKeyEntries(const void* aArg1,
+ const void* aArg2,
+ void*)
+{
+ const DeadKeyEntry* arg1 = static_cast<const DeadKeyEntry*>(aArg1);
+ const DeadKeyEntry* arg2 = static_cast<const DeadKeyEntry*>(aArg2);
+
+ return arg1->BaseChar - arg2->BaseChar;
+}
+
+const DeadKeyTable*
+KeyboardLayout::AddDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries)
+{
+ DeadKeyTableListEntry* next = mDeadKeyTableListHead;
+
+ const size_t bytes = offsetof(DeadKeyTableListEntry, data) +
+ DeadKeyTable::SizeInBytes(aEntries);
+ uint8_t* p = new uint8_t[bytes];
+
+ mDeadKeyTableListHead = reinterpret_cast<DeadKeyTableListEntry*>(p);
+ mDeadKeyTableListHead->next = next;
+
+ DeadKeyTable* dkt =
+ reinterpret_cast<DeadKeyTable*>(mDeadKeyTableListHead->data);
+
+ dkt->Init(aDeadKeyArray, aEntries);
+
+ return dkt;
+}
+
+void
+KeyboardLayout::ReleaseDeadKeyTables()
+{
+ while (mDeadKeyTableListHead) {
+ uint8_t* p = reinterpret_cast<uint8_t*>(mDeadKeyTableListHead);
+ mDeadKeyTableListHead = mDeadKeyTableListHead->next;
+
+ delete [] p;
+ }
+}
+
+bool
+KeyboardLayout::EnsureDeadKeyActive(bool aIsActive,
+ uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState)
+{
+ int32_t ret;
+ do {
+ char16_t dummyChars[5];
+ ret = ::ToUnicodeEx(aDeadKey, 0, (PBYTE)aDeadKeyKbdState,
+ (LPWSTR)dummyChars, ArrayLength(dummyChars), 0,
+ mKeyboardLayout);
+ // returned values:
+ // <0 - Dead key state is active. The keyboard driver will wait for next
+ // character.
+ // 1 - Previous pressed key was a valid base character that produced
+ // exactly one composite character.
+ // >1 - Previous pressed key does not produce any composite characters.
+ // Return dead-key character followed by base character(s).
+ } while ((ret < 0) != aIsActive);
+
+ return (ret < 0);
+}
+
+void
+KeyboardLayout::ActivateDeadKeyState(const NativeKey& aNativeKey,
+ const ModifierKeyState& aModKeyState)
+{
+ // Dead-key state should be activated at keydown.
+ if (!aNativeKey.IsKeyDownMessage()) {
+ return;
+ }
+
+ mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode);
+ mDeadKeyShiftStates.AppendElement(
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState));
+}
+
+void
+KeyboardLayout::DeactivateDeadKeyState()
+{
+ if (mActiveDeadKeys.IsEmpty()) {
+ return;
+ }
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ // Assume that the last dead key can finish dead key sequence.
+ VirtualKey::FillKbdState(kbdState, mDeadKeyShiftStates.LastElement());
+ EnsureDeadKeyActive(false, mActiveDeadKeys.LastElement(), kbdState);
+ mActiveDeadKeys.Clear();
+ mDeadKeyShiftStates.Clear();
+}
+
+bool
+KeyboardLayout::AddDeadKeyEntry(char16_t aBaseChar,
+ char16_t aCompositeChar,
+ DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries)
+{
+ for (uint32_t index = 0; index < aEntries; index++) {
+ if (aDeadKeyArray[index].BaseChar == aBaseChar) {
+ return false;
+ }
+ }
+
+ aDeadKeyArray[aEntries].BaseChar = aBaseChar;
+ aDeadKeyArray[aEntries].CompositeChar = aCompositeChar;
+
+ return true;
+}
+
+uint32_t
+KeyboardLayout::GetDeadKeyCombinations(uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState,
+ uint16_t aShiftStatesWithBaseChars,
+ DeadKeyEntry* aDeadKeyArray,
+ uint32_t aMaxEntries)
+{
+ bool deadKeyActive = false;
+ uint32_t entries = 0;
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ for (uint32_t shiftState = 0; shiftState < 16; shiftState++) {
+ if (!(aShiftStatesWithBaseChars & (1 << shiftState))) {
+ continue;
+ }
+
+ VirtualKey::FillKbdState(kbdState, shiftState);
+
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ // Dead-key can pair only with such key that produces exactly one base
+ // character.
+ if (vki >= 0 &&
+ mVirtualKeys[vki].GetNativeUniChars(shiftState).Length() == 1) {
+ // Ensure dead-key is in active state, when it swallows entered
+ // character and waits for the next pressed key.
+ if (!deadKeyActive) {
+ deadKeyActive = EnsureDeadKeyActive(true, aDeadKey,
+ aDeadKeyKbdState);
+ }
+
+ // Depending on the character the followed the dead-key, the keyboard
+ // driver can produce one composite character, or a dead-key character
+ // followed by a second character.
+ char16_t compositeChars[5];
+ int32_t ret =
+ ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)compositeChars,
+ ArrayLength(compositeChars), 0, mKeyboardLayout);
+ switch (ret) {
+ case 0:
+ // This key combination does not produce any characters. The
+ // dead-key is still in active state.
+ break;
+ case 1: {
+ char16_t baseChars[5];
+ ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)baseChars,
+ ArrayLength(baseChars), 0, mKeyboardLayout);
+ if (entries < aMaxEntries) {
+ switch (ret) {
+ case 1:
+ // Exactly one composite character produced. Now, when
+ // dead-key is not active, repeat the last character one more
+ // time to determine the base character.
+ if (AddDeadKeyEntry(baseChars[0], compositeChars[0],
+ aDeadKeyArray, entries)) {
+ entries++;
+ }
+ deadKeyActive = false;
+ break;
+ case -1: {
+ // If pressing another dead-key produces different character,
+ // we should register the dead-key entry with first character
+ // produced by current key.
+
+ // First inactivate the dead-key state completely.
+ deadKeyActive =
+ EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+ if (NS_WARN_IF(deadKeyActive)) {
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Error,
+ (" failed to deactivating the dead-key state..."));
+ break;
+ }
+ for (int32_t i = 0; i < 5; ++i) {
+ ret = ::ToUnicodeEx(virtualKey, 0, kbdState,
+ (LPWSTR)baseChars,
+ ArrayLength(baseChars),
+ 0, mKeyboardLayout);
+ if (ret >= 0) {
+ break;
+ }
+ }
+ if (ret > 0 &&
+ AddDeadKeyEntry(baseChars[0], compositeChars[0],
+ aDeadKeyArray, entries)) {
+ entries++;
+ }
+ // Inactivate dead-key state for current virtual keycode.
+ EnsureDeadKeyActive(false, virtualKey, kbdState);
+ break;
+ }
+ default:
+ NS_WARN_IF("File a bug for this dead-key handling!");
+ deadKeyActive = false;
+ break;
+ }
+ }
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
+ (" %s -> %s (%d): DeadKeyEntry(%s, %s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ GetCharacterCodeName(compositeChars, 1).get(),
+ ret <= 0 ? "''" :
+ GetCharacterCodeName(baseChars, std::min(ret, 5)).get(), ret));
+ break;
+ }
+ default:
+ // 1. Unexpected dead-key. Dead-key chaining is not supported.
+ // 2. More than one character generated. This is not a valid
+ // dead-key and base character combination.
+ deadKeyActive = false;
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose,
+ (" %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ ret <= 0 ? "''" :
+ GetCharacterCodeName(compositeChars,
+ std::min(ret, 5)).get(), ret));
+ break;
+ }
+ }
+ }
+ }
+
+ if (deadKeyActive) {
+ deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+ }
+
+ NS_QuickSort(aDeadKeyArray, entries, sizeof(DeadKeyEntry),
+ CompareDeadKeyEntries, nullptr);
+ return entries;
+}
+
+uint32_t
+KeyboardLayout::ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const
+{
+ // Alphabet or Numeric or Numpad or Function keys
+ if ((aNativeKeyCode >= 0x30 && aNativeKeyCode <= 0x39) ||
+ (aNativeKeyCode >= 0x41 && aNativeKeyCode <= 0x5A) ||
+ (aNativeKeyCode >= 0x60 && aNativeKeyCode <= 0x87)) {
+ return static_cast<uint32_t>(aNativeKeyCode);
+ }
+ switch (aNativeKeyCode) {
+ // Following keycodes are same as our DOM keycodes
+ case VK_CANCEL:
+ case VK_BACK:
+ case VK_TAB:
+ case VK_CLEAR:
+ case VK_RETURN:
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU: // Alt
+ case VK_PAUSE:
+ case VK_CAPITAL: // CAPS LOCK
+ case VK_KANA: // same as VK_HANGUL
+ case VK_JUNJA:
+ case VK_FINAL:
+ case VK_HANJA: // same as VK_KANJI
+ case VK_ESCAPE:
+ case VK_CONVERT:
+ case VK_NONCONVERT:
+ case VK_ACCEPT:
+ case VK_MODECHANGE:
+ case VK_SPACE:
+ case VK_PRIOR: // PAGE UP
+ case VK_NEXT: // PAGE DOWN
+ case VK_END:
+ case VK_HOME:
+ case VK_LEFT:
+ case VK_UP:
+ case VK_RIGHT:
+ case VK_DOWN:
+ case VK_SELECT:
+ case VK_PRINT:
+ case VK_EXECUTE:
+ case VK_SNAPSHOT:
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_APPS: // Context Menu
+ case VK_SLEEP:
+ case VK_NUMLOCK:
+ case VK_SCROLL: // SCROLL LOCK
+ case VK_ATTN: // Attension key of IBM midrange computers, e.g., AS/400
+ case VK_CRSEL: // Cursor Selection
+ case VK_EXSEL: // Extend Selection
+ case VK_EREOF: // Erase EOF key of IBM 3270 keyboard layout
+ case VK_PLAY:
+ case VK_ZOOM:
+ case VK_PA1: // PA1 key of IBM 3270 keyboard layout
+ return uint32_t(aNativeKeyCode);
+
+ case VK_HELP:
+ return NS_VK_HELP;
+
+ // Windows key should be mapped to a Win keycode
+ // They should be able to be distinguished by DOM3 KeyboardEvent.location
+ case VK_LWIN:
+ case VK_RWIN:
+ return NS_VK_WIN;
+
+ case VK_VOLUME_MUTE:
+ return NS_VK_VOLUME_MUTE;
+ case VK_VOLUME_DOWN:
+ return NS_VK_VOLUME_DOWN;
+ case VK_VOLUME_UP:
+ return NS_VK_VOLUME_UP;
+
+ // Following keycodes are not defined in our DOM keycodes.
+ case VK_BROWSER_BACK:
+ case VK_BROWSER_FORWARD:
+ case VK_BROWSER_REFRESH:
+ case VK_BROWSER_STOP:
+ case VK_BROWSER_SEARCH:
+ case VK_BROWSER_FAVORITES:
+ case VK_BROWSER_HOME:
+ case VK_MEDIA_NEXT_TRACK:
+ case VK_MEDIA_PREV_TRACK:
+ case VK_MEDIA_STOP:
+ case VK_MEDIA_PLAY_PAUSE:
+ case VK_LAUNCH_MAIL:
+ case VK_LAUNCH_MEDIA_SELECT:
+ case VK_LAUNCH_APP1:
+ case VK_LAUNCH_APP2:
+ return 0;
+
+ // Following OEM specific virtual keycodes should pass through DOM keyCode
+ // for compatibility with the other browsers on Windows.
+
+ // Following OEM specific virtual keycodes are defined for Fujitsu/OASYS.
+ case VK_OEM_FJ_JISHO:
+ case VK_OEM_FJ_MASSHOU:
+ case VK_OEM_FJ_TOUROKU:
+ case VK_OEM_FJ_LOYA:
+ case VK_OEM_FJ_ROYA:
+ // Not sure what means "ICO".
+ case VK_ICO_HELP:
+ case VK_ICO_00:
+ case VK_ICO_CLEAR:
+ // Following OEM specific virtual keycodes are defined for Nokia/Ericsson.
+ case VK_OEM_RESET:
+ case VK_OEM_JUMP:
+ case VK_OEM_PA1:
+ case VK_OEM_PA2:
+ case VK_OEM_PA3:
+ case VK_OEM_WSCTRL:
+ case VK_OEM_CUSEL:
+ case VK_OEM_ATTN:
+ case VK_OEM_FINISH:
+ case VK_OEM_COPY:
+ case VK_OEM_AUTO:
+ case VK_OEM_ENLW:
+ case VK_OEM_BACKTAB:
+ // VK_OEM_CLEAR is defined as not OEM specific, but let's pass though
+ // DOM keyCode like other OEM specific virtual keycodes.
+ case VK_OEM_CLEAR:
+ return uint32_t(aNativeKeyCode);
+
+ // 0xE1 is an OEM specific virtual keycode. However, the value is already
+ // used in our DOM keyCode for AltGr on Linux. So, this virtual keycode
+ // cannot pass through DOM keyCode.
+ case 0xE1:
+ return 0;
+
+ // Following keycodes are OEM keys which are keycodes for non-alphabet and
+ // non-numeric keys, we should compute each keycode of them from unshifted
+ // character which is inputted by each key. But if the unshifted character
+ // is not an ASCII character but shifted character is an ASCII character,
+ // we should refer it.
+ case VK_OEM_1:
+ case VK_OEM_PLUS:
+ case VK_OEM_COMMA:
+ case VK_OEM_MINUS:
+ case VK_OEM_PERIOD:
+ case VK_OEM_2:
+ case VK_OEM_3:
+ case VK_OEM_4:
+ case VK_OEM_5:
+ case VK_OEM_6:
+ case VK_OEM_7:
+ case VK_OEM_8:
+ case VK_OEM_102:
+ case VK_ABNT_C1:
+ {
+ NS_ASSERTION(IsPrintableCharKey(aNativeKeyCode),
+ "The key must be printable");
+ ModifierKeyState modKeyState(0);
+ UniCharsAndModifiers uniChars =
+ GetUniCharsAndModifiers(aNativeKeyCode, modKeyState);
+ if (uniChars.Length() != 1 ||
+ uniChars.CharAt(0) < ' ' || uniChars.CharAt(0) > 0x7F) {
+ modKeyState.Set(MODIFIER_SHIFT);
+ uniChars = GetUniCharsAndModifiers(aNativeKeyCode, modKeyState);
+ if (uniChars.Length() != 1 ||
+ uniChars.CharAt(0) < ' ' || uniChars.CharAt(0) > 0x7F) {
+ return 0;
+ }
+ }
+ return WidgetUtils::ComputeKeyCodeFromChar(uniChars.CharAt(0));
+ }
+
+ // IE sets 0xC2 to the DOM keyCode for VK_ABNT_C2. However, we're already
+ // using NS_VK_SEPARATOR for the separator key on Mac and Linux. Therefore,
+ // We should keep consistency between Gecko on all platforms rather than
+ // with other browsers since a lot of keyCode values are already different
+ // between browsers.
+ case VK_ABNT_C2:
+ return NS_VK_SEPARATOR;
+
+ // VK_PROCESSKEY means IME already consumed the key event.
+ case VK_PROCESSKEY:
+ return 0;
+ // VK_PACKET is generated by SendInput() API, we don't need to
+ // care this message as key event.
+ case VK_PACKET:
+ return 0;
+ // If a key is not mapped to a virtual keycode, 0xFF is used.
+ case 0xFF:
+ NS_WARNING("The key is failed to be converted to a virtual keycode");
+ return 0;
+ }
+#ifdef DEBUG
+ nsPrintfCString warning("Unknown virtual keycode (0x%08X), please check the "
+ "latest MSDN document, there may be some new "
+ "keycodes we've never known.",
+ aNativeKeyCode);
+ NS_WARNING(warning.get());
+#endif
+ return 0;
+}
+
+KeyNameIndex
+KeyboardLayout::ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const
+{
+ if (IsPrintableCharKey(aVirtualKey) || aVirtualKey == VK_PACKET) {
+ return KEY_NAME_INDEX_USE_STRING;
+ }
+
+ switch (aVirtualKey) {
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#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:
+ break;
+ }
+
+ HKL layout = GetLayout();
+ WORD langID = LOWORD(static_cast<HKL>(layout));
+ WORD primaryLangID = PRIMARYLANGID(langID);
+
+ if (primaryLangID == LANG_JAPANESE) {
+ switch (aVirtualKey) {
+
+#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex)\
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+ } else if (primaryLangID == LANG_KOREAN) {
+ switch (aVirtualKey) {
+
+#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex)\
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+ }
+
+ switch (aVirtualKey) {
+
+#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex)\
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+// static
+CodeNameIndex
+KeyboardLayout::ConvertScanCodeToCodeNameIndex(UINT aScanCode)
+{
+ switch (aScanCode) {
+
+#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;
+ }
+}
+
+nsresult
+KeyboardLayout::SynthesizeNativeKeyEvent(nsWindowBase* aWidget,
+ int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters)
+{
+ UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, nullptr);
+ NS_ASSERTION(keyboardLayoutListCount > 0,
+ "One keyboard layout must be installed at least");
+ HKL keyboardLayoutListBuff[50];
+ HKL* keyboardLayoutList =
+ keyboardLayoutListCount < 50 ? keyboardLayoutListBuff :
+ new HKL[keyboardLayoutListCount];
+ keyboardLayoutListCount =
+ ::GetKeyboardLayoutList(keyboardLayoutListCount, keyboardLayoutList);
+ NS_ASSERTION(keyboardLayoutListCount > 0,
+ "Failed to get all keyboard layouts installed on the system");
+
+ nsPrintfCString layoutName("%08x", aNativeKeyboardLayout);
+ HKL loadedLayout = LoadKeyboardLayoutA(layoutName.get(), KLF_NOTELLSHELL);
+ if (loadedLayout == nullptr) {
+ if (keyboardLayoutListBuff != keyboardLayoutList) {
+ delete [] keyboardLayoutList;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Setup clean key state and load desired layout
+ BYTE originalKbdState[256];
+ ::GetKeyboardState(originalKbdState);
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+ // This changes the state of the keyboard for the current thread only,
+ // and we'll restore it soon, so this should be OK.
+ ::SetKeyboardState(kbdState);
+
+ OverrideLayout(loadedLayout);
+
+ uint8_t argumentKeySpecific = 0;
+ switch (aNativeKeyCode & 0xFF) {
+ case VK_SHIFT:
+ aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R);
+ argumentKeySpecific = VK_LSHIFT;
+ break;
+ case VK_LSHIFT:
+ aModifierFlags &= ~nsIWidget::SHIFT_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT;
+ break;
+ case VK_RSHIFT:
+ aModifierFlags &= ~nsIWidget::SHIFT_R;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT;
+ break;
+ case VK_CONTROL:
+ aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R);
+ argumentKeySpecific = VK_LCONTROL;
+ break;
+ case VK_LCONTROL:
+ aModifierFlags &= ~nsIWidget::CTRL_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL;
+ break;
+ case VK_RCONTROL:
+ aModifierFlags &= ~nsIWidget::CTRL_R;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL;
+ break;
+ case VK_MENU:
+ aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R);
+ argumentKeySpecific = VK_LMENU;
+ break;
+ case VK_LMENU:
+ aModifierFlags &= ~nsIWidget::ALT_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ break;
+ case VK_RMENU:
+ aModifierFlags &= ~nsIWidget::ALT_R;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ break;
+ case VK_CAPITAL:
+ aModifierFlags &= ~nsIWidget::CAPS_LOCK;
+ argumentKeySpecific = VK_CAPITAL;
+ break;
+ case VK_NUMLOCK:
+ aModifierFlags &= ~nsIWidget::NUM_LOCK;
+ argumentKeySpecific = VK_NUMLOCK;
+ break;
+ }
+
+ AutoTArray<KeyPair,10> keySequence;
+ WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags);
+ keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific));
+
+ // Simulate the pressing of each modifier key and then the real key
+ // FYI: Each NativeKey instance here doesn't need to override keyboard layout
+ // since this method overrides and restores the keyboard layout.
+ for (uint32_t i = 0; i < keySequence.Length(); ++i) {
+ uint8_t key = keySequence[i].mGeneral;
+ uint8_t keySpecific = keySequence[i].mSpecific;
+ uint16_t scanCode = keySequence[i].mScanCode;
+ kbdState[key] = 0x81; // key is down and toggled on if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0x81;
+ }
+ ::SetKeyboardState(kbdState);
+ ModifierKeyState modKeyState;
+ // If scan code isn't specified explicitly, let's compute it with current
+ // keyboard layout.
+ if (!scanCode) {
+ scanCode =
+ ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
+ }
+ LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
+ // If the scan code is for an extended key, set extended key flag.
+ if ((scanCode & 0xFF00) == 0xE000) {
+ lParam |= 0x1000000;
+ }
+ bool makeSysKeyMsg = IsSysKey(key, modKeyState);
+ MSG keyDownMsg =
+ WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYDOWN : WM_KEYDOWN,
+ key, lParam, aWidget->GetWindowHandle());
+ if (i == keySequence.Length() - 1) {
+ bool makeDeadCharMsg =
+ (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty());
+ nsAutoString chars(aCharacters);
+ if (makeDeadCharMsg) {
+ UniCharsAndModifiers deadChars =
+ GetUniCharsAndModifiers(key, modKeyState);
+ chars = deadChars.ToString();
+ NS_ASSERTION(chars.Length() == 1,
+ "Dead char must be only one character");
+ }
+ if (chars.IsEmpty()) {
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
+ nativeKey.HandleKeyDownMessage();
+ } else {
+ AutoTArray<NativeKey::FakeCharMsg, 10> fakeCharMsgs;
+ for (uint32_t j = 0; j < chars.Length(); j++) {
+ NativeKey::FakeCharMsg* fakeCharMsg = fakeCharMsgs.AppendElement();
+ fakeCharMsg->mCharCode = chars.CharAt(j);
+ fakeCharMsg->mScanCode = scanCode;
+ fakeCharMsg->mIsSysKey = makeSysKeyMsg;
+ fakeCharMsg->mIsDeadKey = makeDeadCharMsg;
+ }
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, 0, &fakeCharMsgs);
+ bool dispatched;
+ nativeKey.HandleKeyDownMessage(&dispatched);
+ // If some char messages are not consumed, let's emulate the widget
+ // receiving the message directly.
+ for (uint32_t j = 1; j < fakeCharMsgs.Length(); j++) {
+ if (fakeCharMsgs[j].mConsumed) {
+ continue;
+ }
+ MSG charMsg = fakeCharMsgs[j].GetCharMsg(aWidget->GetWindowHandle());
+ NativeKey nativeKey(aWidget, charMsg, modKeyState);
+ nativeKey.HandleCharMessage(charMsg);
+ }
+ }
+ } else {
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
+ nativeKey.HandleKeyDownMessage();
+ }
+ }
+ for (uint32_t i = keySequence.Length(); i > 0; --i) {
+ uint8_t key = keySequence[i - 1].mGeneral;
+ uint8_t keySpecific = keySequence[i - 1].mSpecific;
+ uint16_t scanCode = keySequence[i - 1].mScanCode;
+ kbdState[key] = 0; // key is up and toggled off if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0;
+ }
+ ::SetKeyboardState(kbdState);
+ ModifierKeyState modKeyState;
+ // If scan code isn't specified explicitly, let's compute it with current
+ // keyboard layout.
+ if (!scanCode) {
+ scanCode =
+ ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
+ }
+ LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
+ // If the scan code is for an extended key, set extended key flag.
+ if ((scanCode & 0xFF00) == 0xE000) {
+ lParam |= 0x1000000;
+ }
+ // Don't use WM_SYSKEYUP for Alt keyup.
+ bool makeSysKeyMsg = IsSysKey(key, modKeyState) && key != VK_MENU;
+ MSG keyUpMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYUP : WM_KEYUP,
+ key, lParam,
+ aWidget->GetWindowHandle());
+ NativeKey nativeKey(aWidget, keyUpMsg, modKeyState);
+ nativeKey.HandleKeyUpMessage();
+ }
+
+ // Restore old key state and layout
+ ::SetKeyboardState(originalKbdState);
+ RestoreLayout();
+
+ // Don't unload the layout if it's installed actually.
+ for (uint32_t i = 0; i < keyboardLayoutListCount; i++) {
+ if (keyboardLayoutList[i] == loadedLayout) {
+ loadedLayout = 0;
+ break;
+ }
+ }
+ if (keyboardLayoutListBuff != keyboardLayoutList) {
+ delete [] keyboardLayoutList;
+ }
+ if (loadedLayout) {
+ ::UnloadKeyboardLayout(loadedLayout);
+ }
+ return NS_OK;
+}
+
+/*****************************************************************************
+ * mozilla::widget::DeadKeyTable
+ *****************************************************************************/
+
+char16_t
+DeadKeyTable::GetCompositeChar(char16_t aBaseChar) const
+{
+ // Dead-key table is sorted by BaseChar in ascending order.
+ // Usually they are too small to use binary search.
+
+ for (uint32_t index = 0; index < mEntries; index++) {
+ if (mTable[index].BaseChar == aBaseChar) {
+ return mTable[index].CompositeChar;
+ }
+ if (mTable[index].BaseChar > aBaseChar) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ * mozilla::widget::RedirectedKeyDownMessage
+ *****************************************************************************/
+
+MSG RedirectedKeyDownMessageManager::sRedirectedKeyDownMsg;
+bool RedirectedKeyDownMessageManager::sDefaultPreventedOfRedirectedMsg = false;
+
+// static
+bool
+RedirectedKeyDownMessageManager::IsRedirectedMessage(const MSG& aMsg)
+{
+ return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) &&
+ (sRedirectedKeyDownMsg.message == aMsg.message &&
+ WinUtils::GetScanCode(sRedirectedKeyDownMsg.lParam) ==
+ WinUtils::GetScanCode(aMsg.lParam));
+}
+
+// static
+void
+RedirectedKeyDownMessageManager::RemoveNextCharMessage(HWND aWnd)
+{
+ MSG msg;
+ if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) {
+ WinUtils::PeekMessage(&msg, aWnd, msg.message, msg.message,
+ PM_REMOVE | PM_NOYIELD);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
+