Bug 900750 - part 2: Make ModifierKeyState and VirtualKey treat AltGraph as new modifier and won't set Control and Alt state while AltGraph is active r?m_kato, smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 30 May 2018 17:27:31 +0900
changeset 806854 d6e496308d305b68b6123462c2f38fba728fea7d
parent 806853 ca9456ac0ec037482b7c98adbc3da6532e224cd1
child 806855 3ef5ae9b6681961533d63d0df9650cacd9c264de
push id112973
push usermasayuki@d-toybox.com
push dateTue, 12 Jun 2018 10:30:22 +0000
reviewersm_kato, smaug
bugs900750
milestone62.0a1
Bug 900750 - part 2: Make ModifierKeyState and VirtualKey treat AltGraph as new modifier and won't set Control and Alt state while AltGraph is active r?m_kato, smaug By the proposal from Google, <https://github.com/w3c/uievents/issues/147>, Chromium treat AltRight key as "AltGraph" modifier if the keyboard layout has AltGr key. When AltRight key is pressed with a keyboard layout which has AltGr key, modifiers should as following: 1. "keydown" for ControlLeft: ctrlKey: true, altKey: false, getModifierState("AltGraph"): false 2. "keydown" for AltRight: ctrlKey: false, altKey: false, getModifierState("AltGraph"): true 3. Some "keydown", "keypress" and "keyup" events: ctrlKey: false, altKey: false, getModifierState("AltGraph"): true 4. "keyup" for ControlLeft: ctrlKey: false, altKey: false, getModifierState("AltGraph"): true 5. "keyup" for AltRight: ctrlKey: false, altKey: false, getModifierState("AltGraph"): false So, only when the preceding "keydown" event for ControlLeft, ctrlKey should be set to true as usual. However, after AltRight key is pressed actually, we should treat "AltGraph" modifier is true and both ctrlKey and altKey should be set to false for web apps can handle text input normally. So, MODIFIER_ALTGRAPH and MODIFIER_CONTROL/MODIFIER_ALT should not be set at the same time. This patch makes ModifierKeyState have only MODIFIER_ALTGRAPH or MODIFIER_CONTROL/MODIFIER_ALT. Additionally, this patch makes VirtualKey::ShiftState treat "AltGraph" as a modifier. So, now, VirtualKey needs to convert ShiftState to index value when it accesses its mShiftStates array. Therefore, this patch adds VirtualKey::ToIndex() and make each VirtualKey method use it before accessing mShiftStates. Note that this patch also fixes bug of WinUtils::SetupKeyModifiersSequence(). The constructor of KeyPair takes 2 keycode values, but the second virtual keycode can have scancode to distinguish if the key is left or right. However, WinUtils::SetupKeyModifiersSequence() never sets scancode to KeyPair. Therefore, it fails to dispatch AltRight key event. MozReview-Commit-ID: 7ealxJH9KlZ
testing/mochitest/tests/SimpleTest/EventUtils.js
widget/nsIWidget.h
widget/tests/test_keycodes.xul
widget/windows/KeyboardLayout.cpp
widget/windows/KeyboardLayout.h
widget/windows/WinModifierKeyState.h
widget/windows/WinMouseScrollHandler.cpp
widget/windows/WinUtils.cpp
widget/windows/WinUtils.h
widget/windows/nsWindowDefs.h
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -1051,17 +1051,17 @@ function _parseNativeModifiers(aModifier
 
   if (aModifiers.accelKey) {
     modifiers |= _EU_isMac(aWindow) ? 0x00004000 : 0x00000400;
   }
   if (aModifiers.accelRightKey) {
     modifiers |= _EU_isMac(aWindow) ? 0x00008000 : 0x00000800;
   }
   if (aModifiers.altGrKey) {
-    modifiers |= _EU_isWin(aWindow) ? 0x00002800 : 0x00001000;
+    modifiers |= _EU_isWin(aWindow) ? 0x00020000 : 0x00001000;
   }
   return modifiers;
 }
 
 // Mac: Any unused number is okay for adding new keyboard layout.
 //      When you add new keyboard layout here, you need to modify
 //      TISInputSourceWrapper::InitByLayoutID().
 // Win: These constants can be found by inspecting registry keys under
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -1491,30 +1491,35 @@ class nsIWidget : public nsISupports
                     int32_t aVertical) = 0;
 
     /**
      * Begin a window moving drag, based on the event passed in.
      */
     virtual MOZ_MUST_USE nsresult
     BeginMoveDrag(mozilla::WidgetMouseEvent* aEvent) = 0;
 
-    enum Modifiers {
-        CAPS_LOCK = 0x01, // when CapsLock is active
-        NUM_LOCK = 0x02, // when NumLock is active
-        SHIFT_L = 0x0100,
-        SHIFT_R = 0x0200,
-        CTRL_L = 0x0400,
-        CTRL_R = 0x0800,
-        ALT_L = 0x1000, // includes Option
-        ALT_R = 0x2000,
-        COMMAND_L = 0x4000,
-        COMMAND_R = 0x8000,
-        HELP = 0x10000,
-        FUNCTION = 0x100000,
-        NUMERIC_KEY_PAD = 0x01000000 // when the key is coming from the keypad
+    enum Modifiers
+    {
+      CAPS_LOCK =       0x00000001, // when CapsLock is active
+      NUM_LOCK =        0x00000002, // when NumLock is active
+      SHIFT_L =         0x00000100,
+      SHIFT_R =         0x00000200,
+      CTRL_L =          0x00000400,
+      CTRL_R =          0x00000800,
+      ALT_L =           0x00001000, // includes Option
+      ALT_R =           0x00002000,
+      COMMAND_L =       0x00004000,
+      COMMAND_R =       0x00008000,
+      HELP =            0x00010000,
+      ALTGRAPH =        0x00020000, // AltGr key on Windows.  This emulates
+                                    // AltRight key behavior of keyboard
+                                    // layouts which maps AltGr to AltRight
+                                    // key.
+      FUNCTION =        0x00100000,
+      NUMERIC_KEY_PAD = 0x01000000 // when the key is coming from the keypad
     };
     /**
      * Utility method intended for testing. Dispatches native key events
      * to this widget to simulate the press and release of a key.
      * @param aNativeKeyboardLayout a *platform-specific* constant.
      * On Mac, this is the resource ID for a 'uchr' or 'kchr' resource.
      * On Windows, it is converted to a hex string and passed to
      * LoadKeyboardLayout, see
--- a/widget/tests/test_keycodes.xul
+++ b/widget/tests/test_keycodes.xul
@@ -118,16 +118,19 @@ function eventToString(aEvent)
     name += " [Ctrl]";
   }
   if (aEvent.modifiers.ctrlRightKey) {
     name += " [Right Ctrl]";
   }
   if (aEvent.modifiers.altKey) {
     name += " [Alt]";
   }
+  if (aEvent.modifiers.altGrKey) {
+    name += " [AltGr]";
+  }
   if (aEvent.modifiers.altRightKey) {
     name += " [Right Alt]";
   }
   if (aEvent.modifiers.metaKey) {
     name += " [Command]";
   }
   if (aEvent.modifiers.metaRightKey) {
     name += " [Right Command]";
@@ -181,16 +184,17 @@ function synthesizeKey(aEvent, aFocusEle
                              aEvent.chars, aEvent.unmodifiedChars,
                              aCallback);
 }
 
 // Test the charcodes and modifiers being delivered to keypress handlers and
 // also keydown/keyup events too.
 function* runKeyEventTests()
 {
+  var name; // Current test name. Needs to be renamed later.
   var eventList, keyDownFlags, keyUpFlags, testingEvent, expectedDOMKeyCode;
   const kShiftFlag    = 0x1;
   const kCtrlFlag     = 0x2;
   const kAltFlag      = 0x4;
   const kMetaFlag     = 0x8;
   const kNumLockFlag  = 0x10;
   const kCapsLockFlag = 0x20;
 
@@ -213,53 +217,64 @@ function* runKeyEventTests()
     function isStateChangingModifierKeyEvent(e)
     {
       var flags = 0;
       if (e.type == "keydown") {
         flags = keyDownFlags ^ keyUpFlags;
       } else if (e.type == "keyup") {
         flags = keyUpFlags;
       }
-      switch (e.keyCode) {
-        case e.DOM_VK_SHIFT:
+      switch (e.key) {
+        case "Shift":
           is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of Shift " + e.type + " event mismatch");
           is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of Shift " + e.type + " event mismatch");
           is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of Shift " + e.type + " event mismatch");
           is(e.shiftKey, e.type == "keydown", name + ", Shift of Shift " + e.type + " event mismatch");
           return (testingEvent.modifiers.shiftKey || testingEvent.modifiers.shiftRightKey) &&
                  removeFlag(e, kShiftFlag) && expectedDOMKeyCode != e.keyCode;
-        case e.DOM_VK_CONTROL:
+        case "Control":
           is(e.ctrlKey, e.type == "keydown", name + ", Ctrl of Ctrl " + e.type + " event mismatch");
           is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of Ctrl " + e.type + " event mismatch");
-          is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of Ctrl " + e.type + " event mismatch");
+          // When AltGr key is released on Windows, ControlLeft keyup event
+          // is followed by AltRight keyup event.  However, altKey should be
+          // false in such case.
+          is(e.altKey, (flags & kAltFlag) != 0 && !(IS_WIN && testingEvent.modifiers.altGrKey),
+             name + ", Alt of Ctrl " + e.type + " event mismatch");
           is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift of Ctrl " + e.type + " event mismatch");
           return (testingEvent.modifiers.ctrlKey || testingEvent.modifiers.ctrlRightKey || (IS_WIN && testingEvent.modifiers.altGrKey)) &&
                  removeFlag(e, kCtrlFlag) && expectedDOMKeyCode != e.keyCode;
-        case e.DOM_VK_ALT:
-          is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of Alt " + e.type + " event mismatch");
+        case "Alt":
+          // When AltGr key is pressed on Windows, ControlLeft keydown event
+          // is fired before AltLeft keydown.  However, ctrlKey should be
+          // true only when the first ControlLeft keydown event.
+          is(e.ctrlKey, (flags & kCtrlFlag) != 0 && !(IS_WIN && testingEvent.modifiers.altGrKey),
+             name + ", Ctrl of Alt " + e.type + " event mismatch");
           is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of Alt " + e.type + " event mismatch");
-          is(e.altKey, e.type == "keydown", name + ", Alt of Alt " + e.type + " event mismatch");
+          // When AltGr key is pressed on Windows, altKey of any events should
+          // be set to false.
+          is(e.altKey, e.type == "keydown" && !(IS_WIN && testingEvent.modifiers.altGrKey),
+             name + ", Alt of Alt " + e.type + " event mismatch");
           is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift of Alt " + e.type + " event mismatch");
           return (testingEvent.modifiers.altKey || testingEvent.modifiers.altRightKey || (IS_WIN && testingEvent.modifiers.altGrKey)) &&
                  removeFlag(e, kAltFlag) && expectedDOMKeyCode != e.keyCode;
-        case e.DOM_VK_META:
+        case "Meta":
           is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of Command " + e.type + " evnet mismatch");
           is(e.metaKey, e.type == "keydown", name + ", Command of Command " + e.type + " evnet mismatch");
           is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of Command " + e.type + " evnet mismatch");
           is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift of Command " + e.type + " evnet mismatch");
           return (testingEvent.modifiers.metaKey || testingEvent.modifiers.metaRightKey) &&
                  removeFlag(e, kMetaFlag) && expectedDOMKeyCode != e.keyCode;
-        case e.DOM_VK_NUM_LOCK:
+        case "NumLock":
           is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of NumLock " + e.type + " event mismatch");
           is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of NumLock " + e.type + " event mismatch");
           is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of NumLock " + e.type + " event mismatch");
           is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift of NumLock " + e.type + " event mismatch");
           return (testingEvent.modifiers.numLockKey || testingEvent.modifiers.numericKeyPadKey) &&
                  removeFlag(e, kNumLockFlag) && expectedDOMKeyCode != e.keyCode;
-        case e.DOM_VK_CAPS_LOCK:
+        case "CapsLock":
           is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of CapsLock " + e.type + " event mismatch");
           is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of CapsLock " + e.type + " event mismatch");
           is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of CapsLock " + e.type + " event mismatch");
           is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift of CapsLock " + e.type + " event mismatch");
           return testingEvent.modifiers.capsLockKey &&
                  removeFlag(e, kCapsLockFlag) && expectedDOMKeyCode != e.keyCode;
       }
       return false;
@@ -328,17 +343,17 @@ function* runKeyEventTests()
         keyDownFlags |= kCapsLockFlag;
       }
       keyUpFlags = keyDownFlags;
     }
 
     testingEvent = aEvent;
     expectedDOMKeyCode = aExpectedGeckoKeyCode;
 
-    var name = eventToString(aEvent);
+    name = eventToString(aEvent);
     ok(true, "Starting: " + name);
 
     return synthesizeKey(aEvent, "button", function() {
 
       var expectEventTypeList = [];
       if (aShouldDelivedEvent & SHOULD_DELIVER_KEYDOWN)
         expectEventTypeList.push("keydown");
       if (aShouldDelivedEvent & SHOULD_DELIVER_KEYPRESS) {
@@ -363,17 +378,17 @@ function* runKeyEventTests()
 
         if (firedEventType != "") {
           var e = eventList[i];
           if (e.type == "keypress") {
             var isCtrlExpected =
               !!(aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey) && !aEvent.isInputtingCharacters;
             var isAltExpected =
               !!(aEvent.modifiers.altKey || aEvent.modifiers.altRightKey) && !aEvent.isInputtingCharacters;
-            if (IS_WIN && (aEvent.modifiers.altGrKey || isCtrlExpected && isAltExpected)) {
+            if (IS_WIN && (isCtrlExpected && isAltExpected)) {
               isCtrlExpected = isAltExpected = (aEvent.chars == "");
             }
             is(e.ctrlKey, isCtrlExpected, name + ", Ctrl mismatch");
             is(e.metaKey, !!(aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey), name + ", Command mismatch");
             is(e.altKey, isAltExpected, name + ", Alt mismatch");
             is(e.shiftKey, !!(aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey), name + ", Shift mismatch");
           }
 
--- a/widget/windows/KeyboardLayout.cpp
+++ b/widget/windows/KeyboardLayout.cpp
@@ -680,86 +680,74 @@ static uint32_t sUniqueKeyEventId = 0;
  * mozilla::widget::ModifierKeyState
  *****************************************************************************/
 
 ModifierKeyState::ModifierKeyState()
 {
   Update();
 }
 
-ModifierKeyState::ModifierKeyState(bool aIsShiftDown,
-                                   bool aIsControlDown,
-                                   bool aIsAltDown)
+ModifierKeyState::ModifierKeyState(Modifiers aModifiers)
+ : mModifiers(aModifiers)
 {
-  Update();
-  Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_ALTGRAPH);
-  Modifiers modifiers = 0;
-  if (aIsShiftDown) {
-    modifiers |= MODIFIER_SHIFT;
-  }
-  if (aIsControlDown) {
-    modifiers |= MODIFIER_CONTROL;
-  }
-  if (aIsAltDown) {
-    modifiers |= MODIFIER_ALT;
-  }
-  if (modifiers) {
-    Set(modifiers);
-  }
-}
-
-ModifierKeyState::ModifierKeyState(Modifiers aModifiers) :
-  mModifiers(aModifiers)
-{
-  EnsureAltGr();
+  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 (IS_VK_DOWN(VK_CONTROL)) {
-    mModifiers |= MODIFIER_CONTROL;
-  }
-  if (IS_VK_DOWN(VK_MENU)) {
-    mModifiers |= MODIFIER_ALT;
+  // 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;
   }
-
-  EnsureAltGr();
 }
 
 void
 ModifierKeyState::Unset(Modifiers aRemovingModifiers)
 {
   mModifiers &= ~aRemovingModifiers;
-  // Note that we don't need to unset AltGr flag here automatically.
-  // For EditorBase, we need to remove Alt and Control flags but AltGr isn't
-  // checked in EditorBase, so, it can be kept.
 }
 
 void
 ModifierKeyState::Set(Modifiers aAddingModifiers)
 {
   mModifiers |= aAddingModifiers;
-  EnsureAltGr();
+  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) {
@@ -817,22 +805,16 @@ ModifierKeyState::IsControl() const
 
 bool
 ModifierKeyState::IsAlt() const
 {
   return (mModifiers & MODIFIER_ALT) != 0;
 }
 
 bool
-ModifierKeyState::IsAltGr() const
-{
-  return IsControl() && IsAlt();
-}
-
-bool
 ModifierKeyState::IsWin() const
 {
   return (mModifiers & MODIFIER_OS) != 0;
 }
 
 bool
 ModifierKeyState::MaybeMatchShortcutKey() const
 {
@@ -866,28 +848,16 @@ ModifierKeyState::IsNumLocked() const
 }
 
 bool
 ModifierKeyState::IsScrollLocked() const
 {
   return (mModifiers & MODIFIER_SCROLLLOCK) != 0;
 }
 
-void
-ModifierKeyState::EnsureAltGr()
-{
-  // If both Control key and Alt key are pressed, it means AltGr is pressed.
-  // Ideally, we should check whether the current keyboard layout has AltGr
-  // or not.  However, setting AltGr flags for keyboard which doesn't have
-  // AltGr must not be serious bug.  So, it should be OK for now.
-  if (IsAltGr()) {
-    mModifiers |= MODIFIER_ALTGRAPH;
-  }
-}
-
 /*****************************************************************************
  * mozilla::widget::UniCharsAndModifiers
  *****************************************************************************/
 
 void
 UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers)
 {
   mChars.Append(aUniChar);
@@ -957,49 +927,53 @@ UniCharsAndModifiers::operator+(const Un
 // static
 VirtualKey::ShiftState
 VirtualKey::ModifiersToShiftState(Modifiers aModifiers)
 {
   ShiftState state = 0;
   if (aModifiers & MODIFIER_SHIFT) {
     state |= STATE_SHIFT;
   }
-  if (aModifiers & MODIFIER_CONTROL) {
-    state |= STATE_CONTROL;
-  }
-  if (aModifiers & MODIFIER_ALT) {
-    state |= STATE_ALT;
+  if (aModifiers & MODIFIER_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_CONTROL) {
-    modifiers |= MODIFIER_CONTROL;
-  }
-  if (aShiftState & STATE_ALT) {
-    modifiers |= MODIFIER_ALT;
+  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;
   }
-  if ((modifiers & (MODIFIER_ALT | MODIFIER_CONTROL)) ==
-         (MODIFIER_ALT | MODIFIER_CONTROL)) {
-    modifiers |= MODIFIER_ALTGRAPH;
-  }
   return modifiers;
 }
 
 const DeadKeyTable*
 VirtualKey::MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
                                  uint32_t aEntries) const
 {
   if (!mIsDeadKey) {
@@ -1019,17 +993,17 @@ VirtualKey::MatchingDeadKeyTable(const D
   return nullptr;
 }
 
 void
 VirtualKey::SetNormalChars(ShiftState aShiftState,
                            const char16_t* aChars,
                            uint32_t aNumOfChars)
 {
-  NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+  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;
   }
@@ -1038,121 +1012,132 @@ VirtualKey::SetNormalChars(ShiftState aS
   for (uint32_t index = aNumOfChars; index < len; index++) {
     mShiftStates[aShiftState].Normal.Chars[index] = 0;
   }
 }
 
 void
 VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar)
 {
-  NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+  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);
 
-  if (!(aShiftState & STATE_CONTROL_ALT)) {
+  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()) {
-    result = GetNativeUniChars(aShiftState & ~STATE_CONTROL_ALT);
+    // 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(aShiftState)) {
-    // Even if the shifted chars and the unshifted chars are same, we
-    // should consume the Alt key state and the Ctrl key state when
-    // AltGr key is pressed. Because if we don't consume them, the input
-    // events are ignored on EditorBase. (I.e., Users cannot input the
-    // characters with this key combination.)
+  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(aShiftState & ~STATE_CONTROL_ALT);
+    GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
   if (!result.UniCharsEqual(unmodifiedReslt)) {
-    // Otherwise, we should consume the Alt key state and the Ctrl key state
-    // only when the shifted chars and unshifted chars are different.
     Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
     finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
     result.FillModifiers(finalModifiers);
   }
   return result;
 }
 
 
 UniCharsAndModifiers
 VirtualKey::GetNativeUniChars(ShiftState aShiftState) const
 {
-#ifdef DEBUG
-  if (aShiftState >= ArrayLength(mShiftStates)) {
-    nsPrintfCString warning("Shift state is out of range: "
-                            "aShiftState=%d, ArrayLength(mShiftState)=%d",
-                            aShiftState, ArrayLength(mShiftStates));
-    NS_WARNING(warning.get());
-  }
-#endif
-
+  const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
   UniCharsAndModifiers result;
   Modifiers modifiers = ShiftStateToModifiers(aShiftState);
   if (IsDeadKey(aShiftState)) {
-    result.Append(mShiftStates[aShiftState].DeadKey.DeadChar, modifiers);
+    result.Append(mShiftStates[kShiftStateIndex].DeadKey.DeadChar, modifiers);
     return result;
   }
 
-  uint32_t index;
-  uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
-  for (index = 0;
-       index < len && mShiftStates[aShiftState].Normal.Chars[index]; index++) {
-    result.Append(mShiftStates[aShiftState].Normal.Chars[index], modifiers);
+  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)
 {
-  NS_ASSERTION(aShiftState < 16, "aShiftState out of range");
-
   if (aShiftState & STATE_SHIFT) {
     aKbdState[VK_SHIFT] |= 0x80;
   } else {
     aKbdState[VK_SHIFT]  &= ~0x80;
     aKbdState[VK_LSHIFT] &= ~0x80;
     aKbdState[VK_RSHIFT] &= ~0x80;
   }
 
-  if (aShiftState & STATE_CONTROL) {
-    aKbdState[VK_CONTROL] |= 0x80;
-  } else {
-    aKbdState[VK_CONTROL]  &= ~0x80;
-    aKbdState[VK_LCONTROL] &= ~0x80;
+  if (aShiftState & STATE_ALTGRAPH) {
+    aKbdState[VK_CONTROL]  |= 0x80;
+    aKbdState[VK_LCONTROL] |= 0x80;
     aKbdState[VK_RCONTROL] &= ~0x80;
-  }
-
-  if (aShiftState & STATE_ALT) {
-    aKbdState[VK_MENU] |= 0x80;
+    aKbdState[VK_MENU]     |= 0x80;
+    aKbdState[VK_LMENU]    &= ~0x80;
+    aKbdState[VK_RMENU]    |= 0x80;
   } else {
-    aKbdState[VK_MENU]  &= ~0x80;
-    aKbdState[VK_LMENU] &= ~0x80;
-    aKbdState[VK_RMENU] &= ~0x80;
+    if (aShiftState & STATE_CONTROL) {
+      aKbdState[VK_CONTROL] |= 0x80;
+    } else {
+      aKbdState[VK_CONTROL]  &= ~0x80;
+      aKbdState[VK_LCONTROL] &= ~0x80;
+      aKbdState[VK_RCONTROL] &= ~0x80;
+    }
+
+    if (aShiftState & STATE_ALT) {
+      aKbdState[VK_MENU] |= 0x80;
+    } else {
+      aKbdState[VK_MENU]  &= ~0x80;
+      aKbdState[VK_LMENU] &= ~0x80;
+      aKbdState[VK_RMENU] &= ~0x80;
+    }
   }
 
   if (aShiftState & STATE_CAPSLOCK) {
     aKbdState[VK_CAPITAL] |= 0x01;
   } else {
     aKbdState[VK_CAPITAL] &= ~0x01;
   }
 }
@@ -2558,16 +2543,17 @@ NativeKey::HandleKeyDownMessage(bool* aE
     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.GetModifiers() & MODIFIER_ALTGRAPH) &&
       !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));
@@ -2679,20 +2665,21 @@ NativeKey::HandleCharMessage(const MSG& 
   }
 
   MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
     ("%p   NativeKey::HandleCharMessage(), initializing keypress "
      "event...", this));
 
   ModifierKeyState modKeyState(mModKeyState);
   // When AltGr is pressed, both Alt and Ctrl are active.  However, when they
-  // are active, EditorBase won't treat the keypress event as inputting a
+  // 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.IsAltGr() && IsPrintableCharMessage(aCharMsg)) {
+  if (modKeyState.IsControl() && modKeyState.IsAlt() &&
+      IsPrintableCharMessage(aCharMsg)) {
     modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
   }
   nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState, &aCharMsg);
   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
     ("%p   NativeKey::HandleCharMessage(), dispatching keypress event...",
      this));
   bool dispatched =
     mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
@@ -2812,17 +2799,20 @@ NativeKey::NeedsToHandleWithoutFollowing
   // 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 AltGraph may map a printable key to input no character.
+  // In such case, we need to eKeyPress event for backward compatibility.
   if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
+      !(mModKeyState.GetModifiers() & MODIFIER_ALTGRAPH) &&
       !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;
@@ -3893,18 +3883,17 @@ KeyboardLayout::InitNativeKey(NativeKey&
       // Currently, we are doing a ugly hack to keypress events to cause
       // inputting character even if Ctrl or Alt key is pressed, that is, we
       // remove Ctrl and Alt modifier state from keypress event.  However, for
       // example, Ctrl+Space which causes ' ' of WM_CHAR message never causes
       // keypress event whose ctrlKey is true.  For preventing this problem,
       // we should mark as not removable if Ctrl or Alt key does not cause
       // changing inputting character.
       if (IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode) &&
-          !aModKeyState.IsAltGr() &&
-          (aModKeyState.IsControl() || aModKeyState.IsAlt())) {
+          (aModKeyState.IsControl() ^ aModKeyState.IsAlt())) {
         ModifierKeyState state = aModKeyState;
         state.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
         UniCharsAndModifiers charsWithoutModifier =
           GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, state);
         aNativeKey.mCanIgnoreModifierStateAtKeyPress =
           !charsWithoutModifier.UniCharsEqual(
                                   aNativeKey.mCommittedCharsAndModifiers);
       }
@@ -5111,16 +5100,30 @@ KeyboardLayout::SynthesizeNativeKeyEvent
   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);
 
+  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;
@@ -5151,32 +5154,33 @@ KeyboardLayout::SynthesizeNativeKeyEvent
       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;
+      aModifierFlags &= ~(nsIWidget::ALT_R | nsIWidget::ALTGRAPH);
       argumentKeySpecific = aNativeKeyCode & 0xFF;
       aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
       break;
     case VK_CAPITAL:
       aModifierFlags &= ~nsIWidget::CAPS_LOCK;
       argumentKeySpecific = VK_CAPITAL;
       break;
     case VK_NUMLOCK:
       aModifierFlags &= ~nsIWidget::NUM_LOCK;
       argumentKeySpecific = VK_NUMLOCK;
       break;
   }
 
   AutoTArray<KeyPair,10> keySequence;
-  WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags);
+  WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags,
+                                      WM_KEYDOWN);
   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;
@@ -5193,17 +5197,20 @@ KeyboardLayout::SynthesizeNativeKeyEvent
       scanCode =
         ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
     }
     LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
     // If the scan code is for an extended key, set extended key flag.
     if ((scanCode & 0xFF00) == 0xE000) {
       lParam |= 0x1000000;
     }
-    bool makeSysKeyMsg = IsSysKey(key, modKeyState);
+    // 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) {
@@ -5239,20 +5246,25 @@ KeyboardLayout::SynthesizeNativeKeyEvent
           nativeKey.HandleCharMessage(charMsg);
         }
       }
     } else {
       NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
       nativeKey.HandleKeyDownMessage();
     }
   }
-  for (uint32_t i = keySequence.Length(); i > 0; --i) {
-    uint8_t key = keySequence[i - 1].mGeneral;
-    uint8_t keySpecific = keySequence[i - 1].mSpecific;
-    uint16_t scanCode = keySequence[i - 1].mScanCode;
+
+  keySequence.Clear();
+  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.
@@ -5261,16 +5273,17 @@ KeyboardLayout::SynthesizeNativeKeyEvent
         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();
   }
 
--- a/widget/windows/KeyboardLayout.h
+++ b/widget/windows/KeyboardLayout.h
@@ -39,25 +39,41 @@
 #define VK_OEM_102              0xE2
 #define VK_OEM_CLEAR            0xFE
 
 class nsIIdleServiceInternal;
 
 namespace mozilla {
 namespace widget {
 
-static const uint32_t sModifierKeyMap[][3] = {
-  { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 },
-  { nsIWidget::NUM_LOCK,  VK_NUMLOCK, 0 },
-  { nsIWidget::SHIFT_L,   VK_SHIFT,   VK_LSHIFT },
-  { nsIWidget::SHIFT_R,   VK_SHIFT,   VK_RSHIFT },
-  { nsIWidget::CTRL_L,    VK_CONTROL, VK_LCONTROL },
-  { nsIWidget::CTRL_R,    VK_CONTROL, VK_RCONTROL },
-  { nsIWidget::ALT_L,     VK_MENU,    VK_LMENU },
-  { nsIWidget::ALT_R,     VK_MENU,    VK_RMENU }
+enum ScanCode : uint16_t
+{
+  eCapsLock =                   0x003A,
+  eNumLock =                    0xE045,
+  eShiftLeft =                  0x002A,
+  eShiftRight =                 0x0036,
+  eControlLeft =                0x001D,
+  eControlRight =               0xE01D,
+  eAltLeft =                    0x0038,
+  eAltRight =                   0xE038,
+};
+
+// 0: nsIWidget's native modifier flag
+// 1: Virtual keycode which does not distinguish whether left or right location.
+// 2: Virtual keycode which distinguishes whether left or right location.
+// 3: Scan code.
+static const uint32_t sModifierKeyMap[][4] = {
+  { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0,           ScanCode::eCapsLock },
+  { nsIWidget::NUM_LOCK,  VK_NUMLOCK, 0,           ScanCode::eNumLock },
+  { nsIWidget::SHIFT_L,   VK_SHIFT,   VK_LSHIFT,   ScanCode::eShiftLeft },
+  { nsIWidget::SHIFT_R,   VK_SHIFT,   VK_RSHIFT,   ScanCode::eShiftRight },
+  { nsIWidget::CTRL_L,    VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft },
+  { nsIWidget::CTRL_R,    VK_CONTROL, VK_RCONTROL, ScanCode::eControlRight },
+  { nsIWidget::ALT_L,     VK_MENU,    VK_LMENU,    ScanCode::eAltLeft },
+  { nsIWidget::ALT_R,     VK_MENU,    VK_RMENU,    ScanCode::eAltRight }
 };
 
 class KeyboardLayout;
 
 class MOZ_STACK_CLASS UniCharsAndModifiers final
 {
 public:
   UniCharsAndModifiers() {}
@@ -198,16 +214,20 @@ public:
   };
 
   enum ShiftStateFlag
   {
     STATE_SHIFT    = 0x01,
     STATE_CONTROL  = 0x02,
     STATE_ALT      = 0x04,
     STATE_CAPSLOCK = 0x08,
+    // ShiftState needs to have AltGr state separately since this is necessary
+    // for lossless conversion with Modifiers.
+    STATE_ALTGRAPH = 0x80,
+    // Useful to remove or check Ctrl and Alt flags.
     STATE_CONTROL_ALT = STATE_CONTROL | STATE_ALT,
   };
 
   typedef uint8_t ShiftState;
 
   static ShiftState ModifiersToShiftState(Modifiers aModifiers);
   static ShiftState ModifierKeyStateToShiftState(
                       const ModifierKeyState& aModKeyState)
@@ -232,43 +252,56 @@ private:
       const DeadKeyTable* Table;
       char16_t DeadChar;
     } DeadKey;
   };
 
   KeyShiftState mShiftStates[16];
   uint16_t mIsDeadKey;
 
+  static uint8_t ToShiftStateIndex(ShiftState aShiftState)
+  {
+    if (!(aShiftState & STATE_ALTGRAPH)) {
+      MOZ_ASSERT(aShiftState <= eAltGrShiftWithCapsLock);
+      return static_cast<uint8_t>(aShiftState);
+    }
+    uint8_t index = aShiftState & ~STATE_ALTGRAPH;
+    index |= (STATE_ALT | STATE_CONTROL);
+    MOZ_ASSERT(index <= eAltGrShiftWithCapsLock);
+    return index;
+  }
+
   void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey)
   {
     if (aIsDeadKey) {
-      mIsDeadKey |= 1 << aShiftState;
+      mIsDeadKey |= 1 << ToShiftStateIndex(aShiftState);
     } else {
-      mIsDeadKey &= ~(1 << aShiftState);
+      mIsDeadKey &= ~(1 << ToShiftStateIndex(aShiftState));
     }
   }
 
 public:
   static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState);
 
   bool IsDeadKey(ShiftState aShiftState) const
   {
-    return (mIsDeadKey & (1 << aShiftState)) != 0;
+    return (mIsDeadKey & (1 << ToShiftStateIndex(aShiftState))) != 0;
   }
 
   /**
    * IsChangedByAltGr() is useful to check if a key with AltGr produces
    * different character(s) from the key without AltGr.
    * Note that this is designed for checking if a keyboard layout has AltGr
    * key.  So, this result may not exactly correct for the key since it's
    * okay to fails in some edge cases when we check all keys which produce
    * character(s) in a layout.
    */
   bool IsChangedByAltGr(ShiftState aShiftState) const
   {
+    MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
     MOZ_ASSERT(IsAltGrIndex(aShiftState));
     MOZ_ASSERT(IsDeadKey(aShiftState) ||
                mShiftStates[aShiftState].Normal.Chars[0]);
     const ShiftState kShiftStateWithoutAltGr =
       aShiftState - ShiftStateIndex::eAltGr;
     if (IsDeadKey(aShiftState) != IsDeadKey(kShiftStateWithoutAltGr)) {
       return false;
     }
@@ -287,42 +320,76 @@ public:
       }
     }
     return false;
   }
 
   void AttachDeadKeyTable(ShiftState aShiftState,
                           const DeadKeyTable* aDeadKeyTable)
   {
+    MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
     mShiftStates[aShiftState].DeadKey.Table = aDeadKeyTable;
   }
 
   void SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
                       uint32_t aNumOfChars);
   void SetDeadChar(ShiftState aShiftState, char16_t aDeadChar);
   const DeadKeyTable* MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
                                            uint32_t aEntries) const;
   inline char16_t GetCompositeChar(ShiftState aShiftState,
                                     char16_t aBaseChar) const
   {
-    return mShiftStates[aShiftState].DeadKey.Table->GetCompositeChar(aBaseChar);
+    return mShiftStates[ToShiftStateIndex(aShiftState)].
+             DeadKey.Table->GetCompositeChar(aBaseChar);
   }
 
   char16_t GetCompositeChar(const ModifierKeyState& aModKeyState,
                             char16_t aBaseChar) const
   {
     return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState),
                             aBaseChar);
   }
+
+  /**
+   * GetNativeUniChars() returns character(s) which is produced by the
+   * key with given modifiers.  This does NOT return proper MODIFIER_ALTGRAPH
+   * state because this is raw accessor of the database of this key.
+   */
   UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const;
   UniCharsAndModifiers GetNativeUniChars(
                          const ModifierKeyState& aModKeyState) const
   {
     return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState));
   }
+
+  /**
+   * GetUniChars() returns characters and modifiers which are not consumed
+   * to input the character.
+   * For example, if you specify Ctrl key but the key produces no character
+   * with Ctrl, this returns character(s) which is produced by the key
+   * without Ctrl.  So, the result is useful to decide KeyboardEvent.key
+   * value.
+   * Another example is, if you specify Ctrl key and the key produces
+   * different character(s) from the case without Ctrl key, this returns
+   * the character(s) *without* MODIFIER_CONTROL.  This modifier information
+   * is useful for eKeyPress since TextEditor does not treat eKeyPress events
+   * whose modifier includes MODIFIER_ALT and/or MODIFIER_CONTROL.
+   *
+   * @param aShiftState         Modifiers which you want to retrieve
+   *                            KeyboardEvent.key value for the key with.
+   *                            If AltGr key is pressed, this should include
+   *                            STATE_ALTGRAPH and should NOT include
+   *                            STATE_ALT nor STATE_CONTROL.
+   *                            If both Alt and Ctrl are pressed to emulate
+   *                            AltGr, this should include both STATE_ALT and
+   *                            STATE_CONTROL but should NOT include
+   *                            MODIFIER_ALTGRAPH.
+   *                            Then, this returns proper modifiers when
+   *                            this key produces no character with AltGr.
+   */
   UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const;
   UniCharsAndModifiers GetUniChars(const ModifierKeyState& aModKeyState) const
   {
     return GetUniChars(ModifierKeyStateToShiftState(aModKeyState));
   }
 };
 
 class MOZ_STACK_CLASS NativeKey final
--- a/widget/windows/WinModifierKeyState.h
+++ b/widget/windows/WinModifierKeyState.h
@@ -13,48 +13,50 @@
 
 namespace mozilla {
 namespace widget {
 
 class MOZ_STACK_CLASS ModifierKeyState final
 {
 public:
   ModifierKeyState();
-  ModifierKeyState(bool aIsShiftDown, bool aIsControlDown, bool aIsAltDown);
   explicit ModifierKeyState(Modifiers aModifiers);
 
   void Update();
 
   void Unset(Modifiers aRemovingModifiers);
   void Set(Modifiers aAddingModifiers);
 
   void InitInputEvent(WidgetInputEvent& aInputEvent) const;
 
+  // Do not create IsAltGr() because it's unclear whether:
+  // - AltGr key is actually pressed.
+  // - Both Ctrl and Alt keys are pressed when a keyboard layout which
+  //   has AltGr key.
+  // - Both Ctrl and Alt keys are pressed when a keyboard layout which
+  //   does not have AltGr key.
   bool IsShift() const;
   bool IsControl() const;
   bool IsAlt() const;
-  bool IsAltGr() const;
   bool IsWin() const;
 
   bool MaybeMatchShortcutKey() const;
 
   bool IsCapsLocked() const;
   bool IsNumLocked() const;
   bool IsScrollLocked() const;
 
   MOZ_ALWAYS_INLINE Modifiers GetModifiers() const
   {
     return mModifiers;
   }
 
 private:
   Modifiers mModifiers;
 
-  MOZ_ALWAYS_INLINE void EnsureAltGr();
-
   void InitMouseEvent(WidgetInputEvent& aMouseEvent) const;
 };
 
 const nsCString ToString(const ModifierKeyState& aModifierKeyState);
 
 } // namespace widget
 } // namespace mozilla
 
--- a/widget/windows/WinMouseScrollHandler.cpp
+++ b/widget/windows/WinMouseScrollHandler.cpp
@@ -292,17 +292,18 @@ MouseScrollHandler::SynthesizeNativeMous
 
   // Ensure to make the instance.
   GetInstance();
 
   BYTE kbdState[256];
   memset(kbdState, 0, sizeof(kbdState));
 
   AutoTArray<KeyPair,10> keySequence;
-  WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags);
+  WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags,
+                                      aNativeMessage);
 
   for (uint32_t i = 0; i < keySequence.Length(); ++i) {
     uint8_t key = keySequence[i].mGeneral;
     uint8_t keySpecific = keySequence[i].mSpecific;
     kbdState[key] = 0x81; // key is down and toggled on if appropriate
     if (keySpecific) {
       kbdState[keySpecific] = 0x81;
     }
@@ -346,16 +347,19 @@ ModifierKeyState
 MouseScrollHandler::GetModifierKeyState(UINT aMessage)
 {
   ModifierKeyState result;
   // Assume the Control key is down if the Elantech touchpad has sent the
   // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages.  (See the comment in
   // MouseScrollHandler::Device::Elantech::HandleKeyMessage().)
   if ((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == WM_MOUSEWHEEL) &&
       !result.IsControl() && Device::Elantech::IsZooming()) {
+    // XXX Do we need to unset MODIFIER_SHIFT, MODIFIER_ALT, MODIFIER_OS too?
+    //     If one of them are true, the default action becomes not zooming.
+    result.Unset(MODIFIER_ALTGRAPH);
     result.Set(MODIFIER_CONTROL);
   }
   return result;
 }
 
 POINT
 MouseScrollHandler::ComputeMessagePos(UINT aMessage,
                                       WPARAM aWParam,
--- a/widget/windows/WinUtils.cpp
+++ b/widget/windows/WinUtils.cpp
@@ -1758,22 +1758,50 @@ WinUtils::IsIMEEnabled(IMEState::Enabled
 {
   return (aIMEState == IMEState::ENABLED ||
           aIMEState == IMEState::PLUGIN);
 }
 
 /* static */
 void
 WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
-                                    uint32_t aModifiers)
+                                    uint32_t aModifiers,
+                                    UINT aMessage)
 {
-  for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) {
-    const uint32_t* map = sModifierKeyMap[i];
-    if (aModifiers & map[0]) {
-      aArray->AppendElement(KeyPair(map[1], map[2]));
+  MOZ_ASSERT(!(aModifiers & nsIWidget::ALTGRAPH) ||
+             !(aModifiers & (nsIWidget::CTRL_L | nsIWidget::ALT_R)));
+  if (aMessage == WM_KEYUP) {
+    // If AltGr is released, ControlLeft key is released first, then,
+    // AltRight key is released.
+    if (aModifiers & nsIWidget::ALTGRAPH) {
+      aArray->AppendElement(
+                KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
+      aArray->AppendElement(
+                KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
+    }
+    for (uint32_t i = ArrayLength(sModifierKeyMap); i; --i) {
+      const uint32_t* map = sModifierKeyMap[i - 1];
+      if (aModifiers & map[0]) {
+        aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
+      }
+    }
+  } else {
+    for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) {
+      const uint32_t* map = sModifierKeyMap[i];
+      if (aModifiers & map[0]) {
+        aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
+      }
+    }
+    // If AltGr is pressed, ControlLeft key is pressed first, then,
+    // AltRight key is pressed.
+    if (aModifiers & nsIWidget::ALTGRAPH) {
+      aArray->AppendElement(
+                KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
+      aArray->AppendElement(
+                KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
     }
   }
 }
 
 // This is in use here and in dom/events/TouchEvent.cpp
 /* static */
 uint32_t
 WinUtils::IsTouchDeviceSupportPresent()
--- a/widget/windows/WinUtils.h
+++ b/widget/windows/WinUtils.h
@@ -448,17 +448,18 @@ public:
   static bool IsIMEEnabled(const InputContext& aInputContext);
   static bool IsIMEEnabled(IMEState::Enabled aIMEState);
 
   /**
    * Returns modifier key array for aModifiers.  This is for
    * nsIWidget::SynthethizeNative*Event().
    */
   static void SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
-                                        uint32_t aModifiers);
+                                        uint32_t aModifiers,
+                                        UINT aMessage);
 
   /**
   * Does device have touch support
   */
   static uint32_t IsTouchDeviceSupportPresent();
 
   /**
   * The maximum number of simultaneous touch contacts supported by the device.
--- a/widget/windows/nsWindowDefs.h
+++ b/widget/windows/nsWindowDefs.h
@@ -90,16 +90,22 @@ struct KeyPair
   uint8_t mSpecific;
   uint16_t mScanCode;
   KeyPair(uint32_t aGeneral, uint32_t aSpecific)
     : mGeneral(aGeneral & 0xFF)
     , mSpecific(aSpecific & 0xFF)
     , mScanCode((aGeneral & 0xFFFF0000) >> 16)
   {
   }
+  KeyPair(uint8_t aGeneral, uint8_t aSpecific, uint16_t aScanCode)
+    : mGeneral(aGeneral)
+    , mSpecific(aSpecific)
+    , mScanCode(aScanCode)
+  {
+  }
 };
 
 #if (WINVER < 0x0600)
 struct TITLEBARINFOEX
 {
     DWORD cbSize;
     RECT rcTitleBar;
     DWORD rgstate[CCHILDREN_TITLEBAR + 1];