When service workers met framesets
Oh boy, do I have some obscure browser behaviour for you!
To set the scene…
I’ve been writing here in my online journal for almost twenty years. The official anniversary will be on September 30th. But this website has been even online longer than that, just in a very different form.
Here’s the first version of adactio.com.
Like a tour guide taking you around the ruins of some lost ancient civilisation, let me point out some interesting features:
- Observe the
.shtml
file extension. That means it was once using Apache’s server-side includes, a simple way of repeating chunks of markup across pages. Scientists have been trying to reproduce the wisdom of the ancients using modern technology ever since. - See how the layout is
100vw
and100vh
? Well, this was long before viewport units existed. In fact there is no CSS at all on that page. It’s one bigtable
element with 100% width and 100% height. - So if there’s no CSS, where is the
border-radius
coming from? Let me introduce you to an old friend—the non-animated GIF. It’s got just enough transparency (though not proper alpha transparency) to fake rounded corners between two solid colours. - The management takes no responsibility for any trauma that might befall you if you view source. There you will uncover JavaScript from the dawn of time; ancient runic writing like
if (navigator.appName == "Netscape")
Now if your constitution was able to withstand that, brace yourself for what happens when you click on either of the two links, deutsch or english.
You find yourself inside a frameset. You may also experience some disorienting “DHTML”—the marketing term given to any combination of JavaScript and positioning in the late ’90s.
Note that these are not iframes, they are frames. Different thing. You could create single page apps long before Ajax was a twinkle in Jesse James Garrett’s eye.
If you view source, you’ll see a React-like component system. Each frameset
component contains frame
components that are isolated from one another. They’re like web components. Each frame has its own (non-shadow) DOM. That’s because each frame is actually a separate web page. If you right-click on any of the frames, your browser should give the option to view the framed document in its own tab or window.
Now for the part where modern and ancient technologies collide…
If you’re looking at the frameset URL in Firefox or Safari, everything displays as it should in all its ancient glory. But if you’re looking in Google Chrome and you’ve visited adactio.com before, something very odd happens.
Each frame of the frameset displays my custom offline page. The only way that could be served up is through my service worker script. You can verify this by opening the framest URL in an incognito window—everything works fine when no service worker has been registered.
I have no idea why this is happening. My service worker logic is saying “if there’s a request for a web page, try fetching it from the network, otherwise look in the cache, otherwise show an offline page.” But if those page requests are initiated by a frame
element, it goes straight to showing the offline page.
Is this a bug? Or perhaps this is the correct behaviour for some security reason? I have no idea.
I wonder if anyone has ever come across this before. It’s a very strange combination of factors:
- a domain served over HTTPS,
- that registers a service worker,
- but also uses framesets and frames.
I could submit a bug report about this but I fear I would be laughed out of the bug tracker.
Still …the World Wide Web is remarkable for its backward compatibility. This behaviour is unusual because browser makers are at pains to support existing content and never break the web.
Technically a modern website (one that registers a service worker) shouldn’t be using deprecated technology like frames. But browsers still need to be able support those old technologies in order to render old websites.
This situation has only arisen because the same domain—adactio.com—is host to a modern website and a really old one.
Maybe Chrome is behaving strangely because I’ve built my online home on ancient burial ground.
Update: Both Remy and Jake did some debugging and found the issue…
It’s all to do with navigation preloads and the value of event.preloadResponse
, which I believe is only supported in Chrome which would explain the differences between browsers.
According to this post by Jake:
event.preloadResponse is a promise that resolves with a response, if:
- Navigation preload is enabled.
- The request is a GET request.
- The request is a navigation request (which browsers generate when they’re loading pages, including iframes).
Otherwise
event.preloadResponse
is still there, but it resolves withundefined
.
Notice that iframes are mentioned, but not frames.
My code was assuming that if event.preloadRepsonse
exists in my block of code for responding to page requests, then there’d be a response. But if the request was initiated from a frameset, it is a request for a page and event.preloadRepsonse
does exist …but it’s undefined.
I’ve updated my code now to check this assumption (and fall back to fetch
).
This may technically still be a bug though. Shouldn’t a page loaded from a frameset count as a navigation request?