From 30fa4b4d1b9a7650b3d85d8c209c52bdfc84f6af Mon Sep 17 00:00:00 2001 From: jdecroock Date: Sun, 3 Sep 2023 10:28:42 +0200 Subject: [PATCH 1/3] add timer to event handler so we can check whether it was attached during the current propagation --- src/diff/props.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/diff/props.js b/src/diff/props.js index db2f1f821d..1a6ed6a7b9 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -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); @@ -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; @@ -151,7 +155,18 @@ 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) { + e._dispatched = Date.now(); + } else if (e._dispatched <= eventHandler._attached) { + return; + } + return eventHandler(options.event ? options.event(e) : e); } function eventProxyCapture(e) { From 3773747ce14162cdd160a31671830c00e66608ad Mon Sep 17 00:00:00 2001 From: jdecroock Date: Sun, 3 Sep 2023 13:19:08 +0200 Subject: [PATCH 2/3] improve performance by only applying _dispatched on a bubbling event --- src/diff/props.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diff/props.js b/src/diff/props.js index 1a6ed6a7b9..ecfcff8a4f 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -161,9 +161,9 @@ function eventProxy(e) { * 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) { + if (!e._dispatched && e.bubbles) { e._dispatched = Date.now(); - } else if (e._dispatched <= eventHandler._attached) { + } else if (e._dispatched && e._dispatched <= eventHandler._attached) { return; } return eventHandler(options.event ? options.event(e) : e); From 576b888593fb6d69ef2b0b75495cdd2c37fa452d Mon Sep 17 00:00:00 2001 From: jdecroock Date: Sun, 3 Sep 2023 13:25:16 +0200 Subject: [PATCH 3/3] remove bubbles --- src/diff/props.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/diff/props.js b/src/diff/props.js index ecfcff8a4f..0ff5b2d26a 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -161,9 +161,13 @@ function eventProxy(e) { * 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 && e.bubbles) { + 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(); - } else if (e._dispatched && e._dispatched <= eventHandler._attached) { + // 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);