Skip to content

Commit

Permalink
fix: add timer to event handler so we can check whether it was attach…
Browse files Browse the repository at this point in the history
…ed during the current propagation (#4126)

* add timer to event handler so we can check whether it was attached during the current propagation

* improve performance by only applying _dispatched on a bubbling event

* remove bubbles
  • Loading branch information
JoviDeCroock committed Sep 3, 2023
1 parent 7a3706a commit 29abdf7
Showing 1 changed file with 21 additions and 2 deletions.
23 changes: 21 additions & 2 deletions src/diff/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
}
// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
else if (name[0] === 'o' && name[1] === 'n') {
useCapture = name !== (name = name.replace(/(PointerCapture)$|Capture$/, '$1'));
useCapture =
name !== (name = name.replace(/(PointerCapture)$|Capture$/, '$1'));

// Infer correct casing for DOM built-in events:
if (name.toLowerCase() in dom) name = name.toLowerCase().slice(2);
Expand All @@ -94,8 +95,11 @@ export function setProperty(dom, name, value, oldValue, isSvg) {

if (value) {
if (!oldValue) {
value._attached = Date.now();
const handler = useCapture ? eventProxyCapture : eventProxy;
dom.addEventListener(name, handler, useCapture);
} else {
value._attached = oldValue._attached;
}
} else {
const handler = useCapture ? eventProxyCapture : eventProxy;
Expand Down Expand Up @@ -151,7 +155,22 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
* @private
*/
function eventProxy(e) {
return this._listeners[e.type + false](options.event ? options.event(e) : e);
const eventHandler = this._listeners[e.type + false];
/**
* This trick is inspired by Vue https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/modules/events.ts#L90-L101
* when the dom performs an event it leaves micro-ticks in between bubbling up which means that an event can trigger on a newly
* created DOM-node while the event bubbles up, this can cause quirky behavior as seen in https://github.com/preactjs/preact/issues/3927
*/
if (!e._dispatched) {
// When an event has no _dispatched we know this is the first event-target in the chain
// so we set the initial dispatched time.
e._dispatched = Date.now();
// When the _dispatched is smaller than the time when the targetted event handler was attached
// we know we have bubbled up to an element that was added during patching the dom.
} else if (e._dispatched <= eventHandler._attached) {
return;
}
return eventHandler(options.event ? options.event(e) : e);
}

function eventProxyCapture(e) {
Expand Down

0 comments on commit 29abdf7

Please sign in to comment.