From da4c7e7ed675c3bf405668739c3012d140856109 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:42 +0200 Subject: Adding upstream version 126.0. Signed-off-by: Daniel Baumann --- servo/components/selectors/attr.rs | 17 +- servo/components/selectors/builder.rs | 198 ++++---- servo/components/selectors/context.rs | 36 +- servo/components/selectors/kleene_value.rs | 131 +++++ servo/components/selectors/lib.rs | 1 + servo/components/selectors/matching.rs | 350 +++++++++---- servo/components/selectors/parser.rs | 257 +++++----- servo/components/selectors/tree.rs | 5 + servo/components/servo_arc/Cargo.toml | 3 +- servo/components/servo_arc/lib.rs | 142 +++--- 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 +- servo/components/style_traits/owned_slice.rs | 2 +- servo/components/style_traits/values.rs | 3 +- servo/ports/geckolib/cbindgen.toml | 39 +- servo/ports/geckolib/glue.rs | 302 +++++++++-- 142 files changed, 4022 insertions(+), 2042 deletions(-) create mode 100644 servo/components/selectors/kleene_value.rs 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') diff --git a/servo/components/selectors/attr.rs b/servo/components/selectors/attr.rs index fee2962237..70a0c5ea95 100644 --- a/servo/components/selectors/attr.rs +++ b/servo/components/selectors/attr.rs @@ -111,16 +111,21 @@ impl AttrSelectorOperator { let case = case_sensitivity; match self { AttrSelectorOperator::Equal => case.eq(e, s), - AttrSelectorOperator::Prefix => e.len() >= s.len() && case.eq(&e[..s.len()], s), + AttrSelectorOperator::Prefix => { + !s.is_empty() && e.len() >= s.len() && case.eq(&e[..s.len()], s) + }, AttrSelectorOperator::Suffix => { - e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) + !s.is_empty() && e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) }, AttrSelectorOperator::Substring => { - case.contains(element_attr_value, attr_selector_value) + !s.is_empty() && case.contains(element_attr_value, attr_selector_value) + }, + AttrSelectorOperator::Includes => { + !s.is_empty() && + element_attr_value + .split(SELECTOR_WHITESPACE) + .any(|part| case.eq(part.as_bytes(), s)) }, - AttrSelectorOperator::Includes => element_attr_value - .split(SELECTOR_WHITESPACE) - .any(|part| case.eq(part.as_bytes(), s)), AttrSelectorOperator::DashMatch => { case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s)) }, diff --git a/servo/components/selectors/builder.rs b/servo/components/selectors/builder.rs index 2406cd84f6..c706554185 100644 --- a/servo/components/selectors/builder.rs +++ b/servo/components/selectors/builder.rs @@ -6,59 +6,45 @@ //! //! Our selector representation is designed to optimize matching, and has //! several requirements: -//! * All simple selectors and combinators are stored inline in the same buffer -//! as Component instances. -//! * We store the top-level compound selectors from right to left, i.e. in -//! matching order. -//! * We store the simple selectors for each combinator from left to right, so -//! that we match the cheaper simple selectors first. +//! * All simple selectors and combinators are stored inline in the same buffer as Component +//! instances. +//! * We store the top-level compound selectors from right to left, i.e. in matching order. +//! * We store the simple selectors for each combinator from left to right, so that we match the +//! cheaper simple selectors first. //! -//! Meeting all these constraints without extra memmove traffic during parsing -//! is non-trivial. This module encapsulates those details and presents an -//! easy-to-use API for the parser. +//! For example, the selector: +//! +//! .bar:hover > .baz:nth-child(2) + .qux +//! +//! Gets stored as: +//! +//! [.qux, + , .baz, :nth-child(2), > , .bar, :hover] +//! +//! Meeting all these constraints without extra memmove traffic during parsing is non-trivial. This +//! module encapsulates those details and presents an easy-to-use API for the parser. -use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl}; +use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl, ParseRelative}; use crate::sink::Push; use servo_arc::{Arc, ThinArc}; -use smallvec::{self, SmallVec}; +use smallvec::SmallVec; use std::cmp; -use std::iter; -use std::ptr; use std::slice; -/// Top-level SelectorBuilder struct. This should be stack-allocated by the -/// consumer and never moved (because it contains a lot of inline data that -/// would be slow to memmov). +/// Top-level SelectorBuilder struct. This should be stack-allocated by the consumer and never +/// moved (because it contains a lot of inline data that would be slow to memmove). /// -/// After instantation, callers may call the push_simple_selector() and -/// push_combinator() methods to append selector data as it is encountered -/// (from left to right). Once the process is complete, callers should invoke -/// build(), which transforms the contents of the SelectorBuilder into a heap- -/// allocated Selector and leaves the builder in a drained state. +/// After instantiation, callers may call the push_simple_selector() and push_combinator() methods +/// to append selector data as it is encountered (from left to right). Once the process is +/// complete, callers should invoke build(), which transforms the contents of the SelectorBuilder +/// into a heap- allocated Selector and leaves the builder in a drained state. #[derive(Debug)] pub struct SelectorBuilder { - /// The entire sequence of simple selectors, from left to right, without combinators. - /// - /// We make this large because the result of parsing a selector is fed into a new - /// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also, - /// Components are large enough that we don't have much cache locality benefit - /// from reserving stack space for fewer of them. - simple_selectors: SmallVec<[Component; 32]>, - /// The combinators, and the length of the compound selector to their left. - combinators: SmallVec<[(Combinator, usize); 16]>, - /// The length of the current compount selector. - current_len: usize, -} - -impl Default for SelectorBuilder { - #[inline(always)] - fn default() -> Self { - SelectorBuilder { - simple_selectors: SmallVec::new(), - combinators: SmallVec::new(), - current_len: 0, - } - } + /// The entire sequence of components. We make this large because the result of parsing a + /// selector is fed into a new Arc-ed allocation, so any spilled vec would be a wasted + /// allocation. Also, Components are large enough that we don't have much cache locality + /// benefit from reserving stack space for fewer of them. + components: SmallVec<[Component; 32]>, + last_compound_start: Option, } impl Push> for SelectorBuilder { @@ -71,101 +57,115 @@ impl SelectorBuilder { /// Pushes a simple selector onto the current compound selector. #[inline(always)] pub fn push_simple_selector(&mut self, ss: Component) { - assert!(!ss.is_combinator()); - self.simple_selectors.push(ss); - self.current_len += 1; + debug_assert!(!ss.is_combinator()); + self.components.push(ss); } - /// Completes the current compound selector and starts a new one, delimited - /// by the given combinator. + /// Completes the current compound selector and starts a new one, delimited by the given + /// combinator. #[inline(always)] pub fn push_combinator(&mut self, c: Combinator) { - self.combinators.push((c, self.current_len)); - self.current_len = 0; + self.reverse_last_compound(); + self.components.push(Component::Combinator(c)); + self.last_compound_start = Some(self.components.len()); + } + + fn reverse_last_compound(&mut self) { + let start = self.last_compound_start.unwrap_or(0); + self.components[start..].reverse(); } /// Returns true if combinators have ever been pushed to this builder. #[inline(always)] pub fn has_combinators(&self) -> bool { - !self.combinators.is_empty() + self.last_compound_start.is_some() } /// Consumes the builder, producing a Selector. #[inline(always)] - pub fn build(&mut self) -> ThinArc> { + pub fn build(&mut self, parse_relative: ParseRelative) -> ThinArc> { // Compute the specificity and flags. - let sf = specificity_and_flags(self.simple_selectors.iter()); - self.build_with_specificity_and_flags(sf) + let sf = specificity_and_flags(self.components.iter()); + self.build_with_specificity_and_flags(sf, parse_relative) } - /// Builds with an explicit SpecificityAndFlags. This is separated from build() so - /// that unit tests can pass an explicit specificity. + /// Builds with an explicit SpecificityAndFlags. This is separated from build() so that unit + /// tests can pass an explicit specificity. #[inline(always)] pub(crate) fn build_with_specificity_and_flags( &mut self, - spec: SpecificityAndFlags, + mut spec: SpecificityAndFlags, + parse_relative: ParseRelative, ) -> ThinArc> { - // Create the Arc using an iterator that drains our buffers. - // Panic-safety: if SelectorBuilderIter is not iterated to the end, some simple selectors - // will safely leak. - let raw_simple_selectors = unsafe { - let simple_selectors_len = self.simple_selectors.len(); - self.simple_selectors.set_len(0); - std::slice::from_raw_parts(self.simple_selectors.as_ptr(), simple_selectors_len) - }; - let (rest, current) = split_from_end(raw_simple_selectors, self.current_len); - let iter = SelectorBuilderIter { - current_simple_selectors: current.iter(), - rest_of_simple_selectors: rest, - combinators: self.combinators.drain(..).rev(), + let implicit_parent = parse_relative.needs_implicit_parent_selector() && + !spec.flags.contains(SelectorFlags::HAS_PARENT); + + let parent_selector_and_combinator; + let implicit_parent = if implicit_parent { + spec.flags.insert(SelectorFlags::HAS_PARENT); + parent_selector_and_combinator = [ + Component::Combinator(Combinator::Descendant), + Component::ParentSelector, + ]; + &parent_selector_and_combinator[..] + } else { + &[] }; - Arc::from_header_and_iter(spec, iter) + // As an optimization, for a selector without combinators, we can just keep the order + // as-is. + if self.last_compound_start.is_none() { + return Arc::from_header_and_iter(spec, ExactChain(self.components.drain(..), implicit_parent.iter().cloned())); + } + + self.reverse_last_compound(); + Arc::from_header_and_iter(spec, ExactChain(self.components.drain(..).rev(), implicit_parent.iter().cloned())) } } -struct SelectorBuilderIter<'a, Impl: SelectorImpl> { - current_simple_selectors: slice::Iter<'a, Component>, - rest_of_simple_selectors: &'a [Component], - combinators: iter::Rev>, + +impl Default for SelectorBuilder { + #[inline(always)] + fn default() -> Self { + SelectorBuilder { + components: SmallVec::new(), + last_compound_start: None, + } + } } -impl<'a, Impl: SelectorImpl> ExactSizeIterator for SelectorBuilderIter<'a, Impl> { +// This is effectively a Chain<>, but Chain isn't an ExactSizeIterator, see +// https://github.com/rust-lang/rust/issues/34433 +struct ExactChain(A, B); + +impl ExactSizeIterator for ExactChain +where + A: ExactSizeIterator, + B: ExactSizeIterator, +{ fn len(&self) -> usize { - self.current_simple_selectors.len() + - self.rest_of_simple_selectors.len() + - self.combinators.len() + self.0.len() + self.1.len() } } -impl<'a, Impl: SelectorImpl> Iterator for SelectorBuilderIter<'a, Impl> { - type Item = Component; +impl Iterator for ExactChain +where + A: ExactSizeIterator, + B: ExactSizeIterator, +{ + type Item = Item; + #[inline(always)] fn next(&mut self) -> Option { - if let Some(simple_selector_ref) = self.current_simple_selectors.next() { - // Move a simple selector out of this slice iterator. - // This is safe because we’ve called SmallVec::set_len(0) above, - // so SmallVec::drop won’t drop this simple selector. - unsafe { Some(ptr::read(simple_selector_ref)) } - } else { - self.combinators.next().map(|(combinator, len)| { - let (rest, current) = split_from_end(self.rest_of_simple_selectors, len); - self.rest_of_simple_selectors = rest; - self.current_simple_selectors = current.iter(); - Component::Combinator(combinator) - }) - } + self.0.next().or_else(|| self.1.next()) } fn size_hint(&self) -> (usize, Option) { - (self.len(), Some(self.len())) + let len = self.len(); + (len, Some(len)) } } -fn split_from_end(s: &[T], at: usize) -> (&[T], &[T]) { - s.split_at(s.len() - at) -} - /// Flags that indicate at which point of parsing a selector are we. #[derive(Clone, Copy, Default, Eq, PartialEq, ToShmem)] pub(crate) struct SelectorFlags(u8); diff --git a/servo/components/selectors/context.rs b/servo/components/selectors/context.rs index 84ee262dfe..289b081b64 100644 --- a/servo/components/selectors/context.rs +++ b/servo/components/selectors/context.rs @@ -79,10 +79,18 @@ pub enum NeedsSelectorFlags { } /// Whether we're matching in the contect of invalidation. -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub enum MatchingForInvalidation { No, Yes, + YesForComparison, +} + +impl MatchingForInvalidation { + /// Are we matching for invalidation? + pub fn is_for_invalidation(&self) -> bool { + matches!(*self, Self::Yes | Self::YesForComparison) + } } /// Which quirks mode is this document in. @@ -314,7 +322,31 @@ where /// Whether or not we're matching to invalidate. #[inline] pub fn matching_for_invalidation(&self) -> bool { - self.matching_for_invalidation == MatchingForInvalidation::Yes + self.matching_for_invalidation.is_for_invalidation() + } + + /// Whether or not we're comparing for invalidation, if we are matching for invalidation. + #[inline] + pub fn matching_for_invalidation_comparison(&self) -> Option { + match self.matching_for_invalidation { + MatchingForInvalidation::No => None, + MatchingForInvalidation::Yes => Some(false), + MatchingForInvalidation::YesForComparison => Some(true), + } + } + + /// Run the given matching function for before/after invalidation comparison. + #[inline] + pub fn for_invalidation_comparison(&mut self, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + debug_assert!(self.matching_for_invalidation(), "Not matching for invalidation?"); + let prev = self.matching_for_invalidation; + self.matching_for_invalidation = MatchingForInvalidation::YesForComparison; + let result = f(self); + self.matching_for_invalidation = prev; + result } /// The case-sensitivity for class and ID selectors diff --git a/servo/components/selectors/kleene_value.rs b/servo/components/selectors/kleene_value.rs new file mode 100644 index 0000000000..58141c1156 --- /dev/null +++ b/servo/components/selectors/kleene_value.rs @@ -0,0 +1,131 @@ +/* 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/. */ + +//! Kleen logic: https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics + +/// A "trilean" value based on Kleen logic. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum KleeneValue { + /// False + False = 0, + /// True + True = 1, + /// Either true or false, but we’re not sure which yet. + Unknown, +} + +impl From for KleeneValue { + fn from(b: bool) -> Self { + if b { + Self::True + } else { + Self::False + } + } +} + +impl KleeneValue { + /// Turns this Kleene value to a bool, taking the unknown value as an + /// argument. + pub fn to_bool(self, unknown: bool) -> bool { + match self { + Self::True => true, + Self::False => false, + Self::Unknown => unknown, + } + } + + /// Return true if any result of f() is true. Otherwise, return the strongest value seen. + /// Returns false if empty, like that of `Iterator`. + pub fn any( + iter: impl Iterator, + f: impl FnMut(T) -> Self, + ) -> Self { + Self::any_value(iter, Self::True, Self::False, f) + } + + /// Return false if any results of f() is false. Otherwise, return the strongest value seen. + /// Returns true if empty, opposite of `Iterator`. + pub fn any_false( + iter: impl Iterator, + f: impl FnMut(T) -> Self, + ) -> Self { + Self::any_value(iter, Self::False, Self::True, f) + } + + fn any_value( + iter: impl Iterator, + value: Self, + on_empty: Self, + mut f: impl FnMut(T) -> Self, + ) -> Self { + let mut result = None; + for item in iter { + let r = f(item); + if r == value { + return r; + } + if let Some(v) = result.as_mut() { + *v = *v & r; + } else { + result = Some(r); + } + } + result.unwrap_or(on_empty) + } +} + +impl std::ops::Not for KleeneValue { + type Output = Self; + + fn not(self) -> Self { + match self { + Self::True => Self::False, + Self::False => Self::True, + Self::Unknown => Self::Unknown, + } + } +} + +// Implements the logical and operation. +impl std::ops::BitAnd for KleeneValue { + type Output = Self; + + fn bitand(self, other: Self) -> Self { + if self == Self::False || other == Self::False { + return Self::False; + } + if self == Self::Unknown || other == Self::Unknown { + return Self::Unknown; + } + Self::True + } +} + +// Implements the logical or operation. +impl std::ops::BitOr for KleeneValue { + type Output = Self; + + fn bitor(self, other: Self) -> Self { + if self == Self::True || other == Self::True { + return Self::True; + } + if self == Self::Unknown || other == Self::Unknown { + return Self::Unknown; + } + Self::False + } +} + +impl std::ops::BitOrAssign for KleeneValue { + fn bitor_assign(&mut self, other: Self) { + *self = *self | other; + } +} + +impl std::ops::BitAndAssign for KleeneValue { + fn bitand_assign(&mut self, other: Self) { + *self = *self & other; + } +} diff --git a/servo/components/selectors/lib.rs b/servo/components/selectors/lib.rs index d909059ccf..6e300893d2 100644 --- a/servo/components/selectors/lib.rs +++ b/servo/components/selectors/lib.rs @@ -35,6 +35,7 @@ pub mod relative_selector; pub mod sink; mod tree; pub mod visitor; +pub mod kleene_value; pub use crate::nth_index_cache::NthIndexCache; pub use crate::parser::{Parser, SelectorImpl, SelectorList}; diff --git a/servo/components/selectors/matching.rs b/servo/components/selectors/matching.rs index 763f65d547..282d04064b 100644 --- a/servo/components/selectors/matching.rs +++ b/servo/components/selectors/matching.rs @@ -7,6 +7,7 @@ use crate::attr::{ ParsedAttrSelectorOperation, ParsedCaseSensitivity, }; use crate::bloom::{BloomFilter, BLOOM_HASH_MASK}; +use crate::kleene_value::KleeneValue; use crate::parser::{ AncestorHashes, Combinator, Component, LocalName, NthSelectorData, RelativeSelectorMatchHint, }; @@ -200,12 +201,39 @@ fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool { /// However since the selector "c1" raises /// NotMatchedAndRestartFromClosestDescendant. So the selector /// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1". +/// +/// There is also the unknown result, which is used during invalidation when +/// specific selector is being tested for before/after comparison. More specifically, +/// selectors that are too expensive to correctly compute during invalidation may +/// return unknown, as the computation will be thrown away and only to be recomputed +/// during styling. For most cases, the unknown result can be treated as matching. +/// This is because a compound of selectors acts like &&, and unknown && matched +/// == matched and unknown && not-matched == not-matched. However, some selectors, +/// like `:is()`, behave like || i.e. `:is(.a, .b)` == a || b. Treating unknown +/// == matching then causes these selectors to always return matching, which undesired +/// for before/after comparison. Coercing to not-matched doesn't work since each +/// inner selector may have compounds: e.g. Toggling `.foo` in `:is(.foo:has(..))` +/// with coersion to not-matched would result in an invalid before/after comparison +/// of not-matched/not-matched. #[derive(Clone, Copy, Eq, PartialEq)] enum SelectorMatchingResult { Matched, NotMatchedAndRestartFromClosestLaterSibling, NotMatchedAndRestartFromClosestDescendant, NotMatchedGlobally, + Unknown, +} + +impl From for KleeneValue { + fn from(value: SelectorMatchingResult) -> Self { + match value { + SelectorMatchingResult::Matched => KleeneValue::True, + SelectorMatchingResult::Unknown => KleeneValue::Unknown, + SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling | + SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant | + SelectorMatchingResult::NotMatchedGlobally => KleeneValue::False, + } + } } /// Matches a selector, fast-rejecting against a bloom filter. @@ -224,6 +252,28 @@ pub fn matches_selector( element: &E, context: &mut MatchingContext, ) -> bool +where + E: Element, +{ + let result = matches_selector_kleene(selector, offset, hashes, element, context); + if cfg!(debug_assertions) && result == KleeneValue::Unknown { + debug_assert!( + context.matching_for_invalidation_comparison().unwrap_or(false), + "How did we return unknown?" + ); + } + result.to_bool(true) +} + +/// Same as matches_selector, but returns the Kleene value as-is. +#[inline(always)] +pub fn matches_selector_kleene( + selector: &Selector, + offset: usize, + hashes: Option<&AncestorHashes>, + element: &E, + context: &mut MatchingContext, +) -> KleeneValue where E: Element, { @@ -231,7 +281,7 @@ where if let Some(hashes) = hashes { if let Some(filter) = context.bloom_filter { if !may_match(hashes, filter) { - return false; + return KleeneValue::False; } } } @@ -275,6 +325,12 @@ pub fn matches_compound_selector_from( where E: Element, { + debug_assert!( + !context + .matching_for_invalidation_comparison() + .unwrap_or(false), + "CompoundSelectorMatchingResult doesn't support unknown" + ); if cfg!(debug_assertions) && from_offset != 0 { selector.combinator_at_parse_order(from_offset - 1); // This asserts. } @@ -320,7 +376,9 @@ where ); for component in iter { - if !matches_simple_selector(component, element, &mut local_context) { + let result = matches_simple_selector(component, element, &mut local_context); + debug_assert!(result != KleeneValue::Unknown, "Returned unknown in non invalidation context?"); + if !result.to_bool(true) { return CompoundSelectorMatchingResult::NotMatched; } } @@ -341,7 +399,7 @@ fn matches_complex_selector( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { @@ -353,7 +411,7 @@ where Component::PseudoElement(ref pseudo) => { if let Some(ref f) = context.pseudo_element_matching_fn { if !f(pseudo) { - return false; + return KleeneValue::False; } } }, @@ -364,12 +422,12 @@ where in a non-pseudo selector {:?}", other ); - return false; + return KleeneValue::False; }, } if !iter.matches_for_stateless_pseudo_element() { - return false; + return KleeneValue::False; } // Advance to the non-pseudo-element part of the selector. @@ -377,9 +435,14 @@ where debug_assert_eq!(next_sequence, Combinator::PseudoElement); } - let result = matches_complex_selector_internal(iter, element, context, rightmost); - - matches!(result, SelectorMatchingResult::Matched) + matches_complex_selector_internal( + iter, + element, + context, + rightmost, + SubjectOrPseudoElement::Yes, + ) + .into() } /// Matches each selector of a list as a complex selector @@ -388,13 +451,16 @@ fn matches_complex_selector_list( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool { - for selector in list { - if matches_complex_selector(selector.iter(), element, context, rightmost) { - return true; - } - } - false +) -> KleeneValue { + KleeneValue::any( + list.iter(), + |selector| matches_complex_selector( + selector.iter(), + element, + context, + rightmost + ) + ) } fn matches_relative_selector( @@ -423,7 +489,8 @@ fn matches_relative_selector( &el, context, rightmost, - ); + ) + .to_bool(true); if !matched && relative_selector.match_hint.is_subtree() { matched = matches_relative_selector_subtree( &relative_selector.selector, @@ -472,6 +539,7 @@ fn matches_relative_selector( ) } else { matches_complex_selector(relative_selector.selector.iter(), &el, context, rightmost) + .to_bool(true) }; if matched { return true; @@ -490,12 +558,6 @@ fn relative_selector_match_early( element: &E, context: &mut MatchingContext, ) -> Option { - if context.matching_for_invalidation() { - // In the context of invalidation, we can't use caching/filtering due to - // now/then matches. DOM structure also may have changed, so just pretend - // that we always match. - return Some(!context.in_negation()); - } // See if we can return a cached result. if let Some(cached) = context .selector_caches @@ -526,18 +588,28 @@ fn match_relative_selectors( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool { +) -> KleeneValue { if context.relative_selector_anchor().is_some() { // FIXME(emilio): This currently can happen with nesting, and it's not fully // correct, arguably. But the ideal solution isn't super-clear either. For now, // cope with it and explicitly reject it at match time. See [1] for discussion. // // [1]: https://github.com/w3c/csswg-drafts/issues/9600 - return false; + return KleeneValue::False; + } + if let Some(may_return_unknown) = context.matching_for_invalidation_comparison() { + // In the context of invalidation, :has is expensive, especially because we + // can't use caching/filtering due to now/then matches. DOM structure also + // may have changed. + return if may_return_unknown { + KleeneValue::Unknown + } else { + KleeneValue::from(!context.in_negation()) + }; } context.nest_for_relative_selector(element.opaque(), |context| { do_match_relative_selectors(selectors, element, context, rightmost) - }) + }).into() } /// Matches a relative selector in a list of relative selectors. @@ -604,7 +676,7 @@ fn matches_relative_selector_subtree( ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR, ); } - if matches_complex_selector(selector.iter(), &el, context, rightmost) { + if matches_complex_selector(selector.iter(), &el, context, rightmost).to_bool(true) { return true; } @@ -643,19 +715,8 @@ fn hover_and_active_quirk_applies( } selector_iter.clone().all(|simple| match *simple { - Component::LocalName(_) | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::AttributeOther(_) | - Component::ID(_) | - Component::Class(_) | - Component::PseudoElement(_) | - Component::Negation(_) | - Component::Empty | - Component::Nth(_) | - Component::NthOf(_) => false, Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), - _ => true, + _ => false, }) } @@ -753,6 +814,7 @@ fn matches_complex_selector_internal( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, + first_subject_compound: SubjectOrPseudoElement, ) -> SelectorMatchingResult where E: Element, @@ -762,8 +824,24 @@ where selector_iter, element ); - let matches_compound_selector = - matches_compound_selector(&mut selector_iter, element, context, rightmost); + let matches_compound_selector = { + let result = matches_compound_selector(&mut selector_iter, element, context, rightmost); + // We only care for unknown match in the first subject in compound - in the context of comparison + // invalidation, ancestors/previous sibling being an unknown match doesn't matter - we must + // invalidate to guarantee correctness. + if result == KleeneValue::Unknown && first_subject_compound == SubjectOrPseudoElement::No { + debug_assert!( + context + .matching_for_invalidation_comparison() + .unwrap_or(false), + "How did we return unknown?" + ); + // Coerce the result to matched. + KleeneValue::True + } else { + result + } + }; let combinator = selector_iter.next_sequence(); if combinator.map_or(false, |c| c.is_sibling()) { @@ -772,24 +850,42 @@ where } } - if !matches_compound_selector { + // We don't short circuit unknown here, since the rest of the selector + // to the left of this compound may return false. + if matches_compound_selector == KleeneValue::False { return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling; } let combinator = match combinator { - None => return SelectorMatchingResult::Matched, + None => { + return match matches_compound_selector { + KleeneValue::True => SelectorMatchingResult::Matched, + KleeneValue::Unknown => SelectorMatchingResult::Unknown, + KleeneValue::False => unreachable!(), + } + }, Some(c) => c, }; - let (candidate_not_found, mut rightmost) = match combinator { - Combinator::NextSibling | Combinator::LaterSibling => { - (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, SubjectOrPseudoElement::No) - }, + let (candidate_not_found, rightmost, first_subject_compound) = match combinator { + Combinator::NextSibling | Combinator::LaterSibling => ( + SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, + SubjectOrPseudoElement::No, + SubjectOrPseudoElement::No, + ), Combinator::Child | Combinator::Descendant | Combinator::SlotAssignment | - Combinator::Part => (SelectorMatchingResult::NotMatchedGlobally, SubjectOrPseudoElement::No), - Combinator::PseudoElement => (SelectorMatchingResult::NotMatchedGlobally, rightmost), + Combinator::Part => ( + SelectorMatchingResult::NotMatchedGlobally, + SubjectOrPseudoElement::No, + SubjectOrPseudoElement::No, + ), + Combinator::PseudoElement => ( + SelectorMatchingResult::NotMatchedGlobally, + rightmost, + first_subject_compound, + ), }; // Stop matching :visited as soon as we find a link, or a combinator for @@ -818,18 +914,27 @@ where &element, context, rightmost, + first_subject_compound, ) }); - if !matches!(combinator, Combinator::PseudoElement) { - rightmost = SubjectOrPseudoElement::No; - } - match (result, combinator) { // Return the status immediately. - (SelectorMatchingResult::Matched, _) | - (SelectorMatchingResult::NotMatchedGlobally, _) | - (_, Combinator::NextSibling) => { + (SelectorMatchingResult::Matched | SelectorMatchingResult::Unknown, _) => { + debug_assert!( + matches_compound_selector.to_bool(true), + "Compound didn't match?" + ); + if result == SelectorMatchingResult::Matched && + matches_compound_selector.to_bool(false) + { + // Matches without question + return result; + } + // Something returned unknown, so return unknown. + return SelectorMatchingResult::Unknown; + }, + (SelectorMatchingResult::NotMatchedGlobally, _) | (_, Combinator::NextSibling) => { return result; }, @@ -921,18 +1026,18 @@ fn matches_host( selector: Option<&Selector>, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { let host = match context.shadow_host() { Some(h) => h, - None => return false, + None => return KleeneValue::False, }; if host != element.opaque() { - return false; + return KleeneValue::False; } - selector.map_or(true, |selector| { + selector.map_or(KleeneValue::True, |selector| { context .nest(|context| matches_complex_selector(selector.iter(), element, context, rightmost)) }) @@ -943,13 +1048,13 @@ fn matches_slotted( selector: &Selector, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { // are never flattened tree slottables. if element.is_html_slot_element() { - return false; + return KleeneValue::False; } context.nest(|context| matches_complex_selector(selector.iter(), element, context, rightmost)) } @@ -994,7 +1099,7 @@ fn matches_compound_selector( element: &E, context: &mut MatchingContext, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { @@ -1008,7 +1113,14 @@ where rightmost, quirks_data, }; - selector_iter.all(|simple| matches_simple_selector(simple, element, &mut local_context)) + KleeneValue::any_false( + selector_iter, + |simple| matches_simple_selector( + simple, + element, + &mut local_context + ) + ) } /// Determines whether the given element matches the given single selector. @@ -1016,13 +1128,13 @@ fn matches_simple_selector( selector: &Component, element: &E, context: &mut LocalMatchingContext, -) -> bool +) -> KleeneValue where E: Element, { debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); let rightmost = context.rightmost; - match *selector { + KleeneValue::from(match *selector { Component::ID(ref id) => { element.has_id(id, context.shared.classes_and_ids_case_sensitivity()) }, @@ -1053,7 +1165,7 @@ where }, Component::Part(ref parts) => matches_part(element, parts, &mut context.shared), Component::Slotted(ref selector) => { - matches_slotted(element, selector, &mut context.shared, rightmost) + return matches_slotted(element, selector, &mut context.shared, rightmost); }, Component::PseudoElement(ref pseudo) => { element.match_pseudo_element(pseudo, context.shared) @@ -1072,7 +1184,7 @@ where !element.is_link() && hover_and_active_quirk_applies(iter, context.shared, context.rightmost) { - return false; + return KleeneValue::False; } } element.match_non_ts_pseudo_class(pc, &mut context.shared) @@ -1085,32 +1197,43 @@ where element.is_empty() }, Component::Host(ref selector) => { - matches_host(element, selector.as_ref(), &mut context.shared, rightmost) + return matches_host(element, selector.as_ref(), &mut context.shared, rightmost); }, Component::ParentSelector | Component::Scope => match context.shared.scope_element { Some(ref scope_element) => element.opaque() == *scope_element, None => element.is_root(), }, Component::Nth(ref nth_data) => { - matches_generic_nth_child(element, context.shared, nth_data, &[], rightmost) + return matches_generic_nth_child(element, context.shared, nth_data, &[], rightmost); }, - Component::NthOf(ref nth_of_data) => context.shared.nest(|context| { - matches_generic_nth_child( + Component::NthOf(ref nth_of_data) => { + return context.shared.nest(|context| { + matches_generic_nth_child( + element, + context, + nth_of_data.nth_data(), + nth_of_data.selectors(), + rightmost, + ) + }) + }, + Component::Is(ref list) | Component::Where(ref list) => { + return context.shared.nest(|context| { + matches_complex_selector_list(list.slice(), element, context, rightmost) + }) + }, + Component::Negation(ref list) => { + return context.shared.nest_for_negation(|context| { + !matches_complex_selector_list(list.slice(), element, context, rightmost) + }) + }, + Component::Has(ref relative_selectors) => { + return match_relative_selectors( + relative_selectors, element, - context, - nth_of_data.nth_data(), - nth_of_data.selectors(), + context.shared, rightmost, - ) - }), - Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { - matches_complex_selector_list(list.slice(), element, context, rightmost) - }), - Component::Negation(ref list) => context.shared.nest_for_negation(|context| { - !matches_complex_selector_list(list.slice(), element, context, rightmost) - }), - Component::Has(ref relative_selectors) => { - match_relative_selectors(relative_selectors, element, context.shared, rightmost) + ); }, Component::Combinator(_) => unsafe { debug_unreachable!("Shouldn't try to selector-match combinators") @@ -1121,7 +1244,7 @@ where anchor.map_or(true, |a| a == element.opaque()) }, Component::Invalid(..) => false, - } + }) } #[inline(always)] @@ -1163,19 +1286,23 @@ fn matches_generic_nth_child( nth_data: &NthSelectorData, selectors: &[Selector], rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { if element.ignores_nth_child_selectors() { - return false; + return KleeneValue::False; } let has_selectors = !selectors.is_empty(); - let selectors_match = - !has_selectors || matches_complex_selector_list(selectors, element, context, rightmost); - if context.matching_for_invalidation() { + let selectors_match = !has_selectors || + matches_complex_selector_list(selectors, element, context, rightmost).to_bool(true); + if let Some(may_return_unknown) = context.matching_for_invalidation_comparison() { // Skip expensive indexing math in invalidation. - return selectors_match && !context.in_negation(); + return if selectors_match && may_return_unknown { + KleeneValue::Unknown + } else { + KleeneValue::from(selectors_match && !context.in_negation()) + }; } let NthSelectorData { ty, a, b, .. } = *nth_data; @@ -1185,18 +1312,23 @@ where !has_selectors, ":only-child and :only-of-type cannot have a selector list!" ); - return matches_generic_nth_child( - element, - context, - &NthSelectorData::first(is_of_type), - selectors, - rightmost, - ) && matches_generic_nth_child( - element, - context, - &NthSelectorData::last(is_of_type), - selectors, - rightmost, + return KleeneValue::from( + matches_generic_nth_child( + element, + context, + &NthSelectorData::first(is_of_type), + selectors, + rightmost, + ) + .to_bool(true) && + matches_generic_nth_child( + element, + context, + &NthSelectorData::last(is_of_type), + selectors, + rightmost, + ) + .to_bool(true), ); } @@ -1223,7 +1355,7 @@ where } if !selectors_match { - return false; + return KleeneValue::False; } // :first/last-child are rather trivial to match, don't bother with the @@ -1234,7 +1366,8 @@ where } else { element.prev_sibling_element() } - .is_none(); + .is_none() + .into(); } // Lookup or compute the index. @@ -1280,6 +1413,7 @@ where None /* a == 0 */ => an == 0, }, } + .into() } #[inline] @@ -1314,7 +1448,7 @@ where let matches = if is_of_type { element.is_same_type(&curr) } else if !selectors.is_empty() { - matches_complex_selector_list(selectors, &curr, context, rightmost) + matches_complex_selector_list(selectors, &curr, context, rightmost).to_bool(true) } else { true }; @@ -1345,7 +1479,7 @@ where let matches = if is_of_type { element.is_same_type(&curr) } else if !selectors.is_empty() { - matches_complex_selector_list(selectors, &curr, context, rightmost) + matches_complex_selector_list(selectors, &curr, context, rightmost).to_bool(true) } else { true }; diff --git a/servo/components/selectors/parser.rs b/servo/components/selectors/parser.rs index 9b0acb0671..37b2b00ca5 100644 --- a/servo/components/selectors/parser.rs +++ b/servo/components/selectors/parser.rs @@ -438,10 +438,20 @@ pub enum ParseRelative { /// Allow selectors to start with a combinator, prepending a parent selector if so. Do nothing /// otherwise ForNesting, + /// Allow selectors to start with a combinator, prepending a scope selector if so. Do nothing + /// otherwise + ForScope, /// Treat as parse error if any selector begins with a combinator. No, } +impl ParseRelative { + #[inline] + pub(crate) fn needs_implicit_parent_selector(self) -> bool { + matches!(self, Self::ForNesting) + } +} + impl SelectorList { /// Returns a selector list with a single `&` pub fn ampersand() -> Self { @@ -469,6 +479,23 @@ impl SelectorList { ) } + pub fn parse_forgiving<'i, 't, P>( + parser: &P, + input: &mut CssParser<'i, 't>, + parse_relative: ParseRelative, + ) -> Result> + where + P: Parser<'i, Impl = Impl>, + { + Self::parse_with_state( + parser, + input, + SelectorParsingState::empty(), + ForgivingParsing::Yes, + parse_relative, + ) + } + #[inline] fn parse_with_state<'i, 't, P>( parser: &P, @@ -932,7 +959,7 @@ impl Selector { } } let spec = SpecificityAndFlags { specificity, flags }; - Selector(builder.build_with_specificity_and_flags(spec)) + Selector(builder.build_with_specificity_and_flags(spec, ParseRelative::No)) } #[inline] @@ -960,9 +987,6 @@ impl Selector { } let result = SelectorList::from_iter(orig.iter().map(|s| { - if !s.has_parent_selector() { - return s.clone(); - } s.replace_parent_selector(parent) })); @@ -1030,82 +1054,53 @@ impl Selector { flags: &mut SelectorFlags, flags_to_propagate: SelectorFlags, ) -> Selector { - if !orig.has_parent_selector() { - return orig.clone(); - } let new_selector = orig.replace_parent_selector(parent); *specificity += Specificity::from(new_selector.specificity() - orig.specificity()); flags.insert(new_selector.flags().intersection(flags_to_propagate)); new_selector } - let mut items = if !self.has_parent_selector() { - // Implicit `&` plus descendant combinator. - let iter = self.iter_raw_match_order(); - let len = iter.len() + 2; - specificity += Specificity::from(parent_specificity_and_flags.specificity); - flags.insert( - parent_specificity_and_flags - .flags - .intersection(SelectorFlags::for_nesting()), - ); - let iter = iter - .cloned() - .chain(std::iter::once(Component::Combinator( - Combinator::Descendant, - ))) - .chain(std::iter::once(Component::Is(parent.clone()))); - UniqueArc::from_header_and_iter_with_size(Default::default(), iter, len) - } else { - let iter = self.iter_raw_match_order().map(|component| { - use self::Component::*; - match *component { - LocalName(..) | - ID(..) | - Class(..) | - AttributeInNoNamespaceExists { .. } | - AttributeInNoNamespace { .. } | - AttributeOther(..) | - ExplicitUniversalType | - ExplicitAnyNamespace | - ExplicitNoNamespace | - DefaultNamespace(..) | - Namespace(..) | - Root | - Empty | - Scope | - Nth(..) | - NonTSPseudoClass(..) | - PseudoElement(..) | - Combinator(..) | - Host(None) | - Part(..) | - Invalid(..) | - RelativeSelectorAnchor => component.clone(), - ParentSelector => { - specificity += Specificity::from(parent_specificity_and_flags.specificity); - flags.insert( - parent_specificity_and_flags - .flags - .intersection(SelectorFlags::for_nesting()), - ); - Is(parent.clone()) - }, - Negation(ref selectors) => { - Negation( - replace_parent_on_selector_list( - selectors.slice(), - parent, - &mut specificity, - &mut flags, - /* propagate_specificity = */ true, - SelectorFlags::for_nesting(), - ) - .unwrap_or_else(|| selectors.clone()), - ) - }, - Is(ref selectors) => { - Is(replace_parent_on_selector_list( + if !self.has_parent_selector() { + return self.clone(); + } + + let iter = self.iter_raw_match_order().map(|component| { + use self::Component::*; + match *component { + LocalName(..) | + ID(..) | + Class(..) | + AttributeInNoNamespaceExists { .. } | + AttributeInNoNamespace { .. } | + AttributeOther(..) | + ExplicitUniversalType | + ExplicitAnyNamespace | + ExplicitNoNamespace | + DefaultNamespace(..) | + Namespace(..) | + Root | + Empty | + Scope | + Nth(..) | + NonTSPseudoClass(..) | + PseudoElement(..) | + Combinator(..) | + Host(None) | + Part(..) | + Invalid(..) | + RelativeSelectorAnchor => component.clone(), + ParentSelector => { + specificity += Specificity::from(parent_specificity_and_flags.specificity); + flags.insert( + parent_specificity_and_flags + .flags + .intersection(SelectorFlags::for_nesting()), + ); + Is(parent.clone()) + }, + Negation(ref selectors) => { + Negation( + replace_parent_on_selector_list( selectors.slice(), parent, &mut specificity, @@ -1113,64 +1108,75 @@ impl Selector { /* propagate_specificity = */ true, SelectorFlags::for_nesting(), ) - .unwrap_or_else(|| selectors.clone())) - }, - Where(ref selectors) => { - Where( - replace_parent_on_selector_list( - selectors.slice(), - parent, - &mut specificity, - &mut flags, - /* propagate_specificity = */ false, - SelectorFlags::for_nesting(), - ) - .unwrap_or_else(|| selectors.clone()), - ) - }, - Has(ref selectors) => Has(replace_parent_on_relative_selector_list( - selectors, + .unwrap_or_else(|| selectors.clone()), + ) + }, + Is(ref selectors) => { + Is(replace_parent_on_selector_list( + selectors.slice(), parent, &mut specificity, &mut flags, + /* propagate_specificity = */ true, SelectorFlags::for_nesting(), ) - .into_boxed_slice()), - - Host(Some(ref selector)) => Host(Some(replace_parent_on_selector( - selector, - parent, - &mut specificity, - &mut flags, - SelectorFlags::for_nesting() - SelectorFlags::HAS_NON_FEATURELESS_COMPONENT, - ))), - NthOf(ref data) => { - let selectors = replace_parent_on_selector_list( - data.selectors(), + .unwrap_or_else(|| selectors.clone())) + }, + Where(ref selectors) => { + Where( + replace_parent_on_selector_list( + selectors.slice(), parent, &mut specificity, &mut flags, - /* propagate_specificity = */ true, + /* propagate_specificity = */ false, SelectorFlags::for_nesting(), - ); - NthOf(match selectors { - Some(s) => { - NthOfSelectorData::new(data.nth_data(), s.slice().iter().cloned()) - }, - None => data.clone(), - }) - }, - Slotted(ref selector) => Slotted(replace_parent_on_selector( - selector, + ) + .unwrap_or_else(|| selectors.clone()), + ) + }, + Has(ref selectors) => Has(replace_parent_on_relative_selector_list( + selectors, + parent, + &mut specificity, + &mut flags, + SelectorFlags::for_nesting(), + ) + .into_boxed_slice()), + + Host(Some(ref selector)) => Host(Some(replace_parent_on_selector( + selector, + parent, + &mut specificity, + &mut flags, + SelectorFlags::for_nesting() - SelectorFlags::HAS_NON_FEATURELESS_COMPONENT, + ))), + NthOf(ref data) => { + let selectors = replace_parent_on_selector_list( + data.selectors(), parent, &mut specificity, &mut flags, + /* propagate_specificity = */ true, SelectorFlags::for_nesting(), - )), - } - }); - UniqueArc::from_header_and_iter(Default::default(), iter) - }; + ); + NthOf(match selectors { + Some(s) => { + NthOfSelectorData::new(data.nth_data(), s.slice().iter().cloned()) + }, + None => data.clone(), + }) + }, + Slotted(ref selector) => Slotted(replace_parent_on_selector( + selector, + parent, + &mut specificity, + &mut flags, + SelectorFlags::for_nesting(), + )), + } + }); + let mut items = UniqueArc::from_header_and_iter(Default::default(), iter); *items.header_mut() = SpecificityAndFlags { specificity: specificity.into(), flags, @@ -2602,9 +2608,14 @@ where // combinator. builder.push_combinator(combinator.unwrap_or(Combinator::Descendant)); }, - ParseRelative::ForNesting => { + ParseRelative::ForNesting | ParseRelative::ForScope => { if let Ok(combinator) = combinator { - builder.push_simple_selector(Component::ParentSelector); + let selector = match parse_relative { + ParseRelative::ForHas | ParseRelative::No => unreachable!(), + ParseRelative::ForNesting => Component::ParentSelector, + ParseRelative::ForScope => Component::Scope, + }; + builder.push_simple_selector(selector); builder.push_combinator(combinator); } }, @@ -2643,7 +2654,7 @@ where builder.push_combinator(combinator); } - return Ok(Selector(builder.build())); + return Ok(Selector(builder.build(parse_relative))); } fn try_parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Result { @@ -4375,7 +4386,7 @@ pub mod tests { parse("#foo:has(:is(.bar, div .baz).bar)").unwrap() ); - let child = parse("#foo").unwrap(); + let child = parse_relative_expected("#foo", ParseRelative::ForNesting, Some("& #foo")).unwrap(); assert_eq!( child.replace_parent_selector(&parent), parse(":is(.bar, div .baz) #foo").unwrap() diff --git a/servo/components/selectors/tree.rs b/servo/components/selectors/tree.rs index c1ea8ff5ae..1c440a124d 100644 --- a/servo/components/selectors/tree.rs +++ b/servo/components/selectors/tree.rs @@ -134,6 +134,11 @@ pub trait Element: Sized + Clone + Debug { case_sensitivity: CaseSensitivity, ) -> bool; + fn has_custom_state( + &self, + name: &::Identifier, + ) -> bool; + /// Returns the mapping from the `exportparts` attribute in the reverse /// direction, that is, in an outer-tree -> inner-tree direction. fn imported_part( diff --git a/servo/components/servo_arc/Cargo.toml b/servo/components/servo_arc/Cargo.toml index 03b1004bac..51aeb0e1f3 100644 --- a/servo/components/servo_arc/Cargo.toml +++ b/servo/components/servo_arc/Cargo.toml @@ -12,7 +12,8 @@ path = "lib.rs" [features] gecko_refcount_logging = [] -servo = ["serde"] +servo = ["serde", "track_alloc_size"] +track_alloc_size = [] [dependencies] serde = { version = "1.0", optional = true } diff --git a/servo/components/servo_arc/lib.rs b/servo/components/servo_arc/lib.rs index 1438ccebfd..c3d27c5bb5 100644 --- a/servo/components/servo_arc/lib.rs +++ b/servo/components/servo_arc/lib.rs @@ -35,10 +35,8 @@ use stable_deref_trait::{CloneStableDeref, StableDeref}; use std::alloc::{self, Layout}; use std::borrow; use std::cmp::Ordering; -use std::convert::From; use std::fmt; use std::hash::{Hash, Hasher}; -use std::iter::{ExactSizeIterator, Iterator}; use std::marker::PhantomData; use std::mem::{self, align_of, size_of}; use std::ops::{Deref, DerefMut}; @@ -122,6 +120,8 @@ impl UniqueArc { .unwrap_or_else(|| alloc::handle_alloc_error(layout)) .cast::>>(); ptr::write(&mut p.as_mut().count, atomic::AtomicUsize::new(1)); + #[cfg(feature = "track_alloc_size")] + ptr::write(&mut p.as_mut().alloc_size, layout.size()); #[cfg(feature = "gecko_refcount_logging")] { @@ -171,9 +171,24 @@ unsafe impl Send for Arc {} unsafe impl Sync for Arc {} /// The object allocated by an Arc +/// +/// See https://github.com/mozilla/cbindgen/issues/937 for the derive-{eq,neq}=false. But we don't +/// use those anyways so we can just disable them. +/// cbindgen:derive-eq=false +/// cbindgen:derive-neq=false #[repr(C)] struct ArcInner { count: atomic::AtomicUsize, + // NOTE(emilio): This needs to be here so that HeaderSlice<> is deallocated properly if the + // allocator relies on getting the right Layout. We don't need to track the right alignment, + // since we know that statically. + // + // This member could be completely avoided once min_specialization feature is stable (by + // implementing a trait for HeaderSlice that gives you the right layout). For now, servo-only + // since Gecko doesn't need it (its allocator doesn't need the size for the alignments we care + // about). See https://github.com/rust-lang/rust/issues/31844. + #[cfg(feature = "track_alloc_size")] + alloc_size: usize, data: T, } @@ -192,24 +207,31 @@ impl Arc { /// Construct an `Arc` #[inline] pub fn new(data: T) -> Self { - let ptr = Box::into_raw(Box::new(ArcInner { - count: atomic::AtomicUsize::new(1), - data, - })); + let layout = Layout::new::>(); + let p = unsafe { + let ptr = ptr::NonNull::new(alloc::alloc(layout)) + .unwrap_or_else(|| alloc::handle_alloc_error(layout)) + .cast::>(); + ptr::write(ptr.as_ptr(), ArcInner { + count: atomic::AtomicUsize::new(1), + #[cfg(feature = "track_alloc_size")] + alloc_size: layout.size(), + data, + }); + ptr + }; #[cfg(feature = "gecko_refcount_logging")] unsafe { // FIXME(emilio): Would be so amazing to have // std::intrinsics::type_name() around, so that we could also report // a real size. - NS_LogCtor(ptr as *mut _, b"ServoArc\0".as_ptr() as *const _, 8); + NS_LogCtor(p.as_ptr() as *mut _, b"ServoArc\0".as_ptr() as *const _, 8); } - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } + Arc { + p, + phantom: PhantomData, } } @@ -266,10 +288,13 @@ impl Arc { where F: FnOnce(Layout) -> *mut u8, { - let ptr = alloc(Layout::new::>()) as *mut ArcInner; + let layout = Layout::new::>(); + let ptr = alloc(layout) as *mut ArcInner; let x = ArcInner { count: atomic::AtomicUsize::new(STATIC_REFCOUNT), + #[cfg(feature = "track_alloc_size")] + alloc_size: layout.size(), data, }; @@ -337,7 +362,14 @@ impl Arc { #[inline(never)] unsafe fn drop_slow(&mut self) { self.record_drop(); - let _ = Box::from_raw(self.ptr()); + let inner = self.ptr(); + + let layout = Layout::for_value(&*inner); + #[cfg(feature = "track_alloc_size")] + let layout = Layout::from_size_align_unchecked((*inner).alloc_size, layout.align()); + + std::ptr::drop_in_place(inner); + alloc::dealloc(inner as *mut _, layout); } /// Test pointer equality between the two Arcs, i.e. they must be the _same_ @@ -711,11 +743,6 @@ impl HeaderSlice { } } -#[inline(always)] -fn divide_rounding_up(dividend: usize, divisor: usize) -> usize { - (dividend + divisor - 1) / divisor -} - impl Arc> { /// Creates an Arc for a HeaderSlice using the given header struct and /// iterator to generate the slice. @@ -742,26 +769,17 @@ impl Arc> { { assert_ne!(size_of::(), 0, "Need to think about ZST"); - let size = size_of::>>() + size_of::() * num_items; - let inner_align = align_of::>>(); - debug_assert!(inner_align >= align_of::()); - - let ptr: *mut ArcInner>; - unsafe { + let layout = Layout::new::>>(); + debug_assert!(layout.align() >= align_of::()); + debug_assert!(layout.align() >= align_of::()); + let array_layout = Layout::array::(num_items).expect("Overflow"); + let (layout, _offset) = layout.extend(array_layout).expect("Overflow"); + let p = unsafe { // Allocate the buffer. - let layout = if inner_align <= align_of::() { - Layout::from_size_align_unchecked(size, align_of::()) - } else if inner_align <= align_of::() { - // On 32-bit platforms may have 8 byte alignment while usize - // has 4 byte aligment. Use u64 to avoid over-alignment. - // This branch will compile away in optimized builds. - Layout::from_size_align_unchecked(size, align_of::()) - } else { - panic!("Over-aligned type not handled"); - }; - let buffer = alloc(layout); - ptr = buffer as *mut ArcInner>; + let mut p = ptr::NonNull::new(buffer) + .unwrap_or_else(|| alloc::handle_alloc_error(layout)) + .cast::>>(); // Write the data. // @@ -772,11 +790,13 @@ impl Arc> { } else { atomic::AtomicUsize::new(1) }; - ptr::write(&mut ((*ptr).count), count); - ptr::write(&mut ((*ptr).data.header), header); - ptr::write(&mut ((*ptr).data.len), num_items); + ptr::write(&mut p.as_mut().count, count); + #[cfg(feature = "track_alloc_size")] + ptr::write(&mut p.as_mut().alloc_size, layout.size()); + ptr::write(&mut p.as_mut().data.header, header); + ptr::write(&mut p.as_mut().data.len, num_items); if num_items != 0 { - let mut current = std::ptr::addr_of_mut!((*ptr).data.data) as *mut T; + let mut current = std::ptr::addr_of_mut!(p.as_mut().data.data) as *mut T; for _ in 0..num_items { ptr::write( current, @@ -789,20 +809,21 @@ impl Arc> { // We should have consumed the buffer exactly, maybe accounting // for some padding from the alignment. debug_assert!( - (buffer.add(size) as usize - current as *mut u8 as usize) < inner_align + (buffer.add(layout.size()) as usize - current as *mut u8 as usize) < layout.align() ); } assert!( items.next().is_none(), "ExactSizeIterator under-reported length" ); - } + p + }; #[cfg(feature = "gecko_refcount_logging")] unsafe { if !is_static { // FIXME(emilio): Would be so amazing to have // std::intrinsics::type_name() around. - NS_LogCtor(ptr as *mut _, b"ServoArc\0".as_ptr() as *const _, 8) + NS_LogCtor(p.as_ptr() as *mut _, b"ServoArc\0".as_ptr() as *const _, 8) } } @@ -812,11 +833,10 @@ impl Arc> { size_of::(), "The Arc should be thin" ); - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } + + Arc { + p, + phantom: PhantomData, } } @@ -828,18 +848,7 @@ impl Arc> { I: Iterator, { Arc::from_header_and_iter_alloc( - |layout| { - // align will only ever be align_of::() or align_of::() - let align = layout.align(); - unsafe { - if align == mem::align_of::() { - Self::allocate_buffer::(layout.size()) - } else { - assert_eq!(align, mem::align_of::()); - Self::allocate_buffer::(layout.size()) - } - } - }, + |layout| unsafe { alloc::alloc(layout) }, header, items, num_items, @@ -857,17 +866,6 @@ impl Arc> { let len = items.len(); Self::from_header_and_iter_with_size(header, items, len) } - - #[inline] - unsafe fn allocate_buffer(size: usize) -> *mut u8 { - // We use Vec because the underlying allocation machinery isn't - // available in stable Rust. To avoid alignment issues, we allocate - // words rather than bytes, rounding up to the nearest word size. - let words_to_allocate = divide_rounding_up(size, mem::size_of::()); - let mut vec = Vec::::with_capacity(words_to_allocate); - vec.set_len(words_to_allocate); - Box::into_raw(vec.into_boxed_slice()) as *mut W as *mut u8 - } } /// This is functionally equivalent to Arc<(H, [T])> 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