tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 0ab0e71fbce04e7cba91c7a372df5251821a44cd
parent 99b0d82c4a92a28d894d4106cb03b8bc514b9313
Author: Swarup Ukil <sukil@mozilla.com>
Date:   Fri,  9 Jan 2026 20:11:44 +0000

Bug 1998245 - Implement support for <number> and <attr-unit>. r=emilio,firefox-style-system-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D277823

Diffstat:
MCargo.lock | 2++
Mservo/components/style/Cargo.toml | 2++
Mservo/components/style/custom_properties.rs | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mservo/components/style/properties_and_values/syntax/mod.rs | 13++++++++++++-
Mservo/components/style/values/generics/calc.rs | 15+++++++++++++--
Mservo/components/style/values/specified/calc.rs | 2+-
Mservo/components/style/values/specified/counters.rs | 2+-
Mtesting/web-platform/meta/css/css-values/attr-all-types.html.ini | 103++++---------------------------------------------------------------------------
Mtesting/web-platform/meta/css/css-values/attr-cycle.html.ini | 9---------
Dtesting/web-platform/meta/css/css-values/attr-length-specified.html.ini | 3---
Mtesting/web-platform/meta/css/css-values/attr-null-namespace.xhtml.ini | 3---
Mtesting/web-platform/meta/css/css-values/attr-security.html.ini | 3+++
Mtesting/web-platform/tests/css/css-values/attr-all-types.html | 16+++++++++++++++-
13 files changed, 144 insertions(+), 151 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -6694,6 +6694,8 @@ dependencies = [ "smallvec", "static_assertions", "static_prefs", + "strum", + "strum_macros", "style_derive", "style_traits", "thin-vec", diff --git a/servo/components/style/Cargo.toml b/servo/components/style/Cargo.toml @@ -89,6 +89,8 @@ smallvec = "1.0" static_assertions = "1.1" static_prefs = { path = "../../../modules/libpref/init/static_prefs" } string_cache = { version = "0.8", optional = true } +strum = "0.27" +strum_macros = "0.27" style_derive = {path = "../style_derive"} style_traits = {path = "../style_traits"} to_shmem = {path = "../to_shmem"} diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs @@ -27,6 +27,7 @@ use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet}; use crate::stylesheets::UrlExtraData; use crate::stylist::Stylist; use crate::values::computed::{self, ToComputedValue}; +use crate::values::generics::calc::SortKey as AttrUnit; use crate::values::specified::FontRelativeLength; use crate::values::AtomIdent; use crate::Atom; @@ -476,8 +477,9 @@ enum SubstitutionFunctionKind { #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)] enum AttributeType { None, + RawString, Type(Descriptor), - Unit, + Unit(AttrUnit), } #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] @@ -841,18 +843,12 @@ fn parse_declaration_value_block<'i, 't>( name.as_ref() }); - let mut attribute_syntax = AttributeType::None; - if substitution_kind == SubstitutionFunctionKind::Attr - && input - .try_parse(|input| input.expect_function_matching("type")) - .is_ok() - { - // TODO(descalante): determine what to do for `type(garbage)` bug 2006626 - attribute_syntax = input - .parse_nested_block(Descriptor::from_css_parser) - .ok() - .map_or(AttributeType::None, AttributeType::Type); - } + let attribute_syntax = + if substitution_kind == SubstitutionFunctionKind::Attr { + parse_attr_type(input) + } else { + AttributeType::None + }; // We want the order of the references to match source order. So we need to reserve our slot // now, _before_ parsing our fallback. Note that we don't care if parsing fails after all, since @@ -972,6 +968,32 @@ fn parse_declaration_value_block<'i, 't>( Ok((first_token_type, last_token_type)) } +/// Parse <attr-type> = type( <syntax> ) | raw-string | number | <attr-unit>. +/// https://drafts.csswg.org/css-values-5/#attr-notation +fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType { + input + .try_parse(|input| { + Ok(match input.next()? { + Token::Function(ref name) if name.eq_ignore_ascii_case("type") => { + AttributeType::Type(input.parse_nested_block(Descriptor::from_css_parser)?) + }, + Token::Ident(ref ident) => { + if ident.eq_ignore_ascii_case("raw-string") { + AttributeType::RawString + } else { + let unit = AttrUnit::from_ident(ident).map_err(|_| { + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + })?; + AttributeType::Unit(unit) + } + }, + Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage), + _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + }) + }) + .unwrap_or(AttributeType::None) +} + /// A struct that takes care of encapsulating the cascade process for custom properties. pub struct CustomPropertiesBuilder<'a, 'b: 'a> { seen: PrecomputedHashSet<&'a Name>, @@ -2149,6 +2171,13 @@ fn substitute_one_reference<'a>( references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>, attr_provider: &dyn AttributeProvider, ) -> Result<Substitution<'a>, ()> { + let simple_subst = |s: &str| { + Some(Substitution::new( + Cow::Owned(quoted_css_string(s)), + TokenSerializationType::Nothing, + TokenSerializationType::Nothing, + )) + }; let substitution: Option<_> = match reference.substitution_kind { SubstitutionFunctionKind::Var => { let registration = stylist.get_custom_property_registration(&reference.name); @@ -2163,27 +2192,56 @@ fn substitute_one_reference<'a>( .get(&reference.name, device, url_data) .map(Substitution::from_value) }, + // https://drafts.csswg.org/css-values-5/#attr-substitution SubstitutionFunctionKind::Attr => attr_provider .get_attr(AtomIdent::cast(&reference.name)) - .and_then(|attr| { - let AttributeType::Type(syntax) = &reference.attribute_syntax else { - return Some(Substitution::new( - Cow::Owned(quoted_css_string(&attr)), - TokenSerializationType::Nothing, - TokenSerializationType::Nothing, - )); - }; - let mut input = ParserInput::new(&attr); - let mut parser = Parser::new(&mut input); - let value = SpecifiedRegisteredValue::parse( - &mut parser, - syntax, - url_data, - AllowComputationallyDependent::Yes, - ) - .ok()?; - Some(Substitution::from_value(value.to_variable_value())) - }), + .map_or_else( + || { + // Special case when fallback and <attr-type> are omitted. + // See FAILURE: https://drafts.csswg.org/css-values-5/#attr-substitution + if reference.fallback.is_none() + && reference.attribute_syntax == AttributeType::None + { + simple_subst("") + } else { + None + } + }, + |attr| { + let mut input = ParserInput::new(&attr); + let mut parser = Parser::new(&mut input); + match &reference.attribute_syntax { + AttributeType::Unit(unit) => { + let css = { + // Verify that attribute data is a <number-token>. + parser.expect_number().ok()?; + let mut s = attr.clone(); + s.push_str(unit.as_ref()); + s + }; + let serialization = match unit { + AttrUnit::Number => TokenSerializationType::Number, + AttrUnit::Percentage => TokenSerializationType::Percentage, + _ => TokenSerializationType::Dimension, + }; + let value = + ComputedValue::new(css, url_data, serialization, serialization); + Some(Substitution::from_value(value)) + }, + AttributeType::Type(syntax) => { + let value = SpecifiedRegisteredValue::parse( + &mut parser, + syntax, + url_data, + AllowComputationallyDependent::Yes, + ) + .ok()?; + Some(Substitution::from_value(value.to_variable_value())) + }, + AttributeType::RawString | AttributeType::None => simple_subst(&attr), + } + }, + ), }; if let Some(s) = substitution { diff --git a/servo/components/style/properties_and_values/syntax/mod.rs b/servo/components/style/properties_and_values/syntax/mod.rs @@ -58,8 +58,19 @@ impl Descriptor { /// https://drafts.csswg.org/css-values-5/#typedef-syntax #[inline] pub fn from_css_parser<'i>(input: &mut CSSParser<'i, '_>) -> Result<Self, StyleParseError<'i>> { - //TODO(bug 2006624): Should also accept <syntax-string> let mut components = vec![]; + + if input.try_parse(|i| i.expect_delim('*')).is_ok() { + return Ok(Self::universal()); + } + + // Parse <syntax-string> if given. + if let Ok(syntax_string) = input.try_parse(|i| i.expect_string_cloned()) { + return Self::from_str(syntax_string.as_ref(), /* save_specified = */ true).or_else( + |err| Err(input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))), + ); + } + loop { let name = Self::try_parse_component_name(input).map_err(|err| { input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err)) diff --git a/servo/components/style/values/generics/calc.rs b/servo/components/style/values/generics/calc.rs @@ -11,9 +11,11 @@ use crate::values::generics::length::GenericAnchorSizeFunction; use crate::values::generics::position::{GenericAnchorFunction, GenericAnchorSide}; use num_traits::Zero; use smallvec::SmallVec; +use std::convert::AsRef; use std::fmt::{self, Write}; use std::ops::{Add, Mul, Neg, Rem, Sub}; use std::{cmp, mem}; +use strum_macros::AsRefStr; use style_traits::{CssWriter, NumericValue, ToCss, ToTyped, TypedValue}; use thin_vec::ThinVec; @@ -116,10 +118,16 @@ pub enum RoundingStrategy { /// This determines the order in which we serialize members of a calc() sum. /// /// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive( + AsRefStr, Clone, Copy, Debug, Eq, Ord, Parse, PartialEq, PartialOrd, MallocSizeOf, ToShmem, +)] +#[strum(serialize_all = "lowercase")] #[allow(missing_docs)] pub enum SortKey { + #[strum(serialize = "")] Number, + #[css(skip)] + #[strum(serialize = "%")] Percentage, Cap, Ch, @@ -147,6 +155,7 @@ pub enum SortKey { Lvmax, Lvmin, Lvw, + Ms, Px, Rcap, Rch, @@ -154,7 +163,7 @@ pub enum SortKey { Rex, Ric, Rlh, - Sec, + S, // Sec Svb, Svh, Svi, @@ -167,7 +176,9 @@ pub enum SortKey { Vmax, Vmin, Vw, + #[css(skip)] ColorComponent, + #[css(skip)] Other, } diff --git a/servo/components/style/values/specified/calc.rs b/servo/components/style/values/specified/calc.rs @@ -306,7 +306,7 @@ impl generic::CalcNodeLeaf for Leaf { match *self { Self::Number(..) => SortKey::Number, Self::Percentage(..) => SortKey::Percentage, - Self::Time(..) => SortKey::Sec, + Self::Time(..) => SortKey::S, Self::Resolution(..) => SortKey::Dppx, Self::Angle(..) => SortKey::Deg, Self::Length(ref l) => match *l { diff --git a/servo/components/style/values/specified/counters.rs b/servo/components/style/values/specified/counters.rs @@ -226,7 +226,7 @@ impl Parse for Content { let style = Content::parse_counter_style(context, input); Ok(generics::ContentItem::Counters(name, separator, style)) }), - "attr" => input.parse_nested_block(|input| { + "attr" if !static_prefs::pref!("layout.css.attr.enabled") => input.parse_nested_block(|input| { Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?)) }), _ => { diff --git a/testing/web-platform/meta/css/css-values/attr-all-types.html.ini b/testing/web-platform/meta/css/css-values/attr-all-types.html.ini @@ -1,108 +1,15 @@ [attr-all-types.html] - [CSS Values and Units Test: attr] - expected: FAIL - - [CSS Values and Units Test: attr 2] - expected: FAIL - - [CSS Values and Units Test: attr 3] - expected: FAIL - - [CSS Values and Units Test: attr 6] - expected: FAIL - - [CSS Values and Units Test: attr 7] - expected: FAIL - - [CSS Values and Units Test: attr 11] - expected: FAIL - - [CSS Values and Units Test: attr 55] - expected: FAIL - - [CSS Values and Units Test: attr 56] - expected: FAIL - - [CSS Values and Units Test: attr 57] - expected: FAIL - - [CSS Values and Units Test: attr 58] - expected: FAIL - - [CSS Values and Units Test: attr 59] - expected: FAIL - - [CSS Values and Units Test: attr 63] - expected: FAIL - - [CSS Values and Units Test: attr 64] - expected: FAIL - - [CSS Values and Units Test: attr 66] - expected: FAIL - - [CSS Values and Units Test: attr 68] - expected: FAIL - - [CSS Values and Units Test: attr 70] - expected: FAIL - - [CSS Values and Units Test: attr 72] - expected: FAIL - [CSS Values and Units Test: attr 74] expected: FAIL - - [CSS Values and Units Test: attr 76] - expected: FAIL - - [CSS Values and Units Test: attr 78] - expected: FAIL - - [CSS Values and Units Test: attr 80] - expected: FAIL - - [CSS Values and Units Test: attr 82] - expected: FAIL - - [CSS Values and Units Test: attr 84] - expected: FAIL - - [CSS Values and Units Test: attr 86] - expected: FAIL - - [CSS Values and Units Test: attr 88] - expected: FAIL - - [CSS Values and Units Test: attr 90] - expected: FAIL - - [CSS Values and Units Test: attr 92] - expected: FAIL - - [CSS Values and Units Test: attr 94] - expected: FAIL - - [CSS Values and Units Test: attr 96] - expected: FAIL - - [CSS Values and Units Test: attr 108] - expected: FAIL - - [CSS Values and Units Test: attr 1] - expected: FAIL - - [CSS Values and Units Test: attr 12] - expected: FAIL - - [CSS Values and Units Test: attr 65] + + [CSS Values and Units Test: attr 75] expected: FAIL - [CSS Values and Units Test: attr 98] + [CSS Values and Units Test: attr 76] expected: FAIL - [CSS Values and Units Test: attr 110] + [CSS Values and Units Test: attr 77] expected: FAIL - [CSS Values and Units Test: attr 69] + [CSS Values and Units Test: attr 79] expected: FAIL diff --git a/testing/web-platform/meta/css/css-values/attr-cycle.html.ini b/testing/web-platform/meta/css/css-values/attr-cycle.html.ini @@ -1,7 +1,4 @@ [attr-cycle.html] - [CSS Values and Units Test: attr] - expected: FAIL - [CSS Values and Units Test: attr 2] expected: FAIL @@ -35,18 +32,12 @@ [CSS Values and Units Test: attr 17] expected: FAIL - [CSS Values and Units Test: attr 21] - expected: FAIL - [CSS Values and Units Test: attr 19] expected: FAIL [CSS Values and Units Test: attr 22] expected: FAIL - [CSS Values and Units Test: attr 23] - expected: FAIL - [CSS Values and Units Test: attr 26] expected: FAIL diff --git a/testing/web-platform/meta/css/css-values/attr-length-specified.html.ini b/testing/web-platform/meta/css/css-values/attr-length-specified.html.ini @@ -1,3 +0,0 @@ -[attr-length-specified.html] - [Unit (em) number] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-values/attr-null-namespace.xhtml.ini b/testing/web-platform/meta/css/css-values/attr-null-namespace.xhtml.ini @@ -1,6 +1,3 @@ [attr-null-namespace.xhtml] - [Attribute in null-namespace is substituted] - expected: FAIL - [Attribute in null-namespace is substituted (JS)] expected: FAIL diff --git a/testing/web-platform/meta/css/css-values/attr-security.html.ini b/testing/web-platform/meta/css/css-values/attr-security.html.ini @@ -20,6 +20,9 @@ ['background-image: image-set(if(style(--true): url(https://does-not-exist.test/404.png);\n style(--condition-val): url(https://does-not-exist.test/404.png);\n else: url(https://does-not-exist.test/404.png);))' with data-foo="attr(data-foo type(*))"] expected: FAIL + ['background-image: attr(data-foo type(*))' with data-foo="url(https://does-not-exist.test/404.png), linear-gradient(black, white)"] + expected: FAIL + ['--x: image-set(if(style(--condition-val: if(style(--true): attr(data-foo type(*));)): url(https://does-not-exist.test/404.png);))' with data-foo="3"] expected: FAIL diff --git a/testing/web-platform/tests/css/css-values/attr-all-types.html b/testing/web-platform/tests/css/css-values/attr-all-types.html @@ -35,7 +35,7 @@ const dimensionTypeToUnits = { "length": ["em", "ex", "cap", "ch", "ic", "rem", "lh", "rlh", "vw", "vh", "vi", "vb", "vmin", "vmax"], "angle": ["deg", "grad", "rad", "turn"], - "time": ["ms", "ms"], + "time": ["s", "ms"], "frequency": ["Hz", "kHz"] }; @@ -127,7 +127,9 @@ test_valid_attr('content', 'attr(data-foo type(<string>))', '"attr(data-foo)"', '"attr(data-foo)"'); test_valid_attr('content', 'attr(data-foo)', '', '""'); test_valid_attr('font-family', 'attr(non-existent)', '', '""'); + test_valid_attr('font-family', 'attr(non-existent, serif)', '', 'serif'); test_valid_attr('font-family', 'attr(non-existent string)', '', ''); + test_valid_attr('font-family', 'attr(non-existent raw-string)', '', ''); test_invalid_attr('font-family', 'attr(non-existent type(<string>))', ''); test_valid_attr('animation-name', 'attr(data-foo type(<custom-ident>))', 'anim', 'anim'); @@ -177,10 +179,21 @@ test_valid_attr('transition-duration', 'attr(data-foo type(<time>), 30s)', '10m', '30s'); test_valid_attr('transition-duration', 'attr(data-foo type(<time>), calc(10s + 20s))', '10m', '30s'); + test_valid_attr('width', 'attr(data-foo type(invalid))', '0px', ''); + test_valid_attr('width', 'attr(data-foo type(invalid), 10px)', '0px', '10px'); + test_invalid_attr('width', 'attr(data-foo type(invalid |), 10px)', '0px'); + + test_valid_attr('background-color', 'attr(data-foo type("<color>")', '#ff0099aa', '#ff0099aa'); + test_valid_attr('background-color', 'attr(data-foo type("invalid | blue")', 'blue', 'blue'); + test_valid_attr('width', 'attr(data-foo type("<length> | <percentage>")', '10%', '10%'); + test_invalid_attr('width', 'attr(data-foo type("<invalid>")', '10px'); + test_valid_attr('height', 'attr(data-foo px)', '10', '10px'); test_valid_attr('width', 'calc(attr(data-foo px) + 1px)', '10', '11px'); test_valid_attr('font-size', 'attr(data-foo %)', '10', '10%'); test_valid_attr('--x', 'attr(data-foo px) 11px', '10', '10px 11px'); + test_valid_attr('height', 'attr(data-foo px)', 'abc', ''); + test_valid_attr('width', 'attr(non-existent px, 3px)', '10', '3px'); test_valid_attr('font-weight', 'attr(data-foo number)', '10', '10'); @@ -244,6 +257,7 @@ test_invalid_attr('width', 'attr(data-foo px)', '10px'); test_invalid_attr('width', 'attr(data-foo <px>)', '10'); test_invalid_attr('width', 'attr(data-foo xx)', '10'); + test_invalid_attr('width', 'attr(data-foo xx, 3px)', '10'); test_invalid_attr('width', 'attr(data-foo px)', 'calc(1 + 3)'); test_invalid_attr('width', 'attr(data-foo px)', 'var(--number)');