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

Proposal: asynchronous event listeners #1308

Open
domfarolino opened this issue Aug 27, 2024 · 21 comments
Open

Proposal: asynchronous event listeners #1308

domfarolino opened this issue Aug 27, 2024 · 21 comments
Labels
needs concrete proposal Moving the issue forward requires someone to figure out a detailed plan needs implementer interest Moving the issue forward requires implementers to express interest topic: events

Comments

@domfarolino
Copy link
Member

domfarolino commented Aug 27, 2024

What is the issue with the DOM Standard?

I've heard chatterings in a few different places about interest in asynchronous event listeners on the platform, so I figured I'd file a bug here for some more centralized discussion. The problem: When some action happens on the web platform that results in firing events, all event listeners for that event are immediately, right then & there on the spot, without regard to the priority of the listener relative to surrounding tasks. It's entirely possible that developers wish to know about/respond to some events at a much lower priority than other competing tasks at around the same time. Currently there is no way to signal to the platform, that an event listener should be invoked asynchronously after the platform would ordinarily do so, saving the event listener's invocation for a less-busy/contentious time, in terms of task scheduling and execution.

Enabling this would let developers extend their existing task scheduling / prioritization logic to their event listeners as well. Something very rough along these lines can already be done today:

button.addEventListener('click', e => {
  if (mustDefer) {
    setTimeout(realClickHandler, kTimeout);
    // or…
    requestAnimationFrame(realClickHandler);
  }
});

…but it's pretty limited. First, it still involves immediately invoking user script in response to the event, so we don't actually avoid a big part of that cost. The fact that the queueing / deferral logic is handled immediately in userland is a missed performance opportunity — perhaps a large one? Second, it's not all that ergonomic, and is perhaps harder to schedule the readClickHandler relative to other userland tasks that follow a certain scheduling dynamic.

I wonder if there is an opportunity to integrate the Prioritized Task Scheduling API here. One option would be doing something as simple as passing in a task priority to addEventListener():

button.addEventListener('click', e => {
    // Do real stuff asynchronously…
    // e.preventDefault() does not work here❗
}, {priority: "user-visible"});

Related discussion: WICG/observable#74.

@chrishtr @mmocny @shaseley @mfreed7

@domfarolino domfarolino added needs implementer interest Moving the issue forward requires implementers to express interest topic: events needs concrete proposal Moving the issue forward requires someone to figure out a detailed plan labels Aug 27, 2024
@WebReflection
Copy link

FYI I've proposed to expose event.waitUntil when a listener is asynchronous as there's previous work on that within a Service Worker fetch listener ... and that didn't go well.

As possible workaround, we ended up polluting the addEventListener to make it possible to prevent the default (or stop propagation) wen a listener comes from a worker.

Basically if the options contains an invoke field which is a stopPropagation or preventDefault string, or an array with one or more methods to invoke, when the event triggers these methods are invoked.

This works well only if you know AOT you want to prevent that default or stop that (immediate) propagation. It cannot possibly work in any other scenario and it can't work conditionally neither (but that's an implementation detail/limitation).

@mmocny
Copy link

mmocny commented Aug 27, 2024

I wonder if there is an opportunity to integrate the Prioritized Task Scheduling API here. One option would be doing something as simple as passing in a task priority to addEventListener():

I like it!

I think this implies that each unique priority would be dispatched as a distinct (macro)task, and that the scheduler could defer lower priority event listeners as needed, perhaps even after rAF.

Would all event listeners at the same given priority still be coalesced into the same task? Or, if they are async now anyway, should they all be scheduled as if called with scheduler.postTask()?


FYI I've proposed to expose event.waitUntil when a listener is asynchronous

Interesting! I've made this suggestion before as well, though I wonder if the use case I had in mind was the same as what you suggested?

I think that waitUntil could be one useful way to express a lazy-loading controller for an event listener. Increasingly frameworks today support lazy-loading controllers/listeners, but rely on synchronous event capture and then later on synthetic event replay, to do so. This is inconvenient and lossy in many ways... though an ideal solution might need to be declarative.

(But, I don't quite see how waitUntil should help make the initial event dispatch async...)

@WebReflection
Copy link

(But, I don't quite see how waitUntil should help make the initial event dispatch async...)

example code I had in mind:

anything.addEventListener('name', (event) => {
  event.waitUntil(new Promise(async (res, rej) => {
    const thing = await (await fetch(permision)).text();
    if (thing === 'OK') {
      event.stopPropagation();
      event.preventDefault();
      return resolve();
    }
    reject();
  }));
});

This idea won't need any execution priority expectation, it would just flag that event as "unresolved" until whatever asynchronous thing needed to eventually keep going happens.

I hope this makes sense.

Original proposal + discussion: whatwg/html#9540

@mmocny
Copy link

mmocny commented Aug 27, 2024

That makes total sense to me, and overlaps with the "lazy-loaded event listener" use case I mentioned. It's just slightly orthogonal to the original proposal here.

(Not to say that all the async use cases shouldn't be discussed together)

@WebReflection
Copy link

@mmocny to clarify, I am 👍 on this proposal ... literally anything that will start tackling how to deal with async events, as every new API is async these days too, would be a great improvement over the current state. Anything would work to me and, to be honest, I find waitUntil thing ugly myself, my previous proposal was trying to simply "hack" around the fact there is some documentation and a previous case to consider around its existence ... if we could have a better approach that maybe one day will land in Service Worker too, even better 👋

@mmocny
Copy link

mmocny commented Aug 27, 2024

Here is one short list of use cases for what "async events" might mean to folks, each with potentially different feature requests:

  • "lazy loading controllers"

    • You don't have the code to attach to addEventListener loaded yet, but the UI is already presented and the user could already interact.
    • You want to delay any events from starting to dispatch, perhaps until some fetch / script finishes loading, or alternatively you just want a better mechanism to capture and replay the event later.
    • Note: once the event listener is loaded, it becomes a normal synchronous event listener.
    • Note: the declarative UI might have a declarative fallback (e.g. link click or form submit) but the imperative event might be "better" if allowed to load. Perhaps a timeout decides when to dispatch the default action?
  • "passive event dispatch"

    • This event listener needs to observe the event, but doesn't actually implement the core functionality of the event and doesn't need to be scheduled immediately and before next paint.
    • Already supported for various scroll events, and for specific features like Intersection Observer and some animation lifetime events, etc.
    • Easy to polyfill or workaround, as an opt-in, but might leave scheduling/performance opportunities on the table.
    • As a native feature, might be applied as a policy / intervention. For example, constraining a third party script to only be allowed to register passive listeners.
    • Note: {priority} seems to me to imply "passive" yet gives even more control.
  • "async effects which follow the sync event"

    • The use case @WebReflection lists.
    • Might still want to be able to preventDefault etc from the async., e.g. form validation which needs a network hop first.
    • Might want other web platform features, like performance event timing, to measure all the way to the end of async effects.
    • (Potentially?) has some overlap with AsyncContext / Task Attribution
  • "document unload" use cases

    • Today you can easily block the start of a navigation that would unload a document, but, once a document starts unloading there is a limited amount of time for script.
    • As a result, many scripts hook onto events and delay the default action (such as a link navigaton or form submit) from even starting, for fear of not having a change to observe the event at all when that event triggers document unload.
    • Perhaps we need a contract: This event listener is only passively attached to this event... but it should be "flushed" before unload. (Reminder: these scripts are already blocking unload start, anyway)
  • "document loading" use cases

    • Related to lazy-loading controllers but less fine grained. We have blocking=rendering for first paint but we don't have a way to block all events on resources like script.
    • Very early interactions are racy, and the priority of event dispatch is unpredictably prioritized over async/defer scripts.

@WebReflection
Copy link

I don't know if this is useful extra use case or implementation detail, but:

Perhaps a timeout decides when to dispatch the default action?

I have worked with SerialPort API and discovered the transient activation standard which already works well on Chrome/ium browsers (at least) and it has a transient activation duration spec that might (maybe?) help this proposal forward.

@flackr
Copy link

flackr commented Sep 19, 2024

  • "async effects which follow the sync event"

    • Might still want to be able to preventDefault etc from the async., e.g. form validation which needs a network hop first.

Being able to arbitrarily delay (and as a result re-order) default actions could have pretty far reaching complexity and implications.

I suspect for security reasons we might need to have a limit on how long a default action could be delayed and still executed, this reminds me a bit some of the async user activation use cases and could be a better way to handle it (having a direct link from the event to the user-activation requiring API), though we likely still need to support the model we have today. @mustaqahmed

I think we'd have to look through the different default actions and figure out whether they make sense to be possible to be delayed. E.g. we probably don't want to delay the default action of touchdown starting a scroll, and preventing the touch from turning into compatibility gestures like click.

Similarly there may be groups of default actions which don't make sense to be re-ordered. E.g. delaying setting focus while still clicking on the element?

@WebReflection
Copy link

@flackr links to delayed activations is in my previous comment: #1308 (comment)

I agree some event doesn't want that activation but your gesture example is spot-on: the whole clicking is a delayed event, why can't that event await before landing the user into a different page or avoid submitting that form already?

So 👍 to me about defining non-async-friendly events (I think these are an easy/small list related to mouse or touch movements or scrolling and not much else) and those that could wait a transient time before firing out of the blue.

The fact transient events exist in the first place should be a strong indication the synchronous API defined "ages ago" when no async existed in the first place might need some modernization for current APIs which are 99% of the time asynchronous anyway.

@esprehn
Copy link

esprehn commented Sep 20, 2024

I like the spirit of this, though I think you'd need to make the events uncancellable per the top comment and the default action happen in the past for this to not introduce a lot of engine complexity.

I think at least in Chrome input (ex. click) is aligned to the frame boundaries at "user-visible" priority because of how it flows through the compositor. Other events happen at animation frame timing ("user-blocking") too:

https://source.chromium.org/search?q=%22enqueueAnimationFrameEvent(%22

https://source.chromium.org/search?q=%22-%3EEnqueuePerFrameEvent(%22&sq=

So in most cases calling requestAnimationFrame from inside those events won't help except to try to batch work differently.

If you spec that the browser always posts a fresh task even if you're already at the same priority that would move the click later in time.

I suspect the most expensive part of clicking is the hit test which you can't avoid, but maybe those 3 lines of script add up. I don't see any harm in letting folks request a postTask priority like Dominic is suggesting though.

@smaug----
Copy link
Collaborator

There are so many different ways to schedule things that handling all the options in a param of addEventListener might be tricky. (For example, how to schedule running during idle time but at least after timeout?)
It is also a bit unclear how much having native support for async listener would help with anything, when doing it all in JS is easy.

@WebReflection

This comment was marked as abuse.

@mmocny
Copy link

mmocny commented Sep 25, 2024

Please, keep things civil.


I have created a document to expand on the ideas in this thread: https://github.com/mmocny/proposal-async-event-listeners, to prepare for the discussion happening later today in a TPAC breakout session: https://www.w3.org/events/meetings/df616a60-8591-4f24-b305-aa0870aac1cb/


Some of the concerns with preventDefault() do not apply to the original proposal in this thread. Hopefully we can create a separate forum for further discussion for the related problems that this proposal touched on.

To keep things focused, please, let's constrain this thread to the specific problem: "Lack of support for passive Event Listeners." and the specific suggestion to leverage Prioritized Task Scheduling API options.

@mmocny
Copy link

mmocny commented Sep 25, 2024

not possible to do it all in JS

I think that just capturing an event and re-scheduling it in a future task, is possible from JS, and @smaug---- argues that it offers more flexibility.

I tried to address this comment in the other doc I linked but will update this issue with a response after brainstorm.

@annevk
Copy link
Member

annevk commented Sep 26, 2024

As a reminder, the Code of Conduct applies to this and every other WHATWG repository, as well as our Matrix channel and any meeting. Please familiarize yourself with it and if you have any questions feel free to reach out on Chat.

@flackr
Copy link

flackr commented Sep 26, 2024

This was discussed at the TPAC breakout discussion on 2024-09-25:

The full IRC log of that discussion flackr: mmocny: this is a discussion around ideas for 5 areas worthy of discussing. The bulk of the conversation could be for the first proposal but I wanted to seed the rest
noamh joined the room
ayu joined the room
flackr: mmocny: [reads from slide]
aykut joined the room
flackr: mmocny: There's a large lack of support for passive listeners. When some action happens, all event listeners are immediately dispatched
flackr: mmocny: When I click button, the counter increases value and does some expensive work
flackr: mmocny: My component isn't bad, something else is blocking the update
smaug joined the room
flackr: mmocny: Developers often wish to respond to events at some lower priority without blocking paint. There's currently no way to signal to the platform. There's many patterns (e.g. double raf) to try to do this
mustaq joined the room
flackr: mmocny: Many listeners don't need to do any work to implement the default action
flackr: mmocny: common example, cookie consent dialogs have many things that follow the click, e.g. dialog disappears, records choice. There's a case study where one cookie response provider saw an improvement of ??% by doing this
flackr: mmocny: another idea is passive events. click events only dispatch non-passively even if you add a passive listener today. But if you polyfill this, you can delay expensive work
flackr: mmocny: Some differences, passive events can't preventDefault. It can be easy to polyfill, but relying on polyfills makes it less accessible and less used in practice. Native impls might have some opportunities to improve perf. We might also have an opportunity for interventions, e.g. some script is only allowed to listen passively
flackr: mmocny: an extension is to pass a priority. Right now, the priority is implicitly very high but if the dev knows priority is low maybe the dev could tell the UA
flackr: mmocny: some apis have noticed this and work around it, e.g. beforetoggle and toggle for popover
nathan joined the room
flackr: mmocny: beyond UI events, there are other apis like IntersectionObserver which are inherently async, perhaps others could be
flackr: bas: why passive? why not async?
flackr: mmocny: any name you want. I picked this because passive exists
flackr: mmocny: this was a prior choice made for scrolling, to allow devs to declare they don't need to prevent the event
flackr: dom: since it's more about the effect that caused the event, that's why passive was chosen whereas async is ambiguous (microtask)
flackr: mmocny: there are risks with deferring work because of unloads. You could attach listeners for every link click, you could block navigation by adding expensive work on click
flackr: mmocny: worse yet, it prevents the start of any network request, speculation rules address some of this, but the issue is that at any point you could prevent the nav from happening
flackr: mmocny: a nice script might choose to yield or add a passive listener to avoid this
flackr: mmocny: however, once you do this, if the document unloads there's a very limited time to run your script, the effect could get dropped
flackr: mmocny: [shows demo of this]
flackr: mmocny: we know from lots of people that tried to be nice and yield they saw a decreased reliability and went back to blocking the nav
noamh left the room: Quit: noamh
flackr: mmocny: maybe we need assurances that tasks are flushed before unload. If you task could already have blocked the nav, and you choose to yield, maybe we can ensure we run them before unload
flackr: mmocny: there is a pattern, idleUntilUrgent, that does this using doc lifecycle events
flackr: mmocny: scott presented an api which could possibly have something like runBeforeUnload
flackr: dom: with idleUntilUrgent, are we guaranteeing the task will run? If the task takes forever or there's 200 tasks before it we want to guarantee it succeeds/
flackr: mmocny: correct, these scripts are currently guaranteed to run, we'd like to give them a lesser guarantee but some assurance that we will try to run them
flackr: mmocny: there are reasons to block click events that might still be there, but most could likely do the nice thing
flackr: dom: is there any diff between please before unload and having event handler run immediately and schedule the action in the unload listener?
flackr: mmocny: not sure, there are reasons we motivate people not to do things before unload, like prevents bfcache, this could also run before unload
flackr: dom: so it's like please run when idle or at worst before unload
flackr: mmocny: right, it lets you be low priority before unload, then guaranteeing
flackr: mmocny: there is a desire to track a string of async events. some sites create concepts to track groups of work
flackr: mmocny: support for passive events might add to this problem, you might have many actions arbitrarily scheduled
flackr: mmocny: maybe we should support tracking completion of effect
flackr: mmocny: developers have asked for this, state of event listener dispatch, etc
flackr: mmocny: there's also a string of projects to track scheduled work, task attribution, aync ??
flackr: mmocny: we could have a TaskController, Finalize Registry
flackr: mmocny: some folks have asked for preventDefault support. Right now you have to decide in the synchronous event whether to preventDefault
flackr: mmocny: this can be difficult, right now you have to preventDefault always and then recreate the default action
flackr: mmocny: some libraries debounce all events, with the real listeners delayed by one anim frame
flackr: mmocny: validation could require a network hop
flackr: mmocny: [reads from slide]
flackr: mmocny: one common thing, you click, await fetch, before updating events
flackr: mmocny: document loading, there's a moment in time where the page is loaded and painted but not ready to receive input yet
flackr: mmocny: we have blocking=render but we don't have blocking=interactions
flackr: mmocny: it is very recommended that sites server render content without requiring scripts to run, but this means early renderings are without event listeners on many sites
flackr: .. very common to wait for DOMContentLoaded which means whole body has finished
flackr: .. module scripts are implicitly after this
flackr: .. many developers report that early interactions seem to perform better, but it's doing nothing
flackr: .. some workarounds, inline JS in the HTML, have a wrapper to capture and replay listeners later
flackr: ... more recently, there's a clever use of forms where all actions are wrapped with a form. But when your page is loading if an event arrives we'll do the default thing and submit the form reloading the page. It would have been much faster to run the already loaded script
flackr: .. maybe we shoudl be delaying event dispatch
flackr: .. if your script that would register event listeners is ready maybe we should let it run and put it behind the event queue
flackr: ... similar question, what about hit testing? we should capture before the layout shift
flackr: .. 5. lazy listeners. There could be many targets on the page that are lazy in loading controllers or listeners
tbondwilkinson joined the room
flackr: .. we complain we're shipping too much JS, we incentivize being ready for event dispatch. Many sites rely on event capture and replay
flackr: ... instead of creating a giant bundle, server render the page, get started preloading portions of page (at low priority)
flackr: ... then when user does interact, switch to priority loading for that listener
flackr: .. but you have to rely on event replaying
flackr: .. this can be complicated, but it also breaks a lot of UA default features, preventDefault, user activation, accessiblity, etc
flackr: .. Maybe addEventListener could support promises
flackr: .. possibly following service worker event.waitUntil pattern
flackr: .. nav api has event.intercept
flackr: .. view transitions returns a promise, waits until resolved
flackr: .. or what if onclick could have a URL or some pattern to know what to load
flackr: q?
* Zakim sees no one on the speaker queue
noamr: q+
* Zakim sees noamr on the speaker queue
domfarolino joined the room
flackr: noamr: about waitUntilUrgent, is this mostly common in listeners that are the reason for the notification. e.g. click link that is navigating, and you yield in there? Or is it a more general problem?
flackr: bas: a save button could be the same thing
flackr: noamr: right, if you save there's a low chance you're navigating
flackr: bas: save and quit is common
noamh joined the room
flackr: mmocny: I think it is general, unique thing is when you're interacting with event that navigates you could have blocked it
flackr: ack noamh
* Zakim sees noamr on the speaker queue
flackr: noamh: we started working on fetchLater for last minute fetching on unload. Maybe we need to think about this more hollistically instead of pushing more arbitrary code towards unload
flackr: s/noamh/noamr
flackr: mmocny: FYI fechLater is an API that can fetch even after unloading. The two features would work really well together. E.g. you want to stuff the fetch payload eagerly, so that it is queued. It's the second part of the equation
flackr: noamr: can the first part also be more general?
guohuideng left the room: Quit: guohuideng
flackr: mmocny: I'm motivated to solve this as lack of passive event listeners is significantly impacting web perf
flackr: mmocny: and the hesitation to do so would be addressed, at least partially, by this
flackr: noamr: one thing comes to mind, if we had passive listeners, and there is a nav starting. The nav starting would be a sign to run the listeners, or some, or multiple levels e.g. i don't care if it gets dropped
flackr: noamr: something a bit more generic than last minute work for the scheduler
flackr: bas: concrete example
flackr: noamr: if you have an onclick that adds something to analytics. This click event won't affect input feedback in any way, doesn't need to be quick just needs to happen before unload. You also don't want to block navigation and make it slower
flackr: bas: Maybe i misunderstood, but idleUntilUrgent also doesn't block
flackr: noamr: right, you don't want it to block, but it should block commit
flackr: mmocny: my undersatnding is we unload the doc but allow event loop to continue
flackr: mmocny: there are certain features that stop working but not all. I don't know if you have to delay this or not, but right now page freezes and you continue to do things
flackr: bas: wouldn't stuff stop working?
flackr: mmocny: in my experimentation, we'll commit the nav, show the new doc, but in the background there's some amount of time to schedule tasks.
flackr: bas: but those tasks might expect stuff to be around, like document
flackr: noamh: how critical is it to have reliably running events low priority?
flackr: mmocny: i believe the priority is distinct from desire to run before unload
flackr: mmocny: when dev says it's okay to run on idle, it means user is not waiting
flackr: bas: i agree, can we move away from idleBeforeUrgent which implies priority
flackr: bas: run before unload is more obvious to me
flackr: mmocny: maybe it's priority: eventually or priority: background *and* run before unload
flackr: bas: that makes more sense to me
flackr: mmocny: we could consider whether all tasks should be flushed. We could make it easier but we could choose to be more efficient
flackr: noamh: i still want to challenge assumption. Do we really want to encourage building something that assumes tasks will block navigation or might run while nav is happening? It seems like the wrong guidance
flackr: noamh: but ... there's no alternative for users, e.g. say they want to do a critical activity
flackr: bas: save the doc?
flackr: noamh: but that should be part of building a reliable application
flackr: noamh: you might want a more persistent way of doing things, not necessarily execute JS but have some ?? you can store
flackr: noamh: we can never guarantee
flackr: bas: there's never a guarantee anyways
flackr: noamh: exactly, developer needs to consider this
flackr: bas: right, but the probability is very different
flackr: bas: you develop your app on the assumption it won't crash, so you don't have to auto-save often, and good probability you don't lose stuff. If we can increase the odds that we can run before unload it helps
flackr: dom: I'm all for using priority based vocab to refer to increasing the probability that your work will run
flackr: dom: i'm not sure about unload. I'm worried we'll push lots of work towards unload
flackr: dom: I'm worried we'll have apps be fast until they unload
flackr: dom: I think we should talk about it in terms of priority. At what point in time can you tell the platform it is urgent to do your work rather than assuming the last point in time
flackr: bas: like an idle timeout?
flackr: mmocny: these are all great points. There's an implicit deadline that when you run code that blocks the user interaction there's an implicit frustration. We're moving this to the background
flackr: mmocny: I understand the concern, it is about signaling some assurance / making the patterns easier. The alternatives are yield and hope for best, but folks choose not to do this today
flackr: mmocny: alternately you can guarantee it runs and reduce perf, and folks choose to do this
flackr: mmocny: they are incentivized to do the bad thing
flackr: bas: I'd like to understand concern, what if we accumulate huge amount of things, why would that happen?
flackr: dom: probably lots of tasks would happen, but more would accumulate right before unload than would today
flackr: Scott: we are encouraging putting things in pagehide, but yes does making it easier become dangerous?
flackr: mmocny: maybe unload is wrong, e.g. resource contention, time limit may not be enough. Maybe this is high priority work. It's not supposed to block the interaction, but maybe it's eagerly flushed
flackr: noamr: I see this work as after input feedback before navigation response. It doesn't have to block nav start, etc
flackr: bas: there's multiple use cases
flackr: * everyone agrees lower than feedback
flackr: ??: i've looked at numbers, the unload event typically fires around the same as response time. we're not blocking navigation start
flackr: mmocny: right, you can do this already
flackr: philip: another scary situation, when doc starts unloading before it completes loading. We want to collect information. Do we want to make sure the nav still gets blocked?
flackr: mmocny: I see some folks deploying large apps in room, which are serious problems as opposed to problems that you've solved and are fine
flackr: mmocny: personally, I see it all the time
flackr: bas: is it possible frameworks frequently hide this from people?
flackr: mmocny: how?
flackr: bas: don't know
flackr: mmocny: stories i've heard is an ecommerce site invested in SSR, but it takes seconds for page to be interactive.
flackr: mmocny: this can be busy loading script, or browser is optimizing first render and hasn't attempted to load script yet, event is immediately handled as a no-op
tbondwilkinson: q+
* Zakim sees noamr, tbondwilkinson on the speaker queue
flackr: mmocny: this might trigger default action, but it doesn't do anything
flackr: smaug: inert attribute, should you inert the html element?
flackr: bas: but it blocks the interaction, doesn't store it
flackr: smaug: but we can't replay it
flackr: .. layout is different
nathan left the room: Quit: nathan
flackr: mmocny: my theory is this is already racy. When i interact, i send event to OS > browser > renderer > schedules, etc, it might not hit test for many frames, i hope there's no layout shift that changes target
flackr: mmocny: I still think we should find the visible target
flackr: mmocny: but we could do the same hit test we do today, capture the target immediately, but dispatch later
flackr: mmocny: you might have a normal priority task which would have added the event listener
flackr: smaug: of course you don't know the dependencies
flackr: bas: the author can indicate it in this case
flackr: smaug: the script might just be a tracking script. You just want to run the user input before the tracking script. It could be hard to figure out which scripts you want to load
flackr: mmocny: right, that's a risk
flackr: Ack noamr
* Zakim sees tbondwilkinson on the speaker queue
flackr: Ack tbondwilkinson
* Zakim sees no one on the speaker queue
noamh: q+
* Zakim sees noamh on the speaker queue
smaug: (we're running quite a bit overtime)
flackr: tbondwilkinson: progressive hydration is pretty buggy. angular is working on it, others. That's one thing that may be interesting to focus on since frameworks are focusing on this. None of these seem like a complete dud. As a thought, how many frameworks use browser's event dispatch system?
flackr: .. probably not many, because it's not a great system
flackr: .. anything we can do to improve this would be great to encourage people to use the system
Scott left the room: Quit: Scott
domfarolino left the room: Quit: domfarolino
noamr left the room: Quit: noamr
noamh left the room
aykut left the room: Quit: aykut
flackr: mmocny: thanks for discussion. I've linked to all of this from the session

@github-staff github-staff deleted a comment from Lxx-c Oct 23, 2024
@github-staff github-staff deleted a comment from Lxx-c Oct 23, 2024
@github-staff github-staff deleted a comment from Lxx-c Oct 23, 2024
@ethangardner

This comment was marked as spam.

@WebReflection
Copy link

I haven't seen updates after late September but there is one option that hasn't been discussed: the usage of { async: true } as third argument field:

  • it can help disambiguating more than an async callback added as listener, because even a sync callback could return a Promise
  • it can easily fail down the path if other listeners were added without such option but ... if there is only one listener for that specific node (node ownership via library/FW/utility) it helps branching logic without affecting the rest, delaying eventually the bubbling, so that capturing could prevent those listeners to ever happen but if inner-node has one or more async listeners attached, it can decide when the rest of the bubbling could happen, if ever
  • there should be still a way to prevent events from never resolving, so that this behavior could be related to a MAX_LISTENER_TIME defined by vendors

Once upon a time, we had click events triggering after ~300ms because ondblckick event could've also happened in the meantime, these days that delay has been mitigated but I find that ~300ms a sweet spot that makes events more convoluted only if an explicit { async: true } has been provided.

This idea is still about branching events in a way or another, but to me it feels like the least obtrusive approach:

  • fully backward compatible with all the Web that never used such property as optional third argument field
  • it doesn't change anything in those pages where such property never exists
  • it helps libraries' authors to define listeners on specific nodes which asynchronicity is crucial to better operate

Any thought about this option? Thanks.

@mmocny
Copy link

mmocny commented Nov 20, 2024

async callback added as listener

Someone might very well want an "async callback" for convenience of accessing await syntax, but still have it behave perfectly normally. The sync/async nature of the callback could never be "the signal" of how the default action behaves. (besides your point that non-async functions can still return promises)

if other listeners were added without such option

I think the entire goal here is for some listeners to still opt in to synchronous dispatch (as today) and some others to opt in to async dispatch (as if calling scheduler.yield() or scheduler.postTask() immediately from the event listener.

there should be still a way to prevent events from never resolving

At the moment, there would be no awaiting of any promise for any default action as part of this specific proposal.

Such ideas were discussed, i.e. could you literally delay the default action, such as a form submit, waiting on a server hop-- or even just a local postTask while still allowing paint-- but that is not in this specific proposal at the moment, I think.

I tried to list related use cases in a comment above in order to help disambiguate. There was a lot of good initial conversation at TPAC 2024 about these use cases and potential proposals for each of them.

But again, the specific scope of this proposal here I think is limited to the "passive events" use case.

Obviously we could create other proposals to handle the other use cases-- I'm also eager to see more of this area being improved.

I find that ~300ms a sweet spot

So, I think that 300ms is too long to provide a default feedback to the user. However, if there is a "passive listener" it might very well be acceptable for it to take hundreds of ms or even multuple seconds to resolve, so long as those actions are more like "optional followups" rather than immediate feedback.


bubbling, capturing

This is an interesting question. I wonder if these types of events should participate in the same type of bubbling/capturing. If we do both sync and async event dispatch that might mean more total tree walking.

@WebReflection
Copy link

But again, the specific scope of this proposal here I think is limited to the "passive events" use case.

I am not sure when that became the case as the issue title is Proposal: asynchronous event listeners and the first presented example is a click event.

Obviously we could create other proposals to handle the other use cases

What would be, after this one, a good title for those proposals?

If we do both sync and async event dispatch that might mean more total tree walking.

I think async listeners should throw if attached as capturing, as that makes no-sense to me, just my 2c on that.

@mmocny
Copy link

mmocny commented Nov 20, 2024

I am not sure when that became the case as the issue title is Proposal: asynchronous event listeners and the first presented example is a click event.

Yes the example uses click, but the very first paragraph says the scope of the proposal:

Currently there is no way to signal to the platform, that an event listener should be invoked asynchronously after the platform would ordinarily do so, saving the event listener's invocation for a less-busy/contentious time, in terms of task scheduling and execution

Then, the code comments inside the first example say:

// e.preventDefault() does not work here❗

I do agree that some important details were implied, and that the name "asynchronous event dispatch" can mean a lot of powerful things for a lot of folks. Hence my trying to decouple the use cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs concrete proposal Moving the issue forward requires someone to figure out a detailed plan needs implementer interest Moving the issue forward requires implementers to express interest topic: events
Development

No branches or pull requests

10 participants