Skip to content

Conversation

wbamberg
Copy link
Collaborator

@wbamberg wbamberg commented Aug 28, 2025

This came out of #40188, which wanted to talk about how to make WebSocket clients bfcache-compatible, and https://github.com/orgs/mdn/discussions/817, in which we find that the example that's supposed to underpin the current Writing WebSocket client applications page doesn't work.

In mdn/dom-examples#326 I added a new example that does work and is bfcache-compatible.

This PR updates the guide page to use that example as a backbone. I hope I haven't jettisoned anything too important from the old page.

I wonder if we ought to say more about URL schemes: this example uses ws which is maybe shouldn't, but it can't use wss and still work with localhost AFAICT. But it could I think use a relative URL, and maybe should?

I also don't talk about the protocols option, as we don't use it in the example and it would feel like quite a big digression. Maybe that would be better as a separate guide?

On the differences between different browsers: this is mostly just what I have observed by testing. But this: https://docs.google.com/document/d/1JtDCN9A_1UBlDuwkjn1HWxdhQ1H2un9K4kyPLgBqJUc/edit?tab=t.0#heading=h.58d6ijfz2say is also a useful doc.

I wasn't sure about the "Security considerations" bit: I've kept it as-is but AFAIK mixed content just isn't allowed any more, and I couldn't find any more detail on "Most browsers now only allow secure WebSocket connections" (though I would love to know more about this, because if so it would be good to document it in BCD and describe it better here...).

I wouldn't be particularly averse to adding a note in https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API about bfcache-compatibility as well, if we think it merits more visibility than this.

If this merges I should file a follow-up to update https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno with the new example, too.

cc @gilbertococchi and maybe @tunetheweb ?

Fixes #29029

@github-actions github-actions bot added Content:WebAPI Web API docs size/m [PR only] 51-500 LoC changed labels Aug 28, 2025
Copy link
Contributor

github-actions bot commented Aug 28, 2025

seems not a single file was built! 🙀

(comment last updated: 2025-09-09 15:02:02)

@wbamberg wbamberg marked this pull request as ready for review August 28, 2025 22:24
@wbamberg wbamberg requested a review from a team as a code owner August 28, 2025 22:24
@wbamberg wbamberg requested review from sideshowbarker and removed request for a team August 28, 2025 22:24
@wbamberg wbamberg requested review from chrisdavidmills and removed request for sideshowbarker August 28, 2025 23:03
Copy link
Contributor

@chrisdavidmills chrisdavidmills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @wbamberg; this is a massive improvement over what we had before, in terms of both demo and guide. I've left you a few comments, but nothing major really.

I would still like to see a tech review from the other folks on the thread.

If you don't specify a protocol string, an empty string is assumed.
The `WebSocket` constructor takes one mandatory argument, which is the URL of the WebSocket server to connect to. In this case, since we're running the server locally, we're using the address of localhost.

The constructor takes another optional argument [`protocols`](/en-US/docs/Web/API/WebSocket/WebSocket#protocols), which allows a single server to implement multiple sub-protocols. We're not using this feature in our example.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your note that this probably should be discussed in a separate guide. It certainly doesn't belong in a basic usage guide, although from reading this and following the link, it feels like it'd be nicer if we provided some more info about it somewhere on MDN.


The back/forward cache, or {{glossary("bfcache")}}, enables much faster back and forward navigation between pages that the user has recently visited. It does this by storing a complete snapshot of the page, including the JavaScript heap.

The browser pauses and then resumes JavaScript execution when a page is added to or loaded from the bfcache. This means that, depending on what the page is doing, it's not always safe for the browser to use the bfcache for the page. If the browser determines that it is not safe, then the page will not be added to the bfcache, and then the user will not get the performance benefit that it can bring.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The browser pauses and then resumes JavaScript execution when a page is added to or loaded from the bfcache. This means that, depending on what the page is doing, it's not always safe for the browser to use the bfcache for the page. If the browser determines that it is not safe, then the page will not be added to the bfcache, and then the user will not get the performance benefit that it can bring.
The browser pauses and then resumes JavaScript execution when a page is added to or loaded from the bfcache. This means that, depending on what the page is doing, it's not always safe for the browser to use the bfcache for the page. If the browser determines that it is not safe, the page will not be added to the bfcache, and the user will not get the performance benefit that it can bring.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, maybe link to https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Monitoring_bfcache_blocking_reasons for more information on why pages might not be added to the bfcache.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why you are deleting my "then"s? I like my thens. "If....then" I think is clearer structurally than an implicit then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a shame that this is presented as a page on how to use a Chrome-only feature. It would be better to have a page under https://developer.mozilla.org/en-US/docs/Web/Performance, that talks about the bfcache as it works across browsers, and the various reasons pages might not be bfcache-friendly in various browsers, and links to notRestoredReasons as a tool you can use in that context.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say "restored from the bfcache" rather than "loaded from the bfcache".

The bfcache isn't a "cache" really. More a preservation and restore of a page's state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a shame that this is presented as a page on how to use a Chrome-only feature. It would be better to have a page under https://developer.mozilla.org/en-US/docs/Web/Performance, that talks about the bfcache as it works across browsers, and the various reasons pages might not be bfcache-friendly in various browsers, and links to notRestoredReasons as a tool you can use in that context.

That's fair. The concepts are generally the same. It's just that only Chrome currently exposes the reasons in an easy manner. I think testing each in each browser is gonna be quite painful without that as could run into false positives (e.g. you think it didn't use bfcache for X reasons, but it was actually for Y reason).

Either way I'm in two minds whether you should link it. I mean it is the only definitive list (and is part of the HTML spec btw!), but also is only queryable from Chrome as you say.

Copy link
Collaborator Author

@wbamberg wbamberg Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry, I didn't mean to sound so negative about it! It is a good article and obviously a useful API. And we should link to it. I just think it would be great to have a browser-agnostic article about bfcache compatibility on MDN, that's not tied to specific APIs. But I'm not planning to write that any time soon.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> ef5e49c and 86ebca5

});
```

Conversely, by listening for the {{domxref("Window.pageshow_event", "pageshow")}} event, you can seamlessly start the connection again when the page is loaded, or when it is retrieved from the bfcache. So in our example we will add all the code to initialize our WebSocket and set up its event listeners inside the `pageshow` event handler:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Conversely, by listening for the {{domxref("Window.pageshow_event", "pageshow")}} event, you can seamlessly start the connection again when the page is loaded, or when it is retrieved from the bfcache. So in our example we will add all the code to initialize our WebSocket and set up its event listeners inside the `pageshow` event handler:
Conversely, by listening for the {{domxref("Window.pageshow_event", "pageshow")}} event, you can seamlessly start the connection again when the page is loaded or retrieved from the bfcache. In our example, we add all the code to initialize our WebSocket and set up its event listeners inside the `pageshow` event handler:

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again I think I like my extra words around ", or when it is". I think it makes the structure clearer. I suppose this is subjective?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pageshow is also fired on page load. So might wanna mention that (to avoid people opening a connection twice on pageload).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again I think I like my extra words around ", or when it is". I think it makes the structure clearer. I suppose this is subjective?

Yes, I think so. They seem a little wordy to me, but it is not wrong. Up to you whether you want to commit these suggestions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pageshow is also fired on page load. So might wanna mention that (to avoid people opening a connection twice on pageload).

I do mention this:

you can seamlessly start the connection again when the page is loaded or retrieved from the bfcache.

good enough?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm.... it's the "start the connection again" which is not quite working for me. If you're using this as the main way of initialising the web socket on initial page load, then it't not again. Also "when the page is loaded or retrieved from the bfcache" could easily be misread to mean "when the page is (loaded or retrieved) from the bfcache" rather than "when the page (is loaded) or (retrieved from the bfcache)"

WDYT about something more explicit like this?:

Conversely, by listening for the {{domxref("Window.pageshow_event", "pageshow")}} event, you can seamlessly start the connection again when the page is retrieved from the bfcache. Since the pageshow event also fires on page load, this can also be used to initiate the websocket connection on initial page load."

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, makes sense, I missed the "again". Does f15529d work for you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks.

```

It may be helpful to examine the socket's {{domxref("WebSocket.bufferedAmount", "bufferedAmount")}} attribute before attempting to close the connection to determine if any data has yet to be transmitted on the network.
If this value isn't 0, there's pending data still, so you may wish to wait before closing the connection.
If you run our example, try navigating to a different page, then back to the example. In Chrome, you should see that the example starts the connection again, and keeps its original context: so, for example, it remembers the count of exchanged messages.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Firefox and Safari, it just seems to start the app again from its initial state, which is a shame. Worth mentioning?

Copy link
Collaborator Author

@wbamberg wbamberg Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not what I see in Safari, and I would like to know why we see different things here.

But it is what I see in Firefox, and that's expected: because the page isn't added to the bfcache, then it is reloaded, so the log starts out empty and the counter starts again at zero.

In Safari, after a forward/back navigation, I see this:

Screenshot 2025-09-02 at 8 57 24 AM

Here, I clicked "forward" after exchange 4, and then "back". I think what happens here is that:

  • the page was added to/loaded from the bfcache, so the HTML/JS is not reloaded, and we keep the contents of the log and the counter value
  • but because Safari closed the network request as soon as we navigated forward, we get a WebSocket error notification

At least this is my understanding of the situation. It is perhaps worth spelling this out in the guide, maybe not to the screenshot level of detail though.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @wbamberg, thanks for raising this.

I would love to hear from someone from WebKit if possible if they can share what Safari is doing with WebSocket.

I also noticed that Safari is behaving somehow differently from Chrome and Firefox.

It looks like pages using WebSocket without closing the connection via pagehide are still eligible for BFCache on Safari somehow.
From my tests it looked like the approach of closing the connection via pagehide and restarting the connection via pageshow was also working in Safari.

Can you please share the error you are getting?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction: Safari is definitively throwing a readyState 3 error (CLOSED), although I see the connection restarting on Safari too on my test page (https://output.jsbin.com/sepojum).

Did you see the WebSocket connection not working on a BFCache restored page on Safari while testing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like pages using WebSocket without closing the connection via pagehide are still eligible for BFCache on Safari somehow.

This is what I would have expected, given the info in https://docs.google.com/document/d/1JtDCN9A_1UBlDuwkjn1HWxdhQ1H2un9K4kyPLgBqJUc/edit?tab=t.0#heading=h.yqabnzb880lx :

Safari caches such pages but cancels active network requests.

Copy link
Contributor

@chrisdavidmills chrisdavidmills Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not what I see in Safari, and I would like to know why we see different things here.

Ah, hold on. I've tried it again in Safari. When I navigate to a new page by typing the URL into the address bar and pressing Enter (or following a link), then press "Back", I get the behavior I describe (I am using gamespot.com as a test).

When I then press "Forward" to go back to the new page, then press "Back", I get the behavior your screenshot is showing.

I've tried it with a few other example pages, and get the same effects,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I see that as well. In 1b0a049 I've been less specific about what the browsers do, so I hope that means we don't have to worry about this.

@wbamberg
Copy link
Collaborator Author

Thanks for the review, Chris! I'm out until Tuesday but will look when I get back.

@gilbertococchi
Copy link

Thanks everyone for your support for the WebSocket guide, sorry for late replying but I had been busy with lots of conflicting priorities these weeks.

I will review this first thing next week and will review the changes.

Thank you so much again for helping developers awareness about this opportunity for their sites when using WebSocket!

@Josh-Cena
Copy link
Member

Looks like this may fix #29029, though I'm not sure if completely.

Copy link
Contributor

@tunetheweb tunetheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM but have made some comments.

Note that I'm far from a WebSockets expert, but I do know a fair bit about bfcache.


The back/forward cache, or {{glossary("bfcache")}}, enables much faster back and forward navigation between pages that the user has recently visited. It does this by storing a complete snapshot of the page, including the JavaScript heap.

The browser pauses and then resumes JavaScript execution when a page is added to or loaded from the bfcache. This means that, depending on what the page is doing, it's not always safe for the browser to use the bfcache for the page. If the browser determines that it is not safe, then the page will not be added to the bfcache, and then the user will not get the performance benefit that it can bring.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say "restored from the bfcache" rather than "loaded from the bfcache".

The bfcache isn't a "cache" really. More a preservation and restore of a page's state.

Comment on lines 133 to 136
Different browsers use different criteria for adding a page to the bfcache. If a page has an active WebSocket connection, then:

### Text data format
- Firefox and Chrome will not add the page to the bfcache.
- Safari will add the page to the bfcache, but will cancel any active network requests. This is likely to generate an error in your WebSocket connection, leading to the connection being closed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably be less explicit here as this ius subject to change.

For example Chrome is looking into whether just using a WebSocket really should disqualify a page from the bfcache. Or if we can still pout it in the bfcache but evict it only if a message is received on that WebSocket connection.

So maybe just leave as "Different browsers use different criteria for adding a page to the bfcache. For maximum support of the bfcache it is recommended to close any WebSocket clients on the {{domxref("Window.pagehide_event", "pagehide")}} event..."

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose slightly different wording, hope this is OK? -> 1b0a049

This also gets us out of puzzling over Safari's behavior here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

});
```

Conversely, by listening for the {{domxref("Window.pageshow_event", "pageshow")}} event, you can seamlessly start the connection again when the page is loaded, or when it is retrieved from the bfcache. So in our example we will add all the code to initialize our WebSocket and set up its event listeners inside the `pageshow` event handler:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pageshow is also fired on page load. So might wanna mention that (to avoid people opening a connection twice on pageload).

@tunetheweb
Copy link
Contributor

I wouldn't be particularly averse to adding a note in https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API about bfcache-compatibility as well, if we think it merits more visibility than this.

I think this would still be good as a short note (perhaps linking to the relevant section in this guide?). This updated guide is good, but it feels a little out of the way.

@wbamberg
Copy link
Collaborator Author

wbamberg commented Sep 9, 2025

I wouldn't be particularly averse to adding a note in https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API about bfcache-compatibility as well, if we think it merits more visibility than this.

I think this would still be good as a short note (perhaps linking to the relevant section in this guide?). This updated guide is good, but it feels a little out of the way.

-> 21592f9 (I deleted a previous note that I didn't think was particularly useful)

@wbamberg wbamberg requested a review from tunetheweb September 9, 2025 03:39
@wbamberg
Copy link
Collaborator Author

wbamberg commented Sep 9, 2025

Thanks for the reviews, people! I think this is ready for another look.

@tunetheweb
Copy link
Contributor

Looks great now. One nit on the page_show on page load to consider, but other than that LGTM.

Copy link
Contributor

@chrisdavidmills chrisdavidmills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wbamberg One minor language nitpick, which you can take or leave. Apart from that, LGTM!

@wbamberg wbamberg merged commit 20c3765 into mdn:main Sep 9, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Content:WebAPI Web API docs size/m [PR only] 51-500 LoC changed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

No information on how to handle errors
5 participants