fix: ensure event context is reset before invoking callback #13737
+69
−38
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #13684 and is a better approach than #13694 #13719.
Essentially, we have a complicated issue that only affects Chromium and they're well aware of it https://chromestatus.com/feature/5128696823545856. If you have an element with
onblur
, the event will fire synchronously if that element is removed from the DOM or if the element or its parent moves in certain cases. This behaviour was discovered because of the improved validation around block effects erroring on state mutations – but has nothing to do with this issue at all, it actually was great that it helped show this huge issue.In the ideal world, event handlers are always without a reactive context – because this is how it works in 99% of cases. The case where it's not true is synchronous events handlers – either because of this strange Chromium bug or because someone does
element.focus()
orelement.click()
. If there is a reactive context, it's likely the wrong one that just happens to be active at the time of dispatch, which can cause very bad bugs:Now, how I thought we could tackle this is to wrap the call-sites where we do the actual DOM mutations, such as removals or moves with the try/finally logic that sets the context. However, I was recently made aware that these changes have caused performance to degrade significantly – our removal logic is now 3.3x slower than before! Try/finally wrapping clearly has overhead. For even more investigation I wrapped the each
move
function logic and saw what the impact would be on reversing an array of 100 items – it was78ms
, compared to9ms
. That's unacceptable.So instead, what if we deal with removing the context when we invoke the event handler through Svelte's event system instead? This has the least overhead and works nicely in that it's predictable and can be applied to all events – which I think is desirable. I don't want an event handler to have a context, even if you call
element.click()
from within another context as it's a nightmare to debug these cases when it comes to event propagation. This also applied to ouron
event listener too.However, that raises the question, what if someone does their own event handling without
on
? Well they'll unfortunately run into the same issues from before, and for that I have no good solution. FWIW this issue affects all the other signals frameworks too – and it causes the same buggy behaviour there in my testing. So it would be good to be the first framework to solve this from Chromium browsers.