style: Serialize NaN and infinity numbers

Added NaN/inf serialization of <number> and changed calc() code to not
remove NaN/infinity in code using it.

This change is unfortunately imperfect as some things using <number>
still refuse to serialize NaN/infinity for some reason (scale()?), but
this bug/patch is just for <number> so leaving that out of scope for
this.

Also added new WPT test file for number NaN/inf serialization based
on existing serialization tests (all pass already!).

5 other WPT subtests now newly pass.

Differential Revision: https://phabricator.services.mozilla.com/D178587
This commit is contained in:
CanadaHonk 2023-06-08 21:22:19 +00:00 committed by Martin Robinson
parent dcb61c095f
commit a10df24ffb
3 changed files with 37 additions and 23 deletions

View file

@ -99,6 +99,14 @@ fn nan_inf_enabled() -> bool {
return false;
}
/// Serialize a number with calc, and NaN/infinity handling (if enabled)
pub fn serialize_number<W>(v: f32, was_calc: bool, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
serialize_specified_dimension(v, "", was_calc, dest)
}
/// Serialize a specified dimension with unit, calc, and NaN/infinity handling (if enabled)
pub fn serialize_specified_dimension<W>(
v: f32,
@ -120,11 +128,15 @@ where
// requires an expression like calc(infinity * 1px)."
if v.is_nan() {
dest.write_str("NaN * 1")?;
dest.write_str("NaN")?;
} else if v == f32::INFINITY {
dest.write_str("infinity * 1")?;
dest.write_str("infinity")?;
} else if v == f32::NEG_INFINITY {
dest.write_str("-infinity * 1")?;
dest.write_str("-infinity")?;
}
if !unit.is_empty() {
dest.write_str(" * 1")?;
}
} else {
v.to_css(dest)?;

View file

@ -12,7 +12,7 @@ use crate::values::generics::calc::{MinMaxOp, ModRemOp, RoundingStrategy, SortKe
use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength};
use crate::values::specified::{self, Angle, Resolution, Time};
use crate::values::{CSSFloat, CSSInteger};
use crate::values::{serialize_number, serialize_percentage, CSSFloat, CSSInteger};
use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token};
use smallvec::SmallVec;
use std::cmp;
@ -131,9 +131,9 @@ impl ToCss for Leaf {
{
match *self {
Self::Length(ref l) => l.to_css(dest),
Self::Number(ref n) => n.to_css(dest),
Self::Number(n) => serialize_number(n, /* was_calc = */ false, dest),
Self::Resolution(ref r) => r.to_css(dest),
Self::Percentage(p) => crate::values::serialize_percentage(p, dest),
Self::Percentage(p) => serialize_percentage(p, dest),
Self::Angle(ref a) => a.to_css(dest),
Self::Time(ref t) => t.to_css(dest),
}
@ -887,10 +887,16 @@ impl CalcNode {
/// Tries to simplify this expression into a `<number>` value.
fn to_number(&self) -> Result<CSSFloat, ()> {
self.resolve(|leaf| match *leaf {
let number = self.resolve(|leaf| match *leaf {
Leaf::Number(n) => Ok(n),
_ => Err(()),
})
})?;
let result = if nan_inf_enabled() {
number
} else {
crate::values::normalize(number)
};
Ok(result)
}
/// Tries to simplify this expression into a `<percentage>` value.
@ -992,7 +998,6 @@ impl CalcNode {
) -> Result<CSSFloat, ParseError<'i>> {
Self::parse(context, input, function, CalcUnits::empty())?
.to_number()
.map(crate::values::normalize)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}

View file

@ -16,8 +16,8 @@ use super::generics::{self, GreaterThanOrEqualToOne, NonNegative};
use super::{CSSFloat, CSSInteger};
use crate::context::QuirksMode;
use crate::parser::{Parse, ParserContext};
use crate::values::serialize_atom_identifier;
use crate::values::specified::calc::CalcNode;
use crate::values::{serialize_atom_identifier, serialize_number};
use crate::{Atom, Namespace, One, Prefix, Zero};
use cssparser::{Parser, Token};
use std::fmt::{self, Write};
@ -196,15 +196,15 @@ fn parse_number_with_clamping_mode<'i, 't>(
match *input.next()? {
Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => {
Ok(Number {
value: value.min(f32::MAX).max(f32::MIN),
value,
calc_clamping_mode: None,
})
},
Token::Function(ref name) => {
let function = CalcNode::math_function(context, name, location)?;
let result = CalcNode::parse_number(context, input, function)?;
let value = CalcNode::parse_number(context, input, function)?;
Ok(Number {
value: result.min(f32::MAX).max(f32::MIN),
value,
calc_clamping_mode: Some(clamping_mode),
})
},
@ -264,8 +264,12 @@ impl Number {
/// Returns the numeric value, clamped if needed.
#[inline]
pub fn get(&self) -> f32 {
self.calc_clamping_mode
.map_or(self.value, |mode| mode.clamp(self.value))
crate::values::normalize(
self.calc_clamping_mode
.map_or(self.value, |mode| mode.clamp(self.value)),
)
.min(f32::MAX)
.max(f32::MIN)
}
#[allow(missing_docs)]
@ -316,14 +320,7 @@ impl ToCss for Number {
where
W: Write,
{
if self.calc_clamping_mode.is_some() {
dest.write_str("calc(")?;
}
self.value.to_css(dest)?;
if self.calc_clamping_mode.is_some() {
dest.write_char(')')?;
}
Ok(())
serialize_number(self.value, self.calc_clamping_mode.is_some(), dest)
}
}