Skip to content

Commit 0a2ae29

Browse files
committed
fix: use a virtual clock instead of Date.now() for event dispatch times
Fixes issue #4161.
1 parent bc7c551 commit 0a2ae29

File tree

1 file changed

+38
-10
lines changed

1 file changed

+38
-10
lines changed

src/diff/props.js

+38-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@ function setStyle(style, key, value) {
1313
}
1414
}
1515

16+
// A "virtual clock" to solve issues like https://github.com/preactjs/preact/issues/3927.
17+
// When the DOM performs an event it leaves micro-ticks in between bubbling up which means that
18+
// an event can trigger on a newly reated DOM-node while the event bubbles up.
19+
//
20+
// Originally inspired by Vue https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/modules/events.ts#L90-L101,
21+
// but modified to use a virtual clock instead of Date.now() in case event handlers get attached and
22+
// events get dispatched during the same millisecond.
23+
//
24+
// Odd values are reserved for event dispatch times, and even values are reserved for new
25+
// event handler attachment times.
26+
//
27+
// The clock is incremented before a new event is dispatched if the value is even
28+
// (i.e a new event handler was attached after the previous new event).
29+
// The clock is also incremented when a new event handler gets attached if the value is odd
30+
// (i.e. a new event was dispatched after the previous new event dispatch).
31+
let eventClock = 0;
32+
1633
/**
1734
* Set a property value on a DOM node
1835
* @param {PreactElement} dom The DOM node to modify
@@ -68,7 +85,16 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
6885

6986
if (value) {
7087
if (!oldValue) {
71-
value._attached = Date.now();
88+
// If any new events were dispatched between this moment and the last time
89+
// an event handler was attached (i.e. `eventClock` is an odd number),
90+
// then increment `eventClock` first.
91+
//
92+
// The following line is a compacted version of:
93+
// if (eventClock % 2 === 1) {
94+
// eventClock += 1;
95+
// }
96+
// value._attached = eventClock;
97+
value._attached = eventClock += eventClock % 2;
7298
const handler = useCapture ? eventProxyCapture : eventProxy;
7399
dom.addEventListener(name, handler, useCapture);
74100
} else {
@@ -131,18 +157,20 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
131157
function eventProxy(e) {
132158
if (this._listeners) {
133159
const eventHandler = this._listeners[e.type + false];
134-
/**
135-
* This trick is inspired by Vue https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/modules/events.ts#L90-L101
136-
* when the dom performs an event it leaves micro-ticks in between bubbling up which means that an event can trigger on a newly
137-
* created DOM-node while the event bubbles up, this can cause quirky behavior as seen in https://github.com/preactjs/preact/issues/3927
138-
*/
160+
// If e._dispatched is set, it has to be an odd number, so !e._dispatched must be true if set.
139161
if (!e._dispatched) {
140-
// When an event has no _dispatched we know this is the first event-target in the chain
141-
// so we set the initial dispatched time.
142-
e._dispatched = Date.now();
162+
// If any new event handlers were attached after the previous new event dispatch
163+
// (i.e. `eventClock` is an even number), then increment `eventClock` first.
164+
//
165+
// The following line is a compacted version of:
166+
// if (eventClock % 2 === 0) {
167+
// eventClock += 1;
168+
// }
169+
// e._dispatched = eventClock;
170+
e._dispatched = eventClock += (eventClock + 1) % 2;
143171
// When the _dispatched is smaller than the time when the targetted event handler was attached
144172
// we know we have bubbled up to an element that was added during patching the dom.
145-
} else if (e._dispatched <= eventHandler._attached) {
173+
} else if (e._dispatched < eventHandler._attached) {
146174
return;
147175
}
148176
return eventHandler(options.event ? options.event(e) : e);

0 commit comments

Comments
 (0)