Skip to content

fix(osr): give the page e.key back — a zero character pair is FlagsChanged - #32

Merged
wenkaifan0720 merged 1 commit into
mainfrom
fix/osr-dom-key
Aug 5, 2026
Merged

fix(osr): give the page e.key back — a zero character pair is FlagsChanged#32
wenkaifan0720 merged 1 commit into
mainfrom
fix/osr-dom-key

Conversation

@wenkaifan0720

Copy link
Copy Markdown
Collaborator

Every printable keydown reached the page as e.key === "Unidentified", so no
page-level keyboard shortcut worked in an OSR webview. e.code was correct
throughout, which made it look like a partial keyboard rather than a broken one.

Cause

Not a missing dom_key — CEF never sets dom_key on macOS at all.
TranslateWebKeyEvent builds a synthetic NSEvent and lets Chromium's
NativeWebKeyboardEvent derive it. But its first branch is:

if (key_event.character == 0 && key_event.unmodified_character == 0)
  event_type = NSEventTypeFlagsChanged;

and DomKeyFromNSEvent answers a FlagsChanged event with DomKeyFromKeyCode(),
which maps only the modifier keys:

case NSEventTypeFlagsChanged:
  return DomKeyFromKeyCode(event->keyCode);

We sent 0 for every printable key — kCefMacKeyChars held editing/navigation
keys only — so each one was delivered as a modifier-state change carrying no
modifier. DomCodeFromNSEvent reads keyCode whatever the event type, which is
exactly why e.code survived and hid the shape of the bug.

Fix

cefMacCharForKey falls back to the key's own printable codepoint.
LogicalKeyboardKey.keyId is the lowercase Unicode value and follows the active
layout, so it is the character macOS would report. The call site prefers the
typed character when there is one, so a shifted key reports A like a browser
rather than the unshifted a. Function-row keys get their NSEvent private-use
codepoints for the same reason.

Modifier keys stay 0 — FlagsChanged is the correct event for them, and it is
the one case DomKeyFromKeyCode does map.

Dart-side only: main.mm already forwards character into both CefKeyEvent
character fields, so no protocol change and no cef_host rebuild.

Verification

Built the example app against a probe page with no JS shim. Confirmed live:
plain letters, Shift+letter, function keys, arrows, and ⌘ combos all report a
real e.key.

⌘A and ⌘F still do not reach the page, and that is unrelated to this bug — the
accelerator block claims ⌘C ⌘X ⌘V ⌘A ⌘Z ⌘⇧Z ⌘= ⌘- ⌘0 and returns handled
before sendKey, plus ⌘F when the host wires onFind. ⌘J confirms the ⌘ path
itself is fixed.

Full analyze clean; 173 tests pass. One existing test asserted
printable keys carry no override char (-> 0; they ride the IME) — it encoded
the bug, so it is replaced by its inverse plus a modifier-keys-stay-zero guard.

Known follow-ups (not in this PR)

  • unmodified_character is a copy of character, so it is wrong for Shift and
    Option and for non-Latin layouts; is_system_key is never sent at all.
    Carrying both properly needs two new wire fields and a host rebuild.
  • A browser gives the page first refusal on shortcuts — preventDefault() on
    ⌘A should suppress select-all. The nine accelerators above are currently
    unconditional. CEF supports the browser behaviour via OnPreKeyEvent
    marking the combo is_keyboard_shortcut.

…anged

Every printable keydown reached the page as `e.key === "Unidentified"`, so no
page-level keyboard shortcut worked in an OSR webview (⌘F, ⌘K, j/k navigation).
`e.code` was correct throughout, which made it look like a partial keyboard
rather than a broken one.

The cause is not a missing `dom_key`. CEF never sets `dom_key` on macOS at all —
`TranslateWebKeyEvent` builds a synthetic NSEvent and lets Chromium's
`NativeWebKeyboardEvent` derive it. But its first branch is:

    if (key_event.character == 0 && key_event.unmodified_character == 0)
      event_type = NSEventTypeFlagsChanged;

and `DomKeyFromNSEvent` answers a FlagsChanged event with
`DomKeyFromKeyCode()`, which maps only the modifier keys. We sent 0 for every
printable key — `kCefMacKeyChars` held editing/navigation keys only — so each
one was delivered as a modifier-state change carrying no modifier.
`DomCodeFromNSEvent` reads `keyCode` whatever the event type, which is exactly
why `e.code` survived and hid the shape of the bug.

So `cefMacCharForKey` now falls back to the key's own printable codepoint
(`LogicalKeyboardKey.keyId` is the lowercase Unicode value and follows the
active layout, so it is the character macOS would report), and the call site
prefers the typed character when there is one — a shifted key reports `A` like
a browser rather than the unshifted `a`. The function-row keys get their
NSEvent private-use codepoints for the same reason. Modifier keys stay 0:
FlagsChanged is the correct event for them, and it is the one case
`DomKeyFromKeyCode` does map.

Dart-side only — `main.mm` already forwards `character` into both CefKeyEvent
character fields, so no protocol change and no cef_host rebuild.

Follow-up, not needed for this fix: `unmodified_character` is currently a copy
of `character`, and `is_system_key` is never sent at all. Carrying both
properly needs two new wire fields and a host rebuild.
@wenkaifan0720
wenkaifan0720 merged commit 7957d93 into main Aug 5, 2026
2 checks passed
@wenkaifan0720
wenkaifan0720 deleted the fix/osr-dom-key branch August 5, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant