惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Palo Alto Networks Blog
N
Netflix TechBlog - Medium
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
小众软件
小众软件
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
B
Blog
MyScale Blog
MyScale Blog
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
S
Schneier on Security
Project Zero
Project Zero
T
Tenable Blog
T
Tor Project blog
U
Unit 42
G
GRAHAM CLULEY
N
News and Events Feed by Topic
P
Proofpoint News Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
C
Cybersecurity and Infrastructure Security Agency CISA
腾讯CDC
博客园 - 叶小钗
M
MIT News - Artificial intelligence
Vercel News
Vercel News
T
Threat Research - Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Exploit Database - CXSecurity.com
P
Privacy & Cybersecurity Law Blog
I
Intezer
博客园 - 聂微东
SecWiki News
SecWiki News
Scott Helme
Scott Helme
C
Cyber Attacks, Cyber Crime and Cyber Security
IT之家
IT之家
量子位
S
Secure Thoughts
The Cloudflare Blog
博客园 - 司徒正美
Know Your Adversary
Know Your Adversary
Hacker News: Ask HN
Hacker News: Ask HN
V
Vulnerabilities – Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
N
News | PayPal Newsroom
C
Check Point Blog
Spread Privacy
Spread Privacy
C
CXSECURITY Database RSS Feed - CXSecurity.com
Cyberwarzone
Cyberwarzone

alexwlchan

Preventing line breaks in <code> elements Fixing a bug with byte order marks A Git hook to prevent committing directly to main Describing all my photos I don’t want to repeat repeat myself Rebuilding the computer room Using the Screen Capture API to record a browser window Using Pytester to test my Playwright fixtures Rendering a chat thread in CSS and JavaScript Waiting for website changes in the browser Watching for file changes on macOS Using Playwright to test my static sites Building a basic cache with SQLite HTTP GET requests with the Python standard library Auditing my local Python packages Quietly quantum-resistant blogging Creating a personalised bin calendar Monki Gras 2026 “Prepping Craft” The selfish case for public libraries Dreaming of a ten-year computer Gumdrop, a silly app for messing with my webcam The bare minimum for syncing Git repos Creating Caddyfiles with Cog Swapping gems for tiles Parody posters for made-up movies The Good, the Bad, and the Gutters Using perceptual distance to create better headers The passwords I actually memorise Where I store my multi-factor recovery codes Quick-and-dirty print debugging in Go My favourite books from 2025 Drawing Truchet tiles in SVG Adding a README to S3 buckets with Terraform The palm tree that led to Palmyra
What can wonky APIs tell us about the web?
alexwlchan.n · 2026-06-22 · via alexwlchan

A few days ago, Misty posted something that caught my eye:

Finding myself asking if there's ever been a wonkier official browser API than canPlayType

The HTMLMediaElement.canPlayType API tells you how likely it is that a browser can play media with a given MIME type, but the response is unusual. The word “likely” is important here, because it’s not a simple yes/no answer. The possible responses are:

  • "" – no, the browser can’t play the media
  • "probably" – the browser can probably play the media
  • "maybe" – there isn’t enough information to determine if the media is playable.

A ternary, probabilistic response is already a bit weird; the return values double down on the weirdness. A clearer set of return values would be "no", "probably" and "unknown".

But when thinking of wonky web APIs, my mind went somewhere else: to History.pushState() and replaceState(). These APIs are for manipulating your browser history, and take an unused parameter which you have to pass but do absolutely nothing.

Here’s the description of pushState from MDN (emphasis mine):

The pushState() method of the History interface adds an entry to the browser’s session history stack.

Syntax:

    pushState(state, unused)
    pushState(state, unused, url)

Parameters:

  • state – The state object is a JavaScript object which is associated with the new history entry created by pushState(). […]
  • unusedThis parameter exists for historical reasons, and cannot be omitted; passing an empty string is safe against future changes to the method.
  • url – The new history entry’s URL.

This begs the question: what historical reasons? Why does an API supported by every major browser have a parameter that nobody uses?

The History API was designed for use with single-page applications (SPAs) – sites that only load a single page, then use JavaScript to update the contents of a page, rather than loading a new page every time something changes. Using SPAs can make a site faster, because they only have to load the part of a page that’s changed, but they also break the behaviour of the browser’s “back” and “forward” buttons.

From the user’s point of view, they click links and the page changes, so if they click the “back” button, they expect to go back to their previous state.

But the browser only records a single history event, when the user first loads the SPA – all the in-page updates using JavaScript don’t register as new pages. When the user clicks the “back” button, the browser will take them to whatever page they were looking at before they opened the SPA.

Using pushState() and replaceState() allows an app to create synthetic history entries, so the “back” and “forward” buttons can step the user through the pages they’ve seen within the SPA.

The pushState() API first appeared in a draft HTML5 spec in January 2008, with three parameters:

  • The state object would be attached to the history event, and if the user clicked the “back” or “forward” buttons, your app would get a popstate event with the state value associated with the history event they’d selected.
  • The title parameter allowed apps to set a title for the entry saved in the browser’s session history, which could be different to the title shown in the browser window.
  • The url parameter allowed apps to set a URL for the history entry, which could be different to the URL shown in the browser window. If omitted, the browser would use the current URL.

The title parameter was always “advisory”, and in practice most browsers completely ignored the parameter, to avoid the confusion of mismatched titles in the browser UI and session history.

It soon became clear that the title parameter was pointless, but it was already too late to change. Lots of sites were built as single-page applications and already using the new pushState and replaceState APIs, and breaking those sites was unacceptable.

The argument could be neither removed nor made optional. If you removed it, you’d break sites that used the three-parameter pushState(state, title, url). If you made it optional, its position in the middle of the signature would leave browsers unable to distinguish between pushState(state, url) and pushState(state, title).

Instead, the spec was updated to rename the parameter to unused and clarify it has no effect.

This wonky API reflects the challenge of designing for the web: it’s difficult to design APIs without seeing how they’re used on real world websites, but then it’s too late to make changes in response to feedback. The web goes to incredible lengths to preserve backward compatibility, and it’s the ultimate form of “anything described as a prototype will get shipped in production”.

The longevity of vanilla web technology is why I keep using static websites for my media archives – more than anything else in my career in tech, web technology is what lasts.