diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /servo | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
142 files changed, 4022 insertions, 2042 deletions
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<Impl: SelectorImpl> { - /// 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<Impl>; 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<Impl: SelectorImpl> Default for SelectorBuilder<Impl> { - #[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<Impl>; 32]>, + last_compound_start: Option<usize>, } impl<Impl: SelectorImpl> Push<Component<Impl>> for SelectorBuilder<Impl> { @@ -71,101 +57,115 @@ impl<Impl: SelectorImpl> SelectorBuilder<Impl> { /// Pushes a simple selector onto the current compound selector. #[inline(always)] pub fn push_simple_selector(&mut self, ss: Component<Impl>) { - 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<SpecificityAndFlags, Component<Impl>> { + pub fn build(&mut self, parse_relative: ParseRelative) -> ThinArc<SpecificityAndFlags, Component<Impl>> { // 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<SpecificityAndFlags, Component<Impl>> { - // 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<Impl>>, - rest_of_simple_selectors: &'a [Component<Impl>], - combinators: iter::Rev<smallvec::Drain<'a, [(Combinator, usize); 16]>>, + +impl<Impl: SelectorImpl> Default for SelectorBuilder<Impl> { + #[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>(A, B); + +impl<A, B, Item> ExactSizeIterator for ExactChain<A, B> +where + A: ExactSizeIterator<Item = Item>, + B: ExactSizeIterator<Item = Item>, +{ 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>; +impl<A, B, Item> Iterator for ExactChain<A, B> +where + A: ExactSizeIterator<Item = Item>, + B: ExactSizeIterator<Item = Item>, +{ + type Item = Item; + #[inline(always)] fn next(&mut self) -> Option<Self::Item> { - 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<usize>) { - (self.len(), Some(self.len())) + let len = self.len(); + (len, Some(len)) } } -fn split_from_end<T>(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<bool> { + 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<F, R>(&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<bool> 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<T>( + iter: impl Iterator<Item = T>, + 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<T>( + iter: impl Iterator<Item = T>, + f: impl FnMut(T) -> Self, + ) -> Self { + Self::any_value(iter, Self::False, Self::True, f) + } + + fn any_value<T>( + iter: impl Iterator<Item = T>, + 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<SelectorMatchingResult> 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. @@ -227,11 +255,33 @@ pub fn matches_selector<E>( 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<E>( + selector: &Selector<E::Impl>, + offset: usize, + hashes: Option<&AncestorHashes>, + element: &E, + context: &mut MatchingContext<E::Impl>, +) -> KleeneValue +where + E: Element, +{ // Use the bloom filter to fast-reject. 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<E>( 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<E>( element: &E, context: &mut MatchingContext<E::Impl>, 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<E: Element>( element: &E, context: &mut MatchingContext<E::Impl>, 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<E: Element>( @@ -423,7 +489,8 @@ fn matches_relative_selector<E: Element>( &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<E: Element>( ) } 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<E: Element>( element: &E, context: &mut MatchingContext<E::Impl>, ) -> Option<bool> { - 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<E: Element>( element: &E, context: &mut MatchingContext<E::Impl>, 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<E: Element>( 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<Impl: SelectorImpl>( } 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<E>( element: &E, context: &mut MatchingContext<E::Impl>, 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<E>( selector: Option<&Selector<E::Impl>>, context: &mut MatchingContext<E::Impl>, 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<E>( selector: &Selector<E::Impl>, context: &mut MatchingContext<E::Impl>, rightmost: SubjectOrPseudoElement, -) -> bool +) -> KleeneValue where E: Element, { // <slots> 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<E>( element: &E, context: &mut MatchingContext<E::Impl>, 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<E>( selector: &Component<E::Impl>, element: &E, context: &mut LocalMatchingContext<E::Impl>, -) -> 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<E>( nth_data: &NthSelectorData, selectors: &[Selector<E::Impl>], 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<Impl: SelectorImpl> SelectorList<Impl> { /// Returns a selector list with a single `&` pub fn ampersand() -> Self { @@ -469,6 +479,23 @@ impl<Impl: SelectorImpl> SelectorList<Impl> { ) } + pub fn parse_forgiving<'i, 't, P>( + parser: &P, + input: &mut CssParser<'i, 't>, + parse_relative: ParseRelative, + ) -> Result<Self, ParseError<'i, P::Error>> + 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<Impl: SelectorImpl> Selector<Impl> { } } 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<Impl: SelectorImpl> Selector<Impl> { } 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<Impl: SelectorImpl> Selector<Impl> { flags: &mut SelectorFlags, flags_to_propagate: SelectorFlags, ) -> Selector<Impl> { - 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<Impl: SelectorImpl> Selector<Impl> { /* 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<Combinator, ()> { @@ -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: &<Self::Impl as SelectorImpl>::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<T> UniqueArc<T> { .unwrap_or_else(|| alloc::handle_alloc_error(layout)) .cast::<ArcInner<mem::MaybeUninit<T>>>(); 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<T: ?Sized + Sync + Send> Send for Arc<T> {} unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {} /// The object allocated by an Arc<T> +/// +/// 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<T: ?Sized> { 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<T> Arc<T> { /// Construct an `Arc<T>` #[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::<ArcInner<T>>(); + let p = unsafe { + let ptr = ptr::NonNull::new(alloc::alloc(layout)) + .unwrap_or_else(|| alloc::handle_alloc_error(layout)) + .cast::<ArcInner<T>>(); + 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<T> Arc<T> { where F: FnOnce(Layout) -> *mut u8, { - let ptr = alloc(Layout::new::<ArcInner<T>>()) as *mut ArcInner<T>; + let layout = Layout::new::<ArcInner<T>>(); + let ptr = alloc(layout) as *mut ArcInner<T>; let x = ArcInner { count: atomic::AtomicUsize::new(STATIC_REFCOUNT), + #[cfg(feature = "track_alloc_size")] + alloc_size: layout.size(), data, }; @@ -337,7 +362,14 @@ impl<T: ?Sized> Arc<T> { #[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<H, T> HeaderSlice<H, T> { } } -#[inline(always)] -fn divide_rounding_up(dividend: usize, divisor: usize) -> usize { - (dividend + divisor - 1) / divisor -} - impl<H, T> Arc<HeaderSlice<H, T>> { /// Creates an Arc for a HeaderSlice using the given header struct and /// iterator to generate the slice. @@ -742,26 +769,17 @@ impl<H, T> Arc<HeaderSlice<H, T>> { { assert_ne!(size_of::<T>(), 0, "Need to think about ZST"); - let size = size_of::<ArcInner<HeaderSlice<H, T>>>() + size_of::<T>() * num_items; - let inner_align = align_of::<ArcInner<HeaderSlice<H, T>>>(); - debug_assert!(inner_align >= align_of::<T>()); - - let ptr: *mut ArcInner<HeaderSlice<H, T>>; - unsafe { + let layout = Layout::new::<ArcInner<HeaderSlice<H, T>>>(); + debug_assert!(layout.align() >= align_of::<T>()); + debug_assert!(layout.align() >= align_of::<usize>()); + let array_layout = Layout::array::<T>(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::<usize>() { - Layout::from_size_align_unchecked(size, align_of::<usize>()) - } else if inner_align <= align_of::<u64>() { - // On 32-bit platforms <T> 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::<u64>()) - } else { - panic!("Over-aligned type not handled"); - }; - let buffer = alloc(layout); - ptr = buffer as *mut ArcInner<HeaderSlice<H, T>>; + let mut p = ptr::NonNull::new(buffer) + .unwrap_or_else(|| alloc::handle_alloc_error(layout)) + .cast::<ArcInner<HeaderSlice<H, T>>>(); // Write the data. // @@ -772,11 +790,13 @@ impl<H, T> Arc<HeaderSlice<H, T>> { } 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<H, T> Arc<HeaderSlice<H, T>> { // 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<H, T> Arc<HeaderSlice<H, T>> { size_of::<usize>(), "The Arc should be thin" ); - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } + + Arc { + p, + phantom: PhantomData, } } @@ -828,18 +848,7 @@ impl<H, T> Arc<HeaderSlice<H, T>> { I: Iterator<Item = T>, { Arc::from_header_and_iter_alloc( - |layout| { - // align will only ever be align_of::<usize>() or align_of::<u64>() - let align = layout.align(); - unsafe { - if align == mem::align_of::<usize>() { - Self::allocate_buffer::<usize>(layout.size()) - } else { - assert_eq!(align, mem::align_of::<u64>()); - Self::allocate_buffer::<u64>(layout.size()) - } - } - }, + |layout| unsafe { alloc::alloc(layout) }, header, items, num_items, @@ -857,17 +866,6 @@ impl<H, T> Arc<HeaderSlice<H, T>> { let len = items.len(); Self::from_header_and_iter_with_size(header, items, len) } - - #[inline] - unsafe fn allocate_buffer<W>(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::<W>()); - let mut vec = Vec::<W>::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<E: TElement> PushedElement<E> { /// 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 { + /// <https://drafts.csswg.org/css-color-4/#rgb-functions> + Rgb( + ColorComponent<u8>, // red + ColorComponent<u8>, // green + ColorComponent<u8>, // blue + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#the-hsl-notation> + Hsl( + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // saturation + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#the-hwb-notation> + Hwb( + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // whiteness + ColorComponent<NumberOrPercentage>, // blackness + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch> + Lab( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // a + ColorComponent<NumberOrPercentage>, // b + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch> + Lch( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // chroma + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch> + Oklab( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // a + ColorComponent<NumberOrPercentage>, // b + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch> + Oklch( + ColorComponent<NumberOrPercentage>, // lightness + ColorComponent<NumberOrPercentage>, // chroma + ColorComponent<NumberOrAngle>, // hue + ColorComponent<NumberOrPercentage>, // alpha + ), + /// <https://drafts.csswg.org/css-color-4/#color-function> + Color( + PredefinedColorSpace, + ColorComponent<NumberOrPercentage>, // red / x + ColorComponent<NumberOrPercentage>, // green / y + ColorComponent<NumberOrPercentage>, // blue / z + ColorComponent<NumberOrPercentage>, // 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<ValueType> { /// The "none" keyword. None, @@ -46,3 +56,49 @@ impl<ValueType> ColorComponent<ValueType> { } } } + +/// 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<Self, ()>; + + /// Try to create a new component from the given [CalcNodeLeaf] that was + /// resolved from a [CalcNode]. + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result<Self, ()>; +} + +impl<ValueType: ColorComponentType> ColorComponent<ValueType> { + /// Parse a single [ColorComponent]. + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_none: bool, + ) -> Result<Self, ParseError<'i>> { + 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<u8> for ColorComponent<u8> { #[inline] fn from(value: u8) -> Self { @@ -29,56 +49,39 @@ impl From<u8> for ColorComponent<u8> { /// 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<Output>(ident: &str) -> Result<Output, ()> -where - Output: FromParsedColor, -{ +pub fn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> { 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<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<SpecifiedColor, ParseError<'i>> { 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<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { 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<ColorComponent<NumberOrPercentage>, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorComponent<NumberOrPercentage>, 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<ColorComponent<NumberOrPercentage>, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { if !arguments.is_exhausted() { arguments.expect_delim('/')?; color_parser.parse_number_or_percentage(arguments, true) @@ -157,22 +151,45 @@ impl ColorComponent<NumberOrPercentage> { } } +/// Parse the relative color syntax "from" syntax `from <color>`. +fn parse_origin_color<'i, 't>( + color_parser: &ColorParser<'_, '_>, + arguments: &mut Parser<'i, 't>, +) -> Result<Option<SpecifiedColor>, 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<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + 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. /// /// <https://drafts.csswg.org/css-color/#the-hsl-notation> #[inline] -fn parse_hsl<'i, 't, P>( - color_parser: &P, +fn parse_hsl<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + 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. /// /// <https://drafts.csswg.org/css-color/#the-hbw-notation> #[inline] -fn parse_hwb<'i, 't, P>( - color_parser: &P, +fn parse_hwb<'i, 't>( + color_parser: &ColorParser<'_, '_>, arguments: &mut Parser<'i, 't>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + 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<Output> = fn( @@ -293,20 +310,19 @@ type IntoLabFn<Output> = 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<P::Output>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ + into_color: IntoLabFn<ColorFunction>, +) -> Result<ColorFunction, ParseError<'i>> { + 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<Output> = 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<P::Output>, -) -> Result<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ + into_color: IntoLchFn<ColorFunction>, +) -> Result<ColorFunction, ParseError<'i>> { + 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<P::Output, ParseError<'i>> -where - P: ColorParser<'i>, -{ +) -> Result<ColorFunction, ParseError<'i>> { + 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<ColorComponent<R1>, ParseError<'i>>, - F2: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R2>, ParseError<'i>>, - F3: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R3>, ParseError<'i>>, + F1: FnOnce( + &ColorParser<'a, 'b>, + &mut Parser<'i, 't>, + bool, + ) -> Result<ColorComponent<R1>, ParseError<'i>>, + F2: FnOnce( + &ColorParser<'a, 'b>, + &mut Parser<'i, 't>, + bool, + ) -> Result<ColorComponent<R2>, ParseError<'i>>, + F3: FnOnce( + &ColorParser<'a, 'b>, + &mut Parser<'i, 't>, + bool, + ) -> Result<ColorComponent<R3>, ParseError<'i>>, { let r1 = f1(color_parser, input, true)?; let r2 = f2(color_parser, input, true)?; @@ -407,6 +426,7 @@ where } /// Either a number or a percentage. +#[derive(Clone, Copy, Debug)] pub enum NumberOrPercentage { /// `<number>`. Number { @@ -432,7 +452,32 @@ impl NumberOrPercentage { } } +impl ColorComponentType for NumberOrPercentage { + fn units() -> CalcUnits { + CalcUnits::PERCENTAGE + } + + fn try_from_token(token: &Token) -> Result<Self, ()> { + 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<Self, ()> { + 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>`. 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 `<angle>` or `<number>`. - /// - /// 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<Self, ()> { + 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<Self, ()> { + 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 <number>. +impl ColorComponentType for f32 { + fn units() -> CalcUnits { + CalcUnits::empty() + } + + fn try_from_token(token: &Token) -> Result<Self, ()> { + if let Token::Number { value, .. } = *token { + Ok(value) + } else { + Err(()) + } + } + + fn try_from_leaf(leaf: &SpecifiedLeaf) -> Result<Self, ()> { + 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 `<number>` or `<angle>` value. + fn parse_number_or_angle<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>>; + ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } /// Parse a `<percentage>` 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<ColorComponent<f32>, ParseError<'i>>; + ) -> Result<ColorComponent<f32>, 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::<NumberOrPercentage>::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 `<number>` value. - fn parse_number<'t>( + fn parse_number<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result<ColorComponent<f32>, ParseError<'i>>; + ) -> Result<ColorComponent<f32>, ParseError<'i>> { + ColorComponent::parse(self.context, input, allow_none) + } - /// Parse a `<number>` value or a `<percentage>` value. - fn parse_number_or_percentage<'t>( + /// Parse a `<number>` or `<percentage>` value. + fn parse_number_or_percentage<'i, 't>( &self, input: &mut Parser<'i, 't>, allow_none: bool, - ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>; + ) -> Result<ColorComponent<NumberOrPercentage>, 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<F>(&self, callback: F) + where + F: FnMut(&AtomIdent); + /// Internal iterator for the part names of this element. fn each_part<F>(&self, _callback: F) where @@ -540,7 +540,7 @@ pub trait TElement: /// Internal iterator for the attribute names of this element. fn each_attr_name<F>(&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<ElementSelectorFlags>; + 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<E, Q, F>( } } -fn has_attr<E>(element: E, local_name: &AtomIdent) -> bool +fn has_attr<E>(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<SelectorImpl>) -> Option<&AtomIdent> { +fn get_attr_name(component: &Component<SelectorImpl>) -> 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<SelectorImpl>), } 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<Au> { let mut width = 0; @@ -286,16 +287,26 @@ fn eval_prefers_contrast(context: &Context, query_value: Option<PrefersContrast> 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<ForcedColors>) -> 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<F>(&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 } @@ -1237,20 +1241,6 @@ impl<'le> TElement for GeckoElement<'le> { } #[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() .get_bool_flag(nsINode_BooleanFlag::ElementHasPart) @@ -1292,6 +1282,20 @@ impl<'le> TElement for GeckoElement<'le> { } #[inline] + fn each_custom_state<F>(&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<F>(&self, name: &AtomIdent, callback: F) where F: FnMut(&AtomIdent), @@ -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<ElementSelectorFlags> { + 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(); @@ -2186,6 +2187,20 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { } #[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<F>(&self, callback: F) + where + F: FnMut(&AtomIdent); + /// The `xml:lang=""` or `lang=""` attribute value per this snapshot. fn lang_attr(&self) -> Option<AttrValue>; } @@ -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<Atom, SmallVec<[De pub type StateDependencyMap = SelectorMap<StateDependency>; /// Dependency mapping for local names. pub type LocalNameDependencyMap = PrecomputedHashMap<LocalName, SmallVec<[Dependency; 1]>>; +/// Dependency mapping for customstates +pub type CustomStateDependencyMap = PrecomputedHashMap<AtomIdent, SmallVec<[Dependency; 1]>>; /// A map where we store invalidations. /// @@ -282,6 +285,8 @@ pub struct InvalidationMap { pub document_state_selectors: Vec<DocumentStateDependency>, /// 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(), } } @@ -398,6 +404,9 @@ impl InvalidationMap { .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<C: Collector>(name: LocalName, collector: &mut C) -> Resu add_local_name(name, dependency, map) } +fn add_custom_state_dependency<C: Collector>(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<C: Collector>(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<E: TElement>(&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<Impl = E::Impl>, { - 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<CssRuleType>) -> 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<CssRuleType>) -> 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 <template> | html ]" + "Usage: %s [ servo | gecko ] [ style-crate | geckolib <template> ]" % sys.argv[0] ) if len(sys.argv) < 3: @@ -57,29 +31,15 @@ def main(): engine = sys.argv[1] output = sys.argv[2] - if engine not in ["servo-2013", "servo-2020", "gecko"] or output not in [ + if engine not in ["servo", "gecko"] or output not in [ "style-crate", "geckolib", - "html", ]: abort(usage) properties = data.PropertiesData(engine=engine) - files = {} - for kind in ["longhands", "shorthands"]: - files[kind] = {} - for struct in STYLE_STRUCT_LIST: - file_name = os.path.join(BASE, kind, "{}.mako.rs".format(struct)) - if kind == "shorthands" and not os.path.exists(file_name): - files[kind][struct] = "" - continue - files[kind][struct] = render( - file_name, - engine=engine, - data=properties, - ) properties_template = os.path.join(BASE, "properties.mako.rs") - files["properties"] = render( + properties_file = render( properties_template, engine=engine, data=properties, @@ -87,31 +47,15 @@ def main(): OUT_DIR=OUT_DIR, ) if output == "style-crate": - write(OUT_DIR, "properties.rs", files["properties"]) - for kind in ["longhands", "shorthands"]: - for struct in files[kind]: - write( - os.path.join(OUT_DIR, kind), - "{}.rs".format(struct), - files[kind][struct], - ) - - if engine == "gecko": - template = os.path.join(BASE, "gecko.mako.rs") - rust = render(template, data=properties) - write(OUT_DIR, "gecko_properties.rs", rust) - - if engine in ["servo-2013", "servo-2020"]: - if engine == "servo-2013": - pref_attr = "servo_2013_pref" - if engine == "servo-2020": - pref_attr = "servo_2020_pref" + write(OUT_DIR, "properties.rs", properties_file) + + if engine == "servo": properties_dict = { kind: { - p.name: {"pref": getattr(p, pref_attr)} + p.name: {"pref": getattr(p, "servo_pref")} for prop in properties_list if prop.enabled_in_content() - for p in [prop] + prop.alias + for p in [prop] + prop.aliases } for kind, properties_list in [ ("longhands", properties.longhands), diff --git a/servo/components/style/properties/cascade.rs b/servo/components/style/properties/cascade.rs index d4d48eaeb8..0f264a9249 100644 --- a/servo/components/style/properties/cascade.rs +++ b/servo/components/style/properties/cascade.rs @@ -297,7 +297,7 @@ where context.style().add_flags(cascade_input_flags); let using_cached_reset_properties; - let ignore_colors = !context.builder.device.use_document_colors(); + let ignore_colors = context.builder.device.forced_colors().is_active(); let mut cascade = Cascade::new(first_line_reparenting, ignore_colors); let mut declarations = Default::default(); let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); @@ -763,6 +763,11 @@ impl<'b> Cascade<'b> { if apply!(Zoom) { self.compute_zoom(context); + // NOTE(emilio): This is a bit of a hack, but matches the shipped WebKit and Blink + // behavior for now. Ideally, in the future, we have a pass over all + // implicitly-or-explicitly-inherited properties that can contain lengths and + // re-compute them properly, see https://github.com/w3c/csswg-drafts/issues/9397. + self.recompute_font_size_for_zoom_change(&mut context.builder); } // Compute font-family. @@ -1226,8 +1231,6 @@ impl<'b> Cascade<'b> { /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up /// the struct when this happens by unzooming its contained font values, which will have been /// zoomed in the parent. - /// - /// FIXME(emilio): Why doing this _before_ handling font-size? That sounds wrong. #[cfg(feature = "gecko")] fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) { debug_assert!(self.seen.contains(LonghandId::XTextScale)); @@ -1250,6 +1253,19 @@ impl<'b> Cascade<'b> { builder.mutate_font().unzoom_fonts(device); } + fn recompute_font_size_for_zoom_change(&self, builder: &mut StyleBuilder) { + debug_assert!(self.seen.contains(LonghandId::Zoom)); + // NOTE(emilio): Intentionally not using the effective zoom here, since all the inherited + // zooms are already applied. + let zoom = builder.get_box().clone_zoom(); + let old_size = builder.get_font().clone_font_size(); + let new_size = old_size.zoom(zoom); + if old_size == new_size { + return; + } + builder.mutate_font().set_font_size(new_size); + } + /// Special handling of font-size: math (used for MathML). /// https://w3c.github.io/mathml-core/#the-math-script-level-property /// TODO: Bug: 1548471: MathML Core also does not specify a script min size diff --git a/servo/components/style/properties/data.py b/servo/components/style/properties/data.py index 093f1cb75f..2a29a7ed58 100644 --- a/servo/components/style/properties/data.py +++ b/servo/components/style/properties/data.py @@ -176,11 +176,9 @@ class Keyword(object): gecko_enum_prefix=None, custom_consts=None, extra_gecko_values=None, - extra_servo_2013_values=None, - extra_servo_2020_values=None, + extra_servo_values=None, gecko_aliases=None, - servo_2013_aliases=None, - servo_2020_aliases=None, + servo_aliases=None, gecko_strip_moz_prefix=None, gecko_inexhaustive=None, ): @@ -196,11 +194,9 @@ class Keyword(object): ) self.gecko_enum_prefix = gecko_enum_prefix self.extra_gecko_values = (extra_gecko_values or "").split() - self.extra_servo_2013_values = (extra_servo_2013_values or "").split() - self.extra_servo_2020_values = (extra_servo_2020_values or "").split() + self.extra_servo_values = (extra_servo_values or "").split() self.gecko_aliases = parse_aliases(gecko_aliases or "") - self.servo_2013_aliases = parse_aliases(servo_2013_aliases or "") - self.servo_2020_aliases = parse_aliases(servo_2020_aliases or "") + self.servo_aliases = parse_aliases(servo_aliases or "") self.consts_map = {} if custom_consts is None else custom_consts self.gecko_strip_moz_prefix = ( True if gecko_strip_moz_prefix is None else gecko_strip_moz_prefix @@ -210,20 +206,16 @@ class Keyword(object): def values_for(self, engine): if engine == "gecko": return self.values + self.extra_gecko_values - elif engine == "servo-2013": - return self.values + self.extra_servo_2013_values - elif engine == "servo-2020": - return self.values + self.extra_servo_2020_values + elif engine == "servo": + return self.values + self.extra_servo_values else: raise Exception("Bad engine: " + engine) def aliases_for(self, engine): if engine == "gecko": return self.gecko_aliases - elif engine == "servo-2013": - return self.servo_2013_aliases - elif engine == "servo-2020": - return self.servo_2020_aliases + elif engine == "servo": + return self.servo_aliases else: raise Exception("Bad engine: " + engine) @@ -289,8 +281,7 @@ class Property(object): self, name, spec, - servo_2013_pref, - servo_2020_pref, + servo_pref, gecko_pref, enabled_in, rule_types_allowed, @@ -304,8 +295,7 @@ class Property(object): self.spec = spec self.ident = to_rust_ident(name) self.camel_case = to_camel_case(self.ident) - self.servo_2013_pref = servo_2013_pref - self.servo_2020_pref = servo_2020_pref + self.servo_pref = servo_pref self.gecko_pref = gecko_pref self.rule_types_allowed = rule_values_from_arg(rule_types_allowed) # For enabled_in, the setup is as follows: @@ -329,10 +319,8 @@ class Property(object): def experimental(self, engine): if engine == "gecko": return bool(self.gecko_pref) - elif engine == "servo-2013": - return bool(self.servo_2013_pref) - elif engine == "servo-2020": - return bool(self.servo_2020_pref) + elif engine == "servo": + return bool(self.servo_pref) else: raise Exception("Bad engine: " + engine) @@ -364,8 +352,7 @@ class Longhand(Property): animation_value_type=None, keyword=None, predefined_type=None, - servo_2013_pref=None, - servo_2020_pref=None, + servo_pref=None, gecko_pref=None, enabled_in="content", need_index=False, @@ -390,8 +377,7 @@ class Longhand(Property): self, name=name, spec=spec, - servo_2013_pref=servo_2013_pref, - servo_2020_pref=servo_2020_pref, + servo_pref=servo_pref, gecko_pref=gecko_pref, enabled_in=enabled_in, rule_types_allowed=rule_types_allowed, @@ -518,16 +504,8 @@ class Longhand(Property): def may_be_disabled_in(self, shorthand, engine): if engine == "gecko": return self.gecko_pref and self.gecko_pref != shorthand.gecko_pref - elif engine == "servo-2013": - return ( - self.servo_2013_pref - and self.servo_2013_pref != shorthand.servo_2013_pref - ) - elif engine == "servo-2020": - return ( - self.servo_2020_pref - and self.servo_2020_pref != shorthand.servo_2020_pref - ) + elif engine == "servo": + return self.servo_pref and self.servo_pref != shorthand.servo_pref else: raise Exception("Bad engine: " + engine) @@ -659,8 +637,7 @@ class Shorthand(Property): name, sub_properties, spec=None, - servo_2013_pref=None, - servo_2020_pref=None, + servo_pref=None, gecko_pref=None, enabled_in="content", rule_types_allowed=DEFAULT_RULES, @@ -672,8 +649,7 @@ class Shorthand(Property): self, name=name, spec=spec, - servo_2013_pref=servo_2013_pref, - servo_2020_pref=servo_2020_pref, + servo_pref=servo_pref, gecko_pref=gecko_pref, enabled_in=enabled_in, rule_types_allowed=rule_types_allowed, @@ -704,8 +680,7 @@ class Alias(object): self.original = original self.enabled_in = original.enabled_in self.animatable = original.animatable - self.servo_2013_pref = original.servo_2013_pref - self.servo_2020_pref = original.servo_2020_pref + self.servo_pref = original.servo_pref self.gecko_pref = gecko_pref self.rule_types_allowed = original.rule_types_allowed self.flags = original.flags @@ -722,10 +697,8 @@ class Alias(object): def experimental(self, engine): if engine == "gecko": return bool(self.gecko_pref) - elif engine == "servo-2013": - return bool(self.servo_2013_pref) - elif engine == "servo-2020": - return bool(self.servo_2020_pref) + elif engine == "servo": + return bool(self.servo_pref) else: raise Exception("Bad engine: " + engine) @@ -768,7 +741,7 @@ class Method(object): class StyleStruct(object): - def __init__(self, name, inherited, gecko_name=None, additional_methods=None): + def __init__(self, name, inherited, gecko_name=None): self.gecko_struct_name = "Gecko" + name self.name = name self.name_lower = to_snake_case(name) @@ -777,15 +750,12 @@ class StyleStruct(object): self.inherited = inherited self.gecko_name = gecko_name or name self.gecko_ffi_name = "nsStyle" + self.gecko_name - self.additional_methods = additional_methods or [] self.document_dependent = self.gecko_name in ["Font", "Visibility", "Text"] class PropertiesData(object): def __init__(self, engine): self.engine = engine - self.style_structs = [] - self.current_style_struct = None self.longhands = [] self.longhands_by_name = {} self.longhands_by_logical_group = {} @@ -797,13 +767,35 @@ class PropertiesData(object): CountedUnknownProperty(p) for p in COUNTED_UNKNOWN_PROPERTIES ] - def new_style_struct(self, *args, **kwargs): - style_struct = StyleStruct(*args, **kwargs) - self.style_structs.append(style_struct) - self.current_style_struct = style_struct + self.style_structs = [ + StyleStruct("Background", inherited=False), + StyleStruct("Border", inherited=False), + StyleStruct("Box", inherited=False, gecko_name="Display"), + StyleStruct("Column", inherited=False), + StyleStruct("Counters", inherited=False, gecko_name="Content"), + StyleStruct("Effects", inherited=False), + StyleStruct("Font", inherited=True), + StyleStruct("InheritedBox", inherited=True, gecko_name="Visibility"), + StyleStruct("InheritedSVG", inherited=True, gecko_name="SVG"), + StyleStruct("InheritedTable", inherited=True, gecko_name="TableBorder"), + StyleStruct("InheritedText", inherited=True, gecko_name="Text"), + StyleStruct("InheritedUI", inherited=True, gecko_name="UI"), + StyleStruct("List", inherited=True), + StyleStruct("Margin", inherited=False), + StyleStruct("Outline", inherited=False), + StyleStruct("Padding", inherited=False), + StyleStruct("Page", inherited=False), + StyleStruct("Position", inherited=False), + StyleStruct("SVG", inherited=False, gecko_name="SVGReset"), + StyleStruct("Table", inherited=False), + StyleStruct("Text", inherited=False, gecko_name="TextReset"), + StyleStruct("UI", inherited=False, gecko_name="UIReset"), + StyleStruct("XUL", inherited=False), + ] + self.current_style_struct = None def active_style_structs(self): - return [s for s in self.style_structs if s.additional_methods or s.longhands] + return [s for s in self.style_structs if s.longhands] def add_prefixed_aliases(self, property): # FIXME Servo's DOM architecture doesn't support vendor-prefixed properties. @@ -856,7 +848,7 @@ def _add_logical_props(data, props): groups = set() for prop in props: if prop not in data.longhands_by_name: - assert data.engine in ["servo-2013", "servo-2020"] + assert data.engine == "servo" continue prop = data.longhands_by_name[prop] if prop.logical_group: diff --git a/servo/components/style/properties/declaration_block.rs b/servo/components/style/properties/declaration_block.rs index 81d7148e62..94945ab8c0 100644 --- a/servo/components/style/properties/declaration_block.rs +++ b/servo/components/style/properties/declaration_block.rs @@ -36,10 +36,10 @@ use cssparser::{ use itertools::Itertools; use selectors::SelectorList; use servo_arc::Arc; -use smallbitvec::{self, SmallBitVec}; +use smallbitvec::SmallBitVec; use smallvec::SmallVec; use std::fmt::{self, Write}; -use std::iter::{DoubleEndedIterator, Zip}; +use std::iter::Zip; use std::slice::Iter; use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss}; use thin_vec::ThinVec; diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs index 968a97aa00..82998632ca 100644 --- a/servo/components/style/properties/helpers.mako.rs +++ b/servo/components/style/properties/helpers.mako.rs @@ -594,11 +594,9 @@ 'gecko_constant_prefix', 'gecko_enum_prefix', 'extra_gecko_values', - 'extra_servo_2013_values', - 'extra_servo_2020_values', + 'extra_servo_values', 'gecko_aliases', - 'servo_2013_aliases', - 'servo_2020_aliases', + 'servo_aliases', 'custom_consts', 'gecko_inexhaustive', 'gecko_strip_moz_prefix', diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs index 290684cdab..171413b243 100644 --- a/servo/components/style/properties/helpers/animated_properties.mako.rs +++ b/servo/components/style/properties/helpers/animated_properties.mako.rs @@ -737,7 +737,7 @@ impl<'a> TransitionPropertyIterator<'a> { /// A single iteration of the TransitionPropertyIterator. pub struct TransitionPropertyIteration { /// The id of the longhand for this property. - pub longhand_id: LonghandId, + pub property: OwnedPropertyDeclarationId, /// The index of this property in the list of transition properties for this /// iterator's style. @@ -753,7 +753,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> { if let Some(ref mut longhand_iterator) = self.longhand_iterator { if let Some(longhand_id) = longhand_iterator.next() { return Some(TransitionPropertyIteration { - longhand_id, + property: OwnedPropertyDeclarationId::Longhand(longhand_id), index: self.index_range.start - 1, }); } @@ -766,7 +766,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> { match id.longhand_or_shorthand() { Ok(longhand_id) => { return Some(TransitionPropertyIteration { - longhand_id, + property: OwnedPropertyDeclarationId::Longhand(longhand_id), index, }); }, @@ -778,7 +778,13 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> { }, } } - TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {} + TransitionProperty::Custom(name) => { + return Some(TransitionPropertyIteration { + property: OwnedPropertyDeclarationId::Custom(name), + index, + }) + }, + TransitionProperty::Unsupported(..) => {}, } } } diff --git a/servo/components/style/properties/longhands/background.mako.rs b/servo/components/style/properties/longhands/background.mako.rs index 48270f748e..535dd64e20 100644 --- a/servo/components/style/properties/longhands/background.mako.rs +++ b/servo/components/style/properties/longhands/background.mako.rs @@ -4,13 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("Background", inherited=False) %> - ${helpers.predefined_type( "background-color", "Color", "computed::Color::TRANSPARENT_BLACK", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="SpecifiedValue::transparent()", spec="https://drafts.csswg.org/css-backgrounds/#background-color", animation_value_type="AnimatedColor", @@ -23,7 +21,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "background-image", "Image", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::Image::None", initial_specified_value="specified::Image::None", spec="https://drafts.csswg.org/css-backgrounds/#the-background-image", @@ -38,7 +36,7 @@ ${helpers.predefined_type( "background-position-" + axis, "position::" + direction + "Position", "computed::LengthPercentage::zero_percent()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="SpecifiedValue::initial_specified_value()", spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis, animation_value_type="ComputedValue", @@ -52,7 +50,7 @@ ${helpers.predefined_type( "background-repeat", "BackgroundRepeat", "computed::BackgroundRepeat::repeat()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::BackgroundRepeat::repeat()", animation_value_type="discrete", vector=True, @@ -62,8 +60,8 @@ ${helpers.predefined_type( ${helpers.single_keyword( "background-attachment", - "scroll" + (" fixed" if engine in ["gecko", "servo-2013"] else "") + (" local" if engine == "gecko" else ""), - engines="gecko servo-2013 servo-2020", + "scroll fixed" + (" local" if engine == "gecko" else ""), + engines="gecko servo", vector=True, gecko_enum_prefix="StyleImageLayerAttachment", spec="https://drafts.csswg.org/css-backgrounds/#the-background-attachment", @@ -74,7 +72,7 @@ ${helpers.single_keyword( ${helpers.single_keyword( "background-clip", "border-box padding-box content-box", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", extra_gecko_values="text", vector=True, extra_prefixes="webkit", gecko_enum_prefix="StyleGeometryBox", @@ -87,7 +85,7 @@ ${helpers.single_keyword( ${helpers.single_keyword( "background-origin", "padding-box border-box content-box", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", vector=True, extra_prefixes="webkit", gecko_enum_prefix="StyleGeometryBox", gecko_inexhaustive=True, @@ -99,7 +97,7 @@ ${helpers.single_keyword( ${helpers.predefined_type( "background-size", "BackgroundSize", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::BackgroundSize::auto()", initial_specified_value="specified::BackgroundSize::auto()", spec="https://drafts.csswg.org/css-backgrounds/#the-background-size", diff --git a/servo/components/style/properties/longhands/border.mako.rs b/servo/components/style/properties/longhands/border.mako.rs index 4d0676f678..59a0fee94a 100644 --- a/servo/components/style/properties/longhands/border.mako.rs +++ b/servo/components/style/properties/longhands/border.mako.rs @@ -3,11 +3,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Keyword, Method, ALL_CORNERS, PHYSICAL_SIDES, ALL_SIDES, maybe_moz_logical_alias %> +<% from data import ALL_CORNERS, ALL_SIDES, maybe_moz_logical_alias %> -<% data.new_style_struct("Border", inherited=False, - additional_methods=[Method("border_" + side + "_has_nonzero_width", - "bool") for side in ["top", "right", "bottom", "left"]]) %> <% def maybe_logical_spec(side, kind): if side[1]: # if it is logical @@ -23,7 +20,7 @@ ${helpers.predefined_type( "border-%s-color" % side_name, "Color", "computed_value::T::currentcolor()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-color"), spec=maybe_logical_spec(side, "color"), animation_value_type="AnimatedColor", @@ -37,7 +34,7 @@ ${helpers.predefined_type( "border-%s-style" % side_name, "BorderStyle", "specified::BorderStyle::None", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-style"), spec=maybe_logical_spec(side, "style"), animation_value_type="discrete" if not is_logical else "none", @@ -50,7 +47,7 @@ "border-%s-width" % side_name, "BorderSideWidth", "app_units::Au::from_px(3)", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-width"), spec=maybe_logical_spec(side, "width"), animation_value_type="NonNegativeLength", @@ -76,7 +73,7 @@ "BorderCornerRadius", "computed::BorderCornerRadius::zero()", "parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", extra_prefixes=prefixes, spec=maybe_logical_spec(corner, "radius"), boxed=True, @@ -111,13 +108,13 @@ ${helpers.single_keyword( ${helpers.predefined_type( "border-image-source", "Image", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::Image::None", initial_specified_value="specified::Image::None", spec="https://drafts.csswg.org/css-backgrounds/#the-background-image", vector=False, animation_value_type="discrete", - boxed=engine == "servo-2013", + boxed=engine == "servo", ignored_when_colors_disabled=True, affects="paint", )} @@ -125,7 +122,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "border-image-outset", "NonNegativeLengthOrNumberRect", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="generics::rect::Rect::all(computed::NonNegativeLengthOrNumber::zero())", initial_specified_value="generics::rect::Rect::all(specified::NonNegativeLengthOrNumber::zero())", spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset", @@ -138,7 +135,7 @@ ${helpers.predefined_type( "border-image-repeat", "BorderImageRepeat", "computed::BorderImageRepeat::stretch()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::BorderImageRepeat::stretch()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat", @@ -148,7 +145,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "border-image-width", "BorderImageWidth", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::BorderImageWidth::all(computed::BorderImageSideWidth::one())", initial_specified_value="specified::BorderImageWidth::all(specified::BorderImageSideWidth::one())", spec="https://drafts.csswg.org/css-backgrounds/#border-image-width", @@ -160,7 +157,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "border-image-slice", "BorderImageSlice", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::BorderImageSlice::hundred_percent()", initial_specified_value="specified::BorderImageSlice::hundred_percent()", spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice", diff --git a/servo/components/style/properties/longhands/box.mako.rs b/servo/components/style/properties/longhands/box.mako.rs index 017bef38ea..187e8f8169 100644 --- a/servo/components/style/properties/longhands/box.mako.rs +++ b/servo/components/style/properties/longhands/box.mako.rs @@ -3,17 +3,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%> - -<% data.new_style_struct("Box", - inherited=False, - gecko_name="Display") %> +<% from data import ALL_AXES %> ${helpers.predefined_type( "display", "Display", "computed::Display::inline()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::Display::inline()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-display/#propdef-display", @@ -38,7 +34,7 @@ ${helpers.single_keyword( ${helpers.single_keyword( "-servo-top-layer", "none top", - engines="servo-2013 servo-2020", + engines="servo", animation_value_type="none", enabled_in="ua", spec="Internal (not web-exposed)", @@ -47,8 +43,8 @@ ${helpers.single_keyword( <%helpers:single_keyword name="position" - values="static absolute relative fixed ${'sticky' if engine in ['gecko', 'servo-2013'] else ''}" - engines="gecko servo-2013 servo-2020" + values="static absolute relative fixed sticky" + engines="gecko servo" animation_value_type="discrete" gecko_enum_prefix="StylePositionProperty" spec="https://drafts.csswg.org/css-position/#position-property" @@ -69,8 +65,7 @@ ${helpers.predefined_type( "float", "Float", "computed::Float::None", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", initial_specified_value="specified::Float::None", spec="https://drafts.csswg.org/css-box/#propdef-float", animation_value_type="discrete", @@ -83,7 +78,7 @@ ${helpers.predefined_type( "clear", "Clear", "computed::Clear::None", - engines="gecko servo-2013", + engines="gecko servo", animation_value_type="discrete", spec="https://drafts.csswg.org/css2/#propdef-clear", servo_restyle_damage="rebuild_and_reflow", @@ -94,7 +89,7 @@ ${helpers.predefined_type( "vertical-align", "VerticalAlign", "computed::VerticalAlign::baseline()", - engines="gecko servo-2013", + engines="gecko servo", animation_value_type="ComputedValue", spec="https://www.w3.org/TR/CSS2/visudet.html#propdef-vertical-align", servo_restyle_damage = "reflow", @@ -105,7 +100,7 @@ ${helpers.predefined_type( "baseline-source", "BaselineSource", "computed::BaselineSource::Auto", - engines="gecko servo-2013", + engines="gecko servo", animation_value_type="discrete", spec="https://drafts.csswg.org/css-inline-3/#baseline-source", servo_restyle_damage = "reflow", @@ -117,7 +112,8 @@ ${helpers.predefined_type( ${helpers.single_keyword( "-servo-overflow-clip-box", "padding-box content-box", - engines="servo-2013", + engines="servo", + servo_pref="layout.legacy_layout", animation_value_type="none", enabled_in="ua", spec="Internal, not web-exposed, \ @@ -146,7 +142,7 @@ ${helpers.single_keyword( full_name, "Overflow", "computed::Overflow::Visible", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", logical_group="overflow", logical=logical, animation_value_type="discrete", @@ -174,7 +170,7 @@ ${helpers.predefined_type( "transform", "Transform", "generics::transform::Transform::none()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", extra_prefixes=transform_extra_prefixes, animation_value_type="ComputedValue", flags="CAN_ANIMATE_ON_COMPOSITOR", @@ -187,7 +183,7 @@ ${helpers.predefined_type( "rotate", "Rotate", "generics::transform::Rotate::None", - engines="gecko servo-2013", + engines="gecko servo", animation_value_type="ComputedValue", boxed=True, flags="CAN_ANIMATE_ON_COMPOSITOR", @@ -201,7 +197,7 @@ ${helpers.predefined_type( "scale", "Scale", "generics::transform::Scale::None", - engines="gecko servo-2013", + engines="gecko servo", animation_value_type="ComputedValue", boxed=True, flags="CAN_ANIMATE_ON_COMPOSITOR", @@ -215,7 +211,7 @@ ${helpers.predefined_type( "translate", "Translate", "generics::transform::Translate::None", - engines="gecko servo-2013", + engines="gecko servo", animation_value_type="ComputedValue", boxed=True, flags="CAN_ANIMATE_ON_COMPOSITOR", @@ -285,7 +281,6 @@ ${helpers.predefined_type( "computed::OffsetPosition::normal()", engines="gecko", animation_value_type="ComputedValue", - gecko_pref="layout.css.motion-path-offset-position.enabled", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.fxtf.org/motion-1/#offset-position-property", servo_restyle_damage="reflow_out_of_flow", @@ -409,7 +404,7 @@ ${helpers.predefined_type( "perspective", "Perspective", "computed::Perspective::none()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", gecko_ffi_name="mChildPerspective", spec="https://drafts.csswg.org/css-transforms/#perspective", extra_prefixes=transform_extra_prefixes, @@ -422,7 +417,7 @@ ${helpers.predefined_type( "perspective-origin", "Position", "computed::position::Position::center()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", boxed=True, extra_prefixes=transform_extra_prefixes, spec="https://drafts.csswg.org/css-transforms-2/#perspective-origin-property", @@ -434,7 +429,7 @@ ${helpers.predefined_type( ${helpers.single_keyword( "backface-visibility", "visible hidden", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", gecko_enum_prefix="StyleBackfaceVisibility", spec="https://drafts.csswg.org/css-transforms/#backface-visibility-property", extra_prefixes=transform_extra_prefixes, @@ -456,7 +451,7 @@ ${helpers.predefined_type( "transform-style", "TransformStyle", "computed::TransformStyle::Flat", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-transforms-2/#transform-style-property", extra_prefixes=transform_extra_prefixes, animation_value_type="discrete", @@ -468,7 +463,7 @@ ${helpers.predefined_type( "transform-origin", "TransformOrigin", "computed::TransformOrigin::initial_value()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="ComputedValue", extra_prefixes=transform_extra_prefixes, gecko_ffi_name="mTransformOrigin", @@ -503,10 +498,11 @@ ${helpers.predefined_type( "container-type", "ContainerType", "computed::ContainerType::Normal", - engines="gecko", + engines="gecko servo", animation_value_type="none", enabled_in="ua", gecko_pref="layout.css.container-queries.enabled", + servo_pref="layout.container-queries.enabled", spec="https://drafts.csswg.org/css-contain-3/#container-type", affects="layout", )} @@ -515,10 +511,11 @@ ${helpers.predefined_type( "container-name", "ContainerName", "computed::ContainerName::none()", - engines="gecko", + engines="gecko servo", animation_value_type="none", enabled_in="ua", gecko_pref="layout.css.container-queries.enabled", + servo_pref="layout.container-queries.enabled", spec="https://drafts.csswg.org/css-contain-3/#container-name", affects="", )} diff --git a/servo/components/style/properties/longhands/column.mako.rs b/servo/components/style/properties/longhands/column.mako.rs index 38c32938c6..ca97f36dba 100644 --- a/servo/components/style/properties/longhands/column.mako.rs +++ b/servo/components/style/properties/longhands/column.mako.rs @@ -4,17 +4,14 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("Column", inherited=False) %> - ${helpers.predefined_type( "column-width", "length::NonNegativeLengthOrAuto", "computed::length::NonNegativeLengthOrAuto::auto()", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", initial_specified_value="specified::length::NonNegativeLengthOrAuto::auto()", animation_value_type="NonNegativeLengthOrAuto", - servo_2013_pref="layout.columns.enabled", + servo_pref="layout.columns.enabled", spec="https://drafts.csswg.org/css-multicol/#propdef-column-width", servo_restyle_damage="rebuild_and_reflow", affects="layout", @@ -24,10 +21,9 @@ ${helpers.predefined_type( "column-count", "ColumnCount", "computed::ColumnCount::auto()", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", initial_specified_value="specified::ColumnCount::auto()", - servo_2013_pref="layout.columns.enabled", + servo_pref="layout.columns.enabled", animation_value_type="AnimatedColumnCount", spec="https://drafts.csswg.org/css-multicol/#propdef-column-count", servo_restyle_damage="rebuild_and_reflow", @@ -71,7 +67,8 @@ ${helpers.predefined_type( ${helpers.single_keyword( "column-span", "none all", - engines="gecko", + engines="gecko servo", + servo_pref="layout.columns.enabled", animation_value_type="discrete", gecko_enum_prefix="StyleColumnSpan", spec="https://drafts.csswg.org/css-multicol/#propdef-column-span", diff --git a/servo/components/style/properties/longhands/counters.mako.rs b/servo/components/style/properties/longhands/counters.mako.rs index 6c844c3567..5991bd416c 100644 --- a/servo/components/style/properties/longhands/counters.mako.rs +++ b/servo/components/style/properties/longhands/counters.mako.rs @@ -4,13 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %> - ${helpers.predefined_type( "content", "Content", "computed::Content::normal()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::Content::normal()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-content/#propdef-content", @@ -21,7 +19,8 @@ ${helpers.predefined_type( ${helpers.predefined_type( "counter-increment", "CounterIncrement", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", initial_value="Default::default()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment", @@ -32,7 +31,8 @@ ${helpers.predefined_type( ${helpers.predefined_type( "counter-reset", "CounterReset", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", initial_value="Default::default()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset", diff --git a/servo/components/style/properties/longhands/effects.mako.rs b/servo/components/style/properties/longhands/effects.mako.rs index b301aab5dd..742844a670 100644 --- a/servo/components/style/properties/longhands/effects.mako.rs +++ b/servo/components/style/properties/longhands/effects.mako.rs @@ -4,14 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -// Box-shadow, etc. -<% data.new_style_struct("Effects", inherited=False) %> - ${helpers.predefined_type( "opacity", "Opacity", "1.0", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="ComputedValue", flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-color/#transparency", @@ -23,8 +20,8 @@ ${helpers.predefined_type( "box-shadow", "BoxShadow", None, - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", + servo_pref="layout.legacy_layout", vector=True, simple_vector_bindings=True, animation_value_type="AnimatedBoxShadowList", @@ -39,7 +36,7 @@ ${helpers.predefined_type( "clip", "ClipRectOrAuto", "computed::ClipRectOrAuto::auto()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="ComputedValue", boxed=True, allow_quirks="Yes", @@ -51,7 +48,7 @@ ${helpers.predefined_type( "filter", "Filter", None, - engines="gecko servo-2013 servo-2020", + engines="gecko servo", vector=True, simple_vector_bindings=True, gecko_ffi_name="mFilters", @@ -84,7 +81,7 @@ ${helpers.single_keyword( """normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue saturation color luminosity plus-lighter""", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", gecko_enum_prefix="StyleBlend", animation_value_type="discrete", spec="https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode", diff --git a/servo/components/style/properties/longhands/font.mako.rs b/servo/components/style/properties/longhands/font.mako.rs index 4583de0bd7..3f7a2f0657 100644 --- a/servo/components/style/properties/longhands/font.mako.rs +++ b/servo/components/style/properties/longhands/font.mako.rs @@ -3,14 +3,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method, to_camel_case, to_rust_ident, to_camel_case_lower, SYSTEM_FONT_LONGHANDS %> - -<% data.new_style_struct("Font", inherited=True) %> +<% from data import SYSTEM_FONT_LONGHANDS %> ${helpers.predefined_type( "font-family", "FontFamily", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::FontFamily::serif()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-fonts/#propdef-font-family", @@ -21,7 +19,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "font-style", "FontStyle", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::FontStyle::normal()", initial_specified_value="specified::FontStyle::normal()", animation_value_type="FontStyle", @@ -39,7 +37,7 @@ ${helpers.predefined_type( ${helpers.single_keyword( "font-variant-caps", "normal small-caps", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", extra_gecko_values="all-small-caps petite-caps all-petite-caps unicase titling-caps", gecko_constant_prefix="NS_FONT_VARIANT_CAPS", gecko_ffi_name="mFont.variantCaps", @@ -53,7 +51,7 @@ ${helpers.single_keyword( ${helpers.predefined_type( "font-weight", "FontWeight", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::FontWeight::normal()", initial_specified_value="specified::FontWeight::normal()", animation_value_type="Number", @@ -65,7 +63,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "font-size", "FontSize", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::FontSize::medium()", initial_specified_value="specified::FontSize::medium()", animation_value_type="NonNegativeLength", @@ -137,7 +135,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "font-stretch", "FontStretch", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::FontStretch::hundred()", initial_specified_value="specified::FontStretch::normal()", animation_value_type="Percentage", @@ -354,7 +352,7 @@ ${helpers.predefined_type( "line-height", "LineHeight", "computed::LineHeight::normal()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="LineHeight", spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height", servo_restyle_damage="reflow", diff --git a/servo/components/style/properties/longhands/inherited_box.mako.rs b/servo/components/style/properties/longhands/inherited_box.mako.rs index 7fd94d1a1f..add367798b 100644 --- a/servo/components/style/properties/longhands/inherited_box.mako.rs +++ b/servo/components/style/properties/longhands/inherited_box.mako.rs @@ -4,13 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %> - // TODO: collapse. Well, do tables first. ${helpers.single_keyword( "visibility", "visible hidden collapse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", gecko_ffi_name="mVisible", animation_value_type="ComputedValue", spec="https://drafts.csswg.org/css-box/#propdef-visibility", @@ -23,13 +21,12 @@ ${helpers.single_keyword( ${helpers.single_keyword( "writing-mode", "horizontal-tb vertical-rl vertical-lr", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", extra_gecko_values="sideways-rl sideways-lr", gecko_aliases="lr=horizontal-tb lr-tb=horizontal-tb \ rl=horizontal-tb rl-tb=horizontal-tb \ tb=vertical-rl tb-rl=vertical-rl", - servo_2013_pref="layout.writing-mode.enabled", - servo_2020_pref="layout.writing-mode.enabled", + servo_pref="layout.writing-mode.enabled", animation_value_type="none", spec="https://drafts.csswg.org/css-writing-modes/#propdef-writing-mode", gecko_enum_prefix="StyleWritingModeProperty", @@ -40,8 +37,8 @@ ${helpers.single_keyword( ${helpers.single_keyword( "direction", "ltr rtl", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="none", spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction", gecko_enum_prefix="StyleDirection", @@ -88,7 +85,7 @@ ${helpers.predefined_type( "image-rendering", "ImageRendering", "computed::ImageRendering::Auto", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-images/#propdef-image-rendering", animation_value_type="discrete", affects="paint", diff --git a/servo/components/style/properties/longhands/inherited_svg.mako.rs b/servo/components/style/properties/longhands/inherited_svg.mako.rs index 90443f962a..d01d5a4a80 100644 --- a/servo/components/style/properties/longhands/inherited_svg.mako.rs +++ b/servo/components/style/properties/longhands/inherited_svg.mako.rs @@ -4,10 +4,6 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -// SVG 2 -// https://svgwg.org/svg2-draft/ -<% data.new_style_struct("InheritedSVG", inherited=True, gecko_name="SVG") %> - // Section 10 - Text ${helpers.single_keyword( diff --git a/servo/components/style/properties/longhands/inherited_table.mako.rs b/servo/components/style/properties/longhands/inherited_table.mako.rs index 7eb42a6eb2..c7ada525b8 100644 --- a/servo/components/style/properties/longhands/inherited_table.mako.rs +++ b/servo/components/style/properties/longhands/inherited_table.mako.rs @@ -4,12 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("InheritedTable", inherited=True, gecko_name="TableBorder") %> - ${helpers.single_keyword( "border-collapse", "separate collapse", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", gecko_enum_prefix="StyleBorderCollapse", animation_value_type="discrete", spec="https://drafts.csswg.org/css-tables/#propdef-border-collapse", @@ -20,7 +19,8 @@ ${helpers.single_keyword( ${helpers.single_keyword( "empty-cells", "show hide", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", gecko_enum_prefix="StyleEmptyCells", animation_value_type="discrete", spec="https://drafts.csswg.org/css-tables/#propdef-empty-cells", @@ -32,7 +32,8 @@ ${helpers.predefined_type( "caption-side", "table::CaptionSide", "computed::table::CaptionSide::Top", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="discrete", spec="https://drafts.csswg.org/css-tables/#propdef-caption-side", servo_restyle_damage="rebuild_and_reflow", @@ -43,8 +44,7 @@ ${helpers.predefined_type( "border-spacing", "BorderSpacing", "computed::BorderSpacing::zero()", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", animation_value_type="BorderSpacing", boxed=True, spec="https://drafts.csswg.org/css-tables/#propdef-border-spacing", diff --git a/servo/components/style/properties/longhands/inherited_text.mako.rs b/servo/components/style/properties/longhands/inherited_text.mako.rs index 544ba99bf7..c6ad907b23 100644 --- a/servo/components/style/properties/longhands/inherited_text.mako.rs +++ b/servo/components/style/properties/longhands/inherited_text.mako.rs @@ -3,14 +3,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Keyword %> -<% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %> ${helpers.predefined_type( "color", "ColorPropertyValue", "crate::color::AbsoluteColor::BLACK", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="AbsoluteColor", ignored_when_colors_disabled="True", spec="https://drafts.csswg.org/css-color/#color", @@ -23,7 +21,8 @@ ${helpers.predefined_type( "text-transform", "TextTransform", "computed::TextTransform::none()", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-text-transform", servo_restyle_damage="rebuild_and_reflow", @@ -58,8 +57,7 @@ ${helpers.predefined_type( "text-indent", "TextIndent", "computed::TextIndent::zero()", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", animation_value_type="ComputedValue", spec="https://drafts.csswg.org/css-text/#propdef-text-indent", servo_restyle_damage = "reflow", @@ -72,8 +70,8 @@ ${helpers.predefined_type( "overflow-wrap", "OverflowWrap", "computed::OverflowWrap::Normal", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-overflow-wrap", aliases="word-wrap", @@ -85,8 +83,8 @@ ${helpers.predefined_type( "word-break", "WordBreak", "computed::WordBreak::Normal", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-word-break", servo_restyle_damage="rebuild_and_reflow", @@ -97,8 +95,7 @@ ${helpers.predefined_type( "text-justify", "TextJustify", "computed::TextJustify::Auto", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-text-justify", servo_restyle_damage="rebuild_and_reflow", @@ -109,7 +106,7 @@ ${helpers.predefined_type( "text-align-last", "TextAlignLast", "computed::text::TextAlignLast::Auto", - engines="gecko", + engines="gecko servo", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-text-align-last", affects="layout", @@ -120,7 +117,7 @@ ${helpers.predefined_type( "text-align", "TextAlign", "computed::TextAlign::Start", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text/#propdef-text-align", servo_restyle_damage = "reflow", @@ -131,7 +128,7 @@ ${helpers.predefined_type( "letter-spacing", "LetterSpacing", "computed::LetterSpacing::normal()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="ComputedValue", spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing", servo_restyle_damage="rebuild_and_reflow", @@ -142,7 +139,7 @@ ${helpers.predefined_type( "word-spacing", "WordSpacing", "computed::WordSpacing::zero()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="ComputedValue", spec="https://drafts.csswg.org/css-text/#propdef-word-spacing", servo_restyle_damage="rebuild_and_reflow", @@ -164,7 +161,8 @@ ${helpers.predefined_type( "text-shadow", "SimpleShadow", None, - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", vector=True, vector_animation_type="with_zero", animation_value_type="AnimatedTextShadowList", @@ -304,7 +302,7 @@ ${helpers.single_keyword( ${helpers.single_keyword( "text-rendering", "auto optimizespeed optimizelegibility geometricprecision", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", gecko_enum_prefix="StyleTextRendering", animation_value_type="discrete", spec="https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty", diff --git a/servo/components/style/properties/longhands/inherited_ui.mako.rs b/servo/components/style/properties/longhands/inherited_ui.mako.rs index 6cdf721336..7ee1adc602 100644 --- a/servo/components/style/properties/longhands/inherited_ui.mako.rs +++ b/servo/components/style/properties/longhands/inherited_ui.mako.rs @@ -4,13 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UI") %> - ${helpers.predefined_type( "cursor", "Cursor", "computed::Cursor::auto()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::Cursor::auto()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-ui/#cursor", @@ -23,7 +21,7 @@ ${helpers.predefined_type( ${helpers.single_keyword( "pointer-events", "auto none", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", animation_value_type="discrete", extra_gecko_values="visiblepainted visiblefill visiblestroke visible painted fill stroke all", spec="https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty", diff --git a/servo/components/style/properties/longhands/list.mako.rs b/servo/components/style/properties/longhands/list.mako.rs index 619724bd32..0a3f8e36ec 100644 --- a/servo/components/style/properties/longhands/list.mako.rs +++ b/servo/components/style/properties/longhands/list.mako.rs @@ -4,13 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("List", inherited=True) %> - ${helpers.single_keyword( "list-style-position", "outside inside", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", + servo_pref="layout.legacy_layout", gecko_enum_prefix="StyleListStylePosition", animation_value_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-list-style-position", @@ -24,17 +22,16 @@ ${helpers.single_keyword( // upper-roman // // [1]: http://dev.w3.org/csswg/css-counter-styles/ -% if engine in ["servo-2013", "servo-2020"]: +% if engine == "servo": ${helpers.single_keyword( "list-style-type", - "disc none circle square disclosure-open disclosure-closed", - extra_servo_2013_values=""" + """disc none circle square disclosure-open disclosure-closed decimal lower-alpha upper-alpha arabic-indic bengali cambodian cjk-decimal devanagari gujarati gurmukhi kannada khmer lao malayalam mongolian myanmar oriya persian telugu thai tibetan cjk-earthly-branch cjk-heavenly-stem lower-greek hiragana hiragana-iroha katakana katakana-iroha """, - engines="servo-2013 servo-2020", + engines="servo", animation_value_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type", servo_restyle_damage="rebuild_and_reflow", @@ -59,11 +56,12 @@ ${helpers.single_keyword( ${helpers.predefined_type( "list-style-image", "Image", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_value="computed::Image::None", initial_specified_value="specified::Image::None", animation_value_type="discrete", spec="https://drafts.csswg.org/css-lists/#propdef-list-style-image", + boxed=engine == "servo", servo_restyle_damage="rebuild_and_reflow", affects="layout", )} @@ -72,7 +70,8 @@ ${helpers.predefined_type( "quotes", "Quotes", "computed::Quotes::get_initial_value()", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="discrete", spec="https://drafts.csswg.org/css-content/#propdef-quotes", servo_restyle_damage="rebuild_and_reflow", diff --git a/servo/components/style/properties/longhands/margin.mako.rs b/servo/components/style/properties/longhands/margin.mako.rs index b5a87f9683..1000704140 100644 --- a/servo/components/style/properties/longhands/margin.mako.rs +++ b/servo/components/style/properties/longhands/margin.mako.rs @@ -4,7 +4,6 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <% from data import ALL_SIDES, DEFAULT_RULES_AND_PAGE, maybe_moz_logical_alias %> -<% data.new_style_struct("Margin", inherited=False) %> % for side in ALL_SIDES: <% @@ -16,7 +15,7 @@ "margin-%s" % side[0], "LengthPercentageOrAuto", "computed::LengthPercentageOrAuto::zero()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", aliases=maybe_moz_logical_alias(engine, side, "-moz-margin-%s"), allow_quirks="No" if side[1] else "Yes", animation_value_type="ComputedValue", diff --git a/servo/components/style/properties/longhands/outline.mako.rs b/servo/components/style/properties/longhands/outline.mako.rs index 8e7f956bf5..d366cbc996 100644 --- a/servo/components/style/properties/longhands/outline.mako.rs +++ b/servo/components/style/properties/longhands/outline.mako.rs @@ -3,18 +3,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method %> -<% data.new_style_struct("Outline", - inherited=False, - additional_methods=[Method("outline_has_nonzero_width", "bool")]) %> - -// TODO(pcwalton): `invert` ${helpers.predefined_type( "outline-color", "Color", "computed_value::T::currentcolor()", - engines="gecko servo-2013", + engines="gecko servo", initial_specified_value="specified::Color::currentcolor()", animation_value_type="AnimatedColor", ignored_when_colors_disabled=True, @@ -26,8 +20,7 @@ ${helpers.predefined_type( "outline-style", "OutlineStyle", "computed::OutlineStyle::none()", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", initial_specified_value="specified::OutlineStyle::none()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-ui/#propdef-outline-style", @@ -38,8 +31,7 @@ ${helpers.predefined_type( "outline-width", "BorderSideWidth", "app_units::Au::from_px(3)", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", + engines="gecko servo", initial_specified_value="specified::BorderSideWidth::medium()", animation_value_type="NonNegativeLength", spec="https://drafts.csswg.org/css-ui/#propdef-outline-width", @@ -50,7 +42,7 @@ ${helpers.predefined_type( "outline-offset", "Length", "crate::values::computed::Length::new(0.)", - engines="gecko servo-2013", + engines="gecko servo", animation_value_type="ComputedValue", spec="https://drafts.csswg.org/css-ui/#propdef-outline-offset", affects="overflow", diff --git a/servo/components/style/properties/longhands/padding.mako.rs b/servo/components/style/properties/longhands/padding.mako.rs index a165e2cd34..d3984b71d2 100644 --- a/servo/components/style/properties/longhands/padding.mako.rs +++ b/servo/components/style/properties/longhands/padding.mako.rs @@ -4,7 +4,6 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <% from data import ALL_SIDES, maybe_moz_logical_alias %> -<% data.new_style_struct("Padding", inherited=False) %> % for side in ALL_SIDES: <% @@ -16,7 +15,7 @@ "padding-%s" % side[0], "NonNegativeLengthPercentage", "computed::NonNegativeLengthPercentage::zero()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", aliases=maybe_moz_logical_alias(engine, side, "-moz-padding-%s"), animation_value_type="NonNegativeLengthPercentage", logical=side[1], diff --git a/servo/components/style/properties/longhands/page.mako.rs b/servo/components/style/properties/longhands/page.mako.rs index 86cd284e18..bb7729c722 100644 --- a/servo/components/style/properties/longhands/page.mako.rs +++ b/servo/components/style/properties/longhands/page.mako.rs @@ -5,8 +5,6 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <% from data import PAGE_RULE %> -<% data.new_style_struct("Page", inherited=False) %> - ${helpers.predefined_type( "size", "PageSize", diff --git a/servo/components/style/properties/longhands/position.mako.rs b/servo/components/style/properties/longhands/position.mako.rs index fb68baa6b4..e71803e026 100644 --- a/servo/components/style/properties/longhands/position.mako.rs +++ b/servo/components/style/properties/longhands/position.mako.rs @@ -2,19 +2,16 @@ * 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/. */ -<%! from data import to_rust_ident %> <%namespace name="helpers" file="/helpers.mako.rs" /> <% from data import ALL_SIZES, PHYSICAL_SIDES, LOGICAL_SIDES %> -<% data.new_style_struct("Position", inherited=False) %> - // "top" / "left" / "bottom" / "right" % for side in PHYSICAL_SIDES: ${helpers.predefined_type( side, "LengthPercentageOrAuto", "computed::LengthPercentageOrAuto::auto()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://www.w3.org/TR/CSS2/visuren.html#propdef-%s" % side, animation_value_type="ComputedValue", allow_quirks="Yes", @@ -29,7 +26,7 @@ "inset-%s" % side, "LengthPercentageOrAuto", "computed::LengthPercentageOrAuto::auto()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical-props/#propdef-inset-%s" % side, animation_value_type="ComputedValue", logical=True, @@ -42,7 +39,7 @@ ${helpers.predefined_type( "z-index", "ZIndex", "computed::ZIndex::auto()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://www.w3.org/TR/CSS2/visuren.html#z-index", animation_value_type="ComputedValue", affects="paint", @@ -55,8 +52,8 @@ ${helpers.predefined_type( ${helpers.single_keyword( "flex-direction", "row row-reverse column column-reverse", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", spec="https://drafts.csswg.org/css-flexbox/#flex-direction-property", extra_prefixes="webkit", animation_value_type="discrete", @@ -68,8 +65,8 @@ ${helpers.single_keyword( ${helpers.single_keyword( "flex-wrap", "nowrap wrap wrap-reverse", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", spec="https://drafts.csswg.org/css-flexbox/#flex-wrap-property", extra_prefixes="webkit", animation_value_type="discrete", @@ -78,12 +75,13 @@ ${helpers.single_keyword( affects="layout", )} -% if engine == "servo-2013": +% if engine in "servo": // FIXME: Update Servo to support the same Syntax as Gecko. ${helpers.single_keyword( "justify-content", "flex-start stretch flex-end center space-between space-around", - engines="servo-2013", + engines="servo", + servo_pref="layout.flexbox.enabled", extra_prefixes="webkit", spec="https://drafts.csswg.org/css-align/#propdef-justify-content", animation_value_type="discrete", @@ -117,12 +115,13 @@ ${helpers.single_keyword( )} % endif -% if engine in ["servo-2013", "servo-2020"]: +% if engine == "servo": // FIXME: Update Servo to support the same Syntax as Gecko. ${helpers.single_keyword( "align-content", "stretch flex-start flex-end center space-between space-around", - engines="servo-2013", + engines="servo", + servo_pref="layout.flexbox.enabled", extra_prefixes="webkit", spec="https://drafts.csswg.org/css-align/#propdef-align-content", animation_value_type="discrete", @@ -133,8 +132,8 @@ ${helpers.single_keyword( ${helpers.single_keyword( "align-items", "stretch flex-start flex-end center baseline", - engines="servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="servo", + servo_pref="layout.flexbox.enabled", extra_prefixes="webkit", spec="https://drafts.csswg.org/css-flexbox/#align-items-property", animation_value_type="discrete", @@ -195,8 +194,8 @@ ${helpers.predefined_type( "flex-grow", "NonNegativeNumber", "From::from(0.0)", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", spec="https://drafts.csswg.org/css-flexbox/#flex-grow-property", extra_prefixes="webkit", animation_value_type="NonNegativeNumber", @@ -208,8 +207,8 @@ ${helpers.predefined_type( "flex-shrink", "NonNegativeNumber", "From::from(1.0)", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", spec="https://drafts.csswg.org/css-flexbox/#flex-shrink-property", extra_prefixes="webkit", animation_value_type="NonNegativeNumber", @@ -218,13 +217,13 @@ ${helpers.predefined_type( )} // https://drafts.csswg.org/css-align/#align-self-property -% if engine in ["servo-2013", "servo-2020"]: +% if engine == "servo": // FIXME: Update Servo to support the same syntax as Gecko. ${helpers.single_keyword( "align-self", "auto stretch flex-start flex-end center baseline", - engines="servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="servo", + servo_pref="layout.flexbox.enabled", extra_prefixes="webkit", spec="https://drafts.csswg.org/css-flexbox/#propdef-align-self", animation_value_type="discrete", @@ -260,8 +259,8 @@ ${helpers.predefined_type( "order", "Integer", "0", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", extra_prefixes="webkit", animation_value_type="ComputedValue", spec="https://drafts.csswg.org/css-flexbox/#order-property", @@ -273,8 +272,8 @@ ${helpers.predefined_type( "flex-basis", "FlexBasis", "computed::FlexBasis::auto()", - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property", extra_prefixes="webkit", animation_value_type="FlexBasis", @@ -294,7 +293,7 @@ ${helpers.predefined_type( size, "Size", "computed::Size::auto()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", logical=logical, logical_group="size", allow_quirks="No" if logical else "Yes", @@ -308,7 +307,7 @@ ${helpers.predefined_type( "min-%s" % size, "Size", "computed::Size::auto()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", logical=logical, logical_group="min-size", allow_quirks="No" if logical else "Yes", @@ -321,7 +320,7 @@ ${helpers.predefined_type( "max-%s" % size, "MaxSize", "computed::MaxSize::none()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", logical=logical, logical_group="max-size", allow_quirks="No" if logical else "Yes", @@ -335,7 +334,7 @@ ${helpers.predefined_type( ${helpers.single_keyword( "box-sizing", "content-box border-box", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", extra_prefixes="moz:layout.css.prefixes.box-sizing webkit", spec="https://drafts.csswg.org/css-ui/#propdef-box-sizing", gecko_enum_prefix="StyleBoxSizing", @@ -436,9 +435,9 @@ ${helpers.predefined_type( "column-gap", "length::NonNegativeLengthPercentageOrNormal", "computed::length::NonNegativeLengthPercentageOrNormal::normal()", - engines="gecko servo-2013", + engines="gecko servo", aliases="grid-column-gap" if engine == "gecko" else "", - servo_2013_pref="layout.columns.enabled", + servo_pref="layout.columns.enabled", spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap", animation_value_type="NonNegativeLengthPercentageOrNormal", servo_restyle_damage="reflow", @@ -462,7 +461,8 @@ ${helpers.predefined_type( "aspect-ratio", "AspectRatio", "computed::AspectRatio::auto()", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="ComputedValue", spec="https://drafts.csswg.org/css-sizing-4/#aspect-ratio", servo_restyle_damage="reflow", diff --git a/servo/components/style/properties/longhands/svg.mako.rs b/servo/components/style/properties/longhands/svg.mako.rs index 10788d4802..12998d553b 100644 --- a/servo/components/style/properties/longhands/svg.mako.rs +++ b/servo/components/style/properties/longhands/svg.mako.rs @@ -4,8 +4,6 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("SVG", inherited=False, gecko_name="SVGReset") %> - ${helpers.single_keyword( "vector-effect", "none non-scaling-stroke", diff --git a/servo/components/style/properties/longhands/table.mako.rs b/servo/components/style/properties/longhands/table.mako.rs index 3a756636ad..059838bac1 100644 --- a/servo/components/style/properties/longhands/table.mako.rs +++ b/servo/components/style/properties/longhands/table.mako.rs @@ -4,12 +4,11 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("Table", inherited=False) %> - ${helpers.single_keyword( "table-layout", "auto fixed", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", gecko_ffi_name="mLayoutStrategy", animation_value_type="discrete", gecko_enum_prefix="StyleTableLayout", diff --git a/servo/components/style/properties/longhands/text.mako.rs b/servo/components/style/properties/longhands/text.mako.rs index 0ee8ba3168..68b8ff3ceb 100644 --- a/servo/components/style/properties/longhands/text.mako.rs +++ b/servo/components/style/properties/longhands/text.mako.rs @@ -3,15 +3,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method %> - -<% data.new_style_struct("Text", inherited=False, gecko_name="TextReset") %> ${helpers.predefined_type( "text-overflow", "TextOverflow", "computed::TextOverflow::get_initial_value()", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", animation_value_type="discrete", boxed=True, spec="https://drafts.csswg.org/css-ui/#propdef-text-overflow", @@ -22,7 +20,8 @@ ${helpers.predefined_type( ${helpers.single_keyword( "unicode-bidi", "normal embed isolate bidi-override isolate-override plaintext", - engines="gecko servo-2013", + engines="gecko servo", + servo_pref="layout.legacy_layout", gecko_enum_prefix="StyleUnicodeBidi", animation_value_type="none", spec="https://drafts.csswg.org/css-writing-modes/#propdef-unicode-bidi", @@ -34,7 +33,7 @@ ${helpers.predefined_type( "text-decoration-line", "TextDecorationLine", "specified::TextDecorationLine::none()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::TextDecorationLine::none()", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-line", @@ -45,7 +44,7 @@ ${helpers.predefined_type( ${helpers.single_keyword( "text-decoration-style", "solid double dotted dashed wavy -moz-none", - engines="gecko servo-2020", + engines="gecko servo", gecko_enum_prefix="StyleTextDecorationStyle", animation_value_type="discrete", spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style", @@ -56,7 +55,7 @@ ${helpers.predefined_type( "text-decoration-color", "Color", "computed_value::T::currentcolor()", - engines="gecko servo-2020", + engines="gecko servo", initial_specified_value="specified::Color::currentcolor()", animation_value_type="AnimatedColor", ignored_when_colors_disabled=True, diff --git a/servo/components/style/properties/longhands/ui.mako.rs b/servo/components/style/properties/longhands/ui.mako.rs index 58006e0d65..762cb64e9a 100644 --- a/servo/components/style/properties/longhands/ui.mako.rs +++ b/servo/components/style/properties/longhands/ui.mako.rs @@ -3,11 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %> - -// CSS Basic User Interface Module Level 1 -// https://drafts.csswg.org/css-ui-3/ -<% data.new_style_struct("UI", inherited=False, gecko_name="UIReset") %> +<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME %> // TODO spec says that UAs should not support this // we should probably remove from gecko (https://bugzilla.mozilla.org/show_bug.cgi?id=1328331) @@ -145,7 +141,7 @@ ${helpers.predefined_type( "transition-duration", "Time", "computed::Time::zero()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::Time::zero()", parse_method="parse_non_negative", vector=True, @@ -160,7 +156,7 @@ ${helpers.predefined_type( "transition-timing-function", "TimingFunction", "computed::TimingFunction::ease()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::TimingFunction::ease()", vector=True, need_index=True, @@ -174,7 +170,7 @@ ${helpers.predefined_type( "transition-property", "TransitionProperty", "computed::TransitionProperty::all()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::TransitionProperty::all()", vector=True, none_value="computed::TransitionProperty::none()", @@ -189,7 +185,7 @@ ${helpers.predefined_type( "transition-delay", "Time", "computed::Time::zero()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::Time::zero()", vector=True, need_index=True, @@ -219,7 +215,7 @@ ${helpers.predefined_type( "animation-name", "AnimationName", "computed::AnimationName::none()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::AnimationName::none()", vector=True, need_index=True, @@ -234,7 +230,7 @@ ${helpers.predefined_type( "animation-duration", "Time", "computed::Time::zero()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::Time::zero()", parse_method="parse_non_negative", vector=True, @@ -251,7 +247,7 @@ ${helpers.predefined_type( "animation-timing-function", "TimingFunction", "computed::TimingFunction::ease()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::TimingFunction::ease()", vector=True, need_index=True, @@ -265,7 +261,7 @@ ${helpers.predefined_type( "animation-iteration-count", "AnimationIterationCount", "computed::AnimationIterationCount::one()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::AnimationIterationCount::one()", vector=True, need_index=True, @@ -280,7 +276,7 @@ ${helpers.predefined_type( "animation-direction", "AnimationDirection", "computed::AnimationDirection::Normal", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::AnimationDirection::Normal", vector=True, need_index=True, @@ -295,7 +291,7 @@ ${helpers.predefined_type( "animation-play-state", "AnimationPlayState", "computed::AnimationPlayState::Running", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="computed::AnimationPlayState::Running", vector=True, need_index=True, @@ -310,7 +306,7 @@ ${helpers.predefined_type( "animation-fill-mode", "AnimationFillMode", "computed::AnimationFillMode::None", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="computed::AnimationFillMode::None", vector=True, need_index=True, @@ -325,12 +321,13 @@ ${helpers.predefined_type( "animation-composition", "AnimationComposition", "computed::AnimationComposition::Replace", - engines="gecko", + engines="gecko servo", initial_specified_value="computed::AnimationComposition::Replace", vector=True, need_index=True, animation_value_type="none", gecko_pref="layout.css.animation-composition.enabled", + servo_pref="layout.unimplemented", spec="https://drafts.csswg.org/css-animations-2/#animation-composition", affects="", )} @@ -339,7 +336,7 @@ ${helpers.predefined_type( "animation-delay", "Time", "computed::Time::zero()", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", initial_specified_value="specified::Time::zero()", vector=True, need_index=True, @@ -354,7 +351,8 @@ ${helpers.predefined_type( "animation-timeline", "AnimationTimeline", "computed::AnimationTimeline::auto()", - engines="gecko", + engines="gecko servo", + servo_pref="layout.unimplemented", initial_specified_value="specified::AnimationTimeline::auto()", vector=True, need_index=True, diff --git a/servo/components/style/properties/longhands/xul.mako.rs b/servo/components/style/properties/longhands/xul.mako.rs index 8974ac30dc..f9de8eb239 100644 --- a/servo/components/style/properties/longhands/xul.mako.rs +++ b/servo/components/style/properties/longhands/xul.mako.rs @@ -3,10 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method %> - -// Non-standard properties that Gecko uses for XUL elements. -<% data.new_style_struct("XUL", inherited=False) %> ${helpers.single_keyword( "-moz-box-align", diff --git a/servo/components/style/properties/mod.rs b/servo/components/style/properties/mod.rs index 7adb6d4ae6..4a08d67f77 100644 --- a/servo/components/style/properties/mod.rs +++ b/servo/components/style/properties/mod.rs @@ -17,12 +17,6 @@ pub use self::generated::*; #[deny(missing_docs)] pub mod generated { include!(concat!(env!("OUT_DIR"), "/properties.rs")); - - #[cfg(feature = "gecko")] - #[allow(unsafe_code, missing_docs)] - pub mod gecko { - include!(concat!(env!("OUT_DIR"), "/gecko_properties.rs")); - } } use crate::custom_properties::{self, ComputedCustomProperties}; @@ -1040,7 +1034,7 @@ impl<'a> PropertyDeclarationId<'a> { pub fn is_discrete_animatable(&self) -> bool { match self { Self::Longhand(longhand) => longhand.is_discrete_animatable(), - // TODO(bug 1846516): Refine this? + // TODO(bug 1885995): Refine this. Self::Custom(_) => true, } } diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs index b08314d7d5..e438967c35 100644 --- a/servo/components/style/properties/properties.mako.rs +++ b/servo/components/style/properties/properties.mako.rs @@ -87,10 +87,19 @@ macro_rules! expanded { #[allow(missing_docs)] pub mod longhands { % for style_struct in data.style_structs: - include!("${repr(os.path.join(OUT_DIR, 'longhands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); + <% data.current_style_struct = style_struct %> + <%include file="/longhands/${style_struct.name_lower}.mako.rs" /> % endfor } + +#[cfg(feature = "gecko")] +#[allow(unsafe_code, missing_docs)] +pub mod gecko { + <%include file="/gecko.mako.rs" /> +} + + macro_rules! unwrap_or_initial { ($prop: ident) => (unwrap_or_initial!($prop, $prop)); ($prop: ident, $expr: expr) => @@ -107,7 +116,7 @@ pub mod shorthands { use crate::values::specified; % for style_struct in data.style_structs: - include!("${repr(os.path.join(OUT_DIR, 'shorthands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); + <%include file="/shorthands/${style_struct.name_lower}.mako.rs" /> % endfor // We didn't define the 'all' shorthand using the regular helpers:shorthand @@ -142,7 +151,7 @@ pub mod shorthands { data.declare_shorthand( "all", logical_longhands + other_longhands, - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-cascade-3/#all-shorthand" ) ALL_SHORTHAND_LEN = len(logical_longhands) + len(other_longhands); @@ -505,8 +514,7 @@ impl NonCustomPropertyId { }] = [ % for property in data.longhands + data.shorthands + data.all_aliases(): <% - attrs = {"servo-2013": "servo_2013_pref", "servo-2020": "servo_2020_pref"} - pref = getattr(property, attrs[engine]) + pref = getattr(property, "servo_pref") %> % if pref: Some("${pref}"), @@ -2482,7 +2490,7 @@ impl<'a> StyleBuilder<'a> { } % endif - % if not property.is_vector or property.simple_vector_bindings or engine in ["servo-2013", "servo-2020"]: + % if not property.is_vector or property.simple_vector_bindings or engine == "servo": /// Set the `${property.ident}` to the computed value `value`. #[allow(non_snake_case)] pub fn set_${property.ident}( @@ -2736,7 +2744,13 @@ impl<'a> StyleBuilder<'a> { if matches!(line_height, computed::LineHeight::Normal) { self.add_flags(flag); } - device.calc_line_height(&font, writing_mode, None) + let lh = device.calc_line_height(&font, writing_mode, None); + if line_height_base == LineHeightBase::InheritedStyle { + // Apply our own zoom if our style source is the parent style. + computed::NonNegativeLength::new(self.get_box().clone_zoom().zoom(lh.px())) + } else { + lh + } } /// And access to inherited style structs. @@ -2932,7 +2946,7 @@ const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>( % endif % endfor -% if engine in ["servo-2013", "servo-2020"]: +% if engine == "servo": % for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]: macro_rules! restyle_damage_${effect_name} { ($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ]) => ({ diff --git a/servo/components/style/properties/shorthands/background.mako.rs b/servo/components/style/properties/shorthands/background.mako.rs index 08838233f6..5fee5cb2b0 100644 --- a/servo/components/style/properties/shorthands/background.mako.rs +++ b/servo/components/style/properties/shorthands/background.mako.rs @@ -6,7 +6,7 @@ // TODO: other background-* properties <%helpers:shorthand name="background" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties="background-color background-position-x background-position-y background-repeat background-attachment background-image background-size background-origin background-clip" @@ -230,7 +230,7 @@ </%helpers:shorthand> <%helpers:shorthand name="background-position" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" flags="SHORTHAND_IN_GETCS" sub_properties="background-position-x background-position-y" spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position"> diff --git a/servo/components/style/properties/shorthands/border.mako.rs b/servo/components/style/properties/shorthands/border.mako.rs index c6a87f3197..2eeb691e24 100644 --- a/servo/components/style/properties/shorthands/border.mako.rs +++ b/servo/components/style/properties/shorthands/border.mako.rs @@ -9,7 +9,7 @@ ${helpers.four_sides_shorthand( "border-color", "border-%s-color", "specified::Color::parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-backgrounds/#border-color", allow_quirks="Yes", )} @@ -17,13 +17,13 @@ ${helpers.four_sides_shorthand( ${helpers.four_sides_shorthand( "border-style", "border-%s-style", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-backgrounds/#border-style", )} <%helpers:shorthand name="border-width" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties="${ ' '.join('border-%s-width' % side for side in PHYSICAL_SIDES)}" @@ -104,7 +104,7 @@ pub fn parse_border<'i, 't>( %> <%helpers:shorthand name="border-${side}" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties="${' '.join( 'border-%s-%s' % (side, prop) for prop in ['width', 'style', 'color'] @@ -139,7 +139,7 @@ pub fn parse_border<'i, 't>( % endfor <%helpers:shorthand name="border" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties="${' '.join('border-%s-%s' % (side, prop) for side in PHYSICAL_SIDES for prop in ['width', 'style', 'color'] )} @@ -232,7 +232,7 @@ pub fn parse_border<'i, 't>( <%helpers:shorthand name="border-radius" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties="${' '.join( 'border-%s-radius' % (corner) for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left'] @@ -278,7 +278,8 @@ pub fn parse_border<'i, 't>( <%helpers:shorthand name="border-image" - engines="gecko servo-2013" + engines="gecko servo" + servo_pref="layout.legacy_layout", sub_properties="border-image-outset border-image-repeat border-image-slice border-image-source border-image-width" extra_prefixes="moz:layout.css.prefixes.border-image webkit" @@ -402,7 +403,7 @@ pub fn parse_border<'i, 't>( spec = "https://drafts.csswg.org/css-logical/#propdef-border-%s-%s" % (axis, prop) %> <%helpers:shorthand - engines="gecko servo-2013 servo-2020" + engines="gecko servo" name="border-${axis}-${prop}" sub_properties="${' '.join( 'border-%s-%s-%s' % (axis, side, prop) @@ -448,7 +449,7 @@ pub fn parse_border<'i, 't>( %> <%helpers:shorthand name="border-${axis}" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties="${' '.join( 'border-%s-%s-width' % (axis, side) for side in ['start', 'end'] diff --git a/servo/components/style/properties/shorthands/box.mako.rs b/servo/components/style/properties/shorthands/box.mako.rs index f644687dc0..078a511ae9 100644 --- a/servo/components/style/properties/shorthands/box.mako.rs +++ b/servo/components/style/properties/shorthands/box.mako.rs @@ -8,7 +8,7 @@ ${helpers.two_properties_shorthand( "overflow", "overflow-x", "overflow-y", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", flags="SHORTHAND_IN_GETCS", spec="https://drafts.csswg.org/css-overflow/#propdef-overflow", )} @@ -159,13 +159,7 @@ ${helpers.two_properties_shorthand( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Longhands, ParseError<'i>> { - let offset_position = - if static_prefs::pref!("layout.css.motion-path-offset-position.enabled") { - input.try_parse(|i| OffsetPosition::parse(context, i)).ok() - } else { - None - }; - + let offset_position = input.try_parse(|i| OffsetPosition::parse(context, i)).ok(); let offset_path = input.try_parse(|i| OffsetPath::parse(context, i)).ok(); // Must have one of [offset-position, offset-path]. @@ -212,24 +206,19 @@ ${helpers.two_properties_shorthand( impl<'a> ToCss for LonghandsToSerialize<'a> { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if let Some(offset_position) = self.offset_position { - // The basic concept is: we must serialize offset-position or offset-path group. - // offset-path group means "offset-path offset-distance offset-rotate". - let must_serialize_path = *self.offset_path != OffsetPath::None - || (!self.offset_distance.is_zero() || !self.offset_rotate.is_auto()); - let position_is_default = matches!(offset_position, OffsetPosition::Normal); - if !position_is_default || !must_serialize_path { - offset_position.to_css(dest)?; - } + // The basic concept is: we must serialize offset-position or offset-path group. + // offset-path group means "offset-path offset-distance offset-rotate". + let must_serialize_path = *self.offset_path != OffsetPath::None + || (!self.offset_distance.is_zero() || !self.offset_rotate.is_auto()); + let position_is_default = matches!(self.offset_position, OffsetPosition::Normal); + if !position_is_default || !must_serialize_path { + self.offset_position.to_css(dest)?; + } - if must_serialize_path { - if !position_is_default { - dest.write_char(' ')?; - } - self.offset_path.to_css(dest)?; + if must_serialize_path { + if !position_is_default { + dest.write_char(' ')?; } - } else { - // If the pref is off, we always show offset-path. self.offset_path.to_css(dest)?; } diff --git a/servo/components/style/properties/shorthands/column.mako.rs b/servo/components/style/properties/shorthands/column.mako.rs index 3740775a7e..4cf9a8d786 100644 --- a/servo/components/style/properties/shorthands/column.mako.rs +++ b/servo/components/style/properties/shorthands/column.mako.rs @@ -5,9 +5,9 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <%helpers:shorthand name="columns" - engines="gecko servo-2013" + engines="gecko servo" sub_properties="column-width column-count" - servo_2013_pref="layout.columns.enabled", + servo_pref="layout.columns.enabled" spec="https://drafts.csswg.org/css-multicol/#propdef-columns"> use crate::properties::longhands::{column_count, column_width}; diff --git a/servo/components/style/properties/shorthands/counters.mako.rs b/servo/components/style/properties/shorthands/counters.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/counters.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/properties/shorthands/effects.mako.rs b/servo/components/style/properties/shorthands/effects.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/effects.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/properties/shorthands/font.mako.rs b/servo/components/style/properties/shorthands/font.mako.rs index 17dcf9d926..aaf0895aa5 100644 --- a/servo/components/style/properties/shorthands/font.mako.rs +++ b/servo/components/style/properties/shorthands/font.mako.rs @@ -7,7 +7,7 @@ <%helpers:shorthand name="font" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties=" font-style font-variant-caps @@ -321,7 +321,8 @@ </%helpers:shorthand> <%helpers:shorthand name="font-variant" - engines="gecko servo-2013" + engines="gecko servo" + servo_pref="layout.legacy_layout", flags="SHORTHAND_IN_GETCS" sub_properties="font-variant-caps ${'font-variant-alternates' if engine == 'gecko' else ''} diff --git a/servo/components/style/properties/shorthands/inherited_box.mako.rs b/servo/components/style/properties/shorthands/inherited_box.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/inherited_box.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/properties/shorthands/inherited_table.mako.rs b/servo/components/style/properties/shorthands/inherited_table.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/inherited_table.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/properties/shorthands/inherited_ui.mako.rs b/servo/components/style/properties/shorthands/inherited_ui.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/inherited_ui.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/properties/shorthands/list.mako.rs b/servo/components/style/properties/shorthands/list.mako.rs index 2e234e3d8f..183c5ab5da 100644 --- a/servo/components/style/properties/shorthands/list.mako.rs +++ b/servo/components/style/properties/shorthands/list.mako.rs @@ -5,7 +5,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <%helpers:shorthand name="list-style" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" sub_properties="list-style-position list-style-image list-style-type" spec="https://drafts.csswg.org/css-lists/#propdef-list-style"> use crate::properties::longhands::{list_style_image, list_style_position, list_style_type}; @@ -110,7 +110,11 @@ use longhands::list_style_type::SpecifiedValue as ListStyleType; use longhands::list_style_image::SpecifiedValue as ListStyleImage; let mut have_one_non_initial_value = false; - if self.list_style_position != &ListStylePosition::Outside { + #[cfg(feature = "gecko")] + let position_is_initial = self.list_style_position == &ListStylePosition::Outside; + #[cfg(feature = "servo")] + let position_is_initial = matches!(self.list_style_position, None | Some(&ListStylePosition::Outside)); + if !position_is_initial { self.list_style_position.to_css(dest)?; have_one_non_initial_value = true; } @@ -121,7 +125,11 @@ self.list_style_image.to_css(dest)?; have_one_non_initial_value = true; } - if self.list_style_type != &ListStyleType::disc() { + #[cfg(feature = "gecko")] + let type_is_initial = self.list_style_type == &ListStyleType::disc(); + #[cfg(feature = "servo")] + let type_is_initial = self.list_style_type == &ListStyleType::Disc; + if !type_is_initial { if have_one_non_initial_value { dest.write_char(' ')?; } @@ -129,7 +137,14 @@ have_one_non_initial_value = true; } if !have_one_non_initial_value { + #[cfg(feature = "gecko")] self.list_style_position.to_css(dest)?; + #[cfg(feature = "servo")] + if let Some(position) = self.list_style_position { + position.to_css(dest)?; + } else { + self.list_style_type.to_css(dest)?; + } } Ok(()) } diff --git a/servo/components/style/properties/shorthands/margin.mako.rs b/servo/components/style/properties/shorthands/margin.mako.rs index 6b5bf7e467..ba994316a2 100644 --- a/servo/components/style/properties/shorthands/margin.mako.rs +++ b/servo/components/style/properties/shorthands/margin.mako.rs @@ -9,7 +9,7 @@ ${helpers.four_sides_shorthand( "margin", "margin-%s", "specified::LengthPercentageOrAuto::parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-box/#propdef-margin", rule_types_allowed=DEFAULT_RULES_AND_PAGE, allow_quirks="Yes", @@ -20,7 +20,7 @@ ${helpers.two_properties_shorthand( "margin-block-start", "margin-block-end", "specified::LengthPercentageOrAuto::parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical/#propdef-margin-block" )} @@ -29,7 +29,7 @@ ${helpers.two_properties_shorthand( "margin-inline-start", "margin-inline-end", "specified::LengthPercentageOrAuto::parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical/#propdef-margin-inline" )} diff --git a/servo/components/style/properties/shorthands/outline.mako.rs b/servo/components/style/properties/shorthands/outline.mako.rs index 6ee8ed22c9..ff77e1175e 100644 --- a/servo/components/style/properties/shorthands/outline.mako.rs +++ b/servo/components/style/properties/shorthands/outline.mako.rs @@ -5,7 +5,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <%helpers:shorthand name="outline" - engines="gecko servo-2013" + engines="gecko servo" sub_properties="outline-color outline-style outline-width" spec="https://drafts.csswg.org/css-ui/#propdef-outline"> use crate::properties::longhands::{outline_color, outline_width, outline_style}; diff --git a/servo/components/style/properties/shorthands/padding.mako.rs b/servo/components/style/properties/shorthands/padding.mako.rs index 11ddfed3b1..dad0193708 100644 --- a/servo/components/style/properties/shorthands/padding.mako.rs +++ b/servo/components/style/properties/shorthands/padding.mako.rs @@ -8,7 +8,7 @@ ${helpers.four_sides_shorthand( "padding", "padding-%s", "specified::NonNegativeLengthPercentage::parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-box-3/#propdef-padding", allow_quirks="Yes", )} @@ -18,7 +18,7 @@ ${helpers.two_properties_shorthand( "padding-block-start", "padding-block-end", "specified::NonNegativeLengthPercentage::parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical/#propdef-padding-block" )} @@ -27,7 +27,7 @@ ${helpers.two_properties_shorthand( "padding-inline-start", "padding-inline-end", "specified::NonNegativeLengthPercentage::parse", - engines="gecko servo-2013 servo-2020", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical/#propdef-padding-inline" )} diff --git a/servo/components/style/properties/shorthands/page.mako.rs b/servo/components/style/properties/shorthands/page.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/page.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/properties/shorthands/position.mako.rs b/servo/components/style/properties/shorthands/position.mako.rs index ed7df5e27a..ac09bb27d6 100644 --- a/servo/components/style/properties/shorthands/position.mako.rs +++ b/servo/components/style/properties/shorthands/position.mako.rs @@ -5,8 +5,8 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <%helpers:shorthand name="flex-flow" - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", sub_properties="flex-direction flex-wrap" extra_prefixes="webkit" spec="https://drafts.csswg.org/css-flexbox/#flex-flow-property"> @@ -60,8 +60,8 @@ </%helpers:shorthand> <%helpers:shorthand name="flex" - engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.flexbox.enabled", + engines="gecko servo", + servo_pref="layout.flexbox.enabled", sub_properties="flex-grow flex-shrink flex-basis" extra_prefixes="webkit" derive_serialize="True" @@ -858,7 +858,7 @@ ${helpers.four_sides_shorthand( "inset", "%s", "specified::LengthPercentageOrAuto::parse", - engines="gecko servo-2013", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical/#propdef-inset", allow_quirks="No", )} @@ -868,7 +868,7 @@ ${helpers.two_properties_shorthand( "inset-block-start", "inset-block-end", "specified::LengthPercentageOrAuto::parse", - engines="gecko servo-2013", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical/#propdef-inset-block" )} @@ -877,7 +877,7 @@ ${helpers.two_properties_shorthand( "inset-inline-start", "inset-inline-end", "specified::LengthPercentageOrAuto::parse", - engines="gecko servo-2013", + engines="gecko servo", spec="https://drafts.csswg.org/css-logical/#propdef-inset-inline" )} diff --git a/servo/components/style/properties/shorthands/table.mako.rs b/servo/components/style/properties/shorthands/table.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/table.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/properties/shorthands/text.mako.rs b/servo/components/style/properties/shorthands/text.mako.rs index 5b071be2c4..a913f81875 100644 --- a/servo/components/style/properties/shorthands/text.mako.rs +++ b/servo/components/style/properties/shorthands/text.mako.rs @@ -5,7 +5,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <%helpers:shorthand name="text-decoration" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" flags="SHORTHAND_IN_GETCS" sub_properties="text-decoration-line ${' text-decoration-style text-decoration-color text-decoration-thickness' if engine == 'gecko' else ''}" diff --git a/servo/components/style/properties/shorthands/ui.mako.rs b/servo/components/style/properties/shorthands/ui.mako.rs index 4a27d5e003..1915c65a63 100644 --- a/servo/components/style/properties/shorthands/ui.mako.rs +++ b/servo/components/style/properties/shorthands/ui.mako.rs @@ -18,7 +18,7 @@ macro_rules! try_parse_one { } <%helpers:shorthand name="transition" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" extra_prefixes="moz:layout.css.prefixes.transitions webkit" sub_properties="transition-property transition-duration transition-timing-function @@ -204,7 +204,7 @@ macro_rules! try_parse_one { </%helpers:shorthand> <%helpers:shorthand name="animation" - engines="gecko servo-2013 servo-2020" + engines="gecko servo" extra_prefixes="moz:layout.css.prefixes.animations webkit" sub_properties="animation-name animation-duration animation-timing-function animation-delay @@ -295,6 +295,13 @@ macro_rules! try_parse_one { impl<'a> ToCss for LonghandsToSerialize<'a> { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use crate::values::specified::easing::TimingFunction; + use crate::values::specified::{ + AnimationDirection, AnimationFillMode, AnimationPlayState, + }; + use crate::Zero; + use style_traits::values::SequenceWriter; + let len = self.animation_name.0.len(); // There should be at least one declared value if len == 0 { @@ -320,28 +327,83 @@ macro_rules! try_parse_one { dest.write_str(", ")?; } - % for name in props[2:]: - self.animation_${name}.0[i].to_css(dest)?; - dest.write_char(' ')?; + // We follow the order of this syntax: + // <single-animation> = + // <animation-duration> || + // <easing-function> || + // <animation-delay> || + // <single-animation-iteration-count> || + // <single-animation-direction> || + // <single-animation-fill-mode> || + // <single-animation-play-state> || + // [ none | <keyframes-name> ] || + // <single-animation-timeline> + // + // https://drafts.csswg.org/css-animations-2/#animation-shorthand + // + // FIXME: Bug 1804574. The initial value of duration should be auto, per + // css-animations-2. + let has_duration = !self.animation_duration.0[i].is_zero(); + let has_timing_function = !self.animation_timing_function.0[i].is_ease(); + let has_delay = !self.animation_delay.0[i].is_zero(); + let has_iteration_count = !self.animation_iteration_count.0[i].is_one(); + let has_direction = + !matches!(self.animation_direction.0[i], AnimationDirection::Normal); + let has_fill_mode = + !matches!(self.animation_fill_mode.0[i], AnimationFillMode::None); + let has_play_state = + !matches!(self.animation_play_state.0[i], AnimationPlayState::Running); + let animation_name = &self.animation_name.0[i]; + let has_name = !animation_name.is_none(); + let has_timeline = match self.animation_timeline { + Some(timeline) => !timeline.0[i].is_auto(), + _ => false, + }; + + let mut writer = SequenceWriter::new(dest, " "); + + // To avoid ambiguity, we have to serialize duration if both duration is initial + // but delay is not. (In other words, it's ambiguous if we serialize delay only.) + if has_duration || has_delay { + writer.item(&self.animation_duration.0[i])?; + } + + if has_timing_function || TimingFunction::match_keywords(animation_name) { + writer.item(&self.animation_timing_function.0[i])?; + } + + // For animation-delay and animation-iteration-count. + % for name in props[4:6]: + if has_${name} { + writer.item(&self.animation_${name}.0[i])?; + } % endfor - self.animation_name.0[i].to_css(dest)?; + if has_direction || AnimationDirection::match_keywords(animation_name) { + writer.item(&self.animation_direction.0[i])?; + } + + if has_fill_mode || AnimationFillMode::match_keywords(animation_name) { + writer.item(&self.animation_fill_mode.0[i])?; + } - // Based on the spec, the default values of other properties must be output in at - // least the cases necessary to distinguish an animation-name. The serialization - // order of animation-timeline is always later than animation-name, so it's fine - // to not serialize it if it is the default value. It's still possible to - // distinguish them (because we always serialize animation-name). - // https://drafts.csswg.org/css-animations-1/#animation - // https://drafts.csswg.org/css-animations-2/#typedef-single-animation - // - // Note: it's also fine to always serialize this. However, it seems Blink - // doesn't serialize default animation-timeline now, so we follow the same rule. - if let Some(ref timeline) = self.animation_timeline { - if !timeline.0[i].is_auto() { - dest.write_char(' ')?; - timeline.0[i].to_css(dest)?; - } + if has_play_state || AnimationPlayState::match_keywords(animation_name) { + writer.item(&self.animation_play_state.0[i])?; + } + + // If all values are initial, we must serialize animation-name. + let has_any = { + has_timeline + % for name in props[2:]: + || has_${name} + % endfor + }; + if has_name || !has_any { + writer.item(animation_name)?; + } + + if has_timeline { + writer.item(&self.animation_timeline.unwrap().0[i])?; } } Ok(()) diff --git a/servo/components/style/properties/shorthands/xul.mako.rs b/servo/components/style/properties/shorthands/xul.mako.rs new file mode 100644 index 0000000000..daa3e8897c --- /dev/null +++ b/servo/components/style/properties/shorthands/xul.mako.rs @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/servo/components/style/queries/condition.rs b/servo/components/style/queries/condition.rs index e17e6abd2e..25af4cdb01 100644 --- a/servo/components/style/queries/condition.rs +++ b/servo/components/style/queries/condition.rs @@ -13,6 +13,7 @@ use crate::{error_reporting::ContextualParseError, parser::ParserContext}; use cssparser::{Parser, Token}; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use selectors::kleene_value::KleeneValue; /// A binary `and` or `or` operator. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] @@ -29,93 +30,6 @@ enum AllowOr { No, } -/// https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)] -pub enum KleeneValue { - /// False - False = 0, - /// True - True = 1, - /// Either true or false, but we’re not sure which yet. - Unknown, -} - -impl From<bool> 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, - } - } -} - -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; - } -} - /// Represents a condition. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum QueryCondition { diff --git a/servo/components/style/queries/feature.rs b/servo/components/style/queries/feature.rs index 83ff7e7522..2d4c60c362 100644 --- a/servo/components/style/queries/feature.rs +++ b/servo/components/style/queries/feature.rs @@ -4,7 +4,6 @@ //! Query features. -use super::condition::KleeneValue; use crate::parser::ParserContext; use crate::values::computed::{self, CSSPixelLength, Ratio, Resolution}; use crate::values::AtomString; @@ -12,6 +11,7 @@ use crate::Atom; use cssparser::Parser; use std::fmt; use style_traits::ParseError; +use selectors::kleene_value::KleeneValue; /// A generic discriminant for an enum value. pub type KeywordDiscriminant = u8; @@ -87,12 +87,12 @@ macro_rules! keyword_evaluator { fn __evaluate( context: &$crate::values::computed::Context, value: Option<$crate::queries::feature::KeywordDiscriminant>, - ) -> $crate::queries::condition::KleeneValue { + ) -> selectors::kleene_value::KleeneValue { // This unwrap is ok because the only discriminants that get // back to us is the ones that `parse` produces. let value: Option<$keyword_type> = value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap()); - $crate::queries::condition::KleeneValue::from($actual_evaluator(context, value)) + selectors::kleene_value::KleeneValue::from($actual_evaluator(context, value)) } $crate::queries::feature::Evaluator::Enumerated { diff --git a/servo/components/style/queries/feature_expression.rs b/servo/components/style/queries/feature_expression.rs index c0171c2058..3450f85cba 100644 --- a/servo/components/style/queries/feature_expression.rs +++ b/servo/components/style/queries/feature_expression.rs @@ -8,16 +8,16 @@ use super::feature::{Evaluator, QueryFeatureDescription}; use super::feature::{FeatureFlags, KeywordDiscriminant}; use crate::parser::{Parse, ParserContext}; -use crate::queries::condition::KleeneValue; use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use crate::values::computed::{self, Ratio, ToComputedValue}; use crate::values::specified::{Integer, Length, Number, Resolution}; use crate::values::{AtomString, CSSFloat}; use crate::{Atom, Zero}; use cssparser::{Parser, Token}; -use std::cmp::{Ordering, PartialOrd}; +use std::cmp::Ordering; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use selectors::kleene_value::KleeneValue; /// Whether we're parsing a media or container query feature. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] diff --git a/servo/components/style/scoped_tls.rs b/servo/components/style/scoped_tls.rs index 0d3267397a..6fb8730d18 100644 --- a/servo/components/style/scoped_tls.rs +++ b/servo/components/style/scoped_tls.rs @@ -8,7 +8,6 @@ #![deny(missing_docs)] use crate::global_style_data::STYLO_MAX_THREADS; -use rayon; use std::cell::{Ref, RefCell, RefMut}; use std::ops::DerefMut; diff --git a/servo/components/style/str.rs b/servo/components/style/str.rs index 9badcdf413..fe598f546e 100644 --- a/servo/components/style/str.rs +++ b/servo/components/style/str.rs @@ -8,7 +8,6 @@ use num_traits::ToPrimitive; use std::borrow::Cow; -use std::convert::AsRef; use std::iter::{Filter, Peekable}; use std::str::Split; diff --git a/servo/components/style/style_adjuster.rs b/servo/components/style/style_adjuster.rs index a993d79d6a..78a631ec14 100644 --- a/servo/components/style/style_adjuster.rs +++ b/servo/components/style/style_adjuster.rs @@ -294,11 +294,9 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { .add_flags(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE); } - #[cfg(feature = "servo-layout-2013")] - { - if self.style.get_parent_column().is_multicol() { - self.style.add_flags(ComputedValueFlags::CAN_BE_FRAGMENTED); - } + #[cfg(feature = "servo")] + if self.style.get_parent_column().is_multicol() { + self.style.add_flags(ComputedValueFlags::CAN_BE_FRAGMENTED); } } diff --git a/servo/components/style/stylesheets/container_rule.rs b/servo/components/style/stylesheets/container_rule.rs index 28c387fde2..77dfa0e179 100644 --- a/servo/components/style/stylesheets/container_rule.rs +++ b/servo/components/style/stylesheets/container_rule.rs @@ -11,7 +11,6 @@ use crate::dom::TElement; use crate::logical_geometry::{LogicalSize, WritingMode}; use crate::parser::ParserContext; use crate::properties::ComputedValues; -use crate::queries::condition::KleeneValue; use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; use crate::queries::values::Orientation; use crate::queries::{FeatureType, QueryCondition}; @@ -31,6 +30,7 @@ use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use servo_arc::Arc; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; +use selectors::kleene_value::KleeneValue; /// A container rule. #[derive(Debug, ToShmem)] diff --git a/servo/components/style/stylesheets/import_rule.rs b/servo/components/style/stylesheets/import_rule.rs index e96134b436..d539471590 100644 --- a/servo/components/style/stylesheets/import_rule.rs +++ b/servo/components/style/stylesheets/import_rule.rs @@ -20,7 +20,7 @@ use crate::values::CssUrl; use cssparser::{Parser, SourceLocation}; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; +use to_shmem::{SharedMemoryBuilder, ToShmem}; /// A sheet that is held from an import rule. #[cfg(feature = "gecko")] diff --git a/servo/components/style/stylesheets/mod.rs b/servo/components/style/stylesheets/mod.rs index 2bf75565de..6bb8e19fb4 100644 --- a/servo/components/style/stylesheets/mod.rs +++ b/servo/components/style/stylesheets/mod.rs @@ -23,15 +23,17 @@ mod property_rule; mod rule_list; mod rule_parser; mod rules_iterator; +mod starting_style_rule; mod style_rule; mod stylesheet; pub mod supports_rule; +mod scope_rule; #[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::refptr::RefCounted; #[cfg(feature = "gecko")] use crate::gecko_bindings::{bindings, structs}; -use crate::parser::ParserContext; +use crate::parser::{NestingContext, ParserContext}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; @@ -45,7 +47,7 @@ use std::fmt; use std::mem::{self, ManuallyDrop}; use style_traits::ParsingMode; #[cfg(feature = "gecko")] -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; +use to_shmem::{SharedMemoryBuilder, ToShmem}; pub use self::container_rule::ContainerRule; pub use self::counter_style_rule::CounterStyleRule; @@ -69,7 +71,9 @@ pub use self::rules_iterator::{AllRules, EffectiveRules}; pub use self::rules_iterator::{ EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator, }; +pub use self::starting_style_rule::StartingStyleRule; pub use self::style_rule::StyleRule; +pub use self::scope_rule::ScopeRule; pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind}; pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; @@ -132,7 +136,11 @@ impl Drop for UrlExtraData { impl ToShmem for UrlExtraData { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { if self.0 & 1 == 0 { - let shared_extra_datas = unsafe { &structs::URLExtraData_sShared }; + let shared_extra_datas = unsafe { + std::ptr::addr_of!(structs::URLExtraData_sShared) + .as_ref() + .unwrap() + }; let self_ptr = self.as_ref() as *const _ as *mut _; let sheet_id = shared_extra_datas .iter() @@ -265,6 +273,8 @@ pub enum CssRule { Document(Arc<DocumentRule>), LayerBlock(Arc<LayerBlockRule>), LayerStatement(Arc<LayerStatementRule>), + Scope(Arc<ScopeRule>), + StartingStyle(Arc<StartingStyleRule>), } impl CssRule { @@ -309,8 +319,14 @@ impl CssRule { CssRule::Document(ref arc) => { arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) }, + CssRule::StartingStyle(ref arc) => { + arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) + } // TODO(emilio): Add memory reporting for these rules. CssRule::LayerBlock(_) | CssRule::LayerStatement(_) => 0, + CssRule::Scope(ref rule) => { + rule.unconditional_shallow_size_of(ops) + rule.size_of(guard, ops) + } } } } @@ -349,6 +365,9 @@ pub enum CssRuleType { FontPaletteValues = 19, // 20 is an arbitrary number to use for Property. Property = 20, + Scope = 21, + // https://drafts.csswg.org/css-transitions-2/#the-cssstartingstylerule-interface + StartingStyle = 22, } impl CssRuleType { @@ -436,6 +455,8 @@ impl CssRule { CssRule::LayerBlock(_) => CssRuleType::LayerBlock, CssRule::LayerStatement(_) => CssRuleType::LayerStatement, CssRule::Container(_) => CssRuleType::Container, + CssRule::Scope(_) => CssRuleType::Scope, + CssRule::StartingStyle(_) => CssRuleType::StartingStyle, } } @@ -464,7 +485,11 @@ impl CssRule { None, None, ); - context.rule_types = insert_rule_context.containing_rule_types; + // Override the nesting context with existing data. + context.nesting_context = NestingContext::new( + insert_rule_context.containing_rule_types, + insert_rule_context.parse_relative_rule_type + ); let state = if !insert_rule_context.containing_rule_types.is_empty() { State::Body @@ -567,6 +592,12 @@ impl DeepCloneWithLock for CssRule { CssRule::LayerBlock(ref arc) => { CssRule::LayerBlock(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) }, + CssRule::Scope(ref arc) => { + CssRule::Scope(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) + }, + CssRule::StartingStyle(ref arc) => { + CssRule::StartingStyle(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) + }, } } } @@ -592,6 +623,8 @@ impl ToCssWithGuard for CssRule { CssRule::LayerBlock(ref rule) => rule.to_css(guard, dest), CssRule::LayerStatement(ref rule) => rule.to_css(guard, dest), CssRule::Container(ref rule) => rule.to_css(guard, dest), + CssRule::Scope(ref rule) => rule.to_css(guard, dest), + CssRule::StartingStyle(ref rule) => rule.to_css(guard, dest), } } } diff --git a/servo/components/style/stylesheets/page_rule.rs b/servo/components/style/stylesheets/page_rule.rs index a1618309a3..cdcfe0b8a7 100644 --- a/servo/components/style/stylesheets/page_rule.rs +++ b/servo/components/style/stylesheets/page_rule.rs @@ -221,7 +221,7 @@ impl Parse for PageSelector { ) -> Result<Self, ParseError<'i>> { let name = input .try_parse(parse_page_name) - .unwrap_or(AtomIdent(atom!(""))); + .unwrap_or(AtomIdent::new(atom!(""))); let mut pseudos = PagePseudoClasses::default(); while let Ok(pc) = input.try_parse(PagePseudoClass::parse) { pseudos.push(pc); diff --git a/servo/components/style/stylesheets/rule_list.rs b/servo/components/style/stylesheets/rule_list.rs index 1b9f330185..9fb7629d3d 100644 --- a/servo/components/style/stylesheets/rule_list.rs +++ b/servo/components/style/stylesheets/rule_list.rs @@ -16,6 +16,8 @@ use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps}; use servo_arc::Arc; use std::fmt::{self, Write}; +use super::CssRuleType; + /// A list of CSS rules. #[derive(Debug, ToShmem)] pub struct CssRules(pub Vec<CssRule>); @@ -136,6 +138,7 @@ pub trait CssRulesHelpers { parent_stylesheet_contents: &StylesheetContents, index: usize, nested: CssRuleTypes, + parse_relative_rule_type: Option<CssRuleType>, loader: Option<&dyn StylesheetLoader>, allow_import_rules: AllowImportRules, ) -> Result<CssRule, RulesMutateError>; @@ -149,6 +152,7 @@ impl CssRulesHelpers for Locked<CssRules> { parent_stylesheet_contents: &StylesheetContents, index: usize, containing_rule_types: CssRuleTypes, + parse_relative_rule_type: Option<CssRuleType>, loader: Option<&dyn StylesheetLoader>, allow_import_rules: AllowImportRules, ) -> Result<CssRule, RulesMutateError> { @@ -165,6 +169,7 @@ impl CssRulesHelpers for Locked<CssRules> { rule_list: &rules.0, index, containing_rule_types, + parse_relative_rule_type, }; // Steps 3, 4, 5, 6 diff --git a/servo/components/style/stylesheets/rule_parser.rs b/servo/components/style/stylesheets/rule_parser.rs index 742ad5d250..634f7c1af3 100644 --- a/servo/components/style/stylesheets/rule_parser.rs +++ b/servo/components/style/stylesheets/rule_parser.rs @@ -23,6 +23,8 @@ use crate::stylesheets::font_feature_values_rule::parse_family_name_list; use crate::stylesheets::import_rule::{ImportLayer, ImportRule, ImportSupportsCondition}; use crate::stylesheets::keyframes_rule::parse_keyframe_list; use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule}; +use crate::stylesheets::scope_rule::{ScopeBounds, ScopeRule}; +use crate::stylesheets::starting_style_rule::StartingStyleRule; use crate::stylesheets::supports_rule::SupportsCondition; use crate::stylesheets::{ AllowImportRules, CorsMode, CssRule, CssRuleType, CssRuleTypes, CssRules, DocumentRule, @@ -50,6 +52,8 @@ pub struct InsertRuleContext<'a> { pub index: usize, /// The containing rule types of our ancestors. pub containing_rule_types: CssRuleTypes, + /// Rule type determining if and how we parse relative selector syntax. + pub parse_relative_rule_type: Option<CssRuleType>, } impl<'a> InsertRuleContext<'a> { @@ -231,6 +235,10 @@ pub enum AtRulePrelude { Namespace(Option<Prefix>, Namespace), /// A @layer rule prelude. Layer(Vec<LayerName>), + /// A @scope rule prelude. + Scope(ScopeBounds), + /// A @starting-style prelude. + StartingStyle, } impl AtRulePrelude { @@ -251,6 +259,8 @@ impl AtRulePrelude { Self::Margin(..) => "margin", Self::Namespace(..) => "namespace", Self::Layer(..) => "layer", + Self::Scope(..) => "scope", + Self::StartingStyle => "starting-style", } } } @@ -483,18 +493,29 @@ impl NestedParseResult { impl<'a, 'i> NestedRuleParser<'a, 'i> { #[inline] fn in_style_rule(&self) -> bool { - self.context.rule_types.contains(CssRuleType::Style) + self.context + .nesting_context + .rule_types + .contains(CssRuleType::Style) } #[inline] fn in_page_rule(&self) -> bool { - self.context.rule_types.contains(CssRuleType::Page) + self.context + .nesting_context + .rule_types + .contains(CssRuleType::Page) } #[inline] fn in_style_or_page_rule(&self) -> bool { let types = CssRuleTypes::from_bits(CssRuleType::Style.bit() | CssRuleType::Page.bit()); - self.context.rule_types.intersects(types) + self.context.nesting_context.rule_types.intersects(types) + } + + #[inline] + fn parse_relative(&self) -> ParseRelative { + self.context.nesting_context.parse_relative } // https://drafts.csswg.org/css-nesting/#conditionals @@ -507,7 +528,9 @@ impl<'a, 'i> NestedRuleParser<'a, 'i> { AtRulePrelude::Supports(..) | AtRulePrelude::Container(..) | AtRulePrelude::Document(..) | - AtRulePrelude::Layer(..) => true, + AtRulePrelude::Layer(..) | + AtRulePrelude::Scope(..) | + AtRulePrelude::StartingStyle => true, AtRulePrelude::Namespace(..) | AtRulePrelude::FontFace | @@ -523,10 +546,9 @@ impl<'a, 'i> NestedRuleParser<'a, 'i> { } fn nest_for_rule<R>(&mut self, rule_type: CssRuleType, cb: impl FnOnce(&mut Self) -> R) -> R { - let old_rule_types = self.context.rule_types; - self.context.rule_types.insert(rule_type); + let old = self.context.nesting_context.save(rule_type); let r = cb(self); - self.context.rule_types = old_rule_types; + self.context.nesting_context.restore(old); r } @@ -701,6 +723,13 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> { let cond = DocumentCondition::parse(&self.context, input)?; AtRulePrelude::Document(cond) }, + "scope" if static_prefs::pref!("layout.css.at-scope.enabled") => { + let bounds = ScopeBounds::parse(&self.context, input, self.in_style_rule()); + AtRulePrelude::Scope(bounds) + }, + "starting-style" if static_prefs::pref!("layout.css.starting-style-at-rules.enabled") => { + AtRulePrelude::StartingStyle + }, _ => { if static_prefs::pref!("layout.css.margin-rules.enabled") { if let Some(margin_rule_type) = MarginRuleType::match_name(&name) { @@ -862,11 +891,30 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> { block: Arc::new(self.shared_lock.wrap(declarations)), source_location: start.source_location(), })) - } + }, AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => { // These rules don't have blocks. return Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock)); }, + AtRulePrelude::Scope(bounds) => { + let source_location = start.source_location(); + CssRule::Scope(Arc::new(ScopeRule { + bounds, + rules: self + .parse_nested(input, CssRuleType::Scope) + .into_rules(self.shared_lock, source_location), + source_location, + })) + }, + AtRulePrelude::StartingStyle => { + let source_location = start.source_location(); + CssRule::StartingStyle(Arc::new(StartingStyleRule { + rules: self + .parse_nested(input, CssRuleType::StartingStyle) + .into_rules(self.shared_lock, source_location), + source_location, + })) + }, }; self.rules.push(rule); Ok(()) @@ -913,12 +961,7 @@ impl<'a, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'i> { url_data: self.context.url_data, for_supports_rule: false, }; - let parse_relative = if self.in_style_rule() { - ParseRelative::ForNesting - } else { - ParseRelative::No - }; - SelectorList::parse(&selector_parser, input, parse_relative) + SelectorList::parse(&selector_parser, input, self.parse_relative()) } fn parse_block<'t>( diff --git a/servo/components/style/stylesheets/rules_iterator.rs b/servo/components/style/stylesheets/rules_iterator.rs index 76d41c8184..60f3df1ea9 100644 --- a/servo/components/style/stylesheets/rules_iterator.rs +++ b/servo/components/style/stylesheets/rules_iterator.rs @@ -116,6 +116,8 @@ where Some(supports_rule.rules.read_with(guard).0.iter()) }, CssRule::LayerBlock(ref layer_rule) => Some(layer_rule.rules.read_with(guard).0.iter()), + CssRule::Scope(ref rule) => Some(rule.rules.read_with(guard).0.iter()), + CssRule::StartingStyle(ref rule) => Some(rule.rules.read_with(guard).0.iter()), } } } diff --git a/servo/components/style/stylesheets/scope_rule.rs b/servo/components/style/stylesheets/scope_rule.rs new file mode 100644 index 0000000000..6816c433a7 --- /dev/null +++ b/servo/components/style/stylesheets/scope_rule.rs @@ -0,0 +1,161 @@ +/* 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/. */ + +//! A [`@scope`][scope] rule. +//! +//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles + +use crate::parser::ParserContext; +use crate::selector_parser::{SelectorImpl, SelectorParser}; +use crate::shared_lock::{ + DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, +}; +use crate::str::CssStringWriter; +use crate::stylesheets::CssRules; +use cssparser::{Parser, SourceLocation, ToCss}; +#[cfg(feature = "gecko")] +use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalSizeOf, MallocUnconditionalShallowSizeOf}; +use selectors::parser::{ParseRelative, SelectorList}; +use servo_arc::Arc; +use std::fmt::{self, Write}; +use style_traits::CssWriter; + +/// A scoped rule. +#[derive(Debug, ToShmem)] +pub struct ScopeRule { + /// Bounds at which this rule applies. + pub bounds: ScopeBounds, + /// The nested rules inside the block. + pub rules: Arc<Locked<CssRules>>, + /// The source position where this rule was found. + pub source_location: SourceLocation, +} + +impl DeepCloneWithLock for ScopeRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + let rules = self.rules.read_with(guard); + Self { + bounds: self.bounds.clone(), + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), + source_location: self.source_location.clone(), + } + } +} + +impl ToCssWithGuard for ScopeRule { + fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + dest.write_str("@scope")?; + { + let mut writer = CssWriter::new(dest); + if let Some(start) = self.bounds.start.as_ref() { + writer.write_str(" (")?; + start.to_css(&mut writer)?; + writer.write_char(')')?; + } + if let Some(end) = self.bounds.end.as_ref() { + writer.write_str(" to (")?; + end.to_css(&mut writer)?; + writer.write_char(')')?; + } + } + self.rules.read_with(guard).to_css_block(guard, dest) + } +} + +impl ScopeRule { + /// Measure heap usage. + #[cfg(feature = "gecko")] + pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { + self.rules.unconditional_shallow_size_of(ops) + + self.rules.read_with(guard).size_of(guard, ops) + + self.bounds.size_of(ops) + } +} + +/// Bounds of the scope. +#[derive(Debug, Clone, ToShmem)] +pub struct ScopeBounds { + /// Start of the scope. + pub start: Option<SelectorList<SelectorImpl>>, + /// End of the scope. + pub end: Option<SelectorList<SelectorImpl>>, +} + +impl ScopeBounds { + #[cfg(feature = "gecko")] + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + fn bound_size_of(bound: &Option<SelectorList<SelectorImpl>>, ops: &mut MallocSizeOfOps) -> usize { + bound.as_ref().map(|list| list.unconditional_size_of(ops)).unwrap_or(0) + } + bound_size_of(&self.start, ops) + bound_size_of(&self.end, ops) + } +} + +fn parse_scope<'a>( + context: &ParserContext, + input: &mut Parser<'a, '_>, + in_style_rule: bool, + for_end: bool +) -> Option<SelectorList<SelectorImpl>> { + input.try_parse(|input| { + if for_end { + input.expect_ident_matching("to")?; + } + input.expect_parenthesis_block()?; + input.parse_nested_block(|input| { + if input.is_exhausted() { + return Ok(None); + } + let selector_parser = SelectorParser { + stylesheet_origin: context.stylesheet_origin, + namespaces: &context.namespaces, + url_data: context.url_data, + for_supports_rule: false, + }; + let parse_relative = if for_end { + ParseRelative::ForScope + } else if in_style_rule { + ParseRelative::ForNesting + } else { + ParseRelative::No + }; + Ok(Some(SelectorList::parse_forgiving( + &selector_parser, + input, + parse_relative, + )?)) + }) + }) + .ok() + .flatten() +} + +impl ScopeBounds { + /// Parse a container condition. + pub fn parse<'a>( + context: &ParserContext, + input: &mut Parser<'a, '_>, + in_style_rule: bool, + ) -> Self { + let start = parse_scope( + context, + input, + in_style_rule, + false + ); + + let end = parse_scope( + context, + input, + in_style_rule, + true + ); + Self { start, end } + } +} diff --git a/servo/components/style/stylesheets/starting_style_rule.rs b/servo/components/style/stylesheets/starting_style_rule.rs new file mode 100644 index 0000000000..3c2627582b --- /dev/null +++ b/servo/components/style/stylesheets/starting_style_rule.rs @@ -0,0 +1,57 @@ +/* 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/. */ + +//! before-change style: the `@starting-style` rules. +//! https://drafts.csswg.org/css-transitions-2/#defining-before-change-style + +use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; +use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use crate::str::CssStringWriter; +use crate::stylesheets::CssRules; +use cssparser::SourceLocation; +use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; +use servo_arc::Arc; +use std::fmt::{self, Debug, Write}; + +/// A [`@starting-style`][starting-style] rule. +/// +/// [starting-style]: https://drafts.csswg.org/css-transitions-2/#at-ruledef-starting-style +#[derive(Debug, ToShmem)] +pub struct StartingStyleRule { + /// The nested rules to this starting-style rule. + pub rules: Arc<Locked<CssRules>>, + /// The source position where this starting-style rule was found. + pub source_location: SourceLocation, +} + +impl StartingStyleRule { + /// Measure heap usage. + #[cfg(feature = "gecko")] + pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { + self.rules.unconditional_shallow_size_of(ops) + + self.rules.read_with(guard).size_of(guard, ops) + } +} + +impl ToCssWithGuard for StartingStyleRule { + fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + dest.write_str("@starting-style")?; + self.rules.read_with(guard).to_css_block(guard, dest) + } +} + +impl DeepCloneWithLock for StartingStyleRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + let rules = self.rules.read_with(guard); + StartingStyleRule { + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), + source_location: self.source_location.clone(), + } + } +} diff --git a/servo/components/style/stylesheets/stylesheet.rs b/servo/components/style/stylesheets/stylesheet.rs index 1604022871..84d84b0d46 100644 --- a/servo/components/style/stylesheets/stylesheet.rs +++ b/servo/components/style/stylesheets/stylesheet.rs @@ -333,7 +333,11 @@ impl SanitizationKind { // TODO(emilio): Perhaps Layer should not be always sanitized? But // we sanitize @media and co, so this seems safer for now. CssRule::LayerStatement(..) | - CssRule::LayerBlock(..) => false, + CssRule::LayerBlock(..) | + // TODO(dshin): Same comment as Layer applies - shouldn't give away + // something like display size - erring on the side of "safe" for now. + CssRule::Scope(..) | + CssRule::StartingStyle(..) => false, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index cc1cb75689..06cbe6276b 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -31,7 +31,7 @@ use crate::rule_collector::RuleCollector; use crate::rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; use crate::sharing::RevalidationResult; use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, SelectorMap, SelectorMapEntry}; -use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, SnapshotMap}; +use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, NonTSPseudoClass, SnapshotMap}; use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; @@ -48,7 +48,7 @@ use crate::stylesheets::{ PerOriginIter, }; use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; -use crate::values::computed; +use crate::values::{computed, AtomIdent}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use dom::{DocumentState, ElementState}; @@ -113,8 +113,6 @@ unsafe impl Send for CascadeDataCacheKey {} unsafe impl Sync for CascadeDataCacheKey {} trait CascadeDataCacheEntry: Sized { - /// Returns a reference to the cascade data. - fn cascade_data(&self) -> &CascadeData; /// Rebuilds the cascade data for the new stylesheet collection. The /// collection is guaranteed to be dirty. fn rebuild<S>( @@ -269,10 +267,6 @@ lazy_static! { } impl CascadeDataCacheEntry for UserAgentCascadeData { - fn cascade_data(&self) -> &CascadeData { - &self.cascade_data - } - fn rebuild<S>( device: &Device, quirks_mode: QuirksMode, @@ -2023,6 +2017,11 @@ struct StylistSelectorVisitor<'a> { /// selectors. nth_of_attribute_dependencies: &'a mut PrecomputedHashSet<LocalName>, + /// The filter with the local names of custom states in selectors for + /// within the selector list of :nth-child(... of <selector list>) + /// selectors. + nth_of_custom_state_dependencies: &'a mut PrecomputedHashSet<AtomIdent>, + /// All the states selectors in the page reference. state_dependencies: &'a mut ElementState, @@ -2144,6 +2143,16 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> { component_needs_revalidation(s, self.passed_rightmost_selector); match *s { + Component::NonTSPseudoClass(NonTSPseudoClass::CustomState(ref name)) => { + // CustomStateSet is special cased as it is a functional pseudo + // class with unbounded inner values. This is different to + // other psuedo class like :emtpy or :dir() which can be packed + // into the ElementState bitflags. For CustomState, however, + // the state name should be checked for presence in the selector. + if self.in_selector_list_of.relevant_to_nth_of_dependencies() { + self.nth_of_custom_state_dependencies.insert(name.0.clone()); + } + }, Component::NonTSPseudoClass(ref p) => { self.state_dependencies.insert(p.state_flag()); self.document_state_dependencies @@ -2377,6 +2386,11 @@ pub struct CascadeData { /// an element when an irrelevant attribute changes. nth_of_attribute_dependencies: PrecomputedHashSet<LocalName>, + /// The custom states that appear in the selector list of + /// :nth-child(... of <selector list>). Used to avoid restyling siblings of + /// an element when an irrelevant custom state changes. + nth_of_custom_state_dependencies: PrecomputedHashSet<AtomIdent>, + /// The element state bits that are relied on by selectors. Like /// `attribute_dependencies`, this is used to avoid taking element snapshots /// when an irrelevant element state bit changes. @@ -2456,6 +2470,7 @@ impl CascadeData { nth_of_mapped_ids: PrecomputedHashSet::default(), nth_of_class_dependencies: PrecomputedHashSet::default(), nth_of_attribute_dependencies: PrecomputedHashSet::default(), + nth_of_custom_state_dependencies: PrecomputedHashSet::default(), nth_of_state_dependencies: ElementState::empty(), attribute_dependencies: PrecomputedHashSet::default(), state_dependencies: ElementState::empty(), @@ -2535,6 +2550,13 @@ impl CascadeData { self.state_dependencies.intersects(state) } + /// Returns whether the given Custom State is relied upon by a selector + /// of some rule in the selector list of :nth-child(... of <selector list>). + #[inline] + pub fn has_nth_of_custom_state_dependency(&self, state: &AtomIdent) -> bool { + self.nth_of_custom_state_dependencies.contains(state) + } + /// Returns whether the given ElementState bit is relied upon by a selector /// of some rule in the selector list of :nth-child(... of <selector list>). #[inline] @@ -2664,6 +2686,7 @@ impl CascadeData { self.relative_selector_invalidation_map.shrink_if_needed(); self.attribute_dependencies.shrink_if_needed(); self.nth_of_attribute_dependencies.shrink_if_needed(); + self.nth_of_custom_state_dependencies.shrink_if_needed(); self.nth_of_class_dependencies.shrink_if_needed(); self.nth_of_mapped_ids.shrink_if_needed(); self.mapped_ids.shrink_if_needed(); @@ -2859,6 +2882,8 @@ impl CascadeData { nth_of_class_dependencies: &mut self.nth_of_class_dependencies, nth_of_attribute_dependencies: &mut self .nth_of_attribute_dependencies, + nth_of_custom_state_dependencies: &mut self + .nth_of_custom_state_dependencies, state_dependencies: &mut self.state_dependencies, nth_of_state_dependencies: &mut self.nth_of_state_dependencies, document_state_dependencies: &mut self.document_state_dependencies, @@ -3234,7 +3259,9 @@ impl CascadeData { CssRule::LayerBlock(..) | CssRule::LayerStatement(..) | CssRule::FontPaletteValues(..) | - CssRule::FontFeatureValues(..) => { + CssRule::FontFeatureValues(..) | + CssRule::Scope(..) | + CssRule::StartingStyle(..) => { // Not affected by device changes. continue; }, @@ -3323,6 +3350,7 @@ impl CascadeData { self.relative_selector_invalidation_map.clear(); self.attribute_dependencies.clear(); self.nth_of_attribute_dependencies.clear(); + self.nth_of_custom_state_dependencies.clear(); self.nth_of_class_dependencies.clear(); self.state_dependencies = ElementState::empty(); self.nth_of_state_dependencies = ElementState::empty(); @@ -3335,10 +3363,6 @@ impl CascadeData { } impl CascadeDataCacheEntry for CascadeData { - fn cascade_data(&self) -> &CascadeData { - self - } - fn rebuild<S>( device: &Device, quirks_mode: QuirksMode, @@ -3482,6 +3506,7 @@ pub fn needs_revalidation_for_testing(s: &Selector<SelectorImpl>) -> bool { let mut attribute_dependencies = Default::default(); let mut nth_of_class_dependencies = Default::default(); let mut nth_of_attribute_dependencies = Default::default(); + let mut nth_of_custom_state_dependencies = Default::default(); let mut state_dependencies = ElementState::empty(); let mut nth_of_state_dependencies = ElementState::empty(); let mut document_state_dependencies = DocumentState::empty(); @@ -3494,6 +3519,7 @@ pub fn needs_revalidation_for_testing(s: &Selector<SelectorImpl>) -> bool { attribute_dependencies: &mut attribute_dependencies, nth_of_class_dependencies: &mut nth_of_class_dependencies, nth_of_attribute_dependencies: &mut nth_of_attribute_dependencies, + nth_of_custom_state_dependencies: &mut nth_of_custom_state_dependencies, state_dependencies: &mut state_dependencies, nth_of_state_dependencies: &mut nth_of_state_dependencies, document_state_dependencies: &mut document_state_dependencies, diff --git a/servo/components/style/traversal.rs b/servo/components/style/traversal.rs index d63c3cb965..fa256d0f60 100644 --- a/servo/components/style/traversal.rs +++ b/servo/components/style/traversal.rs @@ -688,7 +688,7 @@ where element.finish_restyle(context, data, new_styles, important_rules_changed) } -#[cfg(feature = "servo-layout-2013")] +#[cfg(feature = "servo")] fn notify_paint_worklet<E>(context: &StyleContext<E>, data: &ElementData) where E: TElement, @@ -726,7 +726,7 @@ where } } -#[cfg(not(feature = "servo-layout-2013"))] +#[cfg(not(feature = "servo"))] fn notify_paint_worklet<E>(_context: &StyleContext<E>, _data: &ElementData) where E: TElement, diff --git a/servo/components/style/values/computed/basic_shape.rs b/servo/components/style/values/computed/basic_shape.rs index d39110ec1c..21df7baf93 100644 --- a/servo/components/style/values/computed/basic_shape.rs +++ b/servo/components/style/values/computed/basic_shape.rs @@ -7,9 +7,12 @@ //! //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape +use crate::values::animated::{Animate, Procedure}; +use crate::values::computed::angle::Angle; use crate::values::computed::url::ComputedUrl; use crate::values::computed::{Image, LengthPercentage, NonNegativeLengthPercentage, Position}; use crate::values::generics::basic_shape as generic; +use crate::values::specified::svg_path::{CoordPair, PathCommand}; /// A computed alias for FillRule. pub use crate::values::generics::basic_shape::FillRule; @@ -21,8 +24,13 @@ pub type ClipPath = generic::GenericClipPath<BasicShape, ComputedUrl>; pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>; /// A computed basic shape. -pub type BasicShape = - generic::GenericBasicShape<Position, LengthPercentage, NonNegativeLengthPercentage, InsetRect>; +pub type BasicShape = generic::GenericBasicShape< + Angle, + Position, + LengthPercentage, + NonNegativeLengthPercentage, + InsetRect, +>; /// The computed value of `inset()`. pub type InsetRect = generic::GenericInsetRect<LengthPercentage, NonNegativeLengthPercentage>; @@ -35,3 +43,179 @@ pub type Ellipse = generic::Ellipse<Position, NonNegativeLengthPercentage>; /// The computed value of `ShapeRadius`. pub type ShapeRadius = generic::GenericShapeRadius<NonNegativeLengthPercentage>; + +/// The computed value of `shape()`. +pub type Shape = generic::Shape<Angle, LengthPercentage>; + +/// The computed value of `ShapeCommand`. +pub type ShapeCommand = generic::GenericShapeCommand<Angle, LengthPercentage>; + +/// The computed value of `PathOrShapeFunction`. +pub type PathOrShapeFunction = generic::GenericPathOrShapeFunction<Angle, LengthPercentage>; + +/// The computed value of `CoordinatePair`. +pub type CoordinatePair = generic::CoordinatePair<LengthPercentage>; + +/// Animate from `Shape` to `Path`, and vice versa. +macro_rules! animate_shape { + ( + $from:ident, + $to:ident, + $procedure:ident, + $from_as_shape:tt, + $to_as_shape:tt + ) => {{ + // Check fill-rule. + if $from.fill != $to.fill { + return Err(()); + } + + // Check the list of commands. (This is a specialized lists::by_computed_value::animate().) + let from_cmds = $from.commands(); + let to_cmds = $to.commands(); + if from_cmds.len() != to_cmds.len() { + return Err(()); + } + let commands = from_cmds + .iter() + .zip(to_cmds.iter()) + .map(|(from_cmd, to_cmd)| { + $from_as_shape(from_cmd).animate(&$to_as_shape(to_cmd), $procedure) + }) + .collect::<Result<Vec<ShapeCommand>, ()>>()?; + + Ok(Shape { + fill: $from.fill, + commands: commands.into(), + }) + }}; +} + +impl Animate for PathOrShapeFunction { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + // Per spec, commands are "the same" if they use the same command keyword, and use the same + // <by-to> keyword. For curve and smooth, they also must have the same number of control + // points. Therefore, we don't have to do normalization here. (Note that we do + // normalization if we animate from path() to path(). See svg_path.rs for more details.) + // + // https://drafts.csswg.org/css-shapes-2/#interpolating-shape + match (self, other) { + (Self::Path(ref from), Self::Path(ref to)) => { + from.animate(to, procedure).map(Self::Path) + }, + (Self::Shape(ref from), Self::Shape(ref to)) => { + from.animate(to, procedure).map(Self::Shape) + }, + (Self::Shape(ref from), Self::Path(ref to)) => { + // Animate from shape() to path(). We convert each PathCommand into ShapeCommand, + // and return shape(). + animate_shape!( + from, + to, + procedure, + (|shape_cmd| shape_cmd), + (|path_cmd| ShapeCommand::from(path_cmd)) + ) + .map(Self::Shape) + }, + (Self::Path(ref from), Self::Shape(ref to)) => { + // Animate from path() to shape(). We convert each PathCommand into ShapeCommand, + // and return shape(). + animate_shape!( + from, + to, + procedure, + (|path_cmd| ShapeCommand::from(path_cmd)), + (|shape_cmd| shape_cmd) + ) + .map(Self::Shape) + }, + } + } +} + +impl From<&PathCommand> for ShapeCommand { + #[inline] + fn from(path: &PathCommand) -> Self { + use crate::values::computed::CSSPixelLength; + match path { + &PathCommand::Close => Self::Close, + &PathCommand::Move { by_to, ref point } => Self::Move { + by_to, + point: point.into(), + }, + &PathCommand::Line { by_to, ref point } => Self::Move { + by_to, + point: point.into(), + }, + &PathCommand::HLine { by_to, x } => Self::HLine { + by_to, + x: LengthPercentage::new_length(CSSPixelLength::new(x)), + }, + &PathCommand::VLine { by_to, y } => Self::VLine { + by_to, + y: LengthPercentage::new_length(CSSPixelLength::new(y)), + }, + &PathCommand::CubicCurve { + by_to, + ref point, + ref control1, + ref control2, + } => Self::CubicCurve { + by_to, + point: point.into(), + control1: control1.into(), + control2: control2.into(), + }, + &PathCommand::QuadCurve { + by_to, + ref point, + ref control1, + } => Self::QuadCurve { + by_to, + point: point.into(), + control1: control1.into(), + }, + &PathCommand::SmoothCubic { + by_to, + ref point, + ref control2, + } => Self::SmoothCubic { + by_to, + point: point.into(), + control2: control2.into(), + }, + &PathCommand::SmoothQuad { by_to, ref point } => Self::SmoothQuad { + by_to, + point: point.into(), + }, + &PathCommand::Arc { + by_to, + ref point, + ref radii, + arc_sweep, + arc_size, + rotate, + } => Self::Arc { + by_to, + point: point.into(), + radii: radii.into(), + arc_sweep, + arc_size, + rotate: Angle::from_degrees(rotate), + }, + } + } +} + +impl From<&CoordPair> for CoordinatePair { + #[inline] + fn from(p: &CoordPair) -> Self { + use crate::values::computed::CSSPixelLength; + Self::new( + LengthPercentage::new_length(CSSPixelLength::new(p.x)), + LengthPercentage::new_length(CSSPixelLength::new(p.y)), + ) + } +} diff --git a/servo/components/style/values/computed/box.rs b/servo/components/style/values/computed/box.rs index 62811d9851..0dea1c7ab9 100644 --- a/servo/components/style/values/computed/box.rs +++ b/servo/components/style/values/computed/box.rs @@ -383,6 +383,15 @@ impl Zoom { if self == Self::ONE { return value; } - self.value() * value + value * self.value() + } + + /// Returns the un-zoomed value. + #[inline] + pub fn unzoom(self, value: f32) -> f32 { + if self == Self::ONE { + return value; + } + value / self.value() } } diff --git a/servo/components/style/values/computed/font.rs b/servo/components/style/values/computed/font.rs index de0a5e372b..e492584f75 100644 --- a/servo/components/style/values/computed/font.rs +++ b/servo/components/style/values/computed/font.rs @@ -8,7 +8,7 @@ use crate::parser::{Parse, ParserContext}; use crate::values::animated::ToAnimatedValue; use crate::values::computed::{ Angle, Context, Integer, Length, NonNegativeLength, NonNegativeNumber, Number, Percentage, - ToComputedValue, + ToComputedValue, Zoom, }; use crate::values::generics::font::{ FeatureTagValue, FontSettings, TaggedFontValue, VariationValue, @@ -278,6 +278,16 @@ impl FontSize { self.used_size.0 } + /// Apply zoom to the font-size. This is usually done by ToComputedValue. + #[inline] + pub fn zoom(&self, zoom: Zoom) -> Self { + Self { + computed_size: NonNegative(Length::new(zoom.zoom(self.computed_size.0.px()))), + used_size: NonNegative(Length::new(zoom.zoom(self.used_size.0.px()))), + keyword_info: self.keyword_info, + } + } + #[inline] /// Get default value of font size. pub fn medium() -> Self { @@ -1359,7 +1369,7 @@ impl ToResolvedValue for LineHeight { context.style.get_font(), wm, Some(context.element_info.element), - )) + ).to_resolved_value(context)) } #[inline] diff --git a/servo/components/style/values/computed/length.rs b/servo/components/style/values/computed/length.rs index e75676a76d..881efed126 100644 --- a/servo/components/style/values/computed/length.rs +++ b/servo/components/style/values/computed/length.rs @@ -6,12 +6,13 @@ use super::{Context, Number, ToComputedValue}; use crate::values::animated::ToAnimatedValue; -use crate::values::computed::NonNegativeNumber; +use crate::values::computed::{NonNegativeNumber, Zoom}; use crate::values::generics::length as generics; use crate::values::generics::length::{ GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize, }; use crate::values::generics::NonNegative; +use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue}; use crate::values::specified::length::{AbsoluteLength, FontBaseSize, LineHeightBase}; use crate::values::{specified, CSSFloat}; use crate::Zero; @@ -227,12 +228,24 @@ impl Size { ToAnimatedValue, ToAnimatedZero, ToComputedValue, - ToResolvedValue, ToShmem, )] #[repr(C)] pub struct CSSPixelLength(CSSFloat); +impl ToResolvedValue for CSSPixelLength { + type ResolvedValue = Self; + + fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue { + Self(context.style.effective_zoom.unzoom(self.0)) + } + + #[inline] + fn from_resolved_value(value: Self::ResolvedValue) -> Self { + value + } +} + impl fmt::Debug for CSSPixelLength { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f)?; @@ -271,6 +284,12 @@ impl CSSPixelLength { self.0 } + /// Zooms a particular length. + #[inline] + pub fn zoom(self, zoom: Zoom) -> Self { + Self::new(zoom.zoom(self.px())) + } + /// Return the length with app_unit i32 type. #[inline] pub fn to_i32_au(self) -> i32 { diff --git a/servo/components/style/values/computed/length_percentage.rs b/servo/components/style/values/computed/length_percentage.rs index 0dbd2de76d..c448025dd1 100644 --- a/servo/components/style/values/computed/length_percentage.rs +++ b/servo/components/style/values/computed/length_percentage.rs @@ -30,6 +30,7 @@ use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZer use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::calc::{CalcUnits, PositivePercentageBasis}; use crate::values::generics::{calc, NonNegative}; +use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue}; use crate::values::specified::length::{FontBaseSize, LineHeightBase}; use crate::values::{specified, CSSFloat}; use crate::{Zero, ZeroNoPercent}; @@ -164,6 +165,25 @@ impl MallocSizeOf for LengthPercentage { } } +impl ToResolvedValue for LengthPercentage { + type ResolvedValue = Self; + + fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue { + if context.style.effective_zoom.is_one() { + return self; + } + match self.unpack() { + Unpacked::Length(l) => Self::new_length(l.to_resolved_value(context)), + Unpacked::Percentage(..) | Unpacked::Calc(..) => self, + } + } + + #[inline] + fn from_resolved_value(value: Self::ResolvedValue) -> Self { + value + } +} + /// An unpacked `<length-percentage>` that borrows the `calc()` variant. #[derive(Clone, Debug, PartialEq, ToCss)] enum Unpacked<'a> { diff --git a/servo/components/style/values/computed/ratio.rs b/servo/components/style/values/computed/ratio.rs index ae8997cfc0..6964eec3e4 100644 --- a/servo/components/style/values/computed/ratio.rs +++ b/servo/components/style/values/computed/ratio.rs @@ -9,7 +9,7 @@ use crate::values::computed::NonNegativeNumber; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::ratio::Ratio as GenericRatio; use crate::{One, Zero}; -use std::cmp::{Ordering, PartialOrd}; +use std::cmp::Ordering; /// A computed <ratio> value. pub type Ratio = GenericRatio<NonNegativeNumber>; diff --git a/servo/components/style/values/generics/basic_shape.rs b/servo/components/style/values/generics/basic_shape.rs index 13d27995c1..ca7646fb13 100644 --- a/servo/components/style/values/generics/basic_shape.rs +++ b/servo/components/style/values/generics/basic_shape.rs @@ -10,7 +10,7 @@ use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::border::GenericBorderRadius; use crate::values::generics::position::GenericPositionOrAuto; use crate::values::generics::rect::Rect; -use crate::values::specified::SVGPathData; +use crate::values::specified::svg_path::{PathCommand, SVGPathData}; use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; @@ -181,8 +181,13 @@ pub use self::GenericShapeOutside as ShapeOutside; ToShmem, )] #[repr(C, u8)] -pub enum GenericBasicShape<Position, LengthPercentage, NonNegativeLengthPercentage, BasicShapeRect> -{ +pub enum GenericBasicShape< + Angle, + Position, + LengthPercentage, + NonNegativeLengthPercentage, + BasicShapeRect, +> { /// The <basic-shape-rect>. Rect(BasicShapeRect), /// Defines a circle with a center and a radius. @@ -199,10 +204,11 @@ pub enum GenericBasicShape<Position, LengthPercentage, NonNegativeLengthPercenta ), /// Defines a polygon with pair arguments. Polygon(GenericPolygon<LengthPercentage>), - /// Defines a path with SVG path syntax. - Path(Path), - // TODO: Bug 1823463. Add shape(). - // https://drafts.csswg.org/css-shapes-2/#shape-function + /// Defines a path() or shape(). + PathOrShape( + #[animation(field_bound)] + #[css(field_bound)] + GenericPathOrShapeFunction<Angle, LengthPercentage>), } pub use self::GenericBasicShape as BasicShape; @@ -366,6 +372,30 @@ pub use self::GenericPolygon as Polygon; #[repr(C)] pub struct PolygonCoord<LengthPercentage>(pub LengthPercentage, pub LengthPercentage); +/// path() function or shape() function. +#[derive( + Clone, + ComputeSquaredDistance, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericPathOrShapeFunction<Angle, LengthPercentage> { + /// Defines a path with SVG path syntax. + Path(Path), + /// Defines a shape function, which is identical to path() but it uses the CSS syntax. + Shape(#[css(field_bound)] Shape<Angle, LengthPercentage>), +} + // https://drafts.csswg.org/css-shapes/#typedef-fill-rule // NOTE: Basic shapes spec says that these are the only two values, however // https://www.w3.org/TR/SVG/painting.html#FillRuleProperty @@ -397,9 +427,9 @@ pub enum FillRule { Evenodd, } -/// The path function defined in css-shape-2. +/// The path function. /// -/// https://drafts.csswg.org/css-shapes-2/#funcdef-path +/// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-path #[derive( Animate, Clone, @@ -426,6 +456,14 @@ pub struct Path { pub path: SVGPathData, } +impl Path { + /// Returns the slice of PathCommand. + #[inline] + pub fn commands(&self) -> &[PathCommand] { + self.path.commands() + } +} + impl<B, U> ToAnimatedZero for ClipPath<B, U> { fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) @@ -565,3 +603,479 @@ impl Default for FillRule { fn is_default<T: Default + PartialEq>(fill: &T) -> bool { *fill == Default::default() } + +/// The shape function defined in css-shape-2. +/// shape() = shape(<fill-rule>? from <coordinate-pair>, <shape-command>#) +/// +/// https://drafts.csswg.org/css-shapes-2/#shape-function +#[derive( + Clone, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct Shape<Angle, LengthPercentage> { + /// The filling rule for this shape. + pub fill: FillRule, + /// The shape command data. Note that the starting point will be the first command in this + /// slice. + // Note: The first command is always GenericShapeCommand::Move. + pub commands: crate::OwnedSlice<GenericShapeCommand<Angle, LengthPercentage>>, +} + +impl<Angle, LengthPercentage> Shape<Angle, LengthPercentage> { + /// Returns the slice of GenericShapeCommand<..>. + #[inline] + pub fn commands(&self) -> &[GenericShapeCommand<Angle, LengthPercentage>] { + &self.commands + } +} + +impl<Angle, LengthPercentage> Animate for Shape<Angle, LengthPercentage> +where + Angle: Animate, + LengthPercentage: Animate, +{ + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + if self.fill != other.fill { + return Err(()); + } + let commands = + lists::by_computed_value::animate(&self.commands, &other.commands, procedure)?; + Ok(Self { + fill: self.fill, + commands, + }) + } +} + +impl<Angle, LengthPercentage> ComputeSquaredDistance for Shape<Angle, LengthPercentage> +where + Angle: ComputeSquaredDistance, + LengthPercentage: ComputeSquaredDistance, +{ + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + if self.fill != other.fill { + return Err(()); + } + lists::by_computed_value::squared_distance(&self.commands, &other.commands) + } +} + +impl<Angle, LengthPercentage> ToCss for Shape<Angle, LengthPercentage> +where + Angle: ToCss + Zero, + LengthPercentage: PartialEq + ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + use style_traits::values::SequenceWriter; + + // Per spec, we must have the first move command and at least one following command. + debug_assert!(self.commands.len() > 1); + + dest.write_str("shape(")?; + if !is_default(&self.fill) { + self.fill.to_css(dest)?; + dest.write_char(' ')?; + } + dest.write_str("from ")?; + match self.commands[0] { + ShapeCommand::Move { + by_to: _, + ref point, + } => point.to_css(dest)?, + _ => unreachable!("The first command must be move"), + } + dest.write_str(", ")?; + { + let mut writer = SequenceWriter::new(dest, ", "); + for command in self.commands.iter().skip(1) { + writer.item(command)?; + } + } + dest.write_char(')') + } +} + +/// This is a more general shape(path) command type, for both shape() and path(). +/// +/// https://www.w3.org/TR/SVG11/paths.html#PathData +/// https://drafts.csswg.org/css-shapes-2/#shape-function +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[allow(missing_docs)] +#[repr(C, u8)] +pub enum GenericShapeCommand<Angle, LengthPercentage> { + /// The move command. + Move { + by_to: ByTo, + point: CoordinatePair<LengthPercentage>, + }, + /// The line command. + Line { + by_to: ByTo, + point: CoordinatePair<LengthPercentage>, + }, + /// The hline command. + HLine { by_to: ByTo, x: LengthPercentage }, + /// The vline command. + VLine { by_to: ByTo, y: LengthPercentage }, + /// The cubic Bézier curve command. + CubicCurve { + by_to: ByTo, + point: CoordinatePair<LengthPercentage>, + control1: CoordinatePair<LengthPercentage>, + control2: CoordinatePair<LengthPercentage>, + }, + /// The quadratic Bézier curve command. + QuadCurve { + by_to: ByTo, + point: CoordinatePair<LengthPercentage>, + control1: CoordinatePair<LengthPercentage>, + }, + /// The smooth command. + SmoothCubic { + by_to: ByTo, + point: CoordinatePair<LengthPercentage>, + control2: CoordinatePair<LengthPercentage>, + }, + /// The smooth quadratic Bézier curve command. + SmoothQuad { + by_to: ByTo, + point: CoordinatePair<LengthPercentage>, + }, + /// The arc command. + Arc { + by_to: ByTo, + point: CoordinatePair<LengthPercentage>, + radii: CoordinatePair<LengthPercentage>, + arc_sweep: ArcSweep, + arc_size: ArcSize, + rotate: Angle, + }, + /// The closepath command. + Close, +} + +pub use self::GenericShapeCommand as ShapeCommand; + +impl<Angle, LengthPercentage> ToCss for ShapeCommand<Angle, LengthPercentage> +where + Angle: ToCss + Zero, + LengthPercentage: PartialEq + ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + use self::ShapeCommand::*; + match *self { + Move { by_to, ref point } => { + dest.write_str("move ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + Line { by_to, ref point } => { + dest.write_str("line ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + HLine { by_to, ref x } => { + dest.write_str("hline ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + x.to_css(dest) + }, + VLine { by_to, ref y } => { + dest.write_str("vline ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + y.to_css(dest) + }, + CubicCurve { + by_to, + ref point, + ref control1, + ref control2, + } => { + dest.write_str("curve ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest)?; + dest.write_str(" via ")?; + control1.to_css(dest)?; + dest.write_char(' ')?; + control2.to_css(dest) + }, + QuadCurve { + by_to, + ref point, + ref control1, + } => { + dest.write_str("curve ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest)?; + dest.write_str(" via ")?; + control1.to_css(dest) + }, + SmoothCubic { + by_to, + ref point, + ref control2, + } => { + dest.write_str("smooth ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest)?; + dest.write_str(" via ")?; + control2.to_css(dest) + }, + SmoothQuad { by_to, ref point } => { + dest.write_str("smooth ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest) + }, + Arc { + by_to, + ref point, + ref radii, + arc_sweep, + arc_size, + ref rotate, + } => { + dest.write_str("arc ")?; + by_to.to_css(dest)?; + dest.write_char(' ')?; + point.to_css(dest)?; + dest.write_str(" of ")?; + radii.x.to_css(dest)?; + if radii.x != radii.y { + dest.write_char(' ')?; + radii.y.to_css(dest)?; + } + + if matches!(arc_sweep, ArcSweep::Cw) { + dest.write_str(" cw")?; + } + + if matches!(arc_size, ArcSize::Large) { + dest.write_str(" large")?; + } + + if !rotate.is_zero() { + dest.write_str(" rotate ")?; + rotate.to_css(dest)?; + } + Ok(()) + }, + Close => dest.write_str("close"), + } + } +} + +/// This indicates the command is absolute or relative. +/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-by-to +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + Parse, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ByTo { + /// This indicates that the <coordinate-pair>s are relative to the command’s starting point. + By, + /// This relative to the top-left corner of the reference box. + To, +} + +impl ByTo { + /// Return true if it is absolute, i.e. it is To. + #[inline] + pub fn is_abs(&self) -> bool { + matches!(self, ByTo::To) + } + + /// Create ByTo based on the flag if it is absolute. + #[inline] + pub fn new(is_abs: bool) -> Self { + if is_abs { + Self::To + } else { + Self::By + } + } +} + +/// Defines a pair of coordinates, representing a rightward and downward offset, respectively, from +/// a specified reference point. Percentages are resolved against the width or height, +/// respectively, of the reference box. +/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-coordinate-pair +#[allow(missing_docs)] +#[derive( + AddAssign, + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct CoordinatePair<LengthPercentage> { + pub x: LengthPercentage, + pub y: LengthPercentage, +} + +impl<LengthPercentage> CoordinatePair<LengthPercentage> { + /// Create a CoordinatePair. + #[inline] + pub fn new(x: LengthPercentage, y: LengthPercentage) -> Self { + Self { x, y } + } +} + +/// This indicates that the arc that is traced around the ellipse clockwise or counter-clockwise +/// from the center. +/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-sweep +#[derive( + Clone, + Copy, + Debug, + Deserialize, + FromPrimitive, + MallocSizeOf, + Parse, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ArcSweep { + /// Counter-clockwise. The default value. (This also represents 0 in the svg path.) + Ccw = 0, + /// Clockwise. (This also represents 1 in the svg path.) + Cw = 1, +} + +impl Animate for ArcSweep { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + use num_traits::FromPrimitive; + // If an arc command has different <arc-sweep> between its starting and ending list, then + // the interpolated result uses cw for any progress value between 0 and 1. + (*self as i32) + .animate(&(*other as i32), procedure) + .map(|v| ArcSweep::from_u8((v > 0) as u8).unwrap_or(ArcSweep::Ccw)) + } +} + +impl ComputeSquaredDistance for ArcSweep { + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + (*self as i32).compute_squared_distance(&(*other as i32)) + } +} + +/// This indicates that the larger or smaller, respectively, of the two possible arcs must be +/// chosen. +/// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-size +#[derive( + Clone, + Copy, + Debug, + Deserialize, + FromPrimitive, + MallocSizeOf, + Parse, + PartialEq, + Serialize, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ArcSize { + /// Choose the small one. The default value. (This also represents 0 in the svg path.) + Small = 0, + /// Choose the large one. (This also represents 1 in the svg path.) + Large = 1, +} + +impl Animate for ArcSize { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + use num_traits::FromPrimitive; + // If it has different <arc-size> keywords, then the interpolated result uses large for any + // progress value between 0 and 1. + (*self as i32) + .animate(&(*other as i32), procedure) + .map(|v| ArcSize::from_u8((v > 0) as u8).unwrap_or(ArcSize::Small)) + } +} + +impl ComputeSquaredDistance for ArcSize { + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + (*self as i32).compute_squared_distance(&(*other as i32)) + } +} diff --git a/servo/components/style/values/generics/counters.rs b/servo/components/style/values/generics/counters.rs index 1d4518c57b..3f23c74b33 100644 --- a/servo/components/style/values/generics/counters.rs +++ b/servo/components/style/values/generics/counters.rs @@ -4,11 +4,10 @@ //! Generic types for counters-related CSS values. -#[cfg(feature = "servo-layout-2013")] +#[cfg(feature = "servo")] use crate::computed_values::list_style_type::T as ListStyleType; #[cfg(feature = "gecko")] use crate::values::generics::CounterStyle; -#[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] use crate::values::specified::Attr; use crate::values::CustomIdent; use std::fmt::{self, Write}; @@ -186,13 +185,13 @@ pub struct GenericCounters<I>( ); pub use self::GenericCounters as Counters; -#[cfg(feature = "servo-layout-2013")] +#[cfg(feature = "servo")] type CounterStyleType = ListStyleType; #[cfg(feature = "gecko")] type CounterStyleType = CounterStyle; -#[cfg(feature = "servo-layout-2013")] +#[cfg(feature = "servo")] #[inline] fn is_decimal(counter_type: &CounterStyleType) -> bool { *counter_type == ListStyleType::Decimal @@ -254,11 +253,9 @@ pub enum GenericContentItem<I> { /// Literal string content. String(crate::OwnedStr), /// `counter(name, style)`. - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] #[css(comma, function)] Counter(CustomIdent, #[css(skip_if = "is_decimal")] CounterStyleType), /// `counters(name, separator, style)`. - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] #[css(comma, function)] Counters( CustomIdent, @@ -266,16 +263,12 @@ pub enum GenericContentItem<I> { #[css(skip_if = "is_decimal")] CounterStyleType, ), /// `open-quote`. - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] OpenQuote, /// `close-quote`. - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] CloseQuote, /// `no-open-quote`. - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] NoOpenQuote, /// `no-close-quote`. - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] NoCloseQuote, /// `-moz-alt-content`. #[cfg(feature = "gecko")] @@ -286,7 +279,6 @@ pub enum GenericContentItem<I> { #[cfg(feature = "gecko")] MozLabelContent, /// `attr([namespace? `|`]? ident)` - #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] Attr(Attr), /// image-set(url) | url(url) Image(I), diff --git a/servo/components/style/values/generics/image.rs b/servo/components/style/values/generics/image.rs index 6fc0870e15..ca1c716052 100644 --- a/servo/components/style/values/generics/image.rs +++ b/servo/components/style/values/generics/image.rs @@ -41,7 +41,7 @@ pub enum GenericImage<G, ImageUrl, Color, Percentage, Resolution> { /// A paint worklet image. /// <https://drafts.css-houdini.org/css-paint-api/> - #[cfg(feature = "servo-layout-2013")] + #[cfg(feature = "servo")] PaintWorklet(PaintWorklet), /// A `<cross-fade()>` image. Storing this directly inside of @@ -416,7 +416,7 @@ where Image::None => dest.write_str("none"), Image::Url(ref url) => url.to_css(dest), Image::Gradient(ref gradient) => gradient.to_css(dest), - #[cfg(feature = "servo-layout-2013")] + #[cfg(feature = "servo")] Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest), #[cfg(feature = "gecko")] Image::Element(ref selector) => { diff --git a/servo/components/style/values/generics/transform.rs b/servo/components/style/values/generics/transform.rs index 3a65c460a7..4d70e4465c 100644 --- a/servo/components/style/values/generics/transform.rs +++ b/servo/components/style/values/generics/transform.rs @@ -11,7 +11,6 @@ use crate::values::specified::length::Length as SpecifiedLength; use crate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage; use crate::values::{computed, CSSFloat}; use crate::{Zero, ZeroNoPercent}; -use euclid; use euclid::default::{Rect, Transform3D}; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; diff --git a/servo/components/style/values/resolved/mod.rs b/servo/components/style/values/resolved/mod.rs index 675f3cca68..d830474fe6 100644 --- a/servo/components/style/values/resolved/mod.rs +++ b/servo/components/style/values/resolved/mod.rs @@ -95,7 +95,6 @@ trivial_to_resolved_value!(computed::url::ComputedImageUrl); trivial_to_resolved_value!(crate::Namespace); #[cfg(feature = "servo")] trivial_to_resolved_value!(crate::Prefix); -trivial_to_resolved_value!(computed::LengthPercentage); trivial_to_resolved_value!(style_traits::values::specified::AllowedNumericType); trivial_to_resolved_value!(computed::TimingFunction); diff --git a/servo/components/style/values/specified/animation.rs b/servo/components/style/values/specified/animation.rs index 5a1f5003f3..552521711c 100644 --- a/servo/components/style/values/specified/animation.rs +++ b/servo/components/style/values/specified/animation.rs @@ -165,6 +165,12 @@ impl AnimationIterationCount { pub fn one() -> Self { Self::Number(NonNegativeNumber::new(1.0)) } + + /// Returns true if it's `1.0`. + #[inline] + pub fn is_one(&self) -> bool { + *self == Self::one() + } } /// A value for the `animation-name` property. @@ -230,6 +236,17 @@ pub enum AnimationDirection { AlternateReverse, } +impl AnimationDirection { + /// Returns true if the name matches any animation-direction keyword. + #[inline] + pub fn match_keywords(name: &AnimationName) -> bool { + if let Some(name) = name.as_atom() { + return name.with_str(|n| Self::from_ident(n).is_ok()); + } + false + } +} + /// https://drafts.csswg.org/css-animations/#animation-play-state #[derive(Copy, Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] #[repr(u8)] @@ -239,6 +256,17 @@ pub enum AnimationPlayState { Paused, } +impl AnimationPlayState { + /// Returns true if the name matches any animation-play-state keyword. + #[inline] + pub fn match_keywords(name: &AnimationName) -> bool { + if let Some(name) = name.as_atom() { + return name.with_str(|n| Self::from_ident(n).is_ok()); + } + false + } +} + /// https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode #[derive(Copy, Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] #[repr(u8)] @@ -250,6 +278,18 @@ pub enum AnimationFillMode { Both, } +impl AnimationFillMode { + /// Returns true if the name matches any animation-fill-mode keyword. + /// Note: animation-name:none is its initial value, so we don't have to match none here. + #[inline] + pub fn match_keywords(name: &AnimationName) -> bool { + if let Some(atom) = name.as_atom() { + return !name.is_none() && atom.with_str(|n| Self::from_ident(n).is_ok()); + } + false + } +} + /// https://drafts.csswg.org/css-animations-2/#animation-composition #[derive(Copy, Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] #[repr(u8)] diff --git a/servo/components/style/values/specified/basic_shape.rs b/servo/components/style/values/specified/basic_shape.rs index 526296b735..8ed32c40c5 100644 --- a/servo/components/style/values/specified/basic_shape.rs +++ b/servo/components/style/values/specified/basic_shape.rs @@ -14,6 +14,7 @@ use crate::values::generics::basic_shape as generic; use crate::values::generics::basic_shape::{Path, PolygonCoord}; use crate::values::generics::position::{GenericPosition, GenericPositionOrAuto}; use crate::values::generics::rect::Rect; +use crate::values::specified::angle::Angle; use crate::values::specified::border::BorderRadius; use crate::values::specified::image::Image; use crate::values::specified::length::LengthPercentageOrAuto; @@ -40,6 +41,7 @@ pub type ShapePosition = GenericPosition<LengthPercentage, LengthPercentage>; /// A specified basic shape. pub type BasicShape = generic::GenericBasicShape< + Angle, ShapePosition, LengthPercentage, NonNegativeLengthPercentage, @@ -61,6 +63,12 @@ pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>; /// The specified value of `Polygon`. pub type Polygon = generic::GenericPolygon<LengthPercentage>; +/// The specified value of `PathOrShapeFunction`. +pub type PathOrShapeFunction = generic::GenericPathOrShapeFunction<Angle, LengthPercentage>; + +/// The specified value of `ShapeCommand`. +pub type ShapeCommand = generic::GenericShapeCommand<Angle, LengthPercentage>; + /// The specified value of `xywh()`. /// Defines a rectangle via offsets from the top and left edge of the reference box, and a /// specified width and height. @@ -168,8 +176,8 @@ bitflags! { const POLYGON = 1 << 5; /// path(). const PATH = 1 << 6; - // TODO: Bug 1823463. Add shape(). - // const SHAPE = 1 << 7; + /// shape(). + const SHAPE = 1 << 7; /// All flags. const ALL = @@ -179,7 +187,8 @@ bitflags! { Self::CIRCLE.bits() | Self::ELLIPSE.bits() | Self::POLYGON.bits() | - Self::PATH.bits(); + Self::PATH.bits() | + Self::SHAPE.bits(); /// For shape-outside. const SHAPE_OUTSIDE = @@ -329,7 +338,17 @@ impl BasicShape { .map(BasicShape::Polygon) }, "path" if flags.contains(AllowedBasicShapes::PATH) => { - Path::parse_function_arguments(i, shape_type).map(BasicShape::Path) + Path::parse_function_arguments(i, shape_type) + .map(PathOrShapeFunction::Path) + .map(BasicShape::PathOrShape) + }, + "shape" + if flags.contains(AllowedBasicShapes::SHAPE) + && static_prefs::pref!("layout.css.basic-shape-shape.enabled") => + { + generic::Shape::parse_function_arguments(context, i, shape_type) + .map(PathOrShapeFunction::Shape) + .map(BasicShape::PathOrShape) }, _ => Err(location .new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))), @@ -490,7 +509,11 @@ impl Ellipse { } } -fn parse_fill_rule<'i, 't>(input: &mut Parser<'i, 't>, shape_type: ShapeType) -> FillRule { +fn parse_fill_rule<'i, 't>( + input: &mut Parser<'i, 't>, + shape_type: ShapeType, + expect_comma: bool, +) -> FillRule { match shape_type { // Per [1] and [2], we ignore `<fill-rule>` for outline shapes, so always use a default // value. @@ -508,7 +531,9 @@ fn parse_fill_rule<'i, 't>(input: &mut Parser<'i, 't>, shape_type: ShapeType) -> ShapeType::Filled => input .try_parse(|i| -> Result<_, ParseError> { let fill = FillRule::parse(i)?; - i.expect_comma()?; + if expect_comma { + i.expect_comma()?; + } Ok(fill) }) .unwrap_or_default(), @@ -532,7 +557,7 @@ impl Polygon { input: &mut Parser<'i, 't>, shape_type: ShapeType, ) -> Result<Self, ParseError<'i>> { - let fill = parse_fill_rule(input, shape_type); + let fill = parse_fill_rule(input, shape_type, true /* has comma */); let coordinates = input .parse_comma_separated(|i| { Ok(PolygonCoord( @@ -554,7 +579,7 @@ impl Path { ) -> Result<Self, ParseError<'i>> { use crate::values::specified::svg_path::AllowEmpty; - let fill = parse_fill_rule(input, shape_type); + let fill = parse_fill_rule(input, shape_type, true /* has comma */); let path = SVGPathData::parse(input, AllowEmpty::No)?; Ok(Path { fill, path }) } @@ -717,3 +742,168 @@ impl ToComputedValue for BasicShapeRect { Self::Inset(ToComputedValue::from_computed_value(computed)) } } + +impl generic::Shape<Angle, LengthPercentage> { + /// Parse the inner arguments of a `shape` function. + /// shape() = shape(<fill-rule>? from <coordinate-pair>, <shape-command>#) + fn parse_function_arguments<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + shape_type: ShapeType, + ) -> Result<Self, ParseError<'i>> { + let fill = parse_fill_rule(input, shape_type, false /* no following comma */); + + let mut first = true; + let commands = input.parse_comma_separated(|i| { + if first { + first = false; + + // The starting point for the first shape-command. It adds an initial absolute + // moveto to the list of path data commands, with the <coordinate-pair> measured + // from the top-left corner of the reference + i.expect_ident_matching("from")?; + Ok(ShapeCommand::Move { + by_to: generic::ByTo::To, + point: generic::CoordinatePair::parse(context, i)?, + }) + } else { + // The further path data commands. + ShapeCommand::parse(context, i) + } + })?; + + // We must have one starting point and at least one following <shape-command>. + if commands.len() < 2 { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + Ok(Self { + fill, + commands: commands.into(), + }) + } +} + +impl Parse for ShapeCommand { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + use crate::values::generics::basic_shape::{ArcSize, ArcSweep, ByTo, CoordinatePair}; + + // <shape-command> = <move-command> | <line-command> | <hv-line-command> | + // <curve-command> | <smooth-command> | <arc-command> | close + Ok(try_match_ident_ignore_ascii_case! { input, + "close" => Self::Close, + "move" => { + let by_to = ByTo::parse(input)?; + let point = CoordinatePair::parse(context, input)?; + Self::Move { by_to, point } + }, + "line" => { + let by_to = ByTo::parse(input)?; + let point = CoordinatePair::parse(context, input)?; + Self::Line { by_to, point } + }, + "hline" => { + let by_to = ByTo::parse(input)?; + let x = LengthPercentage::parse(context, input)?; + Self::HLine { by_to, x } + }, + "vline" => { + let by_to = ByTo::parse(input)?; + let y = LengthPercentage::parse(context, input)?; + Self::VLine { by_to, y } + }, + "curve" => { + let by_to = ByTo::parse(input)?; + let point = CoordinatePair::parse(context, input)?; + input.expect_ident_matching("via")?; + let control1 = CoordinatePair::parse(context, input)?; + match input.try_parse(|i| CoordinatePair::parse(context, i)) { + Ok(control2) => Self::CubicCurve { + by_to, + point, + control1, + control2, + }, + Err(_) => Self::QuadCurve { + by_to, + point, + control1, + }, + } + }, + "smooth" => { + let by_to = ByTo::parse(input)?; + let point = CoordinatePair::parse(context, input)?; + if input.try_parse(|i| i.expect_ident_matching("via")).is_ok() { + let control2 = CoordinatePair::parse(context, input)?; + Self::SmoothCubic { + by_to, + point, + control2, + } + } else { + Self::SmoothQuad { by_to, point } + } + }, + "arc" => { + let by_to = ByTo::parse(input)?; + let point = CoordinatePair::parse(context, input)?; + input.expect_ident_matching("of")?; + let rx = LengthPercentage::parse(context, input)?; + let ry = input + .try_parse(|i| LengthPercentage::parse(context, i)) + .unwrap_or(rx.clone()); + let radii = CoordinatePair::new(rx, ry); + + // [<arc-sweep> || <arc-size> || rotate <angle>]? + let mut arc_sweep = None; + let mut arc_size = None; + let mut rotate = None; + loop { + if arc_sweep.is_none() { + arc_sweep = input.try_parse(ArcSweep::parse).ok(); + } + + if arc_size.is_none() { + arc_size = input.try_parse(ArcSize::parse).ok(); + if arc_size.is_some() { + continue; + } + } + + if rotate.is_none() + && input + .try_parse(|i| i.expect_ident_matching("rotate")) + .is_ok() + { + rotate = Some(Angle::parse(context, input)?); + continue; + } + break; + } + Self::Arc { + by_to, + point, + radii, + arc_sweep: arc_sweep.unwrap_or(ArcSweep::Ccw), + arc_size: arc_size.unwrap_or(ArcSize::Small), + rotate: rotate.unwrap_or(Angle::zero()), + } + }, + }) + } +} + +impl Parse for generic::CoordinatePair<LengthPercentage> { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let x = LengthPercentage::parse(context, input)?; + let y = LengthPercentage::parse(context, input)?; + Ok(Self::new(x, y)) + } +} diff --git a/servo/components/style/values/specified/box.rs b/servo/components/style/values/specified/box.rs index 8414591c2b..ee50227504 100644 --- a/servo/components/style/values/specified/box.rs +++ b/servo/components/style/values/specified/box.rs @@ -19,12 +19,12 @@ use std::fmt::{self, Write}; use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; -#[cfg(not(feature = "servo-layout-2020"))] +#[cfg(not(feature = "servo"))] fn flexbox_enabled() -> bool { true } -#[cfg(feature = "servo-layout-2020")] +#[cfg(feature = "servo")] fn flexbox_enabled() -> bool { servo_config::prefs::pref_map() .get("layout.flexbox.enabled") @@ -42,9 +42,7 @@ pub enum DisplayOutside { None = 0, Inline, Block, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableCaption, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] InternalTable, #[cfg(feature = "gecko")] InternalRuby, @@ -55,28 +53,19 @@ pub enum DisplayOutside { #[repr(u8)] pub enum DisplayInside { None = 0, - #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] Contents, Flow, FlowRoot, Flex, #[cfg(feature = "gecko")] Grid, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] Table, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableRowGroup, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableColumn, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableColumnGroup, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableHeaderGroup, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableFooterGroup, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableRow, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] TableCell, #[cfg(feature = "gecko")] Ruby, @@ -145,7 +134,6 @@ impl Display { /// ::new() inlined so cbindgen can use it pub const None: Self = Self(((DisplayOutside::None as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::None as u16); - #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] pub const Contents: Self = Self( ((DisplayOutside::None as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Contents as u16, ); @@ -170,14 +158,11 @@ impl Display { #[cfg(feature = "gecko")] pub const InlineGrid: Self = Self(((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Grid as u16); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const Table: Self = Self(((DisplayOutside::Block as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Table as u16); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const InlineTable: Self = Self( ((DisplayOutside::Inline as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Table as u16, ); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableCaption: Self = Self( ((DisplayOutside::TableCaption as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::Flow as u16, ); @@ -195,37 +180,30 @@ impl Display { // Internal table boxes. - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableRowGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableRowGroup as u16, ); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableHeaderGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableHeaderGroup as u16, ); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableFooterGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableFooterGroup as u16, ); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableColumn: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableColumn as u16, ); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableColumnGroup: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableColumnGroup as u16, ); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableRow: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableRow as u16, ); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const TableCell: Self = Self( ((DisplayOutside::InternalTable as u16) << Self::OUTSIDE_SHIFT) | DisplayInside::TableCell as u16, @@ -336,7 +314,6 @@ impl Display { pub fn is_atomic_inline_level(&self) -> bool { match *self { Display::InlineBlock | Display::InlineFlex => true, - #[cfg(any(feature = "servo-layout-2013"))] Display::InlineTable => true, _ => false, } @@ -373,7 +350,6 @@ impl Display { /// /// Also used for :root style adjustments. pub fn equivalent_block_display(&self, _is_root_element: bool) -> Self { - #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] { // Special handling for `contents` and `list-item`s on the root element. if _is_root_element && (self.is_contents() || self.is_list_item()) { @@ -392,7 +368,6 @@ impl Display { Display::from3(DisplayOutside::Block, inside, self.is_list_item()) }, DisplayOutside::Block | DisplayOutside::None => *self, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] _ => Display::Block, } } @@ -419,7 +394,6 @@ impl Display { #[inline] pub fn is_contents(&self) -> bool { match *self { - #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] Display::Contents => true, _ => false, } @@ -444,30 +418,20 @@ impl DisplayKeyword { use self::DisplayKeyword::*; Ok(try_match_ident_ignore_ascii_case! { input, "none" => Full(Display::None), - #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] "contents" => Full(Display::Contents), "inline-block" => Full(Display::InlineBlock), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "inline-table" => Full(Display::InlineTable), "-webkit-flex" if flexbox_enabled() => Full(Display::Flex), "inline-flex" | "-webkit-inline-flex" if flexbox_enabled() => Full(Display::InlineFlex), #[cfg(feature = "gecko")] "inline-grid" => Full(Display::InlineGrid), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-caption" => Full(Display::TableCaption), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-row-group" => Full(Display::TableRowGroup), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-header-group" => Full(Display::TableHeaderGroup), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-footer-group" => Full(Display::TableFooterGroup), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-column" => Full(Display::TableColumn), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-column-group" => Full(Display::TableColumnGroup), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-row" => Full(Display::TableRow), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table-cell" => Full(Display::TableCell), #[cfg(feature = "gecko")] "ruby-base" => Full(Display::RubyBase), @@ -493,9 +457,7 @@ impl DisplayKeyword { /// https://drafts.csswg.org/css-display/#typedef-display-inside "flow" => Inside(DisplayInside::Flow), "flex" if flexbox_enabled() => Inside(DisplayInside::Flex), - #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] "flow-root" => Inside(DisplayInside::FlowRoot), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] "table" => Inside(DisplayInside::Table), #[cfg(feature = "gecko")] "grid" => Inside(DisplayInside::Grid), @@ -517,13 +479,11 @@ impl ToCss for Display { Display::InlineBlock => dest.write_str("inline-block"), #[cfg(feature = "gecko")] Display::WebkitInlineBox => dest.write_str("-webkit-inline-box"), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] Display::TableCaption => dest.write_str("table-caption"), _ => match (outside, inside) { #[cfg(feature = "gecko")] (DisplayOutside::Inline, DisplayInside::Grid) => dest.write_str("inline-grid"), (DisplayOutside::Inline, DisplayInside::Flex) => dest.write_str("inline-flex"), - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] (DisplayOutside::Inline, DisplayInside::Table) => dest.write_str("inline-table"), #[cfg(feature = "gecko")] (DisplayOutside::Block, DisplayInside::Ruby) => dest.write_str("block ruby"), @@ -1607,18 +1567,12 @@ pub enum Appearance { TabScrollArrowBack, #[parse(condition = "ParserContext::chrome_rules_enabled")] TabScrollArrowForward, - /// A toolbar in an application window. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Toolbar, /// A single toolbar button (with no associated dropdown). #[parse(condition = "ParserContext::chrome_rules_enabled")] Toolbarbutton, /// The dropdown portion of a toolbar button #[parse(condition = "ParserContext::chrome_rules_enabled")] ToolbarbuttonDropdown, - /// The toolbox that contains the toolbars. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Toolbox, /// A tooltip. #[parse(condition = "ParserContext::chrome_rules_enabled")] Tooltip, diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs index 3694b4e9bc..f823ba7d30 100644 --- a/servo/components/style/values/specified/color.rs +++ b/servo/components/style/values/specified/color.rs @@ -7,21 +7,20 @@ use super::AllowQuirks; use crate::color::component::ColorComponent; use crate::color::convert::normalize_hue; -use crate::color::parsing::{self, FromParsedColor, NumberOrAngle, NumberOrPercentage}; +use crate::color::parsing::{ + self, ColorParser, FromParsedColor, NumberOrAngle, NumberOrPercentage, +}; use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace}; use crate::media_queries::Device; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; -use crate::values::generics::calc::CalcUnits; use crate::values::generics::color::{ ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto, }; -use crate::values::specified::calc::{CalcNode, Leaf}; use crate::values::specified::Percentage; use crate::values::{normalize, CustomIdent}; use cssparser::color::OPAQUE; use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token}; -use itoa; use std::fmt::{self, Write}; use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; @@ -623,156 +622,6 @@ impl FromParsedColor for Color { } } -struct ColorParser<'a, 'b: 'a>(&'a ParserContext<'b>); - -impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> { - type Output = Color; - - fn parse_number_or_angle<'t>( - &self, - input: &mut Parser<'i, 't>, - allow_none: bool, - ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> { - use crate::values::specified::Angle; - - let location = input.current_source_location(); - let token = input.next()?.clone(); - Ok(match token { - Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { - ColorComponent::None - }, - Token::Dimension { - value, ref unit, .. - } => { - let angle = Angle::parse_dimension(value, unit, /* from_calc = */ false); - - let degrees = match angle { - Ok(angle) => angle.degrees(), - Err(()) => return Err(location.new_unexpected_token_error(token.clone())), - }; - - ColorComponent::Value(NumberOrAngle::Angle { degrees }) - }, - Token::Number { value, .. } => ColorComponent::Value(NumberOrAngle::Number { value }), - Token::Function(ref name) => { - let function = CalcNode::math_function(self.0, name, location)?; - let node = CalcNode::parse(self.0, input, function, CalcUnits::ANGLE)?; - - // If we can resolve the calc node, then use the value. - match node.resolve() { - Ok(Leaf::Number(value)) => { - ColorComponent::Value(NumberOrAngle::Number { value }) - }, - Ok(Leaf::Angle(angle)) => ColorComponent::Value(NumberOrAngle::Angle { - degrees: angle.degrees(), - }), - _ => { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - }, - } - }, - t => return Err(location.new_unexpected_token_error(t)), - }) - } - - fn parse_percentage<'t>( - &self, - input: &mut Parser<'i, 't>, - allow_none: bool, - ) -> Result<ColorComponent<f32>, ParseError<'i>> { - let location = input.current_source_location(); - - Ok(match *input.next()? { - Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { - ColorComponent::None - }, - Token::Percentage { unit_value, .. } => ColorComponent::Value(unit_value), - Token::Function(ref name) => { - let function = CalcNode::math_function(self.0, name, location)?; - let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?; - - // If we can resolve the calc node, then use the value. - let Ok(resolved_leaf) = node.resolve() else { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - }; - if let Leaf::Percentage(value) = resolved_leaf { - ColorComponent::Value(value) - } else { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - }, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - }) - } - - fn parse_number<'t>( - &self, - input: &mut Parser<'i, 't>, - allow_none: bool, - ) -> Result<ColorComponent<f32>, ParseError<'i>> { - let location = input.current_source_location(); - - Ok(match *input.next()? { - Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { - ColorComponent::None - }, - Token::Number { value, .. } => ColorComponent::Value(value), - Token::Function(ref name) => { - let function = CalcNode::math_function(self.0, name, location)?; - let node = CalcNode::parse(self.0, input, function, CalcUnits::empty())?; - - // If we can resolve the calc node, then use the value. - let Ok(resolved_leaf) = node.resolve() else { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - }; - if let Leaf::Number(value) = resolved_leaf { - ColorComponent::Value(value) - } else { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - }, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - }) - } - - fn parse_number_or_percentage<'t>( - &self, - input: &mut Parser<'i, 't>, - allow_none: bool, - ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> { - let location = input.current_source_location(); - - Ok(match *input.next()? { - Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => { - ColorComponent::None - }, - Token::Number { value, .. } => { - ColorComponent::Value(NumberOrPercentage::Number { value }) - }, - Token::Percentage { unit_value, .. } => { - ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(self.0, name, location)?; - let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?; - - // If we can resolve the calc node, then use the value. - let Ok(resolved_leaf) = node.resolve() else { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - }; - if let Leaf::Percentage(unit_value) = resolved_leaf { - ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) - } else if let Leaf::Number(value) = resolved_leaf { - ColorComponent::Value(NumberOrPercentage::Number { value }) - } else { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - }, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - }) - } -} - /// Whether to preserve authored colors during parsing. That's useful only if we /// plan to serialize the color back. #[derive(Copy, Clone)] @@ -809,7 +658,7 @@ impl Color { }, }; - let color_parser = ColorParser(&*context); + let color_parser = ColorParser { context: &context }; match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) { Ok(mut color) => { if let Color::Absolute(ref mut absolute) = color { diff --git a/servo/components/style/values/specified/counters.rs b/servo/components/style/values/specified/counters.rs index 9d8261ce6c..7760be91d7 100644 --- a/servo/components/style/values/specified/counters.rs +++ b/servo/components/style/values/specified/counters.rs @@ -4,7 +4,7 @@ //! Specified types for counter properties. -#[cfg(feature = "servo-layout-2013")] +#[cfg(feature = "servo")] use crate::computed_values::list_style_type::T as ListStyleType; use crate::parser::{Parse, ParserContext}; use crate::values::generics::counters as generics; @@ -12,12 +12,10 @@ use crate::values::generics::counters::CounterPair; #[cfg(feature = "gecko")] use crate::values::generics::CounterStyle; use crate::values::specified::image::Image; -#[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] use crate::values::specified::Attr; use crate::values::specified::Integer; use crate::values::CustomIdent; use cssparser::{Parser, Token}; -#[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] use selectors::parser::SelectorParseErrorKind; use style_traits::{ParseError, StyleParseErrorKind}; @@ -151,7 +149,7 @@ pub type Content = generics::GenericContent<Image>; pub type ContentItem = generics::GenericContentItem<Image>; impl Content { - #[cfg(feature = "servo-layout-2013")] + #[cfg(feature = "servo")] fn parse_counter_style(_: &ParserContext, input: &mut Parser) -> ListStyleType { input .try_parse(|input| { @@ -197,7 +195,6 @@ impl Parse for Content { let mut content = vec![]; let mut has_alt_content = false; loop { - #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] { if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) { content.push(generics::ContentItem::Image(image)); @@ -212,13 +209,11 @@ impl Parse for Content { }, Ok(&Token::Function(ref name)) => { let result = match_ignore_ascii_case! { &name, - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] "counter" => input.parse_nested_block(|input| { let name = CustomIdent::parse(input, &[])?; let style = Content::parse_counter_style(context, input); Ok(generics::ContentItem::Counter(name, style)) }), - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] "counters" => input.parse_nested_block(|input| { let name = CustomIdent::parse(input, &[])?; input.expect_comma()?; @@ -226,7 +221,6 @@ impl Parse for Content { let style = Content::parse_counter_style(context, input); Ok(generics::ContentItem::Counters(name, separator, style)) }), - #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] "attr" => input.parse_nested_block(|input| { Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?)) }), @@ -240,7 +234,6 @@ impl Parse for Content { }?; content.push(result); }, - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] Ok(&Token::Ident(ref ident)) => { content.push(match_ignore_ascii_case! { &ident, "open-quote" => generics::ContentItem::OpenQuote, diff --git a/servo/components/style/values/specified/easing.rs b/servo/components/style/values/specified/easing.rs index 5e4d8ae1ea..7697e6a5d7 100644 --- a/servo/components/style/values/specified/easing.rs +++ b/servo/components/style/values/specified/easing.rs @@ -9,7 +9,7 @@ use crate::values::computed::easing::TimingFunction as ComputedTimingFunction; use crate::values::computed::{Context, ToComputedValue}; use crate::values::generics::easing::TimingFunction as GenericTimingFunction; use crate::values::generics::easing::{StepPosition, TimingKeyword}; -use crate::values::specified::{Integer, Number, Percentage}; +use crate::values::specified::{AnimationName, Integer, Number, Percentage}; use cssparser::{Delimiter, Parser, Token}; use selectors::parser::SelectorParseErrorKind; use style_traits::{ParseError, StyleParseErrorKind}; @@ -140,6 +140,15 @@ impl TimingFunction { Ok(GenericTimingFunction::LinearFunction(builder.build())) } + + /// Returns true if the name matches any keyword. + #[inline] + pub fn match_keywords(name: &AnimationName) -> bool { + if let Some(name) = name.as_atom() { + return name.with_str(|n| TimingKeyword::from_ident(n).is_ok()); + } + false + } } // We need this for converting the specified TimingFunction into computed TimingFunction without diff --git a/servo/components/style/values/specified/effects.rs b/servo/components/style/values/specified/effects.rs index 0453582768..a32390e148 100644 --- a/servo/components/style/values/specified/effects.rs +++ b/servo/components/style/values/specified/effects.rs @@ -28,7 +28,7 @@ use crate::values::specified::{Angle, Number, NumberOrPercentage}; #[cfg(feature = "servo")] use crate::values::Impossible; use crate::Zero; -use cssparser::{self, BasicParseErrorKind, Parser, Token}; +use cssparser::{BasicParseErrorKind, Parser, Token}; use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind}; /// A specified value for a single shadow of the `box-shadow` property. diff --git a/servo/components/style/values/specified/image.rs b/servo/components/style/values/specified/image.rs index 76bbbf85df..bedced1a27 100644 --- a/servo/components/style/values/specified/image.rs +++ b/servo/components/style/values/specified/image.rs @@ -241,7 +241,7 @@ impl Image { let function = input.expect_function()?.clone(); input.parse_nested_block(|input| { Ok(match_ignore_ascii_case! { &function, - #[cfg(feature = "servo-layout-2013")] + #[cfg(feature = "servo")] "paint" => Self::PaintWorklet(PaintWorklet::parse_args(context, input)?), "cross-fade" if cross_fade_enabled() => Self::CrossFade(Box::new(CrossFade::parse_args(context, input, cors_mode, flags)?)), #[cfg(feature = "gecko")] diff --git a/servo/components/style/values/specified/length.rs b/servo/components/style/values/specified/length.rs index d2e1d7d346..17ff43cd6f 100644 --- a/servo/components/style/values/specified/length.rs +++ b/servo/components/style/values/specified/length.rs @@ -97,9 +97,15 @@ pub enum LineHeightBase { impl FontBaseSize { /// Calculate the actual size for a given context pub fn resolve(&self, context: &Context) -> computed::FontSize { + let style = context.style(); match *self { - Self::CurrentStyle => context.style().get_font().clone_font_size(), - Self::InheritedStyle => context.style().get_parent_font().clone_font_size(), + Self::CurrentStyle => style.get_font().clone_font_size(), + Self::InheritedStyle => { + // If we're using the size from our inherited style, we still need to apply our + // own zoom. + let zoom = style.get_box().clone_zoom(); + style.get_parent_font().clone_font_size().zoom(zoom) + }, } } } @@ -351,7 +357,7 @@ impl FontRelativeLength { let reference_size = if context.builder.is_root_element || context.in_media_query { reference_font_size.computed_size() } else { - context.device().root_font_size() + context.device().root_font_size().zoom(context.builder.effective_zoom) }; (reference_size, length) }, @@ -394,19 +400,19 @@ impl FontRelativeLength { // When specified on the root element, the rlh units refer // to the initial values of font and line-height properties. // - let reference_size: CSSPixelLength = - if context.builder.is_root_element || context.in_media_query { - context - .device() - .calc_line_height( - &context.default_style().get_font(), - context.style().writing_mode, - None, - ) - .0 - } else { - context.device().root_line_height() - }; + let reference_size = if context.builder.is_root_element || context.in_media_query { + context + .device() + .calc_line_height( + &context.default_style().get_font(), + context.style().writing_mode, + None, + ) + .0 + } else { + context.device().root_line_height() + }; + let reference_size = reference_size.zoom(context.builder.effective_zoom); (reference_size, length) }, } @@ -668,7 +674,7 @@ impl ViewportPercentageLength { pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength { let (variant, unit, factor) = self.unpack(); let size = context.viewport_size_for_viewport_unit_resolution(variant); - let length = match unit { + let length: app_units::Au = match unit { ViewportUnit::Vw => size.width, ViewportUnit::Vh => size.height, ViewportUnit::Vmin => cmp::min(size.width, size.height), @@ -686,13 +692,15 @@ impl ViewportPercentageLength { }, }; + // NOTE: This is in app units! + let length = context.builder.effective_zoom.zoom(length.0 as f32); + // FIXME: Bug 1396535, we need to fix the extremely small viewport length for transform. - // See bug 989802. We truncate so that adding multiple viewport units - // that add up to 100 does not overflow due to rounding differences. - // We convert appUnits to CSS px manually here to avoid premature clamping by - // going through the Au type. + // See bug 989802. We truncate so that adding multiple viewport units that add up to 100 + // does not overflow due to rounding differences. We convert appUnits to CSS px manually + // here to avoid premature clamping by going through the Au type. let trunc_scaled = - ((length.0 as f64 * factor as f64 / 100.).trunc() / AU_PER_PX as f64) as f32; + ((length as f64 * factor as f64 / 100.).trunc() / AU_PER_PX as f64) as f32; CSSPixelLength::new(crate::values::normalize(trunc_scaled)) } } @@ -797,7 +805,7 @@ impl ToComputedValue for AbsoluteLength { type ComputedValue = CSSPixelLength; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - CSSPixelLength::new(context.builder.effective_zoom().zoom(self.to_px())).finite() + CSSPixelLength::new(self.to_px()).zoom(context.builder.effective_zoom).finite() } fn from_computed_value(computed: &Self::ComputedValue) -> Self { @@ -910,6 +918,9 @@ impl ContainerRelativeLength { .builder .add_flags(ComputedValueFlags::USES_CONTAINER_UNITS); + // TODO(emilio, bug 1894104): Need to handle zoom here, probably something like + // container_zoom - effective_zoom or so. See + // https://github.com/w3c/csswg-drafts/issues/10268 let size = context.get_container_size_query(); let (factor, container_length) = match *self { Self::Cqw(v) => (v, size.get_container_width(context)), diff --git a/servo/components/style/values/specified/motion.rs b/servo/components/style/values/specified/motion.rs index 98858c712c..15c76bc640 100644 --- a/servo/components/style/values/specified/motion.rs +++ b/servo/components/style/values/specified/motion.rs @@ -76,10 +76,6 @@ impl Parse for RayFunction { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>> { - if !static_prefs::pref!("layout.css.motion-path-ray.enabled") { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - input.expect_function_matching("ray")?; input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) } @@ -154,11 +150,8 @@ impl Parse for OffsetPathFunction { // <offset-path> = <ray()> | <url> | <basic-shape> // https://drafts.fxtf.org/motion-1/#typedef-offset-path - - if static_prefs::pref!("layout.css.motion-path-ray.enabled") { - if let Ok(ray) = input.try_parse(|i| RayFunction::parse(context, i)) { - return Ok(OffsetPathFunction::Ray(ray)); - } + if let Ok(ray) = input.try_parse(|i| RayFunction::parse(context, i)) { + return Ok(OffsetPathFunction::Ray(ray)); } if static_prefs::pref!("layout.css.motion-path-url.enabled") { @@ -167,13 +160,7 @@ impl Parse for OffsetPathFunction { } } - let allowed_shapes = if static_prefs::pref!("layout.css.motion-path-basic-shapes.enabled") { - AllowedBasicShapes::ALL - } else { - AllowedBasicShapes::PATH - }; - - BasicShape::parse(context, input, allowed_shapes, ShapeType::Outline) + BasicShape::parse(context, input, AllowedBasicShapes::ALL, ShapeType::Outline) .map(OffsetPathFunction::Shape) } } @@ -197,9 +184,7 @@ impl Parse for OffsetPath { .ok(); } - if static_prefs::pref!("layout.css.motion-path-coord-box.enabled") && - coord_box.is_none() - { + if coord_box.is_none() { coord_box = input.try_parse(CoordBox::parse).ok(); if coord_box.is_some() { continue; diff --git a/servo/components/style/values/specified/svg_path.rs b/servo/components/style/values/specified/svg_path.rs index 1eb9866dd1..56342b48bb 100644 --- a/servo/components/style/values/specified/svg_path.rs +++ b/servo/components/style/values/specified/svg_path.rs @@ -5,10 +5,13 @@ //! Specified types for SVG Path. use crate::parser::{Parse, ParserContext}; -use crate::values::animated::{lists, Animate, Procedure, ToAnimatedZero}; +use crate::values::animated::{lists, Animate, Procedure}; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::basic_shape::GenericShapeCommand; +use crate::values::generics::basic_shape::{ArcSize, ArcSweep, ByTo, CoordinatePair}; use crate::values::CSSFloat; use cssparser::Parser; +use num_traits::FromPrimitive; use std::fmt::{self, Write}; use std::iter::{Cloned, Peekable}; use std::slice; @@ -70,6 +73,7 @@ impl SVGPathData { #[cfg(feature = "gecko")] pub fn decode_from_f32_array(path: &[f32]) -> Result<Self, ()> { use crate::gecko_bindings::structs::dom::SVGPathSeg_Binding::*; + use crate::values::generics::basic_shape::GenericShapeCommand::*; let mut result: Vec<PathCommand> = Vec::new(); let mut i: usize = 0; @@ -80,85 +84,84 @@ impl SVGPathData { let seg_type = path[i].to_bits() as u16; i = i + 1; match seg_type { - PATHSEG_CLOSEPATH => result.push(PathCommand::ClosePath), + PATHSEG_CLOSEPATH => result.push(Close), PATHSEG_MOVETO_ABS | PATHSEG_MOVETO_REL => { debug_assert!(i + 1 < path.len()); - result.push(PathCommand::MoveTo { + result.push(Move { point: CoordPair::new(path[i], path[i + 1]), - absolute: IsAbsolute::new(seg_type == PATHSEG_MOVETO_ABS), + by_to: ByTo::new(seg_type == PATHSEG_MOVETO_ABS), }); i = i + 2; }, PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => { debug_assert!(i + 1 < path.len()); - result.push(PathCommand::LineTo { + result.push(Line { point: CoordPair::new(path[i], path[i + 1]), - absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_ABS), + by_to: ByTo::new(seg_type == PATHSEG_LINETO_ABS), }); i = i + 2; }, PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => { debug_assert!(i + 5 < path.len()); - result.push(PathCommand::CurveTo { + result.push(CubicCurve { control1: CoordPair::new(path[i], path[i + 1]), control2: CoordPair::new(path[i + 2], path[i + 3]), point: CoordPair::new(path[i + 4], path[i + 5]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS), + by_to: ByTo::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS), }); i = i + 6; }, PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => { debug_assert!(i + 3 < path.len()); - result.push(PathCommand::QuadBezierCurveTo { + result.push(QuadCurve { control1: CoordPair::new(path[i], path[i + 1]), point: CoordPair::new(path[i + 2], path[i + 3]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS), + by_to: ByTo::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS), }); i = i + 4; }, PATHSEG_ARC_ABS | PATHSEG_ARC_REL => { debug_assert!(i + 6 < path.len()); - result.push(PathCommand::EllipticalArc { - rx: path[i], - ry: path[i + 1], - angle: path[i + 2], - large_arc_flag: ArcFlag(path[i + 3] != 0.0f32), - sweep_flag: ArcFlag(path[i + 4] != 0.0f32), + result.push(Arc { + radii: CoordPair::new(path[i], path[i + 1]), + rotate: path[i + 2], + arc_size: ArcSize::from_u8((path[i + 3] != 0.0f32) as u8).unwrap(), + arc_sweep: ArcSweep::from_u8((path[i + 4] != 0.0f32) as u8).unwrap(), point: CoordPair::new(path[i + 5], path[i + 6]), - absolute: IsAbsolute::new(seg_type == PATHSEG_ARC_ABS), + by_to: ByTo::new(seg_type == PATHSEG_ARC_ABS), }); i = i + 7; }, PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => { debug_assert!(i < path.len()); - result.push(PathCommand::HorizontalLineTo { + result.push(HLine { x: path[i], - absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS), + by_to: ByTo::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS), }); i = i + 1; }, PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => { debug_assert!(i < path.len()); - result.push(PathCommand::VerticalLineTo { + result.push(VLine { y: path[i], - absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS), + by_to: ByTo::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS), }); i = i + 1; }, PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => { debug_assert!(i + 3 < path.len()); - result.push(PathCommand::SmoothCurveTo { + result.push(SmoothCubic { control2: CoordPair::new(path[i], path[i + 1]), point: CoordPair::new(path[i + 2], path[i + 3]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS), + by_to: ByTo::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS), }); i = i + 4; }, PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => { debug_assert!(i + 1 < path.len()); - result.push(PathCommand::SmoothQuadBezierCurveTo { + result.push(SmoothQuad { point: CoordPair::new(path[i], path[i + 1]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS), + by_to: ByTo::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS), }); i = i + 2; }, @@ -215,7 +218,7 @@ impl ToCss for SVGPathData { { let mut writer = SequenceWriter::new(dest, " "); for command in self.commands() { - writer.item(command)?; + writer.write_item(|inner| command.to_css_for_svg(inner))?; } } dest.write_char('"') @@ -268,79 +271,7 @@ impl ComputeSquaredDistance for SVGPathData { /// points of the Bézier curve in the spec. /// /// https://www.w3.org/TR/SVG11/paths.html#PathData -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(C, u8)] -pub enum PathCommand { - /// The unknown type. - /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN - Unknown, - /// The "moveto" command. - MoveTo { - point: CoordPair, - absolute: IsAbsolute, - }, - /// The "lineto" command. - LineTo { - point: CoordPair, - absolute: IsAbsolute, - }, - /// The horizontal "lineto" command. - HorizontalLineTo { x: CSSFloat, absolute: IsAbsolute }, - /// The vertical "lineto" command. - VerticalLineTo { y: CSSFloat, absolute: IsAbsolute }, - /// The cubic Bézier curve command. - CurveTo { - control1: CoordPair, - control2: CoordPair, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The smooth curve command. - SmoothCurveTo { - control2: CoordPair, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The quadratic Bézier curve command. - QuadBezierCurveTo { - control1: CoordPair, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The smooth quadratic Bézier curve command. - SmoothQuadBezierCurveTo { - point: CoordPair, - absolute: IsAbsolute, - }, - /// The elliptical arc curve command. - EllipticalArc { - rx: CSSFloat, - ry: CSSFloat, - angle: CSSFloat, - large_arc_flag: ArcFlag, - sweep_flag: ArcFlag, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The "closepath" command. - ClosePath, -} +pub type PathCommand = GenericShapeCommand<CSSFloat, CSSFloat>; /// For internal SVGPath normalization. #[allow(missing_docs)] @@ -355,177 +286,157 @@ impl PathCommand { /// /// See discussion: https://github.com/w3c/svgwg/issues/321 fn normalize(&self, state: &mut PathTraversalState) -> Self { - use self::PathCommand::*; + use crate::values::generics::basic_shape::GenericShapeCommand::*; match *self { - Unknown => Unknown, - ClosePath => { + Close => { state.pos = state.subpath_start; - ClosePath + Close }, - MoveTo { - mut point, - absolute, - } => { - if !absolute.is_yes() { + Move { by_to, mut point } => { + if !by_to.is_abs() { point += state.pos; } state.pos = point; state.subpath_start = point; - MoveTo { + Move { + by_to: ByTo::To, point, - absolute: IsAbsolute::Yes, } }, - LineTo { - mut point, - absolute, - } => { - if !absolute.is_yes() { + Line { by_to, mut point } => { + if !by_to.is_abs() { point += state.pos; } state.pos = point; - LineTo { + Line { + by_to: ByTo::To, point, - absolute: IsAbsolute::Yes, } }, - HorizontalLineTo { mut x, absolute } => { - if !absolute.is_yes() { + HLine { by_to, mut x } => { + if !by_to.is_abs() { x += state.pos.x; } state.pos.x = x; - HorizontalLineTo { - x, - absolute: IsAbsolute::Yes, - } + HLine { by_to: ByTo::To, x } }, - VerticalLineTo { mut y, absolute } => { - if !absolute.is_yes() { + VLine { by_to, mut y } => { + if !by_to.is_abs() { y += state.pos.y; } state.pos.y = y; - VerticalLineTo { - y, - absolute: IsAbsolute::Yes, - } + VLine { by_to: ByTo::To, y } }, - CurveTo { + CubicCurve { + by_to, + mut point, mut control1, mut control2, - mut point, - absolute, } => { - if !absolute.is_yes() { + if !by_to.is_abs() { + point += state.pos; control1 += state.pos; control2 += state.pos; - point += state.pos; } state.pos = point; - CurveTo { + CubicCurve { + by_to: ByTo::To, + point, control1, control2, - point, - absolute: IsAbsolute::Yes, } }, - SmoothCurveTo { - mut control2, + QuadCurve { + by_to, mut point, - absolute, + mut control1, } => { - if !absolute.is_yes() { - control2 += state.pos; + if !by_to.is_abs() { point += state.pos; + control1 += state.pos; } state.pos = point; - SmoothCurveTo { - control2, + QuadCurve { + by_to: ByTo::To, point, - absolute: IsAbsolute::Yes, + control1, } }, - QuadBezierCurveTo { - mut control1, + SmoothCubic { + by_to, mut point, - absolute, + mut control2, } => { - if !absolute.is_yes() { - control1 += state.pos; + if !by_to.is_abs() { point += state.pos; + control2 += state.pos; } state.pos = point; - QuadBezierCurveTo { - control1, + SmoothCubic { + by_to: ByTo::To, point, - absolute: IsAbsolute::Yes, + control2, } }, - SmoothQuadBezierCurveTo { - mut point, - absolute, - } => { - if !absolute.is_yes() { + SmoothQuad { by_to, mut point } => { + if !by_to.is_abs() { point += state.pos; } state.pos = point; - SmoothQuadBezierCurveTo { + SmoothQuad { + by_to: ByTo::To, point, - absolute: IsAbsolute::Yes, } }, - EllipticalArc { - rx, - ry, - angle, - large_arc_flag, - sweep_flag, + Arc { + by_to, mut point, - absolute, + radii, + arc_sweep, + arc_size, + rotate, } => { - if !absolute.is_yes() { + if !by_to.is_abs() { point += state.pos; } state.pos = point; - EllipticalArc { - rx, - ry, - angle, - large_arc_flag, - sweep_flag, + Arc { + by_to: ByTo::To, point, - absolute: IsAbsolute::Yes, + radii, + arc_sweep, + arc_size, + rotate, } }, } } -} -impl ToCss for PathCommand { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + /// The serialization of the svg path. + fn to_css_for_svg<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write, { - use self::PathCommand::*; + use crate::values::generics::basic_shape::GenericShapeCommand::*; match *self { - Unknown => dest.write_char('X'), - ClosePath => dest.write_char('Z'), - MoveTo { point, absolute } => { - dest.write_char(if absolute.is_yes() { 'M' } else { 'm' })?; + Close => dest.write_char('Z'), + Move { by_to, point } => { + dest.write_char(if by_to.is_abs() { 'M' } else { 'm' })?; dest.write_char(' ')?; point.to_css(dest) }, - LineTo { point, absolute } => { - dest.write_char(if absolute.is_yes() { 'L' } else { 'l' })?; + Line { by_to, point } => { + dest.write_char(if by_to.is_abs() { 'L' } else { 'l' })?; dest.write_char(' ')?; point.to_css(dest) }, - CurveTo { + CubicCurve { + by_to, + point, control1, control2, - point, - absolute, } => { - dest.write_char(if absolute.is_yes() { 'C' } else { 'c' })?; + dest.write_char(if by_to.is_abs() { 'C' } else { 'c' })?; dest.write_char(' ')?; control1.to_css(dest)?; dest.write_char(' ')?; @@ -533,63 +444,60 @@ impl ToCss for PathCommand { dest.write_char(' ')?; point.to_css(dest) }, - QuadBezierCurveTo { - control1, + QuadCurve { + by_to, point, - absolute, + control1, } => { - dest.write_char(if absolute.is_yes() { 'Q' } else { 'q' })?; + dest.write_char(if by_to.is_abs() { 'Q' } else { 'q' })?; dest.write_char(' ')?; control1.to_css(dest)?; dest.write_char(' ')?; point.to_css(dest) }, - EllipticalArc { - rx, - ry, - angle, - large_arc_flag, - sweep_flag, + Arc { + by_to, point, - absolute, + radii, + arc_sweep, + arc_size, + rotate, } => { - dest.write_char(if absolute.is_yes() { 'A' } else { 'a' })?; - dest.write_char(' ')?; - rx.to_css(dest)?; + dest.write_char(if by_to.is_abs() { 'A' } else { 'a' })?; dest.write_char(' ')?; - ry.to_css(dest)?; + radii.to_css(dest)?; dest.write_char(' ')?; - angle.to_css(dest)?; + rotate.to_css(dest)?; dest.write_char(' ')?; - large_arc_flag.to_css(dest)?; + (arc_size as i32).to_css(dest)?; dest.write_char(' ')?; - sweep_flag.to_css(dest)?; + (arc_sweep as i32).to_css(dest)?; dest.write_char(' ')?; point.to_css(dest) }, - HorizontalLineTo { x, absolute } => { - dest.write_char(if absolute.is_yes() { 'H' } else { 'h' })?; + HLine { by_to, x } => { + dest.write_char(if by_to.is_abs() { 'H' } else { 'h' })?; dest.write_char(' ')?; x.to_css(dest) }, - VerticalLineTo { y, absolute } => { - dest.write_char(if absolute.is_yes() { 'V' } else { 'v' })?; + VLine { by_to, y } => { + dest.write_char(if by_to.is_abs() { 'V' } else { 'v' })?; dest.write_char(' ')?; y.to_css(dest) }, - SmoothCurveTo { - control2, + SmoothCubic { + by_to, point, - absolute, + control2, } => { - dest.write_char(if absolute.is_yes() { 'S' } else { 's' })?; + dest.write_char(if by_to.is_abs() { 'S' } else { 's' })?; dest.write_char(' ')?; control2.to_css(dest)?; dest.write_char(' ')?; point.to_css(dest) }, - SmoothQuadBezierCurveTo { point, absolute } => { - dest.write_char(if absolute.is_yes() { 'T' } else { 't' })?; + SmoothQuad { by_to, point } => { + dest.write_char(if by_to.is_abs() { 'T' } else { 't' })?; dest.write_char(' ')?; point.to_css(dest) }, @@ -597,135 +505,8 @@ impl ToCss for PathCommand { } } -/// The path command absolute type. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum IsAbsolute { - Yes, - No, -} - -impl IsAbsolute { - /// Return true if this is IsAbsolute::Yes. - #[inline] - pub fn is_yes(&self) -> bool { - *self == IsAbsolute::Yes - } - - /// Return Yes if value is true. Otherwise, return No. - #[inline] - fn new(value: bool) -> Self { - if value { - IsAbsolute::Yes - } else { - IsAbsolute::No - } - } -} - /// The path coord type. -#[allow(missing_docs)] -#[derive( - AddAssign, - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct CoordPair { - x: CSSFloat, - y: CSSFloat, -} - -impl CoordPair { - /// Create a CoordPair. - #[inline] - pub fn new(x: CSSFloat, y: CSSFloat) -> Self { - CoordPair { x, y } - } -} - -/// The EllipticalArc flag type. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct ArcFlag(bool); - -impl ToCss for ArcFlag { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - (self.0 as i32).to_css(dest) - } -} - -impl Animate for ArcFlag { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - (self.0 as i32) - .animate(&(other.0 as i32), procedure) - .map(|v| ArcFlag(v > 0)) - } -} - -impl ComputeSquaredDistance for ArcFlag { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - (self.0 as i32).compute_squared_distance(&(other.0 as i32)) - } -} - -impl ToAnimatedZero for ArcFlag { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - // The 2 ArcFlags in EllipticalArc determine which one of the 4 different arcs will be - // used. (i.e. From 4 combinations). In other words, if we change the flag, we get a - // different arc. Therefore, we return *self. - // https://svgwg.org/svg2-draft/paths.html#PathDataEllipticalArcCommands - Ok(*self) - } -} +pub type CoordPair = CoordinatePair<CSSFloat>; /// SVG Path parser. struct PathParser<'a> { @@ -736,7 +517,7 @@ struct PathParser<'a> { macro_rules! parse_arguments { ( $parser:ident, - $abs:ident, + $by_to:ident, $enum:ident, [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ] ) => { @@ -747,7 +528,9 @@ macro_rules! parse_arguments { skip_comma_wsp(&mut $parser.chars); let $other_para = $other_func(&mut $parser.chars)?; )* - $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs }); + $parser.path.push( + PathCommand::$enum { $by_to, $para $(, $other_para)* } + ); // End of string or the next character is a possible new command. if !skip_wsp(&mut $parser.chars) || @@ -785,23 +568,23 @@ impl<'a> PathParser<'a> { } let command = self.chars.next().unwrap(); - let abs = if command.is_ascii_uppercase() { - IsAbsolute::Yes + let by_to = if command.is_ascii_uppercase() { + ByTo::To } else { - IsAbsolute::No + ByTo::By }; skip_wsp(&mut self.chars); match command { b'Z' | b'z' => self.parse_closepath(), - b'L' | b'l' => self.parse_lineto(abs), - b'H' | b'h' => self.parse_h_lineto(abs), - b'V' | b'v' => self.parse_v_lineto(abs), - b'C' | b'c' => self.parse_curveto(abs), - b'S' | b's' => self.parse_smooth_curveto(abs), - b'Q' | b'q' => self.parse_quadratic_bezier_curveto(abs), - b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(abs), - b'A' | b'a' => self.parse_elliptical_arc(abs), + b'L' | b'l' => self.parse_lineto(by_to), + b'H' | b'h' => self.parse_h_lineto(by_to), + b'V' | b'v' => self.parse_v_lineto(by_to), + b'C' | b'c' => self.parse_curveto(by_to), + b'S' | b's' => self.parse_smooth_curveto(by_to), + b'Q' | b'q' => self.parse_quadratic_bezier_curveto(by_to), + b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(by_to), + b'A' | b'a' => self.parse_elliptical_arc(by_to), _ => return Err(()), }?; } @@ -817,12 +600,8 @@ impl<'a> PathParser<'a> { skip_wsp(&mut self.chars); let point = parse_coord(&mut self.chars)?; - let absolute = if command == b'M' { - IsAbsolute::Yes - } else { - IsAbsolute::No - }; - self.path.push(PathCommand::MoveTo { point, absolute }); + let by_to = if command == b'M' { ByTo::To } else { ByTo::By }; + self.path.push(PathCommand::Move { by_to, point }); // End of string or the next character is a possible new command. if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) @@ -833,69 +612,74 @@ impl<'a> PathParser<'a> { // If a moveto is followed by multiple pairs of coordinates, the subsequent // pairs are treated as implicit lineto commands. - self.parse_lineto(absolute) + self.parse_lineto(by_to) } /// Parse "closepath" command. fn parse_closepath(&mut self) -> Result<(), ()> { - self.path.push(PathCommand::ClosePath); + self.path.push(PathCommand::Close); Ok(()) } /// Parse "lineto" command. - fn parse_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, LineTo, [ point => parse_coord ]) + fn parse_lineto(&mut self, by_to: ByTo) -> Result<(), ()> { + parse_arguments!(self, by_to, Line, [ point => parse_coord ]) } /// Parse horizontal "lineto" command. - fn parse_h_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ]) + fn parse_h_lineto(&mut self, by_to: ByTo) -> Result<(), ()> { + parse_arguments!(self, by_to, HLine, [ x => parse_number ]) } /// Parse vertical "lineto" command. - fn parse_v_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ]) + fn parse_v_lineto(&mut self, by_to: ByTo) -> Result<(), ()> { + parse_arguments!(self, by_to, VLine, [ y => parse_number ]) } /// Parse cubic Bézier curve command. - fn parse_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, CurveTo, [ + fn parse_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { + parse_arguments!(self, by_to, CubicCurve, [ control1 => parse_coord, control2 => parse_coord, point => parse_coord ]) } /// Parse smooth "curveto" command. - fn parse_smooth_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, SmoothCurveTo, [ + fn parse_smooth_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { + parse_arguments!(self, by_to, SmoothCubic, [ control2 => parse_coord, point => parse_coord ]) } /// Parse quadratic Bézier curve command. - fn parse_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, QuadBezierCurveTo, [ + fn parse_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { + parse_arguments!(self, by_to, QuadCurve, [ control1 => parse_coord, point => parse_coord ]) } /// Parse smooth quadratic Bézier curveto command. - fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ]) + fn parse_smooth_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> { + parse_arguments!(self, by_to, SmoothQuad, [ point => parse_coord ]) } /// Parse elliptical arc curve command. - fn parse_elliptical_arc(&mut self, absolute: IsAbsolute) -> Result<(), ()> { + fn parse_elliptical_arc(&mut self, by_to: ByTo) -> Result<(), ()> { // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). - let parse_flag = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() { - Some(c) if c == b'0' || c == b'1' => Ok(ArcFlag(c == b'1')), + let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() { + Some(c) if c == b'1' => Ok(ArcSize::Large), + Some(c) if c == b'0' => Ok(ArcSize::Small), + _ => Err(()), + }; + let parse_arc_sweep = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() { + Some(c) if c == b'1' => Ok(ArcSweep::Cw), + Some(c) if c == b'0' => Ok(ArcSweep::Ccw), _ => Err(()), }; - parse_arguments!(self, absolute, EllipticalArc, [ - rx => parse_number, - ry => parse_number, - angle => parse_number, - large_arc_flag => parse_flag, - sweep_flag => parse_flag, + parse_arguments!(self, by_to, Arc, [ + radii => parse_coord, + rotate => parse_number, + arc_size => parse_arc_size, + arc_sweep => parse_arc_sweep, point => parse_coord ]) } diff --git a/servo/components/style/values/specified/text.rs b/servo/components/style/values/specified/text.rs index 0e70bd26ac..e26a17ba27 100644 --- a/servo/components/style/values/specified/text.rs +++ b/servo/components/style/values/specified/text.rs @@ -454,7 +454,6 @@ pub enum TextAlignKeyword { Left, Right, Center, - #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] Justify, #[css(skip)] #[cfg(feature = "gecko")] @@ -466,11 +465,11 @@ pub enum TextAlignKeyword { MozLeft, #[cfg(feature = "gecko")] MozRight, - #[cfg(feature = "servo-layout-2013")] + #[cfg(feature = "servo")] ServoCenter, - #[cfg(feature = "servo-layout-2013")] + #[cfg(feature = "servo")] ServoLeft, - #[cfg(feature = "servo-layout-2013")] + #[cfg(feature = "servo")] ServoRight, } diff --git a/servo/components/style_traits/owned_slice.rs b/servo/components/style_traits/owned_slice.rs index 36ba3162e5..c0d4ceff8d 100644 --- a/servo/components/style_traits/owned_slice.rs +++ b/servo/components/style_traits/owned_slice.rs @@ -13,7 +13,7 @@ use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::ptr::NonNull; use std::{fmt, iter, mem, slice}; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; +use to_shmem::{SharedMemoryBuilder, ToShmem}; /// A struct that basically replaces a `Box<[T]>`, but which cbindgen can /// understand. diff --git a/servo/components/style_traits/values.rs b/servo/components/style_traits/values.rs index 58b50d6576..e2154f606a 100644 --- a/servo/components/style_traits/values.rs +++ b/servo/components/style_traits/values.rs @@ -243,8 +243,9 @@ where Self { inner, separator } } + /// Serialize the CSS Value with the specific serialization function. #[inline] - fn write_item<F>(&mut self, f: F) -> fmt::Result + pub fn write_item<F>(&mut self, f: F) -> fmt::Result where F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result, { diff --git a/servo/ports/geckolib/cbindgen.toml b/servo/ports/geckolib/cbindgen.toml index 8e6818e421..3edab4dfbb 100644 --- a/servo/ports/geckolib/cbindgen.toml +++ b/servo/ports/geckolib/cbindgen.toml @@ -51,6 +51,7 @@ derive_neq = true "feature = servo" = "CBINDGEN_IS_SERVO" "feature = servo-layout-2013" = "CBINDGEN_IS_SERVO" "feature = servo-layout-2020" = "CBINDGEN_IS_SERVO" +"feature = track_alloc_size" = "CBINDGEN_IS_SERVO" # These will always be defined. "feature = gecko" = "CBINDGEN_IS_GECKO" "feature = cbindgen" = "CBINDGEN_IS_GECKO" @@ -214,6 +215,7 @@ include = [ "BasicShape", "InsetRect", "ShapeRadius", + "ShapeCommand", "ArcSlice", "ForgottenArcSlicePtr", "HeaderWithLength", @@ -366,7 +368,8 @@ renaming_overrides_prefixing = true "CalcLengthPercentage" = """ inline CSSCoord ResolveToCSSPixels(CSSCoord aBasis) const; - inline nscoord Resolve(nscoord aBasis) const; + template<typename Rounder> + inline nscoord Resolve(nscoord aBasis, Rounder) const; """ "GenericCalcNode" = """ @@ -427,10 +430,10 @@ renaming_overrides_prefixing = true inline bool IsDefinitelyZero() const; inline CSSCoord ResolveToCSSPixels(CSSCoord aPercentageBasisInCSSPixels) const; template<typename T> inline CSSCoord ResolveToCSSPixelsWith(T aPercentageGetter) const; - template<typename T, typename PercentRounder> - inline nscoord Resolve(T aPercentageGetter, PercentRounder) const; - template<typename PercentRounder> - inline nscoord Resolve(nscoord aPercentageBasis, PercentRounder) const; + template<typename T, typename Rounder> + inline nscoord Resolve(T aPercentageGetter, Rounder) const; + template<typename Rounder> + inline nscoord Resolve(nscoord aPercentageBasis, Rounder) const; template<typename T> inline nscoord Resolve(T aPercentageGetter) const; inline nscoord Resolve(nscoord aPercentageBasis) const; """ @@ -757,9 +760,11 @@ renaming_overrides_prefixing = true } """ -"CoordPair" = """ - explicit StyleCoordPair(const gfx::Point& aPoint): x(aPoint.x), y(aPoint.y) {} - gfx::Point ConvertsToGfxPoint() const { return gfx::Point(x, y); } +"CoordinatePair" = """ + inline gfx::Point ToGfxPoint(const CSSSize* aBasis = nullptr) const; + gfx::Point ToGfxPoint(const CSSSize& aBasis) const { + return ToGfxPoint(&aBasis); + }; """ "TextOverflow" = """ @@ -1035,12 +1040,19 @@ renaming_overrides_prefixing = true // Return true if the <basic-shape> is path(). bool IsPath() const { - return IsOffsetPath() && AsOffsetPath().path->IsShape() && - AsOffsetPath().path->AsShape().IsPath(); + if (!IsOffsetPath()) { + return false; + } + const auto& path = AsOffsetPath().path; + if (!path->IsShape()) { + return false; + } + const auto& shape = path->AsShape(); + return shape.IsPathOrShape() && shape.AsPathOrShape().IsPath(); } const StyleSVGPathData& AsSVGPathData() const { - return AsOffsetPath().path->AsShape().AsPath().path; + return AsOffsetPath().path->AsShape().AsPathOrShape().AsPath().path; } // Return true if this is "<basic-shape> || <coord-box>". @@ -1049,6 +1061,11 @@ renaming_overrides_prefixing = true } """ +"GenericShapeCommand" = """ + bool IsCubicType() const { return IsCubicCurve() || IsSmoothCubic(); } + bool IsQuadraticType() const { return IsQuadCurve() || IsSmoothQuad(); } +""" + "GenericContainIntrinsicSize" = """ bool HasAuto() const { return IsAutoLength() || IsAutoNone(); } """ diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index 83c55ad9e0..14614704b7 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -138,8 +138,8 @@ use style::stylesheets::{ CssRules, CssRulesHelpers, DocumentRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, Origin, OriginSet, PagePseudoClassFlags, PageRule, PropertyRule, - SanitizationData, SanitizationKind, StyleRule, StylesheetContents, - StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData, + SanitizationData, SanitizationKind, StartingStyleRule, StyleRule, StylesheetContents, + StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData, ScopeRule, }; use style::stylist::{add_size_of_ua_cache, AuthorStylesEnabled, RuleInclusion, Stylist}; use style::thread_state; @@ -227,7 +227,12 @@ pub unsafe extern "C" fn Servo_Shutdown() { #[inline(always)] unsafe fn dummy_url_data() -> &'static UrlExtraData { - UrlExtraData::from_ptr_ref(&DUMMY_URL_DATA) + UrlExtraData::from_ptr_ref(std::ptr::addr_of!(DUMMY_URL_DATA).as_ref().unwrap()) +} + +#[inline(always)] +unsafe fn dummy_chrome_url_data() -> &'static UrlExtraData { + UrlExtraData::from_ptr_ref(std::ptr::addr_of!(DUMMY_CHROME_URL_DATA).as_ref().unwrap()) } #[allow(dead_code)] @@ -1211,6 +1216,11 @@ fn is_transitionable(prop: PropertyDeclarationId, behavior: computed::Transition if !prop.is_animatable() { return false; } + // TODO(bug 1885995): Return `false` in is_discrete_animatable for interpolatable custom + // property types. + if matches!(prop, PropertyDeclarationId::Custom(..)) { + return true; + } match behavior { computed::TransitionBehavior::Normal => !prop.is_discrete_animatable(), @@ -2157,6 +2167,7 @@ pub extern "C" fn Servo_CssRules_InsertRule( rule: &nsACString, index: u32, containing_rule_types: u32, + parse_relative_rule_type: Option<&CssRuleType>, loader: *mut Loader, allow_import_rules: AllowImportRules, gecko_stylesheet: *mut DomStyleSheet, @@ -2184,6 +2195,7 @@ pub extern "C" fn Servo_CssRules_InsertRule( contents, index as usize, CssRuleTypes::from_bits(containing_rule_types), + parse_relative_rule_type.cloned(), loader, allow_import_rules, ); @@ -2397,7 +2409,8 @@ impl_basic_rule_funcs! { (Namespace, NamespaceRule, NamespaceRule), changed: Servo_StyleSet_NamespaceRuleChanged, } -impl_basic_rule_funcs! { (Page, PageRule, Locked<PageRule>), +impl_group_rule_funcs! { (Page, PageRule, Locked<PageRule>), + get_rules: Servo_PageRule_GetRules, getter: Servo_CssRules_GetPageRuleAt, debug: Servo_PageRule_Debug, to_css: Servo_PageRule_GetCssText, @@ -2478,6 +2491,22 @@ impl_basic_rule_funcs! { (CounterStyle, CounterStyleRule, Locked<CounterStyleRul changed: Servo_StyleSet_CounterStyleRuleChanged, } +impl_group_rule_funcs! { (Scope, ScopeRule, ScopeRule), + get_rules: Servo_ScopeRule_GetRules, + getter: Servo_CssRules_GetScopeRuleAt, + debug: Servo_ScopeRule_Debug, + to_css: Servo_ScopeRule_GetCssText, + changed: Servo_StyleSet_ScopeRuleChanged, +} + +impl_group_rule_funcs! { (StartingStyle, StartingStyleRule, StartingStyleRule), + get_rules: Servo_StartingStyleRule_GetRules, + getter: Servo_CssRules_GetStartingStyleRuleAt, + debug: Servo_StartingStyleRule_Debug, + to_css: Servo_StartingStyleRule_GetCssText, + changed: Servo_StyleSet_StartingStyleRuleChanged, +} + #[no_mangle] pub extern "C" fn Servo_StyleRule_GetStyle( rule: &LockedStyleRule, @@ -5825,11 +5854,11 @@ pub extern "C" fn Servo_CSSSupports( Origin::Author }; let url_data = unsafe { - UrlExtraData::from_ptr_ref(if chrome_sheet { - &DUMMY_CHROME_URL_DATA + if chrome_sheet { + dummy_chrome_url_data() } else { - &DUMMY_URL_DATA - }) + dummy_url_data() + } }; let quirks_mode = if quirks { QuirksMode::Quirks @@ -6963,18 +6992,15 @@ fn inherit_relative_selector_search_direction( ) -> ElementSelectorFlags { let mut inherited = ElementSelectorFlags::empty(); if let Some(parent) = parent { - if let Some(direction) = parent.relative_selector_search_direction() { - inherited |= direction - .intersection(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR); - } + inherited |= parent + .relative_selector_search_direction() + .intersection(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR); } if let Some(sibling) = prev_sibling { - if let Some(direction) = sibling.relative_selector_search_direction() { - // Inherit both, for e.g. a sibling with `:has(~.sibling .descendant)` - inherited |= direction.intersection( - ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING, - ); - } + // Inherit both, for e.g. a sibling with `:has(~.sibling .descendant)` + inherited |= sibling.relative_selector_search_direction().intersection( + ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING, + ); } inherited } @@ -7147,6 +7173,41 @@ pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorStateDependency( ); } +#[no_mangle] +pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorCustomStateDependency( + raw_data: &PerDocumentStyleData, + element: &RawGeckoElement, + state: *mut nsAtom, + snapshots: &ServoElementSnapshotTable, +) { + let data = raw_data.borrow(); + let element = GeckoElement(element); + + let quirks_mode: QuirksMode = data.stylist.quirks_mode(); + let invalidator = RelativeSelectorInvalidator { + element, + quirks_mode, + snapshot_table: Some(snapshots), + invalidated: relative_selector_invalidated_at, + sibling_traversal_map: SiblingTraversalMap::default(), + _marker: std::marker::PhantomData, + }; + + invalidator.invalidate_relative_selectors_for_this( + &data.stylist, + |element, scope, data, _quirks_mode, collector| { + let invalidation_map = data.relative_selector_invalidation_map(); + relative_selector_dependencies_for_custom_state( + state, + *element, + scope, + &invalidation_map, + collector, + ); + }, + ); +} + fn invalidate_relative_selector_prev_sibling_side_effect( prev_sibling: GeckoElement, quirks_mode: QuirksMode, @@ -7304,13 +7365,9 @@ pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorForInsertion( ) { (Some(prev_sibling), Some(next_sibling)) => 'sibling: { // If the prev sibling is not on the sibling search path, skip. - if prev_sibling + if !prev_sibling .relative_selector_search_direction() - .map_or(true, |direction| { - !direction.intersects( - ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING, - ) - }) + .intersects(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) { break 'sibling; } @@ -7429,7 +7486,7 @@ pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorForRemoval( // This element was in-tree, so we can safely say that if it was not on // the relative selector search path, its removal will not invalidate any // relative selector. - if element.relative_selector_search_direction().is_none() { + if element.relative_selector_search_direction().is_empty() { return; } let following_node = following_node.map(GeckoNode); @@ -7493,6 +7550,21 @@ pub extern "C" fn Servo_StyleSet_HasStateDependency( } #[no_mangle] +pub extern "C" fn Servo_StyleSet_HasNthOfCustomStateDependency( + raw_data: &PerDocumentStyleData, + element: &RawGeckoElement, + state: *mut nsAtom, +) -> bool { + let element = GeckoElement(element); + let data = raw_data.borrow(); + data.stylist + .any_applicable_rule_data(element, |data| unsafe { + AtomIdent::with(state, |atom| data.has_nth_of_custom_state_dependency(atom)) + }) +} + + +#[no_mangle] pub extern "C" fn Servo_StyleSet_HasNthOfStateDependency( raw_data: &PerDocumentStyleData, element: &RawGeckoElement, @@ -7682,6 +7754,31 @@ fn relative_selector_dependencies_for_class<'a>( }); } +fn relative_selector_dependencies_for_custom_state<'a>( + state: *const nsAtom, + element: GeckoElement<'a>, + scope: Option<OpaqueElement>, + invalidation_map: &'a RelativeSelectorInvalidationMap, + collector: &mut RelativeSelectorDependencyCollector<'a, GeckoElement<'a>>, +) { + unsafe { + AtomIdent::with(state, |atom| { + match invalidation_map + .map + .custom_state_affecting_selectors + .get(atom) + { + Some(v) => { + for dependency in v { + collector.add_dependency(dependency, element, scope); + } + }, + None => (), + }; + }) + } +} + fn process_relative_selector_invalidations( element: &GeckoElement, snapshot_table: &ServoElementSnapshotTable, @@ -7880,11 +7977,11 @@ pub unsafe extern "C" fn Servo_SelectorList_Parse( ) -> *mut SelectorList { use style::selector_parser::SelectorParser; - let url_data = UrlExtraData::from_ptr_ref(if is_chrome { - &DUMMY_CHROME_URL_DATA + let url_data = if is_chrome { + dummy_chrome_url_data() } else { - &DUMMY_URL_DATA - }); + dummy_url_data() + }; let input = selector_list.as_str_unchecked(); let selector_list = match SelectorParser::parse_author_origin_no_namespace(&input, url_data) { @@ -8647,6 +8744,24 @@ pub extern "C" fn Servo_LayerBlockRule_GetName(rule: &LayerBlockRule, result: &m } #[no_mangle] +pub extern "C" fn Servo_ScopeRule_GetStart(rule: &ScopeRule, result: &mut nsACString) { + if let Some(v) = rule.bounds.start.as_ref() { + v.to_css(&mut CssWriter::new(result)).unwrap(); + } else { + result.set_is_void(true); + } +} + +#[no_mangle] +pub extern "C" fn Servo_ScopeRule_GetEnd(rule: &ScopeRule, result: &mut nsACString) { + if let Some(v) = rule.bounds.end.as_ref() { + v.to_css(&mut CssWriter::new(result)).unwrap(); + } else { + result.set_is_void(true); + } +} + +#[no_mangle] pub extern "C" fn Servo_LayerStatementRule_GetNameCount(rule: &LayerStatementRule) -> usize { rule.names.len() } @@ -9006,23 +9121,20 @@ pub extern "C" fn Servo_GetSelectorWarnings( } #[no_mangle] -pub extern "C" fn Servo_GetRuleBodyTextOffsets( +pub extern "C" fn Servo_GetRuleBodyText( initial_text: &nsACString, - result_start_offset: &mut u32, - result_end_offset: &mut u32, -) -> bool { + ret_val: &mut nsACString, +) { let css_text = unsafe { initial_text.as_str_unchecked() }; let mut input = ParserInput::new(&css_text); let mut input = Parser::new(&mut input); - let mut start_offset = 0; let mut found_start = false; // Search forward for the opening brace. while let Ok(token) = input.next() { match *token { Token::CurlyBracketBlock => { - start_offset = input.position().byte_index(); found_start = true; break; }, @@ -9030,13 +9142,14 @@ pub extern "C" fn Servo_GetRuleBodyTextOffsets( } if token.is_parse_error() { - return false; + break; } } if !found_start { - return false; + ret_val.set_is_void(true); + return; } let token_start = input.position(); @@ -9046,16 +9159,121 @@ pub extern "C" fn Servo_GetRuleBodyTextOffsets( Ok(()) } ); - let mut end_offset = input.position().byte_index(); + + // We're not guaranteed to have a closing bracket, but when we do, we need to move + // the end offset before it. + let mut token_slice = input.slice_from(token_start); + if token_slice.ends_with("}") { + token_slice = token_slice.strip_suffix("}").unwrap(); + } + ret_val.assign(token_slice); +} + +#[no_mangle] +pub extern "C" fn Servo_ReplaceBlockRuleBodyTextInStylesheetText( + stylesheet_text: &nsACString, + line: u32, + column: u32, + new_body_text: &nsACString, + ret_val: &mut nsACString, +) { + let css_text = unsafe { stylesheet_text.as_str_unchecked() }; + + let Some(rule_start_index) = get_byte_index_from_line_and_column(css_text, line, column) else { + ret_val.set_is_void(true); + return; + }; + + let mut input = ParserInput::new(&css_text[rule_start_index..]); + let mut input = Parser::new(&mut input); + let mut found_start = false; + + // Search forward for the opening brace. + while let Ok(token) = input.next() { + if matches!(*token, Token::CurlyBracketBlock) { + found_start = true; + break; + } + + if token.is_parse_error() { + break; + } + } + + if !found_start { + ret_val.set_is_void(true); + return; + } + + let token_start = input.position(); + let rule_body_start = rule_start_index + token_start.byte_index(); + // Parse the nested block to move the parser to the end of the block + let _ = input.parse_nested_block( + |_i| -> Result<(), CssParseError<'_, BasicParseError>> { + Ok(()) + } + ); + let mut rule_body_end = rule_start_index + input.position().byte_index(); + // We're not guaranteed to have a closing bracket, but when we do, we need to move // the end offset before it. let token_slice = input.slice_from(token_start); if token_slice.ends_with("}") { - end_offset = end_offset - 1; + rule_body_end -= 1; + } + + ret_val.append(&css_text[..rule_body_start]); + ret_val.append(new_body_text); + ret_val.append(&css_text[rule_body_end..]); +} + +/// Find css_text byte position corresponding to the passed line and column +fn get_byte_index_from_line_and_column( + css_text: &str, + line: u32, + column: u32, +) -> Option<usize> { + // Find the byte index of the start of the passed line within css_text + let mut line_byte_index = Some(0); + if line != 1 { + let mut current_line = 1; + let mut last_byte = None; + let mut bytes_iter = css_text.bytes(); + line_byte_index = bytes_iter.position(|byte| { + // We want to get the position _after_ the EOF sequence + let on_expected_line = current_line == line; + let is_previous_byte_carriage_return = last_byte == Some(b'\r'); + last_byte = Some(byte); + + if byte == b'\r' { + current_line += 1; + } else if byte == b'\n' { + if !is_previous_byte_carriage_return { + current_line += 1; + } else { + return false; + } + } + on_expected_line + }); + } + + if line_byte_index.is_none() { + return None; } - *result_start_offset = start_offset as u32; - *result_end_offset = end_offset as u32; + if column == 1 { + return line_byte_index; + } + + let line_byte_index = line_byte_index.unwrap(); + let mut current_column = 1; + for (byte_index, _char) in css_text[line_byte_index..].char_indices() { + if current_column == column { + return Some(line_byte_index + byte_index); + } + current_column += 1; + } - return true; + None } |