w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.44k stars 656 forks source link

[cssom] Define serialization of specified values with more detail #5642

Open tabatkins opened 3 years ago

tabatkins commented 3 years ago

Serialization of computed values is generally governed by applying the shortest-serialization principle to the computed value. It's a bit handwavey, and there are some exceptions, but overall it works.

But specified values do not seem to serialize according to the SSP in general; instead, they seem to usually retain more details of what the author originally wrote. Sometimes it's literally the original text, sometimes some light modification is done. (For example, Chrome sorts the 'contain' keywords specified, but doesn't combine them into shorthands like it'll do for computed values.)

I suspect there's a lot of non-interop around this, but we should have more details at least on general principles, even if we don't have all the weird specifics.

Loirooriol commented 3 years ago

Some common patterns:

Remove optional components when the default value was provided (according to the shortest serialization principle) ```js document.body.style.margin = "1px 1px 1px 1px"; document.body.style.margin; // "1px" in Chromium, WebKit and Firefox ``` ```js document.body.style.gridAutoFlow = "row dense"; document.body.style.gridAutoFlow; // "dense" in Firefox (stays as "row dense" in Chromium and WebKit) ```
Add optional components that were omitted (against the shortest serialization principle) ```js document.body.style.counterIncrement = "c"; document.body.style.counterIncrement; // "c 1" in Chromium, WebKit and Firefox ``` ```js document.body.style.borderTop = "solid"; document.body.style.borderTop; // "medium solid" in Firefox (stays as "solid" in Chromium and WebKit) ```
Normalize to canonical order ```js document.body.style.flexFlow = "wrap column"; document.body.style.flexFlow; // "column wrap" in Chromium, WebKit and Firefox ```
cdoublev commented 2 years ago

If I may, what is the rationale for pattern 1 (remove optional component values)? It is always extra work, for what purpose?

EDIT: from reading multiple related issues, serialization to an older syntax, which may me shorter or longer, can help back-compat. I wonder what would be the conditions/scenarios where this backward compatibility requirement might be useful, though.

tabatkins commented 2 years ago

It brings the value closer to what humans would likely write, which makes it easier to read.

cdoublev commented 2 years ago

I had figured it that too, but it seems less important than back-compat. And for shorthand (eg. animation), the shorter meaning the more human-friendly, may be subjective.

cdoublev commented 1 year ago

I do not know if math functions generally fall into the "weird case" category but the SSP is currently applied inconsistently with them.

For example, Serialization in CSS Images 3 requires omitting components when possible without changing the meaning:

For example, a gradient specified as:

Linear-Gradient( to bottom, red 0%,yellow,black 100px)

must serialize as:

linear-gradient(red, yellow, black 100px)

(The same section in CSS Images 4 serializes red, yellow, black, to rgb(), which makes me wonder if it applies to specified values though.)

Chrome/Firefox omit to calc(180deg) (same as to bottom) in <linear-gradient()> but math functions usually seem to be preserved elsewhere. And they do not omit components in the color stop list. You cannot know 75% can be omitted in linear-gradient(red 50%, blue 0%, 75%, green) without applying color stop "fixup", which is meant to resolve used values.

Obviously color stop list is a "weird case" but I think there are many cases similar to the first. Eg. Chrome/FF do not serialize specified border-radius: 1em calc(1em) to border-radius: 1em.

I guess browsers do not try too hard to resolve values until they know it is required for rendering, which makes me think that not applying the SSP for specified values (except for backward-compatibility) would be way more simple.

cdoublev commented 1 year ago

I did an in-depth specific research on serialization of optional numeric values, or optional keyword values mapping to a numeric value, in Chrome and Firefox.

They usually only seem to be omitted when they are strictly equal to a default value or an initial longhand value, without applying any resolution before the comparison (presumably with serialized strings):

Two numerics with a value of 0 but of a different type or with a different unit are usually not considered equal, except 0px and 0 when it matches <length> (it is presumably normalized to 0px at parse time).

Exceptions (probably not exhaustive) - often Chrome and sometimes Firefox preserve optional initial longhand values in shorthands - color functions serialize without a default ``: `100%`/`1`, `101%`/`2` `calc(101%)`, `calc(2)` - [``](https://drafts.csswg.org/css-images-4/#funcdef-conic-gradient) serializes with `from 0deg` as a default gradient line origin in Chrome - `` serializes without `from calc(0deg)` as a default gradient line origin in Firefox - [``](https://drafts.csswg.org/css-images-3/#funcdef-linear-gradient) serializes without a default gradient line direction: `to bottom`, `180deg`, `calc(180deg)` - [``](https://drafts.fxtf.org/filter-effects-1/#typedef-filter-function)s serialize with default values - [`font: normal normal 400 16px "family"`](https://drafts.csswg.org/css-fonts-4/#font-prop) serializes without `400` (equivalent to the initial `font-weight`: `normal`) in Firefox - [`font-style: oblique 14deg`](https://drafts.csswg.org/css-fonts-4/#font-style-prop) serializes with `14deg` in Chrome - [`hyphenate-limit-chars: auto 1 1`](https://drafts.csswg.org/css-text-4/#hyphenate-char-limits) serializes as is (even if the trailing `1` is strictly equal to the previous `1`) - [`transform-origin`](https://drafts.csswg.org/css-transforms-1/#transform-origin-property) serializes with the default offset: `0px` or `0` - [`scale: 1 100% 1`](https://drafts.csswg.org/css-transforms-2/#propdef-scale) serializes with `scale: 1` - `scale: 100% 1 100%` serializes with `scale: 1` - `scale: calc(100%) calc(1)` serializes with `scale: 1` in Chrome and `scale: calc(1)` in Firefox - [`translate: 1px 0em 0in`](https://drafts.csswg.org/css-transforms-2/#propdef-translate) serializes with `translate: 1px` - `translate: 1px calc(0em)` serializes without `calc(0em)` in Chrome - [`transform: scale(1, 1)`](https://drafts.csswg.org/css-transforms-1/#two-d-transform-functions) serializes with `, 1` in Chrome - `transform: translate(1px, 0px)` serializes with `, 0px` in Chrome - `transform: translate(1px, 0in)` serializes without `, 0in` in Firefox - `transform: skew(1deg, 0)` serializes with `, 0` in Chrome (invalid in Firefox) - `transform: skew(1deg, 0deg)` serializes without `, 0deg` in Firefox - `transform: skew(1deg, 0rad)` serializes without `, 0in` in Firefox - `transform: skew(1deg, calc(0deg))` serializes without `, calc(0deg)` in Firefox - `transform: skew(1deg, calc(0rad))` serializes without `, calc(0in)` in Firefox