Skip to content

Commit 9d2ecc1

Browse files
authored
chore: improve event error handling (#11840)
I noticed that we spend a lot of time dealing with a recursive function when propagating events. Let's avoid that overhead and move back to a much faster while loop. We can also stack the errors and throw them at the end.
1 parent 2382eb0 commit 9d2ecc1

File tree

1 file changed

+50
-28
lines changed
  • packages/svelte/src/internal/client/dom/elements

1 file changed

+50
-28
lines changed

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

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -173,38 +173,60 @@ export function handle_event_propagation(handler_element, event) {
173173
}
174174
});
175175

176-
/** @param {Element} next_target */
177-
function next(next_target) {
178-
current_target = next_target;
179-
/** @type {null | Element} */
180-
var parent_element = next_target.parentNode || /** @type {any} */ (next_target).host || null;
181-
182-
try {
183-
// @ts-expect-error
184-
var delegated = next_target['__' + event_name];
185-
186-
if (delegated !== undefined && !(/** @type {any} */ (next_target).disabled)) {
187-
if (is_array(delegated)) {
188-
var [fn, ...data] = delegated;
189-
fn.apply(next_target, [event, ...data]);
190-
} else {
191-
delegated.call(next_target, event);
176+
try {
177+
/**
178+
* @type {unknown}
179+
*/
180+
var throw_error;
181+
/**
182+
* @type {unknown[]}
183+
*/
184+
var other_errors = [];
185+
yield_event_updates(() => {
186+
while (current_target !== null) {
187+
/** @type {null | Element} */
188+
var parent_element =
189+
current_target.parentNode || /** @type {any} */ (current_target).host || null;
190+
191+
try {
192+
// @ts-expect-error
193+
var delegated = current_target['__' + event_name];
194+
195+
if (delegated !== undefined && !(/** @type {any} */ (current_target).disabled)) {
196+
if (is_array(delegated)) {
197+
var [fn, ...data] = delegated;
198+
fn.apply(current_target, [event, ...data]);
199+
} else {
200+
delegated.call(current_target, event);
201+
}
202+
}
203+
} catch (error) {
204+
if (throw_error) {
205+
other_errors.push(error);
206+
} else {
207+
throw_error = error;
208+
}
209+
}
210+
if (
211+
event.cancelBubble ||
212+
parent_element === handler_element ||
213+
parent_element === null ||
214+
current_target === handler_element
215+
) {
216+
break;
192217
}
218+
current_target = parent_element;
193219
}
194-
} finally {
195-
if (
196-
!event.cancelBubble &&
197-
parent_element !== handler_element &&
198-
parent_element !== null &&
199-
next_target !== handler_element
200-
) {
201-
next(parent_element);
220+
});
221+
if (throw_error) {
222+
for (let error of other_errors) {
223+
// Throw the rest of the errors, one-by-one on a microtask
224+
queueMicrotask(() => {
225+
throw error;
226+
});
202227
}
228+
throw throw_error;
203229
}
204-
}
205-
206-
try {
207-
yield_event_updates(() => next(/** @type {Element} */ (current_target)));
208230
} finally {
209231
// @ts-expect-error is used above
210232
event.__root = handler_element;

0 commit comments

Comments
 (0)