waitUntil on DOM Events


#1

I would like to offload as much application logic into a Web Worker as possible. That includes logic that handles events. Since event propagation happens synchronously I cannot communicate with a Web Worker in order to determine if an event should be preventDefault-ed or stopPropagation-ed.

I think a solution could be to add waitUntil (or something similar) to MouseEvents.

anchor.addEventListener('click', ev => {
  ev.waitUntil(new Promise((resolve) => {
    let msg = Object.assign({}, ev);
    worker.postMessage(msg);

    worker.onmessage = ev => {
      if(ev.data.preventPlease) {
        ev.preventDefault();
      }
      resolve();
    };
  });
});

#2

This also looks like a more elegant way to solve my previous concerns around using async code with the user-gesture requirement: User-gesture restrictions and async code

So - thumbs up from me!


#3

I’ve asked the editor of the DOM spec to comment. The use case outlined by Ashley are quite helpful.


#4

This basically breaks the way events work. You can’t just turn all synchronous checks for the canceled flag into asynchronous checks. That requires way too much changes.

Handling user input differently somehow might be doable, but someone should first write up how user input actually works today in more detail, since that is all still rather messy.


#5

It’s true you can’t asynchronously cancel all events, e.g. a wheel event needs to know synchronously if it will block scrolling. However I don’t see why it wouldn’t work in general with non-cancellable events. The waitUntil method also helps make the user gesture requirements more reasonable.


#6

What about only adding waitUntil() for passive listeners? That might at least work for the user gesture requirement.


#7

I’m a bit concerned about the user experience of this. E.g. imagine a user clicking on a <span> inside a link and the link is actually followed like a minute later because the event hadn’t propagated from the <span> to the <a> until then.


#8

I don’t really see how this would make that case worse than what is currently possible. For example, a site could be doing something like a sync xhr which could also block for a minute.


#9

UX-sensitive actions like navigating the page could have a time limit imposed by the browser. For example the page must act on the event within 5 seconds, otherwise it loses the ability to perform the action.


#10

Thanks everyone for comments! I agree that there are some possible UX concerns here. I’m not sure if this proposal is worse than the status quo, as a lot of this can be done using new Event.

Nevertheless, as a browser dev, @annevk, says this proposal isn’t possible I have to believe him and withdraw the idea. I am still very much interested in a way to handle events off the main thread; if there is another possible avenue here.


#11

What? It’s only not possible for cancelable events - non cancelable events can still use this mechanism, right?


#12

Here’s another motivating example: a real-world user using a real-world web app we’ve recently published, complaining that they can’t copy data directly, on day 1 of the launch: https://www.scirra.com/forum/copy-to-clipboard-is-blocked-by-chrome-repeatedly_t189480

The reason for this is we have to asynchronously generate the data to copy, and so by the time the data to copy is ready, we no longer have permission to copy data to the clipboard, even if it only took milliseconds.

We have similar problems with popup windows. We really need a way to relax these restrictions.


#13

How about this modified way?

anchor.addEventListener('click', ev => {
  ev.preventDefault(); // prevent first
  worker.postMessage({ ...ev });

  worker.onmessage = ev => {
    if (!ev.data.preventPlease) {
      ev.doDefault(); // do the deferred default action
    }
  }
}

#14

This problem becomes more pressing with OffscreenCanvas and the implications by browser vendors that heavy content like games ought to run in a worker.

Things like calling preventDefault() and performing user-gesture-restricted actions (which even include playing audio on some platforms) become effectively impossible if you have to postMessage() to a worker in an event handler.