One of the goals of all the web related committees for more than a decade has been to ensure that there is no undefined behaviour. Undefined behaviour is exactly the reason we used to have to have different versions of a webpage for every browser.
The only intentionally remaining ambiguities are in areas where there is significant existing content that depends on mutually incompatible behaviour, eg: content that does one thing in browser A, and another in browser B, but is using the same API in each path yet expecting different behaviour. I can’t recall any specifics off the top of my head any more, but a classic has always been for(in) enumeration along the prototype chain when properties are being added and removed. @dbaron or @brendaneich (that his account? I can’t recall) may recall some of the others. Prefixes (that everyone on HN hates) solved this problem - it makes it possible to keep that old non-spec API around, without permanently screwing up the final specification (every points to those old -webkit-gradient properties, but forgets that that api was much clunkier to use than the final gradient spec. If the prefix hadn’t existed then the webkit api would be the permanent one that everyone would have to use)
The other big one where there’s specified behaviour that is allowed to vary is webgl. I was kind of responsible for the hated “only support the minimum subset” rules of version 1, but I believe modern specifications allow an implementation to vary according to hardware. The only other place in webgl that I made people (gpu engineers mostly) unhappy was not allowing undefined behaviour when accessing beyond the end of an array in the shader - there were voices that literally wanted to allow this to be undefined for perf reasons even when shown you could read data from other execution environments (this was mind blowing). The compromise in the end was allowing an implementation to return either zero, or some other constant defined value (I think this would fall under the C/C++ “unspecified” behaviour banner).
Anyway, in general undefined behaviour is bad on the web, and all web spec authors try to ensure that every edgecase is covered explicitly so that we never get stuck back in the terrible 2000s again.
Without actually digging into it (I'm on vacation damnit), I'm pretty sure all renderings are valid per CSS 2.1 (though I don't think they are per CSS UI 3 or 4? but maybe that's still a SHOULD level requirement given people wanting to publish as REC ).
The CSS WG has been pretty poor at actually tightening up specification for old features (on the face of it, due to lack of editorial time, which really points at a separate problem).
(Also, given the for(in) example, as I'm sure you know but for the benefit of others, note that iteration order of each object on the prototype chain is still undefined, and not interoperable.)
Some other good examples of things that have been standardised over the past decade: HTML parsing(!), what specific DOM exceptions are thrown all over the place, when the various document load events fire.
But behavior when you deleted an own property that shadowed a property on the proto chain, and what happens when you add such property back.
Also the historic spec mandated integer properties on regular objects were iterated in insertion order as well, but V8 chose not to implement that as doing so broke their "Indexed properties are just an array" semantics. Because of the huge perf impact other browsers had to go down that same path.
I was thinking that "undefined behavior" in a spec is a serious misnomer. It's paradoxical if anything is defined as undefined, and it lets you confuse it with actually undefined behavior.
If something is in the spec as undefined behavior, it's my understanding it could legitimately lead to, say, global thermonuclear war. No guarantees.
But if something is not in the spec at all, say the effect of having your "const" keywords bright blue, then the spec is not sanctioning the end of the world happening as a consequence. The entire world provides constraints of reasonableness and other relationships that must hold.
This seems like an important difference! The latter is what I would call literally undefined; the standard version of undefined is (a) inherently defined as undefined, and (b) falsifies itself, because nobody would actually use a compiler that could cause the end of the world, ever.
The discussion of undefined behavior and the concomitant blaming of those who inadvertently invoke it have gradually come to be associated in my mind with the blaming of people for self-driving car crashes. They may not be related exactly, but homomorphic. It seems like popular and gradually spreading pathology in human thinking. Maybe it could be called "computism" after "corporatism".
I guess maybe the issue is that the world is mostly composed of "unknown unknowns" and it is incredibly attractive to a lot of programmers to declare them Somebody Else's Problem. But I'm tentatively suggesting you can never just disclaim those "unknown unknowns" and therefore you should never fall into the trap of trying to define the undefined. It's a vast ocean, not a few drops of water you can build a fence around. Unfortunately this sounds, even to me, like vague philosophical meanderings, but I strongly feel that there is something like an intellectual prion disease out there, and that impression has been building ever since I was browsing comp.lang.c.* in the 90s.
Rust aims to solve these problems with something called zero cost abstractions.
The worst part of these frontend issues is, ultimately, the UX can become inconsistent, if not mess. Given that nearly everyone interfaces via the visual UI, you'd think we'd have this sorted out by now.
That said, the browser's impact on UX is essential. A lot more people use browsers than compilers. You'd think the UX would matter by now.
For example, a race condition is an UB. Compiler won't invent locks that were not specified by the programmer in a code with some raw threads. As a result, CPUs will trash the memory.