Sadly this is more complicated than I anticipated. We still need to queue some events, but note that the prior approach was still wrong since it was queuing everything, and this is more than just dispatching multiple progress events rather than just 1 when unblocked: error or timeouts are also "progress events" just like abort is, but should override all the prior ones. This worked for abort only since I had special handling for it (because it's synchronous), but not for the others.
I think simplest way is to just register handlers for all the events we care about, then we can treat them properly and synthesize or send them later, when unblocked.
And also found some other small issues of course. For instance, abort() doesn't always end up aborting in Gecko, so proper way is to use actual readyState from it instead of hardcoding 0 at the end of abort().
It's doable, but I need to figure out a few things how to keep it from getting too messy while still being correct.