🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

preventDefault and stopPropagation in the addEventListener options bag #1378

Open

Description

@nicolo-ribaudo

What problem are you trying to solve?

#1038 proposes a method to create a promise that gets resolved once a given event is fired. For example (ignore the name, I don't think it's settled and I just came up with one):

const img = new Image();
img.src = srcURL;
img.addEventListener("load", () => { /* continue doing something with the image */ });

// -->

const img = new Image();
img.src = srcURL;
await img.eventPromise("load");
/* continue doing something with the image */

One problem with that proposal, that is discussed in its comments, is that it does not work with preventDefault and stopPropagation, because they need to be called synchronously and thus before that the promise resolves.

What solutions exist today?

/

How would you solve it?

In many cases stopPropagation/stopImmediatePropagation/preventDefault are called unconditionally at the beginning of the event listener callback. For those cases, a declarative API would be enough. Assuming that .waitFor("load") would take the same options bags as .addEventListener (only disallowing once, I guess), then it would look like:

await button.eventPromise("click", { preventDefault: true });

With the .addEventListener API, it would be:

button.addEventListener("click", e => {
  e.preventDefault();
  // do stuff
});

// -->
button.addEventListener("click", e => {
  // do stuff
}, { preventDefault: true });

I'm opening this as a separate issue from #1038 just because it would also apply to the non-promise API. It's most useful for the promise-based one, but in general it's nice to have declarative alternative to imperative ones.

I'd be curious to know if it'd be helpful for implementations to know that an event is going to be prevented/stopped before calling the callback.

Context

For the web integration of the AsyncContext proposal, we are exploring the possibility of not propagating the context through async JS-caused events.

However, we received feedback that it'd be highly desirable to propagate it for load/error event, as their listener is conceptually a continuation of whatever started the loading process.

Events/addEventListener are not the best primitive for signaling completion of some operation, which would be best suited by promises. If we can untangle the "I want to continue do something when this intermediate operation completes" use case (e.g. load/error) from the "start this new operation when something happens", we can avoid pushing complexity needed by the first one onto the second.

cc @annevk

Activity

  1. WebReflection commented on Jun 4, 2025

    @WebReflection

    to whom it might concern, because we drive DOM synchronously from Workers and WASM interpreters we ended up solving this issue in a similar way, although we used just an invoke: ['preventDefault', 'stopPropagation', 'stopImmediatePropagation'] patch for the addEventListener because we didn't want to implement something possibly conflicting with the future of the API ... yet a field that couldn't be a symbol, as these can't survive structured clone, had to be taken.

    If we had this implemented natively we wouldn't need to fix that ourselves patching the global prototype as that's usually the least desired thing one should do out there.

    +1 for this request 👍

    edit ... and happy to polyfill it properly once/if it lands

  2. deleted a comment from on Jun 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions