Bug 900750 - part 4: Make NativeKey replaces MODIFIER_CONTROL and MODIFIER_ALT of mModKeyState with MODIFIER_ALTGRAPH if user emulates AltGr key press with pressing both Ctrl and Alt keys and current keydown produces character(s) r?m_kato, smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 04 Jun 2018 14:45:28 +0900
changeset 806856 900222ca199f4fa91d2edd3eaeb84e7e13b6b7ad
parent 806855 3ef5ae9b6681961533d63d0df9650cacd9c264de
child 806857 d5673bbd1fee3b2c6eede1e96e50a057c828bf59
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 4: Make NativeKey replaces MODIFIER_CONTROL and MODIFIER_ALT of mModKeyState with MODIFIER_ALTGRAPH if user emulates AltGr key press with pressing both Ctrl and Alt keys and current keydown produces character(s) r?m_kato, smaug Users can emulate AltGr key with pressing both Ctrl key and Alt key on Windows since AltGr is represented as so in Windows and physical keyboard may not have AltRight key. If user emulates AltGr key, we should set MODIFIER_ALTGRAPH to a set of keyboard events for printable keys only when the key press produces character(s) or a dead key. For example: 1. ControlLeft keydown event should make ctrlKey true. 2. AltLeft keydown event should make altKey true (not AltGraph state). 3. ctrlKey and altKey of printable keydown, keypress and keyup events should be set to false, but getModifierState("AltGraph") should return true. 4. AltLeft keyup event should make altKey false. 5. ControlLeft keyup event should make ctrlKey false. (If AltLeft key is pressed first, altKey of AltLeft keydown is true and both altKey and ctrlKey of the following ControlLeft keydown are true as usual.) MozReview-Commit-ID: 8Km8GXPDQw1
testing/mochitest/tests/SimpleTest/EventUtils.js
widget/tests/test_keycodes.xul
widget/windows/KeyboardLayout.cpp
widget/windows/KeyboardLayout.h
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -1063,49 +1063,66 @@ function _parseNativeModifiers(aModifier
 
 // 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
 //      HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Keyboard Layouts
 
 const KEYBOARD_LAYOUT_ARABIC =
-  { name: "Arabic",             Mac: 6,    Win: 0x00000401 };
+  { name: "Arabic",             Mac: 6,
+                                Win: 0x00000401, hasAltGrOnWin: false };
 const KEYBOARD_LAYOUT_ARABIC_PC =
-  { name: "Arabic - PC",        Mac: 7,    Win: null       };
+  { name: "Arabic - PC",        Mac: 7,
+                                Win: null,       hasAltGrOnWin: false };
 const KEYBOARD_LAYOUT_BRAZILIAN_ABNT =
-  { name: "Brazilian ABNT",     Mac: null, Win: 0x00000416 };
+  { name: "Brazilian ABNT",     Mac: null,
+                                Win: 0x00000416, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_DVORAK_QWERTY =
-  { name: "Dvorak-QWERTY",      Mac: 4,    Win: null       };
+  { name: "Dvorak-QWERTY",      Mac: 4,
+                                Win: null,       hasAltGrOnWin: false };
 const KEYBOARD_LAYOUT_EN_US =
-  { name: "US",                 Mac: 0,    Win: 0x00000409 };
+  { name: "US",                 Mac: 0,
+                                Win: 0x00000409, hasAltGrOnWin: false };
 const KEYBOARD_LAYOUT_FRENCH =
-  { name: "French",             Mac: 8,    Win: 0x0000040C };
+  { name: "French",             Mac: 8,
+                                Win: 0x0000040C, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_GREEK =
-  { name: "Greek",              Mac: 1,    Win: 0x00000408 };
+  { name: "Greek",              Mac: 1,
+                                Win: 0x00000408, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_GERMAN =
-  { name: "German",             Mac: 2,    Win: 0x00000407 };
+  { name: "German",             Mac: 2,
+                                Win: 0x00000407, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_HEBREW =
-  { name: "Hebrew",             Mac: 9,    Win: 0x0000040D };
+  { name: "Hebrew",             Mac: 9,
+                                Win: 0x0000040D, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_JAPANESE =
-  { name: "Japanese",           Mac: null, Win: 0x00000411 };
+  { name: "Japanese",           Mac: null,
+                                Win: 0x00000411, hasAltGrOnWin: false };
 const KEYBOARD_LAYOUT_KHMER =
-  { name: "Khmer",              Mac: null, Win: 0x00000453 }; // available on Win7 or later.
+  { name: "Khmer",              Mac: null,
+                                Win: 0x00000453, hasAltGrOnWin: true  }; // available on Win7 or later.
 const KEYBOARD_LAYOUT_LITHUANIAN =
-  { name: "Lithuanian",         Mac: 10,   Win: 0x00010427 };
+  { name: "Lithuanian",         Mac: 10,
+                                Win: 0x00010427, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_NORWEGIAN =
-  { name: "Norwegian",          Mac: 11,   Win: 0x00000414 };
+  { name: "Norwegian",          Mac: 11,
+                                Win: 0x00000414, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC =
-  { name: "Russian - Mnemonic", Mac: null, Win: 0x00020419 }; // available on Win8 or later.
+  { name: "Russian - Mnemonic", Mac: null,
+                                Win: 0x00020419, hasAltGrOnWin: true  }; // available on Win8 or later.
 const KEYBOARD_LAYOUT_SPANISH =
-  { name: "Spanish",            Mac: 12,   Win: 0x0000040A };
+  { name: "Spanish",            Mac: 12,
+                                Win: 0x0000040A, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_SWEDISH =
-  { name: "Swedish",            Mac: 3,    Win: 0x0000041D };
+  { name: "Swedish",            Mac: 3,
+                                Win: 0x0000041D, hasAltGrOnWin: true  };
 const KEYBOARD_LAYOUT_THAI =
-  { name: "Thai",               Mac: 5,    Win: 0x0002041E };
+  { name: "Thai",               Mac: 5,
+                                Win: 0x0002041E, hasAltGrOnWin: false };
 
 /**
  * synthesizeNativeKey() dispatches native key event on active window.
  * This is implemented only on Windows and Mac. Note that this function
  * dispatches the key event asynchronously and returns immediately. If a
  * callback function is provided, the callback will be called upon
  * completion of the key dispatch.
  *
--- a/widget/tests/test_keycodes.xul
+++ b/widget/tests/test_keycodes.xul
@@ -223,62 +223,77 @@ function* runKeyEventTests()
         flags = keyUpFlags;
       }
       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");
+          // AltGr on Windows is always pressed after and released before Shift key operation.
+          is(e.getModifierState("AltGraph"), (IS_MAC && e.altKey),
+             name + ", AltGraph of Shift " + e.type + " event mismatch");
           return (testingEvent.modifiers.shiftKey || testingEvent.modifiers.shiftRightKey) &&
                  removeFlag(e, kShiftFlag) && expectedDOMKeyCode != e.keyCode;
         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");
           // 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),
+          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)) &&
+          is(e.getModifierState("AltGraph"),
+             (IS_WIN && !!testingEvent.modifiers.altGrKey && e.type == "keyup") || (IS_MAC && e.altKey),
+             name + ", AltGraph 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 "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),
+          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");
-          // 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),
+          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)) &&
+          is(e.getModifierState("AltGraph"),
+             e.type == "keydown" && ((IS_WIN && !!testingEvent.modifiers.altGrKey) || (IS_MAC && e.altKey)),
+             name + ", AltGraph 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 "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");
+          is(e.getModifierState("AltGraph"),
+             (IS_WIN && (flags & kAltGraphFlag) != 0) || (IS_MAC && e.altKey),
+             name + ", AltGraph of Meta " + e.type + " event mismatch");
           return (testingEvent.modifiers.metaKey || testingEvent.modifiers.metaRightKey) &&
                  removeFlag(e, kMetaFlag) && expectedDOMKeyCode != e.keyCode;
         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");
+          is(e.getModifierState("AltGraph"), false,
+             name + ", AltGraph of NumLock " + e.type + " event mismatch");
+          // AltGr on Windows is always pressed after and released before NumLock key operation.
           return (testingEvent.modifiers.numLockKey || testingEvent.modifiers.numericKeyPadKey) &&
                  removeFlag(e, kNumLockFlag) && expectedDOMKeyCode != e.keyCode;
         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");
+          // AltGr on Windows is always pressed after and released before CapsLock key operation.
+          is(e.getModifierState("AltGraph"), false,
+             name + ", AltGraph of CapsLock " + e.type + " event mismatch");
           return testingEvent.modifiers.capsLockKey &&
                  removeFlag(e, kCapsLockFlag) && expectedDOMKeyCode != e.keyCode;
       }
       return false;
     }
 
     // Ignore the state changing key events which is fired by the testing event.
     if (!isStateChangingModifierKeyEvent(e))
@@ -372,35 +387,64 @@ function* runKeyEventTests()
         var firedEventType = i < eventList.length ? eventList[i].type : "";
         var expectEventType = i < expectEventTypeList.length ? expectEventTypeList[i] : "";
         if (firedEventType != "")
           is(firedEventType, expectEventType, name + ", " + expectEventType + " should be fired");
         else
           is(firedEventType, expectEventType, name + ", a needed event is not fired");
 
         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 && (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");
-          }
-
           var expectedKeyValue =
             typeof aExpectedKeyValues === "string" ? aExpectedKeyValues :
                      i < aExpectedKeyValues.length ? aExpectedKeyValues[i] :
                                                      undefined;
+
+          var e = eventList[i];
+          switch (e.key) {
+            case "Shift":
+            case "Control":
+            case "Alt":
+            case "AltGraph":
+            case "Meta":
+            case "CapsLock":
+            case "NumLock":
+              // XXX To check modifier state of modifiers, we need to check
+              //     e.type since modifier key may change modifier state.
+              //     However, doing it makes the following check more
+              //     complicated.  So, we ignore the modifier state of
+              //     modifier keydown/keyup events for now.
+              break;
+            default:
+              is(e.shiftKey, !!(aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey),
+                 name + ", Shift of " + e.type + " of " + e.code + " mismatch");
+              is(e.metaKey, !!(aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey),
+                 name + ", Command of " + e.type + " of " + e.code + " mismatch");
+              var isControlPressed = !!(aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey);
+              var isAltPressed = !!(aEvent.modifiers.altKey || aEvent.modifiers.altRightKey);
+              var isAltGraphExpected =
+                !!aEvent.modifiers.altGrKey ||
+                (IS_WIN && aEvent.layout.hasAltGrOnWin &&
+                 isControlPressed && isAltPressed &&
+                 (aEvent.isInputtingCharacters || expectedKeyValue == "Dead")) ||
+                (IS_MAC && isAltPressed);
+              var isControlExpected = !(IS_WIN && isAltGraphExpected) && isControlPressed;
+              var isAltExpected = !(IS_WIN && isAltGraphExpected) && isAltPressed;
+              if (e.type == "keypress" && aEvent.isInputtingCharacters) {
+                isControlExpected = false;
+                isAltExpected = false;
+              }
+              is(e.ctrlKey, isControlExpected,
+                 name + ", Ctrl of " + e.type + " of " + e.code + " mismatch");
+              is(e.altKey, isAltExpected,
+                 name + ", Alt of " + e.type + " of " + e.code + " mismatch");
+              is(e.getModifierState("AltGraph"), isAltGraphExpected,
+                 name + ", AltGraph of " + e.type + " of " + e.code + " mismatch");
+              break;
+          }
+
           is(e.key, expectedKeyValue, name + ", wrong key value");
           is(e.code, aExpectedCodeValue, name + ", wrong code value");
 
           if (aExpectGeckoChar.length > 0 && e.type == "keypress") {
             is(e.charCode, aExpectGeckoChar.charCodeAt(keypressCount++), name + ", charcode");
             if (aExpectedGeckoKeyCode >= 0) {
               if (aExpectGeckoChar) {
                 is(e.keyCode, 0, name + ", wrong keycode");
@@ -4324,16 +4368,30 @@ function* runKeyEventTests()
                   "^", "Digit9", KeyboardEvent.DOM_VK_9, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
                    modifiers:{altGrKey:1}, chars:"]"},
                   "]", "Minus", KeyboardEvent.DOM_VK_CLOSE_PAREN, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_PLUS,
                    modifiers:{altGrKey:1}, chars:"}"},
                   "}", "Equal", KeyboardEvent.DOM_VK_EQUALS, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
+    // AltGr emulated with Ctrl and Alt
+    yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+                   modifiers:{ctrlKey:1, altKey:1}, chars:"@", isInputtingCharacters:true},
+                  "@", "Digit0", KeyboardEvent.DOM_VK_0, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
+                   modifiers:{ctrlKey:1, altKey:1, shiftKey:1}, chars:"", isInputtingCharacters:false},
+                  "0", "Digit0", KeyboardEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+                   modifiers:{ctrlKey:1, altKey:1}, chars:"", isInputtingCharacters:false},
+                  "&", "Digit1", KeyboardEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+                   modifiers:{ctrlKey:1, altKey:1, shiftKey:1}, chars:"", isInputtingCharacters:false},
+                  "1", "Digit1", KeyboardEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
     // German
     yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_OEM_2,
                    modifiers:{}, chars:"#"},
                   "#", "Backslash", KeyboardEvent.DOM_VK_HASH, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_OEM_2,
                    modifiers:{shiftKey:1}, chars:"'"},
                   "'", "Backslash", KeyboardEvent.DOM_VK_HASH, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
@@ -4560,16 +4618,30 @@ function* runKeyEventTests()
 
     yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_OEM_7,
                    modifiers:{shiftKey:1}, chars:""},
                   "Dead", "Quote", KeyboardEvent.DOM_VK_QUOTE, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_SPANISH, keyCode:WIN_VK_Q,
                    modifiers:{shiftKey:1}, chars:"\u00A8Q"},
                   ["\u00A8Q", "\u00A8", "Q", "Q"], "KeyQ", KeyboardEvent.DOM_VK_Q, "\u00A8Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
+    yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_1,
+                   modifiers:{altGrKey:1}, chars:""},
+                  "Dead", "BracketRight", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_A,
+                   modifiers:{}, chars:"\u00E3"},
+                  ["\u00E3", "\u00E3", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+    yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_OEM_1,
+                   modifiers:{ctrlKey:1, altKey:1}, chars:""},
+                  "Dead", "BracketRight", KeyboardEvent.DOM_VK_CIRCUMFLEX, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_NORWEGIAN, keyCode:WIN_VK_A,
+                   modifiers:{}, chars:"\u00E3"},
+                  ["\u00E3", "\u00E3", "a"], "KeyA", KeyboardEvent.DOM_VK_A, "\u00E3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
     if (OS_VERSION >= WIN8) {
       // On Russian Mnemonic layout, both 'KeyS' and 'KeyC' are dead key.  However, the sequence 'KeyS' -> 'KeyC' causes a composite character.
       yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
                      modifiers:{}, chars:""},
                     "Dead", "KeyS", KeyboardEvent.DOM_VK_S, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
       yield testKey({layout:KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC, keyCode:WIN_VK_S,
                      modifiers:{}, chars:"\u0441\u0441"},
                     ["\u0441\u0441", "\u0441", "\u0441", "\u0441"], "KeyS", KeyboardEvent.DOM_VK_S, "\u0441\u0441", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
--- a/widget/windows/KeyboardLayout.cpp
+++ b/widget/windows/KeyboardLayout.cpp
@@ -1521,16 +1521,30 @@ NativeKey::InitWithKeyOrChar()
          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
@@ -1558,16 +1572,19 @@ NativeKey::InitCommittedCharsAndModifier
 {
   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;
     }
@@ -1692,16 +1709,22 @@ NativeKey::InitWithAppCommand()
   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;
 }
--- a/widget/windows/KeyboardLayout.h
+++ b/widget/windows/KeyboardLayout.h
@@ -106,16 +106,21 @@ public:
     return mModifiers[aIndex];
   }
   size_t Length() const
   {
     MOZ_ASSERT(mChars.Length() == mModifiers.Length());
     return mChars.Length();
   }
 
+  bool IsProducingCharsWithAltGr() const
+  {
+    return !IsEmpty() && (ModifiersAt(0) & MODIFIER_ALTGRAPH) != 0;
+  }
+
   void FillModifiers(Modifiers aModifiers);
   /**
    * OverwriteModifiersIfBeginsWith() assigns mModifiers with aOther between
    * [0] and [aOther.mLength - 1] only when mChars begins with aOther.mChars.
    */
   void OverwriteModifiersIfBeginsWith(const UniCharsAndModifiers& aOther);
 
   bool UniCharsEqual(const UniCharsAndModifiers& aOther) const;
@@ -475,16 +480,17 @@ public:
   /**
    * Returns true if aChar is a control character which shouldn't be inputted
    * into focused text editor.
    */
   static bool IsControlChar(char16_t aChar);
 
   bool IsControl() const { return mModKeyState.IsControl(); }
   bool IsAlt() const { return mModKeyState.IsAlt(); }
+  bool MaybeEmulatingAltGraph() const;
   Modifiers GetModifiers() const { return mModKeyState.GetModifiers(); }
   const ModifierKeyState& ModifierKeyStateRef() const
   {
     return mModKeyState;
   }
   VirtualKey::ShiftState GetShiftState() const
   {
     return VirtualKey::ModifierKeyStateToShiftState(mModKeyState);