#1089: Extended lifetime shared workers

Visit on Github.

Opened May 9, 2025

こんにちは TAG-さん!

I'm requesting an early TAG design review of extended lifetime shared workers.

We propose adding a new option to the SharedWorker constructor that serves as a request to extend its lifetime after all current clients have unloaded:

const sharedWorker = new SharedWorker(url, { extendedLifetime: true });

The primary use case here is to allow pages to perform some async work that requires JavaScript after a page unloads, without needing to rely on a service worker.

Discussions

Log in to see TAG-private discussions.

Discussed May 12, 2025 (See Github)
<!-- PRs -->
Discussed May 19, 2025 (See Github)

Xiaocheng: Shared workers are bound to pages. If you close all the pages, worker might be terminated. But some pages want to do async work after the page closes. They can resort to Service Worker. This proposal adds a flag to give the shared worker a longer lifetime. Don't think there are privacy/security considerations. API design. Thoughts are that first, the behavior is quite reasonable. Using Service Worker for async work is not great. Service Worker shouldn't be doing heavy work. But, this API is unnecessary because existing Shared Worker spec allows UAs to extend lifetime to a certain extent. Don't need this. Implementers can just do this. Don't see necessity. Unless there are cases where lifetime extension is undesirable in some other cases, so we need this to explicitly switch it on, but I don't see such cases. Suggestion is to leave a comment and ask about the use cases that make this necessary.

Marcos: Things like this come up a lot, where you have a use case that requires an extended lifetime. That use case should be the browser's responsibility without signaling. From a privacy perspective, once you shut the page, users expect the page to be dead, but this might be making network requests. Concerned that this approaches the problem the wrong way. "Extend this to perform actions that might not require an extended lifetime." It's a footgun that uses up system resources. If a thing requires to run after page unloads, this should be bound to page unloads. Use case is "page unloads, and then ???" It doesn't require a service worker but kinda requires service worker.

Xiaocheng: Navigation between two pages. Want Javascript during navigation. Ensure that some state is maintained during navigation.

Marcos: It's origin-bound anyway. If you're navigating ... don't want a shared worker across multiple origins. Act of navigating is the thing that would keep it alive. Some events already extend lifetime. E.g. in service worker, you have extendible events.

Jeffrey: respondWith()

Jeffrey: You might be agreeing with Xiaocheng, that in a same-origin navigation, the UA should keep the shared worker alive, while in a cross-origin navigation, maybe the UA should kill it quickly.

Marcos: Surprising that other solutions weren't proposed. But maybe they're explained in the explainer itself. E.g. the case in the motivation: fetch analytics (delayed beacon) would be covered by the API for fetchLater(). I get the use case, but I don't think it's done on the right level.

Jeffrey: I think Xiaocheng's question is good, and he should send it and see what Domenic says.

Marcos: Yes, just worried that there is a privacy concern. Don't need to mention that in the comment. I agree with the rest of the conclusion.

Xiaocheng: Will draft and send a comment.

Discussed May 26, 2025 (See Github)

Xiaocheng: We discussed it before but we missed its major use case which is to allow doing async work after the page is closed. Current approach is after page closed, a task is posted to a service worker, but this isn't great because a service worker is very heavy.

... The proposal is about allowing posting tasks to a shared worker. I wrote a draft comment in the private thread. After thinking about the major usecase, I still think it's a footgun because a developer might always set 'extent lifetime' to true - because there's no harm; it won't break their app, but adds possibilities for them. They may do this without realizing that it's increasing resource burden on users.

Martin: Have you looked at the WebKit standards position?

https://github.com/WebKit/standards-positions/issues/492

Xiaocheng: Not yet.

Martin: They were pretty negative on this one. It's probably timed out.

DanA: I share your concern Xiaocheng. Also concerned about user expectation. User has tab open; it's doing something; user decides 'I don't want this thing'; they click the 'close' control; but surprise - it keeps going. The undesired task continues.

... Yes, that may already be happening with a service worker, but maybe because they're more heavyweight there's less of a worry that they'll be overused.

Martin: Service workers have the same lifecycle that Xiaocheng is talking about here. They can be kept alive by mechansims such as promises, handling actions that they've been tasked with. There are no hard limits on them (or hard limits are on the order of 10 seconds, but these are not guarantees).

DanA: Could we post something that basically reflects the concern that Anne put forward in the WebKit position and saying that TAG shares this concern (not just following it). Need to make it clear this is also our own view.

Martin: Effectively the feedback that Xiaocheng already has, but add a paragraph that it removes the accountability feature that the web has which is that if you close a tab, it can't continue to do stuff indefinitely.

DanA: Do we want to close this, or suggest mitigations?

Resolution: Xiaocheng to post draft comment and include a mention of Anne's feedback.

Discussed Jun 2, 2025 (See Github)

Marked as pending now that the comment is posted. Skipped.

<!-- PRs -->
Discussed Jun 9, 2025 (See Github)

Max: They added a response to the explainer: https://gist.github.com/domenic/c5bd38339f33b49120ae11b3b4af5b9b#doing-this-automatically-or-with-an-opt-in-from-within-the-worker

Jeffrey: Interesting reply. They (Chrome + Mozilla) are saying it's about as expensive to extend the lifetime by one task as to extend by ~30s. Don't know the implementation well enough to challenge that.

Max: They also point to Safari feedback.

Jeffrey: Which looks negative from Anne.

Jeffrey: Privacy difference from my suggestion is negligible since the developer can always intentionally extend up to the UA's limit.

Jeffrey: I'm inclined to see what Xiaocheng thinks. He and Dan C have the most context.

Max: Their S&P Questionnaire says they plan to add Connsiderations to the HTML spec.

Jeffrey: And it's just an early review.

Jeffrey: I note that the explainer, with the alternatives considered, ought to land somewhere. A Gist isn't a long-term home.

Jeffrey: I'll work with Xiaocheng to post a comment.

Discussed Jun 16, 2025 (See Github)

Xiaocheng: Maybe we could add a new worker type.

Martin: Could work.

Jeffrey: Worried about a new worker type. Would be dedicated, but live longer than the page.

Martin: Cross of two types. Would have some properties like SW, but simpler.

Jeffrey: Recaptulates proposed feedback. They did suggest a worklet.

Xiaocheng: Worklets live in the page context, not quite the same.

Martin: Audio worklets do not.

Jeffrey: Worklets don't have an event loop, which this needs.

Martin: Xiaocheng's feedback seems right.

Jeffrey: Good to send.

Comment by @xiaochengh Jun 19, 2025 (See Github)
<details> <summary>repost by mistake</summary> Hi @domenic, thanks for pointing out the hidden resource cost for preparing a shared worker to have an extended lifetime. There is a concern we previously raised that isn't addressed yet:

What if developers set extendedLifetime: true to every shared worker just to be "safe" (as it won't break any app) without realizing the hidden resource cost of doing so?

Since using a shared worker causes so many detailed issues (like mismatching options, reconnection, etc), should we instead introduce a new type of worker (not a worklet as already discussed in the explainer) that is designed for this use case?

</details>
Comment by @jyasskin Jun 19, 2025 (See Github)

@xiaochengh, note that you already posted that comment, and Domenic replied to it.

Discussed Jun 23, 2025 (See Github)

Xiaocheng: Last time we raised the concern that dev would set the flag on all SWs to be safe. They replied that we don't see that behavior from fetch(keepAlive). It is hypothetical. Second concern is to propose a new worker type. Pushback is that introducing a new worker type is a lot of work. I don't know what to do. Presented with a bunch of bad solutions, and compare what's least bad.

Martin: Sympathize. Worker scope seems like a lot of work, but commensurate to the cost to end users? Maybe spec writer cost should match the cost to users. If pages can run things in the background after page close, that's a big ask.

Xiaocheng: How strong is this point? I'm leaning to closing as satisfied-with-concerns.

Martin: Do we have a more passive-aggressive one? "ambivalent"?

Jeffrey: I'd be fine with satisfied-with-concerns. [other discussion]

Martin: ambivalent. If we think workers are the right thing, it should be cheaper to create a new kind of worker. New kinds of worklets are easy. New kinds of workers should be too.

Xiaocheng: It's between worklet and worker, although they gave arguments against worklets. API design is hiding the cost of doing this.

Martin: Is there a version of this that works without creating a SharedWorker in a particular mode? e.g. if the page closes and it has outstanding work, through whatever system indicates to the browser that the outstanding work exists, it can keep the worker alive?

Jeffrey: That's what I suggested, and they said that's hard to implement. I don't totally buy it, but I also don't know the implementation well enough to argue this.

Martin: When you register 'pagehide' or "I'm going to run a cleanup task", you've registered an intention to clean up after yourself on the other end. So have registering those events enable extended-lifetime on all future shared workers, or on a particular shared worker url. Register something with the browser that you want to do a cleanup. That action associates state with the new shared worker. Can mutate that over the page's lifetime. Then when the page ends, that state is passed to the shared worker.

Jeffrey: Kinda like that design better. Maybe we should suggest they consider it as an alternative.

Martin: [more discussion]

Jeffrey: Ah, the "I'm going to pass state" flag might turn out to be the same as the proposed "extended lifetime" flag.

Martin + Xiaocheng to continue iterating.

Discussed Jun 30, 2025 (See Github)

Skipping.

Discussed Jul 7, 2025 (See Github)

Xiaocheng: Didn't have time to look at this. I'm aware that Martin and Jeffrey proposed another alternative.

Martin: Might be very close to what they're proposing, may not actually be an alternative.

Xiaocheng: Fine if we post a response with the alternative. Another question: For this review we have been proposing a lot of alternatives. Looks like we're showing a strong opinion that we don't like the approach.

Martin: Don't really agree. Questions: What do you do at creation time?

… uses an extendable Event to keep the worker live, just like SW?

… Difference is extension by default in their proposal and not in the alternative …

Lola: Worth asking if they've considered a secondary API?

Martin: Think we can just ask the question if they've considered that. We should respect their position here: they are the experts in this area.

Xiaocheng: Still have a doubt about proposing yet another alternative.

Lola: Think it's fine to ask for alternatives. If they haven't documented that alternative, it's not wrong to ask.

Xiaocheng: Need to think about it, think there should be a boundary between thinking about alternatives and finetuning the API shape.

Lola: Can Xiaocheng and Martin discuss this offline?

Martin: Yes. I'll defer to Xiaocheng on this one. We should not engage in an unhealthy interaction mode.

Discussed Jul 21, 2025 (See Github)

Xiaocheng: Wanted to post a comment, but didn't do it yet. Will have a look.

Lola: Do you want someone else to look at this review or are you fine?

Xiaocheng: Unless someone else wants to join?

Discussed Jul 28, 2025 (See Github)

Xiaocheng: Discussed a while ago. We should say their proposed solution is ok for the scope of the problem. Should we raise other concerns like it not being idiomatic?

Jeffrey: Think that would be fine.

Xiaocheng: Scope isn't so big, so don't want to introduce a big mechanism for it. But at the same time, but when proposing a smaller solution. Neither their proposal nor the alternatives are idiomatic enough. They look like variants of each other. Don't feel good about continuing to suggest alternatives.

Jeffrey: It's possible for us to say that the solutions are too complicated for the amount of benefit, so they just shouldn't solve the problem for now.

Jeffrey: See also https://github.com/whatwg/html/issues/10997#issuecomment-3105509844, with 2 pre-existing problems that extended lifetime makes worse. And for both, the ExtendableEvent design would solve the problems.

Xiaocheng: Not really about how we enable lifetime extension, but there are a whole bunch of issues that get worse if we enable lifetime extension at all.

Jeffrey: There's a difference in how much lifetime extension you get if you need any. N minutes vs exactly as much as you ask for.

Xiaocheng: They say you have to declare that you need lifetime extension before you use it. I'll draft a comment about being unsure that the benefits outweigh the risks of making other issues worse.

Marcos: Theory that it's time-based is not a great thing. From experience, these things should be tied to tasks. Haven't looked so much, just hearing that it doesn't sound good.

Xiaocheng to draft a comment.

Discussed Aug 4, 2025 (See Github)

Skipped, wait for Xiaocheng.

Discussed Aug 11, 2025 (See Github)

Lola: Last comment was June 19. I think we're waiting for them.

Matthew: They're waiting for us, Domenic replied. Can anyone take this on?

Hadley: There's a comment from Xiaocheng reaady to post, Jeffrey agreed. Let's see if we're OK with it and then post. We need someone to pull up the other alternatives we discussed.

Lola: This seems low-lift, does anyone have bandwidth?

Matthew: Where are the other discussed alternatives?

Hadley: Should be in the conversation

Matthew: Jeffrey and I had two examples

Lola: I can take this; will find the 2 alternatives, and post the existing comment

Discussed Aug 18, 2025 (See Github)

Lola: Jeffrey mentioned updating Xiaocheng's comment including the previous examples and suggested alternative approaches that haven't been mentioned to the group.

… Couldn't see where those alternatives are. Everything I've read has already been suggested to the group. @Martin: Any insights?

Martin: No. Do you need help?

Lola: Last time, we wanted to take Jeffrey's suggestions for the comment into Xiaocheng's draft comment. If we do want to be clearer about the several other alternatives we've discussed, maybe it makes sense to flesh those out. But they aren't written down anywhere. Not in minutes, brainstorm, …

Martin: There are some in the brainstorm?

Lola: The ones in the brainstorm have been suggested to the proponents before. Xiaocheng has been in conversations with them since May.

… Was wondering if one of the things he was taking about was regarding the code that was reviewed.

… Guess I need some help to find out which one of them haven't been suggested.

Martin: Don't see any evidence…

Lola: In the main thread, not the brainstorming, Xiaocheng first suggests making Shared Workers more accessible and introducing a new global scope (?), suggestes lifetime: true to every shared worker or new worker that is designed for such a use case.

Martin: Don't think there is any discussion about the use of the extended lifetime.

Lola: Should I re-suggest that?

… Jeffrey said they should consider a Lifetime Promise API via internal channels.

… Assuming what Jeffrey has proposed built up on what you proposed, Martin?

Martin: Yes.

Lola: Go with Jeffrey's suggestion?

Martin: Yes.

Comment by @lolaodelola Aug 26, 2025 (See Github)

Hi @domenic, we have decided to mark this proposal as unsatisfied. The TAG discussed this proposal and are unsure whether the benefits outweigh the risks of making other issues worse (for example unnecessarily long lifetimes, from @annevk, and WebLock-based hangs from @fergald). We are also concerned that we did not see an idiomatic solution with a complexity matching the scope of the use case ( without introducing a whole new complicated mechanism). The current proposal starts the SharedWorker when it’s ready to do some work, however, we feel there’s a need for some pre-mediation work to set up the worker without the unbounded shape:

// Create a transferable item that encapsulates the state that needs to be cleaned up
// if the page closes before the regular process has a chance to do that work.
let item = { ... }; 
// Could be `using` when https://github.com/tc39/proposal-explicit-resource-management is ready.
let h = await SharedWorker.runCleanupOnExit(url, item);

// ...Later
// If the work completes successfully.
h.cancel();

The process to register intent to extend should be tied to activity on-page that precedes the closing of the window/tab. The item is dispatched as an extendable event to the SharedWorker instance when the page exits, and the associated promise should have enough time to resolve between user actions, except in the case where the closing of the window is the action that needs to be saved.

Thanks for having us discuss this issue! We are closing it for now, but feel free to reopen it if you have updates to the proposal.

Comment by @domenic Aug 26, 2025 (See Github)

we feel there’s a need for some pre-mediation work to set up the worker without the unbounded shape:

This proposed shape seems the same as the current proposal. Note that in the current proposal also you can start the SharedWorker ahead of time, and destroy it ahead of time, and send messages to it ahead of time.

In particular, your proposal does not have any mitigations that address the things you pointed out, of unnecessarily long lifetimes and WebLock-based hangs.

I've updated the explainer to mention your alternative API shape at https://gist.github.com/domenic/c5bd38339f33b49120ae11b3b4af5b9b#the-same-api-with-a-different-shape .

Comment by @jyasskin Aug 27, 2025 (See Github)

Hi @domenic. Sorry that we were unclear in describing the idea. We wanted to try to address the unnecessarily long lifetimes and WebLock-based hangs by ensuring that the SharedWorker only stays alive as long as the developer is actively using it, instead of the fixed lifetime caused by the extendedLifetime: true flag. The extendLifetimeUntil() function based on ExtendableEvents seems to accomplish that, but, as @asutherland pointed out, implementations need to know if it's going to be called before the SharedWorker starts.

SharedWorker.runCleanupOnExit(url, item) is meant to do this. It should not start the SharedWorker when it's called, as that would waste memory between that time and the time that the page closes. Instead, it would serialize item and instruct the browser that an extendable SharedWorker needs to be started whenever the page closes.

Does that make more sense?

This doesn't address the case of a page that needs the same SharedWorker both during its lifetime and to do cleanup, and it doesn't help if the primary or only use for this is to be called from a pagehide event handler.... I haven't checked this with the rest of the TAG, but I think we'd get the same benefits if new SharedWorker(url, { extendedLifetime: true });, instead of always extending the lifetime for 30 seconds (depending on the browser), just enabled the extendLifetimeUntil() global function.

Comment by @domenic Aug 28, 2025 (See Github)

I think it's making a bit more sense, but it still seems like it's syntactic sugar for the existing proposal. That is, you can reproduce what you're proposing in the following manner:

// In the main document
const controller = new AbortController();

document.addEventListener("pagehide", () => {
  const worker = new SharedWorker(url, { extendedLifetime: true });
  worker.postMessage({ runCleanupOrExitUsingThisData: item });
}, { signal: controller.signal });

// If later we decide we don't need to run cleanup or exit:
controller.abort();
// In the worker:
self.addEventListener("message", async e => {
  const data = e.data.runCleanupOrExitUsingThisData;
  try {
    await runCleanupOrExit(e);
  } finally {
    self.close();
  }
});

From what I can tell, the differences between the above and SharedWorker.runCleanupOnExit() are:

  • SharedWorker.runCleanupOnExit() serializes (but does not deserialize) the data ahead of time. This uses the user's CPU time earlier, which might be good if it shifts work away from the busy time of unloading the document, but might be bad if we later decide we don't need to run cleanup or exit.
  • SharedWorker.runCleanupOnExit() uses ExtendableEvents so that it automatically closes the shared worker, instead of relying on finally { self.close(); }
    • However, the browser can clean up the shared worker (even the extended lifetime shared worker) automatically if it is not performing any async work, so, I'm not sure this is a real advantage. That is, neither the ExtendableEvent complexity nor the finally { self.close(); } code are really necessary.

Are there other differences I might be missing?

Comment by @martinthomson Aug 28, 2025 (See Github)

That matches my understanding, mostly.

  • SharedWorker.runCleanupOnExit() doesn't instantiate the worker until shutdown happens. So while it serializes the argument, it doesn't involve a worker that is active until cleanup runs. The difference in work is therefore Shared Worker instantiation vs. serialization. So the busy closing period might even have to do more work if instantiating a worker is a heavier lift.
  • The use of ExtendableEvent avoids inventing a new paradigm. (Your point about neither being a guarantee is already understood in that context, so maybe it saves you having to say "this applies here too" quite as often.)
Comment by @domenic Aug 28, 2025 (See Github)

Got it, thanks! Just to confirm:

  • SharedWorker.runCleanupOnExit() doesn't instantiate the worker until shutdown happens.

Right, just like my example with the pagehide handler.

  • The use of ExtendableEvent avoids inventing a new paradigm.

Right, and try/finally is also not a new paradigm.

Comment by @martinthomson Aug 28, 2025 (See Github)

try/finally is also not a new paradigm.

The new paradigm I was referring to is the extension of the lifetime of a worker.

Edit to note: And ExtendableEvent picks up a different default. Unless explicitly acted upon, it will not extend the lifetime of the worker. I think that's valuable, even if browsers do need to have a backstop.

Comment by @domenic Aug 28, 2025 (See Github)

Edit to note: And ExtendableEvent picks up a different default. Unless explicitly acted upon, it will not extend the lifetime of the worker. I think that's valuable, even if browsers do need to have a backstop.

I don't think the default is actually different, because in both cases we're discussing, the web developer opts-in to add code which extends the lifetime of the worker.

With the proposal as-is, that code is:

document.addEventListener("pagehide", () => {
  const worker = new SharedWorker(url, { extendedLifetime: true });
  worker.postMessage({ runCleanupOrExitUsingThisData: item });
});

With the TAG proposal, that code is:

await SharedWorker.runCleanupOnExit(url, item);

I can appreciate that the latter is a bit nicer to type, but I don't think it changes the properties of the proposal in terms of default behavior. In both cases, the default behavior (with no code added) is to not start the worker at all.

Comment by @martinthomson Aug 28, 2025 (See Github)

I was talking about the code in the worker.

Assuming either setup, setting aside the spelling of the stuff on the main thread, the code in the worker varies. A worker that fails to extend an event lifetime can be cleaned up immediately. One that fails to close sticks around until the browser gets sick of waiting or recognizes that nothing can run again. That is, the default is to have the worker cleaned up.

Comment by @jyasskin Aug 28, 2025 (See Github)

Personal, non-TAG opinion: SharedWorker.runCleanupOnExit() is nicer to type, but only if that's exactly the behavior the site wants. As y'all explored, it also changes the time that various work happens, and page-exit==navigation might be exactly the wrong time to do the work of spinning up a worker. So I now think that the original proposal is likely to be the right design to start with on the startup end. A function to start-on-exit seems like something that can be added later if developers need it.

On the exit end, I'd forgotten or never knew that there's a sharedWorkerGlobal.close() function, so the disagreement here is completely about what the default should be. A conscientious developer will write,

port.onmessage = async (e) => {
  try {
      const { cryptoKey, analyticsData } = e.data;

      const encryptedData = await crypto.subtle.encrypt(
        { name: "AES-GCM", iv: new Uint8Array(12) }, 
        cryptoKey, 
        analyticsData
      );
      await fetch("/send-analytics", { method: "POST", body: encryptedData, keepalive: true });
  } finally {
    self.close();
  }
};

And get their SharedWorker shut down promptly. A forgetful developer will skip the self.close(), and quietly use ~30s of extra resources ... unless the browser notices that their script has no outstanding Promises or inbound open message pipes.

With a design based on ExtendableEvent, the forgetful developer might start by forgetting to extend their event, which will lead to occasional missed reports. Presumably they're watching for this, or they wouldn't have made an extendedLifetime SharedWorker in the first place. Then the next step is to extend the event as they want, and there's no simple way to accidentally ask for the whole 30s.

So ... I think I still lean toward the ExtendableEvent style over letting people call self.close(), even though it requires a new function that throws if called from the wrong kind of SharedWorker ... unless there's another angle that I'm still missing.

Comment by @domenic Aug 29, 2025 (See Github)

I think the angle that might be missed is that

unless the browser notices that their script has no outstanding Promises or inbound open message pipes.

is very likely to happen quickly and automatically, since it ties in with existing mechanisms browsers implement for shutting down idle workers for which no references exist.

Comment by @asutherland Aug 29, 2025 (See Github)

From an implementation perspective (for Gecko), runCleanupOnExit introduces scary new platform responsibilities and complexities that I don't think we want to sign up for, especially since it entails 1-2 orders of magnitude more work than the proposal in https://gist.github.com/domenic/c5bd38339f33b49120ae11b3b4af5b9b.

Specifically:

  • runCleanupOnExit implies a browser obligation to reliably run something when a global goes away. Whereas I view the proposal at https://gist.github.com/domenic/c5bd38339f33b49120ae11b3b4af5b9b as more paving a cowpath for a capability we can't take back and where it's probably better for everyone involved if people aren't using a ServiceWorker in that case.
  • The eager delivery of postMessage is straightforward and already implemented. runCleanupOnExit implies a whole bunch of new edge-cases and implementation complexities unless we explicitly use the existing structured serialization forStorage flag and define things in terms of actually being stored and charged against quota.

I do want to emphasize that I appreciate the thought and care that went into the runCleanupOnExit proposal and attempts to address the existing structural problems of SharedWorker. It would be definitely interesting to revisit the fundamental idea as an entirely new and separate proposal like a BatchWorker where the "forStorage" aspects could potentially be advantageous. But I do want to emphasize that would still be similarly a major implementation undertaking.

Comment by @martinthomson Aug 29, 2025 (See Github)

That's fair; I'm willing to take your word that it's more expensive to implement this. I would like to understand it though. Domenic seemed to claim that the proposal would be nothing more than syntactic sugar over the proposal; now I'm hearing that it would be vastly more expensive to implement. I can't reconcile those two statements.

FWIW, I wouldn't consider this an obligation to run any more than "pagehide" event delivery is reliable. In that sense, implementing the proposal as something like a sugared version of the original proposal seems viable in that light.

Comment by @asutherland Aug 29, 2025 (See Github)

I understood Domenic to be concisely expressing that the runCleanupOnExit could be implemented in terms of existing primitives with the minimal changes in https://gist.github.com/domenic/c5bd38339f33b49120ae11b3b4af5b9b without introducing more expansive new primitives, not suggesting the proposals were isomorphic.

I do agree that if the implementation was a self-hosted function that literally expanded into Domenic's example code that it would not be that hard to implement, but I have significant problems with what the name promises because...

FWIW, I wouldn't consider this an obligation to run any more than "pagehide" event delivery is reliable.

This doesn't stop people from filing bugs about it and their use cases. I very much do not want to sign up for an ongoing series of fresh debates in bugzilla and on github where I am arguing that the API called "run cleanup on exit" does not and should not reliably run cleanup on exit. In particular, it feels like the people arguing that it should would have a very good point. (I also expect a lot of people would be surprised that they can't put a function in and have it run in the current global, but structured cloning at least would throw immediately instead of coercing silently into nothing.)