Meeting minutes
<mmocny> Document: mmocny/
<mmocny> mmocny/
<mmocny> Proposal: whatwg/
github-bot, take up whatwg/
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
mmocny: [reads from slide]
mmocny: There's a large lack of support for passive listeners. When some action happens, all event listeners are immediately dispatched
mmocny: When I click button, the counter increases value and does some expensive work
mmocny: My component isn't bad, something else is blocking the update
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
mmocny: Many listeners don't need to do any work to implement the default action
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
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
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
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
mmocny: some apis have noticed this and work around it, e.g. beforetoggle and toggle for popover
mmocny: beyond UI events, there are other apis like IntersectionObserver which are inherently async, perhaps others could be
bas: why passive? why not async?
mmocny: any name you want. I picked this because passive exists
mmocny: this was a prior choice made for scrolling, to allow devs to declare they don't need to prevent the event
dom: since it's more about the effect that caused the event, that's why passive was chosen whereas async is ambiguous (microtask)
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
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
mmocny: a nice script might choose to yield or add a passive listener to avoid this
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
mmocny: [shows demo of this]
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
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
mmocny: there is a pattern, idleUntilUrgent, that does this using doc lifecycle events
mmocny: scott presented an api which could possibly have something like runBeforeUnload
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/
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
mmocny: there are reasons to block click events that might still be there, but most could likely do the nice thing
dom: is there any diff between please before unload and having event handler run immediately and schedule the action in the unload listener?
mmocny: not sure, there are reasons we motivate people not to do things before unload, like prevents bfcache, this could also run before unload
dom: so it's like please run when idle or at worst before unload
mmocny: right, it lets you be low priority before unload, then guaranteeing
mmocny: there is a desire to track a string of async events. some sites create concepts to track groups of work
mmocny: support for passive events might add to this problem, you might have many actions arbitrarily scheduled
mmocny: maybe we should support tracking completion of effect
mmocny: developers have asked for this, state of event listener dispatch, etc
mmocny: there's also a string of projects to track scheduled work, task attribution, aync ??
mmocny: we could have a TaskController, Finalize Registry
mmocny: some folks have asked for preventDefault support. Right now you have to decide in the synchronous event whether to preventDefault
mmocny: this can be difficult, right now you have to preventDefault always and then recreate the default action
mmocny: some libraries debounce all events, with the real listeners delayed by one anim frame
mmocny: validation could require a network hop
mmocny: [reads from slide]
mmocny: one common thing, you click, await fetch, before updating events
mmocny: document loading, there's a moment in time where the page is loaded and painted but not ready to receive input yet
mmocny: we have blocking=render but we don't have blocking=interactions
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
… very common to wait for DOMContentLoaded which means whole body has finished
… module scripts are implicitly after this
… many developers report that early interactions seem to perform better, but it's doing nothing
… some workarounds, inline JS in the HTML, have a wrapper to capture and replay listeners later
… 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
… maybe we shoudl be delaying event dispatch
… if your script that would register event listeners is ready maybe we should let it run and put it behind the event queue
… similar question, what about hit testing? we should capture before the layout shift
… 5. lazy listeners. There could be many targets on the page that are lazy in loading controllers or listeners
… we complain we're shipping too much JS, we incentivize being ready for event dispatch. Many sites rely on event capture and replay
… instead of creating a giant bundle, server render the page, get started preloading portions of page (at low priority)
… then when user does interact, switch to priority loading for that listener
… but you have to rely on event replaying
… this can be complicated, but it also breaks a lot of UA default features, preventDefault, user activation, accessiblity, etc
… Maybe addEventListener could support promises
… possibly following service worker event.waitUntil pattern
… nav api has event.intercept
… view transitions returns a promise, waits until resolved
… or what if onclick could have a URL or some pattern to know what to load
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?
bas: a save button could be the same thing
noamr: right, if you save there's a low chance you're navigating
bas: save and quit is common
mmocny: I think it is general, unique thing is when you're interacting with event that navigates you could have blocked it
noamr: 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
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
noamr: can the first part also be more general?
mmocny: I'm motivated to solve this as lack of passive event listeners is significantly impacting web perf
mmocny: and the hesitation to do so would be addressed, at least partially, by this
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
noamr: something a bit more generic than last minute work for the scheduler
bas: concrete example
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
bas: Maybe i misunderstood, but idleUntilUrgent also doesn't block
noamr: right, you don't want it to block, but it should block commit
mmocny: my undersatnding is we unload the doc but allow event loop to continue
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
bas: wouldn't stuff stop working?
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.
bas: but those tasks might expect stuff to be around, like document
noamh: how critical is it to have reliably running events low priority?
mmocny: i believe the priority is distinct from desire to run before unload
mmocny: when dev says it's okay to run on idle, it means user is not waiting
bas: i agree, can we move away from idleBeforeUrgent which implies priority
bas: run before unload is more obvious to me
mmocny: maybe it's priority: eventually or priority: background *and* run before unload
bas: that makes more sense to me
mmocny: we could consider whether all tasks should be flushed. We could make it easier but we could choose to be more efficient
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
noamh: but ... there's no alternative for users, e.g. say they want to do a critical activity
bas: save the doc?
noamh: but that should be part of building a reliable application
noamh: you might want a more persistent way of doing things, not necessarily execute JS but have some ?? you can store
noamh: we can never guarantee
bas: there's never a guarantee anyways
noamh: exactly, developer needs to consider this
bas: right, but the probability is very different
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
dom: I'm all for using priority based vocab to refer to increasing the probability that your work will run
dom: i'm not sure about unload. I'm worried we'll push lots of work towards unload
dom: I'm worried we'll have apps be fast until they unload
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
bas: like an idle timeout?
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
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
mmocny: alternately you can guarantee it runs and reduce perf, and folks choose to do this
mmocny: they are incentivized to do the bad thing
bas: I'd like to understand concern, what if we accumulate huge amount of things, why would that happen?
dom: probably lots of tasks would happen, but more would accumulate right before unload than would today
Scott: we are encouraging putting things in pagehide, but yes does making it easier become dangerous?
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
noamr: I see this work as after input feedback before navigation response. It doesn't have to block nav start, etc
bas: there's multiple use cases
* everyone agrees lower than feedback
??: i've looked at numbers, the unload event typically fires around the same as response time. we're not blocking navigation start
mmocny: right, you can do this already
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?
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
mmocny: personally, I see it all the time
bas: is it possible frameworks frequently hide this from people?
mmocny: how?
bas: don't know
mmocny: stories i've heard is an ecommerce site invested in SSR, but it takes seconds for page to be interactive.
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
mmocny: this might trigger default action, but it doesn't do anything
smaug: inert attribute, should you inert the html element?
bas: but it blocks the interaction, doesn't store it
smaug: but we can't replay it
… layout is different
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
mmocny: I still think we should find the visible target
mmocny: but we could do the same hit test we do today, capture the target immediately, but dispatch later
mmocny: you might have a normal priority task which would have added the event listener
smaug: of course you don't know the dependencies
bas: the author can indicate it in this case
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
mmocny: right, that's a risk
<smaug> (we're running quite a bit overtime)
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?
… probably not many, because it's not a great system
… anything we can do to improve this would be great to encourage people to use the system
mmocny: thanks for discussion. I've linked to all of this from the session