about:home startup cache¶
By default, a user’s browser session starts with a single window and a single tab, pointed at about:home. This means that it’s important to ensure that
about:home loads as quickly as possible to provide a fast overall startup experience.
about:home, which is functionally identical to
about:newtab, is generated dynamically by calculating an appropriate state object in the parent process, and passing it down to a content process into the React library in order to render the final interactive page. This is problematic during the startup sequence, as calculating that initial state can be computationally expensive, and requires multiple reads from the disk.
about:home startup cache is an attempt to address this expense. It works by assuming that between browser sessions,
about:home usually doesn’t need to change.
Components of the
about:home startup cache mechanism¶
There are 3 primary components to the cache mechanism:
The HTTP Cache¶
The HTTP cache is normally used for caching webpages retrieved over the network, but seemed like the right fit for storage of the
about:home cache as well.
The HTTP cache is usually queried by the networking stack when browsing the web. The HTTP cache is, however, not typically queried when accessing
resource:// URLs, so we have to do it ourselves, manually for the
about:home case. This means giving
about:home special capabilities for populating and reading from the HTTP cache. In order to avoid potential security issues, this requires that we sequester
about:newtab in their own special content process. The “privileged about content process” exists for this purpose, and is also used for
The HTTP cache lives in the parent process, and so any read and write operations need to be initiated in the parent process. Thankfully, however, the HTTP cache accepts data using
nsIOutputStream and serves it using
nsIInputStream. We can send
nsIInputStream over the message manager, and convert an
nsIInputStream into an
nsIOutputStream, so we have everything we need to efficiently communicate with the “privileged about content process” to save and retrieve page data.
The official documentation for the HTTP cache can be found here.
This singleton component lives inside of
BrowserGlue to avoid having to load yet another JSM out of the
omni.ja file in the parent process during startup.
AboutHomeStartupCache is responsible for feeding the “privileged about content process” with the
nsIInputStream’s that it needs to present the initial
about:home document. It is also responsible for populating the cache with updated versions of
about:home that are sent by the “privileged about content process”.
Since accessing the HTTP cache is asynchronous, there is an opportunity for a race, where the cache can either be accessed and available before the initial
about:home is requested, or after. To accommodate for both cases, the
nsIPipe instances, which it sends down to the “privileged about content process” as soon as one launches.
If the HTTP cache entry is already available when the process launches, and cached data is available, we connect the cache to the
nsIPipe’s to stream the data down to the “privileged about content process”.
If the HTTP cache is not yet available, we hold references to those
nsIPipe instances, and wait until the cache entry is available. Only then do we connect the cache entry to the
nsIPipe instances to send the data down to the “privileged about content process”.
AboutNewTabService is used by the
AboutRedirector in both the parent and content processes to determine how to handle attempts to load
There are distinct versions of the
AboutNewTabService - one for the parent process (
BaseAboutNewTabService), and one for content processes (
AboutNewTabChildService, which inherits from
AboutRedirector, when running inside of a “privileged about content process” knows to direct attempts to load
aboutHomeCacheChannel method. This method is then responsible for choosing whether or not to return an
nsIChannel for the cached document, or for the dynamically generated version of
This singleton component lives inside of the “privileged about content process”, and is initialized as soon as the message is received from the parent that includes the
nsIInputStream’s that will be used to potentially load from the cache.
AboutRedirector in the “privileged about content process” notices that a request has been made to
about:home, it asks
nsIAboutNewTabService to return a new
nsIChannel for that document. The
AboutNewTabChildService then checks to see if the
AboutHomeStartupCacheChild can return an
nsIChannel for any cached content.
If, at this point, nothing has been streamed from the parent, we fall back to loading the dynamic
about:home document. This might occur if the cache doesn’t exist yet, or if we were too slow to pull it off of the disk. Subsequent attempts to load
about:home will bypass the cache and load the dynamic document instead. This is true even if the privileged about content process crashes and a new one is created.
AboutHomeStartupCacheChild will also be responsible for generating the cache periodically. Periodically, the
AboutNewTabService will send down the most up-to-date state for
about:home from the parent process, and then the
AboutHomeStartupCacheChild will generate document markup using ReactDOMServer within a
ChromeWorker. After that’s generated, the “privileged about content process” will send up
nsIInputStream instances for both the markup and the script for the initial page state. The
AboutHomeStartupCache singleton inside of
BrowserGlue is responsible for receiving those
nsIInputStream’s and persisting them in the HTTP cache for the next start.
What is cached?¶
Two things are cached:
The raw HTML mark-up of
about:home, which does not allow inline scripting. Instead, we load a script from
about:home?jscache. This goes through the same mechanism for retrieving the HTML document from the cache, but instead pulls down the cached script.
If the HTML mark-up is cached, then we presume that the script is also cached. We cannot cache one and not the other. If only one cache exists, or only one has been sent down to the “privileged about content process” by the time the
about:home document is requested, then we fallback to loading the dynamic
Refreshing the cache¶
The cache is refreshed periodically by having
AboutHomeStartupCache when it has sent any messages down to the preloaded
about:newtab. In general, such messages are a good hint that something visual has updated for the next
about:newtab, and that the cache should probably be refreshed.
AboutHomeStartupCache debounces notifications about such messages, since they tend to be bursty.
Invalidating the cache¶
It’s possible that the composition or layout of
about:home will change over time from release to release. When this occurs, it might be desirable to invalidate any pre-existing cache that might exist for a user, so that they don’t see an outdated
about:home on startup.
To do this, we set a version number on the cache entry, and ensure that the version number is equal to our expectations on startup. If the version number does not match our expectation, then the cache is discarded and the
about:home document will be rendered dynamically.
The version number is currently set to the application build ID. This means that when the application updates, the cache is invalidated on the first restart after a browser update is applied.
about:home is typically the first thing that the user sees upon starting the browser. It is critically important that it function quickly and correctly. If anything happens to go wrong when retrieving or saving to the cache, we should fall back to generating the document dynamically.
As an example, it’s theoretically possible for the browser to crash while in the midst of saving to the cache. In that case, we might have a partial document saved, or a partial script saved - neither of which is acceptable.
Thankfully, the HTTP cache was designed with resilience in mind, so partially written entries are automatically discarded, which allows us to fall back to the dynamic page generation mode.
As additional redundancy to that resilience, we also make sure to create a new nsICacheEntry every time the cache is populated, and write the version metadata as the last step. Since the version metadata is written last, we know that if it’s missing when we try to load the cache that the writing of the page and the script did not complete, and that we should fall back to dynamically rendering the page.