Skip to content

Commit

Permalink
Bug 900750 - part 4: Make NativeKey replaces MODIFIER_CONTROL and MOD…
Browse files Browse the repository at this point in the history
…IFIER_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

--HG--
extra : rebase_source : f4924f075c68361c8ce563910280ea24774c519f
  • Loading branch information
masayuki-nakano committed Jun 4, 2018
1 parent ad475fd commit cccab7b
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 42 deletions.
51 changes: 34 additions & 17 deletions testing/mochitest/tests/SimpleTest/EventUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1068,39 +1068,56 @@ function _parseNativeModifiers(aModifiers, aWindow = window)
// 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.
Expand Down
122 changes: 97 additions & 25 deletions widget/tests/test_keycodes.xul
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ function* runKeyEventTests()
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":
Expand All @@ -236,44 +239,56 @@ function* runKeyEventTests()
// 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;
}
Expand Down Expand Up @@ -377,25 +392,54 @@ function* runKeyEventTests()
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");
Expand Down Expand Up @@ -4329,6 +4373,20 @@ function* runKeyEventTests()
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:"#"},
Expand Down Expand Up @@ -4565,6 +4623,20 @@ function* runKeyEventTests()
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,
Expand Down
23 changes: 23 additions & 0 deletions widget/windows/KeyboardLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,20 @@ NativeKey::InitWithKeyOrChar()

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));
Expand Down Expand Up @@ -1563,6 +1577,9 @@ NativeKey::InitCommittedCharsAndModifiersWithFollowingCharMessages()
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.
Expand Down Expand Up @@ -1697,6 +1714,12 @@ NativeKey::InitWithAppCommand()
}
}

bool
NativeKey::MaybeEmulatingAltGraph() const
{
return IsControl() && IsAlt() && KeyboardLayout::GetInstance()->HasAltGr();
}

// static
bool
NativeKey::IsControlChar(char16_t aChar)
Expand Down
6 changes: 6 additions & 0 deletions widget/windows/KeyboardLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ class MOZ_STACK_CLASS UniCharsAndModifiers final
return mChars.Length();
}

bool IsProducingCharsWithAltGr() const
{
return !IsEmpty() && (ModifiersAt(0) & MODIFIER_ALTGRAPH) != 0;
}

void FillModifiers(Modifiers aModifiers);
/**
* OverwriteModifiersIfBeginsWith() assigns mModifiers with aOther between
Expand Down Expand Up @@ -480,6 +485,7 @@ class MOZ_STACK_CLASS NativeKey final

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
{
Expand Down

0 comments on commit cccab7b

Please sign in to comment.