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 a=jcristau
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 31 Jan 2017 23:23:06 +0900
changeset 375796 3c3be02271337ec3d23c4515f2aa7041c8ee4c09
parent 375795 a4e9d4072936261641774d5b135f12a4b1b23db5
child 375797 594b1915813622cc282e1878f7b6d2875ff7aa99
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato, jcristau
bugs1335306
milestone53.0a2
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 a=jcristau 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"
 
@@ -2910,21 +2911,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,
@@ -2968,19 +2972,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,
@@ -3019,18 +3026,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;
 }
@@ -3841,29 +3851,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
@@ -668,16 +668,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.
    */
@@ -843,16 +845,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);