DEV Community

Anthony Frehner
Anthony Frehner

Posted on • Edited on

The 'discarded' page lifecycle state

Background

Browsers don't like the reputation of being resource hogs, even if you have 100+ tabs open. To help reduce CPU + memory demands while still allowing you to keep those tabs around, browsers came up with the Page Lifecycle - a way to reduce resource usage while still providing a good user experience.

This article does a great job of further explaining the different states and what happens in them, as well as APIs you can use to check which state your page was or is in.

Discarded State

Let's focus on the discarded state. The article above defines that state as

A page is in the discarded state when it is unloaded by the browser in order to conserve resources. No tasks, event callbacks, or JavaScript of any kind can run in this state, as discards typically occur under resource constraints, where starting new processes is impossible.

In the discarded state the tab itself (including the tab title and favicon ) is usually visible to the user even though the page is gone.

Or in other words - the page hasn't been used for awhile, and the browser wants to free up resources, so it's going to "close the page" while still showing the tab for the page. That's pretty cool and useful.

However, what isn't detailed in that article (at the time of writing) is: what exactly happens when the page is loaded up again?

The answer (again, for the moment) appears to be: it's up to each browser to determine that. (To be clear, there's nothing wrong with that!)

What actually happens

From my testing, let me tell you what appears to happens in Chrome - and how it caused a bug in my application:

  1. The page is loaded
  2. After some time, Chrome determines that it needs to go into the discarded state
  3. Chrome appears to cache the original HTML of the page that you're on
  4. The page goes into discarded state and all resources (CPU, memory) are removed for that tab
  5. Now you open that tab/page again, and Chrome needs it to be in the active state
  6. It pulls the cached HTML and re-parses + re-executes it - (which means that it refetches any CSS or Javascript referenced by the HTML)
  7. The page is now loaded

The problem I ran into lies in that second-to-last step: it is using a cached version of the HTML but downloading a new version of the JS.

Is that a problem?

But why would that cause a problem?

Well, in any case where you need a change to happen in both your HTML and your JS in order to work, and there's some place where you don't have versioned assets.

In my case, it's because of how the infrastructure is set up:

  • We're using import-maps with SystemJS
  • I have one import-map that is not versioned and is not cached; the JS assets it links to are though
  • I have another import-map that is inlined into the HTML page; this import-map is for things like shared dependencies that don't change very often (e.g. react, react-dom, etc.)

So in this situation, I had put a new shared library into the inlined import-map, and also updated all the JS assets to look for the shared version of that library instead of the bundled one. However, for those people that had a cached version of the HTML (for example, the people that had their page go into the discarded state), they never downloaded the new HTML but did download the new JS - and that broke the page!

A solution

Fortunately, the solution for users is simple enough - just a page refresh will cause them to download the latest HTML and all is good again.

For me, it also means that I can handle the situation where the app is being started from the discarded state by checking document.wasDiscarded, and if that's true, then force a full-page refresh which should resolve the bug before it happens.

if(document.wasDiscarded) {
  window.location.reload(true);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)