From def92d1b8e9d373e2f6f27c366d578d97d8960c6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:50 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- servo/components/style/Cargo.toml | 2 - servo/components/style/animation.rs | 7 +- servo/components/style/applicable_declarations.rs | 5 +- servo/components/style/bloom.rs | 5 +- servo/components/style/build.rs | 12 +- servo/components/style/color/color_function.rs | 198 ++++++++ servo/components/style/color/component.rs | 58 ++- servo/components/style/color/mix.rs | 3 +- servo/components/style/color/mod.rs | 6 +- servo/components/style/color/parsing.rs | 416 +++++++++------ servo/components/style/context.rs | 1 - servo/components/style/custom_properties.rs | 7 +- servo/components/style/dom.rs | 18 +- servo/components/style/dom_apis.rs | 6 +- servo/components/style/driver.rs | 21 +- servo/components/style/gecko/arc_types.rs | 13 +- servo/components/style/gecko/media_features.rs | 19 +- servo/components/style/gecko/media_queries.rs | 41 +- .../style/gecko/non_ts_pseudo_class_list.rs | 1 - servo/components/style/gecko/pseudo_element.rs | 6 + servo/components/style/gecko/selector_parser.rs | 4 +- servo/components/style/gecko/snapshot.rs | 29 ++ servo/components/style/gecko/url.rs | 2 +- servo/components/style/gecko/wrapper.rs | 79 +-- .../gecko_bindings/sugar/ns_style_auto_array.rs | 2 +- servo/components/style/gecko_string_cache/mod.rs | 2 +- servo/components/style/global_style_data.rs | 2 - .../style/invalidation/element/element_wrapper.rs | 24 + .../style/invalidation/element/invalidation_map.rs | 37 ++ .../invalidation/element/relative_selector.rs | 25 +- .../invalidation/element/state_and_attributes.rs | 66 ++- servo/components/style/invalidation/stylesheets.rs | 7 +- servo/components/style/matching.rs | 14 +- servo/components/style/media_queries/media_list.rs | 2 +- servo/components/style/parallel.rs | 1 - servo/components/style/parser.rs | 70 ++- servo/components/style/properties/build.py | 72 +-- servo/components/style/properties/cascade.rs | 22 +- servo/components/style/properties/data.py | 108 ++-- .../style/properties/declaration_block.rs | 4 +- servo/components/style/properties/helpers.mako.rs | 6 +- .../properties/helpers/animated_properties.mako.rs | 14 +- .../style/properties/longhands/background.mako.rs | 20 +- .../style/properties/longhands/border.mako.rs | 25 +- .../style/properties/longhands/box.mako.rs | 53 +- .../style/properties/longhands/column.mako.rs | 15 +- .../style/properties/longhands/counters.mako.rs | 10 +- .../style/properties/longhands/effects.mako.rs | 15 +- .../style/properties/longhands/font.mako.rs | 18 +- .../properties/longhands/inherited_box.mako.rs | 15 +- .../properties/longhands/inherited_svg.mako.rs | 4 - .../properties/longhands/inherited_table.mako.rs | 14 +- .../properties/longhands/inherited_text.mako.rs | 34 +- .../properties/longhands/inherited_ui.mako.rs | 6 +- .../style/properties/longhands/list.mako.rs | 19 +- .../style/properties/longhands/margin.mako.rs | 3 +- .../style/properties/longhands/outline.mako.rs | 16 +- .../style/properties/longhands/padding.mako.rs | 3 +- .../style/properties/longhands/page.mako.rs | 2 - .../style/properties/longhands/position.mako.rs | 68 +-- .../style/properties/longhands/svg.mako.rs | 2 - .../style/properties/longhands/table.mako.rs | 5 +- .../style/properties/longhands/text.mako.rs | 15 +- .../style/properties/longhands/ui.mako.rs | 36 +- .../style/properties/longhands/xul.mako.rs | 4 - servo/components/style/properties/mod.rs | 8 +- .../components/style/properties/properties.mako.rs | 30 +- .../style/properties/shorthands/background.mako.rs | 4 +- .../style/properties/shorthands/border.mako.rs | 19 +- .../style/properties/shorthands/box.mako.rs | 37 +- .../style/properties/shorthands/column.mako.rs | 4 +- .../style/properties/shorthands/counters.mako.rs | 3 + .../style/properties/shorthands/effects.mako.rs | 3 + .../style/properties/shorthands/font.mako.rs | 5 +- .../properties/shorthands/inherited_box.mako.rs | 3 + .../properties/shorthands/inherited_table.mako.rs | 3 + .../properties/shorthands/inherited_ui.mako.rs | 3 + .../style/properties/shorthands/list.mako.rs | 21 +- .../style/properties/shorthands/margin.mako.rs | 6 +- .../style/properties/shorthands/outline.mako.rs | 2 +- .../style/properties/shorthands/padding.mako.rs | 6 +- .../style/properties/shorthands/page.mako.rs | 3 + .../style/properties/shorthands/position.mako.rs | 14 +- .../style/properties/shorthands/table.mako.rs | 3 + .../style/properties/shorthands/text.mako.rs | 2 +- .../style/properties/shorthands/ui.mako.rs | 104 +++- .../style/properties/shorthands/xul.mako.rs | 3 + servo/components/style/queries/condition.rs | 88 +--- servo/components/style/queries/feature.rs | 6 +- .../components/style/queries/feature_expression.rs | 4 +- servo/components/style/scoped_tls.rs | 1 - servo/components/style/str.rs | 1 - servo/components/style/style_adjuster.rs | 8 +- .../components/style/stylesheets/container_rule.rs | 2 +- servo/components/style/stylesheets/import_rule.rs | 2 +- servo/components/style/stylesheets/mod.rs | 41 +- servo/components/style/stylesheets/page_rule.rs | 2 +- servo/components/style/stylesheets/rule_list.rs | 5 + servo/components/style/stylesheets/rule_parser.rs | 71 ++- .../components/style/stylesheets/rules_iterator.rs | 2 + servo/components/style/stylesheets/scope_rule.rs | 161 ++++++ .../style/stylesheets/starting_style_rule.rs | 57 +++ servo/components/style/stylesheets/stylesheet.rs | 6 +- servo/components/style/stylist.rs | 52 +- servo/components/style/traversal.rs | 4 +- .../style/values/computed/basic_shape.rs | 188 ++++++- servo/components/style/values/computed/box.rs | 11 +- servo/components/style/values/computed/font.rs | 14 +- servo/components/style/values/computed/length.rs | 23 +- .../style/values/computed/length_percentage.rs | 20 + servo/components/style/values/computed/ratio.rs | 2 +- .../style/values/generics/basic_shape.rs | 532 ++++++++++++++++++- servo/components/style/values/generics/counters.rs | 14 +- servo/components/style/values/generics/image.rs | 4 +- .../components/style/values/generics/transform.rs | 1 - servo/components/style/values/resolved/mod.rs | 1 - .../components/style/values/specified/animation.rs | 40 ++ .../style/values/specified/basic_shape.rs | 206 +++++++- servo/components/style/values/specified/box.rs | 50 +- servo/components/style/values/specified/color.rs | 159 +----- .../components/style/values/specified/counters.rs | 11 +- servo/components/style/values/specified/easing.rs | 11 +- servo/components/style/values/specified/effects.rs | 2 +- servo/components/style/values/specified/image.rs | 2 +- servo/components/style/values/specified/length.rs | 57 ++- servo/components/style/values/specified/motion.rs | 23 +- .../components/style/values/specified/svg_path.rs | 562 +++++++-------------- servo/components/style/values/specified/text.rs | 7 +- 128 files changed, 3002 insertions(+), 1576 deletions(-) create mode 100644 servo/components/style/color/color_function.rs create mode 100644 servo/components/style/properties/shorthands/counters.mako.rs create mode 100644 servo/components/style/properties/shorthands/effects.mako.rs create mode 100644 servo/components/style/properties/shorthands/inherited_box.mako.rs create mode 100644 servo/components/style/properties/shorthands/inherited_table.mako.rs create mode 100644 servo/components/style/properties/shorthands/inherited_ui.mako.rs create mode 100644 servo/components/style/properties/shorthands/page.mako.rs create mode 100644 servo/components/style/properties/shorthands/table.mako.rs create mode 100644 servo/components/style/properties/shorthands/xul.mako.rs create mode 100644 servo/components/style/stylesheets/scope_rule.rs create mode 100644 servo/components/style/stylesheets/starting_style_rule.rs (limited to 'servo/components/style') diff --git a/servo/components/style/Cargo.toml b/servo/components/style/Cargo.toml index acf1bcf6fe..d6e37d4903 100644 --- a/servo/components/style/Cargo.toml +++ b/servo/components/style/Cargo.toml @@ -21,8 +21,6 @@ gecko = ["nsstring", "serde", "style_traits/gecko", "bindgen", "regex", "toml", servo = ["serde", "style_traits/servo", "servo_atoms", "servo_config", "html5ever", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "arrayvec/use_union", "servo_url", "string_cache", "to_shmem/servo", "servo_arc/servo"] -servo-layout-2013 = [] -servo-layout-2020 = [] gecko_debug = [] gecko_refcount_logging = [] diff --git a/servo/components/style/animation.rs b/servo/components/style/animation.rs index b865120aba..1cab5da84e 100644 --- a/servo/components/style/animation.rs +++ b/servo/components/style/animation.rs @@ -1273,9 +1273,10 @@ pub fn start_transitions_if_applicable( ) -> PropertyDeclarationIdSet { let mut properties_that_transition = PropertyDeclarationIdSet::default(); for transition in new_style.transition_properties() { - let physical_property = PropertyDeclarationId::Longhand( - transition.longhand_id.to_physical(new_style.writing_mode), - ); + let physical_property = transition + .property + .as_borrowed() + .to_physical(new_style.writing_mode); if properties_that_transition.contains(physical_property) { continue; } diff --git a/servo/components/style/applicable_declarations.rs b/servo/components/style/applicable_declarations.rs index 96049b76e3..b2fb05b0b8 100644 --- a/servo/components/style/applicable_declarations.rs +++ b/servo/components/style/applicable_declarations.rs @@ -142,8 +142,9 @@ pub struct ApplicableDeclarationBlock { /// The style source, either a style rule, or a property declaration block. #[ignore_malloc_size_of = "Arc"] pub source: StyleSource, - /// The bits containing the source order, cascade level, and shadow cascade - /// order. + /// Order of appearance in which this rule appears - Set to 0 if not relevant + /// (e.g. Declaration from `style="/*...*/"`, presentation hints, animations + /// - See `CascadePriority` instead). source_order: u32, /// The specificity of the selector. pub specificity: u32, diff --git a/servo/components/style/bloom.rs b/servo/components/style/bloom.rs index 824acb7114..63be881505 100644 --- a/servo/components/style/bloom.rs +++ b/servo/components/style/bloom.rs @@ -8,6 +8,7 @@ #![deny(missing_docs)] use crate::dom::{SendElement, TElement}; +use crate::LocalName; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use owning_ref::OwningHandle; use selectors::bloom::BloomFilter; @@ -107,8 +108,8 @@ impl PushedElement { /// We do this for attributes that are very common but not commonly used in /// selectors. #[inline] -pub fn is_attr_name_excluded_from_filter(atom: &crate::Atom) -> bool { - *atom == atom!("class") || *atom == atom!("id") || *atom == atom!("style") +pub fn is_attr_name_excluded_from_filter(name: &LocalName) -> bool { + *name == local_name!("class") || *name == local_name!("id") || *name == local_name!("style") } /// Gather all relevant hash for fast-reject filters from an element. diff --git a/servo/components/style/build.rs b/servo/components/style/build.rs index 2247e87618..4b27edbe2c 100644 --- a/servo/components/style/build.rs +++ b/servo/components/style/build.rs @@ -71,16 +71,12 @@ fn generate_properties(engine: &str) { fn main() { let gecko = cfg!(feature = "gecko"); let servo = cfg!(feature = "servo"); - let l2013 = cfg!(feature = "servo-layout-2013"); - let l2020 = cfg!(feature = "servo-layout-2020"); - let engine = match (gecko, servo, l2013, l2020) { - (true, false, false, false) => "gecko", - (false, true, true, false) => "servo-2013", - (false, true, false, true) => "servo-2020", + let engine = match (gecko, servo) { + (true, false) => "gecko", + (false, true) => "servo", _ => panic!( "\n\n\ - The style crate requires enabling one of its 'servo' or 'gecko' feature flags \ - and, in the 'servo' case, one of 'servo-layout-2013' or 'servo-layout-2020'.\ + The style crate requires enabling one of its 'servo' or 'gecko' feature flags. \ \n\n" ), }; diff --git a/servo/components/style/color/color_function.rs b/servo/components/style/color/color_function.rs new file mode 100644 index 0000000000..2edb4fff3b --- /dev/null +++ b/servo/components/style/color/color_function.rs @@ -0,0 +1,198 @@ +/* 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/. */ + +//! Output of parsing a color function, e.g. rgb(..), hsl(..), color(..) + +use crate::values::normalize; +use cssparser::color::{PredefinedColorSpace, OPAQUE}; + +use super::{ + component::ColorComponent, + convert::normalize_hue, + parsing::{NumberOrAngle, NumberOrPercentage}, + AbsoluteColor, ColorSpace, +}; + +/// Represents a specified color function. +#[derive(Debug)] +pub enum ColorFunction { + /// + Rgb( + ColorComponent, // red + ColorComponent, // green + ColorComponent, // blue + ColorComponent, // alpha + ), + /// + Hsl( + ColorComponent, // hue + ColorComponent, // saturation + ColorComponent, // lightness + ColorComponent, // alpha + ), + /// + Hwb( + ColorComponent, // hue + ColorComponent, // whiteness + ColorComponent, // blackness + ColorComponent, // alpha + ), + /// + Lab( + ColorComponent, // lightness + ColorComponent, // a + ColorComponent, // b + ColorComponent, // alpha + ), + /// + Lch( + ColorComponent, // lightness + ColorComponent, // chroma + ColorComponent, // hue + ColorComponent, // alpha + ), + /// + Oklab( + ColorComponent, // lightness + ColorComponent, // a + ColorComponent, // b + ColorComponent, // alpha + ), + /// + Oklch( + ColorComponent, // lightness + ColorComponent, // chroma + ColorComponent, // hue + ColorComponent, // alpha + ), + /// + Color( + PredefinedColorSpace, + ColorComponent, // red / x + ColorComponent, // green / y + ColorComponent, // blue / z + ColorComponent, // alpha + ), +} + +impl ColorFunction { + /// Try to resolve the color function to an [`AbsoluteColor`] that does not + /// contain any variables (currentcolor, color components, etc.). + pub fn resolve_to_absolute(&self) -> AbsoluteColor { + macro_rules! value { + ($v:expr) => {{ + match $v { + ColorComponent::None => None, + // value should be Copy. + ColorComponent::Value(value) => Some(*value), + } + }}; + } + + macro_rules! alpha { + ($alpha:expr) => {{ + value!($alpha).map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE)) + }}; + } + + match self { + ColorFunction::Rgb(r, g, b, alpha) => { + let r = value!(r).unwrap_or(0); + let g = value!(g).unwrap_or(0); + let b = value!(b).unwrap_or(0); + + AbsoluteColor::srgb_legacy(r, g, b, alpha!(alpha).unwrap_or(0.0)) + }, + ColorFunction::Hsl(h, s, l, alpha) => { + // 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( + 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), + ) + }, + ColorFunction::Hwb(h, w, b, alpha) => { + // 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( + 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), + ) + }, + ColorFunction::Lab(l, a, b, alpha) => { + // 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; + + AbsoluteColor::new( + ColorSpace::Lab, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(a).map(|a| a.to_number(A_B_RANGE)), + value!(b).map(|b| b.to_number(A_B_RANGE)), + alpha!(alpha), + ) + }, + ColorFunction::Lch(l, c, h, alpha) => { + // 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; + + AbsoluteColor::new( + ColorSpace::Lch, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(c).map(|c| c.to_number(CHROMA_RANGE)), + value!(h).map(|angle| normalize_hue(angle.degrees())), + alpha!(alpha), + ) + }, + ColorFunction::Oklab(l, a, b, alpha) => { + // 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; + + AbsoluteColor::new( + ColorSpace::Oklab, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(a).map(|a| a.to_number(A_B_RANGE)), + value!(b).map(|b| b.to_number(A_B_RANGE)), + alpha!(alpha), + ) + }, + ColorFunction::Oklch(l, c, h, alpha) => { + // 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; + + AbsoluteColor::new( + ColorSpace::Oklch, + value!(l).map(|l| l.to_number(LIGHTNESS_RANGE)), + value!(c).map(|c| c.to_number(CHROMA_RANGE)), + value!(h).map(|angle| normalize_hue(angle.degrees())), + alpha!(alpha), + ) + }, + ColorFunction::Color(color_space, r, g, b, alpha) => AbsoluteColor::new( + (*color_space).into(), + value!(r).map(|c| c.to_number(1.0)), + value!(g).map(|c| c.to_number(1.0)), + value!(b).map(|c| c.to_number(1.0)), + alpha!(alpha), + ), + } + } +} diff --git a/servo/components/style/color/component.rs b/servo/components/style/color/component.rs index 9f101a460c..5f9d8a137e 100644 --- a/servo/components/style/color/component.rs +++ b/servo/components/style/color/component.rs @@ -4,8 +4,18 @@ //! Parse/serialize and resolve a single color component. +use crate::{ + parser::ParserContext, + values::{ + generics::calc::CalcUnits, + specified::calc::{CalcNode as SpecifiedCalcNode, Leaf as SpecifiedLeaf}, + }, +}; +use cssparser::{Parser, Token}; +use style_traits::{ParseError, StyleParseErrorKind}; + /// A single color component. -#[derive(Clone, MallocSizeOf, PartialEq, ToShmem)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum ColorComponent { /// The "none" keyword. None, @@ -46,3 +56,49 @@ impl ColorComponent { } } } + +/// An utility trait that allows the construction of [ColorComponent] +/// `ValueType`'s after parsing a color component. +pub trait ColorComponentType: Sized { + /// Return the [CalcUnits] flags that the impl can handle. + fn units() -> CalcUnits; + + /// Try to create a new component from the given token. + fn try_from_token(token: &Token) -> Result; + + /// Try to create a new component from the given [CalcNodeLeaf] that was + /// resolved from a [CalcNode]. + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result; +} + +impl ColorComponent { + /// Parse a single [ColorComponent]. + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_none: bool, + ) -> Result> { + let location = input.current_source_location(); + + match *input.next()? { + Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { + Ok(ColorComponent::None) + }, + Token::Function(ref name) => { + let function = SpecifiedCalcNode::math_function(context, name, location)?; + let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?; + + let Ok(resolved_leaf) = node.resolve() else { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + }; + + ValueType::try_from_leaf(&resolved_leaf) + .map(Self::Value) + .map_err(|_| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + }, + ref t => ValueType::try_from_token(t) + .map(Self::Value) + .map_err(|_| location.new_unexpected_token_error(t.clone())), + } + } +} diff --git a/servo/components/style/color/mix.rs b/servo/components/style/color/mix.rs index bcc4628575..3b3970e1e0 100644 --- a/servo/components/style/color/mix.rs +++ b/servo/components/style/color/mix.rs @@ -402,7 +402,8 @@ fn adjust_hue(left: &mut f32, right: &mut f32, hue_interpolation: HueInterpolati // https://drafts.csswg.org/css-color/#longer HueInterpolationMethod::Longer => { let delta = *right - *left; - if 0. < delta && delta < 180. { + // In the specific case of delta == 0 we need to use Decreasing + if 0. <= delta && delta < 180. { *left += 360.; } else if -180. < delta && delta <= 0. { *right += 360.; diff --git a/servo/components/style/color/mod.rs b/servo/components/style/color/mod.rs index 6f35daaa8a..f7e661cddf 100644 --- a/servo/components/style/color/mod.rs +++ b/servo/components/style/color/mod.rs @@ -4,9 +4,13 @@ //! Color support functions. -pub mod component; +/// cbindgen:ignore +mod color_function; + /// cbindgen:ignore pub mod convert; + +pub mod component; pub mod mix; pub mod parsing; mod to_css; diff --git a/servo/components/style/color/parsing.rs b/servo/components/style/color/parsing.rs index 1ae32c4ad7..68bcee6c56 100644 --- a/servo/components/style/color/parsing.rs +++ b/servo/components/style/color/parsing.rs @@ -8,14 +8,34 @@ //! Relative colors, color-mix, system colors, and other such things require better calc() support //! and integration. -use crate::color::component::ColorComponent; -use cssparser::color::{ - clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE, +use super::{ + color_function::ColorFunction, + component::{ColorComponent, ColorComponentType}, + AbsoluteColor, +}; +use crate::{ + parser::ParserContext, + values::{ + generics::calc::CalcUnits, + specified::{ + angle::Angle as SpecifiedAngle, calc::Leaf as SpecifiedLeaf, + color::Color as SpecifiedColor, + }, + }, +}; +use cssparser::{ + color::{clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE}, + match_ignore_ascii_case, CowRcStr, Parser, Token, }; -use cssparser::{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. +#[inline] +pub fn rcs_enabled() -> bool { + static_prefs::pref!("layout.css.relative-color-syntax.enabled") +} + impl From for ColorComponent { #[inline] fn from(value: u8) -> Self { @@ -29,56 +49,39 @@ impl From for ColorComponent { /// CSS escaping (if relevant) should be resolved before calling this function. /// (For example, the value of an `Ident` token is fine.) #[inline] -pub fn parse_color_keyword(ident: &str) -> Result -where - Output: FromParsedColor, -{ +pub fn parse_color_keyword(ident: &str) -> Result { Ok(match_ignore_ascii_case! { ident, - "transparent" => Output::from_rgba( - 0u8.into(), - 0u8.into(), - 0u8.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: 0.0 }), - ), - "currentcolor" => Output::from_current_color(), + "transparent" => { + SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(0u8, 0u8, 0u8, 0.0)) + }, + "currentcolor" => SpecifiedColor::CurrentColor, _ => { let (r, g, b) = cssparser::color::parse_named_color(ident)?; - Output::from_rgba( - r.into(), - g.into(), - b.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: OPAQUE }), - ) + SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, OPAQUE)) }, }) } /// Parse a CSS color using the specified [`ColorParser`] and return a new color /// value on success. -pub fn parse_color_with<'i, 't, P>( - color_parser: &P, +pub fn parse_color_with<'i, 't>( + color_parser: &ColorParser<'_, '_>, input: &mut Parser<'i, 't>, -) -> Result> -where - P: ColorParser<'i>, -{ +) -> Result> { let location = input.current_source_location(); let token = input.next()?; match *token { Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes()) .map(|(r, g, b, a)| { - P::Output::from_rgba( - r.into(), - g.into(), - b.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: a }), - ) + SpecifiedColor::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)) }), Token::Ident(ref value) => parse_color_keyword(value), Token::Function(ref name) => { let name = name.clone(); return input.parse_nested_block(|arguments| { - parse_color_function(color_parser, name, arguments) + Ok(SpecifiedColor::from_absolute_color( + parse_color_function(color_parser, name, arguments)?.resolve_to_absolute(), + )) }); }, _ => Err(()), @@ -88,22 +91,19 @@ where /// Parse one of the color functions: rgba(), lab(), color(), etc. #[inline] -fn parse_color_function<'i, 't, P>( - color_parser: &P, +fn parse_color_function<'i, 't>( + color_parser: &ColorParser<'_, '_>, name: CowRcStr<'i>, arguments: &mut Parser<'i, 't>, -) -> Result> -where - P: ColorParser<'i>, -{ +) -> Result> { 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, P::Output::from_lab), - "lch" => parse_lch_like(color_parser, arguments, P::Output::from_lch), - "oklab" => parse_lab_like(color_parser, arguments, P::Output::from_oklab), - "oklch" => parse_lch_like(color_parser, arguments, P::Output::from_oklch), + "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), _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))), }?; @@ -113,13 +113,10 @@ where Ok(color) } -fn parse_legacy_alpha<'i, 't, P>( - color_parser: &P, +fn parse_legacy_alpha<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result, ParseError<'i>> { if !arguments.is_exhausted() { arguments.expect_comma()?; color_parser.parse_number_or_percentage(arguments, false) @@ -130,13 +127,10 @@ where } } -fn parse_modern_alpha<'i, 't, P>( - color_parser: &P, +fn parse_modern_alpha<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result, ParseError<'i>> { if !arguments.is_exhausted() { arguments.expect_delim('/')?; color_parser.parse_number_or_percentage(arguments, true) @@ -157,22 +151,45 @@ impl ColorComponent { } } +/// Parse the relative color syntax "from" syntax `from `. +fn parse_origin_color<'i, 't>( + color_parser: &ColorParser<'_, '_>, + arguments: &mut Parser<'i, 't>, +) -> Result, ParseError<'i>> { + if !rcs_enabled() { + return Ok(None); + } + + // Not finding the from keyword is not an error, it just means we don't + // have an origin color. + if arguments + .try_parse(|p| p.expect_ident_matching("from")) + .is_err() + { + return Ok(None); + } + + // We still fail if we can't parse the origin color. + parse_color_with(color_parser, arguments).map(|color| Some(color)) +} + #[inline] -fn parse_rgb<'i, 't, P>( - color_parser: &P, +fn parse_rgb<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result> -where - P: ColorParser<'i>, -{ +) -> Result> { + let origin_color = parse_origin_color(color_parser, arguments)?; + let location = arguments.current_source_location(); let maybe_red = color_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. - let is_legacy_syntax = - !maybe_red.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); + // are parsing the legacy syntax. Legacy syntax also doesn't support an + // origin color. + let is_legacy_syntax = 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 { let Ok(is_percentage) = maybe_red.is_percentage() else { @@ -217,25 +234,26 @@ where (red, green, blue, alpha) }; - Ok(P::Output::from_rgba(red, green, blue, alpha)) + Ok(ColorFunction::Rgb(red, green, blue, alpha)) } /// Parses hsl syntax. /// /// #[inline] -fn parse_hsl<'i, 't, P>( - color_parser: &P, +fn parse_hsl<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result> -where - P: ColorParser<'i>, -{ +) -> Result> { + let origin_color = parse_origin_color(color_parser, arguments)?; + let hue = color_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. - let is_legacy_syntax = !hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok(); + // the legacy syntax. Legacy syntax also doesn't support an origin color. + let is_legacy_syntax = 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 @@ -260,29 +278,28 @@ where ) }; - Ok(P::Output::from_hsl(hue, saturation, lightness, alpha)) + Ok(ColorFunction::Hsl(hue, saturation, lightness, alpha)) } /// Parses hwb syntax. /// /// #[inline] -fn parse_hwb<'i, 't, P>( - color_parser: &P, +fn parse_hwb<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result> -where - P: ColorParser<'i>, -{ +) -> Result> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let (hue, whiteness, blackness, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_angle, - P::parse_number_or_percentage, - P::parse_number_or_percentage, + ColorParser::parse_number_or_angle, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, )?; - Ok(P::Output::from_hwb(hue, whiteness, blackness, alpha)) + Ok(ColorFunction::Hwb(hue, whiteness, blackness, alpha)) } type IntoLabFn = fn( @@ -293,20 +310,19 @@ type IntoLabFn = fn( ) -> Output; #[inline] -fn parse_lab_like<'i, 't, P>( - color_parser: &P, +fn parse_lab_like<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, - into_color: IntoLabFn, -) -> Result> -where - P: ColorParser<'i>, -{ + into_color: IntoLabFn, +) -> Result> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let (lightness, a, b, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_percentage, - P::parse_number_or_percentage, - P::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, )?; Ok(into_color(lightness, a, b, alpha)) @@ -320,20 +336,19 @@ type IntoLchFn = fn( ) -> Output; #[inline] -fn parse_lch_like<'i, 't, P>( - color_parser: &P, +fn parse_lch_like<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, - into_color: IntoLchFn, -) -> Result> -where - P: ColorParser<'i>, -{ + into_color: IntoLchFn, +) -> Result> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let (lightness, chroma, hue, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_percentage, - P::parse_number_or_percentage, - P::parse_number_or_angle, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_angle, )?; Ok(into_color(lightness, chroma, hue, alpha)) @@ -341,13 +356,12 @@ where /// Parse the color() function. #[inline] -fn parse_color_with_color_space<'i, 't, P>( - color_parser: &P, +fn parse_color_with_color_space<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result> -where - P: ColorParser<'i>, -{ +) -> Result> { + let _origin_color = parse_origin_color(color_parser, arguments)?; + let color_space = { let location = arguments.current_source_location(); @@ -359,18 +373,12 @@ where let (c1, c2, c3, alpha) = parse_components( color_parser, arguments, - P::parse_number_or_percentage, - P::parse_number_or_percentage, - P::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, + ColorParser::parse_number_or_percentage, )?; - Ok(P::Output::from_color_function( - color_space, - c1, - c2, - c3, - alpha, - )) + Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha)) } type ComponentParseResult<'i, R1, R2, R3> = Result< @@ -384,18 +392,29 @@ type ComponentParseResult<'i, R1, R2, R3> = Result< >; /// Parse the color components and alpha with the modern [color-4] syntax. -pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>( - color_parser: &P, +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 - P: ColorParser<'i>, - F1: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result, ParseError<'i>>, - F2: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result, ParseError<'i>>, - F3: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result, ParseError<'i>>, + F1: FnOnce( + &ColorParser<'a, 'b>, + &mut Parser<'i, 't>, + bool, + ) -> Result, ParseError<'i>>, + F2: FnOnce( + &ColorParser<'a, 'b>, + &mut Parser<'i, 't>, + bool, + ) -> Result, ParseError<'i>>, + F3: FnOnce( + &ColorParser<'a, 'b>, + &mut Parser<'i, 't>, + bool, + ) -> Result, ParseError<'i>>, { let r1 = f1(color_parser, input, true)?; let r2 = f2(color_parser, input, true)?; @@ -407,6 +426,7 @@ where } /// Either a number or a percentage. +#[derive(Clone, Copy, Debug)] pub enum NumberOrPercentage { /// ``. Number { @@ -432,7 +452,32 @@ impl NumberOrPercentage { } } +impl ColorComponentType for NumberOrPercentage { + fn units() -> CalcUnits { + CalcUnits::PERCENTAGE + } + + fn try_from_token(token: &Token) -> Result { + Ok(match *token { + Token::Number { value, .. } => Self::Number { value }, + Token::Percentage { unit_value, .. } => Self::Percentage { unit_value }, + _ => { + return Err(()); + }, + }) + } + + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result { + Ok(match *leaf { + SpecifiedLeaf::Percentage(unit_value) => Self::Percentage { unit_value }, + SpecifiedLeaf::Number(value) => Self::Number { value }, + _ => return Err(()), + }) + } +} + /// Either an angle or a number. +#[derive(Clone, Copy, Debug)] pub enum NumberOrAngle { /// ``. Number { @@ -457,45 +502,118 @@ impl NumberOrAngle { } } -/// A trait that can be used to hook into how `cssparser` parses color -/// components, with the intention of implementing more complicated behavior. -/// -/// For example, this is used by Servo to support calc() in color. -pub trait ColorParser<'i> { - /// The type that the parser will construct on a successful parse. - type Output: FromParsedColor; - - /// Parse an `` or ``. - /// - /// Returns the result in degrees. - fn parse_number_or_angle<'t>( +impl ColorComponentType for NumberOrAngle { + fn units() -> CalcUnits { + CalcUnits::ANGLE + } + + fn try_from_token(token: &Token) -> Result { + Ok(match *token { + Token::Number { value, .. } => Self::Number { value }, + Token::Dimension { + value, ref unit, .. + } => { + let degrees = + SpecifiedAngle::parse_dimension(value, unit, /* from_calc = */ false) + .map(|angle| angle.degrees())?; + + NumberOrAngle::Angle { degrees } + }, + _ => { + return Err(()); + }, + }) + } + + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result { + Ok(match *leaf { + SpecifiedLeaf::Angle(angle) => Self::Angle { + degrees: angle.degrees(), + }, + SpecifiedLeaf::Number(value) => Self::Number { value }, + _ => return Err(()), + }) + } +} + +/// The raw f32 here is for . +impl ColorComponentType for f32 { + fn units() -> CalcUnits { + CalcUnits::empty() + } + + fn try_from_token(token: &Token) -> Result { + if let Token::Number { value, .. } = *token { + Ok(value) + } else { + Err(()) + } + } + + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result { + if let SpecifiedLeaf::Number(value) = *leaf { + Ok(value) + } else { + Err(()) + } + } +} + +/// Used to parse the components of a color. +pub struct ColorParser<'a, 'b: 'a> { + /// Parser context used for parsing the colors. + pub context: &'a ParserContext<'b>, +} + +impl<'a, 'b: 'a> ColorParser<'a, 'b> { + /// Parse an `` or `` value. + fn parse_number_or_angle<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result, ParseError<'i>>; + ) -> Result, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } /// Parse a `` value. - /// - /// Returns the result in a number from 0.0 to 1.0. - fn parse_percentage<'t>( + fn parse_percentage<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result, ParseError<'i>>; + ) -> Result, ParseError<'i>> { + let location = input.current_source_location(); + + // We can use the [NumberOrPercentage] type here, because parsing it + // doesn't have any more overhead than just parsing a percentage on its + // own. + Ok( + match ColorComponent::::parse(self.context, input, allow_none)? { + ColorComponent::None => ColorComponent::None, + ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => { + ColorComponent::Value(unit_value) + }, + _ => return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + }, + ) + } /// Parse a `` value. - fn parse_number<'t>( + fn parse_number<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result, ParseError<'i>>; + ) -> Result, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } - /// Parse a `` value or a `` value. - fn parse_number_or_percentage<'t>( + /// Parse a `` or `` value. + fn parse_number_or_percentage<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result, ParseError<'i>>; + ) -> Result, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } } /// This trait is used by the [`ColorParser`] to construct colors of any type. diff --git a/servo/components/style/context.rs b/servo/components/style/context.rs index a2c020475b..8229721006 100644 --- a/servo/components/style/context.rs +++ b/servo/components/style/context.rs @@ -41,7 +41,6 @@ use style_traits::CSSPixel; use style_traits::DevicePixel; #[cfg(feature = "servo")] use style_traits::SpeculativePainter; -use time; pub use selectors::matching::QuirksMode; diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs index b6523dd489..766fe530d9 100644 --- a/servo/components/style/custom_properties.rs +++ b/servo/components/style/custom_properties.rs @@ -125,7 +125,12 @@ macro_rules! lnf_int_variable { }}; } -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 8] = [ +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [ + lnf_int_variable!( + atom!("-moz-mac-titlebar-height"), + MacTitlebarHeight, + int_pixels + ), lnf_int_variable!( atom!("-moz-gtk-csd-titlebar-button-spacing"), TitlebarButtonSpacing, diff --git a/servo/components/style/dom.rs b/servo/components/style/dom.rs index 358d788845..ec99a796c1 100644 --- a/servo/components/style/dom.rs +++ b/servo/components/style/dom.rs @@ -14,12 +14,12 @@ use crate::context::{PostAnimationTasks, UpdateAnimationsTasks}; use crate::data::ElementData; use crate::media_queries::Device; use crate::properties::{AnimationDeclarations, ComputedValues, PropertyDeclarationBlock}; -use crate::selector_parser::{AttrValue, CustomState, Lang, PseudoElement, SelectorImpl}; +use crate::selector_parser::{AttrValue, Lang, PseudoElement, SelectorImpl}; use crate::shared_lock::{Locked, SharedRwLock}; use crate::stylist::CascadeData; use crate::values::computed::Display; use crate::values::AtomIdent; -use crate::WeakAtom; +use crate::{LocalName, WeakAtom}; use atomic_refcell::{AtomicRef, AtomicRefMut}; use dom::ElementState; use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode}; @@ -511,11 +511,6 @@ pub trait TElement: /// Get this element's state, for non-tree-structural pseudos. fn state(&self) -> ElementState; - /// Returns whether this element's CustomStateSet contains a given state. - fn has_custom_state(&self, _state: &CustomState) -> bool { - false - } - /// Returns whether this element has a `part` attribute. fn has_part_attr(&self) -> bool; @@ -530,6 +525,11 @@ pub trait TElement: where F: FnMut(&AtomIdent); + /// Internal iterator for the classes of this element. + fn each_custom_state(&self, callback: F) + where + F: FnMut(&AtomIdent); + /// Internal iterator for the part names of this element. fn each_part(&self, _callback: F) where @@ -540,7 +540,7 @@ pub trait TElement: /// Internal iterator for the attribute names of this element. fn each_attr_name(&self, callback: F) where - F: FnMut(&AtomIdent); + F: FnMut(&LocalName); /// Internal iterator for the part names that this element exports for a /// given part name. @@ -906,7 +906,7 @@ pub trait TElement: fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool; /// Returns the search direction for relative selector invalidation, if it is on the search path. - fn relative_selector_search_direction(&self) -> Option; + fn relative_selector_search_direction(&self) -> ElementSelectorFlags; } /// TNode and TElement aren't Send because we want to be careful and explicit diff --git a/servo/components/style/dom_apis.rs b/servo/components/style/dom_apis.rs index cdc106e1ad..34ea73e5e6 100644 --- a/servo/components/style/dom_apis.rs +++ b/servo/components/style/dom_apis.rs @@ -368,7 +368,7 @@ fn collect_elements_with_id( } } -fn has_attr(element: E, local_name: &AtomIdent) -> bool +fn has_attr(element: E, local_name: &crate::LocalName) -> bool where E: TElement, { @@ -396,7 +396,7 @@ where element.local_name() == &**chosen_name } -fn get_attr_name(component: &Component) -> Option<&AtomIdent> { +fn get_attr_name(component: &Component) -> Option<&crate::LocalName> { let (name, name_lower) = match component { Component::AttributeInNoNamespace { ref local_name, .. } => return Some(local_name), Component::AttributeInNoNamespaceExists { @@ -512,7 +512,7 @@ where enum SimpleFilter<'a> { Class(&'a AtomIdent), - Attr(&'a AtomIdent), + Attr(&'a crate::LocalName), LocalName(&'a LocalName), } diff --git a/servo/components/style/driver.rs b/servo/components/style/driver.rs index a2407cb209..59f401895a 100644 --- a/servo/components/style/driver.rs +++ b/servo/components/style/driver.rs @@ -13,9 +13,7 @@ use crate::dom::{SendNode, TElement, TNode}; use crate::parallel; use crate::scoped_tls::ScopedTLS; use crate::traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; -use rayon; use std::collections::VecDeque; -use time; #[cfg(feature = "servo")] fn should_report_statistics() -> bool { @@ -36,14 +34,17 @@ fn report_statistics(_stats: &PerThreadTraversalStatistics) { fn report_statistics(stats: &PerThreadTraversalStatistics) { // This should only be called in the main thread, or it may be racy // to update the statistics in a global variable. - debug_assert!(unsafe { crate::gecko_bindings::bindings::Gecko_IsMainThread() }); - let gecko_stats = - unsafe { &mut crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton }; - gecko_stats.mElementsTraversed += stats.elements_traversed; - gecko_stats.mElementsStyled += stats.elements_styled; - gecko_stats.mElementsMatched += stats.elements_matched; - gecko_stats.mStylesShared += stats.styles_shared; - gecko_stats.mStylesReused += stats.styles_reused; + unsafe { + debug_assert!(crate::gecko_bindings::bindings::Gecko_IsMainThread()); + let gecko_stats = std::ptr::addr_of_mut!( + crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton + ); + (*gecko_stats).mElementsTraversed += stats.elements_traversed; + (*gecko_stats).mElementsStyled += stats.elements_styled; + (*gecko_stats).mElementsMatched += stats.elements_matched; + (*gecko_stats).mStylesShared += stats.styles_shared; + (*gecko_stats).mStylesReused += stats.styles_reused; + } } fn with_pool_in_place_scope<'scope>( diff --git a/servo/components/style/gecko/arc_types.rs b/servo/components/style/gecko/arc_types.rs index 24bf22d69a..420b86d332 100644 --- a/servo/components/style/gecko/arc_types.rs +++ b/servo/components/style/gecko/arc_types.rs @@ -16,7 +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, StyleRule, StylesheetContents, SupportsRule, + MediaRule, NamespaceRule, PageRule, PropertyRule, ScopeRule, StartingStyleRule, StyleRule, + StylesheetContents, SupportsRule, }; use servo_arc::Arc; @@ -169,3 +170,13 @@ impl_simple_arc_ffi!( Servo_AnimationValue_AddRef, Servo_AnimationValue_Release ); +impl_simple_arc_ffi!( + ScopeRule, + Servo_ScopeRule_AddRef, + Servo_ScopeRule_Release +); +impl_simple_arc_ffi!( + StartingStyleRule, + Servo_StartingStyleRule_AddRef, + Servo_StartingStyleRule_Release +); diff --git a/servo/components/style/gecko/media_features.rs b/servo/components/style/gecko/media_features.rs index 8de45d95c2..df1c5e464b 100644 --- a/servo/components/style/gecko/media_features.rs +++ b/servo/components/style/gecko/media_features.rs @@ -8,13 +8,14 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::ScreenColorGamut; use crate::media_queries::{Device, MediaType}; -use crate::queries::condition::KleeneValue; +use crate::parser::ParserContext; use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; use crate::queries::values::Orientation; use crate::values::computed::{CSSPixelLength, Context, Ratio, Resolution}; use crate::values::AtomString; use app_units::Au; use euclid::default::Size2D; +use selectors::kleene_value::KleeneValue; fn device_size(device: &Device) -> Size2D { let mut width = 0; @@ -286,16 +287,26 @@ fn eval_prefers_contrast(context: &Context, query_value: Option pub enum ForcedColors { /// Page colors are not being forced. None, + /// Page colors would be forced in content. + #[parse(condition = "ParserContext::chrome_rules_enabled")] + Requested, /// Page colors are being forced. Active, } +impl ForcedColors { + /// Returns whether forced-colors is active for this page. + pub fn is_active(self) -> bool { + matches!(self, Self::Active) + } +} + /// https://drafts.csswg.org/mediaqueries-5/#forced-colors fn eval_forced_colors(context: &Context, query_value: Option) -> bool { - let forced = !context.device().use_document_colors(); + let forced = context.device().forced_colors(); match query_value { - Some(query_value) => forced == (query_value == ForcedColors::Active), - None => forced, + Some(query_value) => query_value == forced, + None => forced != ForcedColors::None, } } diff --git a/servo/components/style/gecko/media_queries.rs b/servo/components/style/gecko/media_queries.rs index ef156ab380..ded66027f2 100644 --- a/servo/components/style/gecko/media_queries.rs +++ b/servo/components/style/gecko/media_queries.rs @@ -29,6 +29,8 @@ use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use std::{cmp, fmt}; use style_traits::{CSSPixel, DevicePixel}; +use super::media_features::ForcedColors; + /// The `Device` in Gecko wraps a pres context, has a default values computed, /// and contains all the viewport rule state. pub struct Device { @@ -172,10 +174,9 @@ impl Device { Length::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed))) } - /// Set the font size of the root element (for rem) - pub fn set_root_font_size(&self, size: Length) { - self.root_font_size - .store(size.px().to_bits(), Ordering::Relaxed) + /// Set the font size of the root element (for rem), in zoom-independent CSS pixels. + pub fn set_root_font_size(&self, size: f32) { + self.root_font_size.store(size.to_bits(), Ordering::Relaxed) } /// Get the line height of the root element (for rlh) @@ -186,10 +187,9 @@ impl Device { )) } - /// Set the line height of the root element (for rlh) - pub fn set_root_line_height(&self, size: Length) { - self.root_line_height - .store(size.px().to_bits(), Ordering::Relaxed); + /// Set the line height of the root element (for rlh), in zoom-independent CSS pixels. + pub fn set_root_line_height(&self, size: f32) { + self.root_line_height.store(size.to_bits(), Ordering::Relaxed); } /// The quirks mode of the document. @@ -498,12 +498,27 @@ impl Device { /// Returns whether document colors are enabled. #[inline] - pub fn use_document_colors(&self) -> bool { - let doc = self.document(); - if doc.mIsBeingUsedAsImage() { - return true; + pub fn forced_colors(&self) -> ForcedColors { + if self.document().mIsBeingUsedAsImage() { + // SVG images never force colors. + return ForcedColors::None + } + let prefs = self.pref_sheet_prefs(); + if !prefs.mUseDocumentColors { + return ForcedColors::Active + } + // On Windows, having a high contrast theme also means that the OS is requesting the + // colors to be forced. This is mostly convenience for the front-end, which wants to + // reuse the forced-colors styles for chrome in this case as well, and it's a lot + // more convenient to use `(forced-colors)` than + // `(forced-colors) or ((-moz-platform: windows) and (prefers-contrast))`. + // + // TODO(emilio): We might want to factor in here the lwtheme attribute in the root element + // and so on. + if cfg!(target_os = "windows") && prefs.mUseAccessibilityTheme && prefs.mIsChrome { + return ForcedColors::Requested; } - self.pref_sheet_prefs().mUseDocumentColors + ForcedColors::None } /// Computes a system color and returns it as an nscolor. diff --git a/servo/components/style/gecko/non_ts_pseudo_class_list.rs b/servo/components/style/gecko/non_ts_pseudo_class_list.rs index cc7495dd9c..f628c77ad5 100644 --- a/servo/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/servo/components/style/gecko/non_ts_pseudo_class_list.rs @@ -98,7 +98,6 @@ macro_rules! apply_non_ts_list { // media query results are more expensive than document state changes. So for now // making them pseudo-classes is probably the right trade-off. ("-moz-is-html", MozIsHTML, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), ("-moz-window-inactive", MozWindowInactive, _, _), ] } diff --git a/servo/components/style/gecko/pseudo_element.rs b/servo/components/style/gecko/pseudo_element.rs index 3bcd873455..f0e79a8acd 100644 --- a/servo/components/style/gecko/pseudo_element.rs +++ b/servo/components/style/gecko/pseudo_element.rs @@ -159,6 +159,11 @@ impl PseudoElement { matches!(*self, Self::Highlight(_)) } + /// Whether this pseudo-element is the ::target-text pseudo. + #[inline] + pub fn is_target_text(&self) -> bool { + *self == PseudoElement::TargetText + } /// Whether this pseudo-element supports user action selectors. pub fn supports_user_action_state(&self) -> bool { (self.flags() & structs::CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) != 0 @@ -168,6 +173,7 @@ impl PseudoElement { pub fn enabled_in_content(&self) -> bool { match *self { Self::Highlight(..) => pref!("dom.customHighlightAPI.enabled"), + Self::TargetText => pref!("dom.text_fragments.enabled"), Self::SliderFill | Self::SliderTrack | Self::SliderThumb => { pref!("layout.css.modern-range-pseudos.enabled") }, diff --git a/servo/components/style/gecko/selector_parser.rs b/servo/components/style/gecko/selector_parser.rs index 203e6a3609..100bb45e81 100644 --- a/servo/components/style/gecko/selector_parser.rs +++ b/servo/components/style/gecko/selector_parser.rs @@ -196,7 +196,6 @@ impl NonTSPseudoClass { None => DocumentState::empty(), }, NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, - NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, _ => DocumentState::empty(), } } @@ -214,11 +213,10 @@ impl NonTSPseudoClass { NonTSPseudoClass::MozNativeAnonymous | // :-moz-placeholder is parsed but never matches. NonTSPseudoClass::MozPlaceholder | - // :-moz-is-html, :-moz-lwtheme, :-moz-locale-dir and :-moz-window-inactive + // :-moz-is-html, :-moz-locale-dir and :-moz-window-inactive // depend only on the state of the document, which is invariant across all // elements involved in a given style cache. NonTSPseudoClass::MozIsHTML | - NonTSPseudoClass::MozLWTheme | NonTSPseudoClass::MozLocaleDir(_) | NonTSPseudoClass::MozWindowInactive ) diff --git a/servo/components/style/gecko/snapshot.rs b/servo/components/style/gecko/snapshot.rs index 2ff04406ac..8f6eb120d5 100644 --- a/servo/components/style/gecko/snapshot.rs +++ b/servo/components/style/gecko/snapshot.rs @@ -171,4 +171,33 @@ impl ElementSnapshot for GeckoElementSnapshot { Some(AtomString(unsafe { Atom::from_addrefed(ptr) })) } } + + /// Returns true if the snapshot has stored state for custom states + #[inline] + fn has_custom_states(&self) -> bool { + self.has_any(Flags::CustomState) + } + + /// Returns true if the snapshot has a given CustomState + #[inline] + fn has_custom_state(&self, state: &AtomIdent) -> bool { + unsafe { + self.mCustomStates.iter().any(|setstate| { + AtomIdent::with(setstate.mRawPtr, |setstate| state == setstate) + }) + } + } + + #[inline] + fn each_custom_state(&self, mut callback: F) + where + F: FnMut(&AtomIdent), + { + unsafe { + for atom in self.mCustomStates.iter() { + AtomIdent::with(atom.mRawPtr, &mut callback) + } + } + } + } diff --git a/servo/components/style/gecko/url.rs b/servo/components/style/gecko/url.rs index 7fe32acc20..fa8d22adb6 100644 --- a/servo/components/style/gecko/url.rs +++ b/servo/components/style/gecko/url.rs @@ -18,7 +18,7 @@ use std::fmt::{self, Write}; use std::mem::ManuallyDrop; use std::sync::RwLock; use style_traits::{CssWriter, ParseError, ToCss}; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; +use to_shmem::{SharedMemoryBuilder, ToShmem}; /// A CSS url() value for gecko. #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs index 43cf6e7941..eab968149c 100644 --- a/servo/components/style/gecko/wrapper.rs +++ b/servo/components/style/gecko/wrapper.rs @@ -19,7 +19,7 @@ use crate::bloom::each_relevant_element_hash; use crate::context::{PostAnimationTasks, QuirksMode, SharedStyleContext, UpdateAnimationsTasks}; use crate::data::ElementData; use crate::dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot}; -use crate::gecko::selector_parser::{CustomState, NonTSPseudoClass, PseudoElement, SelectorImpl}; +use crate::gecko::selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl}; use crate::gecko::snapshot_helpers; use crate::gecko_bindings::bindings; use crate::gecko_bindings::bindings::Gecko_ElementHasAnimations; @@ -889,7 +889,11 @@ impl<'le> GeckoElement<'le> { AnimationValue::from_computed_values(property_declaration_id, before_change_style); let to = AnimationValue::from_computed_values(property_declaration_id, after_change_style); - debug_assert_eq!(to.is_some(), from.is_some()); + // If the declaration contains a custom property and getComputedValue was previously called + // before that custom property was defined, `from` will be `None` here. + debug_assert!( + to.is_some() == from.is_some() || matches!(to, Some(AnimationValue::Custom(..))) + ); from != to } @@ -1236,20 +1240,6 @@ impl<'le> TElement for GeckoElement<'le> { ElementState::from_bits_retain(self.state_internal()) } - #[inline] - fn has_custom_state(&self, state: &CustomState) -> bool { - if !self.is_html_element() { - return false; - } - let check_state_ptr: *const nsAtom = state.0.as_ptr(); - self.extended_slots().map_or(false, |slot| { - (&slot.mCustomStates).iter().any(|setstate| { - let setstate_ptr: *const nsAtom = setstate.mRawPtr; - setstate_ptr == check_state_ptr - }) - }) - } - #[inline] fn has_part_attr(&self) -> bool { self.as_node() @@ -1291,6 +1281,20 @@ impl<'le> TElement for GeckoElement<'le> { snapshot_helpers::each_class_or_part(attr, callback) } + #[inline] + fn each_custom_state(&self, mut callback: F) + where + F: FnMut(&AtomIdent), + { + if let Some(slots) = self.extended_slots() { + unsafe { + for atom in slots.mCustomStates.iter() { + AtomIdent::with(atom.mRawPtr, &mut callback) + } + } + } + } + #[inline] fn each_exported_part(&self, name: &AtomIdent, callback: F) where @@ -1551,14 +1555,13 @@ impl<'le> TElement for GeckoElement<'le> { let mut transitions_to_keep = PropertyDeclarationIdSet::default(); for transition_property in after_change_style.transition_properties() { - let physical_longhand = PropertyDeclarationId::Longhand( - transition_property - .longhand_id - .to_physical(after_change_style.writing_mode), - ); - transitions_to_keep.insert(physical_longhand); + let physical_property = transition_property + .property + .as_borrowed() + .to_physical(after_change_style.writing_mode); + transitions_to_keep.insert(physical_property); if self.needs_transitions_update_per_property( - physical_longhand, + physical_property, after_change_ui_style .transition_combined_duration_at(transition_property.index) .seconds(), @@ -1791,17 +1794,17 @@ impl<'le> TElement for GeckoElement<'le> { self.as_node().selector_flags() & node_flags == node_flags } - fn relative_selector_search_direction(&self) -> Option { + fn relative_selector_search_direction(&self) -> ElementSelectorFlags { use crate::gecko_bindings::structs::NodeSelectorFlags; let flags = self.as_node().selector_flags(); if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling.0) != 0 { - Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING) + ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING } else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestor.0) != 0 { - Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR) + ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR } else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionSibling.0) != 0 { - Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) + ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING } else { - None + ElementSelectorFlags::empty() } } } @@ -2042,15 +2045,14 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozAutofillPreview | NonTSPseudoClass::MozRevealed | NonTSPseudoClass::MozValueEmpty => self.state().intersects(pseudo_class.state_flag()), - // TODO: This applying only to HTML elements is weird. NonTSPseudoClass::Dir(ref dir) => { - self.is_html_element() && self.state().intersects(dir.element_state()) + self.state().intersects(dir.element_state()) }, NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::Link => { self.is_link() && context.visited_handling().matches_unvisited() }, - NonTSPseudoClass::CustomState(ref state) => self.has_custom_state(state), + NonTSPseudoClass::CustomState(ref state) => self.has_custom_state(&state.0), NonTSPseudoClass::Visited => { self.is_link() && context.visited_handling().matches_visited() }, @@ -2101,7 +2103,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { bindings::Gecko_IsSelectListBox(self.0) }, NonTSPseudoClass::MozIsHTML => self.as_node().owner_doc().is_html_document(), - NonTSPseudoClass::MozLWTheme | NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::MozWindowInactive => { let state_bit = pseudo_class.document_state_flag(); @@ -2185,6 +2186,20 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { snapshot_helpers::has_class_or_part(name, case_sensitivity, attr) } + #[inline] + fn has_custom_state(&self, state: &AtomIdent) -> bool { + if !self.is_html_element() { + return false; + } + let check_state_ptr: *const nsAtom = state.as_ptr(); + self.extended_slots().map_or(false, |slot| { + (&slot.mCustomStates).iter().any(|setstate| { + let setstate_ptr: *const nsAtom = setstate.mRawPtr; + setstate_ptr == check_state_ptr + }) + }) + } + #[inline] fn is_html_element_in_html_document(&self) -> bool { self.is_html_element() && self.as_node().owner_doc().is_html_document() diff --git a/servo/components/style/gecko_bindings/sugar/ns_style_auto_array.rs b/servo/components/style/gecko_bindings/sugar/ns_style_auto_array.rs index b5772a6c77..350d578001 100644 --- a/servo/components/style/gecko_bindings/sugar/ns_style_auto_array.rs +++ b/servo/components/style/gecko_bindings/sugar/ns_style_auto_array.rs @@ -11,7 +11,7 @@ use crate::gecko_bindings::bindings::Gecko_EnsureStyleViewTimelineArrayLength; use crate::gecko_bindings::structs::nsStyleAutoArray; use crate::gecko_bindings::structs::{StyleAnimation, StyleTransition}; use crate::gecko_bindings::structs::{StyleScrollTimeline, StyleViewTimeline}; -use std::iter::{once, Chain, IntoIterator, Once}; +use std::iter::{once, Chain, Once}; use std::ops::{Index, IndexMut}; use std::slice::{Iter, IterMut}; diff --git a/servo/components/style/gecko_string_cache/mod.rs b/servo/components/style/gecko_string_cache/mod.rs index 79a5d46525..3640b61d11 100644 --- a/servo/components/style/gecko_string_cache/mod.rs +++ b/servo/components/style/gecko_string_cache/mod.rs @@ -28,7 +28,7 @@ use std::num::NonZeroUsize; use std::ops::Deref; use std::{slice, str}; use style_traits::SpecifiedValueInfo; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; +use to_shmem::{SharedMemoryBuilder, ToShmem}; #[macro_use] #[allow(improper_ctypes, non_camel_case_types, missing_docs)] diff --git a/servo/components/style/global_style_data.rs b/servo/components/style/global_style_data.rs index 38d72b2c74..d6e49e7da6 100644 --- a/servo/components/style/global_style_data.rs +++ b/servo/components/style/global_style_data.rs @@ -10,9 +10,7 @@ use crate::gecko_bindings::bindings; use crate::parallel::STYLE_THREAD_STACK_SIZE_KB; use crate::shared_lock::SharedRwLock; use crate::thread_state; -use gecko_profiler; use parking_lot::{Mutex, RwLock, RwLockReadGuard}; -use rayon; #[cfg(unix)] use std::os::unix::thread::{JoinHandleExt, RawPthread}; #[cfg(windows)] diff --git a/servo/components/style/invalidation/element/element_wrapper.rs b/servo/components/style/invalidation/element/element_wrapper.rs index e17afd7774..9a39b7d5a0 100644 --- a/servo/components/style/invalidation/element/element_wrapper.rs +++ b/servo/components/style/invalidation/element/element_wrapper.rs @@ -73,6 +73,18 @@ pub trait ElementSnapshot: Sized { where F: FnMut(&AtomIdent); + + /// If this snapshot contains CustomStateSet information. + fn has_custom_states(&self) -> bool; + + /// A callback that should be called for each CustomState of the snapshot. + fn has_custom_state(&self, state: &AtomIdent) -> bool; + + /// A callback that should be called for each CustomState of the snapshot. + fn each_custom_state(&self, callback: F) + where + F: FnMut(&AtomIdent); + /// The `xml:lang=""` or `lang=""` attribute value per this snapshot. fn lang_attr(&self) -> Option; } @@ -213,6 +225,11 @@ where .match_element_lang(Some(self.get_lang()), lang_arg); }, + // CustomStateSet should match against the snapshot before element + NonTSPseudoClass::CustomState(ref state) => { + return self.has_custom_state(&state.0) + }, + _ => {}, } @@ -357,6 +374,13 @@ where } } + fn has_custom_state(&self, state: &AtomIdent) -> bool { + match self.snapshot() { + Some(snapshot) if snapshot.has_custom_states() => snapshot.has_custom_state(state), + _ => self.element.has_custom_state(state), + } + } + fn is_empty(&self) -> bool { self.element.is_empty() } diff --git a/servo/components/style/invalidation/element/invalidation_map.rs b/servo/components/style/invalidation/element/invalidation_map.rs index cb03862740..f42f542dd2 100644 --- a/servo/components/style/invalidation/element/invalidation_map.rs +++ b/servo/components/style/invalidation/element/invalidation_map.rs @@ -10,6 +10,7 @@ use crate::selector_map::{ }; use crate::selector_parser::{NonTSPseudoClass, SelectorImpl}; use crate::AllocErr; +use crate::values::AtomIdent; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded}; use dom::{DocumentState, ElementState}; use selectors::attr::NamespaceConstraint; @@ -259,6 +260,8 @@ pub type IdOrClassDependencyMap = MaybeCaseInsensitiveHashMap; /// Dependency mapping for local names. pub type LocalNameDependencyMap = PrecomputedHashMap>; +/// Dependency mapping for customstates +pub type CustomStateDependencyMap = PrecomputedHashMap>; /// A map where we store invalidations. /// @@ -282,6 +285,8 @@ pub struct InvalidationMap { pub document_state_selectors: Vec, /// A map of other attribute affecting selectors. pub other_attribute_affecting_selectors: LocalNameDependencyMap, + /// A map of CSS custom states + pub custom_state_affecting_selectors: CustomStateDependencyMap, } /// Tree-structural pseudoclasses that we care about for (Relative selector) invalidation. @@ -383,6 +388,7 @@ impl InvalidationMap { state_affecting_selectors: StateDependencyMap::new(), document_state_selectors: Vec::new(), other_attribute_affecting_selectors: LocalNameDependencyMap::default(), + custom_state_affecting_selectors: CustomStateDependencyMap::default(), } } @@ -397,6 +403,9 @@ impl InvalidationMap { .iter() .fold(0, |accum, (_, ref v)| accum + v.len()) + self.class_to_selector + .iter() + .fold(0, |accum, (_, ref v)| accum + v.len()) + + self.custom_state_affecting_selectors .iter() .fold(0, |accum, (_, ref v)| accum + v.len()) } @@ -408,6 +417,7 @@ impl InvalidationMap { self.state_affecting_selectors.clear(); self.document_state_selectors.clear(); self.other_attribute_affecting_selectors.clear(); + self.custom_state_affecting_selectors.clear(); } /// Shrink the capacity of hash maps if needed. @@ -416,6 +426,7 @@ impl InvalidationMap { self.id_to_selector.shrink_if_needed(); self.state_affecting_selectors.shrink_if_needed(); self.other_attribute_affecting_selectors.shrink_if_needed(); + self.custom_state_affecting_selectors.shrink_if_needed(); } } @@ -489,6 +500,7 @@ trait Collector { fn class_map(&mut self) -> &mut IdOrClassDependencyMap; fn state_map(&mut self) -> &mut StateDependencyMap; fn attribute_map(&mut self) -> &mut LocalNameDependencyMap; + fn custom_state_map(&mut self) -> &mut LocalNameDependencyMap; fn update_states(&mut self, element_state: ElementState, document_state: DocumentState); // In normal invalidations, type-based dependencies don't need to be explicitly tracked; @@ -551,6 +563,16 @@ fn add_attr_dependency(name: LocalName, collector: &mut C) -> Resu add_local_name(name, dependency, map) } +fn add_custom_state_dependency(name: AtomIdent, collector: &mut C) -> Result<(), AllocErr> { + let dependency = collector.dependency(); + let map = collector.custom_state_map(); + map.try_reserve(1)?; + let vec = map.entry(name).or_default(); + vec.try_reserve(1)?; + vec.push(dependency); + Ok(()) +} + fn add_local_name( name: LocalName, dependency: Dependency, @@ -576,6 +598,9 @@ fn on_pseudo_class(pc: &NonTSPseudoClass, collector: &mut C) -> Re return add_attr_dependency(local_name!("size"), collector); }, NonTSPseudoClass::Lang(..) => local_name!("lang"), + NonTSPseudoClass::CustomState(ref name) => { + return add_custom_state_dependency(name.0.clone(), collector); + }, _ => return Ok(()), }; @@ -702,6 +727,10 @@ impl<'a> Collector for SelectorDependencyCollector<'a> { self.compound_state.element_state |= element_state; *self.document_state |= document_state; } + + fn custom_state_map(&mut self) -> &mut CustomStateDependencyMap { + &mut self.map.custom_state_affecting_selectors + } } impl<'a> SelectorDependencyCollector<'a> { @@ -1074,6 +1103,10 @@ impl<'a> Collector for RelativeSelectorDependencyCollector<'a> { &mut self.map.map.other_attribute_affecting_selectors } + fn custom_state_map(&mut self) -> &mut CustomStateDependencyMap { + &mut self.map.map.custom_state_affecting_selectors + } + fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) { self.compound_state.state.element_state |= element_state; *self.document_state |= document_state; @@ -1255,6 +1288,10 @@ impl<'a, 'b> Collector for RelativeSelectorInnerDependencyCollector<'a, 'b> { &mut self.map.map.other_attribute_affecting_selectors } + fn custom_state_map(&mut self) -> &mut CustomStateDependencyMap { + &mut self.map.map.custom_state_affecting_selectors + } + fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) { self.compound_state.state.element_state |= element_state; *self.document_state |= document_state; diff --git a/servo/components/style/invalidation/element/relative_selector.rs b/servo/components/style/invalidation/element/relative_selector.rs index ccb48e349f..41222304be 100644 --- a/servo/components/style/invalidation/element/relative_selector.rs +++ b/servo/components/style/invalidation/element/relative_selector.rs @@ -53,12 +53,12 @@ impl DomMutationOperation { fn accept(&self, d: &Dependency, e: E) -> bool { match self { Self::Insert | Self::Append | Self::Remove => { - e.relative_selector_search_direction().is_some() + !e.relative_selector_search_direction().is_empty() }, // `:has(+ .a + .b)` with `.anchor + .a + .remove + .b` - `.a` would be present // in the search path. Self::SideEffectPrevSibling => { - e.relative_selector_search_direction().is_some() && + !e.relative_selector_search_direction().is_empty() && d.right_combinator_is_next_sibling() }, // If an element is being removed and would cause next-sibling match to happen, @@ -499,6 +499,19 @@ where }, None => (), }); + element.each_custom_state(|v| { + match map.map.custom_state_affecting_selectors.get(v) { + Some(v) => { + for dependency in v { + if !operation.accept(dependency, element) { + continue; + } + self.add_dependency(dependency, element, scope); + } + }, + None => (), + } + }); element.each_attr_name( |v| match map.map.other_attribute_affecting_selectors.get(v) { Some(v) => { @@ -782,11 +795,7 @@ where /// Is this element in the direction of the given relative selector search path? fn in_search_direction(element: &E, desired: ElementSelectorFlags) -> bool { - if let Some(direction) = element.relative_selector_search_direction() { - direction.intersects(desired) - } else { - false - } + element.relative_selector_search_direction().intersects(desired) } /// Handle a potential relative selector anchor. @@ -1145,7 +1154,7 @@ where dep: &'a Dependency, ) { debug_assert!(dep.parent.is_some(), "Orphaned inners selector?"); - if element.relative_selector_search_direction().is_none() { + if element.relative_selector_search_direction().is_empty() { return; } self.invalidations.push(( diff --git a/servo/components/style/invalidation/element/state_and_attributes.rs b/servo/components/style/invalidation/element/state_and_attributes.rs index 1c58cddf1e..d5f0723d66 100644 --- a/servo/components/style/invalidation/element/state_and_attributes.rs +++ b/servo/components/style/invalidation/element/state_and_attributes.rs @@ -19,11 +19,12 @@ use crate::selector_map::SelectorMap; use crate::selector_parser::Snapshot; use crate::stylesheets::origin::OriginSet; use crate::{Atom, WeakAtom}; +use crate::values::AtomIdent; use dom::ElementState; use selectors::attr::CaseSensitivity; +use selectors::kleene_value::KleeneValue; use selectors::matching::{ - matches_selector, MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, - SelectorCaches, VisitedHandlingMode, + matches_selector_kleene, MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode }; use smallvec::SmallVec; @@ -41,6 +42,8 @@ where added_id: Option<&'a WeakAtom>, classes_removed: &'a SmallVec<[Atom; 8]>, classes_added: &'a SmallVec<[Atom; 8]>, + custom_states_removed: &'a SmallVec<[AtomIdent; 8]>, + custom_states_added: &'a SmallVec<[AtomIdent; 8]>, state_changes: ElementState, descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>, sibling_invalidations: &'a mut InvalidationVector<'selectors>, @@ -97,23 +100,25 @@ where E: TElement, W: selectors::Element, { - let matches_now = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - element, - context, - ); + context.for_invalidation_comparison(|context| { + let matches_now = matches_selector_kleene( + &dependency.selector, + dependency.selector_offset, + None, + element, + context, + ); - let matched_then = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - wrapper, - context, - ); + let matched_then = matches_selector_kleene( + &dependency.selector, + dependency.selector_offset, + None, + wrapper, + context, + ); - matched_then != matches_now + matched_then != matches_now || matches_now == KleeneValue::Unknown + }) } /// Whether we should process the descendants of a given element for style @@ -243,7 +248,7 @@ where return false; }; - if !snapshot.has_attrs() && state_changes.is_empty() { + if !snapshot.has_attrs() && !snapshot.has_custom_states() && state_changes.is_empty() { return false; } @@ -264,6 +269,21 @@ where }) } + let mut custom_states_removed = SmallVec::<[AtomIdent; 8]>::new(); + let mut custom_states_added = SmallVec::<[AtomIdent; 8]>::new(); + if snapshot.has_custom_states() { + snapshot.each_custom_state(|s| { + if !element.has_custom_state(s) { + custom_states_removed.push(s.clone()) + } + }); + element.each_custom_state(|s| { + if !snapshot.has_custom_state(s) { + custom_states_added.push(s.clone()) + } + }) + } + let mut id_removed = None; let mut id_added = None; if snapshot.id_changed() { @@ -326,6 +346,8 @@ where added_id: id_added, classes_removed: &classes_removed, classes_added: &classes_added, + custom_states_removed: &custom_states_removed, + custom_states_added: &custom_states_added, descendant_invalidations, sibling_invalidations, invalidates_self: false, @@ -443,6 +465,14 @@ where } } + for state in self.custom_states_added.iter().chain(self.custom_states_removed.iter()) { + if let Some(deps) = map.custom_state_affecting_selectors.get(state) { + for dep in deps { + self.scan_dependency(dep); + } + } + } + self.snapshot.each_attr_changed(|attribute| { if let Some(deps) = map.other_attribute_affecting_selectors.get(attribute) { for dep in deps { diff --git a/servo/components/style/invalidation/stylesheets.rs b/servo/components/style/invalidation/stylesheets.rs index d845897aa4..48ef8c1b5d 100644 --- a/servo/components/style/invalidation/stylesheets.rs +++ b/servo/components/style/invalidation/stylesheets.rs @@ -616,7 +616,7 @@ impl StylesheetInvalidationSet { return self.invalidate_fully(); }, Document(..) | Import(..) | Media(..) | Supports(..) | Container(..) | - LayerBlock(..) => { + LayerBlock(..) | StartingStyle(..) => { // Do nothing, relevant nested rules are visited as part of rule iteration. }, FontFace(..) => { @@ -646,6 +646,11 @@ impl StylesheetInvalidationSet { debug!(" > Found unsupported rule, marking the whole subtree invalid."); self.invalidate_fully(); }, + Scope(..) => { + // Addition/removal of @scope requires re-evaluation of scope proximity to properly + // figure out the styling order. + self.invalidate_fully(); + }, } } } diff --git a/servo/components/style/matching.rs b/servo/components/style/matching.rs index 646d08a9e3..e9d754aa10 100644 --- a/servo/components/style/matching.rs +++ b/servo/components/style/matching.rs @@ -934,7 +934,8 @@ pub trait MatchMethods: TElement { if old_font_size != Some(new_font_size) { if is_root { debug_assert!(self.owner_doc_matches_for_testing(device)); - device.set_root_font_size(new_font_size.computed_size().into()); + let size = new_font_size.computed_size(); + device.set_root_font_size(new_primary_style.effective_zoom.unzoom(size.px())); if device.used_root_font_size() { // If the root font-size changed since last time, and something // in the document did use rem units, ensure we recascade the @@ -954,7 +955,8 @@ pub trait MatchMethods: TElement { } } - // For line-height, we want the fully resolved value, as `normal` also depends on other font properties. + // For line-height, we want the fully resolved value, as `normal` also depends on other + // font properties. let new_line_height = device .calc_line_height( &new_primary_style.get_font(), @@ -968,14 +970,12 @@ pub trait MatchMethods: TElement { .0 }); - if restyle_requirement != ChildRestyleRequirement::MustMatchDescendants && - old_line_height != Some(new_line_height) - { + if old_line_height != Some(new_line_height) { if is_root { debug_assert!(self.owner_doc_matches_for_testing(device)); - device.set_root_line_height(new_line_height.into()); + device.set_root_line_height(new_primary_style.effective_zoom.unzoom(new_line_height.px())); if device.used_root_line_height() { - restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants; + restyle_requirement = std::cmp::max(restyle_requirement, ChildRestyleRequirement::MustCascadeDescendants); } } diff --git a/servo/components/style/media_queries/media_list.rs b/servo/components/style/media_queries/media_list.rs index 3c2ba9ee5c..7cbbeb5e3e 100644 --- a/servo/components/style/media_queries/media_list.rs +++ b/servo/components/style/media_queries/media_list.rs @@ -10,10 +10,10 @@ use super::{Device, MediaQuery, Qualifier}; use crate::context::QuirksMode; use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; -use crate::queries::condition::KleeneValue; use crate::values::computed; use cssparser::{Delimiter, Parser}; use cssparser::{ParserInput, Token}; +use selectors::kleene_value::KleeneValue; /// A type that encapsulates a media query list. #[derive(Clone, MallocSizeOf, ToCss, ToShmem)] diff --git a/servo/components/style/parallel.rs b/servo/components/style/parallel.rs index 0b2ccf46d1..0e1c509ab7 100644 --- a/servo/components/style/parallel.rs +++ b/servo/components/style/parallel.rs @@ -26,7 +26,6 @@ use crate::context::{StyleContext, ThreadLocalStyleContext}; use crate::dom::{OpaqueNode, SendNode, TElement}; use crate::scoped_tls::ScopedTLS; use crate::traversal::{DomTraversal, PerLevelTraversalData}; -use rayon; use std::collections::VecDeque; /// The minimum stack size for a thread in the styling pool, in kilobytes. diff --git a/servo/components/style/parser.rs b/servo/components/style/parser.rs index 893625854f..1e339841a9 100644 --- a/servo/components/style/parser.rs +++ b/servo/components/style/parser.rs @@ -9,9 +9,64 @@ use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; use crate::stylesheets::{CssRuleType, CssRuleTypes, Namespaces, Origin, UrlExtraData}; use crate::use_counters::UseCounters; use cssparser::{Parser, SourceLocation, UnicodeRange}; +use selectors::parser::ParseRelative; use std::borrow::Cow; use style_traits::{OneOrMoreSeparated, ParseError, ParsingMode, Separator}; +/// Nesting context for parsing rules. +#[derive(Clone, Copy)] +pub struct NestingContext { + /// All rule types we've nested into, if any. + pub rule_types: CssRuleTypes, + /// Whether or not parsing relative selector syntax should be allowed. + pub parse_relative: ParseRelative, +} + +impl NestingContext { + fn parse_relative_for(rule_type: CssRuleType) -> ParseRelative { + match rule_type { + CssRuleType::Scope => ParseRelative::ForScope, + CssRuleType::Style => ParseRelative::ForNesting, + _ => ParseRelative::No, + } + } + + /// Create a new nesting context. + pub fn new(rule_types: CssRuleTypes, parse_nested_rule_type: Option) -> Self { + Self { + rule_types, + parse_relative: parse_nested_rule_type + .map_or(ParseRelative::No, Self::parse_relative_for) + } + } + + /// Create a new nesting context based on the given rule. + pub fn new_from_rule(rule_type: Option) -> Self { + Self { + rule_types: rule_type.map(CssRuleTypes::from).unwrap_or_default(), + parse_relative: rule_type + .map(Self::parse_relative_for) + .unwrap_or(ParseRelative::No), + } + } + + /// Save the current nesting context. + pub fn save(&mut self, rule_type: CssRuleType) -> Self { + let old = *self; + self.rule_types.insert(rule_type); + let new_parse_relative = Self::parse_relative_for(rule_type); + if new_parse_relative != ParseRelative::No { + self.parse_relative = new_parse_relative; + } + old + } + + /// Load the saved nesting context. + pub fn restore(&mut self, saved: Self) { + *self = saved; + } +} + /// The data that the parser needs from outside in order to parse a stylesheet. pub struct ParserContext<'a> { /// The `Origin` of the stylesheet, whether it's a user, author or @@ -19,8 +74,6 @@ pub struct ParserContext<'a> { pub stylesheet_origin: Origin, /// The extra data we need for resolving url values. pub url_data: &'a UrlExtraData, - /// The current rule types, if any. - pub rule_types: CssRuleTypes, /// The mode to use when parsing. pub parsing_mode: ParsingMode, /// The quirks mode of this stylesheet. @@ -31,6 +84,8 @@ pub struct ParserContext<'a> { pub namespaces: Cow<'a, Namespaces>, /// The use counters we want to record while parsing style rules, if any. pub use_counters: Option<&'a UseCounters>, + /// Current nesting context. + pub nesting_context: NestingContext, } impl<'a> ParserContext<'a> { @@ -49,12 +104,12 @@ impl<'a> ParserContext<'a> { Self { stylesheet_origin, url_data, - rule_types: rule_type.map(CssRuleTypes::from).unwrap_or_default(), parsing_mode, quirks_mode, error_reporter, namespaces, use_counters, + nesting_context: NestingContext::new_from_rule(rule_type), } } @@ -64,22 +119,21 @@ impl<'a> ParserContext<'a> { rule_type: CssRuleType, cb: impl FnOnce(&mut Self) -> R, ) -> R { - let old_rule_types = self.rule_types; - self.rule_types.insert(rule_type); + let old = self.nesting_context.save(rule_type); let r = cb(self); - self.rule_types = old_rule_types; + self.nesting_context.restore(old); r } /// Whether we're in a @page rule. #[inline] pub fn in_page_rule(&self) -> bool { - self.rule_types.contains(CssRuleType::Page) + self.nesting_context.rule_types.contains(CssRuleType::Page) } /// Get the rule type, which assumes that one is available. pub fn rule_types(&self) -> CssRuleTypes { - self.rule_types + self.nesting_context.rule_types } /// Returns whether CSS error reporting is enabled. diff --git a/servo/components/style/properties/build.py b/servo/components/style/properties/build.py index 42121a4eae..d1f76d4a25 100644 --- a/servo/components/style/properties/build.py +++ b/servo/components/style/properties/build.py @@ -20,36 +20,10 @@ RE_PYTHON_ADDR = re.compile(r"<.+? object at 0x[0-9a-fA-F]+>") OUT_DIR = os.environ.get("OUT_DIR", "") -STYLE_STRUCT_LIST = [ - "background", - "border", - "box", - "column", - "counters", - "effects", - "font", - "inherited_box", - "inherited_svg", - "inherited_table", - "inherited_text", - "inherited_ui", - "list", - "margin", - "outline", - "page", - "padding", - "position", - "svg", - "table", - "text", - "ui", - "xul", -] - def main(): usage = ( - "Usage: %s [ servo-2013 | servo-2020 | gecko ] [ style-crate | geckolib