Skip to content
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

Fix event bubbling can break in test conditions (#4161) #4232

Closed
wants to merge 4 commits into from

Conversation

nbalatsk-oracle
Copy link

This should fix #4161.

The idea is to use a global counter that will increment on event dispatch only, so it’s less likely that the globalCounter will overflow and the order of _attached/_dispatched is going to be guaranteed.

Copy link

github-actions bot commented Dec 15, 2023

📊 Tachometer Benchmark Results

Summary

duration

  • 02_replace1k: unsure 🔍 -1% - +1% (-0.97ms - +1.08ms)
    preact-local vs preact-main
  • 03_update10th1k_x16: unsure 🔍 -4% - +3% (-1.31ms - +1.21ms)
    preact-local vs preact-main
  • 07_create10k: unsure 🔍 -1% - +1% (-5.12ms - +4.43ms)
    preact-local vs preact-main
  • filter_list: unsure 🔍 -3% - +8% (-0.51ms - +1.29ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -4% - +1% (-2.92ms - +0.94ms)
    preact-local vs preact-main
  • many_updates: unsure 🔍 -1% - +3% (-0.15ms - +0.43ms)
    preact-local vs preact-main
  • text_update: unsure 🔍 -5% - +4% (-0.12ms - +0.10ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -1% - +1% (-0.18ms - +0.24ms)
    preact-local vs preact-main

usedJSHeapSize

  • 02_replace1k: faster ✔ 0% - 1% (0.01ms - 0.02ms)
    preact-local vs preact-main
  • 03_update10th1k_x16: faster ✔ 0% - 1% (0.01ms - 0.03ms)
    preact-local vs preact-main
  • 07_create10k: faster ✔ 1% - 1% (0.24ms - 0.24ms)
    preact-local vs preact-main
  • filter_list: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • hydrate1k: faster ✔ 1% - 1% (0.03ms - 0.05ms)
    preact-local vs preact-main
  • many_updates: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • text_update: unsure 🔍 -1% - +5% (-0.01ms - +0.04ms)
    preact-local vs preact-main
  • todo: unsure 🔍 +0% - +0% (+0.00ms - +0.00ms)
    preact-local vs preact-main

Results

02_replace1k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main72.60ms - 73.82ms-unsure 🔍
-1% - +1%
-1.08ms - +0.97ms
faster ✔
1% - 3%
0.44ms - 2.03ms
preact-local72.45ms - 74.09msunsure 🔍
-1% - +1%
-0.97ms - +1.08ms
-faster ✔
0% - 3%
0.22ms - 2.14ms
preact-hooks73.94ms - 74.95msslower ❌
1% - 3%
0.44ms - 2.03ms
slower ❌
0% - 3%
0.22ms - 2.14ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main3.40ms - 3.40ms-slower ❌
0% - 1%
0.01ms - 0.02ms
unsure 🔍
-0% - -0%
-0.00ms - -0.00ms
preact-local3.38ms - 3.39msfaster ✔
0% - 1%
0.01ms - 0.02ms
-faster ✔
0% - 1%
0.02ms - 0.02ms
preact-hooks3.41ms - 3.41msunsure 🔍
+0% - +0%
+0.00ms - +0.00ms
slower ❌
0% - 1%
0.02ms - 0.02ms
-

run-warmup-0

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main29.44ms - 30.26ms-unsure 🔍
-1% - +3%
-0.35ms - +0.74ms
unsure 🔍
-4% - +0%
-1.29ms - +0.09ms
preact-local29.30ms - 30.02msunsure 🔍
-2% - +1%
-0.74ms - +0.35ms
-faster ✔
0% - 5%
0.13ms - 1.45ms
preact-hooks29.89ms - 31.00msunsure 🔍
-0% - +4%
-0.09ms - +1.29ms
slower ❌
0% - 5%
0.13ms - 1.45ms
-

run-warmup-1

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main39.93ms - 41.41ms-unsure 🔍
-4% - +1%
-1.52ms - +0.52ms
faster ✔
1% - 5%
0.31ms - 2.12ms
preact-local40.48ms - 41.87msunsure 🔍
-1% - +4%
-0.52ms - +1.52ms
-unsure 🔍
-4% - +0%
-1.58ms - +0.16ms
preact-hooks41.36ms - 42.40msslower ❌
1% - 5%
0.31ms - 2.12ms
unsure 🔍
-0% - +4%
-0.16ms - +1.58ms
-

run-warmup-2

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main26.37ms - 27.00ms-unsure 🔍
-1% - +3%
-0.21ms - +0.73ms
slower ❌
0% - 3%
0.13ms - 0.84ms
preact-local26.08ms - 26.77msunsure 🔍
-3% - +1%
-0.73ms - +0.21ms
-unsure 🔍
-1% - +2%
-0.17ms - +0.61ms
preact-hooks26.03ms - 26.37msfaster ✔
0% - 3%
0.13ms - 0.84ms
unsure 🔍
-2% - +1%
-0.61ms - +0.17ms
-

run-warmup-3

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main24.58ms - 25.72ms-unsure 🔍
-3% - +3%
-0.67ms - +0.85ms
unsure 🔍
-1% - +6%
-0.19ms - +1.45ms
preact-local24.55ms - 25.57msunsure 🔍
-3% - +3%
-0.85ms - +0.67ms
-unsure 🔍
-1% - +5%
-0.24ms - +1.33ms
preact-hooks23.92ms - 25.11msunsure 🔍
-6% - +1%
-1.45ms - +0.19ms
unsure 🔍
-5% - +1%
-1.33ms - +0.24ms
-

run-warmup-4

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main26.73ms - 28.32ms-slower ❌
7% - 15%
1.78ms - 3.62ms
unsure 🔍
-2% - +5%
-0.41ms - +1.34ms
preact-local24.35ms - 25.29msfaster ✔
7% - 13%
1.78ms - 3.62ms
-faster ✔
6% - 10%
1.64ms - 2.83ms
preact-hooks26.69ms - 27.43msunsure 🔍
-5% - +1%
-1.34ms - +0.41ms
slower ❌
6% - 12%
1.64ms - 2.83ms
-

run-final

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main22.66ms - 23.32ms-slower ❌
1% - 5%
0.28ms - 1.18ms
faster ✔
3% - 7%
0.66ms - 1.76ms
preact-local21.95ms - 22.57msfaster ✔
1% - 5%
0.28ms - 1.18ms
-faster ✔
6% - 10%
1.40ms - 2.48ms
preact-hooks23.76ms - 24.64msslower ❌
3% - 8%
0.66ms - 1.76ms
slower ❌
6% - 11%
1.40ms - 2.48ms
-
03_update10th1k_x16

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main34.59ms - 36.36ms-unsure 🔍
-3% - +4%
-1.21ms - +1.31ms
unsure 🔍
-4% - +3%
-1.49ms - +1.13ms
preact-local34.53ms - 36.32msunsure 🔍
-4% - +3%
-1.31ms - +1.21ms
-unsure 🔍
-4% - +3%
-1.54ms - +1.09ms
preact-hooks34.69ms - 36.62msunsure 🔍
-3% - +4%
-1.13ms - +1.49ms
unsure 🔍
-3% - +4%
-1.09ms - +1.54ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main3.37ms - 3.38ms-slower ❌
0% - 1%
0.01ms - 0.03ms
unsure 🔍
-0% - +0%
-0.01ms - +0.01ms
preact-local3.35ms - 3.36msfaster ✔
0% - 1%
0.01ms - 0.03ms
-faster ✔
0% - 1%
0.01ms - 0.03ms
preact-hooks3.37ms - 3.38msunsure 🔍
-0% - +0%
-0.01ms - +0.01ms
slower ❌
0% - 1%
0.01ms - 0.03ms
-
07_create10k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main883.22ms - 888.11ms-unsure 🔍
-1% - +1%
-4.43ms - +5.12ms
unsure 🔍
-1% - +0%
-9.01ms - +2.22ms
preact-local881.22ms - 889.42msunsure 🔍
-1% - +1%
-5.12ms - +4.43ms
-unsure 🔍
-1% - +0%
-10.25ms - +2.76ms
preact-hooks884.01ms - 894.12msunsure 🔍
-0% - +1%
-2.22ms - +9.01ms
unsure 🔍
-0% - +1%
-2.76ms - +10.25ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main26.40ms - 26.40ms-slower ❌
1% - 1%
0.24ms - 0.24ms
slower ❌
1% - 1%
0.22ms - 0.22ms
preact-local26.16ms - 26.17msfaster ✔
1% - 1%
0.24ms - 0.24ms
-unsure 🔍
-0% - -0%
-0.02ms - -0.02ms
preact-hooks26.19ms - 26.19msfaster ✔
1% - 1%
0.22ms - 0.22ms
unsure 🔍
+0% - +0%
+0.02ms - +0.02ms
-
filter_list

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main16.58ms - 16.78ms-unsure 🔍
-7% - +3%
-1.29ms - +0.51ms
unsure 🔍
-1% - +1%
-0.12ms - +0.16ms
preact-local16.17ms - 17.96msunsure 🔍
-3% - +8%
-0.51ms - +1.29ms
-unsure 🔍
-3% - +8%
-0.49ms - +1.30ms
preact-hooks16.57ms - 16.76msunsure 🔍
-1% - +1%
-0.16ms - +0.12ms
unsure 🔍
-8% - +3%
-1.30ms - +0.49ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main1.42ms - 1.42ms-unsure 🔍
-0% - +0%
-0.00ms - +0.00ms
faster ✔
2% - 2%
0.02ms - 0.03ms
preact-local1.42ms - 1.42msunsure 🔍
-0% - +0%
-0.00ms - +0.00ms
-faster ✔
2% - 2%
0.02ms - 0.03ms
preact-hooks1.44ms - 1.45msslower ❌
2% - 2%
0.02ms - 0.03ms
slower ❌
2% - 2%
0.02ms - 0.03ms
-
hydrate1k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main79.60ms - 82.16ms-unsure 🔍
-1% - +4%
-0.94ms - +2.92ms
unsure 🔍
-1% - +4%
-0.44ms - +3.26ms
preact-local78.44ms - 81.34msunsure 🔍
-4% - +1%
-2.92ms - +0.94ms
-unsure 🔍
-2% - +3%
-1.55ms - +2.39ms
preact-hooks78.14ms - 80.81msunsure 🔍
-4% - +1%
-3.26ms - +0.44ms
unsure 🔍
-3% - +2%
-2.39ms - +1.55ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main6.10ms - 6.10ms-slower ❌
1% - 1%
0.03ms - 0.05ms
unsure 🔍
+0% - +0%
+0.01ms - +0.03ms
preact-local6.05ms - 6.07msfaster ✔
1% - 1%
0.03ms - 0.05ms
-faster ✔
0% - 1%
0.01ms - 0.03ms
preact-hooks6.08ms - 6.09msunsure 🔍
-0% - -0%
-0.03ms - -0.01ms
slower ❌
0% - 1%
0.01ms - 0.03ms
-
many_updates

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main16.55ms - 16.60ms-unsure 🔍
-3% - +1%
-0.43ms - +0.15ms
unsure 🔍
-0% - +0%
-0.02ms - +0.04ms
preact-local16.43ms - 17.00msunsure 🔍
-1% - +3%
-0.15ms - +0.43ms
-unsure 🔍
-1% - +3%
-0.14ms - +0.44ms
preact-hooks16.55ms - 16.59msunsure 🔍
-0% - +0%
-0.04ms - +0.02ms
unsure 🔍
-3% - +1%
-0.44ms - +0.14ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main4.52ms - 4.52ms-unsure 🔍
-0% - +0%
-0.00ms - +0.00ms
faster ✔
0% - 1%
0.02ms - 0.02ms
preact-local4.52ms - 4.52msunsure 🔍
-0% - +0%
-0.00ms - +0.00ms
-faster ✔
0% - 1%
0.02ms - 0.02ms
preact-hooks4.54ms - 4.54msslower ❌
0% - 1%
0.02ms - 0.02ms
slower ❌
0% - 1%
0.02ms - 0.02ms
-
text_update

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main2.42ms - 2.57ms-unsure 🔍
-4% - +5%
-0.10ms - +0.12ms
faster ✔
5% - 16%
0.13ms - 0.45ms
preact-local2.41ms - 2.57msunsure 🔍
-5% - +4%
-0.12ms - +0.10ms
-faster ✔
5% - 16%
0.14ms - 0.46ms
preact-hooks2.65ms - 2.93msslower ❌
5% - 18%
0.13ms - 0.45ms
slower ❌
5% - 19%
0.14ms - 0.46ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main0.80ms - 0.80ms-unsure 🔍
-5% - +1%
-0.04ms - +0.01ms
faster ✔
2% - 3%
0.02ms - 0.03ms
preact-local0.79ms - 0.84msunsure 🔍
-1% - +5%
-0.01ms - +0.04ms
-unsure 🔍
-4% - +2%
-0.04ms - +0.02ms
preact-hooks0.82ms - 0.83msslower ❌
2% - 3%
0.02ms - 0.03ms
unsure 🔍
-2% - +4%
-0.02ms - +0.04ms
-
todo

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main26.86ms - 27.16ms-unsure 🔍
-1% - +1%
-0.24ms - +0.18ms
faster ✔
3% - 5%
0.70ms - 1.29ms
preact-local26.89ms - 27.18msunsure 🔍
-1% - +1%
-0.18ms - +0.24ms
-faster ✔
2% - 4%
0.67ms - 1.26ms
preact-hooks27.75ms - 28.26msslower ❌
3% - 5%
0.70ms - 1.29ms
slower ❌
2% - 5%
0.67ms - 1.26ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main0.88ms - 0.88ms-unsure 🔍
-0% - -0%
-0.00ms - -0.00ms
faster ✔
3% - 3%
0.03ms - 0.03ms
preact-local0.88ms - 0.88msunsure 🔍
+0% - +0%
+0.00ms - +0.00ms
-faster ✔
3% - 3%
0.03ms - 0.03ms
preact-hooks0.91ms - 0.91msslower ❌
3% - 3%
0.03ms - 0.03ms
slower ❌
3% - 3%
0.03ms - 0.03ms
-

tachometer-reporter-action v2 for Benchmarks

@@ -13,6 +13,11 @@ function setStyle(style, key, value) {
}
}

let globalCounter = Number.MIN_SAFE_INTEGER;
const getCounter = increment => {
return increment ? ++globalCounter : globalCounter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's safe to assume that the user has a certain amount of events, if we however memoize this like the example I gave in the issue that would become a lot safer as then the assumption becomes amount of re-renders instead of amount of events.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe force globalCounter to be a 32 bit int using globalCounter | 0? as far as i know javascript VMs, it would be a tad bit faster.

additionally it would at least lead to predictable overflows whereas a regular double would start doing interesting things at around 2^52.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the update. Used the counter technique mentioned in the issue, forced counters to be unsigned int with >>> 0.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do i miss something that makes >>> 0 better than | 0? it's longer and halves the available range...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it is uglier, but if I understand it correctly the shift operator converts a number to an unsigned 32 bit int with the range of [0 to 4294967295]. The bitwise OR converts a number to a signed 32 bit int with the range of [-2147483648 to 2147483647].
When we start the counter with 0 the unsigned int doubles the range. Right?
You can check it with code like this:

const x = 0b1111111111111111111111111111111 >>> 0; // 2^31 -1 as unsigned int32
const y = 0b1111111111111111111111111111111 | 0; // 2^31 -1 as signed int32
console.log(x, y);
console.log(x + 1 >>> 0, y + 1 | 0);

I've read about the signed/unsigned manipulation in this article. I wonder if we want to use those manipulations at all?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i misunderstood what >>> does and unsigned should have exactly the same range size as signed (both are 32 bits in the end...). i definitely would use integers instead of floating point, it just feels a lot better :)

Copy link
Member

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you been able to test this with the issues that were closed by #4126

@nbalatsk-oracle
Copy link
Author

nbalatsk-oracle commented Dec 15, 2023

Yes, I tested issue #3927 that was closed by #4126. Works fine.

@coveralls
Copy link

coveralls commented Dec 29, 2023

Coverage Status

coverage: 99.47% (-0.1%) from 99.601%
when pulling 1749ffd on nbalatsk-oracle:main
into bc7c551 on preactjs:main.

@nbalatsk-oracle
Copy link
Author

Hi guys, I've refreshed my branch with the latest code. Let me know if you have any comments.

Copy link
Member

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing bothering me most about this approach is when it rolls over (rare case) we might have event-handlers registered with MAX_INT_32 and 0 which all of a sudden would incorrectly bail out of a bubbled click 😅 thinking about alternatives like registering event-handlers during the commit phase but not sure as of just yet because commit is still triggered synchronously so the microTick delay between bubbling could make the bug re-appear.

The initial solution we took was to delay state-setting by a tick rather than a microtick which would successfully make it trigger after bubbling completes https://www.jovidecroock.com/blog/browser-timings however that had its own set of issues

We can see this working in the original issue: https://codesandbox.io/p/sandbox/wandering-cdn-4s53z4?file=%2Fsrc%2Fpreact.js%3A726%2C1
Also has still fixed the original issue of the event timings https://codesandbox.io/p/sandbox/amazing-rhodes-xpj62v?file=%2Fsrc%2Findex.jsx%3A10%2C6

WDYT @marvinhagemeister @developit @andrewiggins

@sschultze
Copy link

@JoviDeCroock I have seen that you have gone for the 32-bit integer solution, as opposed to just counting from Number.MIN_SAFE_INTEGER upwards.

With 32-bit integers, there is a slight chance that it rolls over if many events are fired and if you have a system that runs for a long time (kiosk systems?). If I understand the code correctly, every event attached before MaxInt32 wouldn't be fired after rolling over.

Counting upwards from Number.MIN_SAFE_INTEGER however, you would practically never reach Number.MAX_SAFE_INTEGER. Even with 10,000 events a second, this would take 57,123 years.

@JoviDeCroock
Copy link
Member

@sschultze this isn't my PR just my comment 😅

@sschultze
Copy link

@JoviDeCroock Sorry 😅

@sschultze
Copy link

sschultze commented Jan 29, 2024

@nbalatsk-oracle @JoviDeCroock What about using BigInt for the counter if the browser supports it? BigInts have an arbitrary size and will never overflow, and also no strange things happen if they get very large, giving some peace of mind.

The performance cost should be negligible. On my computer, 10 million increments of a BigInt take 151ms.

Initialization for the counter could go like this:

let counter = window.BigInt ? BigInt(0) : 0;

The ++ operand and the comparison would work fine on both number and bigint, so no need for another case distinction.

@nbalatsk-oracle
Copy link
Author

@ritschwumm @JoviDeCroock do we want to use BigInt for the counter as suggested by @sschultze? Do you any concerns for performance?

@ritschwumm
Copy link

@nbalatsk-oracle can't the rollover be made negligible by just comparing for equality with the previous value? then the only problem left is when 2^32 events happen "at the same instant"...

@sschultze
Copy link

Probably another issue, but isn't it problematic that, even in the original code, the _attached property is added to the event handler function? The same event handler might be added to multiple nodes, potentially leading to internal mess-up. Even I re-use event handlers in my code (e.g. for list items differentiated by a data-key attribute), and my UI is not that special.

I also noticed that in the minified version, _attached is shortened to u. Couldn't it happen that there's a conflict between the minified name and the "official" properties of the function?

@jramanat-oracle
Copy link
Contributor

@nbalatsk-oracle can't the rollover be made negligible by just comparing for equality with the previous value? then the only problem left is when 2^32 events happen "at the same instant"...

@ritschwumm I don't think that's how the current proposed code changes are working. The idea is that the counter is incremented when an event listener is newly attached for the first time during a render cycle. All event listeners that are newly attached during that render cycle will share the same counter value; all event listeners attached during previous renders will have lower counter values, all event listeners attached in future renders will have higher counter values. As a result, event listeners attached during the 0th render cycle appear indistinguishable from event listeners attached during the 2^32nd render cycle, hence the "rollover" issue.

@nbalatsk-oracle
Copy link
Author

Probably another issue, but isn't it problematic that, even in the original code, the _attached property is added to the event handler function? The same event handler might be added to multiple nodes, potentially leading to internal mess-up. Even I re-use event handlers in my code (e.g. for list items differentiated by a data-key attribute), and my UI is not that special.

@sschultze Is it really a problem? It seems that it does not really matter that the handler is shared as long as the _attached/_dispatched order is correct. Even if _attached is updated by some DOM mutation (new elements are added that share the same handler), the event will not propagate to those updated elements. What is the use-case where we want it?  And if you trigger an event on one of those older elements the event should work because the order of the _attached/_dispatched pair is still correct. Right?

@sschultze
Copy link

@sschultze Is it really a problem? It seems that it does not really matter that the handler is shared as long as the _attached/_dispatched order is correct. Even if _attached is updated by some DOM mutation (new elements are added that share the same handler), the event will not propagate to those updated elements. What is the use-case where we want it?  And if you trigger an event on one of those older elements the event should work because the order of the _attached/_dispatched pair is still correct. Right?

@nbalatsk-oracle You are right. Even though adding the same event handler in multiple places was probably not thought of when writing the original _attached/_dispatched workaround (or perhaps it was thought of, i don't know), in practice this shouldn't be a problem as the order will still be correct.

I mean, I can't say for sure - I'm not deep enough into the internal workings of Preact to guarantee that there isn't some weird edge case where still would still create a problem.

I did however write the following test code which re-uses the same event handler handleButtonClick and aggressively creates new DOM nodes, just for a quick validation, and it works fine. No click event is ignored.

import { JSX, useCallback, useState } from 'preact/compat';

interface SectionProps {
    readonly onButtonClick: JSX.MouseEventHandler<HTMLButtonElement>;
}

function Section(props: SectionProps) {
    return (
        <div>
            <button onClick={props.onButtonClick}>Add Section</button>
        </div>
    );
}

let domNodeCreationCounter = 0;

export function PreactTest(props: {}) {
    const [count, setCount] = useState(1);

    const handleButtonClick = useCallback(() => {
        setCount(oldCount => oldCount + 1);
    }, []);

    // console.log((handleButtonClick as any).u); - shows ._attached in the minified version that I use

    return (
        <div key={domNodeCreationCounter++}> {/* Should create new DOM nodes every time */}
            {Array(count).fill(0).map((_, index) => (
                <Section key={index} onButtonClick={handleButtonClick} />
            ))}
        </div>
    );
}

Perhaps it would make sense to add a "warning comment" for anyone who updates the code later? Saying that the property is added to the function itself, even though it might be added to multiple nodes, but as you say this still doesn't create a problem as the _attached/_dispatched order is still okay?

@sschultze
Copy link

@nbalatsk-oracle A thought experiment: If it would indeed be 100% safe to share event handlers between nodes, and still always get the correct behavior here, then it would - as a consequence - also be safe to share the same one event handler for everything in the application. Meaning there's really only one event handler function for everything in the application in this theoretical scenario.

In this scenario, the _attached property would always be set in the same function object.

If this would still lead to the correct behavior, than _attached could be a (module-)global variable, and not be attached to the event handler functions in the first place.

What I want to say by this is:

  • either event handlers can really be shared between nodes and still get 100% correct behavior, in which case _attached could also be made a (module-)global variable
  • or neither the original nor the corrected workaround provide a 100% solution.

In any case, especially if we get the overflow problem away (e.g. with the bigint proposal), the new code definitely provides better results that the old code relying on Date. (The Date-based workaround not only makes problems in the test scenario, but also if the users changes their system date/time while the app is running.)

@nbalatsk-oracle
Copy link
Author

Updated the code to use BigInt for the counters to avoid overflow and added a few comments to _attached/_detached usage.

@jramanat-oracle
Copy link
Contributor

@JoviDeCroock @developit @andrewiggins @marvinhagemeister Just curious if you might be able to provide any indication on the likelihood of moving forward with the approach in this PR or if there are other approaches to this problem that you might like to see investigated? Our product is currently blocked from moving forward from preact 10.17.1 due to #4161 so we're very interested in finding some acceptable resolution. Happy to explore other options if there's a preference, thanks!

// It is supported by all major browsers except IE and Opera mini
// where the plain number is used as fallback.
// eslint-disable-next-line no-undef
const zero = typeof window != 'undefined' && window.BigInt ? BigInt(0) : 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const zero = typeof window != 'undefined' && window.BigInt ? BigInt(0) : 0;
const zero = Number.MIN_SAFE_INTEGER;

@@ -120,7 +120,7 @@ declare global {
}

export interface PreactEvent extends Event {
_dispatched?: number;
_dispatched?: number | bigint;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_dispatched?: number | bigint;
_dispatched?: number;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Event bubbling can break in test conditions
6 participants