Changing Computed CSS Values
I recently merged a PR that changes how the WBE browser handles “computed styles”. This change affects everything back to Chapter 6, so I wanted to explain what computed styles are, how WBE handled them before, and why I changed it.
What are computed style values?
In CSS, an element has a "computed style" as well as a "specified style". The specified styles are what you get after matching and cascading assigns a value to each CSS property for that element. But computed styles differ from that because they resolve relative font sizes and ensure properties like padding only have positive lengths.
I haven’t worked much with the modern CSS specifications, but in CSS 2, it always felt to me like computed styles were mostly there for resolving percentage font-size
values, so that “em” units could be converted into “px” units during styling.1 Note that most percentage sizes are resolved during layout; it’s just font-size
that’s special and has to be resolved during styling.
How do browsers handle computed styles?
Logically, computed styles are separate from specified styles, so computing2 them is a separate pass, applied after all of the other elements of the cascade have happened and determined the element’s specified style.
However, in the WBE browser, the only computed value is font-size
: percentage font sizes resolve into pixel values, so we had some flexibility to implement it in a different way. We chose to write a compute_style
function, which you’d call before storing any value in the style dictionary.
The advantage of this is that the style dictionary only ends up containing computed styles, which is a kind of conceptual simplification. But watching my students work through the style chapter, I realized that this approach created more problems than it solved.
What was the problem?
First, it wasn’t clear why you would do something in compute_style
instead of in style
itself. I suppose the CSS spec does provide the answer (when it defines the differences between computed and specified styles) but readers and students aren’t going to be intimately familiar with those steps, and the boundary between these functions then looks pretty vague.
Second, compute_style
had a weird API. Apparently it could output None
to mean not to store the value at all, which I wanted to use in case someone used an illegal unit for font size. But this was neither explained in the book nor even correctly handled by all of the code. I think I was trying to shove value parsing into this function, too, but conceptually that’s a different step of the process (it normally happens during parsing) and adding it here just made things confusing.
Finally, and most importantly, this design got in the way of Chapter 16. In Chapter 16,3 we need to track dependencies between CSS values, which means mainly working with style fields, not style values. The compute_style
API hindered that because it accessed style fields but returned style values. If the API were otherwise great, I could have worked around it, but given that the API was already bad, it seemed ripe for replacement.
The new approach
Given all of this, I went back and removed the compute_value
function, replacing it with an extra “phase” of style
that runs after all of the cascading is done but before we recurse to style the children. This avoids having an API boundary at all, which avoids all the challenges with designing a good one and also doesn’t hinder Chapter 16. As a bonus, it’s a bit more like a real browser.
I’ve merged this change to the book and updated the website, so you can check out Chapter 6 to see the new approach and update your browser to follow suit if you like it.
See you all again soon!
There were other cases where computed and specified styles differed, but everything besides em
units felt like edge cases which maybe could have been avoided. I think the Stylo name for this step, StyleAdjuster, appropriately signifies that it just adjusts a bunch of stuff according to fairly ad-hoc rules.
By the way, terrible terminology. If it were called the element’s “resolved styles”, then we could use “resolving” as the verb for computing the resolved style. You can’t use “computing” that way, it is too generic.
Which I promise Chris and I are working really hard on and will be ready “soon”.