diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
commit | 8dd16259287f58f9273002717ec4d27e97127719 (patch) | |
tree | 3863e62a53829a84037444beab3abd4ed9dfc7d0 /servo/components | |
parent | Releasing progress-linux version 126.0.1-1~progress7.99u1. (diff) | |
download | firefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz firefox-8dd16259287f58f9273002717ec4d27e97127719.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components')
46 files changed, 1318 insertions, 1032 deletions
diff --git a/servo/components/malloc_size_of/Cargo.toml b/servo/components/malloc_size_of/Cargo.toml index cd5deaea44..fe45affa8a 100644 --- a/servo/components/malloc_size_of/Cargo.toml +++ b/servo/components/malloc_size_of/Cargo.toml @@ -31,7 +31,7 @@ accountable-refcell = { version = "0.2.0", optional = true } app_units = "0.7" content-security-policy = { version = "0.4.0", features = ["serde"], optional = true } crossbeam-channel = { version = "0.4", optional = true } -cssparser = "0.33" +cssparser = "0.34" dom = { path = "../../../dom/base/rust" } euclid = "0.22" hyper = { version = "0.12", optional = true } diff --git a/servo/components/selectors/Cargo.toml b/servo/components/selectors/Cargo.toml index 88360d7dd2..eec2ee9a90 100644 --- a/servo/components/selectors/Cargo.toml +++ b/servo/components/selectors/Cargo.toml @@ -19,7 +19,7 @@ bench = [] [dependencies] bitflags = "2" -cssparser = "0.33" +cssparser = "0.34" derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] } fxhash = "0.2" log = "0.4" diff --git a/servo/components/selectors/context.rs b/servo/components/selectors/context.rs index 289b081b64..c6870c6c96 100644 --- a/servo/components/selectors/context.rs +++ b/servo/components/selectors/context.rs @@ -70,6 +70,19 @@ impl VisitedHandlingMode { } } +/// The mode to use whether we should matching rules inside @starting-style. +/// https://drafts.csswg.org/css-transitions-2/#starting-style +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum IncludeStartingStyle { + /// All without rules inside @starting-style. This is for the most common case because the + /// primary/pseudo styles doesn't use rules inside @starting-style. + No, + /// Get the starting style. The starting style for an element as the after-change style with + /// @starting-style rules applied in addition. In other words, this matches all rules, + /// including rules inside @starting-style. + Yes, +} + /// Whether we need to set selector invalidation flags on elements for this /// match request. #[derive(Clone, Copy, Debug, PartialEq)] @@ -191,6 +204,12 @@ where /// Controls how matching for links is handled. visited_handling: VisitedHandlingMode, + /// Controls if we should match rules in @starting-style. + pub include_starting_style: IncludeStartingStyle, + + /// Whether there are any rules inside @starting-style. + pub has_starting_style: bool, + /// The current nesting level of selectors that we're matching. nesting_level: usize, @@ -239,6 +258,7 @@ where bloom_filter, selector_caches, VisitedHandlingMode::AllLinksUnvisited, + IncludeStartingStyle::No, quirks_mode, needs_selector_flags, matching_for_invalidation, @@ -251,6 +271,7 @@ where bloom_filter: Option<&'a BloomFilter>, selector_caches: &'a mut SelectorCaches, visited_handling: VisitedHandlingMode, + include_starting_style: IncludeStartingStyle, quirks_mode: QuirksMode, needs_selector_flags: NeedsSelectorFlags, matching_for_invalidation: MatchingForInvalidation, @@ -259,6 +280,8 @@ where matching_mode, bloom_filter, visited_handling, + include_starting_style, + has_starting_style: false, quirks_mode, classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), needs_selector_flags, diff --git a/servo/components/selectors/kleene_value.rs b/servo/components/selectors/kleene_value.rs index 58141c1156..96d6457e60 100644 --- a/servo/components/selectors/kleene_value.rs +++ b/servo/components/selectors/kleene_value.rs @@ -38,6 +38,7 @@ impl KleeneValue { /// Return true if any result of f() is true. Otherwise, return the strongest value seen. /// Returns false if empty, like that of `Iterator`. + #[inline(always)] pub fn any<T>( iter: impl Iterator<Item = T>, f: impl FnMut(T) -> Self, @@ -47,6 +48,7 @@ impl KleeneValue { /// Return false if any results of f() is false. Otherwise, return the strongest value seen. /// Returns true if empty, opposite of `Iterator`. + #[inline(always)] pub fn any_false<T>( iter: impl Iterator<Item = T>, f: impl FnMut(T) -> Self, @@ -54,6 +56,7 @@ impl KleeneValue { Self::any_value(iter, Self::False, Self::True, f) } + #[inline(always)] fn any_value<T>( iter: impl Iterator<Item = T>, value: Self, diff --git a/servo/components/selectors/matching.rs b/servo/components/selectors/matching.rs index 282d04064b..6eaafb3038 100644 --- a/servo/components/selectors/matching.rs +++ b/servo/components/selectors/matching.rs @@ -362,12 +362,7 @@ where let iter = selector.iter_from(selector.len() - from_offset); debug_assert!( - iter.clone().next().is_some() || - (from_offset != selector.len() && - matches!( - selector.combinator_at_parse_order(from_offset), - Combinator::SlotAssignment | Combinator::PseudoElement - )), + iter.clone().next().is_some() || from_offset != selector.len(), "Got the math wrong: {:?} | {:?} | {} {}", selector, selector.iter_raw_match_order().as_slice(), diff --git a/servo/components/style/Cargo.toml b/servo/components/style/Cargo.toml index d6e37d4903..7aa9637f70 100644 --- a/servo/components/style/Cargo.toml +++ b/servo/components/style/Cargo.toml @@ -30,7 +30,7 @@ arrayvec = "0.7" atomic_refcell = "0.1" bitflags = "2" byteorder = "1.0" -cssparser = "0.33" +cssparser = "0.34" derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "deref_mut", "from"] } dom = { path = "../../../dom/base/rust" } new_debug_unreachable = "1.0" @@ -39,7 +39,7 @@ euclid = "0.22" fxhash = "0.2" html5ever = {version = "0.24", optional = true} icu_segmenter = { version = "1.4", default-features = false, features = ["auto", "compiled_data"] } -indexmap = {version = "1.0", features = ["std"]} +indexmap = {version = "2", features = ["std"]} itertools = "0.10" itoa = "1.0" lazy_static = "1" @@ -52,7 +52,6 @@ num_cpus = {version = "1.1.0"} num-integer = "0.1" num-traits = "0.2" num-derive = "0.4" -owning_ref = "0.4" parking_lot = "0.12" precomputed-hash = "0.1.1" rayon = "1" diff --git a/servo/components/style/bloom.rs b/servo/components/style/bloom.rs index 63be881505..a5f47d8b41 100644 --- a/servo/components/style/bloom.rs +++ b/servo/components/style/bloom.rs @@ -10,11 +10,8 @@ use crate::dom::{SendElement, TElement}; use crate::LocalName; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; -use owning_ref::OwningHandle; use selectors::bloom::BloomFilter; -use servo_arc::Arc; use smallvec::SmallVec; -use std::mem::ManuallyDrop; thread_local! { /// Bloom filters are large allocations, so we store them in thread-local storage @@ -24,10 +21,12 @@ thread_local! { /// We intentionally leak this from TLS because we don't have the guarantee /// of TLS destructors to run in worker threads. /// + /// Also, leaking it guarantees that we can borrow it indefinitely. + /// /// We could change this once https://github.com/rayon-rs/rayon/issues/688 - /// is fixed, hopefully. - static BLOOM_KEY: ManuallyDrop<Arc<AtomicRefCell<BloomFilter>>> = - ManuallyDrop::new(Arc::new_leaked(Default::default())); + /// is fixed, hopefully, which point we'd need to change the filter member below to be an + /// arc and carry an owning reference around or so. + static BLOOM_KEY: &'static AtomicRefCell<BloomFilter> = Box::leak(Default::default()); } /// A struct that allows us to fast-reject deep descendant selectors avoiding @@ -66,7 +65,7 @@ pub struct StyleBloom<E: TElement> { /// was created. We use AtomicRefCell so that this is all |Send|, which allows /// StyleBloom to live in ThreadLocalStyleContext, which is dropped from the /// parent thread. - filter: OwningHandle<Arc<AtomicRefCell<BloomFilter>>, AtomicRefMut<'static, BloomFilter>>, + filter: AtomicRefMut<'static, BloomFilter>, /// The stack of elements that this bloom filter contains, along with the /// number of hashes pushed for each element. @@ -152,9 +151,7 @@ impl<E: TElement> StyleBloom<E> { // See https://github.com/servo/servo/pull/18420#issuecomment-328769322 #[inline(never)] pub fn new() -> Self { - let bloom_arc = BLOOM_KEY.with(|b| Arc::clone(&*b)); - let filter = - OwningHandle::new_with_fn(bloom_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut()); + let filter = BLOOM_KEY.with(|b| b.borrow_mut()); debug_assert!( filter.is_zeroed(), "Forgot to zero the bloom filter last time" diff --git a/servo/components/style/color/color_function.rs b/servo/components/style/color/color_function.rs index 2edb4fff3b..426256a2e9 100644 --- a/servo/components/style/color/color_function.rs +++ b/servo/components/style/color/color_function.rs @@ -4,7 +4,7 @@ //! Output of parsing a color function, e.g. rgb(..), hsl(..), color(..) -use crate::values::normalize; +use crate::{color::ColorFlags, values::normalize}; use cssparser::color::{PredefinedColorSpace, OPAQUE}; use super::{ @@ -30,6 +30,7 @@ pub enum ColorFunction { ColorComponent<NumberOrPercentage>, // saturation ColorComponent<NumberOrPercentage>, // lightness ColorComponent<NumberOrPercentage>, // alpha + bool, // is_legacy_syntax ), /// <https://drafts.csswg.org/css-color-4/#the-hwb-notation> Hwb( @@ -37,6 +38,7 @@ pub enum ColorFunction { ColorComponent<NumberOrPercentage>, // whiteness ColorComponent<NumberOrPercentage>, // blackness ColorComponent<NumberOrPercentage>, // alpha + bool, // is_legacy_syntax ), /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch> Lab( @@ -104,31 +106,43 @@ impl ColorFunction { AbsoluteColor::srgb_legacy(r, g, b, alpha!(alpha).unwrap_or(0.0)) }, - ColorFunction::Hsl(h, s, l, alpha) => { + ColorFunction::Hsl(h, s, l, alpha, is_legacy_syntax) => { // Percent reference range for S and L: 0% = 0.0, 100% = 100.0 const LIGHTNESS_RANGE: f32 = 100.0; const SATURATION_RANGE: f32 = 100.0; - AbsoluteColor::new( + let mut result = AbsoluteColor::new( ColorSpace::Hsl, value!(h).map(|angle| normalize_hue(angle.degrees())), value!(s).map(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)), value!(l).map(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)), alpha!(alpha), - ) + ); + + if *is_legacy_syntax { + result.flags.insert(ColorFlags::IS_LEGACY_SRGB); + } + + result }, - ColorFunction::Hwb(h, w, b, alpha) => { + ColorFunction::Hwb(h, w, b, alpha, is_legacy_syntax) => { // Percent reference range for W and B: 0% = 0.0, 100% = 100.0 const WHITENESS_RANGE: f32 = 100.0; const BLACKNESS_RANGE: f32 = 100.0; - AbsoluteColor::new( + let mut result = AbsoluteColor::new( ColorSpace::Hwb, value!(h).map(|angle| normalize_hue(angle.degrees())), value!(w).map(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)), value!(b).map(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)), alpha!(alpha), - ) + ); + + if *is_legacy_syntax { + result.flags.insert(ColorFlags::IS_LEGACY_SRGB); + } + + result }, ColorFunction::Lab(l, a, b, alpha) => { // for L: 0% = 0.0, 100% = 100.0 diff --git a/servo/components/style/color/component.rs b/servo/components/style/color/component.rs index 5f9d8a137e..7edebb6267 100644 --- a/servo/components/style/color/component.rs +++ b/servo/components/style/color/component.rs @@ -4,6 +4,10 @@ //! Parse/serialize and resolve a single color component. +use super::{ + parsing::{rcs_enabled, ChannelKeyword}, + AbsoluteColor, +}; use crate::{ parser::ParserContext, values::{ @@ -60,6 +64,12 @@ impl<ValueType> ColorComponent<ValueType> { /// An utility trait that allows the construction of [ColorComponent] /// `ValueType`'s after parsing a color component. pub trait ColorComponentType: Sized { + // TODO(tlouw): This function should be named according to the rules in the spec + // stating that all the values coming from color components are + // numbers and that each has their own rules dependeing on types. + /// Construct a new component from a single value. + fn from_value(value: f32) -> Self; + /// Return the [CalcUnits] flags that the impl can handle. fn units() -> CalcUnits; @@ -77,6 +87,7 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> { context: &ParserContext, input: &mut Parser<'i, 't>, allow_none: bool, + origin_color: Option<&AbsoluteColor>, ) -> Result<Self, ParseError<'i>> { let location = input.current_source_location(); @@ -84,11 +95,48 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> { Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { Ok(ColorComponent::None) }, + ref t @ Token::Ident(ref ident) if origin_color.is_some() => { + if let Ok(channel_keyword) = ChannelKeyword::from_ident(ident) { + if let Ok(value) = origin_color + .unwrap() + .get_component_by_channel_keyword(channel_keyword) + { + Ok(Self::Value(ValueType::from_value(value.unwrap_or(0.0)))) + } else { + Err(location.new_unexpected_token_error(t.clone())) + } + } else { + Err(location.new_unexpected_token_error(t.clone())) + } + }, Token::Function(ref name) => { let function = SpecifiedCalcNode::math_function(context, name, location)?; - let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?; + let units = if rcs_enabled() { + ValueType::units() | CalcUnits::COLOR_COMPONENT + } else { + ValueType::units() + }; + let node = SpecifiedCalcNode::parse(context, input, function, units)?; - let Ok(resolved_leaf) = node.resolve() else { + let Ok(resolved_leaf) = node.resolve_map(|leaf| { + // + Ok(match leaf { + SpecifiedLeaf::ColorComponent(channel_keyword) => { + if let Some(origin_color) = origin_color { + if let Ok(value) = + origin_color.get_component_by_channel_keyword(*channel_keyword) + { + SpecifiedLeaf::Number(value.unwrap_or(0.0)) + } else { + return Err(()); + } + } else { + return Err(()); + } + }, + l => l.clone(), + }) + }) else { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); }; diff --git a/servo/components/style/color/mod.rs b/servo/components/style/color/mod.rs index f7e661cddf..7e427faa84 100644 --- a/servo/components/style/color/mod.rs +++ b/servo/components/style/color/mod.rs @@ -15,6 +15,7 @@ pub mod mix; pub mod parsing; mod to_css; +use self::parsing::ChannelKeyword; use component::ColorComponent; use cssparser::color::PredefinedColorSpace; @@ -449,6 +450,76 @@ impl AbsoluteColor { } } + /// Return the value of a component by its channel keyword. + pub fn get_component_by_channel_keyword( + &self, + channel_keyword: ChannelKeyword, + ) -> Result<Option<f32>, ()> { + if channel_keyword == ChannelKeyword::Alpha { + return Ok(self.alpha()); + } + + Ok(match self.color_space { + ColorSpace::Srgb => { + if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) { + match channel_keyword { + ChannelKeyword::R => self.c0().map(|v| v * 255.0), + ChannelKeyword::G => self.c1().map(|v| v * 255.0), + ChannelKeyword::B => self.c2().map(|v| v * 255.0), + _ => return Err(()), + } + } else { + match channel_keyword { + ChannelKeyword::R => self.c0(), + ChannelKeyword::G => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + } + } + }, + ColorSpace::Hsl => match channel_keyword { + ChannelKeyword::H => self.c0(), + ChannelKeyword::S => self.c1(), + ChannelKeyword::L => self.c2(), + _ => return Err(()), + }, + ColorSpace::Hwb => match channel_keyword { + ChannelKeyword::H => self.c0(), + ChannelKeyword::W => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + }, + ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword { + ChannelKeyword::L => self.c0(), + ChannelKeyword::A => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + }, + ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword { + ChannelKeyword::L => self.c0(), + ChannelKeyword::C => self.c1(), + ChannelKeyword::H => self.c2(), + _ => return Err(()), + }, + ColorSpace::SrgbLinear | + ColorSpace::DisplayP3 | + ColorSpace::A98Rgb | + ColorSpace::ProphotoRgb | + ColorSpace::Rec2020 => match channel_keyword { + ChannelKeyword::R => self.c0(), + ChannelKeyword::G => self.c1(), + ChannelKeyword::B => self.c2(), + _ => return Err(()), + }, + ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword { + ChannelKeyword::X => self.c0(), + ChannelKeyword::Y => self.c1(), + ChannelKeyword::Z => self.c2(), + _ => return Err(()), + }, + }) + } + /// Convert this color to the specified color space. pub fn to_color_space(&self, color_space: ColorSpace) -> Self { use ColorSpace::*; diff --git a/servo/components/style/color/parsing.rs b/servo/components/style/color/parsing.rs index 68bcee6c56..0c8fb2dbaf 100644 --- a/servo/components/style/color/parsing.rs +++ b/servo/components/style/color/parsing.rs @@ -4,17 +4,15 @@ #![deny(missing_docs)] -//! Fairly complete css-color implementation. -//! Relative colors, color-mix, system colors, and other such things require better calc() support -//! and integration. +//! Parsing for CSS colors. use super::{ color_function::ColorFunction, component::{ColorComponent, ColorComponentType}, - AbsoluteColor, + AbsoluteColor, ColorFlags, ColorSpace, }; use crate::{ - parser::ParserContext, + parser::{Parse, ParserContext}, values::{ generics::calc::CalcUnits, specified::{ @@ -27,7 +25,6 @@ use cssparser::{ color::{clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE}, match_ignore_ascii_case, CowRcStr, Parser, Token, }; -use std::str::FromStr; use style_traits::{ParseError, StyleParseErrorKind}; /// Returns true if the relative color syntax pref is enabled. @@ -36,11 +33,35 @@ pub fn rcs_enabled() -> bool { static_prefs::pref!("layout.css.relative-color-syntax.enabled") } -impl From<u8> for ColorComponent<u8> { - #[inline] - fn from(value: u8) -> Self { - ColorComponent::Value(value) - } +/// Represents a channel keyword inside a color. +#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, PartialOrd, ToCss, ToShmem)] +pub enum ChannelKeyword { + /// alpha + Alpha, + /// a + A, + /// b, blackness, blue + B, + /// chroma + C, + /// green + G, + /// hue + H, + /// lightness + L, + /// red + R, + /// saturation + S, + /// whiteness + W, + /// x + X, + /// y + Y, + /// z + Z, } /// Return the named color with the given name. @@ -65,7 +86,7 @@ pub fn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> { /// Parse a CSS color using the specified [`ColorParser`] and return a new color /// value on success. pub fn parse_color_with<'i, 't>( - color_parser: &ColorParser<'_, '_>, + context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<SpecifiedColor, ParseError<'i>> { let location = input.current_source_location(); @@ -80,7 +101,7 @@ pub fn parse_color_with<'i, 't>( let name = name.clone(); return input.parse_nested_block(|arguments| { Ok(SpecifiedColor::from_absolute_color( - parse_color_function(color_parser, name, arguments)?.resolve_to_absolute(), + parse_color_function(context, name, arguments)?.resolve_to_absolute(), )) }); }, @@ -92,19 +113,26 @@ pub fn parse_color_with<'i, 't>( /// Parse one of the color functions: rgba(), lab(), color(), etc. #[inline] fn parse_color_function<'i, 't>( - color_parser: &ColorParser<'_, '_>, + context: &ParserContext, name: CowRcStr<'i>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { + let origin_color = parse_origin_color(context, arguments)?; + + let component_parser = ComponentParser { + context, + origin_color, + }; + let color = match_ignore_ascii_case! { &name, - "rgb" | "rgba" => parse_rgb(color_parser, arguments), - "hsl" | "hsla" => parse_hsl(color_parser, arguments), - "hwb" => parse_hwb(color_parser, arguments), - "lab" => parse_lab_like(color_parser, arguments, ColorFunction::Lab), - "lch" => parse_lch_like(color_parser, arguments, ColorFunction::Lch), - "oklab" => parse_lab_like(color_parser, arguments, ColorFunction::Oklab), - "oklch" => parse_lch_like(color_parser, arguments, ColorFunction::Oklch), - "color" => parse_color_with_color_space(color_parser, arguments), + "rgb" | "rgba" => parse_rgb(&component_parser, arguments), + "hsl" | "hsla" => parse_hsl(&component_parser, arguments), + "hwb" => parse_hwb(&component_parser, arguments), + "lab" => parse_lab_like(&component_parser, arguments, ColorSpace::Lab, ColorFunction::Lab), + "lch" => parse_lch_like(&component_parser, arguments, ColorSpace::Lch, ColorFunction::Lch), + "oklab" => parse_lab_like(&component_parser, arguments, ColorSpace::Oklab, ColorFunction::Oklab), + "oklch" => parse_lch_like(&component_parser, arguments, ColorSpace::Oklch, ColorFunction::Oklch), + "color" =>parse_color_with_color_space(&component_parser, arguments), _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))), }?; @@ -113,34 +141,6 @@ fn parse_color_function<'i, 't>( Ok(color) } -fn parse_legacy_alpha<'i, 't>( - color_parser: &ColorParser<'_, '_>, - arguments: &mut Parser<'i, 't>, -) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { - if !arguments.is_exhausted() { - arguments.expect_comma()?; - color_parser.parse_number_or_percentage(arguments, false) - } else { - Ok(ColorComponent::Value(NumberOrPercentage::Number { - value: OPAQUE, - })) - } -} - -fn parse_modern_alpha<'i, 't>( - color_parser: &ColorParser<'_, '_>, - arguments: &mut Parser<'i, 't>, -) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { - if !arguments.is_exhausted() { - arguments.expect_delim('/')?; - color_parser.parse_number_or_percentage(arguments, true) - } else { - Ok(ColorComponent::Value(NumberOrPercentage::Number { - value: OPAQUE, - })) - } -} - impl ColorComponent<NumberOrPercentage> { /// Return true if the component contains a percentage. pub fn is_percentage(&self) -> Result<bool, ()> { @@ -153,9 +153,9 @@ impl ColorComponent<NumberOrPercentage> { /// Parse the relative color syntax "from" syntax `from <color>`. fn parse_origin_color<'i, 't>( - color_parser: &ColorParser<'_, '_>, + context: &ParserContext, arguments: &mut Parser<'i, 't>, -) -> Result<Option<SpecifiedColor>, ParseError<'i>> { +) -> Result<Option<AbsoluteColor>, ParseError<'i>> { if !rcs_enabled() { return Ok(None); } @@ -169,72 +169,112 @@ fn parse_origin_color<'i, 't>( return Ok(None); } + let location = arguments.current_source_location(); + // We still fail if we can't parse the origin color. - parse_color_with(color_parser, arguments).map(|color| Some(color)) + let origin_color = SpecifiedColor::parse(context, arguments)?; + + // Right now we only handle absolute colors. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1890972 + let Some(computed) = origin_color.to_computed_color(None) else { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + }; + + Ok(Some(computed.resolve_to_absolute(&AbsoluteColor::BLACK))) } #[inline] fn parse_rgb<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser.origin_color.map(|c| { + let mut c = c.to_color_space(ColorSpace::Srgb); + c.flags.insert(ColorFlags::IS_LEGACY_SRGB); + c + }), + }; let location = arguments.current_source_location(); - let maybe_red = color_parser.parse_number_or_percentage(arguments, true)?; + let maybe_red = component_parser.parse_number_or_percentage(arguments, true)?; // If the first component is not "none" and is followed by a comma, then we // are parsing the legacy syntax. Legacy syntax also doesn't support an // origin color. - let is_legacy_syntax = origin_color.is_none() && + let is_legacy_syntax = component_parser.origin_color.is_none() && !maybe_red.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); - let (red, green, blue, alpha) = if is_legacy_syntax { + Ok(if is_legacy_syntax { let Ok(is_percentage) = maybe_red.is_percentage() else { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); }; let (red, green, blue) = if is_percentage { let red = maybe_red.map_value(|v| clamp_unit_f32(v.to_number(1.0))); - let green = color_parser + let green = component_parser .parse_percentage(arguments, false)? .map_value(clamp_unit_f32); arguments.expect_comma()?; - let blue = color_parser + let blue = component_parser .parse_percentage(arguments, false)? .map_value(clamp_unit_f32); (red, green, blue) } else { let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - let green = color_parser + let green = component_parser .parse_number(arguments, false)? .map_value(clamp_floor_256_f32); arguments.expect_comma()?; - let blue = color_parser + let blue = component_parser .parse_number(arguments, false)? .map_value(clamp_floor_256_f32); (red, green, blue) }; - let alpha = parse_legacy_alpha(color_parser, arguments)?; + let alpha = component_parser.parse_legacy_alpha(arguments)?; - (red, green, blue, alpha) + ColorFunction::Rgb(red, green, blue, alpha) } else { - let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - let green = color_parser - .parse_number_or_percentage(arguments, true)? - .map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - let blue = color_parser - .parse_number_or_percentage(arguments, true)? - .map_value(|v| clamp_floor_256_f32(v.to_number(255.0))); - - let alpha = parse_modern_alpha(color_parser, arguments)?; + let green = component_parser.parse_number_or_percentage(arguments, true)?; + let blue = component_parser.parse_number_or_percentage(arguments, true)?; + + let alpha = component_parser.parse_modern_alpha(arguments)?; + + // When using the relative color syntax (having an origin color), the + // resulting color is always in the modern syntax. + if component_parser.origin_color.is_some() { + fn adjust(v: NumberOrPercentage) -> NumberOrPercentage { + if let NumberOrPercentage::Number { value } = v { + NumberOrPercentage::Number { + value: value / 255.0, + } + } else { + v + } + } + + ColorFunction::Color( + PredefinedColorSpace::Srgb, + maybe_red.map_value(adjust), + green.map_value(adjust), + blue.map_value(adjust), + alpha, + ) + } else { + fn clamp(v: NumberOrPercentage) -> u8 { + clamp_floor_256_f32(v.to_number(255.0)) + } - (red, green, blue, alpha) - }; + let red = maybe_red.map_value(clamp); + let green = green.map_value(clamp); + let blue = blue.map_value(clamp); - Ok(ColorFunction::Rgb(red, green, blue, alpha)) + ColorFunction::Rgb(red, green, blue, alpha) + } + }) } /// Parses hsl syntax. @@ -242,43 +282,48 @@ fn parse_rgb<'i, 't>( /// <https://drafts.csswg.org/css-color/#the-hsl-notation> #[inline] fn parse_hsl<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(ColorSpace::Hsl)), + }; - let hue = color_parser.parse_number_or_angle(arguments, true)?; + let hue = component_parser.parse_number_or_angle(arguments, true)?; // If the hue is not "none" and is followed by a comma, then we are parsing // the legacy syntax. Legacy syntax also doesn't support an origin color. - let is_legacy_syntax = origin_color.is_none() && + let is_legacy_syntax = component_parser.origin_color.is_none() && !hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); let (saturation, lightness, alpha) = if is_legacy_syntax { - let saturation = color_parser + let saturation = component_parser .parse_percentage(arguments, false)? .map_value(|unit_value| NumberOrPercentage::Percentage { unit_value }); arguments.expect_comma()?; - let lightness = color_parser + let lightness = component_parser .parse_percentage(arguments, false)? .map_value(|unit_value| NumberOrPercentage::Percentage { unit_value }); - ( - saturation, - lightness, - parse_legacy_alpha(color_parser, arguments)?, - ) + let alpha = component_parser.parse_legacy_alpha(arguments)?; + (saturation, lightness, alpha) } else { - let saturation = color_parser.parse_number_or_percentage(arguments, true)?; - let lightness = color_parser.parse_number_or_percentage(arguments, true)?; - ( - saturation, - lightness, - parse_modern_alpha(color_parser, arguments)?, - ) + let saturation = component_parser.parse_number_or_percentage(arguments, true)?; + let lightness = component_parser.parse_number_or_percentage(arguments, true)?; + let alpha = component_parser.parse_modern_alpha(arguments)?; + (saturation, lightness, alpha) }; - Ok(ColorFunction::Hsl(hue, saturation, lightness, alpha)) + Ok(ColorFunction::Hsl( + hue, + saturation, + lightness, + alpha, + component_parser.origin_color.is_none(), + )) } /// Parses hwb syntax. @@ -286,20 +331,29 @@ fn parse_hsl<'i, 't>( /// <https://drafts.csswg.org/css-color/#the-hbw-notation> #[inline] fn parse_hwb<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(ColorSpace::Hwb)), + }; - let (hue, whiteness, blackness, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_angle, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - )?; + let hue = component_parser.parse_number_or_angle(arguments, true)?; + let whiteness = component_parser.parse_number_or_percentage(arguments, true)?; + let blackness = component_parser.parse_number_or_percentage(arguments, true)?; - Ok(ColorFunction::Hwb(hue, whiteness, blackness, alpha)) + let alpha = component_parser.parse_modern_alpha(arguments)?; + + Ok(ColorFunction::Hwb( + hue, + whiteness, + blackness, + alpha, + component_parser.origin_color.is_none(), + )) } type IntoLabFn<Output> = fn( @@ -311,19 +365,23 @@ type IntoLabFn<Output> = fn( #[inline] fn parse_lab_like<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, + color_space: ColorSpace, into_color: IntoLabFn<ColorFunction>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(color_space)), + }; - let (lightness, a, b, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - )?; + let lightness = component_parser.parse_number_or_percentage(arguments, true)?; + let a = component_parser.parse_number_or_percentage(arguments, true)?; + let b = component_parser.parse_number_or_percentage(arguments, true)?; + + let alpha = component_parser.parse_modern_alpha(arguments)?; Ok(into_color(lightness, a, b, alpha)) } @@ -337,19 +395,23 @@ type IntoLchFn<Output> = fn( #[inline] fn parse_lch_like<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, + color_space: ColorSpace, into_color: IntoLchFn<ColorFunction>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(color_space)), + }; - let (lightness, chroma, hue, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_angle, - )?; + let lightness = component_parser.parse_number_or_percentage(arguments, true)?; + let chroma = component_parser.parse_number_or_percentage(arguments, true)?; + let hue = component_parser.parse_number_or_angle(arguments, true)?; + + let alpha = component_parser.parse_modern_alpha(arguments)?; Ok(into_color(lightness, chroma, hue, alpha)) } @@ -357,72 +419,24 @@ fn parse_lch_like<'i, 't>( /// Parse the color() function. #[inline] fn parse_color_with_color_space<'i, 't>( - color_parser: &ColorParser<'_, '_>, + component_parser: &ComponentParser<'_, '_>, arguments: &mut Parser<'i, 't>, ) -> Result<ColorFunction, ParseError<'i>> { - let _origin_color = parse_origin_color(color_parser, arguments)?; - - let color_space = { - let location = arguments.current_source_location(); - - let ident = arguments.expect_ident()?; - PredefinedColorSpace::from_str(ident) - .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))? + let color_space = PredefinedColorSpace::parse(arguments)?; + let component_parser = ComponentParser { + context: component_parser.context, + origin_color: component_parser + .origin_color + .map(|c| c.to_color_space(ColorSpace::from(color_space))), }; - let (c1, c2, c3, alpha) = parse_components( - color_parser, - arguments, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - ColorParser::parse_number_or_percentage, - )?; + let c1 = component_parser.parse_number_or_percentage(arguments, true)?; + let c2 = component_parser.parse_number_or_percentage(arguments, true)?; + let c3 = component_parser.parse_number_or_percentage(arguments, true)?; - Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha)) -} + let alpha = component_parser.parse_modern_alpha(arguments)?; -type ComponentParseResult<'i, R1, R2, R3> = Result< - ( - ColorComponent<R1>, - ColorComponent<R2>, - ColorComponent<R3>, - ColorComponent<NumberOrPercentage>, - ), - ParseError<'i>, ->; - -/// Parse the color components and alpha with the modern [color-4] syntax. -pub fn parse_components<'a, 'b: 'a, 'i, 't, F1, F2, F3, R1, R2, R3>( - color_parser: &ColorParser<'a, 'b>, - input: &mut Parser<'i, 't>, - f1: F1, - f2: F2, - f3: F3, -) -> ComponentParseResult<'i, R1, R2, R3> -where - F1: FnOnce( - &ColorParser<'a, 'b>, - &mut Parser<'i, 't>, - bool, - ) -> Result<ColorComponent<R1>, ParseError<'i>>, - F2: FnOnce( - &ColorParser<'a, 'b>, - &mut Parser<'i, 't>, - bool, - ) -> Result<ColorComponent<R2>, ParseError<'i>>, - F3: FnOnce( - &ColorParser<'a, 'b>, - &mut Parser<'i, 't>, - bool, - ) -> Result<ColorComponent<R3>, ParseError<'i>>, -{ - let r1 = f1(color_parser, input, true)?; - let r2 = f2(color_parser, input, true)?; - let r3 = f3(color_parser, input, true)?; - - let alpha = parse_modern_alpha(color_parser, input)?; - - Ok((r1, r2, r3, alpha)) + Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha)) } /// Either a number or a percentage. @@ -453,6 +467,10 @@ impl NumberOrPercentage { } impl ColorComponentType for NumberOrPercentage { + fn from_value(value: f32) -> Self { + Self::Number { value } + } + fn units() -> CalcUnits { CalcUnits::PERCENTAGE } @@ -503,6 +521,10 @@ impl NumberOrAngle { } impl ColorComponentType for NumberOrAngle { + fn from_value(value: f32) -> Self { + Self::Number { value } + } + fn units() -> CalcUnits { CalcUnits::ANGLE } @@ -538,6 +560,10 @@ impl ColorComponentType for NumberOrAngle { /// The raw f32 here is for <number>. impl ColorComponentType for f32 { + fn from_value(value: f32) -> Self { + value + } + fn units() -> CalcUnits { CalcUnits::empty() } @@ -560,19 +586,29 @@ impl ColorComponentType for f32 { } /// Used to parse the components of a color. -pub struct ColorParser<'a, 'b: 'a> { +pub struct ComponentParser<'a, 'b: 'a> { /// Parser context used for parsing the colors. pub context: &'a ParserContext<'b>, + /// The origin color that will be used to resolve relative components. + pub origin_color: Option<AbsoluteColor>, } -impl<'a, 'b: 'a> ColorParser<'a, 'b> { +impl<'a, 'b: 'a> ComponentParser<'a, 'b> { + /// Create a new [ColorParser] with the given context. + pub fn new(context: &'a ParserContext<'b>) -> Self { + Self { + context, + origin_color: None, + } + } + /// Parse an `<number>` or `<angle>` value. fn parse_number_or_angle<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> { - ColorComponent::parse(self.context, input, allow_none) + ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref()) } /// Parse a `<percentage>` value. @@ -587,7 +623,12 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> { // doesn't have any more overhead than just parsing a percentage on its // own. Ok( - match ColorComponent::<NumberOrPercentage>::parse(self.context, input, allow_none)? { + match ColorComponent::<NumberOrPercentage>::parse( + self.context, + input, + allow_none, + self.origin_color.as_ref(), + )? { ColorComponent::None => ColorComponent::None, ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => { ColorComponent::Value(unit_value) @@ -603,7 +644,7 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> { input: &mut Parser<'i, 't>, allow_none: bool, ) -> Result<ColorComponent<f32>, ParseError<'i>> { - ColorComponent::parse(self.context, input, allow_none) + ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref()) } /// Parse a `<number>` or `<percentage>` value. @@ -612,77 +653,34 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> { input: &mut Parser<'i, 't>, allow_none: bool, ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { - ColorComponent::parse(self.context, input, allow_none) + ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref()) } -} -/// This trait is used by the [`ColorParser`] to construct colors of any type. -pub trait FromParsedColor { - /// Construct a new color from the CSS `currentcolor` keyword. - fn from_current_color() -> Self; - - /// Construct a new color from red, green, blue and alpha components. - fn from_rgba( - red: ColorComponent<u8>, - green: ColorComponent<u8>, - blue: ColorComponent<u8>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from hue, saturation, lightness and alpha components. - fn from_hsl( - hue: ColorComponent<NumberOrAngle>, - saturation: ColorComponent<NumberOrPercentage>, - lightness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from hue, blackness, whiteness and alpha components. - fn from_hwb( - hue: ColorComponent<NumberOrAngle>, - whiteness: ColorComponent<NumberOrPercentage>, - blackness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `lab` notation. - fn from_lab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `lch` notation. - fn from_lch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `oklab` notation. - fn from_oklab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color from the `oklch` notation. - fn from_oklch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; - - /// Construct a new color with a predefined color space. - fn from_color_function( - color_space: PredefinedColorSpace, - c1: ColorComponent<NumberOrPercentage>, - c2: ColorComponent<NumberOrPercentage>, - c3: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self; + fn parse_legacy_alpha<'i, 't>( + &self, + arguments: &mut Parser<'i, 't>, + ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { + if !arguments.is_exhausted() { + arguments.expect_comma()?; + self.parse_number_or_percentage(arguments, false) + } else { + Ok(ColorComponent::Value(NumberOrPercentage::Number { + value: OPAQUE, + })) + } + } + + fn parse_modern_alpha<'i, 't>( + &self, + arguments: &mut Parser<'i, 't>, + ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { + if !arguments.is_exhausted() { + arguments.expect_delim('/')?; + self.parse_number_or_percentage(arguments, true) + } else { + Ok(ColorComponent::Value(NumberOrPercentage::Number { + value: self.origin_color.map(|c| c.alpha).unwrap_or(OPAQUE), + })) + } + } } diff --git a/servo/components/style/color/to_css.rs b/servo/components/style/color/to_css.rs index 350d8386f1..aba809759a 100644 --- a/servo/components/style/color/to_css.rs +++ b/servo/components/style/color/to_css.rs @@ -60,7 +60,13 @@ impl ToCss for AbsoluteColor { dest.write_char(')') }, - ColorSpace::Hsl | ColorSpace::Hwb => self.into_srgb_legacy().to_css(dest), + ColorSpace::Hsl | ColorSpace::Hwb => { + if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) { + self.into_srgb_legacy().to_css(dest) + } else { + self.to_color_space(ColorSpace::Srgb).to_css(dest) + } + }, ColorSpace::Oklab | ColorSpace::Lab | ColorSpace::Oklch | ColorSpace::Lch => { if let ColorSpace::Oklab | ColorSpace::Oklch = self.color_space { dest.write_str("ok")?; diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs index 766fe530d9..3a7beff90a 100644 --- a/servo/components/style/custom_properties.rs +++ b/servo/components/style/custom_properties.rs @@ -274,6 +274,11 @@ impl ComputedCustomProperties { name: &Name, value: ComputedRegisteredValue, ) { + // Broadening the assert to + // registration.syntax.is_universal() ^ value.as_universal().is_none() would require + // rewriting the cascade to not temporarily store unparsed custom properties with references + // as universal in the custom properties map. + debug_assert!(!registration.syntax.is_universal() || value.as_universal().is_some()); self.map_mut(registration).insert(name, value) } @@ -1925,13 +1930,13 @@ fn do_substitute_chunk<'a>( computed_context, references, )?; + let substitution = substitution.into_universal(); // Optimize the property: var(--...) case to avoid allocating at all. if reference.start == start && reference.end == end && registration.syntax.is_universal() { - return Ok(substitution); + return Ok(Substitution::Universal(substitution)); } - let substitution = substitution.into_universal(); substituted.push( &substitution.css, substitution.first_token_type, diff --git a/servo/components/style/data.rs b/servo/components/style/data.rs index ceddc5bd20..366318b658 100644 --- a/servo/components/style/data.rs +++ b/servo/components/style/data.rs @@ -45,6 +45,9 @@ bitflags! { /// The former gives us stronger transitive guarantees that allows us to /// apply the style sharing cache to cousins. const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2; + + /// Whether this element may have matched rules inside @starting-style. + const MAY_HAVE_STARTING_STYLE = 1 << 3; } } @@ -344,22 +347,28 @@ impl ElementData { let reused_via_rule_node = self .flags .contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); + let may_have_starting_style = self + .flags + .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE); PrimaryStyle { style: ResolvedStyle(self.styles.primary().clone()), reused_via_rule_node, + may_have_starting_style, } } /// Sets a new set of styles, returning the old ones. pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles { - if new_styles.primary.reused_via_rule_node { - self.flags - .insert(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - } else { - self.flags - .remove(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - } + self.flags.set( + ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE, + new_styles.primary.reused_via_rule_node, + ); + self.flags.set( + ElementDataFlags::MAY_HAVE_STARTING_STYLE, + new_styles.primary.may_have_starting_style, + ); + mem::replace(&mut self.styles, new_styles.into()) } @@ -542,4 +551,11 @@ impl ElementData { n } + + /// Returns true if this element data may need to compute the starting style for CSS + /// transitions. + #[inline] + pub fn may_have_starting_style(&self) -> bool { + self.flags.contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE) + } } diff --git a/servo/components/style/gecko/arc_types.rs b/servo/components/style/gecko/arc_types.rs index 420b86d332..259d9f1570 100644 --- a/servo/components/style/gecko/arc_types.rs +++ b/servo/components/style/gecko/arc_types.rs @@ -16,8 +16,8 @@ use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ ContainerRule, CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, - MediaRule, NamespaceRule, PageRule, PropertyRule, ScopeRule, StartingStyleRule, StyleRule, - StylesheetContents, SupportsRule, + MarginRule, MediaRule, NamespaceRule, PageRule, PropertyRule, ScopeRule, StartingStyleRule, + StyleRule, StylesheetContents, SupportsRule, }; use servo_arc::Arc; @@ -101,6 +101,11 @@ impl_simple_arc_ffi!( Servo_NamespaceRule_AddRef, Servo_NamespaceRule_Release ); +impl_simple_arc_ffi!( + MarginRule, + Servo_MarginRule_AddRef, + Servo_MarginRule_Release +); impl_locked_arc_ffi!( PageRule, LockedPageRule, diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs index eab968149c..5e2d893b15 100644 --- a/servo/components/style/gecko/wrapper.rs +++ b/servo/components/style/gecko/wrapper.rs @@ -577,14 +577,14 @@ pub enum GeckoChildrenIterator<'a> { /// replaces it with the next sibling when requested. Current(Option<GeckoNode<'a>>), /// A Gecko-implemented iterator we need to drop appropriately. - GeckoIterator(structs::StyleChildrenIterator), + GeckoIterator(std::mem::ManuallyDrop<structs::StyleChildrenIterator>), } impl<'a> Drop for GeckoChildrenIterator<'a> { fn drop(&mut self) { if let GeckoChildrenIterator::GeckoIterator(ref mut it) = *self { unsafe { - bindings::Gecko_DestroyStyleChildrenIterator(it); + bindings::Gecko_DestroyStyleChildrenIterator(&mut **it); } } } @@ -605,7 +605,7 @@ impl<'a> Iterator for GeckoChildrenIterator<'a> { // however we can't express this easily with bindgen, and it would // introduce functions with two input lifetimes into bindgen, // which would be out of scope for elision. - bindings::Gecko_GetNextStyleChild(&mut *(it as *mut _)) + bindings::Gecko_GetNextStyleChild(&mut **it) .as_ref() .map(GeckoNode) }, @@ -1015,9 +1015,9 @@ impl<'le> TElement for GeckoElement<'le> { self.may_have_anonymous_children() { unsafe { - let mut iter: structs::StyleChildrenIterator = ::std::mem::zeroed(); - bindings::Gecko_ConstructStyleChildrenIterator(self.0, &mut iter); - return LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter)); + let mut iter = std::mem::MaybeUninit::<structs::StyleChildrenIterator>::uninit(); + bindings::Gecko_ConstructStyleChildrenIterator(self.0, iter.as_mut_ptr()); + return LayoutIterator(GeckoChildrenIterator::GeckoIterator(std::mem::ManuallyDrop::new(iter.assume_init()))); } } diff --git a/servo/components/style/gecko_bindings/sugar/mod.rs b/servo/components/style/gecko_bindings/sugar/mod.rs index 00faf63ba6..e639abeda0 100644 --- a/servo/components/style/gecko_bindings/sugar/mod.rs +++ b/servo/components/style/gecko_bindings/sugar/mod.rs @@ -7,7 +7,6 @@ mod ns_com_ptr; mod ns_compatibility; mod ns_style_auto_array; -mod ns_t_array; pub mod origin_flags; pub mod ownership; pub mod refptr; diff --git a/servo/components/style/gecko_bindings/sugar/ns_t_array.rs b/servo/components/style/gecko_bindings/sugar/ns_t_array.rs deleted file mode 100644 index d10ed420dd..0000000000 --- a/servo/components/style/gecko_bindings/sugar/ns_t_array.rs +++ /dev/null @@ -1,144 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Rust helpers for Gecko's nsTArray. - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::{nsTArray, nsTArrayHeader, CopyableTArray}; -use std::mem; -use std::ops::{Deref, DerefMut}; -use std::slice; - -impl<T> Deref for nsTArray<T> { - type Target = [T]; - - #[inline] - fn deref<'a>(&'a self) -> &'a [T] { - unsafe { slice::from_raw_parts(self.slice_begin(), self.header().mLength as usize) } - } -} - -impl<T> DerefMut for nsTArray<T> { - fn deref_mut<'a>(&'a mut self) -> &'a mut [T] { - unsafe { slice::from_raw_parts_mut(self.slice_begin(), self.header().mLength as usize) } - } -} - -impl<T> nsTArray<T> { - #[inline] - fn header<'a>(&'a self) -> &'a nsTArrayHeader { - debug_assert!(!self.mBuffer.is_null()); - unsafe { mem::transmute(self.mBuffer) } - } - - // unsafe, since header may be in shared static or something - unsafe fn header_mut<'a>(&'a mut self) -> &'a mut nsTArrayHeader { - debug_assert!(!self.mBuffer.is_null()); - - mem::transmute(self.mBuffer) - } - - #[inline] - unsafe fn slice_begin(&self) -> *mut T { - debug_assert!(!self.mBuffer.is_null()); - (self.mBuffer as *const nsTArrayHeader).offset(1) as *mut _ - } - - /// Ensures the array has enough capacity at least to hold `cap` elements. - /// - /// NOTE: This doesn't call the constructor on the values! - pub fn ensure_capacity(&mut self, cap: usize) { - if cap >= self.len() { - unsafe { - bindings::Gecko_EnsureTArrayCapacity( - self as *mut nsTArray<T> as *mut _, - cap, - mem::size_of::<T>(), - ) - } - } - } - - /// Clears the array storage without calling the destructor on the values. - #[inline] - pub unsafe fn clear(&mut self) { - if self.len() != 0 { - bindings::Gecko_ClearPODTArray( - self as *mut nsTArray<T> as *mut _, - mem::size_of::<T>(), - mem::align_of::<T>(), - ); - } - } - - /// Clears a POD array. This is safe since copy types are memcopyable. - #[inline] - pub fn clear_pod(&mut self) - where - T: Copy, - { - unsafe { self.clear() } - } - - /// Resize and set the length of the array to `len`. - /// - /// unsafe because this may leave the array with uninitialized elements. - /// - /// This will not call constructors. If you need that, either manually add - /// bindings or run the typed `EnsureCapacity` call on the gecko side. - pub unsafe fn set_len(&mut self, len: u32) { - // this can leak - debug_assert!(len >= self.len() as u32); - if self.len() == len as usize { - return; - } - self.ensure_capacity(len as usize); - self.header_mut().mLength = len; - } - - /// Resizes an array containing only POD elements - /// - /// unsafe because this may leave the array with uninitialized elements. - /// - /// This will not leak since it only works on POD types (and thus doesn't assert) - pub unsafe fn set_len_pod(&mut self, len: u32) - where - T: Copy, - { - if self.len() == len as usize { - return; - } - self.ensure_capacity(len as usize); - let header = self.header_mut(); - header.mLength = len; - } - - /// Collects the given iterator into this array. - /// - /// Not unsafe because we won't leave uninitialized elements in the array. - pub fn assign_from_iter_pod<I>(&mut self, iter: I) - where - T: Copy, - I: ExactSizeIterator + Iterator<Item = T>, - { - debug_assert!(iter.len() <= 0xFFFFFFFF); - unsafe { - self.set_len_pod(iter.len() as u32); - } - self.iter_mut().zip(iter).for_each(|(r, v)| *r = v); - } -} - -impl<T> Deref for CopyableTArray<T> { - type Target = nsTArray<T>; - fn deref(&self) -> &Self::Target { - &self._base - } -} - -impl<T> DerefMut for CopyableTArray<T> { - fn deref_mut(&mut self) -> &mut nsTArray<T> { - &mut self._base - } -} diff --git a/servo/components/style/invalidation/element/document_state.rs b/servo/components/style/invalidation/element/document_state.rs index 0b846510d8..5437943066 100644 --- a/servo/components/style/invalidation/element/document_state.rs +++ b/servo/components/style/invalidation/element/document_state.rs @@ -14,8 +14,8 @@ use crate::invalidation::element::state_and_attributes; use crate::stylist::CascadeData; use dom::DocumentState; use selectors::matching::{ - MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, QuirksMode, - SelectorCaches, VisitedHandlingMode, + IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode, + NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode, }; /// A struct holding the members necessary to invalidate document state @@ -59,6 +59,7 @@ impl<'a, 'b, E: TElement, I> DocumentStateInvalidationProcessor<'a, 'b, E, I> { None, selector_caches, VisitedHandlingMode::AllLinksVisitedAndUnvisited, + IncludeStartingStyle::No, quirks_mode, NeedsSelectorFlags::No, MatchingForInvalidation::No, diff --git a/servo/components/style/invalidation/element/relative_selector.rs b/servo/components/style/invalidation/element/relative_selector.rs index 41222304be..d57676905b 100644 --- a/servo/components/style/invalidation/element/relative_selector.rs +++ b/servo/components/style/invalidation/element/relative_selector.rs @@ -26,8 +26,8 @@ use dom::ElementState; use fxhash::FxHashMap; use selectors::matching::{ matches_compound_selector_from, matches_selector, CompoundSelectorMatchingResult, - ElementSelectorFlags, MatchingContext, MatchingForInvalidation, MatchingMode, - NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode, + ElementSelectorFlags, IncludeStartingStyle, MatchingContext, MatchingForInvalidation, + MatchingMode, NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode, }; use selectors::parser::{Combinator, SelectorKey}; use selectors::OpaqueElement; @@ -822,6 +822,7 @@ where None, &mut selector_caches, VisitedHandlingMode::AllLinksVisitedAndUnvisited, + IncludeStartingStyle::No, self.quirks_mode, NeedsSelectorFlags::No, MatchingForInvalidation::Yes, @@ -1032,6 +1033,7 @@ where None, selector_caches, VisitedHandlingMode::AllLinksVisitedAndUnvisited, + IncludeStartingStyle::No, quirks_mode, NeedsSelectorFlags::No, MatchingForInvalidation::Yes, diff --git a/servo/components/style/invalidation/element/state_and_attributes.rs b/servo/components/style/invalidation/element/state_and_attributes.rs index d5f0723d66..ffb2302b55 100644 --- a/servo/components/style/invalidation/element/state_and_attributes.rs +++ b/servo/components/style/invalidation/element/state_and_attributes.rs @@ -24,7 +24,8 @@ use dom::ElementState; use selectors::attr::CaseSensitivity; use selectors::kleene_value::KleeneValue; use selectors::matching::{ - matches_selector_kleene, MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode + matches_selector_kleene, IncludeStartingStyle, MatchingContext, MatchingForInvalidation, + MatchingMode, NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode }; use smallvec::SmallVec; @@ -73,6 +74,7 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> None, selector_caches, VisitedHandlingMode::AllLinksVisitedAndUnvisited, + IncludeStartingStyle::No, shared_context.quirks_mode(), NeedsSelectorFlags::No, MatchingForInvalidation::Yes, diff --git a/servo/components/style/lib.rs b/servo/components/style/lib.rs index b2a1e20ce8..d390ee371b 100644 --- a/servo/components/style/lib.rs +++ b/servo/components/style/lib.rs @@ -69,6 +69,8 @@ extern crate static_assertions; #[macro_use] extern crate style_derive; #[macro_use] +extern crate thin_vec; +#[macro_use] extern crate to_shmem_derive; #[macro_use] diff --git a/servo/components/style/matching.rs b/servo/components/style/matching.rs index e9d754aa10..739504d260 100644 --- a/servo/components/style/matching.rs +++ b/servo/components/style/matching.rs @@ -21,8 +21,10 @@ use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StrongRuleNode}; use crate::selector_parser::{PseudoElement, RestyleDamage}; use crate::shared_lock::Locked; -use crate::style_resolver::ResolvedElementStyles; -use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; +use crate::style_resolver::{PseudoElementResolution, ResolvedElementStyles}; +#[cfg(feature = "gecko")] +use crate::style_resolver::ResolvedStyle; +use crate::style_resolver::StyleResolverForElement; use crate::stylesheets::layer_rule::LayerOrder; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; @@ -385,6 +387,158 @@ trait PrivateMatchMethods: TElement { } #[cfg(feature = "gecko")] + fn resolve_starting_style( + &self, + context: &mut StyleContext<Self>, + ) -> ResolvedStyle { + use selectors::matching::IncludeStartingStyle; + + // Compute after-change style for the parent and the layout parent. + // Per spec, starting style inherits from the parent’s after-change style just like + // after-change style does. + let parent_el = self.inheritance_parent(); + let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); + let parent_style = parent_data.as_ref().map(|d| d.styles.primary()); + let parent_after_change_style = + parent_style.and_then(|s| self.after_change_style(context, s)); + let parent_values = parent_after_change_style + .as_ref() + .or(parent_style) + .map(|x| &**x); + + let mut layout_parent_el = parent_el.clone(); + let layout_parent_data; + let layout_parent_after_change_style; + let layout_parent_values = if parent_style.map_or(false, |s| s.is_display_contents()) { + layout_parent_el = Some(layout_parent_el.unwrap().layout_parent()); + layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap(); + let layout_parent_style = Some(layout_parent_data.styles.primary()); + layout_parent_after_change_style = + layout_parent_style.and_then(|s| self.after_change_style(context, s)); + layout_parent_after_change_style + .as_ref() + .or(layout_parent_style) + .map(|x| &**x) + } else { + parent_values + }; + + // Note: Basically, we have to remove transition rules because the starting style for an + // element is the after-change style with @starting-style rules applied in addition. + // However, we expect there is no transition rules for this element when calling this + // function because we do this only when we don't have before-change style or we change + // from display:none. In these cases, it's unlikely to have running transitions on this + // element. + let mut resolver = StyleResolverForElement::new( + *self, + context, + RuleInclusion::All, + PseudoElementResolution::IfApplicable, + ); + resolver + .resolve_primary_style( + parent_values, + layout_parent_values, + IncludeStartingStyle::Yes, + ) + .style + } + + #[cfg(feature = "gecko")] + fn maybe_resolve_starting_style( + &self, + context: &mut StyleContext<Self>, + old_values: Option<&Arc<ComputedValues>>, + new_styles: &ResolvedElementStyles, + ) -> Option<Arc<ComputedValues>> { + // For both cases: + // 1. If we didn't see any starting-style rules for this given element during full matching. + // 2. If there is no transitions specified. + // We don't have to resolve starting style. + if !new_styles.may_have_starting_style() + || !new_styles.primary_style().get_ui().specifies_transitions() + { + return None; + } + + // We resolve starting style only if we don't have before-change-style, or we change from + // display:none. + if old_values.is_some() + && !new_styles + .primary_style() + .is_display_property_changed_from_none(old_values.map(|s| &**s)) + { + return None; + } + + let starting_style = self.resolve_starting_style(context); + if starting_style.style().clone_display().is_none() { + return None; + } + + Some(starting_style.0) + } + + /// Handle CSS Transitions. Returns None if we don't need to update transitions. And it returns + /// the before-change style per CSS Transitions spec. + /// + /// Note: The before-change style could be the computed values of all properties on the element + /// as of the previous style change event, or the starting style if we don't have the valid + /// before-change style there. + #[cfg(feature = "gecko")] + fn process_transitions( + &self, + context: &mut StyleContext<Self>, + old_values: Option<&Arc<ComputedValues>>, + new_styles: &mut ResolvedElementStyles, + ) -> Option<Arc<ComputedValues>> { + let starting_values = self.maybe_resolve_starting_style(context, old_values, new_styles); + let before_change_or_starting = if starting_values.is_some() { + starting_values.as_ref() + } else { + old_values + }; + let new_values = new_styles.primary_style_mut(); + + if !self.might_need_transitions_update( + context, + before_change_or_starting.map(|s| &**s), + new_values, + /* pseudo_element = */ None, + ) { + return None; + } + + let after_change_style = + if self.has_css_transitions(context.shared, /* pseudo_element = */ None) { + self.after_change_style(context, new_values) + } else { + None + }; + + // In order to avoid creating a SequentialTask for transitions which + // may not be updated, we check it per property to make sure Gecko + // side will really update transition. + if !self.needs_transitions_update( + before_change_or_starting.unwrap(), + after_change_style.as_ref().unwrap_or(&new_values), + ) { + return None; + } + + if let Some(values_without_transitions) = after_change_style { + *new_values = values_without_transitions; + } + + // Move the new-created starting style, or clone the old values. + if starting_values.is_some() { + starting_values + } else { + old_values.cloned() + } + } + + #[cfg(feature = "gecko")] fn process_animations( &self, context: &mut StyleContext<Self>, @@ -395,13 +549,12 @@ trait PrivateMatchMethods: TElement { ) { use crate::context::UpdateAnimationsTasks; - let new_values = new_styles.primary_style_mut(); let old_values = &old_styles.primary; if context.shared.traversal_flags.for_animation_only() { self.handle_display_change_for_smil_if_needed( context, old_values.as_deref(), - new_values, + new_styles.primary_style(), restyle_hint, ); return; @@ -413,15 +566,15 @@ trait PrivateMatchMethods: TElement { let mut tasks = UpdateAnimationsTasks::empty(); if old_values.as_deref().map_or_else( - || new_values.get_ui().specifies_scroll_timelines(), - |old| !old.get_ui().scroll_timelines_equals(new_values.get_ui()), + || new_styles.primary_style().get_ui().specifies_scroll_timelines(), + |old| !old.get_ui().scroll_timelines_equals(new_styles.primary_style().get_ui()), ) { tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES); } if old_values.as_deref().map_or_else( - || new_values.get_ui().specifies_view_timelines(), - |old| !old.get_ui().view_timelines_equals(new_values.get_ui()), + || new_styles.primary_style().get_ui().specifies_view_timelines(), + |old| !old.get_ui().view_timelines_equals(new_styles.primary_style().get_ui()), ) { tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES); } @@ -429,58 +582,27 @@ trait PrivateMatchMethods: TElement { if self.needs_animations_update( context, old_values.as_deref(), - new_values, + new_styles.primary_style(), /* pseudo_element = */ None, ) { tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS); } - let before_change_style = if self.might_need_transitions_update( - context, - old_values.as_deref(), - new_values, - /* pseudo_element = */ None, - ) { - let after_change_style = - if self.has_css_transitions(context.shared, /* pseudo_element = */ None) { - self.after_change_style(context, new_values) - } else { - None - }; - - // In order to avoid creating a SequentialTask for transitions which - // may not be updated, we check it per property to make sure Gecko - // side will really update transition. - let needs_transitions_update = { - // We borrow new_values here, so need to add a scope to make - // sure we release it before assigning a new value to it. - let after_change_style_ref = after_change_style.as_ref().unwrap_or(&new_values); - - self.needs_transitions_update(old_values.as_ref().unwrap(), after_change_style_ref) - }; - - if needs_transitions_update { - if let Some(values_without_transitions) = after_change_style { - *new_values = values_without_transitions; - } - tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS); - - // We need to clone old_values into SequentialTask, so we can - // use it later. - old_values.clone() - } else { - None - } - } else { - None - }; + let before_change_style = + self.process_transitions(context, old_values.as_ref(), new_styles); + if before_change_style.is_some() { + tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS); + } if self.has_animations(&context.shared) { tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES); if important_rules_changed { tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS); } - if new_values.is_display_property_changed_from_none(old_values.as_deref()) { + if new_styles + .primary_style() + .is_display_property_changed_from_none(old_values.as_deref()) + { tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE); } } diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index 8d3f90f8f5..c9f4e60832 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -580,26 +580,18 @@ impl Clone for ${style_struct.gecko_struct_name} { </%def> <%def name="impl_font_settings(ident, gecko_type, tag_type, value_type, gecko_value_type)"> - <% - gecko_ffi_name = to_camel_case_lower(ident) - %> + <% gecko_ffi_name = to_camel_case_lower(ident) %> pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { let iter = v.0.iter().map(|other| structs::${gecko_type} { mTag: other.tag.0, mValue: other.value as ${gecko_value_type}, }); - self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter); - } - - pub fn copy_${ident}_from(&mut self, other: &Self) { - let iter = other.mFont.${gecko_ffi_name}.iter().map(|s| *s); - self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter); + self.mFont.${gecko_ffi_name}.clear(); + self.mFont.${gecko_ffi_name}.extend(iter); } - pub fn reset_${ident}(&mut self, other: &Self) { - self.copy_${ident}_from(other) - } + <% impl_simple_copy(ident, "mFont." + gecko_ffi_name) %> pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { use crate::values::generics::font::{FontSettings, FontTag, ${tag_type}}; diff --git a/servo/components/style/properties/mod.rs b/servo/components/style/properties/mod.rs index 4a08d67f77..1b378a38bf 100644 --- a/servo/components/style/properties/mod.rs +++ b/servo/components/style/properties/mod.rs @@ -690,6 +690,68 @@ impl ShorthandId { } } +fn parse_non_custom_property_declaration_value_into<'i>( + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, '_>, + start: &cssparser::ParserState, + parse_entirely_into: impl FnOnce(&mut SourcePropertyDeclaration, &mut Parser<'i, '_>) -> Result<(), ParseError<'i>>, + parsed_wide_keyword: impl FnOnce(&mut SourcePropertyDeclaration, CSSWideKeyword), + parsed_custom: impl FnOnce(&mut SourcePropertyDeclaration, custom_properties::VariableValue), +) -> Result<(), ParseError<'i>> { + let mut starts_with_curly_block = false; + if let Ok(token) = input.next() { + match token { + cssparser::Token::Ident(ref ident) => match CSSWideKeyword::from_ident(ident) { + Ok(wk) => { + if input.expect_exhausted().is_ok() { + return Ok(parsed_wide_keyword(declarations, wk)); + } + }, + Err(()) => {}, + }, + cssparser::Token::CurlyBracketBlock => { + starts_with_curly_block = true; + }, + _ => {}, + } + }; + + input.reset(&start); + input.look_for_var_or_env_functions(); + let err = match parse_entirely_into(declarations, input) { + Ok(()) => { + input.seen_var_or_env_functions(); + return Ok(()); + }, + Err(e) => e, + }; + + // Look for var(), env() and top-level curly blocks after the error. + let start_pos = start.position(); + let mut at_start = start_pos == input.position(); + let mut invalid = false; + while let Ok(token) = input.next() { + if matches!(token, cssparser::Token::CurlyBracketBlock) { + if !starts_with_curly_block || !at_start { + invalid = true; + break; + } + } else if starts_with_curly_block { + invalid = true; + break; + } + at_start = false; + } + if !input.seen_var_or_env_functions() || invalid { + return Err(err); + } + input.reset(start); + let value = custom_properties::VariableValue::parse(input, &context.url_data)?; + parsed_custom(declarations, value); + Ok(()) +} + impl PropertyDeclaration { fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option<&str> { match *self { @@ -793,76 +855,67 @@ impl PropertyDeclaration { }; match non_custom_id.longhand_or_shorthand() { Ok(longhand_id) => { - let declaration = input - .try_parse(CSSWideKeyword::parse) - .map(|keyword| PropertyDeclaration::css_wide_keyword(longhand_id, keyword)) - .or_else(|()| { - input.look_for_var_or_env_functions(); - input.parse_entirely(|input| longhand_id.parse_value(context, input)) - }) - .or_else(|err| { - while let Ok(_) = input.next() {} // Look for var() after the error. - if !input.seen_var_or_env_functions() { - return Err(err); - } - input.reset(&start); - let variable_value = - custom_properties::VariableValue::parse(input, &context.url_data)?; - Ok(PropertyDeclaration::WithVariables(VariableDeclaration { + parse_non_custom_property_declaration_value_into( + declarations, + context, + input, + &start, + |declarations, input| { + let decl = input.parse_entirely(|input| longhand_id.parse_value(context, input))?; + declarations.push(decl); + Ok(()) + }, + |declarations, wk| { + declarations.push(PropertyDeclaration::css_wide_keyword(longhand_id, wk)); + }, + |declarations, variable_value| { + declarations.push(PropertyDeclaration::WithVariables(VariableDeclaration { id: longhand_id, value: Arc::new(UnparsedValue { variable_value, from_shorthand: None, }), })) - })?; - declarations.push(declaration) + } + )?; }, Err(shorthand_id) => { - if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { - if shorthand_id == ShorthandId::All { - declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword) - } else { - for longhand in shorthand_id.longhands() { - declarations - .push(PropertyDeclaration::css_wide_keyword(longhand, keyword)); - } - } - } else { - input.look_for_var_or_env_functions(); - // Not using parse_entirely here: each - // ${shorthand.ident}::parse_into function needs to do so - // *before* pushing to `declarations`. - shorthand_id - .parse_into(declarations, context, input) - .or_else(|err| { - while let Ok(_) = input.next() {} // Look for var() after the error. - if !input.seen_var_or_env_functions() { - return Err(err); + parse_non_custom_property_declaration_value_into( + declarations, + context, + input, + &start, + // Not using parse_entirely here: each ShorthandId::parse_into function needs + // to do so *before* pushing to `declarations`. + |declarations, input| shorthand_id.parse_into(declarations, context, input), + |declarations, wk| { + if shorthand_id == ShorthandId::All { + declarations.all_shorthand = AllShorthand::CSSWideKeyword(wk) + } else { + for longhand in shorthand_id.longhands() { + declarations.push(PropertyDeclaration::css_wide_keyword(longhand, wk)); } - - input.reset(&start); - let variable_value = - custom_properties::VariableValue::parse(input, &context.url_data)?; - let unparsed = Arc::new(UnparsedValue { - variable_value, - from_shorthand: Some(shorthand_id), - }); - if shorthand_id == ShorthandId::All { - declarations.all_shorthand = AllShorthand::WithVariables(unparsed) - } else { - for id in shorthand_id.longhands() { - declarations.push(PropertyDeclaration::WithVariables( - VariableDeclaration { - id, - value: unparsed.clone(), - }, - )) - } + } + }, + |declarations, variable_value| { + let unparsed = Arc::new(UnparsedValue { + variable_value, + from_shorthand: Some(shorthand_id), + }); + if shorthand_id == ShorthandId::All { + declarations.all_shorthand = AllShorthand::WithVariables(unparsed) + } else { + for id in shorthand_id.longhands() { + declarations.push(PropertyDeclaration::WithVariables( + VariableDeclaration { + id, + value: unparsed.clone(), + }, + )) } - Ok(()) - })?; - } + } + } + )?; }, } if let Some(use_counters) = context.use_counters { @@ -1051,14 +1104,12 @@ impl<'a> PropertyDeclarationId<'a> { } /// Convert a `PropertyDeclarationId` into an `AnimatedPropertyID` - /// Note that the rust AnimatedPropertyID doesn't implement Drop, so owned controls whether the - /// custom name should be addrefed or not. /// - /// FIXME(emilio, bug 1870107): This is a bit error-prone. We should consider using cbindgen to - /// generate the property id representation or so. + /// FIXME(emilio, bug 1870107): We should consider using cbindgen to generate the property id + /// representation or so. #[cfg(feature = "gecko")] #[inline] - pub fn to_gecko_animated_property_id(&self, owned: bool) -> AnimatedPropertyID { + pub fn to_gecko_animated_property_id(&self) -> AnimatedPropertyID { match self { Self::Longhand(id) => AnimatedPropertyID { mID: id.to_nscsspropertyid(), @@ -1069,11 +1120,7 @@ impl<'a> PropertyDeclarationId<'a> { mID: nsCSSPropertyID::eCSSPropertyExtra_variable, mCustomName: RefPtr::null(), }; - property_id.mCustomName.mRawPtr = if owned { - (*name).clone().into_addrefed() - } else { - name.as_ptr() - }; + property_id.mCustomName.mRawPtr = (*name).clone().into_addrefed(); property_id }, } diff --git a/servo/components/style/selector_map.rs b/servo/components/style/selector_map.rs index 2b8d6add55..439fd414a6 100644 --- a/servo/components/style/selector_map.rs +++ b/servo/components/style/selector_map.rs @@ -330,6 +330,10 @@ impl SelectorMap<Rule> { ) where E: TElement, { + use selectors::matching::IncludeStartingStyle; + + let include_starting_style = + matches!(matching_context.include_starting_style, IncludeStartingStyle::Yes); for rule in rules { if !matches_selector( &rule.selector, @@ -352,6 +356,17 @@ impl SelectorMap<Rule> { } } + if rule.is_starting_style { + // Set this flag if there are any rules inside @starting-style. This flag is for + // optimization to avoid any redundant resolution of starting style if the author + // doesn't specify for this element. + matching_context.has_starting_style = true; + + if !include_starting_style { + continue; + } + } + matching_rules.push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } } diff --git a/servo/components/style/sharing/mod.rs b/servo/components/style/sharing/mod.rs index 28592a02c5..2b66db5530 100644 --- a/servo/components/style/sharing/mod.rs +++ b/servo/components/style/sharing/mod.rs @@ -76,13 +76,11 @@ use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles}; use crate::stylist::Stylist; use crate::values::AtomIdent; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; -use owning_ref::OwningHandle; use selectors::matching::{NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode}; -use servo_arc::Arc; use smallbitvec::SmallBitVec; use smallvec::SmallVec; use std::marker::PhantomData; -use std::mem::{self, ManuallyDrop}; +use std::mem; use std::ops::Deref; use std::ptr::NonNull; use uluru::LRUCache; @@ -535,12 +533,11 @@ impl<E: TElement> SharingCache<E> { /// [2] https://github.com/rust-lang/rust/issues/13707 type SharingCache<E> = SharingCacheBase<StyleSharingCandidate<E>>; type TypelessSharingCache = SharingCacheBase<FakeCandidate>; -type StoredSharingCache = Arc<AtomicRefCell<TypelessSharingCache>>; thread_local! { // See the comment on bloom.rs about why do we leak this. - static SHARING_CACHE_KEY: ManuallyDrop<StoredSharingCache> = - ManuallyDrop::new(Arc::new_leaked(Default::default())); + static SHARING_CACHE_KEY: &'static AtomicRefCell<TypelessSharingCache> = + Box::leak(Default::default()); } /// An LRU cache of the last few nodes seen, so that we can aggressively try to @@ -550,7 +547,7 @@ thread_local! { /// storing nodes here temporarily is safe. pub struct StyleSharingCache<E: TElement> { /// The LRU cache, with the type cast away to allow persisting the allocation. - cache_typeless: OwningHandle<StoredSharingCache, AtomicRefMut<'static, TypelessSharingCache>>, + cache_typeless: AtomicRefMut<'static, TypelessSharingCache>, /// Bind this structure to the lifetime of E, since that's what we effectively store. marker: PhantomData<SendElement<E>>, /// The DOM depth we're currently at. This is used as an optimization to @@ -593,9 +590,7 @@ impl<E: TElement> StyleSharingCache<E> { mem::align_of::<SharingCache<E>>(), mem::align_of::<TypelessSharingCache>() ); - let cache_arc = SHARING_CACHE_KEY.with(|c| Arc::clone(&*c)); - let cache = - OwningHandle::new_with_fn(cache_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut()); + let cache = SHARING_CACHE_KEY.with(|c| c.borrow_mut()); debug_assert!(cache.is_empty()); StyleSharingCache { diff --git a/servo/components/style/style_resolver.rs b/servo/components/style/style_resolver.rs index 5c940ad2be..70afb92eb0 100644 --- a/servo/components/style/style_resolver.rs +++ b/servo/components/style/style_resolver.rs @@ -17,7 +17,8 @@ use crate::selector_parser::{PseudoElement, SelectorImpl}; use crate::stylist::RuleInclusion; use log::Level::Trace; use selectors::matching::{ - MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, VisitedHandlingMode, + IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode, + NeedsSelectorFlags, VisitedHandlingMode, }; use servo_arc::Arc; @@ -47,11 +48,20 @@ where struct MatchingResults { rule_node: StrongRuleNode, flags: ComputedValueFlags, + has_starting_style: bool, } /// A style returned from the resolver machinery. pub struct ResolvedStyle(pub Arc<ComputedValues>); +impl ResolvedStyle { + /// Convenience accessor for the style. + #[inline] + pub fn style(&self) -> &ComputedValues { + &*self.0 + } +} + /// The primary style of an element or an element-backed pseudo-element. pub struct PrimaryStyle { /// The style itself. @@ -59,6 +69,11 @@ pub struct PrimaryStyle { /// Whether the style was reused from another element via the rule node (see /// `StyleSharingCache::lookup_by_rules`). pub reused_via_rule_node: bool, + /// The element may have matched rules inside @starting-style. + /// Basically, we don't apply @starting-style rules to |style|. This is a sugar to let us know + /// if we should resolve the element again for starting style, which is the after-change style + /// with @starting-style rules applied in addition. + pub may_have_starting_style: bool, } /// A set of style returned from the resolver machinery. @@ -79,6 +94,12 @@ impl ResolvedElementStyles { pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> { &mut self.primary.style.0 } + + /// Returns true if this element may have starting style rules. + #[inline] + pub fn may_have_starting_style(&self) -> bool { + self.primary.may_have_starting_style + } } impl PrimaryStyle { @@ -186,16 +207,22 @@ where &mut self, parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, + include_starting_style: IncludeStartingStyle, ) -> PrimaryStyle { - let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited); + let primary_results = self.match_primary( + VisitedHandlingMode::AllLinksUnvisited, + include_starting_style, + ); let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some()); let visited_rules = if self.context.shared.visited_styles_enabled && (inside_link || self.element.is_link()) { - let visited_matching_results = - self.match_primary(VisitedHandlingMode::RelevantLinkVisited); + let visited_matching_results = self.match_primary( + VisitedHandlingMode::RelevantLinkVisited, + IncludeStartingStyle::No, + ); Some(visited_matching_results.rule_node) } else { None @@ -209,6 +236,7 @@ where }, parent_style, layout_parent_style, + primary_results.has_starting_style, ) } @@ -217,6 +245,7 @@ where inputs: CascadeInputs, parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, + may_have_starting_style: bool, ) -> PrimaryStyle { // Before doing the cascade, check the sharing cache and see if we can // reuse the style via rule node identity. @@ -253,6 +282,7 @@ where /* pseudo = */ None, ), reused_via_rule_node: false, + may_have_starting_style, } } @@ -262,7 +292,11 @@ where parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, ) -> ResolvedElementStyles { - let primary_style = self.resolve_primary_style(parent_style, layout_parent_style); + let primary_style = self.resolve_primary_style( + parent_style, + layout_parent_style, + IncludeStartingStyle::No, + ); let mut pseudo_styles = EagerPseudoStyles::default(); @@ -375,10 +409,15 @@ where pub fn cascade_styles_with_default_parents( &mut self, inputs: ElementCascadeInputs, + may_have_starting_style: bool, ) -> ResolvedElementStyles { with_default_parent_styles(self.element, move |parent_style, layout_parent_style| { - let primary_style = - self.cascade_primary_style(inputs.primary, parent_style, layout_parent_style); + let primary_style = self.cascade_primary_style( + inputs.primary, + parent_style, + layout_parent_style, + may_have_starting_style, + ); let mut pseudo_styles = EagerPseudoStyles::default(); if let Some(mut pseudo_array) = inputs.pseudos.into_array() { @@ -427,6 +466,7 @@ where let MatchingResults { rule_node, mut flags, + has_starting_style: _, } = self.match_pseudo( &originating_element_style.style.0, pseudo, @@ -459,7 +499,11 @@ where )) } - fn match_primary(&mut self, visited_handling: VisitedHandlingMode) -> MatchingResults { + fn match_primary( + &mut self, + visited_handling: VisitedHandlingMode, + include_starting_style: IncludeStartingStyle, + ) -> MatchingResults { debug!( "Match primary for {:?}, visited: {:?}", self.element, visited_handling @@ -473,6 +517,7 @@ where Some(bloom_filter), selector_caches, visited_handling, + include_starting_style, self.context.shared.quirks_mode(), NeedsSelectorFlags::Yes, MatchingForInvalidation::No, @@ -512,6 +557,7 @@ where MatchingResults { rule_node, flags: matching_context.extra_data.cascade_input_flags, + has_starting_style: matching_context.has_starting_style, } } @@ -550,6 +596,7 @@ where Some(bloom_filter), selector_caches, visited_handling, + IncludeStartingStyle::No, self.context.shared.quirks_mode(), NeedsSelectorFlags::Yes, MatchingForInvalidation::No, @@ -580,6 +627,7 @@ where Some(MatchingResults { rule_node, flags: matching_context.extra_data.cascade_input_flags, + has_starting_style: false, // We don't care. }) } } diff --git a/servo/components/style/stylesheets/font_feature_values_rule.rs b/servo/components/style/stylesheets/font_feature_values_rule.rs index 06016ec2bd..73d499c041 100644 --- a/servo/components/style/stylesheets/font_feature_values_rule.rs +++ b/servo/components/style/stylesheets/font_feature_values_rule.rs @@ -10,7 +10,7 @@ use crate::error_reporting::ContextualParseError; #[cfg(feature = "gecko")] use crate::gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry; #[cfg(feature = "gecko")] -use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet, nsTArray}; +use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet}; use crate::parser::{Parse, ParserContext}; use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; @@ -24,6 +24,7 @@ use cssparser::{ }; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use thin_vec::ThinVec; /// A @font-feature-values block declaration. /// It is `<ident>: <integer>+`. @@ -54,8 +55,8 @@ impl<T: ToCss> ToCss for FFVDeclaration<T> { /// A trait for @font-feature-values rule to gecko values conversion. #[cfg(feature = "gecko")] pub trait ToGeckoFontFeatureValues { - /// Sets the equivalent of declaration to gecko `nsTArray<u32>` array. - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>); + /// Sets the equivalent of declaration to gecko `ThinVec<u32>` array. + fn to_gecko_font_feature_values(&self) -> ThinVec<u32>; } /// A @font-feature-values block declaration value that keeps one value. @@ -79,11 +80,8 @@ impl Parse for SingleValue { #[cfg(feature = "gecko")] impl ToGeckoFontFeatureValues for SingleValue { - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { - unsafe { - array.set_len_pod(1); - } - array[0] = self.0 as u32; + fn to_gecko_font_feature_values(&self) -> ThinVec<u32> { + thin_vec::thin_vec![self.0 as u32] } } @@ -118,16 +116,12 @@ impl Parse for PairValues { #[cfg(feature = "gecko")] impl ToGeckoFontFeatureValues for PairValues { - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { - let len = if self.1.is_some() { 2 } else { 1 }; - - unsafe { - array.set_len_pod(len); - } - array[0] = self.0 as u32; + fn to_gecko_font_feature_values(&self) -> ThinVec<u32> { + let mut result = thin_vec::thin_vec![self.0 as u32]; if let Some(second) = self.1 { - array[1] = second as u32; - }; + result.push(second as u32); + } + result } } @@ -165,8 +159,8 @@ impl Parse for VectorValues { #[cfg(feature = "gecko")] impl ToGeckoFontFeatureValues for VectorValues { - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { - array.assign_from_iter_pod(self.0.iter().map(|v| *v)); + fn to_gecko_font_feature_values(&self) -> ThinVec<u32> { + self.0.iter().copied().collect() } } @@ -338,7 +332,7 @@ macro_rules! font_feature_values_blocks { ) }; unsafe { - val.value.to_gecko_font_feature_values(&mut *array); + *array = val.value.to_gecko_font_feature_values(); } } } diff --git a/servo/components/style/stylesheets/margin_rule.rs b/servo/components/style/stylesheets/margin_rule.rs index ab46283151..19928c04ba 100644 --- a/servo/components/style/stylesheets/margin_rule.rs +++ b/servo/components/style/stylesheets/margin_rule.rs @@ -21,19 +21,21 @@ macro_rules! margin_rule_types { /// [`@margin`][margin] rule names. /// /// https://drafts.csswg.org/css-page-3/#margin-at-rules - #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] + #[derive(Clone, Copy, Eq, MallocSizeOf, PartialEq, ToShmem)] #[repr(u8)] pub enum MarginRuleType { $($(#[$($meta)+])* $id,)+ } + /// All [`@margin`][margin] rule names, with a preceding '@'. + /// + /// This array lets us have just one single memory region used for + /// to_str, name, and the Debug implementation. + const MARGIN_RULE_AT_NAMES:&[&'static str] = &[ + $( concat!('@', $val), )+ + ]; + impl MarginRuleType { - #[inline] - fn to_str(&self) -> &'static str { - match *self { - $(MarginRuleType::$id => concat!('@', $val),)+ - } - } /// Matches the rule type for this name. This does not expect a /// leading '@'. pub fn match_name(name: &str) -> Option<Self> { @@ -113,6 +115,27 @@ margin_rule_types! { RightBottom => "right-bottom", } +impl MarginRuleType { + #[inline] + fn to_str(&self) -> &'static str { + &MARGIN_RULE_AT_NAMES[*self as usize] + } + #[inline] + fn name(&self) -> &'static str { + // Use the at-name array, skipping the first character to get + // the name without the @ sign. + &MARGIN_RULE_AT_NAMES[*self as usize][1..] + } +} + +// Implement Debug manually so that it will share the same string memory as +// MarginRuleType::name and MarginRuleType::to_str. +impl fmt::Debug for MarginRuleType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str(self.name()) + } +} + /// A [`@margin`][margin] rule. /// /// [margin]: https://drafts.csswg.org/css-page-3/#margin-at-rules @@ -134,6 +157,11 @@ impl MarginRule { self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops) } + /// Gets the name for this margin rule. + #[inline] + pub fn name(&self) -> &'static str { + self.rule_type.name() + } } impl ToCssWithGuard for MarginRule { diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index 06cbe6276b..bef4e569cd 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -577,11 +577,12 @@ impl From<StyleRuleInclusion> for RuleInclusion { } /// A struct containing state from ancestor rules like @layer / @import / -/// @container / nesting. +/// @container / @starting-style / nesting. struct ContainingRuleState { layer_name: LayerName, layer_id: LayerId, container_condition_id: ContainerConditionId, + in_starting_style: bool, ancestor_selector_lists: SmallVec<[SelectorList<SelectorImpl>; 2]>, } @@ -591,6 +592,7 @@ impl Default for ContainingRuleState { layer_name: LayerName::new_empty(), layer_id: LayerId::root(), container_condition_id: ContainerConditionId::none(), + in_starting_style: false, ancestor_selector_lists: Default::default(), } } @@ -601,6 +603,7 @@ struct SavedContainingRuleState { layer_name_len: usize, layer_id: LayerId, container_condition_id: ContainerConditionId, + in_starting_style: bool, } impl ContainingRuleState { @@ -610,6 +613,7 @@ impl ContainingRuleState { layer_name_len: self.layer_name.0.len(), layer_id: self.layer_id, container_condition_id: self.container_condition_id, + in_starting_style: self.in_starting_style, } } @@ -621,6 +625,7 @@ impl ContainingRuleState { self.layer_name.0.truncate(saved.layer_name_len); self.layer_id = saved.layer_id; self.container_condition_id = saved.container_condition_id; + self.in_starting_style = saved.in_starting_style; } } @@ -1258,6 +1263,7 @@ impl Stylist { None, &mut selector_caches, VisitedHandlingMode::RelevantLinkVisited, + selectors::matching::IncludeStartingStyle::No, self.quirks_mode, needs_selector_flags, MatchingForInvalidation::No, @@ -2858,6 +2864,7 @@ impl CascadeData { self.rules_source_order, containing_rule_state.layer_id, containing_rule_state.container_condition_id, + containing_rule_state.in_starting_style, ); if collect_replaced_selectors { @@ -3146,6 +3153,9 @@ impl CascadeData { }); containing_rule_state.container_condition_id = id; }, + CssRule::StartingStyle(..) => { + containing_rule_state.in_starting_style = true; + }, // We don't care about any other rule. _ => {}, } @@ -3434,6 +3444,9 @@ pub struct Rule { /// The current @container rule id. pub container_condition_id: ContainerConditionId, + /// True if this rule is inside @starting-style. + pub is_starting_style: bool, + /// The actual style rule. #[cfg_attr( feature = "gecko", @@ -3480,6 +3493,7 @@ impl Rule { source_order: u32, layer_id: LayerId, container_condition_id: ContainerConditionId, + is_starting_style: bool, ) -> Self { Rule { selector, @@ -3488,6 +3502,7 @@ impl Rule { source_order, layer_id, container_condition_id, + is_starting_style, } } } diff --git a/servo/components/style/traversal.rs b/servo/components/style/traversal.rs index fa256d0f60..651b5cb073 100644 --- a/servo/components/style/traversal.rs +++ b/servo/components/style/traversal.rs @@ -350,7 +350,11 @@ where rule_inclusion, PseudoElementResolution::IfApplicable, ) - .resolve_primary_style(style.as_deref(), layout_parent_style.as_deref()); + .resolve_primary_style( + style.as_deref(), + layout_parent_style.as_deref(), + selectors::matching::IncludeStartingStyle::No, + ); let is_display_contents = primary_style.style().is_display_contents(); @@ -639,7 +643,10 @@ where PseudoElementResolution::IfApplicable, ); - resolver.cascade_styles_with_default_parents(cascade_inputs) + resolver.cascade_styles_with_default_parents( + cascade_inputs, + data.may_have_starting_style(), + ) }, CascadeOnly => { // Skipping full matching, load cascade inputs from previous values. @@ -653,7 +660,10 @@ where PseudoElementResolution::IfApplicable, ); - resolver.cascade_styles_with_default_parents(cascade_inputs) + resolver.cascade_styles_with_default_parents( + cascade_inputs, + data.may_have_starting_style(), + ) }; // Insert into the cache, but only if this style isn't reused from a diff --git a/servo/components/style/values/animated/mod.rs b/servo/components/style/values/animated/mod.rs index 31ea206fc0..235ddcba5a 100644 --- a/servo/components/style/values/animated/mod.rs +++ b/servo/components/style/values/animated/mod.rs @@ -273,6 +273,23 @@ where } } +impl<T> ToAnimatedValue for thin_vec::ThinVec<T> +where + T: ToAnimatedValue, +{ + type AnimatedValue = thin_vec::ThinVec<<T as ToAnimatedValue>::AnimatedValue>; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.into_iter().map(T::to_animated_value).collect() + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + animated.into_iter().map(T::from_animated_value).collect() + } +} + impl<T> ToAnimatedValue for Box<T> where T: ToAnimatedValue, @@ -452,6 +469,16 @@ where } } +impl<T> ToAnimatedZero for thin_vec::ThinVec<T> +where + T: ToAnimatedZero, +{ + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + self.iter().map(|v| v.to_animated_zero()).collect() + } +} + impl<T> ToAnimatedZero for Box<[T]> where T: ToAnimatedZero, diff --git a/servo/components/style/values/computed/length_percentage.rs b/servo/components/style/values/computed/length_percentage.rs index c448025dd1..f5f3303141 100644 --- a/servo/components/style/values/computed/length_percentage.rs +++ b/servo/components/style/values/computed/length_percentage.rs @@ -680,12 +680,12 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { } } - fn unitless_value(&self) -> f32 { - match *self { + fn unitless_value(&self) -> Option<f32> { + Some(match *self { Self::Length(ref l) => l.px(), Self::Percentage(ref p) => p.0, Self::Number(n) => n, - } + }) } fn new_number(value: f32) -> Self { @@ -709,9 +709,18 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { return None; } - let self_negative = self.is_negative(); - if self_negative != other.is_negative() { - return Some(if self_negative { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater }); + let Ok(self_negative) = self.is_negative() else { + return None; + }; + let Ok(other_negative) = other.is_negative() else { + return None; + }; + if self_negative != other_negative { + return Some(if self_negative { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + }); } match (self, other) { @@ -774,15 +783,17 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { } else { // The right side is not a number, so the result should be in the units of the right // side. - other.map(|v| v * *left); - std::mem::swap(self, other); - true + if other.map(|v| v * *left).is_ok() { + std::mem::swap(self, other); + true + } else { + false + } } } else if let Self::Number(ref right) = *other { // The left side is not a number, but the right side is, so the result is the left // side unit. - self.map(|v| v * *right); - true + self.map(|v| v * *right).is_ok() } else { // Neither side is a number, so a product is not possible. false @@ -814,8 +825,8 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { }) } - fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - match self { + fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { + Ok(match self { Self::Length(value) => { *value = Length::new(op(value.px())); }, @@ -825,7 +836,7 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { Self::Number(value) => { *value = op(*value); }, - } + }) } fn simplify(&mut self) {} @@ -921,7 +932,7 @@ impl specified::CalcLengthPercentage { } }), Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n), - Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) => { + Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) | Leaf::ColorComponent(..) => { unreachable!("Shouldn't have parsed") }, }); diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs index 85aadb401f..ca32408a79 100644 --- a/servo/components/style/values/computed/mod.rs +++ b/servo/components/style/values/computed/mod.rs @@ -649,6 +649,25 @@ where } } +impl<T> ToComputedValue for thin_vec::ThinVec<T> +where + T: ToComputedValue, +{ + type ComputedValue = thin_vec::ThinVec<<T as ToComputedValue>::ComputedValue>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.iter() + .map(|item| item.to_computed_value(context)) + .collect() + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + computed.iter().map(T::from_computed_value).collect() + } +} + // NOTE(emilio): This is implementable more generically, but it's unlikely // what you want there, as it forces you to have an extra allocation. // diff --git a/servo/components/style/values/generics/calc.rs b/servo/components/style/values/generics/calc.rs index abcb5fe6eb..215db1932b 100644 --- a/servo/components/style/values/generics/calc.rs +++ b/servo/components/style/values/generics/calc.rs @@ -255,12 +255,15 @@ bitflags! { const TIME = 1 << 3; /// <resolution> const RESOLUTION = 1 << 4; + /// A component of a color (r, g, b, h, s, l, alpha, etc.) + const COLOR_COMPONENT = 1 << 5; /// <length-percentage> const LENGTH_PERCENTAGE = Self::LENGTH.bits() | Self::PERCENTAGE.bits(); // NOTE: When you add to this, make sure to make Atan2 deal with these. /// Allow all units. - const ALL = Self::LENGTH.bits() | Self::PERCENTAGE.bits() | Self::ANGLE.bits() | Self::TIME.bits() | Self::RESOLUTION.bits(); + const ALL = Self::LENGTH.bits() | Self::PERCENTAGE.bits() | Self::ANGLE.bits() | + Self::TIME.bits() | Self::RESOLUTION.bits() | Self::COLOR_COMPONENT.bits(); } } @@ -323,8 +326,8 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { /// Returns the unit of the leaf. fn unit(&self) -> CalcUnits; - /// Returns the unitless value of this leaf. - fn unitless_value(&self) -> f32; + /// Returns the unitless value of this leaf if one is available. + fn unitless_value(&self) -> Option<f32>; /// Return true if the units of both leaves are equal. (NOTE: Does not take /// the values into account) @@ -347,23 +350,31 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { fn as_number(&self) -> Option<f32>; /// Whether this value is known-negative. - fn is_negative(&self) -> bool { - self.unitless_value().is_sign_negative() + fn is_negative(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_sign_negative())) + .unwrap_or_else(|| Err(())) } /// Whether this value is infinite. - fn is_infinite(&self) -> bool { - self.unitless_value().is_infinite() + fn is_infinite(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_infinite())) + .unwrap_or_else(|| Err(())) } /// Whether this value is zero. - fn is_zero(&self) -> bool { - self.unitless_value().is_zero() + fn is_zero(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_zero())) + .unwrap_or_else(|| Err(())) } /// Whether this value is NaN. - fn is_nan(&self) -> bool { - self.unitless_value().is_nan() + fn is_nan(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_nan())) + .unwrap_or_else(|| Err(())) } /// Tries to merge one leaf into another using the sum, that is, perform `x` + `y`. @@ -379,12 +390,7 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { O: Fn(f32, f32) -> f32; /// Map the value of this node with the given operation. - fn map(&mut self, op: impl FnMut(f32) -> f32); - - /// Negates the leaf. - fn negate(&mut self) { - self.map(std::ops::Neg::neg); - } + fn map(&mut self, op: impl FnMut(f32) -> f32) -> Result<(), ()>; /// Canonicalizes the expression if necessary. fn simplify(&mut self); @@ -393,16 +399,20 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { fn sort_key(&self) -> SortKey; /// Create a new leaf containing the sign() result of the given leaf. - fn sign_from(leaf: &impl CalcNodeLeaf) -> Self { - Self::new_number(if leaf.is_nan() { + fn sign_from(leaf: &impl CalcNodeLeaf) -> Result<Self, ()> { + let Some(value) = leaf.unitless_value() else { + return Err(()); + }; + + Ok(Self::new_number(if value.is_nan() { f32::NAN - } else if leaf.is_zero() { - leaf.unitless_value() - } else if leaf.is_negative() { + } else if value.is_zero() { + value + } else if value.is_sign_negative() { -1.0 } else { 1.0 - }) + })) } } @@ -426,8 +436,8 @@ impl<L: CalcNodeLeaf> CalcNode<L> { /// Change all the leaf nodes to have the given value. This is useful when /// you have `calc(1px * nan)` and you want to replace the product node with /// `calc(nan)`, in which case the unit will be retained. - fn coerce_to_value(&mut self, value: f32) { - self.map(|_| value); + fn coerce_to_value(&mut self, value: f32) -> Result<(), ()> { + self.map(|_| value) } /// Return true if a product is distributive over this node. @@ -436,7 +446,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { #[inline] pub fn is_product_distributive(&self) -> bool { match self { - Self::Leaf(_) => true, + Self::Leaf(l) => l.unit() != CalcUnits::COLOR_COMPONENT, Self::Sum(children) => children.iter().all(|c| c.is_product_distributive()), _ => false, } @@ -546,7 +556,11 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } match *self { - CalcNode::Leaf(ref mut leaf) => leaf.negate(), + CalcNode::Leaf(ref mut leaf) => { + if leaf.map(std::ops::Neg::neg).is_err() { + wrap_self_in_negate(self) + } + }, CalcNode::Negate(ref mut value) => { // Don't negate the value here. Replace `self` with it's child. let result = mem::replace(value.as_mut(), Self::dummy()); @@ -668,7 +682,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } if self.is_product_distributive() { - self.map(|v| v * number); + if self.map(|v| v * number).is_err() { + return false; + } return true; } } @@ -682,7 +698,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } if other.is_product_distributive() { - other.map(|v| v * number); + if other.map(|v| v * number).is_err() { + return false; + } std::mem::swap(self, other); return true; } @@ -706,48 +724,52 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } /// Map the value of this node with the given operation. - pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) { + pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { + fn map_internal<L: CalcNodeLeaf>( + node: &mut CalcNode<L>, + op: &mut impl FnMut(f32) -> f32, + ) -> Result<(), ()> { match node { CalcNode::Leaf(l) => l.map(op), CalcNode::Negate(v) | CalcNode::Invert(v) => map_internal(v, op), CalcNode::Sum(children) | CalcNode::Product(children) => { for node in &mut **children { - map_internal(node, op); + map_internal(node, op)?; } + Ok(()) }, CalcNode::MinMax(children, _) => { for node in &mut **children { - map_internal(node, op); + map_internal(node, op)?; } + Ok(()) }, CalcNode::Clamp { min, center, max } => { - map_internal(min, op); - map_internal(center, op); - map_internal(max, op); + map_internal(min, op)?; + map_internal(center, op)?; + map_internal(max, op) }, CalcNode::Round { value, step, .. } => { - map_internal(value, op); - map_internal(step, op); + map_internal(value, op)?; + map_internal(step, op) }, CalcNode::ModRem { dividend, divisor, .. } => { - map_internal(dividend, op); - map_internal(divisor, op); + map_internal(dividend, op)?; + map_internal(divisor, op) }, CalcNode::Hypot(children) => { for node in &mut **children { - map_internal(node, op); + map_internal(node, op)?; } + Ok(()) }, - CalcNode::Abs(child) | CalcNode::Sign(child) => { - map_internal(child, op); - }, + CalcNode::Abs(child) | CalcNode::Sign(child) => map_internal(child, op), } } - map_internal(self, &mut op); + map_internal(self, &mut op) } /// Convert this `CalcNode` into a `CalcNode` with a different leaf kind. @@ -849,12 +871,12 @@ impl<L: CalcNodeLeaf> CalcNode<L> { Self::Leaf(l) => leaf_to_output_fn(l), Self::Negate(child) => { let mut result = child.resolve_internal(leaf_to_output_fn)?; - result.map(|v| v.neg()); + result.map(|v| v.neg())?; Ok(result) }, Self::Invert(child) => { let mut result = child.resolve_internal(leaf_to_output_fn)?; - result.map(|v| 1.0 / v); + result.map(|v| 1.0 / v)?; Ok(result) }, Self::Sum(children) => { @@ -878,13 +900,13 @@ impl<L: CalcNodeLeaf> CalcNode<L> { Some(left) => { // Left side is a number, so we use the right node as the result. result = right; - result.map(|v| v * left); + result.map(|v| v * left)?; }, None => { // Left side is not a number, so check if the right side is. match right.as_number() { Some(right) => { - result.map(|v| v * right); + result.map(|v| v * right)?; }, None => { // Multiplying with both sides having units. @@ -900,7 +922,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { Self::MinMax(children, op) => { let mut result = children[0].resolve_internal(leaf_to_output_fn)?; - if result.is_nan() { + if result.is_nan()? { return Ok(result); } @@ -912,7 +934,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - if candidate.is_nan() { + if candidate.is_nan()? { result = candidate; break; } @@ -938,15 +960,15 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - if min.is_nan() { + if min.is_nan()? { return Ok(min); } - if center.is_nan() { + if center.is_nan()? { return Ok(center); } - if max.is_nan() { + if max.is_nan()? { return Ok(max); } @@ -972,7 +994,10 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - let step = step.unitless_value().abs(); + let Some(step) = step.unitless_value() else { + return Err(()); + }; + let step = step.abs(); value.map(|value| { // TODO(emilio): Seems like at least a few of these @@ -1039,7 +1064,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } }, } - }); + })?; Ok(value) }, @@ -1055,13 +1080,15 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - let divisor = divisor.unitless_value(); - dividend.map(|dividend| op.apply(dividend, divisor)); + let Some(divisor) = divisor.unitless_value() else { + return Err(()); + }; + dividend.map(|dividend| op.apply(dividend, divisor))?; Ok(dividend) }, Self::Hypot(children) => { let mut result = children[0].resolve_internal(leaf_to_output_fn)?; - result.map(|v| v.powi(2)); + result.map(|v| v.powi(2))?; for child in children.iter().skip(1) { let child_value = child.resolve_internal(leaf_to_output_fn)?; @@ -1070,45 +1097,48 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - result.map(|v| v + child_value.unitless_value().powi(2)); + let Some(child_value) = child_value.unitless_value() else { + return Err(()); + }; + result.map(|v| v + child_value.powi(2))?; } - result.map(|v| v.sqrt()); + result.map(|v| v.sqrt())?; Ok(result) }, Self::Abs(ref c) => { let mut result = c.resolve_internal(leaf_to_output_fn)?; - result.map(|v| v.abs()); + result.map(|v| v.abs())?; Ok(result) }, Self::Sign(ref c) => { let result = c.resolve_internal(leaf_to_output_fn)?; - Ok(L::sign_from(&result)) + Ok(L::sign_from(&result)?) }, } } - fn is_negative_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_negative(), + fn is_negative_leaf(&self) -> Result<bool, ()> { + Ok(match *self { + Self::Leaf(ref l) => l.is_negative()?, _ => false, - } + }) } - fn is_zero_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_zero(), + fn is_zero_leaf(&self) -> Result<bool, ()> { + Ok(match *self { + Self::Leaf(ref l) => l.is_zero()?, _ => false, - } + }) } - fn is_infinite_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_infinite(), + fn is_infinite_leaf(&self) -> Result<bool, ()> { + Ok(match *self { + Self::Leaf(ref l) => l.is_infinite()?, _ => false, - } + }) } /// Visits all the nodes in this calculation tree recursively, starting by @@ -1250,54 +1280,64 @@ impl<L: CalcNodeLeaf> CalcNode<L> { ref mut value, ref mut step, } => { - if step.is_zero_leaf() { - value.coerce_to_value(f32::NAN); + if value_or_stop!(step.is_zero_leaf()) { + value_or_stop!(value.coerce_to_value(f32::NAN)); replace_self_with!(&mut **value); return; } - if value.is_infinite_leaf() && step.is_infinite_leaf() { - value.coerce_to_value(f32::NAN); + if value_or_stop!(value.is_infinite_leaf()) && + value_or_stop!(step.is_infinite_leaf()) + { + value_or_stop!(value.coerce_to_value(f32::NAN)); replace_self_with!(&mut **value); return; } - if value.is_infinite_leaf() { + if value_or_stop!(value.is_infinite_leaf()) { replace_self_with!(&mut **value); return; } - if step.is_infinite_leaf() { + if value_or_stop!(step.is_infinite_leaf()) { match strategy { RoundingStrategy::Nearest | RoundingStrategy::ToZero => { - value.coerce_to_value(0.0); + value_or_stop!(value.coerce_to_value(0.0)); replace_self_with!(&mut **value); return; }, RoundingStrategy::Up => { - if !value.is_negative_leaf() && !value.is_zero_leaf() { - value.coerce_to_value(f32::INFINITY); + if !value_or_stop!(value.is_negative_leaf()) && + !value_or_stop!(value.is_zero_leaf()) + { + value_or_stop!(value.coerce_to_value(f32::INFINITY)); replace_self_with!(&mut **value); return; - } else if !value.is_negative_leaf() && value.is_zero_leaf() { + } else if !value_or_stop!(value.is_negative_leaf()) && + value_or_stop!(value.is_zero_leaf()) + { replace_self_with!(&mut **value); return; } else { - value.coerce_to_value(0.0); + value_or_stop!(value.coerce_to_value(0.0)); replace_self_with!(&mut **value); return; } }, RoundingStrategy::Down => { - if value.is_negative_leaf() && !value.is_zero_leaf() { - value.coerce_to_value(f32::INFINITY); + if value_or_stop!(value.is_negative_leaf()) && + !value_or_stop!(value.is_zero_leaf()) + { + value_or_stop!(value.coerce_to_value(f32::INFINITY)); replace_self_with!(&mut **value); return; - } else if value.is_negative_leaf() && value.is_zero_leaf() { + } else if value_or_stop!(value.is_negative_leaf()) && + value_or_stop!(value.is_zero_leaf()) + { replace_self_with!(&mut **value); return; } else { - value.coerce_to_value(0.0); + value_or_stop!(value.coerce_to_value(0.0)); replace_self_with!(&mut **value); return; } @@ -1305,17 +1345,18 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } } - if step.is_negative_leaf() { + if value_or_stop!(step.is_negative_leaf()) { step.negate(); } let remainder = value_or_stop!(value.try_op(step, Rem::rem)); - if remainder.is_zero_leaf() { + if value_or_stop!(remainder.is_zero_leaf()) { replace_self_with!(&mut **value); return; } - let (mut lower_bound, mut upper_bound) = if value.is_negative_leaf() { + let (mut lower_bound, mut upper_bound) = if value_or_stop!(value.is_negative_leaf()) + { let upper_bound = value_or_stop!(value.try_op(&remainder, Sub::sub)); let lower_bound = value_or_stop!(upper_bound.try_op(&step, Sub::sub)); @@ -1348,11 +1389,11 @@ impl<L: CalcNodeLeaf> CalcNode<L> { let mut lower_diff = lower_bound.clone(); let mut upper_diff = upper_bound.clone(); - if lower_diff.is_negative_leaf() { + if value_or_stop!(lower_diff.is_negative_leaf()) { lower_diff.negate(); } - if upper_diff.is_negative_leaf() { + if value_or_stop!(upper_diff.is_negative_leaf()) { upper_diff.negate(); } @@ -1518,13 +1559,13 @@ impl<L: CalcNodeLeaf> CalcNode<L> { }, Self::Abs(ref mut child) => { if let CalcNode::Leaf(leaf) = child.as_mut() { - leaf.map(|v| v.abs()); + value_or_stop!(leaf.map(|v| v.abs())); replace_self_with!(&mut **child); } }, Self::Sign(ref mut child) => { if let CalcNode::Leaf(leaf) = child.as_mut() { - let mut result = Self::Leaf(L::sign_from(leaf)); + let mut result = Self::Leaf(value_or_stop!(L::sign_from(leaf))); replace_self_with!(&mut result); } }, @@ -1553,7 +1594,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { // 1. If root’s child is a number (not a percentage or dimension) return the // reciprocal of the child’s value. if leaf.unit().is_empty() { - child.map(|v| 1.0 / v); + value_or_stop!(child.map(|v| 1.0 / v)); replace_self_with!(&mut **child); } }, @@ -1678,10 +1719,12 @@ impl<L: CalcNodeLeaf> CalcNode<L> { if !first { match child { Self::Leaf(l) => { - if l.is_negative() { + if let Ok(true) = l.is_negative() { dest.write_str(" - ")?; let mut negated = l.clone(); - negated.negate(); + // We can unwrap here, because we already + // checked if the value inside is negative. + negated.map(std::ops::Neg::neg).unwrap(); negated.to_css(dest)?; } else { dest.write_str(" + ")?; diff --git a/servo/components/style/values/generics/counters.rs b/servo/components/style/values/generics/counters.rs index 3f23c74b33..d6db48f9c4 100644 --- a/servo/components/style/values/generics/counters.rs +++ b/servo/components/style/values/generics/counters.rs @@ -203,6 +203,41 @@ fn is_decimal(counter_type: &CounterStyleType) -> bool { *counter_type == CounterStyle::decimal() } +/// The non-normal, non-none values of the content property. +#[derive( + Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToShmem, +)] +#[repr(C)] +pub struct GenericContentItems<Image> { + /// The actual content items. Note that, past the alt marker, only some subset (strings, + /// attr(), counter()) + pub items: thin_vec::ThinVec<GenericContentItem<Image>>, + /// The index at which alt text starts, always non-zero. If equal to items.len(), no alt text + /// exists. + pub alt_start: usize, +} + +impl<Image> ToCss for GenericContentItems<Image> +where + Image: ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + for (i, item) in self.items.iter().enumerate() { + if i == self.alt_start { + dest.write_str(" /")?; + } + if i != 0 { + dest.write_str(" ")?; + } + item.to_css(dest)?; + } + Ok(()) + } +} + /// The specified value for the `content` property. /// /// https://drafts.csswg.org/css-content/#propdef-content @@ -216,7 +251,7 @@ pub enum GenericContent<Image> { /// `none` reserved keyword. None, /// Content items. - Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<Image>>), + Items(GenericContentItems<Image>), } pub use self::GenericContent as Content; diff --git a/servo/components/style/values/resolved/mod.rs b/servo/components/style/values/resolved/mod.rs index d830474fe6..fc59f8f36d 100644 --- a/servo/components/style/values/resolved/mod.rs +++ b/servo/components/style/values/resolved/mod.rs @@ -180,6 +180,25 @@ where } } +impl<T> ToResolvedValue for thin_vec::ThinVec<T> +where + T: ToResolvedValue, +{ + type ResolvedValue = thin_vec::ThinVec<<T as ToResolvedValue>::ResolvedValue>; + + #[inline] + fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { + self.into_iter() + .map(|item| item.to_resolved_value(context)) + .collect() + } + + #[inline] + fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { + resolved.into_iter().map(T::from_resolved_value).collect() + } +} + impl<T> ToResolvedValue for Box<T> where T: ToResolvedValue, diff --git a/servo/components/style/values/specified/box.rs b/servo/components/style/values/specified/box.rs index ee50227504..9b87103048 100644 --- a/servo/components/style/values/specified/box.rs +++ b/servo/components/style/values/specified/box.rs @@ -1495,6 +1495,9 @@ pub enum Appearance { /// For HTML's <input type=number> #[parse(condition = "ParserContext::chrome_rules_enabled")] NumberInput, + /// For HTML's <input type=password> + #[parse(condition = "ParserContext::chrome_rules_enabled")] + PasswordInput, /// The progress bar's progress indicator #[parse(condition = "ParserContext::chrome_rules_enabled")] Progresschunk, @@ -1576,27 +1579,6 @@ pub enum Appearance { /// A tooltip. #[parse(condition = "ParserContext::chrome_rules_enabled")] Tooltip, - /// A listbox or tree widget header - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeheader, - /// An individual header cell - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeheadercell, - /// A tree item. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeitem, - /// A tree widget branch line - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeline, - /// A tree widget twisty. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treetwisty, - /// Open tree widget twisty. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treetwistyopen, - /// A tree widget. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeview, /// Mac help button. #[parse(condition = "ParserContext::chrome_rules_enabled")] diff --git a/servo/components/style/values/specified/calc.rs b/servo/components/style/values/specified/calc.rs index 17f043ac58..a354963ce1 100644 --- a/servo/components/style/values/specified/calc.rs +++ b/servo/components/style/values/specified/calc.rs @@ -6,6 +6,7 @@ //! //! [calc]: https://drafts.csswg.org/css-values/#calc-notation +use crate::color::parsing::ChannelKeyword; use crate::parser::ParserContext; use crate::values::generics::calc::{ self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis, @@ -80,6 +81,8 @@ pub enum Leaf { Time(Time), /// `<resolution>` Resolution(Resolution), + /// A component of a color. + ColorComponent(ChannelKeyword), /// `<percentage>` Percentage(CSSFloat), /// `<number>` @@ -107,6 +110,7 @@ impl ToCss for Leaf { Self::Percentage(p) => serialize_percentage(p, dest), Self::Angle(ref a) => a.to_css(dest), Self::Time(ref t) => t.to_css(dest), + Self::ColorComponent(ref s) => s.to_css(dest), } } } @@ -152,19 +156,21 @@ impl generic::CalcNodeLeaf for Leaf { Leaf::Angle(_) => CalcUnits::ANGLE, Leaf::Time(_) => CalcUnits::TIME, Leaf::Resolution(_) => CalcUnits::RESOLUTION, + Leaf::ColorComponent(_) => CalcUnits::COLOR_COMPONENT, Leaf::Percentage(_) => CalcUnits::PERCENTAGE, Leaf::Number(_) => CalcUnits::empty(), } } - fn unitless_value(&self) -> f32 { - match *self { + fn unitless_value(&self) -> Option<f32> { + Some(match *self { Self::Length(ref l) => l.unitless_value(), Self::Percentage(n) | Self::Number(n) => n, Self::Resolution(ref r) => r.dppx(), Self::Angle(ref a) => a.degrees(), Self::Time(ref t) => t.seconds(), - } + Self::ColorComponent(_) => return None, + }) } fn new_number(value: f32) -> Self { @@ -182,8 +188,8 @@ impl generic::CalcNodeLeaf for Leaf { return None; } - let self_negative = self.is_negative(); - if self_negative != other.is_negative() { + let self_negative = self.is_negative().unwrap_or(false); + if self_negative != other.is_negative().unwrap_or(false) { return Some(if self_negative { cmp::Ordering::Less } else { @@ -198,10 +204,11 @@ impl generic::CalcNodeLeaf for Leaf { (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()), (&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()), (&Number(ref one), &Number(ref other)) => one.partial_cmp(other), + (&ColorComponent(ref one), &ColorComponent(ref other)) => one.partial_cmp(other), _ => { match *self { Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) | - Resolution(..) => {}, + Resolution(..) | ColorComponent(..) => {}, } unsafe { debug_unreachable!("Forgot a branch?"); @@ -216,14 +223,15 @@ impl generic::CalcNodeLeaf for Leaf { Leaf::Angle(_) | Leaf::Time(_) | Leaf::Resolution(_) | - Leaf::Percentage(_) => None, + Leaf::Percentage(_) | + Leaf::ColorComponent(_) => None, Leaf::Number(value) => Some(value), } } fn sort_key(&self) -> SortKey { match *self { - Self::Number(..) => SortKey::Number, + Self::Number(..) | Self::ColorComponent(..) => SortKey::Number, Self::Percentage(..) => SortKey::Percentage, Self::Time(..) => SortKey::Sec, Self::Resolution(..) => SortKey::Dppx, @@ -316,7 +324,7 @@ impl generic::CalcNodeLeaf for Leaf { _ => { match *other { Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..) | - Length(..) => {}, + Length(..) | ColorComponent(..) => {}, } unsafe { debug_unreachable!(); @@ -336,15 +344,17 @@ impl generic::CalcNodeLeaf for Leaf { } else { // The right side is not a number, so the result should be in the units of the right // side. - other.map(|v| v * *left); - std::mem::swap(self, other); - true + if other.map(|v| v * *left).is_ok() { + std::mem::swap(self, other); + true + } else { + false + } } } else if let Self::Number(ref right) = *other { // The left side is not a number, but the right side is, so the result is the left // side unit. - self.map(|v| v * *right); - true + self.map(|v| v * *right).is_ok() } else { // Neither side is a number, so a product is not possible. false @@ -392,7 +402,7 @@ impl generic::CalcNodeLeaf for Leaf { _ => { match *other { Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) | - Resolution(..) => {}, + Resolution(..) | ColorComponent(..) => {}, } unsafe { debug_unreachable!(); @@ -401,15 +411,16 @@ impl generic::CalcNodeLeaf for Leaf { } } - fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - match self { + fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { + Ok(match self { Leaf::Length(one) => *one = one.map(op), Leaf::Angle(one) => *one = specified::Angle::from_calc(op(one.degrees())), Leaf::Time(one) => *one = specified::Time::from_seconds(op(one.seconds())), Leaf::Resolution(one) => *one = specified::Resolution::from_dppx(op(one.dppx())), Leaf::Percentage(one) => *one = op(*one), Leaf::Number(one) => *one = op(*one), - } + Leaf::ColorComponent(..) => return Err(()), + }) } } @@ -468,15 +479,30 @@ impl CalcNode { CalcNode::parse(context, input, function, allowed_units) }, &Token::Ident(ref ident) => { - let number = match_ignore_ascii_case! { &**ident, - "e" => std::f32::consts::E, - "pi" => std::f32::consts::PI, - "infinity" => f32::INFINITY, - "-infinity" => f32::NEG_INFINITY, - "nan" => f32::NAN, - _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))), + let leaf = match_ignore_ascii_case! { &**ident, + "e" => Leaf::Number(std::f32::consts::E), + "pi" => Leaf::Number(std::f32::consts::PI), + "infinity" => Leaf::Number(f32::INFINITY), + "-infinity" => Leaf::Number(f32::NEG_INFINITY), + "nan" => Leaf::Number(f32::NAN), + _ => { + if crate::color::parsing::rcs_enabled() && + allowed_units.intersects(CalcUnits::COLOR_COMPONENT) + { + if let Ok(channel_keyword) = ChannelKeyword::from_ident(&ident) { + Leaf::ColorComponent(channel_keyword) + } else { + return Err(location + .new_unexpected_token_error(Token::Ident(ident.clone()))); + } + } else { + return Err( + location.new_unexpected_token_error(Token::Ident(ident.clone())) + ); + } + }, }; - Ok(CalcNode::Leaf(Leaf::Number(number))) + Ok(CalcNode::Leaf(leaf)) }, t => Err(location.new_unexpected_token_error(t.clone())), } @@ -817,7 +843,9 @@ impl CalcNode { if let Ok(resolved) = right.resolve() { if let Some(number) = resolved.as_number() { if number != 1.0 && left.is_product_distributive() { - left.map(|l| l / number); + if left.map(|l| l / number).is_err() { + return InPlaceDivisionResult::Invalid; + } return InPlaceDivisionResult::Merged; } } else { diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs index f823ba7d30..289d89e44e 100644 --- a/servo/components/style/values/specified/color.rs +++ b/servo/components/style/values/specified/color.rs @@ -5,12 +5,8 @@ //! Specified color values. use super::AllowQuirks; -use crate::color::component::ColorComponent; -use crate::color::convert::normalize_hue; -use crate::color::parsing::{ - self, ColorParser, FromParsedColor, NumberOrAngle, NumberOrPercentage, -}; -use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace}; +use crate::color::mix::ColorInterpolationMethod; +use crate::color::{parsing, AbsoluteColor, ColorSpace}; use crate::media_queries::Device; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; @@ -19,8 +15,7 @@ use crate::values::generics::color::{ }; use crate::values::specified::Percentage; use crate::values::{normalize, CustomIdent}; -use cssparser::color::OPAQUE; -use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token}; +use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser, Token}; use std::fmt::{self, Write}; use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; @@ -429,199 +424,6 @@ impl SystemColor { } } -impl<T> From<ColorComponent<T>> for Option<T> { - fn from(value: ColorComponent<T>) -> Self { - match value { - ColorComponent::None => None, - ColorComponent::Value(value) => Some(value), - } - } -} - -impl ColorComponent<NumberOrPercentage> { - #[inline] - fn into_alpha(self) -> Option<f32> { - match self { - ColorComponent::None => None, - ColorComponent::Value(number_or_percentage) => { - Some(normalize(number_or_percentage.to_number(1.0)).clamp(0.0, OPAQUE)) - }, - } - } -} - -impl FromParsedColor for Color { - fn from_current_color() -> Self { - Color::CurrentColor - } - - fn from_rgba( - red: ColorComponent<u8>, - green: ColorComponent<u8>, - blue: ColorComponent<u8>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - macro_rules! c { - ($c:expr) => {{ - match $c { - ColorComponent::None => 0u8, - ColorComponent::Value(value) => value, - } - }}; - } - - // Legacy rgb() doesn't support "none" alpha values and falls back to 0. - let alpha = alpha.into_alpha().unwrap_or(0.0); - - AbsoluteColor::srgb_legacy(c!(red), c!(green), c!(blue), alpha).into() - } - - fn from_hsl( - hue: ColorComponent<NumberOrAngle>, - saturation: ColorComponent<NumberOrPercentage>, - lightness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // Percent reference range for S and L: 0% = 0.0, 100% = 100.0 - const LIGHTNESS_RANGE: f32 = 100.0; - const SATURATION_RANGE: f32 = 100.0; - - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - let saturation = - saturation.map_value(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)); - let lightness = - lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)); - - AbsoluteColor::new( - ColorSpace::Hsl, - hue, - saturation, - lightness, - alpha.into_alpha(), - ) - .into() - } - - fn from_hwb( - hue: ColorComponent<NumberOrAngle>, - whiteness: ColorComponent<NumberOrPercentage>, - blackness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // Percent reference range for W and B: 0% = 0.0, 100% = 100.0 - const WHITENESS_RANGE: f32 = 100.0; - const BLACKNESS_RANGE: f32 = 100.0; - - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - let whiteness = - whiteness.map_value(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)); - let blackness = - blackness.map_value(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)); - - AbsoluteColor::new( - ColorSpace::Hwb, - hue, - whiteness, - blackness, - alpha.into_alpha(), - ) - .into() - } - - fn from_lab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 100.0 - // for a and b: -100% = -125, 100% = 125 - const LIGHTNESS_RANGE: f32 = 100.0; - const A_B_RANGE: f32 = 125.0; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let a = a.map_value(|a| a.to_number(A_B_RANGE)); - let b = b.map_value(|b| b.to_number(A_B_RANGE)); - - AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha.into_alpha()).into() - } - - fn from_lch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 100.0 - // for C: 0% = 0, 100% = 150 - const LIGHTNESS_RANGE: f32 = 100.0; - const CHROMA_RANGE: f32 = 150.0; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE)); - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - - AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha.into_alpha()).into() - } - - fn from_oklab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 1.0 - // for a and b: -100% = -0.4, 100% = 0.4 - const LIGHTNESS_RANGE: f32 = 1.0; - const A_B_RANGE: f32 = 0.4; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let a = a.map_value(|a| a.to_number(A_B_RANGE)); - let b = b.map_value(|b| b.to_number(A_B_RANGE)); - - AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha.into_alpha()).into() - } - - fn from_oklch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 1.0 - // for C: 0% = 0.0 100% = 0.4 - const LIGHTNESS_RANGE: f32 = 1.0; - const CHROMA_RANGE: f32 = 0.4; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE)); - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - - AbsoluteColor::new( - ColorSpace::Oklch, - lightness, - chroma, - hue, - alpha.into_alpha(), - ) - .into() - } - - fn from_color_function( - color_space: PredefinedColorSpace, - c1: ColorComponent<NumberOrPercentage>, - c2: ColorComponent<NumberOrPercentage>, - c3: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - let c1 = c1.map_value(|c| c.to_number(1.0)); - let c2 = c2.map_value(|c| c.to_number(1.0)); - let c3 = c3.map_value(|c| c.to_number(1.0)); - - AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha.into_alpha()).into() - } -} - /// Whether to preserve authored colors during parsing. That's useful only if we /// plan to serialize the color back. #[derive(Copy, Clone)] @@ -658,8 +460,7 @@ impl Color { }, }; - let color_parser = ColorParser { context: &context }; - match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) { + match input.try_parse(|i| parsing::parse_color_with(context, i)) { Ok(mut color) => { if let Color::Absolute(ref mut absolute) = color { // Because we can't set the `authored` value at construction time, we have to set it @@ -827,12 +628,9 @@ impl Color { loc: &cssparser::SourceLocation, ) -> Result<Self, ParseError<'i>> { match cssparser::color::parse_hash_color(bytes) { - Ok((r, g, b, a)) => Ok(Self::from_rgba( - r.into(), - g.into(), - b.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: a }), - )), + Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy( + r, g, b, a, + ))), Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } } diff --git a/servo/components/style/values/specified/counters.rs b/servo/components/style/values/specified/counters.rs index 7760be91d7..6e41497caf 100644 --- a/servo/components/style/values/specified/counters.rs +++ b/servo/components/style/values/specified/counters.rs @@ -192,29 +192,33 @@ impl Parse for Content { return Ok(generics::Content::None); } - let mut content = vec![]; - let mut has_alt_content = false; + let mut items = thin_vec::ThinVec::new(); + let mut alt_start = None; loop { - { + if alt_start.is_none() { if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) { - content.push(generics::ContentItem::Image(image)); + items.push(generics::ContentItem::Image(image)); continue; } } - match input.next() { - Ok(&Token::QuotedString(ref value)) => { - content.push(generics::ContentItem::String( + let Ok(t) = input.next() else { break }; + match *t { + Token::QuotedString(ref value) => { + items.push(generics::ContentItem::String( value.as_ref().to_owned().into(), )); }, - Ok(&Token::Function(ref name)) => { + Token::Function(ref name) => { + // FIXME(emilio): counter() / counters() should be valid per spec past + // the alt marker, but it's likely non-trivial to support and other + // browsers don't support it either, so restricting it for now. let result = match_ignore_ascii_case! { &name, - "counter" => input.parse_nested_block(|input| { + "counter" if alt_start.is_none() => input.parse_nested_block(|input| { let name = CustomIdent::parse(input, &[])?; let style = Content::parse_counter_style(context, input); Ok(generics::ContentItem::Counter(name, style)) }), - "counters" => input.parse_nested_block(|input| { + "counters" if alt_start.is_none() => input.parse_nested_block(|input| { let name = CustomIdent::parse(input, &[])?; input.expect_comma()?; let separator = input.expect_string()?.as_ref().to_owned().into(); @@ -232,17 +236,16 @@ impl Parse for Content { )) } }?; - content.push(result); + items.push(result); }, - Ok(&Token::Ident(ref ident)) => { - content.push(match_ignore_ascii_case! { &ident, + Token::Ident(ref ident) if alt_start.is_none() => { + items.push(match_ignore_ascii_case! { &ident, "open-quote" => generics::ContentItem::OpenQuote, "close-quote" => generics::ContentItem::CloseQuote, "no-open-quote" => generics::ContentItem::NoOpenQuote, "no-close-quote" => generics::ContentItem::NoCloseQuote, #[cfg(feature = "gecko")] - "-moz-alt-content" => { - has_alt_content = true; + "-moz-alt-content" if context.in_ua_sheet() => { generics::ContentItem::MozAltContent }, "-moz-label-content" if context.chrome_rules_enabled() => { @@ -256,17 +259,26 @@ impl Parse for Content { } }); }, - Err(_) => break, - Ok(t) => { + Token::Delim('/') + if alt_start.is_none() && + !items.is_empty() && + static_prefs::pref!("layout.css.content.alt-text.enabled") => + { + alt_start = Some(items.len()); + }, + ref t => { let t = t.clone(); return Err(input.new_unexpected_token_error(t)); }, } } - // We don't allow to parse `-moz-alt-content` in multiple positions. - if content.is_empty() || (has_alt_content && content.len() != 1) { + if items.is_empty() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - Ok(generics::Content::Items(content.into())) + let alt_start = alt_start.unwrap_or(items.len()); + Ok(generics::Content::Items(generics::GenericContentItems { + items, + alt_start, + })) } } diff --git a/servo/components/style_traits/Cargo.toml b/servo/components/style_traits/Cargo.toml index 81d6e2bdf4..dd1147dd07 100644 --- a/servo/components/style_traits/Cargo.toml +++ b/servo/components/style_traits/Cargo.toml @@ -16,7 +16,7 @@ gecko = ["nsstring"] [dependencies] app_units = "0.7" bitflags = "2" -cssparser = "0.33" +cssparser = "0.34" euclid = "0.22" lazy_static = "1" malloc_size_of = { path = "../malloc_size_of" } @@ -27,6 +27,7 @@ serde = "1.0" servo_arc = { path = "../servo_arc" } servo_atoms = { path = "../atoms", optional = true } servo_url = { path = "../url", optional = true } +thin-vec = "0.2" to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } webrender_api = { git = "https://github.com/servo/webrender", optional = true } diff --git a/servo/components/style_traits/lib.rs b/servo/components/style_traits/lib.rs index 9bb2b3c655..713165b000 100644 --- a/servo/components/style_traits/lib.rs +++ b/servo/components/style_traits/lib.rs @@ -29,6 +29,7 @@ extern crate servo_arc; extern crate servo_atoms; #[cfg(feature = "servo")] extern crate servo_url; +extern crate thin_vec; extern crate to_shmem; #[macro_use] extern crate to_shmem_derive; diff --git a/servo/components/style_traits/specified_value_info.rs b/servo/components/style_traits/specified_value_info.rs index 1dd368d36e..6e6c06ce95 100644 --- a/servo/components/style_traits/specified_value_info.rs +++ b/servo/components/style_traits/specified_value_info.rs @@ -9,6 +9,7 @@ use crate::owned_slice::OwnedSlice; use servo_arc::Arc; use std::ops::Range; use std::sync::Arc as StdArc; +use thin_vec::ThinVec; /// Type of value that a property supports. This is used by Gecko's /// devtools to make sense about value it parses, and types listed @@ -119,6 +120,7 @@ macro_rules! impl_generic_specified_value_info { impl_generic_specified_value_info!(Option<T>); impl_generic_specified_value_info!(OwnedSlice<T>); impl_generic_specified_value_info!(Vec<T>); +impl_generic_specified_value_info!(ThinVec<T>); impl_generic_specified_value_info!(Arc<T>); impl_generic_specified_value_info!(StdArc<T>); impl_generic_specified_value_info!(ArcSlice<T>); diff --git a/servo/components/to_shmem/Cargo.toml b/servo/components/to_shmem/Cargo.toml index 09e78f57d2..684b7fdb5e 100644 --- a/servo/components/to_shmem/Cargo.toml +++ b/servo/components/to_shmem/Cargo.toml @@ -14,7 +14,7 @@ servo = ["cssparser/serde", "string_cache"] gecko = [] [dependencies] -cssparser = "0.33" +cssparser = "0.34" servo_arc = { path = "../servo_arc" } smallbitvec = "2.1.1" smallvec = "1.0" |