Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions lib/src/cef_web_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -597,12 +597,20 @@ class _CefWebViewState extends State<CefWebView>
_isWindows ? wkc : (cefMacNativeKeyCode(event.physicalKey) ?? wkc);
final ch = event.character;
final isText = ch != null && _isPrintable(ch);
// Editing / navigation keys MUST carry the macOS NSEvent character or CEF
// OSR double-applies them (one Backspace deletes two, one arrow moves two).
// macOS-only: the table holds NSEvent codepoints (incl. private-use
// 0xF7xx function-key values) that would corrupt a Windows key event —
// Windows CEF derives the character from the VK itself, so send 0 there.
final keyChar = _isWindows ? 0 : cefMacCharForKey(event.logicalKey);
// Every key MUST carry its macOS NSEvent character. Editing/navigation keys
// because CEF OSR otherwise double-applies them (one Backspace deletes two,
// one arrow moves two); printable keys because a zero character pair makes
// CEF build a FlagsChanged event, which costs the page `e.key` entirely —
// see [cefMacCharForKey]. The typed character wins when there is one, so a
// shifted key reports `A` like a browser rather than the unshifted `a`.
// macOS-only: these are NSEvent codepoints (incl. private-use 0xF7xx
// function-key values) that would corrupt a Windows key event — Windows CEF
// derives the character from the VK itself, so send 0 there.
final keyChar = _isWindows
? 0
: (ch != null && isText
? ch.codeUnitAt(0)
: cefMacCharForKey(event.logicalKey));
// The page should see keydown→keypress→keyup for every character, like a
// browser. We always send RAWKEYDOWN/KEYUP; the keypress (CHAR) is
// synthesized by [_commitText] when the IME's insertText delivers a typed
Expand Down
39 changes: 36 additions & 3 deletions packages/flutter_cef_platform_interface/lib/src/cef_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,44 @@ final Map<LogicalKeyboardKey, int> kCefMacKeyChars = <LogicalKeyboardKey, int>{
LogicalKeyboardKey.end: 0xF72B,
LogicalKeyboardKey.pageUp: 0xF72C,
LogicalKeyboardKey.pageDown: 0xF72D,
LogicalKeyboardKey.f1: 0xF704, // NSF1FunctionKey … NSF12FunctionKey
LogicalKeyboardKey.f2: 0xF705,
LogicalKeyboardKey.f3: 0xF706,
LogicalKeyboardKey.f4: 0xF707,
LogicalKeyboardKey.f5: 0xF708,
LogicalKeyboardKey.f6: 0xF709,
LogicalKeyboardKey.f7: 0xF70A,
LogicalKeyboardKey.f8: 0xF70B,
LogicalKeyboardKey.f9: 0xF70C,
LogicalKeyboardKey.f10: 0xF70D,
LogicalKeyboardKey.f11: 0xF70E,
LogicalKeyboardKey.f12: 0xF70F,
};

/// The macOS NSEvent character for an editing/navigation [key], or 0 for keys
/// whose text rides the IME/CHAR path. See [kCefMacKeyChars].
int cefMacCharForKey(LogicalKeyboardKey key) => kCefMacKeyChars[key] ?? 0;
/// The macOS NSEvent character for [key]: the table above, then the key's own
/// printable codepoint.
///
/// A printable key must carry a character too — 0 for *both* `character` and
/// `unmodified_character` does not mean "no character" to CEF, it means a
/// different event. `TranslateWebKeyEvent` treats that pair as
/// `NSEventTypeFlagsChanged`, and Chromium's `DomKeyFromNSEvent` answers a
/// FlagsChanged event with `DomKeyFromKeyCode()`, which maps only the modifier
/// keys — so every printable keydown reached the page as
/// `e.key === 'Unidentified'`, breaking any page-level shortcut. (`e.code`
/// survived because `DomCodeFromNSEvent` reads `keyCode` whatever the type,
/// which is why the symptom looked like a partial keyboard rather than none.)
///
/// [LogicalKeyboardKey.keyId] is the lowercase Unicode value for a character
/// key and follows the active layout, so it is exactly the unmodified
/// character macOS would report.
int cefMacCharForKey(LogicalKeyboardKey key) {
final mapped = kCefMacKeyChars[key];
if (mapped != null) return mapped;
final id = key.keyId;
// Above the Unicode range are Flutter's synthetic plane ids (modifiers, media
// keys); those genuinely have no character and stay 0.
return (id > 0x20 && id < 0x7F) || (id > 0x7F && id <= 0x10FFFF) ? id : 0;
}

/// The Windows virtual-key code for [key]: the special-key table first, then
/// a→VK_A..z→VK_Z, A–Z, and 0–9. 0 if unmapped (a printable that rides CHAR).
Expand Down
30 changes: 27 additions & 3 deletions test/cef_input_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,33 @@ void main() {
expect(cefMacCharForKey(key), isNonZero); // the whole point
});
});
test('printable keys carry no override char (-> 0; they ride the IME)', () {
expect(cefMacCharForKey(LogicalKeyboardKey.keyA), 0);
expect(cefMacCharForKey(LogicalKeyboardKey.digit5), 0);
// A printable key must carry its character too, though it rides the IME for
// text. Zero for BOTH character fields is not "no character" to CEF: it is
// the FlagsChanged branch of TranslateWebKeyEvent, and Chromium answers a
// FlagsChanged event with DomKeyFromKeyCode(), which maps only modifiers —
// so the page saw `e.key === 'Unidentified'` for every printable keydown.
test('printable keys carry their own codepoint (e.key, not Unidentified)',
() {
expect(cefMacCharForKey(LogicalKeyboardKey.keyA), 0x61); // 'a'
expect(cefMacCharForKey(LogicalKeyboardKey.digit5), 0x35); // '5'
expect(cefMacCharForKey(LogicalKeyboardKey.slash), 0x2F); // '/'
});
test('function keys carry their NSEvent private-use codepoint', () {
expect(cefMacCharForKey(LogicalKeyboardKey.f1), 0xF704);
expect(cefMacCharForKey(LogicalKeyboardKey.f12), 0xF70F);
});
// Modifiers are the one case where FlagsChanged is the CORRECT event, and
// DomKeyFromKeyCode does map them — so these must stay 0.
test('modifier keys stay 0 — FlagsChanged is right for them', () {
for (final key in <LogicalKeyboardKey>[
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.capsLock,
]) {
expect(cefMacCharForKey(key), 0, reason: key.debugName);
}
});
});

Expand Down
Loading