Skip to content

fix: skip the shortcut owner token when the owner is the UI (#25631) (CP: 25.2) - #25644

Merged
vaadin-bot merged 1 commit into
25.2from
cherry-pick-25631-to-25.2-1789047567642
Sep 10, 2026
Merged

fix: skip the shortcut owner token when the owner is the UI (#25631) (CP: 25.2)#25644
vaadin-bot merged 1 commit into
25.2from
cherry-pick-25631-to-25.2-1789047567642

Conversation

@vaadin-bot

Copy link
Copy Markdown
Collaborator

This PR cherry-picks changes from the original PR #25631 to branch 25.2.

Original PR description

Summary

A shortcut's owner-scope guard puts a unique id into the keydown filter text, and that text is kept for the whole life of the UI by the client expression cache and both constant pools. A shortcut owned by the UI does not need that id, because the UI element is <body>, which can never be inside an open popover or modal. Such shortcuts now share one filter expression instead of creating a new one per registration.

Fixes #25624

What changed

  • ShortcutRegistration no longer generates the owner token eagerly. getOwnerToken() is now the single place that decides whether a token is needed and creates it, and it returns null when none is needed: the guard is off (allowEventsFromNestedModals) or the lifecycle owner is the UI. Callers just use what they get.
  • For a UI-owned shortcut the filter is now window.Vaadin.Flow.shortcut.eventInTopLevelScope(event), the same text for every registration.
  • FlowShortcut.js gains eventInTopLevelScope(event), which only checks that the keydown did not originate inside a popover or modal. The shared "which scope did the event come from" logic moved into a small _originScope helper used by both guards.
  • Side fix: with the guard off, updateOwnerMarker used to ask for a token before checking the flag, so a registration that never uses one still created it.

Test summary

# Status What the test verifies Why it matters
1 A UI-owned shortcut leaves the UI element unmarked and its filter is eventInTopLevelScope(event), with no owner attribute or token in the text This is the fix; a token here is what leaked per registration
2 Removing and re-registering the same UI shortcut produces exactly the same set of keydown expressions A filter text that differs per registration grows the client expression cache and both constant pools forever
3 A component-owned shortcut still marks its owner and its filter locates that owner by the marker token The normal, non-UI path must keep working unchanged
4 In a real browser, a UI-owned Alt+S stays silent for a keydown inside an open popover and fires once for a keydown on the top layer The new token-free client helper must guard exactly like the token-based one
5 A shortcut owned by a component inside a popover still fires for keydowns from that popover (#24974) Guards against re-breaking the earlier fix while the test view was reworked
  • ShortcutRegistrationTest.ownerScopeGuard_uiLifecycleOwner_filterHasNoTokenAndUiIsNotMarked → 1
  • ShortcutRegistrationTest.ownerScopeGuard_uiLifecycleOwner_repeatedRegistrationsShareFilter → 2
  • ShortcutRegistrationTest.ownerScopeGuard_componentLifecycleOwner_filterLocatesOwnerByToken → 3
  • PopoverOwnerShortcutIT.uiOwnedShortcut_firesOnlyOutsideThePopover → 4 (uses the new UI-owned shortcut, second input and sync button added to PopoverOwnerShortcutView)
  • PopoverOwnerShortcutIT.ownerInsidePopoverFocused_shortcutFires → 5 (pre-existing, now shares the view with the new case)

Deliberately untested: skipping the token when allowEventsFromNestedModals is on has no observable effect (no filter is generated in that case at all), and the existing ownerScopeGuard_lifecycleOwnerNotMarkedWhenAllowed already pins that the owner ends up unmarked. The _originScope extraction in FlowShortcut.js is a pure move, still covered by the existing delegate tests.

## Summary
A shortcut's owner-scope guard puts a unique id into the keydown filter
text, and that text is kept for the whole life of the UI by the client
expression cache and both constant pools. A shortcut owned by the `UI`
does not need that id, because the UI element is `<body>`, which can
never be inside an open popover or modal. Such shortcuts now share one
filter expression instead of creating a new one per registration.

Fixes #25624

## What changed
- `ShortcutRegistration` no longer generates the owner token eagerly.
`getOwnerToken()` is now the single place that decides whether a token
is needed and creates it, and it returns `null` when none is needed: the
guard is off (`allowEventsFromNestedModals`) or the lifecycle owner is
the `UI`. Callers just use what they get.
- For a UI-owned shortcut the filter is now
`window.Vaadin.Flow.shortcut.eventInTopLevelScope(event)`, the same text
for every registration.
- `FlowShortcut.js` gains `eventInTopLevelScope(event)`, which only
checks that the keydown did not originate inside a popover or modal. The
shared "which scope did the event come from" logic moved into a small
`_originScope` helper used by both guards.
- Side fix: with the guard off, `updateOwnerMarker` used to ask for a
token before checking the flag, so a registration that never uses one
still created it.

## Test summary

| # | Status | What the test verifies | Why it matters |
|---|--------|------------------------|----------------|
| 1 | ✅ | A UI-owned shortcut leaves the UI element unmarked and its
filter is `eventInTopLevelScope(event)`, with no owner attribute or
token in the text | This is the fix; a token here is what leaked per
registration |
| 2 | ✅ | Removing and re-registering the same UI shortcut produces
exactly the same set of keydown expressions | A filter text that differs
per registration grows the client expression cache and both constant
pools forever |
| 3 | ✅ | A component-owned shortcut still marks its owner and its
filter locates that owner by the marker token | The normal, non-UI path
must keep working unchanged |
| 4 | ✅ | In a real browser, a UI-owned Alt+S stays silent for a keydown
inside an open popover and fires once for a keydown on the top layer |
The new token-free client helper must guard exactly like the token-based
one |
| 5 | ✅ | A shortcut owned by a component inside a popover still fires
for keydowns from that popover (#24974) | Guards against re-breaking the
earlier fix while the test view was reworked |

-
`ShortcutRegistrationTest.ownerScopeGuard_uiLifecycleOwner_filterHasNoTokenAndUiIsNotMarked`
→ 1
-
`ShortcutRegistrationTest.ownerScopeGuard_uiLifecycleOwner_repeatedRegistrationsShareFilter`
→ 2
-
`ShortcutRegistrationTest.ownerScopeGuard_componentLifecycleOwner_filterLocatesOwnerByToken`
→ 3
- `PopoverOwnerShortcutIT.uiOwnedShortcut_firesOnlyOutsideThePopover` →
4 (uses the new UI-owned shortcut, second input and sync button added to
`PopoverOwnerShortcutView`)
- `PopoverOwnerShortcutIT.ownerInsidePopoverFocused_shortcutFires` → 5
(pre-existing, now shares the view with the new case)

Deliberately untested: skipping the token when
`allowEventsFromNestedModals` is on has no observable effect (no filter
is generated in that case at all), and the existing
`ownerScopeGuard_lifecycleOwnerNotMarkedWhenAllowed` already pins that
the owner ends up unmarked. The `_originScope` extraction in
`FlowShortcut.js` is a pure move, still covered by the existing delegate
tests.

---------

Co-authored-by: totally-not-ai[bot] <290682512+totally-not-ai[bot]@users.noreply.github.com>
@vaadin-bot

Copy link
Copy Markdown
Collaborator Author

This PR is eligible for auto-merging policy, so it has been approved automatically. If there are pending conditions, auto merge (with 'squash' method) has been enabled for this PR [Message is sent from bot]

@vaadin-bot
vaadin-bot enabled auto-merge (squash) September 10, 2026 13:54
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

 1 385 files  ±0   1 385 suites  ±0   1h 41m 42s ⏱️ + 8m 21s
10 416 tests +4  10 348 ✅ +4  68 💤 ±0  0 ❌ ±0 
10 771 runs  +4  10 702 ✅ +4  69 💤 ±0  0 ❌ ±0 

Results for commit 9c0d55d. ± Comparison against base commit 8a68ad4.

@vaadin-bot
vaadin-bot merged commit d45ed41 into 25.2 Sep 10, 2026
34 of 35 checks passed
@vaadin-bot
vaadin-bot deleted the cherry-pick-25631-to-25.2-1789047567642 branch September 10, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants