fix: skip the shortcut owner token when the owner is the UI (#25631) (CP: 25.3) - #25643
Conversation
## 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>
|
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] |
A UI-owned shortcut now emits one shared keydown filter instead of a unique one per registrationflowchart LR
subgraph Before
direction TB
B1["ShortcutRegistration, UI owner"] -->|"ownerToken = sc-uuid, always"| B2["generateOwnerScopeFilter()"]
B2 -->|"emits, new text per registration"| B3["eventInOwnerScope(event, '[...sc-uuid]')"]
end
subgraph After
direction TB
A1["ShortcutRegistration, UI owner"] -->|"getOwnerToken() returns null (new)"| A2["generateOwnerScopeFilter()"]
A2 -->|"emits, same text every registration"| A3["eventInTopLevelScope(event) (new)"]:::changed
end
Before ~~~ After
classDef changed stroke:#c9a227,stroke-width:3px
The figure shows what Diagram Bot draws the mechanism this pull request touches; it does not review the change. Verify it against the diff.
|
|



This PR cherry-picks changes from the original PR #25631 to branch 25.3.
Original PR description