- Published on
Browsers Situationship: When Browsers Agree but the Spec Doesn’t
- Authors
- Name
- @atbrakhi
A few days ago, I was talking about browsers and specifications when my colleague Brian Kardell commented on a fairly common situation in the browser world: sometimes browsers agree on a behaviour that does not quite match what the specification says.
My immediate response was that this sounds like a situationship between browsers. Nobody wants to commit to the specification.
It was a joke, but then I started thinking about it. What actually happens when the specification says one thing, but Chrome, Firefox, and Safari are all doing something else? You would think the answer is obvious. The specification is the specification, so the browsers are wrong and the browsers should be fixed. Except that is not always what happens. Sometimes we change the specification instead.
That sounds strange if you think of a specification as an algorithm or a set of rules written first, with browsers as implementations that come later. But the history of the Web is not quite that simple. Specifications and browser implementations have influenced each other from the beginning, and that relationship is still very much alive today.
So, what came first: browsers or specifications?
One might think it is a chicken-and-egg question. But it is not. The historically correct answer is a little less dramatic: they grew together.
In 1989, Tim Berners-Lee proposed what would become the World Wide Web. By the end of 1990, he had written the first web server and the first browser/editor, called WorldWideWeb. Around the same time, he was also developing the early versions of HTML, HTTP, and URIs.
So there was never really a clear moment when someone finished a giant specification for the Web and then browser engineers went and implemented it. There was an idea, there was an implementation, and there were descriptions of how that system was supposed to work. All of those things evolved together.
The W3C's history of the Web describes Berners-Lee's early specifications for URI, HTTP, and HTML as being refined and discussed in larger circles as the Web spread. That last part is very important: as the Web spread.
One implementation talking to itself is relatively easy. Multiple independent implementations trying to understand and reproduce the same behaviour is where standards start becoming necessary.
As more browsers appeared and more people started building websites, the Web needed common agreements. If every browser had a completely different understanding of HTML, developers could not rely on the same page behaving consistently everywhere. A Web where every implementation invents its own rules stops being much of a Web. Imagine the user experience here!
In October 1994, Berners-Lee founded the World Wide Web Consortium, W3C, to coordinate the development of Web standards. A year later, in November 1995, HTML 2.0 was published as RFC 1866 through the Internet Engineering Task Force, or IETF. HTML standardization was still moving between different venues at this point; W3C would take the lead on later versions of HTML.
One part of that RFC is particularly interesting in this context. It describes HTML 2.0 as bringing together, clarifying, and formalizing features that were already in common use. In other words, the Web already existed, browsers were already implementing HTML, and the specification was helping describe and stabilize something people were already using.
That is quite different from thinking of standards as commandments delivered before implementations exist.
Why specifications matter
I do not want this to sound like specifications are just suggestions that browsers can ignore whenever they feel like it. They are not.
Specifications are one of the reasons the Web works at all.
A shared specification gives independent browser engines a common target. Blink, Gecko, WebKit, Servo, or any other browser engine can have completely different architectures internally, but they still need to agree on what happens when a website gives them the same input.
That agreement is what allows us to build one website instead of maintaining website-for-chrome.com, website-for-firefox.com, and website-for-safari.com.
Of course, there have been periods in Web history when browser-specific behaviour made this much harder. The browser wars are full of examples of browsers shipping their own features and developers having to deal with incompatible implementations. Standardization made the Web much more predictable by creating a common language that different implementations could target.
Modern browser development also has shared test suites such as Web Platform Tests, or WPT. WPT contains tests for Web-platform behaviour that can be run across multiple browsers. That gives browser engineers and specification authors something more concrete than specification prose alone: they can test whether independent implementations actually agree.
In an ideal world, the relationship is simple. A specification describes a behaviour, browsers implement that behaviour, shared tests verify it, and everybody agrees.
Unfortunately, we are software engineers.
Then reality happens
Specifications are written by humans. Browsers are written by humans. Websites are definitely written by humans. The future might belong to AI, but let's stick to humans for now because I am a human and I am still fixing bugs in browsers!
All three have bugs.
A specification can contain a mistake. A sentence can be ambiguous. An algorithm can describe behaviour that turns out to be impractical. Two browser engines can interpret the same wording differently. A browser can accidentally ship a bug. Websites can then start depending on that bug.
And once enough websites depend on something, the question of what is "correct" becomes surprisingly complicated.
Imagine that the specification says X, while Chrome, Firefox, and Safari all implement Y.
At first glance, this looks easy. Chrome has a bug, Firefox has a bug, Safari has a bug. Fix all three browsers and make them implement X.
But what if browsers have behaved as Y for years? What if existing websites rely on that behaviour without even knowing that they rely on it? Changing every browser to X might make the implementations match the specification while simultaneously breaking real websites for real users.
At that point, the useful question is no longer simply: Who violated the specification?
The useful question becomes: How do we get back to one interoperable behaviour without breaking the Web?
This is where the situationship between browsers starts getting interesting.
An actual browser situationship
There is a surprisingly good real-world example involving one of the most exciting topics in computer science: newline characters in HTML form submissions.
Stay with me.
In 2020, Andreu Botella, who is now my colleague, was investigating how browsers normalize newline characters when serializing HTML forms. Newlines are more complicated than they look because they can be represented in different ways, including LF, CR, and CRLF.
During that work, Andreu found a mismatch between browser behaviour and the HTML specification for filenames in certain form submissions. In the relevant case, browsers normalized the newlines, while the specification text, as written, would leave those filenames without normalization.
The corresponding WHATWG issue contains an almost perfect sentence for this entire blog:
"all browsers agree to normalize the newlines, but the spec would have them without normalization."
There you go. Chrome, Firefox, and Safari are officially in a situationship.
The specification said one thing. Chrome, Firefox, and Safari agreed on something else.
The proposed solution was not simply to file three browser bugs and force every implementation to change. For this case, the fix happened on the specification side: the serialization rules were updated so that the normalization browsers already agreed on was actually specified. The resulting standards work was merged, and Andreu later wrote a detailed WHATWG post explaining the browser differences, the specification problems, and the resulting changes.
There is an important nuance here. Not every newline-related behaviour was identical across Chrome, Firefox, and Safari. The investigation uncovered other places where the browsers disagreed with each other. But in this particular case, the browsers had converged on the same behaviour while the specification described something different.
That gives us a much more interesting question than "who is wrong?"
If every implementation independently does Y, real websites have observed Y, and changing everything to X risks compatibility problems, should the implementations move to the specification, or should the specification move toward reality?
Sometimes the answer is the latter.
But who decides that browser behaviour is right?
Changing a specification does not mean that browser behaviour automatically becomes correct just because enough browsers happen to implement it.
That would be a terrible standards process.
If three browsers accidentally implemented the same security bug, we would not want everyone to look at each other and say, "Well, consensus."
There still has to be reasoning.
WHATWG's Working Mode describes this process surprisingly clearly. When resolving a normative issue, editors are expected to consider questions such as what implementations currently do, what the ideal behaviour would be, what implementers are willing to change, and what the lowest-cost path toward convergence is.
That word — convergence — is important.
The goal is not to prove that the specification was right. The goal is not to prove that Chrome was right, or Firefox, or Safari. The goal is to get independent implementations to a behaviour that is well-defined, implementable, compatible with the existing Web, and ideally consistent everywhere.
WHATWG puts this very directly:
"A standard is a tool towards convergence."
I really like that sentence because it changes how you think about specifications.
The specification is important, but the purpose of the specification is not the specification itself. The purpose is an interoperable Web.
The Web has memory
There is another thing that makes browser engineering particularly interesting: you cannot really deploy a new version of the Web.
There is no Web 2.0 release where everybody gets one year to migrate and then all old behaviour disappears.
The Web we build today has to coexist with websites written years or decades ago. That means old behaviour can become a compatibility constraint, even when that behaviour was never intended in the first place.
The current Web Platform Design Principles explicitly warn standards authors to understand how existing websites use behaviour before changing or removing it. That includes not only standardized features, but also unspecified implementation behaviour, proprietary features, and even browser bugs that websites have come to depend on.
That is a strange idea when you first encounter it.
A browser bug can live long enough that fixing it becomes a Web compatibility problem.
At some point, the Web remembers the bug.
WHATWG even maintains a Compatibility Standard whose purpose is to describe behaviours that browsers need to support for compatibility with the de facto Web. Some historically vendor-specific -webkit- behaviour falls into this category because enough existing websites depend on it that other browsers may need to support it too.
This does not mean that whatever browsers happen to do automatically becomes a standard. It means that compatibility with the Web people already built is itself an engineering constraint.
Specifications cannot pretend that history does not exist.
So browsers can just ignore specifications?
No.
If every browser independently decided what the Web should be, we would end up exactly where standards are supposed to save us from.
The interesting thing is not that browsers and specifications can disagree. The interesting thing is what happens after we discover the disagreement.
Sometimes the browser changes. Sometimes several browsers change. Sometimes the specification changes. Sometimes everyone moves toward a third behaviour that is better than either of the original options.
Tests get written. Browser bugs get filed. Specification issues get discussed. Existing websites get studied. Implementations change. Specifications change. Tests change.
The goal is to make all of those things converge again.
This is also where Web Platform Tests become important. A specification describes expected behaviour, but shared tests allow browsers to check whether that behaviour actually exists across implementations. A failing interoperability test can expose disagreements that prose alone does not make obvious.
So the relationship is not really a hierarchy where specifications sit at the top and browsers sit underneath them.
It is a feedback loop.
Specifications influence browser implementations. Browser implementations expose problems in specifications. Existing websites constrain both. Tests tell us when the pieces disagree. Standards discussions decide what should change, and then the process starts again.
That feedback loop is what keeps the Web moving without completely breaking everything that came before it.
Maybe situationships are useful after all
I started looking into this because "browsers are in a situationship with each other" sounded funny.
I still think it is funny.
But the disagreement with the specification itself is not necessarily the interesting part. A permanent mismatch between specifications and implementations would actually be pretty bad. Chrome doing one thing, Firefox doing another, Safari doing something else, and the specification describing a fourth behaviour is not a situationship I particularly want to debug.
The situationship is the interesting case where the browsers have converged with each other even though they have not converged with the specification.
The useful part is what happens next.
The Web has mechanisms for discovering these disagreements and moving toward convergence. A browser implementation can reveal that the specification missed something. A specification can reveal that an implementation has a bug. A Web Platform Test can reveal that browsers thought they agreed but actually do not. And because these tests are designed to run across browsers, even a test originally written while working on one implementation can expose a difference in another, as long as it is testing Web-platform behaviour rather than something implementation-specific. Existing websites can remind everyone that technically elegant changes still have consequences for users.
So maybe browsers are in a situationship. But the situationship is not what keeps the Web working.
The feedback loop does.
Sometimes browsers move toward the specification. Sometimes the specification moves toward browser behaviour. Sometimes both sides move.
The commitment is not really to either side.
The commitment is to an interoperable Web.
References
W3C — A Little History of the World Wide Web and W3C — History — early Web history, the first browser/server, HTML/HTTP/URI work, and the founding of W3C.
RFC 1866 — Hypertext Markup Language 2.0 — published in November 1995 and useful for understanding how early HTML standardization formalized behaviour already in common use.
web-platform-tests — shared cross-browser test suites for Web-platform specifications.
HTML Living Standard — the current WHATWG HTML standard and its discussion of interoperability and compatibility.
WHATWG HTML issue #6247 — Web reality: Newline normalization in form payloads and in FormData objects — the browser/specification mismatch discussed in this post.
WHATWG Blog — Newline normalizations in form submission — Andreu Botella's detailed explanation of the investigation and specification changes.
WHATWG Working Mode — documents the standards process, implementation alignment, and convergence.
W3C Web Platform Design Principles — particularly the guidance around compatibility and existing Web content.
WHATWG Compatibility Standard — compatibility requirements based on the de facto Web.