Surfin' Safari

WebKit Page Cache I – The Basics

Posted by Brady Eidson on Wednesday, September 16th, 2009 at 4:47 pm

This is the first of two posts that will center around a modern browser engine feature that doesn’t usually get a lot of press: The Page Cache.

Today I’ll talk a bit about what this feature is, why it often doesn’t work, and what plans we have to improve it.

Page Cache Overview

Some of you might be more familiar with what other browsers call their Page Cache. Firefox calls theirs the “Back-Forward Cache” or “bfcache.” Opera refers to theirs as “Fast History Navigation.” We’ve recently started to refer to WebKit’s implementation as the “Page Cache” to reduce confusion with our “Back/Forward List.”

Note that the Page Cache is an end user feature that makes navigating the web much smoother. It is not a “cache” in the “HTTP sense“. It is not a “cache” in the “disk cache” sense where raw resources are stored on the local disk. And it’s not a “cache” in the traditional “memory cache” sense where WebKit keeps decoded resources around in memory to be shared between multiple web pages.

So… what *exactly* is it?

Quite simply, the Page Cache makes it so when you leave a page we “pause” it and when you come back we press “play.”

When a user clicks a link to navigate to a new page the previous page is often thrown out completely. The DOM is destroyed, Javascript objects are garbage collected, plug-ins are torn down, decoded image data is thrown out, and all sorts of other cleanup occurs.

When this happens and the user later clicks the back button it can be painful for them. WebKit may have to re-download the resources over the network, re-parse the main HTML file, re-run the scripts that dynamically setup the page, re-decode image data, re-layout the page, re-scroll to the right position, and re-paint the screen. All of this work requires time, CPU usage, and battery power.

Ideally the previous page can instead be placed in the Page Cache. The entire live page is kept in memory even though it is not on screen. This means that all the different bits and pieces that represent what you see on the screen and how you interact with it are suspended instead of destroyed. They can then be revived later in case you click the back button.

Why is This Important?

When the Page Cache works it makes clicking the back button almost instantaneous.

You can do a search, click a search result, then go back and immediately be looking at the exact same results page. You might be browsing an aggregator site like Reddit or Digg and want to rapidly view a lot of different links in the same tab. You might be navigating an image gallery and decide to compare two images by alternately clicking “back” and “forward” rapidly. Or you might have simply clicked on the wrong link and want to go back to correct your mistake.

Anytime you might click the back button or the forward button you unknowingly hope the Page Cache is on your side. When the Page Cache is used, users are happy even though they’re not aware of the magic behind the scenes.

Conversely, when the Page Cache is bypassed, users often get frustrated with both the browser and the Web in general.

Why Wouldn’t it Work?

So if the Page Cache is so amazing, why doesn’t WebKit always use it when you navigate to a new page?

There’s a few main answers to that question.

Some Pages aren’t Interesting

First off, sometimes it doesn’t make sense to cache a page because it’s not interesting to return to in the exact same state. For example, the page might not even be finished loading yet. Or the page might’ve had an error loading. Or maybe the page was a redirection page that exists solely to automatically move the user to some new URL.

These are cases where we’re happy with the current Page Cache behavior in WebKit.

Some Pages are Complicated

Secondly, a page might not be considered for the Page Cache because it’s difficult to figure out how to “pause” it. This happens with more complex pages that do interesting things.

For example, plug-ins contain native code that can do just about anything it wants so WebKit can’t “hit the pause button” on them. Another example is pages with multiple frames which WebKit has historically not cached.

Distressingly, navigating around these more advanced pages would benefit the most from the Page Cache.

Some Pages are Secure

Server administrators for HTTPS sites often have particular security concerns and are very sensitive with regards to how browsers behave. For example, Financial institutions are often very thorough in verifying each particular browser’s behavior before allowing it to be used by their customers.

One area often focused on is back/forward behavior. Such institutions are – understandably – very picky about the types of data left behind in the browser as a user navigates. As a result, in an effort to err on the side of extreme caution, WebKit has disallowed all HTTPS sites from its Page Cache since the very beginning.

A more fine grained approach might go a long way towards improving the user experience.

Planned Improvements

Clearly there’s some important cases we don’t handle and therefore plenty of room for improvement.

WebKit’s Page Cache was originally written in 2002 before the very first Safari beta release. Its capabilities reflected both the architecture of WebKit at the time and the landscape of the Web in 2002.

The Web of 2009 is a much different place and we need to bring the Page Cache up to par. Fortunately this work is well underway.

For example, as of revision 48036 a major limitation was resolved and pages with frames are now placed in the Page Cache. Browsing with the latest WebKit nightly always seems to “feel faster” in ways you can’t quite put your finger on, and recently some of you might have been experiencing this enhancement.

But there’s plenty more work to do.

Plug-ins are the next huge one on our hit list. As I mentioned earlier, plug-ins can run whatever native code they like so we can’t reliably hit the “pause” button on them.

Earlier versions of WebKit handled single-frame pages with some types of plug-ins. WebKit would tear down the plug-in when leaving the page and restoring it when the user returned. But as work continued on WebCore to make it faster and easier to port, this ability was lost.

Bug #13634 tracks getting this working again for all plug-ins on all pages.

Then there are HTTPS pages. We completely ban them now, but a more selective approach should be able to benefit users as well as keep security-minded institutions happy.

Bug #26777 tracks allowing HTTPS pages to be cached unless their response headers include “cache-control: no-store” or “cache-control: no-cache” which has become the canonical way for a selective organization to secure your content.

If you have any other ideas for what else might be improved, please feel free to comment in the appropriate bug or file a new bug of your own!

Unload Handlers

One thing I haven’t mentioned is pages with unload event handlers.

The unload event was designed to let a page do some cleanup work when the user closes the page.

The browser can’t fire the unload event before it puts the page in the Page Cache, because the page then assumes it is in a terminal state and might destroy critical parts of itself. This completely defeats the purpose of the Page Cache.

But if the browser puts the page in the Page Cache without running the unload handler, then the page might be destroyed by the browser while it is “paused” and hidden, and that cleanup work – which might be very important – will never happen.

Since the unload event’s purpose is to allow “important work when a page is closed,” all major browsers refuse to put such pages in their Page Cache, causing a direct negative impact on the user experience.

In a future post I’ll be talking more about unload event handlers and there will actually be homework for many of you web developers out there! Stay tuned…

8 Responses to “WebKit Page Cache I – The Basics”

  1. Miles Says:

    Spoilers: https://bugs.webkit.org/show_bug.cgi?id=29021 ;)

  2. CyberSkull Says:

    Hmm, perhaps what we need is something like a cache handler for JS and plugins; Ideally to do whatever cleanup or state changes for going into cache and coming out of it…

  3. Brady Eidson Says:

    CyberSkull, you’re exactly right and I strongly recommend you keep your eyes out for part 2.

  4. jomohke Says:

    It’s fantastic to see the return of technical articles detailing the innards of webkit. They stopped appearing when this blog moved from just David Hyatt to an official team blog. Please give us more!

  5. Adrian Russell-Falla Says:

    Brady, I fervently second jomohke’s request!
    looking fwd to Part 2.

  6. kangax Says:

    Unfortunately, in Prototype.js we always attach an “unload” handler in WebKit. This is due to a bug in some of the earlier versions of WebKit, where returning to a page via “page cache” would wipe-off all of the expandos on host objects (e.g. from the `document`). Since Prototype.js extends these host objects with custom methods, there was chaos all over the place : )

    See this ticket for more info –

    What’s even more disappointing is that we use browser sniffing as it seems impossible to feature test this quirk. I suppose disabling unload in newer WebKit wouldn’t affect Prototype, as long as document state persists properly (and it does in newer versions).

    I would also love to hear if there’s a saner work around for this issue (i.e. any kind of inference we can perform, to replace fragile `userAgent` parsing).

  7. kangax Says:

    Link got stripped, here it is again – http://dev.rubyonrails.org/ticket/11430

  8. nhoel Says:

    @kangax THANKS FOR THAT LINK

    @adrian I THIRD DEMOTION

    thanks,
    -Nhoel of http://keywordspeak.com/?p=1234