-
Notifications
You must be signed in to change notification settings - Fork 0
perf: Pre-register event listeners to avoid DOM scanning #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -451,6 +451,36 @@ const { setModuleImports, getAssemblyExports, getConfig, runMain } = await dotne | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const registeredEvents = new Set(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Pre-register all common event types at startup to avoid O(n) DOM scanning | ||||||||||||||||||||||||||||
| // on every incremental update. These match the event types defined in Operations.cs. | ||||||||||||||||||||||||||||
| const COMMON_EVENT_TYPES = [ | ||||||||||||||||||||||||||||
| // Mouse events | ||||||||||||||||||||||||||||
| 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', | ||||||||||||||||||||||||||||
| 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu', 'wheel', | ||||||||||||||||||||||||||||
| // Keyboard events | ||||||||||||||||||||||||||||
| 'keydown', 'keyup', 'keypress', | ||||||||||||||||||||||||||||
| // Form events | ||||||||||||||||||||||||||||
| 'input', 'change', 'submit', 'reset', 'focus', 'blur', 'invalid', 'search', | ||||||||||||||||||||||||||||
| // Touch events | ||||||||||||||||||||||||||||
| 'touchstart', 'touchend', 'touchmove', 'touchcancel', | ||||||||||||||||||||||||||||
| // Pointer events | ||||||||||||||||||||||||||||
| 'pointerdown', 'pointerup', 'pointermove', 'pointercancel', | ||||||||||||||||||||||||||||
| 'pointerover', 'pointerout', 'pointerenter', 'pointerleave', | ||||||||||||||||||||||||||||
| 'gotpointercapture', 'lostpointercapture', | ||||||||||||||||||||||||||||
| // Drag events | ||||||||||||||||||||||||||||
| 'drag', 'dragstart', 'dragend', 'dragenter', 'dragleave', 'dragover', 'drop', | ||||||||||||||||||||||||||||
| // Clipboard events | ||||||||||||||||||||||||||||
| 'copy', 'cut', 'paste', | ||||||||||||||||||||||||||||
| // Media events | ||||||||||||||||||||||||||||
| 'play', 'pause', 'ended', 'volumechange', 'timeupdate', 'seeking', 'seeked', | ||||||||||||||||||||||||||||
| 'loadeddata', 'loadedmetadata', 'canplay', 'canplaythrough', 'playing', | ||||||||||||||||||||||||||||
| 'waiting', 'stalled', 'suspend', 'emptied', 'ratechange', 'durationchange', | ||||||||||||||||||||||||||||
| // Other events | ||||||||||||||||||||||||||||
| 'scroll', 'resize', 'load', 'error', 'abort', 'select', 'toggle', | ||||||||||||||||||||||||||||
| 'animationstart', 'animationend', 'animationiteration', 'animationcancel', | ||||||||||||||||||||||||||||
| 'transitionstart', 'transitionend', 'transitionrun', 'transitioncancel' | ||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function ensureEventListener(eventName) { | ||||||||||||||||||||||||||||
| if (registeredEvents.has(eventName)) return; | ||||||||||||||||||||||||||||
| // Attach to document to survive body innerHTML changes and use capture for early handling | ||||||||||||||||||||||||||||
|
|
@@ -459,6 +489,10 @@ function ensureEventListener(eventName) { | |||||||||||||||||||||||||||
| registeredEvents.add(eventName); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Pre-register all common event types once at module load | ||||||||||||||||||||||||||||
| // This eliminates the need for O(n) DOM scanning on every update | ||||||||||||||||||||||||||||
| COMMON_EVENT_TYPES.forEach(ensureEventListener); | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| // Pre-register all common event types once at module load | |
| // This eliminates the need for O(n) DOM scanning on every update | |
| COMMON_EVENT_TYPES.forEach(ensureEventListener); | |
| // Pre-register all common event types after initial document load instead of | |
| // during module evaluation. This avoids invoking genericEventHandler while | |
| // the runtime exports may still be in the temporal dead zone. | |
| if (document.readyState === 'loading') { | |
| window.addEventListener('DOMContentLoaded', () => { | |
| COMMON_EVENT_TYPES.forEach(ensureEventListener); | |
| }, { once: true }); | |
| } else { | |
| COMMON_EVENT_TYPES.forEach(ensureEventListener); | |
| } |
Outdated
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addEventListeners(root) now returns early for any subtree root (root && root !== document). However, AddChild/ReplaceChild patches insert raw HTML fragments and then call addEventListeners(childElement/newNode) specifically to discover data-event-* attributes in the inserted subtree. With the early return, any non-pre-registered (custom/rare) event types in newly added HTML will never be registered, breaking event dispatch. Either keep scanning for subtree roots (TreeWalker already makes it cheaper), or ensure the AddChild/ReplaceChild patch path registers event types without relying on a DOM scan.
| // Skip scanning entirely during incremental updates since: | |
| // 1. Common events are already registered | |
| // 2. Attribute patches handle event listener registration | |
| if (root && root !== document) { | |
| // For incremental updates (AddChild, ReplaceChild), skip expensive DOM scanning | |
| // The pre-registered common events cover 99%+ of use cases | |
| return; | |
| } | |
| // Full page render (setAppContent) - scan once for any custom event types | |
| // This only happens on initial load or full page navigation | |
| // Full page render (setAppContent) or subtree scan (AddChild/ReplaceChild) - | |
| // scan for any custom event types in the given scope. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
COMMON_EVENT_TYPESpre-registers high-frequency events likemousemove,pointermove,scroll, andwheel. BecausegenericEventHandleralways callsevent.composedPath()and walks the path even when nodata-event-*attribute exists, this adds steady-state overhead for every one of those events in every app (even if the app never uses them). Consider limiting pre-registration to a small set of low-frequency “always used” events (e.g., click/input/change/submit/keydown/keyup) and keep lazy registration (via attribute patches or subtree scan) for the rest.