forked from mirrors/gecko-dev
		
	 cd6fd9bfe5
			
		
	
	
		cd6fd9bfe5
		
	
	
	
	
		
			
			This is the second of two patches in this series that removes a large amount of now dead code from dom/plugins as part of removing all NPAPI plugin support. Differential Revision: https://phabricator.services.mozilla.com/D107150
		
			
				
	
	
		
			5383 lines
		
	
	
	
		
			195 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			5383 lines
		
	
	
	
		
			195 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/Logging.h"
 | |
| 
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/AutoRestore.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/MouseEvents.h"
 | |
| #include "mozilla/MiscEvents.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/TextEvents.h"
 | |
| 
 | |
| #include "nsAlgorithm.h"
 | |
| #include "nsExceptionHandler.h"
 | |
| #include "nsGkAtoms.h"
 | |
| #include "nsIUserIdleServiceInternal.h"
 | |
| #include "nsIWindowsRegKey.h"
 | |
| #include "nsMemory.h"
 | |
| #include "nsPrintfCString.h"
 | |
| #include "nsQuickSort.h"
 | |
| #include "nsReadableUtils.h"
 | |
| #include "nsServiceManagerUtils.h"
 | |
| #include "nsToolkit.h"
 | |
| #include "nsUnicharUtils.h"
 | |
| #include "nsWindowDbg.h"
 | |
| 
 | |
| #include "KeyboardLayout.h"
 | |
| #include "WidgetUtils.h"
 | |
| #include "WinUtils.h"
 | |
| 
 | |
| #include "npapi.h"
 | |
| 
 | |
| #include <windows.h>
 | |
| #include <winuser.h>
 | |
| #include <algorithm>
 | |
| 
 | |
| #ifndef WINABLEAPI
 | |
| #  include <winable.h>
 | |
| #endif
 | |
| 
 | |
| // In WinUser.h, MAPVK_VK_TO_VSC_EX is defined only when WINVER >= 0x0600
 | |
| #ifndef MAPVK_VK_TO_VSC_EX
 | |
| #  define MAPVK_VK_TO_VSC_EX (4)
 | |
| #endif
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace widget {
 | |
| 
 | |
| static const char* const kVirtualKeyName[] = {
 | |
|     "NULL",
 | |
|     "VK_LBUTTON",
 | |
|     "VK_RBUTTON",
 | |
|     "VK_CANCEL",
 | |
|     "VK_MBUTTON",
 | |
|     "VK_XBUTTON1",
 | |
|     "VK_XBUTTON2",
 | |
|     "0x07",
 | |
|     "VK_BACK",
 | |
|     "VK_TAB",
 | |
|     "0x0A",
 | |
|     "0x0B",
 | |
|     "VK_CLEAR",
 | |
|     "VK_RETURN",
 | |
|     "0x0E",
 | |
|     "0x0F",
 | |
| 
 | |
|     "VK_SHIFT",
 | |
|     "VK_CONTROL",
 | |
|     "VK_MENU",
 | |
|     "VK_PAUSE",
 | |
|     "VK_CAPITAL",
 | |
|     "VK_KANA, VK_HANGUL",
 | |
|     "0x16",
 | |
|     "VK_JUNJA",
 | |
|     "VK_FINAL",
 | |
|     "VK_HANJA, VK_KANJI",
 | |
|     "0x1A",
 | |
|     "VK_ESCAPE",
 | |
|     "VK_CONVERT",
 | |
|     "VK_NONCONVERT",
 | |
|     "VK_ACCEPT",
 | |
|     "VK_MODECHANGE",
 | |
| 
 | |
|     "VK_SPACE",
 | |
|     "VK_PRIOR",
 | |
|     "VK_NEXT",
 | |
|     "VK_END",
 | |
|     "VK_HOME",
 | |
|     "VK_LEFT",
 | |
|     "VK_UP",
 | |
|     "VK_RIGHT",
 | |
|     "VK_DOWN",
 | |
|     "VK_SELECT",
 | |
|     "VK_PRINT",
 | |
|     "VK_EXECUTE",
 | |
|     "VK_SNAPSHOT",
 | |
|     "VK_INSERT",
 | |
|     "VK_DELETE",
 | |
|     "VK_HELP",
 | |
| 
 | |
|     "VK_0",
 | |
|     "VK_1",
 | |
|     "VK_2",
 | |
|     "VK_3",
 | |
|     "VK_4",
 | |
|     "VK_5",
 | |
|     "VK_6",
 | |
|     "VK_7",
 | |
|     "VK_8",
 | |
|     "VK_9",
 | |
|     "0x3A",
 | |
|     "0x3B",
 | |
|     "0x3C",
 | |
|     "0x3D",
 | |
|     "0x3E",
 | |
|     "0x3F",
 | |
| 
 | |
|     "0x40",
 | |
|     "VK_A",
 | |
|     "VK_B",
 | |
|     "VK_C",
 | |
|     "VK_D",
 | |
|     "VK_E",
 | |
|     "VK_F",
 | |
|     "VK_G",
 | |
|     "VK_H",
 | |
|     "VK_I",
 | |
|     "VK_J",
 | |
|     "VK_K",
 | |
|     "VK_L",
 | |
|     "VK_M",
 | |
|     "VK_N",
 | |
|     "VK_O",
 | |
| 
 | |
|     "VK_P",
 | |
|     "VK_Q",
 | |
|     "VK_R",
 | |
|     "VK_S",
 | |
|     "VK_T",
 | |
|     "VK_U",
 | |
|     "VK_V",
 | |
|     "VK_W",
 | |
|     "VK_X",
 | |
|     "VK_Y",
 | |
|     "VK_Z",
 | |
|     "VK_LWIN",
 | |
|     "VK_RWIN",
 | |
|     "VK_APPS",
 | |
|     "0x5E",
 | |
|     "VK_SLEEP",
 | |
| 
 | |
|     "VK_NUMPAD0",
 | |
|     "VK_NUMPAD1",
 | |
|     "VK_NUMPAD2",
 | |
|     "VK_NUMPAD3",
 | |
|     "VK_NUMPAD4",
 | |
|     "VK_NUMPAD5",
 | |
|     "VK_NUMPAD6",
 | |
|     "VK_NUMPAD7",
 | |
|     "VK_NUMPAD8",
 | |
|     "VK_NUMPAD9",
 | |
|     "VK_MULTIPLY",
 | |
|     "VK_ADD",
 | |
|     "VK_SEPARATOR",
 | |
|     "VK_SUBTRACT",
 | |
|     "VK_DECIMAL",
 | |
|     "VK_DIVIDE",
 | |
| 
 | |
|     "VK_F1",
 | |
|     "VK_F2",
 | |
|     "VK_F3",
 | |
|     "VK_F4",
 | |
|     "VK_F5",
 | |
|     "VK_F6",
 | |
|     "VK_F7",
 | |
|     "VK_F8",
 | |
|     "VK_F9",
 | |
|     "VK_F10",
 | |
|     "VK_F11",
 | |
|     "VK_F12",
 | |
|     "VK_F13",
 | |
|     "VK_F14",
 | |
|     "VK_F15",
 | |
|     "VK_F16",
 | |
| 
 | |
|     "VK_F17",
 | |
|     "VK_F18",
 | |
|     "VK_F19",
 | |
|     "VK_F20",
 | |
|     "VK_F21",
 | |
|     "VK_F22",
 | |
|     "VK_F23",
 | |
|     "VK_F24",
 | |
|     "0x88",
 | |
|     "0x89",
 | |
|     "0x8A",
 | |
|     "0x8B",
 | |
|     "0x8C",
 | |
|     "0x8D",
 | |
|     "0x8E",
 | |
|     "0x8F",
 | |
| 
 | |
|     "VK_NUMLOCK",
 | |
|     "VK_SCROLL",
 | |
|     "VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO",
 | |
|     "VK_OEM_FJ_MASSHOU",
 | |
|     "VK_OEM_FJ_TOUROKU",
 | |
|     "VK_OEM_FJ_LOYA",
 | |
|     "VK_OEM_FJ_ROYA",
 | |
|     "0x97",
 | |
|     "0x98",
 | |
|     "0x99",
 | |
|     "0x9A",
 | |
|     "0x9B",
 | |
|     "0x9C",
 | |
|     "0x9D",
 | |
|     "0x9E",
 | |
|     "0x9F",
 | |
| 
 | |
|     "VK_LSHIFT",
 | |
|     "VK_RSHIFT",
 | |
|     "VK_LCONTROL",
 | |
|     "VK_RCONTROL",
 | |
|     "VK_LMENU",
 | |
|     "VK_RMENU",
 | |
|     "VK_BROWSER_BACK",
 | |
|     "VK_BROWSER_FORWARD",
 | |
|     "VK_BROWSER_REFRESH",
 | |
|     "VK_BROWSER_STOP",
 | |
|     "VK_BROWSER_SEARCH",
 | |
|     "VK_BROWSER_FAVORITES",
 | |
|     "VK_BROWSER_HOME",
 | |
|     "VK_VOLUME_MUTE",
 | |
|     "VK_VOLUME_DOWN",
 | |
|     "VK_VOLUME_UP",
 | |
| 
 | |
|     "VK_MEDIA_NEXT_TRACK",
 | |
|     "VK_MEDIA_PREV_TRACK",
 | |
|     "VK_MEDIA_STOP",
 | |
|     "VK_MEDIA_PLAY_PAUSE",
 | |
|     "VK_LAUNCH_MAIL",
 | |
|     "VK_LAUNCH_MEDIA_SELECT",
 | |
|     "VK_LAUNCH_APP1",
 | |
|     "VK_LAUNCH_APP2",
 | |
|     "0xB8",
 | |
|     "0xB9",
 | |
|     "VK_OEM_1",
 | |
|     "VK_OEM_PLUS",
 | |
|     "VK_OEM_COMMA",
 | |
|     "VK_OEM_MINUS",
 | |
|     "VK_OEM_PERIOD",
 | |
|     "VK_OEM_2",
 | |
| 
 | |
|     "VK_OEM_3",
 | |
|     "VK_ABNT_C1",
 | |
|     "VK_ABNT_C2",
 | |
|     "0xC3",
 | |
|     "0xC4",
 | |
|     "0xC5",
 | |
|     "0xC6",
 | |
|     "0xC7",
 | |
|     "0xC8",
 | |
|     "0xC9",
 | |
|     "0xCA",
 | |
|     "0xCB",
 | |
|     "0xCC",
 | |
|     "0xCD",
 | |
|     "0xCE",
 | |
|     "0xCF",
 | |
| 
 | |
|     "0xD0",
 | |
|     "0xD1",
 | |
|     "0xD2",
 | |
|     "0xD3",
 | |
|     "0xD4",
 | |
|     "0xD5",
 | |
|     "0xD6",
 | |
|     "0xD7",
 | |
|     "0xD8",
 | |
|     "0xD9",
 | |
|     "0xDA",
 | |
|     "VK_OEM_4",
 | |
|     "VK_OEM_5",
 | |
|     "VK_OEM_6",
 | |
|     "VK_OEM_7",
 | |
|     "VK_OEM_8",
 | |
| 
 | |
|     "0xE0",
 | |
|     "VK_OEM_AX",
 | |
|     "VK_OEM_102",
 | |
|     "VK_ICO_HELP",
 | |
|     "VK_ICO_00",
 | |
|     "VK_PROCESSKEY",
 | |
|     "VK_ICO_CLEAR",
 | |
|     "VK_PACKET",
 | |
|     "0xE8",
 | |
|     "VK_OEM_RESET",
 | |
|     "VK_OEM_JUMP",
 | |
|     "VK_OEM_PA1",
 | |
|     "VK_OEM_PA2",
 | |
|     "VK_OEM_PA3",
 | |
|     "VK_OEM_WSCTRL",
 | |
|     "VK_OEM_CUSEL",
 | |
| 
 | |
|     "VK_OEM_ATTN",
 | |
|     "VK_OEM_FINISH",
 | |
|     "VK_OEM_COPY",
 | |
|     "VK_OEM_AUTO",
 | |
|     "VK_OEM_ENLW",
 | |
|     "VK_OEM_BACKTAB",
 | |
|     "VK_ATTN",
 | |
|     "VK_CRSEL",
 | |
|     "VK_EXSEL",
 | |
|     "VK_EREOF",
 | |
|     "VK_PLAY",
 | |
|     "VK_ZOOM",
 | |
|     "VK_NONAME",
 | |
|     "VK_PA1",
 | |
|     "VK_OEM_CLEAR",
 | |
|     "0xFF"};
 | |
| 
 | |
| static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100,
 | |
|               "The virtual key name must be defined just 256 keys");
 | |
| 
 | |
| static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
 | |
| 
 | |
| static const nsCString GetCharacterCodeName(WPARAM aCharCode) {
 | |
|   switch (aCharCode) {
 | |
|     case 0x0000:
 | |
|       return "NULL (0x0000)"_ns;
 | |
|     case 0x0008:
 | |
|       return "BACKSPACE (0x0008)"_ns;
 | |
|     case 0x0009:
 | |
|       return "CHARACTER TABULATION (0x0009)"_ns;
 | |
|     case 0x000A:
 | |
|       return "LINE FEED (0x000A)"_ns;
 | |
|     case 0x000B:
 | |
|       return "LINE TABULATION (0x000B)"_ns;
 | |
|     case 0x000C:
 | |
|       return "FORM FEED (0x000C)"_ns;
 | |
|     case 0x000D:
 | |
|       return "CARRIAGE RETURN (0x000D)"_ns;
 | |
|     case 0x0018:
 | |
|       return "CANCEL (0x0018)"_ns;
 | |
|     case 0x001B:
 | |
|       return "ESCAPE (0x001B)"_ns;
 | |
|     case 0x0020:
 | |
|       return "SPACE (0x0020)"_ns;
 | |
|     case 0x007F:
 | |
|       return "DELETE (0x007F)"_ns;
 | |
|     case 0x00A0:
 | |
|       return "NO-BREAK SPACE (0x00A0)"_ns;
 | |
|     case 0x00AD:
 | |
|       return "SOFT HYPHEN (0x00AD)"_ns;
 | |
|     case 0x2000:
 | |
|       return "EN QUAD (0x2000)"_ns;
 | |
|     case 0x2001:
 | |
|       return "EM QUAD (0x2001)"_ns;
 | |
|     case 0x2002:
 | |
|       return "EN SPACE (0x2002)"_ns;
 | |
|     case 0x2003:
 | |
|       return "EM SPACE (0x2003)"_ns;
 | |
|     case 0x2004:
 | |
|       return "THREE-PER-EM SPACE (0x2004)"_ns;
 | |
|     case 0x2005:
 | |
|       return "FOUR-PER-EM SPACE (0x2005)"_ns;
 | |
|     case 0x2006:
 | |
|       return "SIX-PER-EM SPACE (0x2006)"_ns;
 | |
|     case 0x2007:
 | |
|       return "FIGURE SPACE (0x2007)"_ns;
 | |
|     case 0x2008:
 | |
|       return "PUNCTUATION SPACE (0x2008)"_ns;
 | |
|     case 0x2009:
 | |
|       return "THIN SPACE (0x2009)"_ns;
 | |
|     case 0x200A:
 | |
|       return "HAIR SPACE (0x200A)"_ns;
 | |
|     case 0x200B:
 | |
|       return "ZERO WIDTH SPACE (0x200B)"_ns;
 | |
|     case 0x200C:
 | |
|       return "ZERO WIDTH NON-JOINER (0x200C)"_ns;
 | |
|     case 0x200D:
 | |
|       return "ZERO WIDTH JOINER (0x200D)"_ns;
 | |
|     case 0x200E:
 | |
|       return "LEFT-TO-RIGHT MARK (0x200E)"_ns;
 | |
|     case 0x200F:
 | |
|       return "RIGHT-TO-LEFT MARK (0x200F)"_ns;
 | |
|     case 0x2029:
 | |
|       return "PARAGRAPH SEPARATOR (0x2029)"_ns;
 | |
|     case 0x202A:
 | |
|       return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns;
 | |
|     case 0x202B:
 | |
|       return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns;
 | |
|     case 0x202D:
 | |
|       return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns;
 | |
|     case 0x202E:
 | |
|       return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns;
 | |
|     case 0x202F:
 | |
|       return "NARROW NO-BREAK SPACE (0x202F)"_ns;
 | |
|     case 0x205F:
 | |
|       return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns;
 | |
|     case 0x2060:
 | |
|       return "WORD JOINER (0x2060)"_ns;
 | |
|     case 0x2066:
 | |
|       return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns;
 | |
|     case 0x2067:
 | |
|       return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns;
 | |
|     case 0x3000:
 | |
|       return "IDEOGRAPHIC SPACE (0x3000)"_ns;
 | |
|     case 0xFEFF:
 | |
|       return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns;
 | |
|     default: {
 | |
|       if (aCharCode < ' ' || (aCharCode >= 0x80 && aCharCode < 0xA0)) {
 | |
|         return nsPrintfCString("control (0x%04X)", aCharCode);
 | |
|       }
 | |
|       if (NS_IS_HIGH_SURROGATE(aCharCode)) {
 | |
|         return nsPrintfCString("high surrogate (0x%04X)", aCharCode);
 | |
|       }
 | |
|       if (NS_IS_LOW_SURROGATE(aCharCode)) {
 | |
|         return nsPrintfCString("low surrogate (0x%04X)", aCharCode);
 | |
|       }
 | |
|       return IS_IN_BMP(aCharCode)
 | |
|                  ? nsPrintfCString(
 | |
|                        "'%s' (0x%04X)",
 | |
|                        NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
 | |
|                        aCharCode)
 | |
|                  : nsPrintfCString(
 | |
|                        "'%s' (0x%08X)",
 | |
|                        NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
 | |
|                        aCharCode);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static const nsCString GetKeyLocationName(uint32_t aLocation) {
 | |
|   switch (aLocation) {
 | |
|     case eKeyLocationLeft:
 | |
|       return "KEY_LOCATION_LEFT"_ns;
 | |
|     case eKeyLocationRight:
 | |
|       return "KEY_LOCATION_RIGHT"_ns;
 | |
|     case eKeyLocationStandard:
 | |
|       return "KEY_LOCATION_STANDARD"_ns;
 | |
|     case eKeyLocationNumpad:
 | |
|       return "KEY_LOCATION_NUMPAD"_ns;
 | |
|     default:
 | |
|       return nsPrintfCString("Unknown (0x%04X)", aLocation);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static const nsCString GetCharacterCodeNames(const char16_t* aChars,
 | |
|                                              uint32_t aLength) {
 | |
|   if (!aLength) {
 | |
|     return ""_ns;
 | |
|   }
 | |
|   nsCString result;
 | |
|   result.AssignLiteral("\"");
 | |
|   StringJoinAppend(result, ", "_ns, Span{aChars, aLength},
 | |
|                    [](nsACString& dest, const char16_t charValue) {
 | |
|                      dest.Append(GetCharacterCodeName(charValue));
 | |
|                    });
 | |
|   result.AppendLiteral("\"");
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| static const nsCString GetCharacterCodeNames(
 | |
|     const UniCharsAndModifiers& aUniCharsAndModifiers) {
 | |
|   if (aUniCharsAndModifiers.IsEmpty()) {
 | |
|     return ""_ns;
 | |
|   }
 | |
|   nsCString result;
 | |
|   result.AssignLiteral("\"");
 | |
|   StringJoinAppend(result, ", "_ns, Span{aUniCharsAndModifiers.ToString()},
 | |
|                    [](nsACString& dest, const char16_t charValue) {
 | |
|                      dest.Append(GetCharacterCodeName(charValue));
 | |
|                    });
 | |
|   result.AppendLiteral("\"");
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString {
 | |
|  public:
 | |
|   explicit GetShiftStateName(VirtualKey::ShiftState aShiftState) {
 | |
|     if (!aShiftState) {
 | |
|       AssignLiteral("none");
 | |
|       return;
 | |
|     }
 | |
|     if (aShiftState & VirtualKey::STATE_SHIFT) {
 | |
|       AssignLiteral("Shift");
 | |
|       aShiftState &= ~VirtualKey::STATE_SHIFT;
 | |
|     }
 | |
|     if (aShiftState & VirtualKey::STATE_CONTROL) {
 | |
|       MaybeAppendSeparator();
 | |
|       AssignLiteral("Ctrl");
 | |
|       aShiftState &= ~VirtualKey::STATE_CONTROL;
 | |
|     }
 | |
|     if (aShiftState & VirtualKey::STATE_ALT) {
 | |
|       MaybeAppendSeparator();
 | |
|       AssignLiteral("Alt");
 | |
|       aShiftState &= ~VirtualKey::STATE_ALT;
 | |
|     }
 | |
|     if (aShiftState & VirtualKey::STATE_CAPSLOCK) {
 | |
|       MaybeAppendSeparator();
 | |
|       AssignLiteral("CapsLock");
 | |
|       aShiftState &= ~VirtualKey::STATE_CAPSLOCK;
 | |
|     }
 | |
|     MOZ_ASSERT(!aShiftState);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   void MaybeAppendSeparator() {
 | |
|     if (!IsEmpty()) {
 | |
|       AppendLiteral(" | ");
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| static const nsCString GetMessageName(UINT aMessage) {
 | |
|   switch (aMessage) {
 | |
|     case WM_NULL:
 | |
|       return "WM_NULL"_ns;
 | |
|     case WM_KEYDOWN:
 | |
|       return "WM_KEYDOWN"_ns;
 | |
|     case WM_KEYUP:
 | |
|       return "WM_KEYUP"_ns;
 | |
|     case WM_SYSKEYDOWN:
 | |
|       return "WM_SYSKEYDOWN"_ns;
 | |
|     case WM_SYSKEYUP:
 | |
|       return "WM_SYSKEYUP"_ns;
 | |
|     case WM_CHAR:
 | |
|       return "WM_CHAR"_ns;
 | |
|     case WM_UNICHAR:
 | |
|       return "WM_UNICHAR"_ns;
 | |
|     case WM_SYSCHAR:
 | |
|       return "WM_SYSCHAR"_ns;
 | |
|     case WM_DEADCHAR:
 | |
|       return "WM_DEADCHAR"_ns;
 | |
|     case WM_SYSDEADCHAR:
 | |
|       return "WM_SYSDEADCHAR"_ns;
 | |
|     case WM_APPCOMMAND:
 | |
|       return "WM_APPCOMMAND"_ns;
 | |
|     case WM_QUIT:
 | |
|       return "WM_QUIT"_ns;
 | |
|     default:
 | |
|       return nsPrintfCString("Unknown Message (0x%04X)", aMessage);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static const nsCString GetVirtualKeyCodeName(WPARAM aVK) {
 | |
|   if (aVK >= ArrayLength(kVirtualKeyName)) {
 | |
|     return nsPrintfCString("Invalid (0x%08X)", aVK);
 | |
|   }
 | |
|   return nsCString(kVirtualKeyName[aVK]);
 | |
| }
 | |
| 
 | |
| static const nsCString GetAppCommandName(WPARAM aCommand) {
 | |
|   switch (aCommand) {
 | |
|     case APPCOMMAND_BASS_BOOST:
 | |
|       return "APPCOMMAND_BASS_BOOST"_ns;
 | |
|     case APPCOMMAND_BASS_DOWN:
 | |
|       return "APPCOMMAND_BASS_DOWN"_ns;
 | |
|     case APPCOMMAND_BASS_UP:
 | |
|       return "APPCOMMAND_BASS_UP"_ns;
 | |
|     case APPCOMMAND_BROWSER_BACKWARD:
 | |
|       return "APPCOMMAND_BROWSER_BACKWARD"_ns;
 | |
|     case APPCOMMAND_BROWSER_FAVORITES:
 | |
|       return "APPCOMMAND_BROWSER_FAVORITES"_ns;
 | |
|     case APPCOMMAND_BROWSER_FORWARD:
 | |
|       return "APPCOMMAND_BROWSER_FORWARD"_ns;
 | |
|     case APPCOMMAND_BROWSER_HOME:
 | |
|       return "APPCOMMAND_BROWSER_HOME"_ns;
 | |
|     case APPCOMMAND_BROWSER_REFRESH:
 | |
|       return "APPCOMMAND_BROWSER_REFRESH"_ns;
 | |
|     case APPCOMMAND_BROWSER_SEARCH:
 | |
|       return "APPCOMMAND_BROWSER_SEARCH"_ns;
 | |
|     case APPCOMMAND_BROWSER_STOP:
 | |
|       return "APPCOMMAND_BROWSER_STOP"_ns;
 | |
|     case APPCOMMAND_CLOSE:
 | |
|       return "APPCOMMAND_CLOSE"_ns;
 | |
|     case APPCOMMAND_COPY:
 | |
|       return "APPCOMMAND_COPY"_ns;
 | |
|     case APPCOMMAND_CORRECTION_LIST:
 | |
|       return "APPCOMMAND_CORRECTION_LIST"_ns;
 | |
|     case APPCOMMAND_CUT:
 | |
|       return "APPCOMMAND_CUT"_ns;
 | |
|     case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
 | |
|       return "APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE"_ns;
 | |
|     case APPCOMMAND_FIND:
 | |
|       return "APPCOMMAND_FIND"_ns;
 | |
|     case APPCOMMAND_FORWARD_MAIL:
 | |
|       return "APPCOMMAND_FORWARD_MAIL"_ns;
 | |
|     case APPCOMMAND_HELP:
 | |
|       return "APPCOMMAND_HELP"_ns;
 | |
|     case APPCOMMAND_LAUNCH_APP1:
 | |
|       return "APPCOMMAND_LAUNCH_APP1"_ns;
 | |
|     case APPCOMMAND_LAUNCH_APP2:
 | |
|       return "APPCOMMAND_LAUNCH_APP2"_ns;
 | |
|     case APPCOMMAND_LAUNCH_MAIL:
 | |
|       return "APPCOMMAND_LAUNCH_MAIL"_ns;
 | |
|     case APPCOMMAND_LAUNCH_MEDIA_SELECT:
 | |
|       return "APPCOMMAND_LAUNCH_MEDIA_SELECT"_ns;
 | |
|     case APPCOMMAND_MEDIA_CHANNEL_DOWN:
 | |
|       return "APPCOMMAND_MEDIA_CHANNEL_DOWN"_ns;
 | |
|     case APPCOMMAND_MEDIA_CHANNEL_UP:
 | |
|       return "APPCOMMAND_MEDIA_CHANNEL_UP"_ns;
 | |
|     case APPCOMMAND_MEDIA_FAST_FORWARD:
 | |
|       return "APPCOMMAND_MEDIA_FAST_FORWARD"_ns;
 | |
|     case APPCOMMAND_MEDIA_NEXTTRACK:
 | |
|       return "APPCOMMAND_MEDIA_NEXTTRACK"_ns;
 | |
|     case APPCOMMAND_MEDIA_PAUSE:
 | |
|       return "APPCOMMAND_MEDIA_PAUSE"_ns;
 | |
|     case APPCOMMAND_MEDIA_PLAY:
 | |
|       return "APPCOMMAND_MEDIA_PLAY"_ns;
 | |
|     case APPCOMMAND_MEDIA_PLAY_PAUSE:
 | |
|       return "APPCOMMAND_MEDIA_PLAY_PAUSE"_ns;
 | |
|     case APPCOMMAND_MEDIA_PREVIOUSTRACK:
 | |
|       return "APPCOMMAND_MEDIA_PREVIOUSTRACK"_ns;
 | |
|     case APPCOMMAND_MEDIA_RECORD:
 | |
|       return "APPCOMMAND_MEDIA_RECORD"_ns;
 | |
|     case APPCOMMAND_MEDIA_REWIND:
 | |
|       return "APPCOMMAND_MEDIA_REWIND"_ns;
 | |
|     case APPCOMMAND_MEDIA_STOP:
 | |
|       return "APPCOMMAND_MEDIA_STOP"_ns;
 | |
|     case APPCOMMAND_MIC_ON_OFF_TOGGLE:
 | |
|       return "APPCOMMAND_MIC_ON_OFF_TOGGLE"_ns;
 | |
|     case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
 | |
|       return "APPCOMMAND_MICROPHONE_VOLUME_DOWN"_ns;
 | |
|     case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
 | |
|       return "APPCOMMAND_MICROPHONE_VOLUME_MUTE"_ns;
 | |
|     case APPCOMMAND_MICROPHONE_VOLUME_UP:
 | |
|       return "APPCOMMAND_MICROPHONE_VOLUME_UP"_ns;
 | |
|     case APPCOMMAND_NEW:
 | |
|       return "APPCOMMAND_NEW"_ns;
 | |
|     case APPCOMMAND_OPEN:
 | |
|       return "APPCOMMAND_OPEN"_ns;
 | |
|     case APPCOMMAND_PASTE:
 | |
|       return "APPCOMMAND_PASTE"_ns;
 | |
|     case APPCOMMAND_PRINT:
 | |
|       return "APPCOMMAND_PRINT"_ns;
 | |
|     case APPCOMMAND_REDO:
 | |
|       return "APPCOMMAND_REDO"_ns;
 | |
|     case APPCOMMAND_REPLY_TO_MAIL:
 | |
|       return "APPCOMMAND_REPLY_TO_MAIL"_ns;
 | |
|     case APPCOMMAND_SAVE:
 | |
|       return "APPCOMMAND_SAVE"_ns;
 | |
|     case APPCOMMAND_SEND_MAIL:
 | |
|       return "APPCOMMAND_SEND_MAIL"_ns;
 | |
|     case APPCOMMAND_SPELL_CHECK:
 | |
|       return "APPCOMMAND_SPELL_CHECK"_ns;
 | |
|     case APPCOMMAND_TREBLE_DOWN:
 | |
|       return "APPCOMMAND_TREBLE_DOWN"_ns;
 | |
|     case APPCOMMAND_TREBLE_UP:
 | |
|       return "APPCOMMAND_TREBLE_UP"_ns;
 | |
|     case APPCOMMAND_UNDO:
 | |
|       return "APPCOMMAND_UNDO"_ns;
 | |
|     case APPCOMMAND_VOLUME_DOWN:
 | |
|       return "APPCOMMAND_VOLUME_DOWN"_ns;
 | |
|     case APPCOMMAND_VOLUME_MUTE:
 | |
|       return "APPCOMMAND_VOLUME_MUTE"_ns;
 | |
|     case APPCOMMAND_VOLUME_UP:
 | |
|       return "APPCOMMAND_VOLUME_UP"_ns;
 | |
|     default:
 | |
|       return nsPrintfCString("Unknown app command (0x%08X)", aCommand);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static const nsCString GetAppCommandDeviceName(LPARAM aDevice) {
 | |
|   switch (aDevice) {
 | |
|     case FAPPCOMMAND_KEY:
 | |
|       return "FAPPCOMMAND_KEY"_ns;
 | |
|     case FAPPCOMMAND_MOUSE:
 | |
|       return "FAPPCOMMAND_MOUSE"_ns;
 | |
|     case FAPPCOMMAND_OEM:
 | |
|       return "FAPPCOMMAND_OEM"_ns;
 | |
|     default:
 | |
|       return nsPrintfCString("Unknown app command device (0x%04X)", aDevice);
 | |
|   }
 | |
| };
 | |
| 
 | |
| class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString {
 | |
|  public:
 | |
|   explicit GetAppCommandKeysName(WPARAM aKeys) {
 | |
|     if (aKeys & MK_CONTROL) {
 | |
|       AppendLiteral("MK_CONTROL");
 | |
|       aKeys &= ~MK_CONTROL;
 | |
|     }
 | |
|     if (aKeys & MK_LBUTTON) {
 | |
|       MaybeAppendSeparator();
 | |
|       AppendLiteral("MK_LBUTTON");
 | |
|       aKeys &= ~MK_LBUTTON;
 | |
|     }
 | |
|     if (aKeys & MK_MBUTTON) {
 | |
|       MaybeAppendSeparator();
 | |
|       AppendLiteral("MK_MBUTTON");
 | |
|       aKeys &= ~MK_MBUTTON;
 | |
|     }
 | |
|     if (aKeys & MK_RBUTTON) {
 | |
|       MaybeAppendSeparator();
 | |
|       AppendLiteral("MK_RBUTTON");
 | |
|       aKeys &= ~MK_RBUTTON;
 | |
|     }
 | |
|     if (aKeys & MK_SHIFT) {
 | |
|       MaybeAppendSeparator();
 | |
|       AppendLiteral("MK_SHIFT");
 | |
|       aKeys &= ~MK_SHIFT;
 | |
|     }
 | |
|     if (aKeys & MK_XBUTTON1) {
 | |
|       MaybeAppendSeparator();
 | |
|       AppendLiteral("MK_XBUTTON1");
 | |
|       aKeys &= ~MK_XBUTTON1;
 | |
|     }
 | |
|     if (aKeys & MK_XBUTTON2) {
 | |
|       MaybeAppendSeparator();
 | |
|       AppendLiteral("MK_XBUTTON2");
 | |
|       aKeys &= ~MK_XBUTTON2;
 | |
|     }
 | |
|     if (aKeys) {
 | |
|       MaybeAppendSeparator();
 | |
|       AppendPrintf("Unknown Flags (0x%04X)", aKeys);
 | |
|     }
 | |
|     if (IsEmpty()) {
 | |
|       AssignLiteral("none (0x0000)");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   void MaybeAppendSeparator() {
 | |
|     if (!IsEmpty()) {
 | |
|       AppendLiteral(" | ");
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| static const nsCString ToString(const MSG& aMSG) {
 | |
|   nsCString result;
 | |
|   result.AssignLiteral("{ message=");
 | |
|   result.Append(GetMessageName(aMSG.message).get());
 | |
|   result.AppendLiteral(", ");
 | |
|   switch (aMSG.message) {
 | |
|     case WM_KEYDOWN:
 | |
|     case WM_KEYUP:
 | |
|     case WM_SYSKEYDOWN:
 | |
|     case WM_SYSKEYUP:
 | |
|       result.AppendPrintf(
 | |
|           "virtual keycode=%s, repeat count=%d, "
 | |
|           "scancode=0x%02X, extended key=%s, "
 | |
|           "context code=%s, previous key state=%s, "
 | |
|           "transition state=%s",
 | |
|           GetVirtualKeyCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
 | |
|           WinUtils::GetScanCode(aMSG.lParam),
 | |
|           GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
 | |
|           GetBoolName((aMSG.lParam & (1 << 29)) != 0),
 | |
|           GetBoolName((aMSG.lParam & (1 << 30)) != 0),
 | |
|           GetBoolName((aMSG.lParam & (1 << 31)) != 0));
 | |
|       break;
 | |
|     case WM_CHAR:
 | |
|     case WM_DEADCHAR:
 | |
|     case WM_SYSCHAR:
 | |
|     case WM_SYSDEADCHAR:
 | |
|       result.AppendPrintf(
 | |
|           "character code=%s, repeat count=%d, "
 | |
|           "scancode=0x%02X, extended key=%s, "
 | |
|           "context code=%s, previous key state=%s, "
 | |
|           "transition state=%s",
 | |
|           GetCharacterCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
 | |
|           WinUtils::GetScanCode(aMSG.lParam),
 | |
|           GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
 | |
|           GetBoolName((aMSG.lParam & (1 << 29)) != 0),
 | |
|           GetBoolName((aMSG.lParam & (1 << 30)) != 0),
 | |
|           GetBoolName((aMSG.lParam & (1 << 31)) != 0));
 | |
|       break;
 | |
|     case WM_APPCOMMAND:
 | |
|       result.AppendPrintf(
 | |
|           "window handle=0x%p, app command=%s, device=%s, dwKeys=%s",
 | |
|           aMSG.wParam,
 | |
|           GetAppCommandName(GET_APPCOMMAND_LPARAM(aMSG.lParam)).get(),
 | |
|           GetAppCommandDeviceName(GET_DEVICE_LPARAM(aMSG.lParam)).get(),
 | |
|           GetAppCommandKeysName(GET_KEYSTATE_LPARAM(aMSG.lParam)).get());
 | |
|       break;
 | |
|     default:
 | |
|       result.AppendPrintf("wParam=%u, lParam=%u", aMSG.wParam, aMSG.lParam);
 | |
|       break;
 | |
|   }
 | |
|   result.AppendPrintf(", hwnd=0x%p", aMSG.hwnd);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| static const nsCString ToString(
 | |
|     const UniCharsAndModifiers& aUniCharsAndModifiers) {
 | |
|   if (aUniCharsAndModifiers.IsEmpty()) {
 | |
|     return "{}"_ns;
 | |
|   }
 | |
|   nsCString result;
 | |
|   result.AssignLiteral("{ ");
 | |
|   result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(0)));
 | |
|   for (size_t i = 1; i < aUniCharsAndModifiers.Length(); ++i) {
 | |
|     if (aUniCharsAndModifiers.ModifiersAt(i - 1) !=
 | |
|         aUniCharsAndModifiers.ModifiersAt(i)) {
 | |
|       result.AppendLiteral(" [");
 | |
|       result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(0)));
 | |
|       result.AppendLiteral("]");
 | |
|     }
 | |
|     result.AppendLiteral(", ");
 | |
|     result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(i)));
 | |
|   }
 | |
|   result.AppendLiteral(" [");
 | |
|   uint32_t lastIndex = aUniCharsAndModifiers.Length() - 1;
 | |
|   result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(lastIndex)));
 | |
|   result.AppendLiteral("] }");
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| const nsCString ToString(const ModifierKeyState& aModifierKeyState) {
 | |
|   nsCString result;
 | |
|   result.AssignLiteral("{ ");
 | |
|   result.Append(GetModifiersName(aModifierKeyState.GetModifiers()).get());
 | |
|   result.AppendLiteral(" }");
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // Unique id counter associated with a keydown / keypress events. Used in
 | |
| // identifing keypress events for removal from async event dispatch queue
 | |
| // in metrofx after preventDefault is called on keydown events.
 | |
| static uint32_t sUniqueKeyEventId = 0;
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::widget::ModifierKeyState
 | |
|  *****************************************************************************/
 | |
| 
 | |
| ModifierKeyState::ModifierKeyState() { Update(); }
 | |
| 
 | |
| ModifierKeyState::ModifierKeyState(Modifiers aModifiers)
 | |
|     : mModifiers(aModifiers) {
 | |
|   MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
 | |
|              "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
 | |
|              "if MODIFIER_ALTGRAPH is set");
 | |
| }
 | |
| 
 | |
| void ModifierKeyState::Update() {
 | |
|   mModifiers = 0;
 | |
|   if (IS_VK_DOWN(VK_SHIFT)) {
 | |
|     mModifiers |= MODIFIER_SHIFT;
 | |
|   }
 | |
|   // If AltGr key (i.e., VK_RMENU on some keyboard layout) is pressed, only
 | |
|   // MODIFIER_ALTGRAPH should be set.  Otherwise, i.e., if both Ctrl and Alt
 | |
|   // keys are pressed to emulate AltGr key, MODIFIER_CONTROL and MODIFIER_ALT
 | |
|   // keys should be set separately.
 | |
|   if (KeyboardLayout::GetInstance()->HasAltGr() && IS_VK_DOWN(VK_RMENU)) {
 | |
|     mModifiers |= MODIFIER_ALTGRAPH;
 | |
|   } else {
 | |
|     if (IS_VK_DOWN(VK_CONTROL)) {
 | |
|       mModifiers |= MODIFIER_CONTROL;
 | |
|     }
 | |
|     if (IS_VK_DOWN(VK_MENU)) {
 | |
|       mModifiers |= MODIFIER_ALT;
 | |
|     }
 | |
|   }
 | |
|   if (IS_VK_DOWN(VK_LWIN) || IS_VK_DOWN(VK_RWIN)) {
 | |
|     mModifiers |= MODIFIER_OS;
 | |
|   }
 | |
|   if (::GetKeyState(VK_CAPITAL) & 1) {
 | |
|     mModifiers |= MODIFIER_CAPSLOCK;
 | |
|   }
 | |
|   if (::GetKeyState(VK_NUMLOCK) & 1) {
 | |
|     mModifiers |= MODIFIER_NUMLOCK;
 | |
|   }
 | |
|   if (::GetKeyState(VK_SCROLL) & 1) {
 | |
|     mModifiers |= MODIFIER_SCROLLLOCK;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ModifierKeyState::Unset(Modifiers aRemovingModifiers) {
 | |
|   mModifiers &= ~aRemovingModifiers;
 | |
| }
 | |
| 
 | |
| void ModifierKeyState::Set(Modifiers aAddingModifiers) {
 | |
|   mModifiers |= aAddingModifiers;
 | |
|   MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
 | |
|              "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
 | |
|              "if MODIFIER_ALTGRAPH is set");
 | |
| }
 | |
| 
 | |
| void ModifierKeyState::InitInputEvent(WidgetInputEvent& aInputEvent) const {
 | |
|   aInputEvent.mModifiers = mModifiers;
 | |
| 
 | |
|   switch (aInputEvent.mClass) {
 | |
|     case eMouseEventClass:
 | |
|     case eMouseScrollEventClass:
 | |
|     case eWheelEventClass:
 | |
|     case eDragEventClass:
 | |
|     case eSimpleGestureEventClass:
 | |
|       InitMouseEvent(aInputEvent);
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const {
 | |
|   NS_ASSERTION(aMouseEvent.mClass == eMouseEventClass ||
 | |
|                    aMouseEvent.mClass == eWheelEventClass ||
 | |
|                    aMouseEvent.mClass == eDragEventClass ||
 | |
|                    aMouseEvent.mClass == eSimpleGestureEventClass,
 | |
|                "called with non-mouse event");
 | |
| 
 | |
|   WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase();
 | |
|   mouseEvent.mButtons = 0;
 | |
|   if (::GetKeyState(VK_LBUTTON) < 0) {
 | |
|     mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag;
 | |
|   }
 | |
|   if (::GetKeyState(VK_RBUTTON) < 0) {
 | |
|     mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag;
 | |
|   }
 | |
|   if (::GetKeyState(VK_MBUTTON) < 0) {
 | |
|     mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag;
 | |
|   }
 | |
|   if (::GetKeyState(VK_XBUTTON1) < 0) {
 | |
|     mouseEvent.mButtons |= MouseButtonsFlag::e4thFlag;
 | |
|   }
 | |
|   if (::GetKeyState(VK_XBUTTON2) < 0) {
 | |
|     mouseEvent.mButtons |= MouseButtonsFlag::e5thFlag;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool ModifierKeyState::IsShift() const {
 | |
|   return (mModifiers & MODIFIER_SHIFT) != 0;
 | |
| }
 | |
| 
 | |
| bool ModifierKeyState::IsControl() const {
 | |
|   return (mModifiers & MODIFIER_CONTROL) != 0;
 | |
| }
 | |
| 
 | |
| bool ModifierKeyState::IsAlt() const {
 | |
|   return (mModifiers & MODIFIER_ALT) != 0;
 | |
| }
 | |
| 
 | |
| bool ModifierKeyState::IsWin() const { return (mModifiers & MODIFIER_OS) != 0; }
 | |
| 
 | |
| bool ModifierKeyState::MaybeMatchShortcutKey() const {
 | |
|   // If Windows key is pressed, even if both Ctrl key and Alt key are pressed,
 | |
|   // it's possible to match a shortcut key.
 | |
|   if (IsWin()) {
 | |
|     return true;
 | |
|   }
 | |
|   // Otherwise, when both Ctrl key and Alt key are pressed, it shouldn't be
 | |
|   // a shortcut key for Windows since it means pressing AltGr key on
 | |
|   // some keyboard layouts.
 | |
|   if (IsControl() ^ IsAlt()) {
 | |
|     return true;
 | |
|   }
 | |
|   // If no modifier key is active except a lockable modifier nor Shift key,
 | |
|   // the key shouldn't match any shortcut keys (there are Space and
 | |
|   // Shift+Space, though, let's ignore these special case...).
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool ModifierKeyState::IsCapsLocked() const {
 | |
|   return (mModifiers & MODIFIER_CAPSLOCK) != 0;
 | |
| }
 | |
| 
 | |
| bool ModifierKeyState::IsNumLocked() const {
 | |
|   return (mModifiers & MODIFIER_NUMLOCK) != 0;
 | |
| }
 | |
| 
 | |
| bool ModifierKeyState::IsScrollLocked() const {
 | |
|   return (mModifiers & MODIFIER_SCROLLLOCK) != 0;
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::widget::UniCharsAndModifiers
 | |
|  *****************************************************************************/
 | |
| 
 | |
| void UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers) {
 | |
|   mChars.Append(aUniChar);
 | |
|   mModifiers.AppendElement(aModifiers);
 | |
| }
 | |
| 
 | |
| void UniCharsAndModifiers::FillModifiers(Modifiers aModifiers) {
 | |
|   for (size_t i = 0; i < Length(); i++) {
 | |
|     mModifiers[i] = aModifiers;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void UniCharsAndModifiers::OverwriteModifiersIfBeginsWith(
 | |
|     const UniCharsAndModifiers& aOther) {
 | |
|   if (!BeginsWith(aOther)) {
 | |
|     return;
 | |
|   }
 | |
|   for (size_t i = 0; i < aOther.Length(); ++i) {
 | |
|     mModifiers[i] = aOther.mModifiers[i];
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool UniCharsAndModifiers::UniCharsEqual(
 | |
|     const UniCharsAndModifiers& aOther) const {
 | |
|   return mChars.Equals(aOther.mChars);
 | |
| }
 | |
| 
 | |
| bool UniCharsAndModifiers::UniCharsCaseInsensitiveEqual(
 | |
|     const UniCharsAndModifiers& aOther) const {
 | |
|   return mChars.Equals(aOther.mChars, nsCaseInsensitiveStringComparator);
 | |
| }
 | |
| 
 | |
| bool UniCharsAndModifiers::BeginsWith(
 | |
|     const UniCharsAndModifiers& aOther) const {
 | |
|   return StringBeginsWith(mChars, aOther.mChars);
 | |
| }
 | |
| 
 | |
| UniCharsAndModifiers& UniCharsAndModifiers::operator+=(
 | |
|     const UniCharsAndModifiers& aOther) {
 | |
|   mChars.Append(aOther.mChars);
 | |
|   mModifiers.AppendElements(aOther.mModifiers);
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| UniCharsAndModifiers UniCharsAndModifiers::operator+(
 | |
|     const UniCharsAndModifiers& aOther) const {
 | |
|   UniCharsAndModifiers result(*this);
 | |
|   result += aOther;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::widget::VirtualKey
 | |
|  *****************************************************************************/
 | |
| 
 | |
| // static
 | |
| VirtualKey::ShiftState VirtualKey::ModifiersToShiftState(Modifiers aModifiers) {
 | |
|   ShiftState state = 0;
 | |
|   if (aModifiers & MODIFIER_SHIFT) {
 | |
|     state |= STATE_SHIFT;
 | |
|   }
 | |
|   if (aModifiers & MODIFIER_ALTGRAPH) {
 | |
|     state |= STATE_ALTGRAPH;
 | |
|   } else {
 | |
|     if (aModifiers & MODIFIER_CONTROL) {
 | |
|       state |= STATE_CONTROL;
 | |
|     }
 | |
|     if (aModifiers & MODIFIER_ALT) {
 | |
|       state |= STATE_ALT;
 | |
|     }
 | |
|   }
 | |
|   if (aModifiers & MODIFIER_CAPSLOCK) {
 | |
|     state |= STATE_CAPSLOCK;
 | |
|   }
 | |
|   return state;
 | |
| }
 | |
| 
 | |
| // static
 | |
| Modifiers VirtualKey::ShiftStateToModifiers(ShiftState aShiftState) {
 | |
|   Modifiers modifiers = 0;
 | |
|   if (aShiftState & STATE_SHIFT) {
 | |
|     modifiers |= MODIFIER_SHIFT;
 | |
|   }
 | |
|   if (aShiftState & STATE_ALTGRAPH) {
 | |
|     modifiers |= MODIFIER_ALTGRAPH;
 | |
|   } else {
 | |
|     if (aShiftState & STATE_CONTROL) {
 | |
|       modifiers |= MODIFIER_CONTROL;
 | |
|     }
 | |
|     if (aShiftState & STATE_ALT) {
 | |
|       modifiers |= MODIFIER_ALT;
 | |
|     }
 | |
|   }
 | |
|   if (aShiftState & STATE_CAPSLOCK) {
 | |
|     modifiers |= MODIFIER_CAPSLOCK;
 | |
|   }
 | |
|   return modifiers;
 | |
| }
 | |
| 
 | |
| const DeadKeyTable* VirtualKey::MatchingDeadKeyTable(
 | |
|     const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const {
 | |
|   if (!mIsDeadKey) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   for (ShiftState shiftState = 0; shiftState < 16; shiftState++) {
 | |
|     if (!IsDeadKey(shiftState)) {
 | |
|       continue;
 | |
|     }
 | |
|     const DeadKeyTable* dkt = mShiftStates[shiftState].DeadKey.Table;
 | |
|     if (dkt && dkt->IsEqual(aDeadKeyArray, aEntries)) {
 | |
|       return dkt;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void VirtualKey::SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
 | |
|                                 uint32_t aNumOfChars) {
 | |
|   MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
 | |
| 
 | |
|   SetDeadKey(aShiftState, false);
 | |
| 
 | |
|   for (uint32_t index = 0; index < aNumOfChars; index++) {
 | |
|     // Ignore legacy non-printable control characters
 | |
|     mShiftStates[aShiftState].Normal.Chars[index] =
 | |
|         (aChars[index] >= 0x20) ? aChars[index] : 0;
 | |
|   }
 | |
| 
 | |
|   uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
 | |
|   for (uint32_t index = aNumOfChars; index < len; index++) {
 | |
|     mShiftStates[aShiftState].Normal.Chars[index] = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar) {
 | |
|   MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
 | |
| 
 | |
|   SetDeadKey(aShiftState, true);
 | |
| 
 | |
|   mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar;
 | |
|   mShiftStates[aShiftState].DeadKey.Table = nullptr;
 | |
| }
 | |
| 
 | |
| UniCharsAndModifiers VirtualKey::GetUniChars(ShiftState aShiftState) const {
 | |
|   UniCharsAndModifiers result = GetNativeUniChars(aShiftState);
 | |
| 
 | |
|   const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
 | |
|   if (!(kShiftStateIndex & STATE_CONTROL_ALT)) {
 | |
|     // If neither Alt nor Ctrl key is pressed, just return stored data
 | |
|     // for the key.
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   if (result.IsEmpty()) {
 | |
|     // If Alt and/or Control are pressed and the key produces no
 | |
|     // character, return characters which is produced by the key without
 | |
|     // Alt and Control, and return given modifiers as is.
 | |
|     result = GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
 | |
|     result.FillModifiers(ShiftStateToModifiers(aShiftState));
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   if (IsAltGrIndex(kShiftStateIndex)) {
 | |
|     // If AltGr or both Ctrl and Alt are pressed and the key produces
 | |
|     // character(s), we need to clear MODIFIER_ALT and MODIFIER_CONTROL
 | |
|     // since TextEditor won't handle eKeyPress event whose mModifiers
 | |
|     // has MODIFIER_ALT or MODIFIER_CONTROL.  Additionally, we need to
 | |
|     // use MODIFIER_ALTGRAPH when a key produces character(s) with
 | |
|     // AltGr or both Ctrl and Alt on Windows.  See following spec issue:
 | |
|     // <https://github.com/w3c/uievents/issues/147>
 | |
|     Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
 | |
|     finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
 | |
|     finalModifiers |= MODIFIER_ALTGRAPH;
 | |
|     result.FillModifiers(finalModifiers);
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   // Otherwise, i.e., Alt or Ctrl is pressed and it produces character(s),
 | |
|   // check if different character(s) is produced by the key without Alt/Ctrl.
 | |
|   // If it produces different character, we need to consume the Alt and
 | |
|   // Ctrl modifier for TextEditor.  Otherwise, the key does not produces the
 | |
|   // character actually.  So, keep setting Alt and Ctrl modifiers.
 | |
|   UniCharsAndModifiers unmodifiedReslt =
 | |
|       GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
 | |
|   if (!result.UniCharsEqual(unmodifiedReslt)) {
 | |
|     Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
 | |
|     finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
 | |
|     result.FillModifiers(finalModifiers);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| UniCharsAndModifiers VirtualKey::GetNativeUniChars(
 | |
|     ShiftState aShiftState) const {
 | |
|   const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
 | |
|   UniCharsAndModifiers result;
 | |
|   Modifiers modifiers = ShiftStateToModifiers(aShiftState);
 | |
|   if (IsDeadKey(aShiftState)) {
 | |
|     result.Append(mShiftStates[kShiftStateIndex].DeadKey.DeadChar, modifiers);
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   uint32_t len = ArrayLength(mShiftStates[kShiftStateIndex].Normal.Chars);
 | |
|   for (uint32_t i = 0;
 | |
|        i < len && mShiftStates[kShiftStateIndex].Normal.Chars[i]; i++) {
 | |
|     result.Append(mShiftStates[kShiftStateIndex].Normal.Chars[i], modifiers);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| // static
 | |
| void VirtualKey::FillKbdState(PBYTE aKbdState, const ShiftState aShiftState) {
 | |
|   if (aShiftState & STATE_SHIFT) {
 | |
|     aKbdState[VK_SHIFT] |= 0x80;
 | |
|   } else {
 | |
|     aKbdState[VK_SHIFT] &= ~0x80;
 | |
|     aKbdState[VK_LSHIFT] &= ~0x80;
 | |
|     aKbdState[VK_RSHIFT] &= ~0x80;
 | |
|   }
 | |
| 
 | |
|   if (aShiftState & STATE_ALTGRAPH) {
 | |
|     aKbdState[VK_CONTROL] |= 0x80;
 | |
|     aKbdState[VK_LCONTROL] |= 0x80;
 | |
|     aKbdState[VK_RCONTROL] &= ~0x80;
 | |
|     aKbdState[VK_MENU] |= 0x80;
 | |
|     aKbdState[VK_LMENU] &= ~0x80;
 | |
|     aKbdState[VK_RMENU] |= 0x80;
 | |
|   } else {
 | |
|     if (aShiftState & STATE_CONTROL) {
 | |
|       aKbdState[VK_CONTROL] |= 0x80;
 | |
|     } else {
 | |
|       aKbdState[VK_CONTROL] &= ~0x80;
 | |
|       aKbdState[VK_LCONTROL] &= ~0x80;
 | |
|       aKbdState[VK_RCONTROL] &= ~0x80;
 | |
|     }
 | |
| 
 | |
|     if (aShiftState & STATE_ALT) {
 | |
|       aKbdState[VK_MENU] |= 0x80;
 | |
|     } else {
 | |
|       aKbdState[VK_MENU] &= ~0x80;
 | |
|       aKbdState[VK_LMENU] &= ~0x80;
 | |
|       aKbdState[VK_RMENU] &= ~0x80;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aShiftState & STATE_CAPSLOCK) {
 | |
|     aKbdState[VK_CAPITAL] |= 0x01;
 | |
|   } else {
 | |
|     aKbdState[VK_CAPITAL] &= ~0x01;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*****************************************************************************
 | |
|  * mozilla::widget::NativeKey
 | |
|  *****************************************************************************/
 | |
| 
 | |
| uint8_t NativeKey::sDispatchedKeyOfAppCommand = 0;
 | |
| NativeKey* NativeKey::sLatestInstance = nullptr;
 | |
| const MSG NativeKey::sEmptyMSG = {};
 | |
| MSG NativeKey::sLastKeyOrCharMSG = {};
 | |
| MSG NativeKey::sLastKeyMSG = {};
 | |
| 
 | |
| LazyLogModule sNativeKeyLogger("NativeKeyWidgets");
 | |
| 
 | |
| NativeKey::NativeKey(nsWindowBase* aWidget, const MSG& aMessage,
 | |
|                      const ModifierKeyState& aModKeyState,
 | |
|                      HKL aOverrideKeyboardLayout,
 | |
|                      nsTArray<FakeCharMsg>* aFakeCharMsgs)
 | |
|     : mLastInstance(sLatestInstance),
 | |
|       mRemovingMsg(sEmptyMSG),
 | |
|       mReceivedMsg(sEmptyMSG),
 | |
|       mWidget(aWidget),
 | |
|       mDispatcher(aWidget->GetTextEventDispatcher()),
 | |
|       mMsg(aMessage),
 | |
|       mFocusedWndBeforeDispatch(::GetFocus()),
 | |
|       mDOMKeyCode(0),
 | |
|       mKeyNameIndex(KEY_NAME_INDEX_Unidentified),
 | |
|       mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN),
 | |
|       mModKeyState(aModKeyState),
 | |
|       mVirtualKeyCode(0),
 | |
|       mOriginalVirtualKeyCode(0),
 | |
|       mShiftedLatinChar(0),
 | |
|       mUnshiftedLatinChar(0),
 | |
|       mScanCode(0),
 | |
|       mIsExtended(false),
 | |
|       mIsRepeat(false),
 | |
|       mIsDeadKey(false),
 | |
|       mIsPrintableKey(false),
 | |
|       mIsSkippableInRemoteProcess(false),
 | |
|       mCharMessageHasGone(false),
 | |
|       mCanIgnoreModifierStateAtKeyPress(true),
 | |
|       mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ? aFakeCharMsgs
 | |
|                                                              : nullptr) {
 | |
|   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
 | |
|           ("%p NativeKey::NativeKey(aWidget=0x%p { GetWindowHandle()=0x%p }, "
 | |
|            "aMessage=%s, aModKeyState=%s), sLatestInstance=0x%p",
 | |
|            this, aWidget, aWidget->GetWindowHandle(), ToString(aMessage).get(),
 | |
|            ToString(aModKeyState).get(), sLatestInstance));
 | |
| 
 | |
|   MOZ_ASSERT(aWidget);
 | |
|   MOZ_ASSERT(mDispatcher);
 | |
|   sLatestInstance = this;
 | |
|   KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
 | |
|   mKeyboardLayout = keyboardLayout->GetLayout();
 | |
|   if (aOverrideKeyboardLayout && mKeyboardLayout != aOverrideKeyboardLayout) {
 | |
|     keyboardLayout->OverrideLayout(aOverrideKeyboardLayout);
 | |
|     mKeyboardLayout = keyboardLayout->GetLayout();
 | |
|     MOZ_ASSERT(mKeyboardLayout == aOverrideKeyboardLayout);
 | |
|     mIsOverridingKeyboardLayout = true;
 | |
|   } else {
 | |
|     mIsOverridingKeyboardLayout = false;
 | |
|     sLastKeyOrCharMSG = aMessage;
 | |
|   }
 | |
| 
 | |
|   if (mMsg.message == WM_APPCOMMAND) {
 | |
|     InitWithAppCommand();
 | |
|   } else {
 | |
|     InitWithKeyOrChar();
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
 | |
|           ("%p   NativeKey::NativeKey(), mKeyboardLayout=0x%08X, "
 | |
|            "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=%s, "
 | |
|            "mKeyNameIndex=%s, mCodeNameIndex=%s, mModKeyState=%s, "
 | |
|            "mVirtualKeyCode=%s, mOriginalVirtualKeyCode=%s, "
 | |
|            "mCommittedCharsAndModifiers=%s, mInputtingStringAndModifiers=%s, "
 | |
|            "mShiftedString=%s, mUnshiftedString=%s, mShiftedLatinChar=%s, "
 | |
|            "mUnshiftedLatinChar=%s, mScanCode=0x%04X, mIsExtended=%s, "
 | |
|            "mIsRepeat=%s, mIsDeadKey=%s, mIsPrintableKey=%s, "
 | |
|            "mIsSkippableInRemoteProcess=%s, mCharMessageHasGone=%s, "
 | |
|            "mIsOverridingKeyboardLayout=%s",
 | |
|            this, mKeyboardLayout, mFocusedWndBeforeDispatch,
 | |
|            GetDOMKeyCodeName(mDOMKeyCode).get(), ToString(mKeyNameIndex).get(),
 | |
|            ToString(mCodeNameIndex).get(), ToString(mModKeyState).get(),
 | |
|            GetVirtualKeyCodeName(mVirtualKeyCode).get(),
 | |
|            GetVirtualKeyCodeName(mOriginalVirtualKeyCode).get(),
 | |
|            ToString(mCommittedCharsAndModifiers).get(),
 | |
|            ToString(mInputtingStringAndModifiers).get(),
 | |
|            ToString(mShiftedString).get(), ToString(mUnshiftedString).get(),
 | |
|            GetCharacterCodeName(mShiftedLatinChar).get(),
 | |
|            GetCharacterCodeName(mUnshiftedLatinChar).get(), mScanCode,
 | |
|            GetBoolName(mIsExtended), GetBoolName(mIsRepeat),
 | |
|            GetBoolName(mIsDeadKey), GetBoolName(mIsPrintableKey),
 | |
|            GetBoolName(mIsSkippableInRemoteProcess),
 | |
|            GetBoolName(mCharMessageHasGone),
 | |
|            GetBoolName(mIsOverridingKeyboardLayout)));
 | |
| }
 | |
| 
 | |
| void NativeKey::InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG) {
 | |
|   mIsSkippableInRemoteProcess = false;
 | |
| 
 | |
|   if (!mIsRepeat) {
 | |
|     // If the message is not repeated key message, the event should be always
 | |
|     // handled in remote process even if it's too old.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Keyboard utilities may send us some generated messages and such messages
 | |
|   // may be marked as "repeated", e.g., SendInput() calls with
 | |
|   // KEYEVENTF_UNICODE but without KEYEVENTF_KEYUP.   However, key sequence
 | |
|   // comes from such utilities may be really important.  For example, utilities
 | |
|   // may send WM_KEYDOWN for VK_BACK to remove previous character and send
 | |
|   // WM_KEYDOWN for VK_PACKET to insert a composite character.  Therefore, we
 | |
|   // should check if current message and previous key message are caused by
 | |
|   // same physical key.  If not, the message may be generated by such
 | |
|   // utility.
 | |
|   // XXX With this approach, if VK_BACK messages are generated with known
 | |
|   //     scancode, we cannot distinguish whether coming VK_BACK message is
 | |
|   //     actually repeated by the auto-repeat feature.  Currently, we need
 | |
|   //     this hack only for "SinhalaTamil IME" and fortunately, it generates
 | |
|   //     VK_BACK messages with odd scancode.  So, we don't need to handle
 | |
|   //     VK_BACK specially at least for now.
 | |
| 
 | |
|   if (mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
 | |
|     // If current event is not caused by physical key operation, it may be
 | |
|     // caused by a keyboard utility.  If so, the event shouldn't be ignored by
 | |
|     // BrowserChild since it want to insert the character, delete a character or
 | |
|     // move caret.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mOriginalVirtualKeyCode == VK_PACKET) {
 | |
|     // If the message is VK_PACKET, that means that a keyboard utility
 | |
|     // tries to insert a character.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   switch (mMsg.message) {
 | |
|     case WM_KEYDOWN:
 | |
|     case WM_SYSKEYDOWN:
 | |
|     case WM_CHAR:
 | |
|     case WM_SYSCHAR:
 | |
|     case WM_DEADCHAR:
 | |
|     case WM_SYSDEADCHAR:
 | |
|       // However, some keyboard layouts may send some keyboard messages with
 | |
|       // activating the bit.  If we dispatch repeated keyboard events, they
 | |
|       // may be ignored by BrowserChild due to performance reason.  So, we need
 | |
|       // to check if actually a physical key is repeated by the auto-repeat
 | |
|       // feature.
 | |
|       switch (aLastKeyMSG.message) {
 | |
|         case WM_KEYDOWN:
 | |
|         case WM_SYSKEYDOWN:
 | |
|           if (aLastKeyMSG.wParam == VK_PACKET) {
 | |
|             // If the last message was VK_PACKET, that means that a keyboard
 | |
|             // utility tried to insert a character.  So, current message is
 | |
|             // not repeated key event of the previous event.
 | |
|             return;
 | |
|           }
 | |
|           // Let's check whether current message and previous message are
 | |
|           // caused by same physical key.
 | |
|           mIsSkippableInRemoteProcess =
 | |
|               mScanCode == WinUtils::GetScanCode(aLastKeyMSG.lParam) &&
 | |
|               mIsExtended == WinUtils::IsExtendedScanCode(aLastKeyMSG.lParam);
 | |
|           return;
 | |
|         default:
 | |
|           // If previous message is not a keydown, this must not be generated
 | |
|           // by the auto-repeat feature.
 | |
|           return;
 | |
|       }
 | |
|       return;
 | |
|     case WM_APPCOMMAND:
 | |
|       MOZ_ASSERT_UNREACHABLE(
 | |
|           "WM_APPCOMMAND should be handled in "
 | |
|           "InitWithAppCommand()");
 | |
|       return;
 | |
|     default:
 | |
|       // keyup message shouldn't be repeated by the auto-repeat feature.
 | |
|       return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NativeKey::InitWithKeyOrChar() {
 | |
|   MSG lastKeyMSG = sLastKeyMSG;
 | |
|   mScanCode = WinUtils::GetScanCode(mMsg.lParam);
 | |
|   mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam);
 | |
|   switch (mMsg.message) {
 | |
|     case WM_KEYDOWN:
 | |
|     case WM_SYSKEYDOWN:
 | |
|     case WM_KEYUP:
 | |
|     case WM_SYSKEYUP: {
 | |
|       // Modify sLastKeyMSG now since retrieving following char messages may
 | |
|       // cause sending another key message if odd tool hooks GetMessage(),
 | |
|       // PeekMessage().
 | |
|       sLastKeyMSG = mMsg;
 | |
| 
 | |
|       // Note that we don't need to compute raw virtual keycode here even when
 | |
|       // it's VK_PROCESS (i.e., already handled by IME) because we need to
 | |
|       // export it as DOM_VK_PROCESS and KEY_NAME_INDEX_Process.
 | |
|       mOriginalVirtualKeyCode = static_cast<uint8_t>(mMsg.wParam);
 | |
| 
 | |
|       // If the key message is sent from other application like a11y tools, the
 | |
|       // scancode value might not be set proper value.  Then, probably the value
 | |
|       // is 0.
 | |
|       // NOTE: If the virtual keycode can be caused by both non-extended key
 | |
|       //       and extended key, the API returns the non-extended key's
 | |
|       //       scancode.  E.g., VK_LEFT causes "4" key on numpad.
 | |
|       if (!mScanCode && mOriginalVirtualKeyCode != VK_PACKET) {
 | |
|         uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mMsg.wParam);
 | |
|         if (scanCodeEx) {
 | |
|           mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF);
 | |
|           uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8);
 | |
|           mIsExtended = (extended == 0xE0) || (extended == 0xE1);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Most keys are not distinguished as left or right keys.
 | |
|       bool isLeftRightDistinguishedKey = false;
 | |
| 
 | |
|       // mOriginalVirtualKeyCode must not distinguish left or right of
 | |
|       // Shift, Control or Alt.
 | |
|       switch (mOriginalVirtualKeyCode) {
 | |
|         case VK_SHIFT:
 | |
|         case VK_CONTROL:
 | |
|         case VK_MENU:
 | |
|           isLeftRightDistinguishedKey = true;
 | |
|           break;
 | |
|         case VK_LSHIFT:
 | |
|         case VK_RSHIFT:
 | |
|           mVirtualKeyCode = mOriginalVirtualKeyCode;
 | |
|           mOriginalVirtualKeyCode = VK_SHIFT;
 | |
|           isLeftRightDistinguishedKey = true;
 | |
|           break;
 | |
|         case VK_LCONTROL:
 | |
|         case VK_RCONTROL:
 | |
|           mVirtualKeyCode = mOriginalVirtualKeyCode;
 | |
|           mOriginalVirtualKeyCode = VK_CONTROL;
 | |
|           isLeftRightDistinguishedKey = true;
 | |
|           break;
 | |
|         case VK_LMENU:
 | |
|         case VK_RMENU:
 | |
|           mVirtualKeyCode = mOriginalVirtualKeyCode;
 | |
|           mOriginalVirtualKeyCode = VK_MENU;
 | |
|           isLeftRightDistinguishedKey = true;
 | |
|           break;
 | |
|       }
 | |
| 
 | |
|       // If virtual keycode (left-right distinguished keycode) is already
 | |
|       // computed, we don't need to do anymore.
 | |
|       if (mVirtualKeyCode) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       // If the keycode doesn't have LR distinguished keycode, we just set
 | |
|       // mOriginalVirtualKeyCode to mVirtualKeyCode.  Note that don't compute
 | |
|       // it from MapVirtualKeyEx() because the scan code might be wrong if
 | |
|       // the message is sent/posted by other application.  Then, we will compute
 | |
|       // unexpected keycode from the scan code.
 | |
|       if (!isLeftRightDistinguishedKey) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       NS_ASSERTION(!mVirtualKeyCode,
 | |
|                    "mVirtualKeyCode has been computed already");
 | |
| 
 | |
|       // Otherwise, compute the virtual keycode with MapVirtualKeyEx().
 | |
|       mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx();
 | |
| 
 | |
|       // Following code shouldn't be used now because we compute scancode value
 | |
|       // if we detect that the sender doesn't set proper scancode.
 | |
|       // However, the detection might fail. Therefore, let's keep using this.
 | |
|       switch (mOriginalVirtualKeyCode) {
 | |
|         case VK_CONTROL:
 | |
|           if (mVirtualKeyCode != VK_LCONTROL &&
 | |
|               mVirtualKeyCode != VK_RCONTROL) {
 | |
|             mVirtualKeyCode = mIsExtended ? VK_RCONTROL : VK_LCONTROL;
 | |
|           }
 | |
|           break;
 | |
|         case VK_MENU:
 | |
|           if (mVirtualKeyCode != VK_LMENU && mVirtualKeyCode != VK_RMENU) {
 | |
|             mVirtualKeyCode = mIsExtended ? VK_RMENU : VK_LMENU;
 | |
|           }
 | |
|           break;
 | |
|         case VK_SHIFT:
 | |
|           if (mVirtualKeyCode != VK_LSHIFT && mVirtualKeyCode != VK_RSHIFT) {
 | |
|             // Neither left shift nor right shift is an extended key,
 | |
|             // let's use VK_LSHIFT for unknown mapping.
 | |
|             mVirtualKeyCode = VK_LSHIFT;
 | |
|           }
 | |
|           break;
 | |
|         default:
 | |
|           MOZ_CRASH("Unsupported mOriginalVirtualKeyCode");
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case WM_CHAR:
 | |
|     case WM_UNICHAR:
 | |
|     case WM_SYSCHAR:
 | |
|       // If there is another instance and it is trying to remove a char message
 | |
|       // from the queue, this message should be handled in the old instance.
 | |
|       if (IsAnotherInstanceRemovingCharMessage()) {
 | |
|         // XXX Do we need to make mReceivedMsg an array?
 | |
|         MOZ_ASSERT(IsEmptyMSG(mLastInstance->mReceivedMsg));
 | |
|         MOZ_LOG(
 | |
|             sNativeKeyLogger, LogLevel::Warning,
 | |
|             ("%p   NativeKey::InitWithKeyOrChar(), WARNING, detecting another "
 | |
|              "instance is trying to remove a char message, so, this instance "
 | |
|              "should do nothing, mLastInstance=0x%p, mRemovingMsg=%s, "
 | |
|              "mReceivedMsg=%s",
 | |
|              this, mLastInstance, ToString(mLastInstance->mRemovingMsg).get(),
 | |
|              ToString(mLastInstance->mReceivedMsg).get()));
 | |
|         mLastInstance->mReceivedMsg = mMsg;
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // NOTE: If other applications like a11y tools sends WM_*CHAR without
 | |
|       //       scancode, we cannot compute virtual keycode.  I.e., with such
 | |
|       //       applications, we cannot generate proper KeyboardEvent.code value.
 | |
| 
 | |
|       mVirtualKeyCode = mOriginalVirtualKeyCode =
 | |
|           ComputeVirtualKeyCodeFromScanCodeEx();
 | |
|       NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode");
 | |
|       break;
 | |
|     default: {
 | |
|       MOZ_CRASH_UNSAFE_PRINTF("Unsupported message: 0x%04X", mMsg.message);
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!mVirtualKeyCode) {
 | |
|     mVirtualKeyCode = mOriginalVirtualKeyCode;
 | |
|   }
 | |
| 
 | |
|   KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
 | |
|   mDOMKeyCode =
 | |
|       keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mVirtualKeyCode);
 | |
|   // Be aware, keyboard utilities can change non-printable keys to printable
 | |
|   // keys.  In such case, we should make the key value as a printable key.
 | |
|   // FYI: IsFollowedByPrintableCharMessage() returns true only when it's
 | |
|   //      handling a keydown message.
 | |
|   mKeyNameIndex =
 | |
|       IsFollowedByPrintableCharMessage()
 | |
|           ? KEY_NAME_INDEX_USE_STRING
 | |
|           : keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mVirtualKeyCode);
 | |
|   mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex(
 | |
|       GetScanCodeWithExtendedFlag());
 | |
| 
 | |
|   // If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key
 | |
|   // combination is not reserved by the system, let's consume it now.
 | |
|   // TODO: We cannot initialize mCommittedCharsAndModifiers for VK_PACKET
 | |
|   //       if the message is WM_KEYUP because we don't have preceding
 | |
|   //       WM_CHAR message.
 | |
|   // TODO: Like Edge, we shouldn't dispatch two sets of keyboard events
 | |
|   //       for a Unicode character in non-BMP because its key value looks
 | |
|   //       broken and not good thing for our editor if only one keydown or
 | |
|   //       keypress event's default is prevented.  I guess, we should store
 | |
|   //       key message information globally and we should wait following
 | |
|   //       WM_KEYDOWN if following WM_CHAR is a part of a Unicode character.
 | |
|   if ((mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN) &&
 | |
|       !IsReservedBySystem()) {
 | |
|     MSG charMsg;
 | |
|     while (GetFollowingCharMessage(charMsg)) {
 | |
|       // Although, got message shouldn't be WM_NULL in desktop apps,
 | |
|       // we should keep checking this.  FYI: This was added for Metrofox.
 | |
|       if (charMsg.message == WM_NULL) {
 | |
|         continue;
 | |
|       }
 | |
|       MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
 | |
|               ("%p   NativeKey::InitWithKeyOrChar(), removed char message, %s",
 | |
|                this, ToString(charMsg).get()));
 | |
|       Unused << NS_WARN_IF(charMsg.hwnd != mMsg.hwnd);
 | |
|       mFollowingCharMsgs.AppendElement(charMsg);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   keyboardLayout->InitNativeKey(*this);
 | |
| 
 | |
|   // Now, we can know if the key produces character(s) or a dead key with
 | |
|   // AltGraph modifier.  When user emulates AltGr key press with pressing
 | |
|   // both Ctrl and Alt and the key produces character(s) or a dead key, we
 | |
|   // need to replace Control and Alt state with AltGraph if the keyboard
 | |
|   // layout has AltGr key.
 | |
|   // Note that if Ctrl and/or Alt are pressed (not to emulate to press AltGr),
 | |
|   // we need to set actual modifiers to eKeyDown and eKeyUp.
 | |
|   if (MaybeEmulatingAltGraph() &&
 | |
|       (mCommittedCharsAndModifiers.IsProducingCharsWithAltGr() ||
 | |
|        mKeyNameIndex == KEY_NAME_INDEX_Dead)) {
 | |
|     mModKeyState.Unset(MODIFIER_CONTROL | MODIFIER_ALT);
 | |
|     mModKeyState.Set(MODIFIER_ALTGRAPH);
 | |
|   }
 | |
| 
 | |
|   mIsDeadKey =
 | |
|       (IsFollowedByDeadCharMessage() ||
 | |
|        keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState));
 | |
|   mIsPrintableKey = mKeyNameIndex == KEY_NAME_INDEX_USE_STRING ||
 | |
|                     KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
 | |
|   // The repeat count in mMsg.lParam isn't useful to check whether the event
 | |
|   // is caused by the auto-repeat feature because it's not incremented even
 | |
|   // if it's repeated twice or more (i.e., always 1).  Therefore, we need to
 | |
|   // check previous key state (31th bit) instead.  If it's 1, the key was down
 | |
|   // before the message was sent.
 | |
|   mIsRepeat = (mMsg.lParam & (1 << 30)) != 0;
 | |
|   InitIsSkippableForKeyOrChar(lastKeyMSG);
 | |
| 
 | |
|   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() {
 | |
|   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 = mModKeyState.GetModifiers();
 | |
|   if (IsFollowedByPrintableCharMessage()) {
 | |
|     modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
 | |
|     if (MaybeEmulatingAltGraph()) {
 | |
|       modifiers |= MODIFIER_ALTGRAPH;
 | |
|     }
 | |
|   }
 | |
|   // 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());
 | |
|   // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust
 | |
|   // the result of GetKeyboardState().  Otherwise, we dispatch both
 | |
|   // keydown and keyup events from WM_APPCOMMAND handler.  Therefore,
 | |
|   // even if WM_APPCOMMAND is caused by auto key repeat, web apps receive
 | |
|   // a pair of DOM keydown and keyup events.  I.e., KeyboardEvent.repeat
 | |
|   // should be never true of such keys.
 | |
|   // XXX Isn't the key state always true?  If the key press caused this
 | |
|   //     WM_APPCOMMAND, that means it's pressed at that time.
 | |
|   if (mVirtualKeyCode) {
 | |
|     BYTE kbdState[256];
 | |
|     memset(kbdState, 0, sizeof(kbdState));
 | |
|     ::GetKeyboardState(kbdState);
 | |
|     mIsSkippableInRemoteProcess = mIsRepeat = !!kbdState[mVirtualKeyCode];
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool NativeKey::MaybeEmulatingAltGraph() const {
 | |
|   return IsControl() && IsAlt() && KeyboardLayout::GetInstance()->HasAltGr();
 | |
| }
 | |
| 
 | |
| // 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 eKeyLocationLeft;
 | |
| 
 | |
|     case VK_RSHIFT:
 | |
|     case VK_RCONTROL:
 | |
|     case VK_RMENU:
 | |
|     case VK_RWIN:
 | |
|       return eKeyLocationRight;
 | |
| 
 | |
|     case VK_RETURN:
 | |
|       // XXX This code assumes that all keyboard drivers use same mapping.
 | |
|       return !mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad;
 | |
| 
 | |
|     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 ? eKeyLocationStandard : eKeyLocationNumpad;
 | |
| 
 | |
|     // 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 eKeyLocationNumpad;
 | |
| 
 | |
|     case VK_SHIFT:
 | |
|     case VK_CONTROL:
 | |
|     case VK_MENU:
 | |
|       NS_WARNING("Failed to decide the key location?");
 | |
| 
 | |
|     default:
 | |
|       return eKeyLocationStandard;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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.
 | |
|   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, MAPVK_VK_TO_VSC_EX, mKeyboardLayout));
 | |
| }
 | |
| 
 | |
| char16_t NativeKey::ComputeUnicharFromScanCode() const {
 | |
|   return static_cast<char16_t>(::MapVirtualKeyEx(
 | |
|       ComputeVirtualKeyCodeFromScanCode(), MAPVK_VK_TO_CHAR, mKeyboardLayout));
 | |
| }
 | |
| 
 | |
| nsEventStatus NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const {
 | |
|   return InitKeyEvent(aKeyEvent, mModKeyState);
 | |
| }
 | |
| 
 | |
| nsEventStatus NativeKey::InitKeyEvent(
 | |
|     WidgetKeyboardEvent& aKeyEvent,
 | |
|     const ModifierKeyState& aModKeyState) 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.
 | |
|       // However, for compatibility with older version and other browsers,
 | |
|       // we should dispatch the events even in the web content.
 | |
|       if (mCharMessageHasGone) {
 | |
|         aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
 | |
|       }
 | |
|       [[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.  On the other hand, we should dispatch the events even
 | |
|       // in the web content for compatibility with older version and other
 | |
|       // browsers.
 | |
|       if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) {
 | |
|         aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
 | |
|       }
 | |
|       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 = mIsRepeat;
 | |
|   aKeyEvent.mMaybeSkippableInRemoteProcess = mIsSkippableInRemoteProcess;
 | |
|   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);
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| bool NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const {
 | |
|   RefPtr<nsAtom> 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 appCommandEvent(true, command, mWidget);
 | |
| 
 | |
|   mWidget->InitEvent(appCommandEvent);
 | |
|   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
 | |
|           ("%p   NativeKey::DispatchCommandEvent(), dispatching "
 | |
|            "%s app command event...",
 | |
|            this, nsAtomCString(command).get()));
 | |
|   bool ok =
 | |
|       mWidget->DispatchWindowEvent(&appCommandEvent) || mWidget->Destroyed();
 | |
|   MOZ_LOG(
 | |
|       sNativeKeyLogger, LogLevel::Info,
 | |
|       ("%p   NativeKey::DispatchCommandEvent(), dispatched app 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);
 | |
|     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);
 | |
|     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 ||
 | |
|       !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));
 | |
| 
 | |
|     WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
 | |
|     nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState);
 | |
|     MOZ_LOG(
 | |
|         sNativeKeyLogger, LogLevel::Info,
 | |
|         ("%p   NativeKey::HandleKeyDownMessage(), dispatching keydown event...",
 | |
|          this));
 | |
|     bool dispatched = mDispatcher->DispatchKeyboardEvent(
 | |
|         eKeyDown, 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;
 | |
| 
 | |
|     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 && focusedWnd && !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));
 | |
|     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 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) || IsEnterKeyPressCharMessage(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.
 | |
|   // Note that when the char is '\r', it means that the char message should
 | |
|   // cause "Enter" keypress event for inserting a line break.
 | |
|   if (IsControlCharMessage(aCharMsg) && !IsEnterKeyPressCharMessage(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);
 | |
|   if (IsEnterKeyPressCharMessage(aCharMsg)) {
 | |
|     keypressEvent.mKeyCode = NS_VK_RETURN;
 | |
|   } else {
 | |
|     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, TextEditor 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.IsControl() && modKeyState.IsAlt() &&
 | |
|       IsPrintableCharMessage(aCharMsg)) {
 | |
|     modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
 | |
|   }
 | |
|   nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState);
 | |
|   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));
 | |
|   WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
 | |
|   nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState);
 | |
|   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
 | |
|           ("%p   NativeKey::HandleKeyUpMessage(), dispatching keyup event...",
 | |
|            this));
 | |
|   bool dispatched = mDispatcher->DispatchKeyboardEvent(
 | |
|       eKeyUp, 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());
 | |
| 
 | |
|   // 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.
 | |
|   // Note that if the key does not produce a character with AltGr and when
 | |
|   // AltGr key is pressed, we don't need to dispatch eKeyPress event for it
 | |
|   // because AltGr shouldn't be used for a modifier for a shortcut without
 | |
|   // Ctrl, Alt or Win.  That means that we should treat it in same path for
 | |
|   // Shift 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;
 | |
| }
 | |
| 
 | |
| static nsCString GetResultOfInSendMessageEx() {
 | |
|   DWORD ret = ::InSendMessageEx(nullptr);
 | |
|   if (!ret) {
 | |
|     return "ISMEX_NOSEND"_ns;
 | |
|   }
 | |
|   nsCString 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;
 | |
| }
 | |
| 
 | |
| 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());
 | |
| 
 | |
|   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) {
 | |
|       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(
 | |
|             "\nThere is no message in any window"_ns);
 | |
|       }
 | |
| 
 | |
|       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()));
 | |
|     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(
 | |
|           nsLiteralCString("\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(
 | |
|           "\nThere is no key message in any windows."_ns);
 | |
|     }
 | |
| 
 | |
|     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()));
 | |
|   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);
 | |
|   MOZ_CRASH("We lost the following char message");
 | |
|   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;
 | |
|   }
 | |
| 
 | |
|   // If user is inputting a Unicode character with typing Alt + Numpad
 | |
|   // keys, we shouldn't set alternative key codes because the key event
 | |
|   // shouldn't match with a mnemonic.  However, we should set only
 | |
|   // mUnshiftedString for keeping traditional behavior at WM_SYSKEYDOWN.
 | |
|   // FYI: I guess that it's okay that mUnshiftedString stays empty.  So,
 | |
|   //      if its value breaks something, you must be able to just return here.
 | |
|   if (MaybeTypingUnicodeScalarValue()) {
 | |
|     if (!mCommittedCharsAndModifiers.IsEmpty()) {
 | |
|       MOZ_ASSERT(mMsg.message == WM_SYSKEYDOWN);
 | |
|       char16_t num = mCommittedCharsAndModifiers.CharAt(0);
 | |
|       MOZ_ASSERT(num >= '0' && num <= '9');
 | |
|       mUnshiftedString.Append(num, MODIFIER_NONE);
 | |
|       return;
 | |
|     }
 | |
|     // If user presses a function key without NumLock or 3rd party utility
 | |
|     // synthesizes a function key on numpad, we should handle it as-is because
 | |
|     // the user's intention may be performing `Alt` + foo.
 | |
|     MOZ_ASSERT(!KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode));
 | |
|     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);
 | |
|   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.
 | |
|         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;
 | |
| nsIUserIdleServiceInternal* 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<nsIUserIdleServiceInternal> idleService =
 | |
|         do_GetService("@mozilla.org/widget/useridleservice;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),
 | |
|       mHasAltGr(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) {
 | |
|   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 =
 | |
|           aNativeKey.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
 | |
|       aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If the aNativeKey is in a sequence to input a Unicode character with
 | |
|   // Alt + numpad keys, we should just set the number as the inputting charcter.
 | |
|   // Note that we should compute the key value from the virtual key code
 | |
|   // because they may be mapped to alphabets, but they should be treated as
 | |
|   // Alt + [0-9] even by web apps.
 | |
|   // However, we shouldn't touch the key value if given virtual key code is
 | |
|   // not a printable key because it may be synthesized by 3rd party utility
 | |
|   // or just NumLock is unlocked and user tries to use shortcut key.  In the
 | |
|   // latter case, we cannot solve the conflict issue with Alt+foo shortcut key
 | |
|   // and inputting a Unicode scalar value like reported to bug 1606655, though,
 | |
|   // I have no better idea.  Perhaps, `Alt` shouldn't be used for shortcut key
 | |
|   // combination on Windows.
 | |
|   if (aNativeKey.MaybeTypingUnicodeScalarValue() &&
 | |
|       KeyboardLayout::IsPrintableCharKey(aNativeKey.mVirtualKeyCode)) {
 | |
|     // If the key code value is mapped to a Numpad key, let's compute the key
 | |
|     // value with it.  This is same behavior as Chrome.  In strictly speaking,
 | |
|     // I think that the else block's computation is better because it seems
 | |
|     // that Windows does not refer virtual key code value, but we should avoid
 | |
|     // web-compat issues.
 | |
|     char16_t num = '0';
 | |
|     if (aNativeKey.mVirtualKeyCode >= VK_NUMPAD0 &&
 | |
|         aNativeKey.mVirtualKeyCode <= VK_NUMPAD9) {
 | |
|       num = '0' + aNativeKey.mVirtualKeyCode - VK_NUMPAD0;
 | |
|     }
 | |
|     // Otherwise, let's use fake key value for making never match with
 | |
|     // mnemonic.
 | |
|     else {
 | |
|       switch (aNativeKey.mScanCode) {
 | |
|         case 0x0052:  // Numpad0
 | |
|           num = '0';
 | |
|           break;
 | |
|         case 0x004F:  // Numpad1
 | |
|           num = '1';
 | |
|           break;
 | |
|         case 0x0050:  // Numpad2
 | |
|           num = '2';
 | |
|           break;
 | |
|         case 0x0051:  // Numpad3
 | |
|           num = '3';
 | |
|           break;
 | |
|         case 0x004B:  // Numpad4
 | |
|           num = '4';
 | |
|           break;
 | |
|         case 0x004C:  // Numpad5
 | |
|           num = '5';
 | |
|           break;
 | |
|         case 0x004D:  // Numpad6
 | |
|           num = '6';
 | |
|           break;
 | |
|         case 0x0047:  // Numpad7
 | |
|           num = '7';
 | |
|           break;
 | |
|         case 0x0048:  // Numpad8
 | |
|           num = '8';
 | |
|           break;
 | |
|         case 0x0049:  // Numpad9
 | |
|           num = '9';
 | |
|           break;
 | |
|         default:
 | |
|           MOZ_ASSERT_UNREACHABLE(
 | |
|               "IsTypingUnicodeScalarValue() must have returned true for wrong "
 | |
|               "scancode");
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|     aNativeKey.mCommittedCharsAndModifiers.Append(num,
 | |
|                                                   aNativeKey.GetModifiers());
 | |
|     aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
 | |
|     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();
 | |
|       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) &&
 | |
|           (aNativeKey.IsControl() ^ aNativeKey.IsAlt())) {
 | |
|         ModifierKeyState state = aNativeKey.ModifierKeyStateRef();
 | |
|         state.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
 | |
|         UniCharsAndModifiers charsWithoutModifier =
 | |
|             GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(), 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)) {
 | |
|     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)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey);
 | |
| 
 | |
|   // 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) {
 | |
|   // Only when it's not in dead key sequence, we can trust IsDeadKey() result.
 | |
|   if (!IsInDeadKeySequence() && !IsDeadKey(aNativeKey)) {
 | |
|     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.GenericVirtualKeyCode());
 | |
| 
 | |
|   if (isDeadKeyDownEvent || isDeadKeyUpEvent) {
 | |
|     ActivateDeadKeyState(aNativeKey);
 | |
|     // 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);
 | |
|     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)) {
 | |
|     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)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Otherwise, dead key followed by another dead key causes inputting both
 | |
|   // character.
 | |
|   UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers();
 | |
|   UniCharsAndModifiers newChars = GetUniCharsAndModifiers(aNativeKey);
 | |
|   // 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) {
 | |
|   if (!IsInDeadKeySequence()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey);
 | |
|   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::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 "No name or too long name"_ns;
 | |
|     }
 | |
|     return NS_ConvertUTF16toUTF8(buf);
 | |
|   }
 | |
| 
 | |
|   if (NS_WARN_IF((layout & 0xF000) != 0xF000)) {
 | |
|     nsCString 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 ""_ns;
 | |
|   }
 | |
|   nsresult rv =
 | |
|       regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
 | |
|                    nsString(kKeyboardLayouts), nsIWindowsRegKey::ACCESS_READ);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return ""_ns;
 | |
|   }
 | |
|   uint32_t childCount = 0;
 | |
|   if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) ||
 | |
|       NS_WARN_IF(!childCount)) {
 | |
|     return ""_ns;
 | |
|   }
 | |
|   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 ""_ns;
 | |
| }
 | |
| 
 | |
| void KeyboardLayout::LoadLayout(HKL aLayout) {
 | |
|   mIsPendingToRestoreKeyboardLayout = false;
 | |
| 
 | |
|   if (mKeyboardLayout == aLayout) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mKeyboardLayout = aLayout;
 | |
|   mHasAltGr = false;
 | |
| 
 | |
|   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);
 | |
|     bool isAltGr = VirtualKey::IsAltGrIndex(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(),
 | |
|                  GetCharacterCodeNames(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(),
 | |
|                  GetCharacterCodeNames(uniChars, ret).get(), ret));
 | |
|       }
 | |
| 
 | |
|       // If the key inputs at least one character with AltGr modifier,
 | |
|       // check if AltGr changes inputting character.  If it does, mark
 | |
|       // this keyboard layout has AltGr modifier actually.
 | |
|       if (!mHasAltGr && ret > 0 && isAltGr &&
 | |
|           mVirtualKeys[vki].IsChangedByAltGr(shiftState)) {
 | |
|         mHasAltGr = true;
 | |
|         MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
 | |
|                 ("  Found a key (%s) changed by AltGr: %s -> %s (%s) (ret=%d)",
 | |
|                  kVirtualKeyName[virtualKey],
 | |
|                  GetCharacterCodeNames(
 | |
|                      mVirtualKeys[vki].GetNativeUniChars(
 | |
|                          shiftState - VirtualKey::ShiftStateIndex::eAltGr))
 | |
|                      .get(),
 | |
|                  GetCharacterCodeNames(
 | |
|                      mVirtualKeys[vki].GetNativeUniChars(shiftState))
 | |
|                      .get(),
 | |
|                  GetShiftStateName(shiftState).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 = MAPVK_VSC_TO_VK_EX;
 | |
|     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]));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
 | |
|           ("  AltGr key is %s in %s", mHasAltGr ? "found" : "not found",
 | |
|            GetLayoutName(aLayout).get()));
 | |
| }
 | |
| 
 | |
| 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) {
 | |
|   // Dead-key state should be activated at keydown.
 | |
|   if (!aNativeKey.IsKeyDownMessage()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode);
 | |
|   mDeadKeyShiftStates.AppendElement(aNativeKey.GetShiftState());
 | |
| }
 | |
| 
 | |
| 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_WARNING("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,
 | |
|                  GetCharacterCodeNames(compositeChars, 1).get(),
 | |
|                  ret <= 0
 | |
|                      ? "''"
 | |
|                      : GetCharacterCodeNames(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
 | |
|                      ? "''"
 | |
|                      : GetCharacterCodeNames(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;
 | |
| 
 | |
|     case VK_LSHIFT:
 | |
|     case VK_RSHIFT:
 | |
|       return NS_VK_SHIFT;
 | |
| 
 | |
|     case VK_LCONTROL:
 | |
|     case VK_RCONTROL:
 | |
|       return NS_VK_CONTROL;
 | |
| 
 | |
|     // Note that even if the key is AltGr, we should return NS_VK_ALT for
 | |
|     // compatibility with both older Gecko and the other browsers.
 | |
|     case VK_LMENU:
 | |
|     case VK_RMENU:
 | |
|       return NS_VK_ALT;
 | |
| 
 | |
|     // 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) {
 | |
|           // In this case, we've returned 0 in this case for long time because
 | |
|           // we decided that we should avoid setting same keyCode value to 2 or
 | |
|           // more keys since active keyboard layout may have a key to input the
 | |
|           // punctuation with different key.  However, setting keyCode to 0
 | |
|           // makes some web applications which are aware of neither
 | |
|           // KeyboardEvent.key nor KeyboardEvent.code not work with Firefox
 | |
|           // when user selects non-ASCII capable keyboard layout such as
 | |
|           // Russian and Thai layout.  So, let's decide keyCode value with
 | |
|           // major keyboard layout's key which causes the OEM keycode.
 | |
|           // Actually, this maps same keyCode value to 2 keys on Russian
 | |
|           // keyboard layout.  "Period" key causes VK_OEM_PERIOD but inputs
 | |
|           // Yu of Cyrillic and "Slash" key causes VK_OEM_2 (same as US
 | |
|           // keyboard layout) but inputs "." (period of ASCII).  Therefore,
 | |
|           // we return DOM_VK_PERIOD which is same as VK_OEM_PERIOD for
 | |
|           // "Period" key.  On the other hand, we use same keyCode value for
 | |
|           // "Slash" key too because it inputs ".".
 | |
|           CodeNameIndex code;
 | |
|           switch (aNativeKeyCode) {
 | |
|             case VK_OEM_1:
 | |
|               code = CODE_NAME_INDEX_Semicolon;
 | |
|               break;
 | |
|             case VK_OEM_PLUS:
 | |
|               code = CODE_NAME_INDEX_Equal;
 | |
|               break;
 | |
|             case VK_OEM_COMMA:
 | |
|               code = CODE_NAME_INDEX_Comma;
 | |
|               break;
 | |
|             case VK_OEM_MINUS:
 | |
|               code = CODE_NAME_INDEX_Minus;
 | |
|               break;
 | |
|             case VK_OEM_PERIOD:
 | |
|               code = CODE_NAME_INDEX_Period;
 | |
|               break;
 | |
|             case VK_OEM_2:
 | |
|               code = CODE_NAME_INDEX_Slash;
 | |
|               break;
 | |
|             case VK_OEM_3:
 | |
|               code = CODE_NAME_INDEX_Backquote;
 | |
|               break;
 | |
|             case VK_OEM_4:
 | |
|               code = CODE_NAME_INDEX_BracketLeft;
 | |
|               break;
 | |
|             case VK_OEM_5:
 | |
|               code = CODE_NAME_INDEX_Backslash;
 | |
|               break;
 | |
|             case VK_OEM_6:
 | |
|               code = CODE_NAME_INDEX_BracketRight;
 | |
|               break;
 | |
|             case VK_OEM_7:
 | |
|               code = CODE_NAME_INDEX_Quote;
 | |
|               break;
 | |
|             case VK_OEM_8:
 | |
|               // Use keyCode value for "Backquote" key on UK keyboard layout.
 | |
|               code = CODE_NAME_INDEX_Backquote;
 | |
|               break;
 | |
|             case VK_OEM_102:
 | |
|               // Use keyCode value for "IntlBackslash" key.
 | |
|               code = CODE_NAME_INDEX_IntlBackslash;
 | |
|               break;
 | |
|             case VK_ABNT_C1:  // "/" of ABNT.
 | |
|               // Use keyCode value for "IntlBackslash" key on ABNT keyboard
 | |
|               // layout.
 | |
|               code = CODE_NAME_INDEX_IntlBackslash;
 | |
|               break;
 | |
|             default:
 | |
|               MOZ_ASSERT_UNREACHABLE("Handle all OEM keycode values");
 | |
|               return 0;
 | |
|           }
 | |
|           return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
 | |
|         }
 | |
|       }
 | |
|       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 NS_VK_PROCESSKEY;
 | |
|     // 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;
 | |
|   }
 | |
| 
 | |
|   // If the keyboard layout has AltGr and AltRight key is pressed,
 | |
|   // return AltGraph.
 | |
|   if (aVirtualKey == VK_RMENU && HasAltGr()) {
 | |
|     return KEY_NAME_INDEX_AltGraph;
 | |
|   }
 | |
| 
 | |
|   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);
 | |
| 
 | |
|   bool isAltGrKeyPress = false;
 | |
|   if (aModifierFlags & nsIWidget::ALTGRAPH) {
 | |
|     if (!HasAltGr()) {
 | |
|       return NS_ERROR_INVALID_ARG;
 | |
|     }
 | |
|     // AltGr emulates ControlLeft key press and AltRight key press.
 | |
|     // So, we should remove those flags from aModifierFlags before
 | |
|     // calling WinUtils::SetupKeyModifiersSequence() to create correct
 | |
|     // key sequence.
 | |
|     // FYI: We don't support both ControlLeft and AltRight (AltGr) are
 | |
|     //      pressed at the same time unless synthesizing key is
 | |
|     //      VK_LCONTROL.
 | |
|     aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::ALT_R);
 | |
|   }
 | |
| 
 | |
|   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 | nsIWidget::ALTGRAPH);
 | |
|       argumentKeySpecific = aNativeKeyCode & 0xFF;
 | |
|       aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
 | |
|       // If AltRight key is AltGr in the keyboard layout, let's use
 | |
|       // SetupKeyModifiersSequence() to emulate the native behavior
 | |
|       // since the same event order between keydown and keyup makes
 | |
|       // the following code complicated.
 | |
|       if (HasAltGr()) {
 | |
|         isAltGrKeyPress = true;
 | |
|         aModifierFlags &= ~nsIWidget::CTRL_L;
 | |
|         aModifierFlags |= nsIWidget::ALTGRAPH;
 | |
|       }
 | |
|       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, WM_KEYDOWN);
 | |
|   if (!isAltGrKeyPress) {
 | |
|     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;
 | |
|     }
 | |
|     // When AltGr key is pressed, both ControlLeft and AltRight cause
 | |
|     // WM_KEYDOWN messages.
 | |
|     bool makeSysKeyMsg =
 | |
|         !(aModifierFlags & nsIWidget::ALTGRAPH) && 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();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   keySequence.Clear();
 | |
|   if (!isAltGrKeyPress) {
 | |
|     keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific));
 | |
|   }
 | |
|   WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYUP);
 | |
|   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] = 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.
 | |
|     // NOTE: When AltGr was pressed, ControlLeft causes WM_SYSKEYUP normally.
 | |
|     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
 |