Re: [csswg-drafts] [cssom] How should extremely small <number>s be serialized? (#2330)

I just ran into this problem this week.  It is clear that scientific notation is being used in some cases of number token serialization.  It is clear that some values don't round-trip. It is clear that browser implementations vary from each other and vary from the spec.  The spec is very clear that scientific notation should not be used. It is very clear that leading whole number zeros should be dropped, and trailing digits after the decimal point should be rounded to no more than 6 digits.

As debugging code, I was trying to verify that CSS values set by Javascript setProperty calls actually have good syntax.  I wrote the following:

// Setting a custom property value to a bad value doesn't return or throw
// an error in all cases, so double-checking the resulting value can catch
// errors sooner.
let setckprop = ( sty, prop, val ) =>
{ sty.setProperty( prop, val );
  let dblck = sty.getPropertyValue( prop );
  if( dblck != val )
  { console.err(`setckprop "${prop}" to "${val}" resulted in "${dblck}"`);
  }
}

Testing with Firefox, it caught only error cases. Testing with Chrome and Safari produced console error messages for non-error cases involving numbers. I had started using CSS custom properties to hold a variety of values, some to be used in later CSS, some to be used in other ways, simply using the cascade to make them available. By reading MDN and experimenting with Firefox, I expected that would work, but in reading the spec, I see that browsers validate custom properties by tokenizing them, and then are allowed to return, via getPropertyValue, the result of serializing the token sequence.  This makes it possible that the result of getPropertyValue is not identical with the value passed to SetProperty (even when !important is not included).

So after reading the spec, particularly the part about numbers, which caused all of the differences I had encountered, I wrote some test code, to verify that no browser is conforming to my understanding of the spec,

Here is my HTML test case:
[mangler.html.txt](https://github.com/w3c/csswg-drafts/files/4733178/mangler.html.txt)

The output from Firefox is:
test 1 ________ property width: in_35em_out_35em_
test 2 MISMATCH property width: in_35.33333333333em_out_35.3333em_
test 3 MISMATCH property width: in_-47.3333333333em_out_35.3333em_
test 4 MISMATCH property width: in_aoheuts_out_35.3333em_
test 5 ________ property --val: in_35em_out_35em_
test 6 ________ property --val: in_35.33333333333em_out_35.33333333333em_
test 7 ________ property --val: in_-47.3333333333em_out_-47.3333333333em_
test 8 ________ property --val: in_353.3333333333em_out_353.3333333333em_
test 9 ________ property --val: in_3533.333333333em_out_3533.333333333em_
test 10 ________ property --val: in_35333.33333333em_out_35333.33333333em_
test 11 ________ property --val: in_353333333.3333em_out_353333333.3333em_
test 12 ________ property --val: in_aoheuts_out_aoheuts_
test 13 ________ property --val: in_blathery77.3333333333foo_out_blathery77.3333333333foo_
test 14 ________ property --val: in_zip code 08619-4455_out_zip code 08619-4455_
test 15 ________ property --val: in_credit card 4396338722196122_out_credit card 4396338722196122_
test 16 ________ property --val: in_product bar code 9223346311036_out_product bar code 9223346311036_
test 17 ________ property --val: in_color value 87654321_out_color value 87654321_
test 18 ________ property --val: in_(3.77777777777turn)_out_(3.77777777777turn)_
test 19 ________ property --val: in_turn3.777777777777_out_turn3.777777777777_
test 20 ________ property --val: in_turn 3.777777777777_out_turn 3.777777777777_
test 21 ________ property --val: in_.777777777777turn_out_.777777777777turn_
test 22 ________ property --val: in_turn.777777777777_out_turn.777777777777_
test 23 ________ property --val: in_really big 439633872219612243963387221961224396338722196122_out_really big 439633872219612243963387221961224396338722196122_

Number of mismatches: 3

It appears that Firefox returns a copy of the supplied parameter value in all cases where that value can be correctly tokenized, and used as a proper value for the specified property. This makes it possible to detect erroneous values with a simple comparison such as is implemented in setckprop.  This is user friendly. However, it is clearly a violation of the spec for getPropertyValue, because the spec states that it should return a serialization of the token sequence, and the number serialization does not occur according to the rules for number token serialization.  But it does have benefits: in addition to simplifying the validation that a value was acceptable, it round-trips: the return value can be used to achieve exactly the same effect as the original value (because it is the original value). Javascript that created the original value may with to retrieve it and modify it, and there are no surprises or complications.

In Chrome and Safari, the output is apparently NOT the original input, but instead is a result of the serialization of tokens... but they don't properly follow the serialization rules, which in some cases result in values which, if directly resubmitted to setPropertyValue would produce a different token sequence.  This is not user-friendly.  Not only is validation harder (it would require something far more complex than my naïve setckprop function), round-trips are also not possible.

Here is the Chrome output (Safari looked the same to me, so I won't include it here):

test 1 ________ property width: in_35em_out_35em_
test 2 MISMATCH property width: in_35.33333333333em_out_35.3333em_
test 3 MISMATCH property width: in_-47.3333333333em_out_35.3333em_
test 4 MISMATCH property width: in_aoheuts_out_35.3333em_
test 5 ________ property --val: in_35em_out_35em_
test 6 MISMATCH property --val: in_35.33333333333em_out_35.3333em_
test 7 MISMATCH property --val: in_-47.3333333333em_out_-47.3333em_
test 8 MISMATCH property --val: in_353.3333333333em_out_353.333em_
test 9 MISMATCH property --val: in_3533.333333333em_out_3533.33em_
test 10 MISMATCH property --val: in_35333.33333333em_out_35333.3em_
test 11 MISMATCH property --val: in_353333333.3333em_out_3.53333e+8em_
test 12 ________ property --val: in_aoheuts_out_aoheuts_
test 13 MISMATCH property --val: in_blathery77.3333333333foo_out_blathery770.333333foo_
test 14 MISMATCH property --val: in_zip code 08619-4455_out_zip code 8619-4455_
test 15 MISMATCH property --val: in_credit card 4396338722196122_out_credit card 4.39634e+15_
test 16 MISMATCH property --val: in_product bar code 9223346311036_out_product bar code 9.22335e+12_
test 17 MISMATCH property --val: in_color value 87654321_out_color value 8.76543e+7_
test 18 MISMATCH property --val: in_(3.77777777777turn)_out_(3.77778turn)_
test 19 MISMATCH property --val: in_turn3.777777777777_out_turn30.777778_
test 20 MISMATCH property --val: in_turn 3.777777777777_out_turn 3.77778_
test 21 MISMATCH property --val: in_.777777777777turn_out_0.777778turn_
test 22 MISMATCH property --val: in_turn.777777777777_out_turn0.777778_
test 23 MISMATCH property --val: in_really big 439633872219612243963387221961224396338722196122_out_really big 3.40282e+38_

Number of mismatches: 20

I'll discuss the MISMATCH results, and whether or not I think they are conformant.

tests 2, 3, 4, 6, 7, 8, 9, 10, 18, and 20 seem to be almost conformant.  The "almost" is due to providing 6 digits of precision, rather than 6 decimal digits (digits after the decimal point).

tests 11, 15, 16, 17, and 23 are non-conformant, as they uses scientific notation in the output.

tests 13, 19, 21, and 22 are non-conformant, as they don't produce the smallest representation: Note that the "77" is part of the prior token, not part of the number token. The number token starts with the decimal point. The serialization is not minimal, because it produces a leading zero prior to the decimal point, which is not conformant because it is not minimal. The only "leading" zero that is appropriate for a minimal representation is if the value is exactly zero, or rounds to exactly zero.

test 14 is conformant, the leading zero is dropped.

So it is clear that because of the possibility of getting a returned value that is a result of serialization, that it is not practical to store "numeric-looking" values in CSS custom properties, except by converting them to strings with surrounding quotation marks.

It is clear that because setPropertyValue does not return a error value when an input parameter value is invalid, that a simple equality check between setProperty and getPropertyValue is insufficient to do verification (except in non-conformant Firefox).

It is clear that this is a specification of a bug-prone interface: one cannot easily determine if a value was valid and accepted, except by writing a complex external validation function (and good luck verifying it), nor can one round-trip values from getPropertyValue to setProperty and expect consistent results.

Returning an error code from setProperty that could be checked, or throwing an exception for invalid values would be far friendlier, or requiring behavior like Firefox presently has, in returning the original string if it is valid, would be far friendlier. Of course, if all the browsers actually conformed to the specification, that would be helpful for cross-platform development, as dependence on non-conformity of one browser leads to problems running on others.

-- 
GitHub Notification of comment by v-python
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/2330#issuecomment-639186325 using your GitHub account

Received on Friday, 5 June 2020 00:19:01 UTC