Bug 1335306 Append active keyboard layout information into crash report when NativeKey crashes due to detecting impossible case caused by 3rd party's keyboard layout or utils r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 31 Jan 2017 23:23:06 +0900
changeset 468427 5516f780bdba9282940146dbd979f185db817ccc
parent 468413 8aca58dcf22cd252b656a089b97a12071dd309b2
child 543953 a1833b58846af42a9adacce387e5b0b4509b3089
push id43469
push usermasayuki@d-toybox.com
push dateTue, 31 Jan 2017 14:24:12 +0000
reviewersm_kato
bugs1335306
milestone54.0a1
Bug 1335306 Append active keyboard layout information into crash report when NativeKey crashes due to detecting impossible case caused by 3rd party's keyboard layout or utils r?m_kato When NativeKey crashes by itself, it means that we detect an impossible situation in usual environment. In such case, active 3rd party's keyboard layout or something other utility may hook API and returns odd result to us. For investigating the cause and deciding what we should do in such cases, we should collect active keyboard layout name via crash reports. MozReview-Commit-ID: HYRj24GwDHZ
widget/windows/KeyboardLayout.cpp
widget/windows/KeyboardLayout.h
--- a/widget/windows/KeyboardLayout.cpp
+++ b/widget/windows/KeyboardLayout.cpp
@@ -14,16 +14,17 @@
 
 #include "nsAlgorithm.h"
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 #include "nsGkAtoms.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIIdleServiceInternal.h"
+#include "nsIWindowsRegKey.h"
 #include "nsMemory.h"
 #include "nsPrintfCString.h"
 #include "nsQuickSort.h"
 #include "nsServiceManagerUtils.h"
 #include "nsToolkit.h"
 #include "nsUnicharUtils.h"
 #include "nsWindowDbg.h"
 
@@ -2943,21 +2944,24 @@ NativeKey::GetFollowingCharMessage(MSG& 
         ("%p   NativeKey::GetFollowingCharMessage(), FAILED, lost target "
          "message to remove, nextKeyMsg=%s",
          this, ToString(nextKeyMsg).get()));
     }
 
     if (doCrash) {
 #ifdef MOZ_CRASHREPORTER
       nsPrintfCString info("\nPeekMessage() failed to remove char message! "
+                           "\nActive keyboard layout=0x%08X (%s), "
                            "\nHandling message: %s, InSendMessageEx()=%s, "
                            "\nFound message: %s, "
                            "\nWM_NULL has been removed: %d, "
                            "\nNext key message in all windows: %s, "
                            "time=%d, ",
+                           KeyboardLayout::GetActiveLayout(),
+                           KeyboardLayout::GetActiveLayoutName().get(),
                            ToString(mMsg).get(),
                            GetResultOfInSendMessageEx().get(),
                            ToString(nextKeyMsg).get(), i,
                            ToString(nextKeyMsgInAllWindows).get(),
                            nextKeyMsgInAllWindows.time);
       CrashReporter::AppendAppNotesToCrashReport(info);
       MSG nextMsg;
       if (WinUtils::PeekMessage(&nextMsg, 0, 0, 0,
@@ -3001,19 +3005,22 @@ NativeKey::GetFollowingCharMessage(MSG& 
     if (!MayBeSameCharMessage(removedMsg, nextKeyMsg)) {
       MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
         ("%p   NativeKey::GetFollowingCharMessage(), FAILED, removed message "
          "is really different from what we have already found, removedMsg=%s, "
          "nextKeyMsg=%s",
          this, ToString(removedMsg).get(), ToString(nextKeyMsg).get()));
 #ifdef MOZ_CRASHREPORTER
       nsPrintfCString info("\nPeekMessage() removed unexpcted char message! "
+                           "\nActive keyboard layout=0x%08X (%s), "
                            "\nHandling message: %s, InSendMessageEx()=%s, "
                            "\nFound message: %s, "
                            "\nRemoved message: %s, ",
+                           KeyboardLayout::GetActiveLayout(),
+                           KeyboardLayout::GetActiveLayoutName().get(),
                            ToString(mMsg).get(),
                            GetResultOfInSendMessageEx().get(),
                            ToString(nextKeyMsg).get(),
                            ToString(removedMsg).get());
       CrashReporter::AppendAppNotesToCrashReport(info);
       // What's the next key message?
       MSG nextKeyMsgAfter;
       if (WinUtils::PeekMessage(&nextKeyMsgAfter, mMsg.hwnd,
@@ -3052,18 +3059,21 @@ NativeKey::GetFollowingCharMessage(MSG& 
     return true;
   }
   MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
     ("%p   NativeKey::GetFollowingCharMessage(), FAILED, removed messages "
      "are all WM_NULL, nextKeyMsg=%s",
      this, ToString(nextKeyMsg).get()));
 #ifdef MOZ_CRASHREPORTER
   nsPrintfCString info("\nWe lost following char message! "
+                       "\nActive keyboard layout=0x%08X (%s), "
                        "\nHandling message: %s, InSendMessageEx()=%s, \n"
                        "Found message: %s, removed a lot of WM_NULL",
+                       KeyboardLayout::GetActiveLayout(),
+                       KeyboardLayout::GetActiveLayoutName().get(),
                        ToString(mMsg).get(),
                        GetResultOfInSendMessageEx().get(),
                        ToString(nextKeyMsg).get());
   CrashReporter::AppendAppNotesToCrashReport(info);
 #endif // #ifdef MOZ_CRASHREPORTER
   MOZ_CRASH("We lost the following char message");
   return false;
 }
@@ -3883,29 +3893,148 @@ KeyboardLayout::GetCompositeChar(char16_
   }
   int32_t key = GetKeyIndex(mActiveDeadKeys[0]);
   if (key < 0) {
     return 0;
   }
   return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar);
 }
 
+// static
+HKL
+KeyboardLayout::GetActiveLayout()
+{
+  return GetInstance()->mKeyboardLayout;
+}
+
+// static
+nsCString
+KeyboardLayout::GetActiveLayoutName()
+{
+  return GetInstance()->GetLayoutName(GetActiveLayout());
+}
+
+static bool IsValidKeyboardLayoutsChild(const nsAString& aChildName)
+{
+  if (aChildName.Length() != 8) {
+    return false;
+  }
+  for (size_t i = 0; i < aChildName.Length(); i++) {
+    if ((aChildName[i] >= '0' && aChildName[i] <= '9') ||
+        (aChildName[i] >= 'a' && aChildName[i] <= 'f') ||
+        (aChildName[i] >= 'A' && aChildName[i] <= 'F')) {
+      continue;
+    }
+    return false;
+  }
+  return true;
+}
+
+nsCString
+KeyboardLayout::GetLayoutName(HKL aLayout) const
+{
+  const wchar_t kKeyboardLayouts[] =
+    L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\";
+  uint16_t language = reinterpret_cast<uintptr_t>(aLayout) & 0xFFFF;
+  uint16_t layout = (reinterpret_cast<uintptr_t>(aLayout) >> 16) & 0xFFFF;
+  // If the layout is less than 0xA000XXXX (normal keyboard layout for the
+  // language) or 0xEYYYXXXX (IMM-IME), we can retrieve its name simply.
+  if (layout < 0xA000 || (layout & 0xF000) == 0xE000) {
+    nsAutoString key(kKeyboardLayouts);
+    key.AppendPrintf("%08X", layout < 0xA000 ?
+                               layout : reinterpret_cast<uintptr_t>(aLayout));
+    wchar_t buf[256];
+    if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+                                             key.get(), L"Layout Text",
+                                             buf, sizeof(buf)))) {
+      return NS_LITERAL_CSTRING("No name or too long name");
+    }
+    return NS_ConvertUTF16toUTF8(buf);
+  }
+
+  if (NS_WARN_IF((layout & 0xF000) != 0xF000)) {
+    nsAutoCString result;
+    result.AppendPrintf("Odd HKL: 0x%08X",
+                        reinterpret_cast<uintptr_t>(aLayout));
+    return result;
+  }
+
+  // Otherwise, we need to walk the registry under "Keyboard Layouts".
+  nsCOMPtr<nsIWindowsRegKey> regKey =
+    do_CreateInstance("@mozilla.org/windows-registry-key;1");
+  if (NS_WARN_IF(!regKey)) {
+    return EmptyCString();
+  }
+  nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+                             nsString(kKeyboardLayouts),
+                             nsIWindowsRegKey::ACCESS_READ);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return EmptyCString();
+  }
+  uint32_t childCount = 0;
+  if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) ||
+      NS_WARN_IF(!childCount)) {
+    return EmptyCString();
+  }
+  for (uint32_t i = 0; i < childCount; i++) {
+    nsAutoString childName;
+    if (NS_WARN_IF(NS_FAILED(regKey->GetChildName(i, childName))) ||
+        !IsValidKeyboardLayoutsChild(childName)) {
+      continue;
+    }
+    uint32_t childNum = static_cast<uint32_t>(childName.ToInteger64(&rv, 16));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+    // Ignore normal keyboard layouts for each language.
+    if (childNum <= 0xFFFF) {
+      continue;
+    }
+    // If it doesn't start with 'A' nor 'a', language should be matched.
+    if ((childNum & 0xFFFF) != language &&
+        (childNum & 0xF0000000) != 0xA0000000) {
+      continue;
+    }
+    // Then, the child should have "Layout Id" which is "YYY" of 0xFYYYXXXX.
+    nsAutoString key(kKeyboardLayouts);
+    key += childName;
+    wchar_t buf[256];
+    if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+                                             key.get(), L"Layout Id",
+                                             buf, sizeof(buf)))) {
+      continue;
+    }
+    uint16_t layoutId = wcstol(buf, nullptr, 16);
+    if (layoutId != (layout & 0x0FFF)) {
+      continue;
+    }
+    if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+                                             key.get(), L"Layout Text",
+                                             buf, sizeof(buf)))) {
+      continue;
+    }
+    return NS_ConvertUTF16toUTF8(buf);
+  }
+  return EmptyCString();
+}
+
 void
 KeyboardLayout::LoadLayout(HKL aLayout)
 {
   mIsPendingToRestoreKeyboardLayout = false;
 
   if (mKeyboardLayout == aLayout) {
     return;
   }
 
   mKeyboardLayout = aLayout;
 
-  MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
-    ("KeyboardLayout::LoadLayout(aLayout=0x%08X)", aLayout));
+  MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
+    ("KeyboardLayout::LoadLayout(aLayout=0x%08X (%s))",
+     aLayout, GetLayoutName(aLayout).get()));
 
   BYTE kbdState[256];
   memset(kbdState, 0, sizeof(kbdState));
 
   BYTE originalKbdState[256];
   // Bitfield with all shift states that have at least one dead-key.
   uint16_t shiftStatesWithDeadKeys = 0;
   // Bitfield with all shift states that produce any possible dead-key base
--- a/widget/windows/KeyboardLayout.h
+++ b/widget/windows/KeyboardLayout.h
@@ -673,16 +673,18 @@ private:
   }
 };
 
 class KeyboardLayout
 {
 public:
   static KeyboardLayout* GetInstance();
   static void Shutdown();
+  static HKL GetActiveLayout();
+  static nsCString GetActiveLayoutName();
   static void NotifyIdleServiceOfUserActivity();
 
   static bool IsPrintableCharKey(uint8_t aVirtualKey);
 
   /**
    * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState.
    * This method isn't stateful.
    */
@@ -848,16 +850,22 @@ private:
 
   /**
    * Loads the specified keyboard layout. This method always clear the dead key
    * state.
    */
   void LoadLayout(HKL aLayout);
 
   /**
+   * Gets the keyboard layout name of aLayout.  Be careful, this may be too
+   * slow to call at handling user input.
+   */
+  nsCString GetLayoutName(HKL aLayout) const;
+
+  /**
    * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or
    * WM_KEYUP.  This method is stateful.  This saves current dead key state at
    * WM_KEYDOWN.  Additionally, computes current inputted character(s) and set
    * them to the aNativeKey.
    */
   void InitNativeKey(NativeKey& aNativeKey,
                      const ModifierKeyState& aModKeyState);