Skip to content

Commit f57836c

Browse files
authored
fix: robust handling of events in spread attributes (#11942)
When an event listener is removed right before it would be fired, adding a new listener comes too late for the browser and the event is swallowed. The fix is to tweak the spread attributes function to keep using the same object for updates so that we can wrap the event listener to invoke it like `attributes[key](evt)` instead. No test because it seems impossible to reproduce in testing environments. Fixes #11903
1 parent aadf629 commit f57836c

File tree

2 files changed

+32
-8
lines changed

2 files changed

+32
-8
lines changed

.changeset/cyan-toes-share.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: more robust handling of events in spread attributes

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export function set_custom_element_data(node, prop, value) {
144144
*/
145145
export function set_attributes(element, prev, next, lowercase_attributes, css_hash) {
146146
var has_hash = css_hash.length !== 0;
147+
var current = prev || {};
147148

148149
for (var key in prev) {
149150
if (!(key in next)) {
@@ -167,14 +168,18 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
167168
for (const key in next) {
168169
// let instead of var because referenced in a closure
169170
let value = next[key];
170-
if (value === prev?.[key]) continue;
171+
var prev_value = current[key];
172+
if (value === prev_value) continue;
173+
174+
current[key] = value;
171175

172176
var prefix = key[0] + key[1]; // this is faster than key.slice(0, 2)
173177
if (prefix === '$$') continue;
174178

175179
if (prefix === 'on') {
176180
/** @type {{ capture?: true }} */
177181
const opts = {};
182+
const event_handle_key = '$$' + key;
178183
let event_name = key.slice(2);
179184
var delegated = DelegatedEvents.includes(event_name);
180185

@@ -183,21 +188,35 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
183188
opts.capture = true;
184189
}
185190

186-
if (!delegated && prev?.[key]) {
187-
element.removeEventListener(event_name, /** @type {any} */ (prev[key]), opts);
191+
if (!delegated && prev_value) {
192+
// Listening to same event but different handler -> our handle function below takes care of this
193+
// If we were to remove and add listeners in this case, it could happen that the event is "swallowed"
194+
// (the browser seems to not know yet that a new one exists now) and doesn't reach the handler
195+
// https://github.com/sveltejs/svelte/issues/11903
196+
if (value != null) continue;
197+
198+
element.removeEventListener(event_name, current[event_handle_key], opts);
199+
current[event_handle_key] = null;
188200
}
189201

190202
if (value != null) {
191203
if (!delegated) {
192-
// we use `addEventListener` here because these events are not delegated
204+
/**
205+
* @this {any}
206+
* @param {Event} evt
207+
*/
208+
function handle(evt) {
209+
current[key].call(this, evt);
210+
}
211+
193212
if (!prev) {
194213
events.push([
195214
key,
196215
value,
197-
() => (next[key] = create_event(event_name, element, value, opts))
216+
() => (current[event_handle_key] = create_event(event_name, element, handle, opts))
198217
]);
199218
} else {
200-
next[key] = create_event(event_name, element, value, opts);
219+
current[event_handle_key] = create_event(event_name, element, handle, opts);
201220
}
202221
} else {
203222
// @ts-ignore
@@ -252,7 +271,7 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
252271
effect(() => {
253272
if (!element.isConnected) return;
254273
for (const [key, value, evt] of events) {
255-
if (next[key] === value) {
274+
if (current[key] === value) {
256275
evt();
257276
}
258277
}
@@ -261,7 +280,7 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
261280
});
262281
}
263282

264-
return next;
283+
return current;
265284
}
266285

267286
/**

0 commit comments

Comments
 (0)