From 9caaa6004e294cbcc10891eac379454fc323c859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 29 Apr 2016 22:29:59 +0200 Subject: [PATCH 1/8] style: Support lazy pseudo-elements These can't be supported in Servo as of right now, because I'm not totally sure the accesses that should be done in layout would be thread-safe. It can be revisited later though. --- components/layout/wrapper.rs | 48 +++++++++++--- components/style/matching.rs | 2 +- components/style/media_queries.rs | 10 ++- components/style/selector_impl.rs | 93 ++++++++++++++++++--------- components/style/selector_matching.rs | 78 +++++++++++++++------- ports/geckolib/glue.rs | 2 +- ports/geckolib/selector_impl.rs | 45 +++++++++++-- 7 files changed, 206 insertions(+), 72 deletions(-) diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index e9215f8717f..29ebb919368 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -71,7 +71,7 @@ use style::element_state::*; use style::properties::{ComputedValues, ServoComputedValues}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::restyle_hints::ElementSnapshot; -use style::selector_impl::{NonTSPseudoClass, PseudoElement, ServoSelectorImpl}; +use style::selector_impl::{NonTSPseudoClass, PseudoElement, PseudoElementCascadeType, ServoSelectorImpl}; use style::servo::{PrivateStyleData, SharedStyleContext}; use url::Url; use util::str::is_whitespace; @@ -774,13 +774,41 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized + PartialEq { // Precompute non-eagerly-cascaded pseudo-element styles if not // cached before. let style_pseudo = other.style_pseudo_element(); - if !style_pseudo.is_eagerly_cascaded() && - !self.borrow_layout_data().unwrap().style_data.per_pseudo.contains_key(&style_pseudo) { - let mut data = self.mutate_layout_data().unwrap(); - let new_style = context.stylist - .computed_values_for_pseudo(&style_pseudo, - data.style_data.style.as_ref()); - data.style_data.per_pseudo.insert(style_pseudo.clone(), new_style.unwrap()); + match style_pseudo.cascade_type() { + // Already computed during the cascade. + PseudoElementCascadeType::Eager => {}, + PseudoElementCascadeType::Precomputed => { + if !self.borrow_layout_data() + .unwrap().style_data + .per_pseudo.contains_key(&style_pseudo) { + let mut data = self.mutate_layout_data().unwrap(); + let new_style = + context.stylist + .precomputed_values_for_pseudo(&style_pseudo, + data.style_data.style.as_ref()); + data.style_data.per_pseudo + .insert(style_pseudo.clone(), new_style.unwrap()); + } + } + PseudoElementCascadeType::Lazy => { + panic!("Lazy pseudo-elements can't be used in Servo \ + since accessing the DOM tree during layout \ + could be unsafe.") + // debug_assert!(self.is_element()); + // if !self.borrow_layout_data() + // .unwrap().style_data + // .per_pseudo.contains_key(&style_pseudo) { + // let mut data = self.mutate_layout_data().unwrap(); + // let new_style = + // context.stylist + // .lazily_compute_pseudo_element_style( + // &self.as_element(), + // &style_pseudo, + // data.style_data.style.as_ref().unwrap()); + // data.style_data.per_pseudo + // .insert(style_pseudo.clone(), new_style.unwrap()) + // } + } } Ref::map(self.borrow_layout_data().unwrap(), |data| { @@ -1252,8 +1280,8 @@ impl Iterator for ThreadSafeLayoutNodeChildrenIterator { element: &'le Element, diff --git a/components/style/matching.rs b/components/style/matching.rs index 84189fde404..11fa2915081 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -540,7 +540,7 @@ pub trait ElementMatchMethods : TElement stylist.push_applicable_declarations(self, parent_bf, None, - Some(pseudo.clone()), + Some(&pseudo.clone()), applicable_declarations.per_pseudo.entry(pseudo).or_insert(vec![])); }); diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs index b19b4d81c7e..2af3ee833ee 100644 --- a/components/style/media_queries.rs +++ b/components/style/media_queries.rs @@ -117,6 +117,13 @@ impl Device { viewport_size: viewport_size, } } + + #[inline] + pub fn au_viewport_size(&self) -> Size2D { + Size2D::new(Au::from_f32_px(self.viewport_size.width.get()), + Au::from_f32_px(self.viewport_size.height.get())) + } + } impl Expression { @@ -203,8 +210,7 @@ pub fn parse_media_query_list(input: &mut Parser) -> MediaQueryList { impl MediaQueryList { pub fn evaluate(&self, device: &Device) -> bool { - let viewport_size = Size2D::new(Au::from_f32_px(device.viewport_size.width.get()), - Au::from_f32_px(device.viewport_size.height.get())); + let viewport_size = device.au_viewport_size(); // Check if any queries match (OR condition) self.media_queries.iter().any(|mq| { diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index b7faff4c028..e0c1b2c3f98 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -8,6 +8,50 @@ use selectors::Element; use selectors::parser::{ParserContext, SelectorImpl}; use stylesheets::Stylesheet; +/// This function determines if a pseudo-element is eagerly cascaded or not. +/// +/// Eagerly cascaded pseudo-elements are "normal" pseudo-elements (i.e. +/// `::before` and `::after`). They inherit styles normally as another +/// selector would do, and they're part of the cascade. +/// +/// Lazy pseudo-elements are affected by selector matching, but they're only +/// computed when needed, and not before. They're useful for general +/// pseudo-elements that are not very common. +/// +/// Precomputed ones skip the cascade process entirely, mostly as an +/// optimisation since they are private pseudo-elements (like +/// `::-servo-details-content`). +/// +/// This pseudo-elements are resolved on the fly using *only* global rules +/// (rules of the form `*|*`), and applying them to the parent style. +/// +/// If you're implementing a public selector that the end-user might customize, +/// then you probably need doing the whole cascading process and return true in +/// this function for that pseudo (either as Eager or Lazy). +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PseudoElementCascadeType { + Eager, + Lazy, + Precomputed, +} + +impl PseudoElementCascadeType { + #[inline] + pub fn is_eager(&self) -> bool { + *self == PseudoElementCascadeType::Eager + } + + #[inline] + pub fn is_lazy(&self) -> bool { + *self == PseudoElementCascadeType::Lazy + } + + #[inline] + pub fn is_precomputed(&self) -> bool { + *self == PseudoElementCascadeType::Precomputed + } +} + pub trait ElementExt: Element { fn is_link(&self) -> bool; } @@ -15,49 +59,32 @@ pub trait ElementExt: Element { pub trait SelectorImplExt : SelectorImpl + Sized { type ComputedValues: properties::ComputedValues; - fn each_pseudo_element(mut fun: F) - where F: FnMut(::PseudoElement); + fn pseudo_element_cascade_type(pseudo: &Self::PseudoElement) -> PseudoElementCascadeType; - /// This function determines if a pseudo-element is eagerly cascaded or not. - /// - /// Eagerly cascaded pseudo-elements are "normal" pseudo-elements (i.e. - /// `::before` and `::after`). They inherit styles normally as another - /// selector would do. - /// - /// Non-eagerly cascaded ones skip the cascade process entirely, mostly as - /// an optimisation since they are private pseudo-elements (like - /// `::-servo-details-content`). This pseudo-elements are resolved on the - /// fly using global rules (rules of the form `*|*`), and applying them to - /// the parent style. - /// - /// If you're implementing a public selector that the end-user might - /// customize, then you probably need doing the whole cascading process and - /// return true in this function for that pseudo. - /// - /// But if you are implementing a private pseudo-element, please consider if - /// it might be possible to skip the cascade for it. - fn is_eagerly_cascaded_pseudo_element(pseudo: &::PseudoElement) -> bool; + fn each_pseudo_element(mut fun: F) + where F: FnMut(Self::PseudoElement); #[inline] fn each_eagerly_cascaded_pseudo_element(mut fun: F) where F: FnMut(::PseudoElement) { Self::each_pseudo_element(|pseudo| { - if Self::is_eagerly_cascaded_pseudo_element(&pseudo) { + if Self::pseudo_element_cascade_type(&pseudo).is_eager() { fun(pseudo) } }) } #[inline] - fn each_non_eagerly_cascaded_pseudo_element(mut fun: F) + fn each_precomputed_pseudo_element(mut fun: F) where F: FnMut(::PseudoElement) { Self::each_pseudo_element(|pseudo| { - if !Self::is_eagerly_cascaded_pseudo_element(&pseudo) { + if Self::pseudo_element_cascade_type(&pseudo).is_precomputed() { fun(pseudo) } }) } + fn pseudo_class_state_flag(pc: &Self::NonTSPseudoClass) -> ElementState; fn get_user_or_user_agent_stylesheets() -> &'static [Stylesheet]; @@ -76,13 +103,21 @@ pub enum PseudoElement { impl PseudoElement { #[inline] - pub fn is_eagerly_cascaded(&self) -> bool { + pub fn cascade_type(&self) -> PseudoElementCascadeType { + // TODO: Make PseudoElementCascadeType::Lazy work for Servo. + // + // This can't be done right now since it would require + // ServoThreadSafeLayoutElement to implement ::selectors::Element, + // and it might not be thread-safe. + // + // After that, we'd probably want ::selection and + // ::-servo-details-summary to be lazy. match *self { PseudoElement::Before | PseudoElement::After | PseudoElement::Selection | - PseudoElement::DetailsSummary => true, - PseudoElement::DetailsContent => false, + PseudoElement::DetailsSummary => PseudoElementCascadeType::Eager, + PseudoElement::DetailsContent => PseudoElementCascadeType::Precomputed, } } } @@ -191,8 +226,8 @@ impl SelectorImplExt for ServoSelectorImpl { type ComputedValues = ServoComputedValues; #[inline] - fn is_eagerly_cascaded_pseudo_element(pseudo: &PseudoElement) -> bool { - pseudo.is_eagerly_cascaded() + fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType { + pseudo.cascade_type() } #[inline] diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 72fdec38e43..e59955f3a0e 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -8,7 +8,6 @@ use dom::TElement; use element_state::*; use error_reporting::{ParseErrorReporter, StdoutErrorReporter}; -use euclid::Size2D; use media_queries::{Device, MediaType}; use properties::{self, ComputedValues, PropertyDeclaration, PropertyDeclarationBlock}; use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet}; @@ -127,9 +126,9 @@ pub struct Stylist { /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// These are eagerly computed once, and then used to resolve the new /// computed values on the fly on layout. - non_eagerly_cascaded_pseudo_element_decls: HashMap, - BuildHasherDefault<::fnv::FnvHasher>>, + precomputed_pseudo_element_decls: HashMap, + BuildHasherDefault<::fnv::FnvHasher>>, rules_source_order: usize, @@ -148,7 +147,7 @@ impl Stylist { element_map: PerPseudoElementSelectorMap::new(), pseudos_map: HashMap::with_hasher(Default::default()), - non_eagerly_cascaded_pseudo_element_decls: HashMap::with_hasher(Default::default()), + precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()), rules_source_order: 0, state_deps: DependencySet::new(), }; @@ -175,7 +174,7 @@ impl Stylist { self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new()); }); - self.non_eagerly_cascaded_pseudo_element_decls = HashMap::with_hasher(Default::default()); + self.precomputed_pseudo_element_decls = HashMap::with_hasher(Default::default()); self.rules_source_order = 0; self.state_deps.clear(); @@ -242,39 +241,70 @@ impl Stylist { self.rules_source_order = rules_source_order; - Impl::each_non_eagerly_cascaded_pseudo_element(|pseudo| { - // TODO: Don't precompute this, compute it on demand instead and - // cache it. - // - // This is actually kind of hard, because the stylist is shared - // between threads. + Impl::each_precomputed_pseudo_element(|pseudo| { + // TODO: Consider not doing this and just getting the rules on the + // fly. It should be a bit slower, but we'd take rid of the + // extra field, and avoid this precomputation entirely. if let Some(map) = self.pseudos_map.remove(&pseudo) { let mut declarations = vec![]; map.user_agent.normal.get_universal_rules(&mut declarations); map.user_agent.important.get_universal_rules(&mut declarations); - self.non_eagerly_cascaded_pseudo_element_decls.insert(pseudo, declarations); + self.precomputed_pseudo_element_decls.insert(pseudo, declarations); } }) } - pub fn computed_values_for_pseudo(&self, - pseudo: &Impl::PseudoElement, - parent: Option<&Arc>) -> Option> { - debug_assert!(!Impl::is_eagerly_cascaded_pseudo_element(pseudo)); - if let Some(declarations) = self.non_eagerly_cascaded_pseudo_element_decls.get(pseudo) { + /// Computes the style for a given "precomputed" pseudo-element, taking the + /// universal rules and applying them. + pub fn precomputed_values_for_pseudo(&self, + pseudo: &Impl::PseudoElement, + parent: Option<&Arc>) + -> Option> { + debug_assert!(Impl::pseudo_element_cascade_type(pseudo).is_precomputed()); + if let Some(declarations) = self.precomputed_pseudo_element_decls.get(pseudo) { + let (computed, _) = - properties::cascade::(Size2D::zero(), - &declarations, false, - parent.map(|p| &**p), None, - box StdoutErrorReporter); + properties::cascade(self.device.au_viewport_size(), + &declarations, false, + parent.map(|p| &**p), None, + box StdoutErrorReporter); Some(Arc::new(computed)) } else { parent.map(|p| p.clone()) } } + pub fn lazily_compute_pseudo_element_style(&self, + element: &E, + pseudo: &Impl::PseudoElement, + parent: &Arc) + -> Option> + where E: Element + TElement { + debug_assert!(Impl::pseudo_element_cascade_type(pseudo).is_lazy()); + if self.pseudos_map.get(pseudo).is_none() { + return None; + } + + let mut declarations = vec![]; + + // NB: This being cached could be worth it, maybe allow an optional + // ApplicableDeclarationsCache?. + self.push_applicable_declarations(element, + None, + None, + Some(pseudo), + &mut declarations); + + let (computed, _) = + properties::cascade(self.device.au_viewport_size(), + &declarations, false, + Some(&**parent), None, + box StdoutErrorReporter); + Some(Arc::new(computed)) + } + pub fn compute_restyle_hint(&self, element: &E, snapshot: &ElementSnapshot, // NB: We need to pass current_state as an argument because @@ -325,7 +355,7 @@ impl Stylist { element: &E, parent_bf: Option<&BloomFilter>, style_attribute: Option<&PropertyDeclarationBlock>, - pseudo_element: Option, + pseudo_element: Option<&Impl::PseudoElement>, applicable_declarations: &mut V) -> bool where E: Element + TElement, @@ -334,7 +364,7 @@ impl Stylist { assert!(style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements"); debug_assert!(pseudo_element.is_none() || - Impl::is_eagerly_cascaded_pseudo_element(pseudo_element.as_ref().unwrap())); + !Impl::pseudo_element_cascade_type(pseudo_element.as_ref().unwrap()).is_precomputed()); let map = match pseudo_element { Some(ref pseudo) => self.pseudos_map.get(pseudo).unwrap(), diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 27edf4ed357..ac5e1b74b2b 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -267,7 +267,7 @@ pub extern "C" fn Servo_GetComputedValuesForAnonymousBox(parent_style_or_null: * type Helpers = ArcHelpers; Helpers::maybe_with(parent_style_or_null, |maybe_parent| { - let new_computed = data.stylist.computed_values_for_pseudo(&pseudo, maybe_parent); + let new_computed = data.stylist.precomputed_values_for_pseudo(&pseudo, maybe_parent); new_computed.map_or(ptr::null_mut(), |c| Helpers::from(c)) }) } diff --git a/ports/geckolib/selector_impl.rs b/ports/geckolib/selector_impl.rs index 03803563443..5717cadf5af 100644 --- a/ports/geckolib/selector_impl.rs +++ b/ports/geckolib/selector_impl.rs @@ -6,7 +6,7 @@ use properties::GeckoComputedValues; use selectors::parser::{ParserContext, SelectorImpl}; use style; use style::element_state::ElementState; -use style::selector_impl::SelectorImplExt; +use style::selector_impl::{PseudoElementCascadeType, SelectorImplExt}; pub type Stylist = style::selector_matching::Stylist; pub type Stylesheet = style::stylesheets::Stylesheet; @@ -89,6 +89,36 @@ pub enum PseudoElement { MozSVGText, } +impl PseudoElement { + fn is_anon_box_pseudo(&self) -> bool { + use self::PseudoElement::*; + match *self { + MozNonElement | MozAnonymousBlock | MozAnonymousPositionedBlock | + MozMathMLAnonymousBlock | MozXULAnonymousBlock | + MozHorizontalFramesetBorder | MozVerticalFramesetBorder | + MozLineFrame | MozButtonContent | MozButtonLabel | MozCellContent | + MozDropdownList | MozFieldsetContent | MozFramesetBlank | + MozDisplayComboboxControlFrame | + MozHTMLCanvasContent | MozInlineTable | MozTable | MozTableCell | + MozTableColumnGroup | MozTableColumn | MozTableOuter | + MozTableRowGroup | MozTableRow | MozCanvas | MozPageBreak | + MozPage | MozPageContent | MozPageSequence | MozScrolledContent | + MozScrolledCanvas | MozScrolledPageSequence | MozColumnContent | + MozViewport | MozViewportScroll | MozAnonymousFlexItem | + MozAnonymousGridItem | MozRuby | MozRubyBase | + MozRubyBaseContainer | MozRubyText | MozRubyTextContainer | + MozTreeColumn | MozTreeRow | MozTreeSeparator | MozTreeCell | + MozTreeIndentation | MozTreeLine | MozTreeTwisty | MozTreeImage | + MozTreeCellText | MozTreeCheckbox | MozTreeProgressMeter | + MozTreeDropFeedback | MozSVGMarkerAnonChild | + MozSVGOuterSVGAnonChild | MozSVGForeignContent | + MozSVGText => true, + _ => false, + } + } +} + + #[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)] pub enum NonTSPseudoClass { AnyLink, @@ -243,12 +273,17 @@ impl SelectorImplExt for GeckoSelectorImpl { type ComputedValues = GeckoComputedValues; #[inline] - fn is_eagerly_cascaded_pseudo_element(pseudo: &PseudoElement) -> bool { + fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType { match *pseudo { PseudoElement::Before | - PseudoElement::After | - PseudoElement::FirstLine => true, - _ => false, + PseudoElement::After => PseudoElementCascadeType::Eager, + _ => { + if pseudo.is_anon_box_pseudo() { + PseudoElementCascadeType::Precomputed + } else { + PseudoElementCascadeType::Lazy + } + } } } From 18c1fee3c7cd2fb36064bdb06a130b16785db128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 29 Apr 2016 23:16:24 +0200 Subject: [PATCH 2/8] geckolib: Make anon-box PEs an ADT. --- ports/geckolib/selector_impl.rs | 177 ++++++++++++++------------------ 1 file changed, 76 insertions(+), 101 deletions(-) diff --git a/ports/geckolib/selector_impl.rs b/ports/geckolib/selector_impl.rs index 5717cadf5af..bedac08a025 100644 --- a/ports/geckolib/selector_impl.rs +++ b/ports/geckolib/selector_impl.rs @@ -22,7 +22,13 @@ pub enum PseudoElement { FirstLine, // TODO: Probably a few more are missing here - // https://mxr.mozilla.org/mozilla-central/source/layout/style/nsCSSAnonBoxList.h + AnonBox(AnonBoxPseudoElement), + +} + +// https://mxr.mozilla.org/mozilla-central/source/layout/style/nsCSSAnonBoxList.h +#[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)] +pub enum AnonBoxPseudoElement { MozNonElement, MozAnonymousBlock, MozAnonymousPositionedBlock, @@ -89,36 +95,6 @@ pub enum PseudoElement { MozSVGText, } -impl PseudoElement { - fn is_anon_box_pseudo(&self) -> bool { - use self::PseudoElement::*; - match *self { - MozNonElement | MozAnonymousBlock | MozAnonymousPositionedBlock | - MozMathMLAnonymousBlock | MozXULAnonymousBlock | - MozHorizontalFramesetBorder | MozVerticalFramesetBorder | - MozLineFrame | MozButtonContent | MozButtonLabel | MozCellContent | - MozDropdownList | MozFieldsetContent | MozFramesetBlank | - MozDisplayComboboxControlFrame | - MozHTMLCanvasContent | MozInlineTable | MozTable | MozTableCell | - MozTableColumnGroup | MozTableColumn | MozTableOuter | - MozTableRowGroup | MozTableRow | MozCanvas | MozPageBreak | - MozPage | MozPageContent | MozPageSequence | MozScrolledContent | - MozScrolledCanvas | MozScrolledPageSequence | MozColumnContent | - MozViewport | MozViewportScroll | MozAnonymousFlexItem | - MozAnonymousGridItem | MozRuby | MozRubyBase | - MozRubyBaseContainer | MozRubyText | MozRubyTextContainer | - MozTreeColumn | MozTreeRow | MozTreeSeparator | MozTreeCell | - MozTreeIndentation | MozTreeLine | MozTreeTwisty | MozTreeImage | - MozTreeCellText | MozTreeCheckbox | MozTreeProgressMeter | - MozTreeDropFeedback | MozSVGMarkerAnonChild | - MozSVGOuterSVGAnonChild | MozSVGForeignContent | - MozSVGText => true, - _ => false, - } - } -} - - #[derive(Clone, Debug, PartialEq, Eq, HeapSizeOf, Hash)] pub enum NonTSPseudoClass { AnyLink, @@ -183,6 +159,7 @@ impl SelectorImpl for GeckoSelectorImpl { fn parse_pseudo_element(context: &ParserContext, name: &str) -> Result { + use self::AnonBoxPseudoElement::*; use self::PseudoElement::*; // The braces here are unfortunate, but they're needed for @@ -198,7 +175,7 @@ impl SelectorImpl for GeckoSelectorImpl { return Err(()) } - Ok(match_ignore_ascii_case! { name, + Ok(AnonBox(match_ignore_ascii_case! { name, "-moz-non-element" => MozNonElement, "-moz-anonymous-block" => MozAnonymousBlock, @@ -265,7 +242,7 @@ impl SelectorImpl for GeckoSelectorImpl { "-moz-svg-text" => MozSVGText, _ => return Err(()) - }) + })) } } @@ -277,87 +254,85 @@ impl SelectorImplExt for GeckoSelectorImpl { match *pseudo { PseudoElement::Before | PseudoElement::After => PseudoElementCascadeType::Eager, - _ => { - if pseudo.is_anon_box_pseudo() { - PseudoElementCascadeType::Precomputed - } else { - PseudoElementCascadeType::Lazy - } - } + PseudoElement::AnonBox(_) => PseudoElementCascadeType::Precomputed, + _ => PseudoElementCascadeType::Lazy, } } #[inline] fn each_pseudo_element(mut fun: F) where F: FnMut(PseudoElement) { - fun(PseudoElement::Before); - fun(PseudoElement::After); - fun(PseudoElement::FirstLine); + use self::PseudoElement::*; + use self::AnonBoxPseudoElement::*; - fun(PseudoElement::MozNonElement); - fun(PseudoElement::MozAnonymousBlock); - fun(PseudoElement::MozAnonymousPositionedBlock); - fun(PseudoElement::MozMathMLAnonymousBlock); - fun(PseudoElement::MozXULAnonymousBlock); + fun(Before); + fun(After); + fun(FirstLine); - fun(PseudoElement::MozHorizontalFramesetBorder); - fun(PseudoElement::MozVerticalFramesetBorder); - fun(PseudoElement::MozLineFrame); - fun(PseudoElement::MozButtonContent); - fun(PseudoElement::MozButtonLabel); - fun(PseudoElement::MozCellContent); - fun(PseudoElement::MozDropdownList); - fun(PseudoElement::MozFieldsetContent); - fun(PseudoElement::MozFramesetBlank); - fun(PseudoElement::MozDisplayComboboxControlFrame); + fun(AnonBox(MozNonElement)); + fun(AnonBox(MozAnonymousBlock)); + fun(AnonBox(MozAnonymousPositionedBlock)); + fun(AnonBox(MozMathMLAnonymousBlock)); + fun(AnonBox(MozXULAnonymousBlock)); - fun(PseudoElement::MozHTMLCanvasContent); - fun(PseudoElement::MozInlineTable); - fun(PseudoElement::MozTable); - fun(PseudoElement::MozTableCell); - fun(PseudoElement::MozTableColumnGroup); - fun(PseudoElement::MozTableColumn); - fun(PseudoElement::MozTableOuter); - fun(PseudoElement::MozTableRowGroup); - fun(PseudoElement::MozTableRow); + fun(AnonBox(MozHorizontalFramesetBorder)); + fun(AnonBox(MozVerticalFramesetBorder)); + fun(AnonBox(MozLineFrame)); + fun(AnonBox(MozButtonContent)); + fun(AnonBox(MozButtonLabel)); + fun(AnonBox(MozCellContent)); + fun(AnonBox(MozDropdownList)); + fun(AnonBox(MozFieldsetContent)); + fun(AnonBox(MozFramesetBlank)); + fun(AnonBox(MozDisplayComboboxControlFrame)); - fun(PseudoElement::MozCanvas); - fun(PseudoElement::MozPageBreak); - fun(PseudoElement::MozPage); - fun(PseudoElement::MozPageContent); - fun(PseudoElement::MozPageSequence); - fun(PseudoElement::MozScrolledContent); - fun(PseudoElement::MozScrolledCanvas); - fun(PseudoElement::MozScrolledPageSequence); - fun(PseudoElement::MozColumnContent); - fun(PseudoElement::MozViewport); - fun(PseudoElement::MozViewportScroll); - fun(PseudoElement::MozAnonymousFlexItem); - fun(PseudoElement::MozAnonymousGridItem); + fun(AnonBox(MozHTMLCanvasContent)); + fun(AnonBox(MozInlineTable)); + fun(AnonBox(MozTable)); + fun(AnonBox(MozTableCell)); + fun(AnonBox(MozTableColumnGroup)); + fun(AnonBox(MozTableColumn)); + fun(AnonBox(MozTableOuter)); + fun(AnonBox(MozTableRowGroup)); + fun(AnonBox(MozTableRow)); - fun(PseudoElement::MozRuby); - fun(PseudoElement::MozRubyBase); - fun(PseudoElement::MozRubyBaseContainer); - fun(PseudoElement::MozRubyText); - fun(PseudoElement::MozRubyTextContainer); + fun(AnonBox(MozCanvas)); + fun(AnonBox(MozPageBreak)); + fun(AnonBox(MozPage)); + fun(AnonBox(MozPageContent)); + fun(AnonBox(MozPageSequence)); + fun(AnonBox(MozScrolledContent)); + fun(AnonBox(MozScrolledCanvas)); + fun(AnonBox(MozScrolledPageSequence)); + fun(AnonBox(MozColumnContent)); + fun(AnonBox(MozViewport)); + fun(AnonBox(MozViewportScroll)); + fun(AnonBox(MozAnonymousFlexItem)); + fun(AnonBox(MozAnonymousGridItem)); - fun(PseudoElement::MozTreeColumn); - fun(PseudoElement::MozTreeRow); - fun(PseudoElement::MozTreeSeparator); - fun(PseudoElement::MozTreeCell); - fun(PseudoElement::MozTreeIndentation); - fun(PseudoElement::MozTreeLine); - fun(PseudoElement::MozTreeTwisty); - fun(PseudoElement::MozTreeImage); - fun(PseudoElement::MozTreeCellText); - fun(PseudoElement::MozTreeCheckbox); - fun(PseudoElement::MozTreeProgressMeter); - fun(PseudoElement::MozTreeDropFeedback); + fun(AnonBox(MozRuby)); + fun(AnonBox(MozRubyBase)); + fun(AnonBox(MozRubyBaseContainer)); + fun(AnonBox(MozRubyText)); + fun(AnonBox(MozRubyTextContainer)); - fun(PseudoElement::MozSVGMarkerAnonChild); - fun(PseudoElement::MozSVGOuterSVGAnonChild); - fun(PseudoElement::MozSVGForeignContent); - fun(PseudoElement::MozSVGText); + fun(AnonBox(MozTreeColumn)); + fun(AnonBox(MozTreeRow)); + fun(AnonBox(MozTreeSeparator)); + fun(AnonBox(MozTreeCell)); + fun(AnonBox(MozTreeIndentation)); + fun(AnonBox(MozTreeLine)); + fun(AnonBox(MozTreeTwisty)); + fun(AnonBox(MozTreeImage)); + fun(AnonBox(MozTreeCellText)); + fun(AnonBox(MozTreeCheckbox)); + fun(AnonBox(MozTreeProgressMeter)); + fun(AnonBox(MozTreeDropFeedback)); + + fun(AnonBox(MozSVGMarkerAnonChild)); + fun(AnonBox(MozSVGOuterSVGAnonChild)); + fun(AnonBox(MozSVGForeignContent)); + fun(AnonBox(MozSVGText)); } #[inline] From 028f9b6cd26f0cd2835166d96ffd30d69a39e7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 3 May 2016 18:50:25 +0200 Subject: [PATCH 3/8] style: layout: Allow a lazy pseudo-element implementation in Servo. --- components/layout/wrapper.rs | 180 +++++++++++++++++++++----- components/style/dom.rs | 10 +- components/style/selector_impl.rs | 12 +- components/style/selector_matching.rs | 7 +- ports/geckolib/selector_impl.rs | 3 +- ports/geckolib/wrapper.rs | 17 ++- 6 files changed, 172 insertions(+), 57 deletions(-) diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 29ebb919368..928ae1777ac 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -66,7 +66,7 @@ use std::sync::Arc; use string_cache::{Atom, Namespace}; use style::computed_values::content::ContentItem; use style::computed_values::{content, display}; -use style::dom::{TDocument, TElement, TNode, UnsafeNode}; +use style::dom::{PresentationalHintsSynthetizer, TDocument, TElement, TNode, UnsafeNode}; use style::element_state::*; use style::properties::{ComputedValues, ServoComputedValues}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; @@ -81,7 +81,7 @@ pub type NonOpaqueStyleAndLayoutData = *mut RefCell; /// A wrapper so that layout can access only the methods that it should have access to. Layout must /// only ever see these and must never see instances of `LayoutJS`. -pub trait LayoutNode : TNode { +pub trait LayoutNode: TNode { type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode; fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode; @@ -401,6 +401,16 @@ pub struct ServoLayoutElement<'le> { chain: PhantomData<&'le ()>, } +impl<'le> PresentationalHintsSynthetizer for ServoLayoutElement<'le> { + fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) + where V: VecLike>> + { + unsafe { + self.element.synthesize_presentational_hints_for_legacy_attributes(hints); + } + } +} + impl<'le> TElement for ServoLayoutElement<'le> { type ConcreteNode = ServoLayoutNode<'le>; type ConcreteDocument = ServoLayoutDocument<'le>; @@ -419,14 +429,6 @@ impl<'le> TElement for ServoLayoutElement<'le> { self.element.get_state_for_layout() } - fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) - where V: VecLike>> - { - unsafe { - self.element.synthesize_presentational_hints_for_legacy_attributes(hints); - } - } - #[inline] fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&str> { unsafe { @@ -665,8 +667,10 @@ impl PseudoElementType { /// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout /// node does not allow any parents or siblings of nodes to be accessed, to avoid races. -pub trait ThreadSafeLayoutNode : Clone + Copy + Sized + PartialEq { - type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement; +pub trait ThreadSafeLayoutNode: Clone + Copy + Sized + PartialEq { + type ConcreteThreadSafeLayoutElement: + ThreadSafeLayoutElement + + ::selectors::Element; type ChildrenIterator: Iterator + Sized; /// Creates a new `ThreadSafeLayoutNode` for the same `LayoutNode` @@ -680,6 +684,18 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized + PartialEq { /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. fn type_id(&self) -> Option; + /// Returns the type ID of this node, without discarding pseudo-elements as + /// `type_id` does. + fn type_id_without_excluding_pseudo_elements(&self) -> NodeTypeId; + + #[inline] + fn is_element_or_elements_pseudo(&self) -> bool { + match self.type_id_without_excluding_pseudo_elements() { + NodeTypeId::Element(..) => true, + _ => false, + } + } + fn debug_id(self) -> usize; fn flow_debug_id(self) -> usize; @@ -791,23 +807,20 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized + PartialEq { } } PseudoElementCascadeType::Lazy => { - panic!("Lazy pseudo-elements can't be used in Servo \ - since accessing the DOM tree during layout \ - could be unsafe.") - // debug_assert!(self.is_element()); - // if !self.borrow_layout_data() - // .unwrap().style_data - // .per_pseudo.contains_key(&style_pseudo) { - // let mut data = self.mutate_layout_data().unwrap(); - // let new_style = - // context.stylist - // .lazily_compute_pseudo_element_style( - // &self.as_element(), - // &style_pseudo, - // data.style_data.style.as_ref().unwrap()); - // data.style_data.per_pseudo - // .insert(style_pseudo.clone(), new_style.unwrap()) - // } + debug_assert!(self.is_element_or_elements_pseudo()); + if !self.borrow_layout_data() + .unwrap().style_data + .per_pseudo.contains_key(&style_pseudo) { + let mut data = self.mutate_layout_data().unwrap(); + let new_style = + context.stylist + .lazily_compute_pseudo_element_style( + &self.as_element(), + &style_pseudo, + data.style_data.style.as_ref().unwrap()); + data.style_data.per_pseudo + .insert(style_pseudo.clone(), new_style.unwrap()); + } } } @@ -932,12 +945,14 @@ pub trait ThreadSafeLayoutNode : Clone + Copy + Sized + PartialEq { // This trait is only public so that it can be implemented by the gecko wrapper. // It can be used to violate thread-safety, so don't use it elsewhere in layout! -pub trait DangerousThreadSafeLayoutNode : ThreadSafeLayoutNode { +pub trait DangerousThreadSafeLayoutNode: ThreadSafeLayoutNode { unsafe fn dangerous_first_child(&self) -> Option; unsafe fn dangerous_next_sibling(&self) -> Option; } -pub trait ThreadSafeLayoutElement: Clone + Copy + Sized { +pub trait ThreadSafeLayoutElement: Clone + Copy + Sized + + ::selectors::Element + + PresentationalHintsSynthetizer { type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode; #[inline] @@ -1027,6 +1042,11 @@ impl<'ln> ThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> { Some(self.node.type_id()) } + #[inline] + fn type_id_without_excluding_pseudo_elements(&self) -> NodeTypeId { + self.node.type_id() + } + fn debug_id(self) -> usize { self.node.debug_id() } @@ -1320,3 +1340,101 @@ impl TextContent { } } } + +impl <'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { + type Impl = ServoSelectorImpl; + + fn parent_element(&self) -> Option { + warn!("ServoThreadSafeLayoutElement::parent_element called"); + None + } + + fn first_child_element(&self) -> Option { + warn!("ServoThreadSafeLayoutElement::first_child_element called"); + None + } + + // Skips non-element nodes + fn last_child_element(&self) -> Option { + warn!("ServoThreadSafeLayoutElement::last_child_element called"); + None + } + + // Skips non-element nodes + fn prev_sibling_element(&self) -> Option { + warn!("ServoThreadSafeLayoutElement::prev_sibling_element called"); + None + } + + // Skips non-element nodes + fn next_sibling_element(&self) -> Option { + warn!("ServoThreadSafeLayoutElement::next_sibling_element called"); + None + } + + fn is_html_element_in_html_document(&self) -> bool { + warn!("ServoThreadSafeLayoutElement::is_html_element_in_html_document called"); + true + } + + #[inline] + fn get_local_name(&self) -> &Atom { + ThreadSafeLayoutElement::get_local_name(self) + } + + #[inline] + fn get_namespace(&self) -> &Namespace { + ThreadSafeLayoutElement::get_namespace(self) + } + + fn match_non_ts_pseudo_class(&self, _: NonTSPseudoClass) -> bool { + // NB: This could maybe be implemented + warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called"); + false + } + + fn get_id(&self) -> Option { + warn!("ServoThreadSafeLayoutElement::get_id called"); + None + } + + fn has_class(&self, _name: &Atom) -> bool { + warn!("ServoThreadSafeLayoutElement::has_class called"); + false + } + + fn match_attr(&self, attr: &AttrSelector, test: F) -> bool + where F: Fn(&str) -> bool { + match attr.namespace { + NamespaceConstraint::Specific(ref ns) => { + self.get_attr(ns, &attr.name).map_or(false, |attr| test(attr)) + }, + NamespaceConstraint::Any => { + unsafe { + self.element.get_attr_vals_for_layout(&attr.name).iter() + .any(|attr| test(*attr)) + } + } + } + } + + fn is_empty(&self) -> bool { + warn!("ServoThreadSafeLayoutElement::is_empty called"); + false + } + + fn is_root(&self) -> bool { + warn!("ServoThreadSafeLayoutElement::is_root called"); + false + } + + fn each_class(&self, _callback: F) + where F: FnMut(&Atom) { + warn!("ServoThreadSafeLayoutElement::each_class called"); + } +} + +impl<'le> PresentationalHintsSynthetizer for ServoThreadSafeLayoutElement<'le> { + fn synthesize_presentational_hints_for_legacy_attributes(&self, _hints: &mut V) + where V: VecLike>> {} +} diff --git a/components/style/dom.rs b/components/style/dom.rs index fed1597c627..badd4f31065 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -195,7 +195,12 @@ pub trait TDocument : Sized + Copy + Clone { fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement, ElementSnapshot)>; } -pub trait TElement : Sized + Copy + Clone + ElementExt { +pub trait PresentationalHintsSynthetizer { + fn synthesize_presentational_hints_for_legacy_attributes(&self, hints: &mut V) + where V: VecLike>>; +} + +pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer { type ConcreteNode: TNode; type ConcreteDocument: TDocument; @@ -205,9 +210,6 @@ pub trait TElement : Sized + Copy + Clone + ElementExt { fn get_state(&self) -> ElementState; - fn synthesize_presentational_hints_for_legacy_attributes(&self, &mut V) - where V: VecLike>>; - fn get_attr<'a>(&'a self, namespace: &Namespace, attr: &Atom) -> Option<&'a str>; fn get_attrs<'a>(&'a self, attr: &Atom) -> Vec<&'a str>; diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index e0c1b2c3f98..b424affb101 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -104,19 +104,11 @@ pub enum PseudoElement { impl PseudoElement { #[inline] pub fn cascade_type(&self) -> PseudoElementCascadeType { - // TODO: Make PseudoElementCascadeType::Lazy work for Servo. - // - // This can't be done right now since it would require - // ServoThreadSafeLayoutElement to implement ::selectors::Element, - // and it might not be thread-safe. - // - // After that, we'd probably want ::selection and - // ::-servo-details-summary to be lazy. match *self { PseudoElement::Before | PseudoElement::After | - PseudoElement::Selection | - PseudoElement::DetailsSummary => PseudoElementCascadeType::Eager, + PseudoElement::Selection => PseudoElementCascadeType::Eager, + PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy, PseudoElement::DetailsContent => PseudoElementCascadeType::Precomputed, } } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index e59955f3a0e..4faf9aa0eb4 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -5,7 +5,7 @@ // For lazy_static #![allow(unsafe_code)] -use dom::TElement; +use dom::PresentationalHintsSynthetizer; use element_state::*; use error_reporting::{ParseErrorReporter, StdoutErrorReporter}; use media_queries::{Device, MediaType}; @@ -281,7 +281,8 @@ impl Stylist { pseudo: &Impl::PseudoElement, parent: &Arc) -> Option> - where E: Element + TElement { + where E: Element + + PresentationalHintsSynthetizer { debug_assert!(Impl::pseudo_element_cascade_type(pseudo).is_lazy()); if self.pseudos_map.get(pseudo).is_none() { return None; @@ -358,7 +359,7 @@ impl Stylist { pseudo_element: Option<&Impl::PseudoElement>, applicable_declarations: &mut V) -> bool - where E: Element + TElement, + where E: Element + PresentationalHintsSynthetizer, V: VecLike { assert!(!self.is_device_dirty); assert!(style_attribute.is_none() || pseudo_element.is_none(), diff --git a/ports/geckolib/selector_impl.rs b/ports/geckolib/selector_impl.rs index bedac08a025..ddc26f6c541 100644 --- a/ports/geckolib/selector_impl.rs +++ b/ports/geckolib/selector_impl.rs @@ -23,7 +23,6 @@ pub enum PseudoElement { // TODO: Probably a few more are missing here AnonBox(AnonBoxPseudoElement), - } // https://mxr.mozilla.org/mozilla-central/source/layout/style/nsCSSAnonBoxList.h @@ -262,8 +261,8 @@ impl SelectorImplExt for GeckoSelectorImpl { #[inline] fn each_pseudo_element(mut fun: F) where F: FnMut(PseudoElement) { - use self::PseudoElement::*; use self::AnonBoxPseudoElement::*; + use self::PseudoElement::*; fun(Before); fun(After); diff --git a/ports/geckolib/wrapper.rs b/ports/geckolib/wrapper.rs index edf007a0016..f27cf770bf0 100644 --- a/ports/geckolib/wrapper.rs +++ b/ports/geckolib/wrapper.rs @@ -32,7 +32,8 @@ use std::slice; use std::str::from_utf8_unchecked; use std::sync::Arc; use string_cache::{Atom, Namespace}; -use style::dom::{OpaqueNode, TDocument, TElement, TNode, TRestyleDamage, UnsafeNode}; +use style::dom::{OpaqueNode, PresentationalHintsSynthetizer}; +use style::dom::{TDocument, TElement, TNode, TRestyleDamage, UnsafeNode}; use style::element_state::ElementState; #[allow(unused_imports)] // Used in commented-out code. use style::error_reporting::StdoutErrorReporter; @@ -339,12 +340,6 @@ impl<'le> TElement for GeckoElement<'le> { } } - fn synthesize_presentational_hints_for_legacy_attributes(&self, _hints: &mut V) - where V: VecLike>> - { - // FIXME(bholley) - Need to implement this. - } - #[inline] fn get_attr<'a>(&'a self, namespace: &Namespace, name: &Atom) -> Option<&'a str> { unsafe { @@ -360,6 +355,14 @@ impl<'le> TElement for GeckoElement<'le> { } } +impl<'le> PresentationalHintsSynthetizer for GeckoElement<'le> { + fn synthesize_presentational_hints_for_legacy_attributes(&self, _hints: &mut V) + where V: VecLike>> + { + // FIXME(bholley) - Need to implement this. + } +} + impl<'le> ::selectors::Element for GeckoElement<'le> { type Impl = GeckoSelectorImpl; From a87bf7c7768708570c1de8758d68137dffc0b524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 3 May 2016 19:01:20 +0200 Subject: [PATCH 4/8] style: Minor documentation tweaks about lazy pseudo-elements --- components/layout/wrapper.rs | 12 ++++++++++++ components/style/selector_impl.rs | 7 +++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 928ae1777ac..96540e4299c 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -1341,6 +1341,18 @@ impl TextContent { } } +/// This implementation of `::selectors::Element` is used for implementing lazy +/// pseudo-elements. +/// +/// Lazy pseudo-elements in Servo only allows selectors using safe properties, +/// i.e., local_name, attributes, so they can only be used for **private** +/// pseudo-elements (like `::-servo-details-content`). +/// +/// Probably a few more of this functions can be implemented (like `has_class`, +/// `each_class`, etc), but they have no use right now. +/// +/// Note that the element implementation is needed only for selector matching, +/// not for inheritance (styles are inherited appropiately). impl <'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { type Impl = ServoSelectorImpl; diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index b424affb101..925ccb816ef 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -18,6 +18,10 @@ use stylesheets::Stylesheet; /// computed when needed, and not before. They're useful for general /// pseudo-elements that are not very common. /// +/// Note that in Servo lazy pseudo-elements are restricted to a subset of +/// selectors, so you can't use it for public pseudo-elements. This is not the +/// case with Gecko though. +/// /// Precomputed ones skip the cascade process entirely, mostly as an /// optimisation since they are private pseudo-elements (like /// `::-servo-details-content`). @@ -26,8 +30,7 @@ use stylesheets::Stylesheet; /// (rules of the form `*|*`), and applying them to the parent style. /// /// If you're implementing a public selector that the end-user might customize, -/// then you probably need doing the whole cascading process and return true in -/// this function for that pseudo (either as Eager or Lazy). +/// then you probably need to make it eager. #[derive(Debug, Clone, PartialEq, Eq)] pub enum PseudoElementCascadeType { Eager, From ca41e5359f2bcddabe5bd69de747750272a6db84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 3 May 2016 22:33:09 +0200 Subject: [PATCH 5/8] docs: Add style overview. --- components/style/README.md | 2 + docs/components/style.md | 158 +++++++++++++++++++++++++++++++++++++ docs/glossary.md | 2 +- 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 docs/components/style.md diff --git a/components/style/README.md b/components/style/README.md index e2a1a1664e8..70670a5f96b 100644 --- a/components/style/README.md +++ b/components/style/README.md @@ -2,3 +2,5 @@ servo-style =========== Style system for Servo, using [rust-cssparser](https://github.com/mozilla-servo/rust-cssparser) for parsing. + + * [Documentation](https://github.com/servo/servo/blob/master/docs/components/style.md). diff --git a/docs/components/style.md b/docs/components/style.md new file mode 100644 index 00000000000..a85816e6fef --- /dev/null +++ b/docs/components/style.md @@ -0,0 +1,158 @@ +# Servo's style system overview + +This needs to be filled more extensively. Meanwhile, you can also take a look to +the [style doc comments][style-doc], or the [Styling +Overview][wiki-styling-overview] in the wiki, which is a conversation between +Boris Zbarsky and Patrick Walton about how style sharing works. + + +## Selector Implementation + +The style system is generic over quite a few things, in order to be shareable +with Servo's layout system, and with [Stylo][stylo], an ambitious project that +aims to integrate Servo's style system into Gecko. + +The main generic trait is [selectors' SelectorImpl][selector-impl], that has all +the logic related to parsing pseudo-elements and other pseudo-classes appart +from [tree-structural ones][tree-structural-pseudo-classes]. + +Servo [extends][selector-impl-ext] that trait in order to allow a few more +things to be shared between Stylo and Servo. + +The main Servo implementation (the one that is used in regular builds) is +[ServoSelectorImpl][servo-selector-impl]. + + +## DOM glue + +In order to keep DOM, layout and style in different modules, there are a few +traits involved. + +Style's [`dom` traits][style-dom-traits] (`TDocument`, `TElement`, `TNode`, +`TRestyleDamage`) are the main "wall" between layout and style. + +Layout's [`wrapper`][layout-wrapper] module is the one that makes sure that +layout traits have the required traits implemented. + + +## The Stylist + +The [`stylist`][stylist] structure is the one that holds all the selectors and +device characteristics for a given document. + +The stylesheets' CSS rules are converted into [`Rule`][selectors-rules]s, and +introduced in a [`SelectorMap`][selectors-selectormap] depending on the +pseudo-element (see [`PerPseudoElementSelectorMap`][per-pseudo-selectormap]), +stylesheet origin (see [`PerOriginSelectorMap`][per-origin-selectormap]), and +priority (see the `normal` and `important` fields in +[`PerOriginSelectorMap`][per-origin-selectormap]). + +This structure is effectively created once per [pipeline][docs-pipeline], in the +LayoutThread corresponding to that pipeline. + + +## The `properties` module + +The [properties module][properties-module] is a mako template where all the +properties, computed value computation and cascading logic resides. + +It's a complex template with a **lot** of code, but the main function it exposes +is the [`cascade` function][properties-cascade-fn], which performs all the +computation. + + +## Pseudo-Element resolution + +Pseudo-elements are a tricky section of the style system. Not all +pseudo-elements are very common, and so some of them might want to skip the +cascade. + +Servo has, as of right now, five [pseudo-elements][servo-pseudo-elements]: + + * [`::before`][mdn-pseudo-before] and [`::after`][mdn-pseudo-after]. + * [`::selection`][mdn-pseudo-selection]: This one is only partially + implemented, and only works for text inputs and textareas as of right now. + * `::-servo-details-summary`: This pseudo-element represents the `` of + a `
` element. + * `::-servo-details-content`: This pseudo-element represents the contents of + a `
` element. + +Both `::-servo-details-*` pseudo-elements are private (i.e. they are only parsed +from User-Agent stylesheets). + +Servo has three different ways of cascading a pseudo-element, which are defined +in [`PseudoElementCascadeType`][pseudo-cascade-type]: + + +### "Eager" cascading + +This mode computes the computed values of a given node's pseudo-element over the +first pass of the style system. + +This is used for all public pseudo-elements, and is, as of right now, **the only +way a public pseudo-element should be cascaded** (the explanation for this is +below). + + +### "Precomputed" cascading + +Or, better said, no cascading at all. A pseudo-element marked as such is not +cascaded. + +The only rules that apply to the styles of that pseudo-element are universal +rules (rules with a `*|*` selector), and they are applied directly over the +element's style if present. + +`::-servo-details-content` is an example of this kind of pseudo-element, all the +rules in the UA stylesheet with the selector `*|*::-servo-details-content` (and +only those) are evaluated over the element's style (except the `display` value, +that is overwritten by layout). + +This should be the **preferred type for private pseudo-elements** (although some +of them might need selectors, see below). + + +### "Lazy" cascading + +Lazy cascading allows to compute pseudo-element styles lazily, that is, just +when needed. + +Currently (for Servo, not that much for stylo), **selectors supported for this +kind of pseudo-elements are only a subset of selectors that can be computed +in a thread-safe way in layout**. + +This subset includes tags and attribute selectors, enough for making +`::-servo-details-summary` a lazy pseudo-element (that only needs to know +if it is in an `open` details element or not). + +Since no other selectors would apply to it, **this is (at least for now) not an +acceptable type for public pseudo-elements, but should be considered for private +pseudo-elements**. + +#### Not found what you were looking for? + +Feel free to ping @SimonSapin, @mbrubeck or @emilio on irc, and please mention +that you didn't find it here so it can be added :) + +[style-doc]: http://doc.servo.org/style/index.html +[wiki-styling-overview]: https://github.com/servo/servo/wiki/Styling-overview +[stylo]: https://public.etherpad-mozilla.org/p/stylo +[selector-impl]: http://doc.servo.org/selectors/parser/trait.SelectorImpl.html +[selector-impl-ext]: http://doc.servo.org/style/selector_impl/trait.SelectorImplExt.html +[servo-selector-impl]: http://doc.servo.org/style/selector_impl/struct.ServoSelectorImpl.html +[tree-structural-pseudo-classes]: https://www.w3.org/TR/selectors4/#structural-pseudos +[style-dom-traits]: http://doc.servo.org/style/dom/index.html +[layout-wrapper]: http://doc.servo.org/layout/wrapper/index.html +[pseudo-cascade-type]: http://doc.servo.org/style/selector_impl/enum.PseudoElementCascadeType.html +[servo-pseudo-elements]: http://doc.servo.org/style/selector_impl/enum.PseudoElement.html +[mdn-pseudo-before]: https://developer.mozilla.org/en/docs/Web/CSS/::before +[mdn-pseudo-after]: https://developer.mozilla.org/en/docs/Web/CSS/::after +[mdn-pseudo-selection]: https://developer.mozilla.org/en/docs/Web/CSS/::selection +[stylist]: http://doc.servo.org/style/selector_matching/struct.Stylist.html +[selectors-selectormap]: http://doc.servo.org/selectors/matching/struct.SelectorMap.html +[selectors-rule]: http://doc.servo.org/selectors/matching/struct.Rule.html +[per-pseudo-selectormap]: http://doc.servo.org/style/selector_matching/struct.PerPseudoElementSelectorMap.html +[per-origin-selectormap]: http://doc.servo.org/style/selector_matching/struct.PerOriginSelectorMap.html +[docs-pipeline]: https://github.com/servo/servo/blob/master/docs/glossary.md#pipeline +[properties-module]: http://doc.servo.org/style/properties/index.html +[properties-cascade-fn]: http://doc.servo.org/style/properties/fn.cascade.html diff --git a/docs/glossary.md b/docs/glossary.md index 32e907823d0..7328db28528 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -7,7 +7,7 @@ If there is a word or phrase used in Servo's code, issue tracker, mailing list, # Glossary ### Compositor ### - + The thread that receives input events from the operating system and forwards them to the constellation. It is also in charge of compositing complete renders of web content and displaying them on the screen as fast as possible. ### Constellation ### From aacf67afcdbd712406b7f8ca4ca398492a5addce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 3 May 2016 22:41:14 +0200 Subject: [PATCH 6/8] layout: use `debug!` instead of `warn!` for the expected ThreadSafeLayoutElement methods --- components/layout/wrapper.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 96540e4299c..35353a85cf1 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -1385,7 +1385,7 @@ impl <'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { } fn is_html_element_in_html_document(&self) -> bool { - warn!("ServoThreadSafeLayoutElement::is_html_element_in_html_document called"); + debug!("ServoThreadSafeLayoutElement::is_html_element_in_html_document called"); true } @@ -1406,12 +1406,12 @@ impl <'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { } fn get_id(&self) -> Option { - warn!("ServoThreadSafeLayoutElement::get_id called"); + debug!("ServoThreadSafeLayoutElement::get_id called"); None } fn has_class(&self, _name: &Atom) -> bool { - warn!("ServoThreadSafeLayoutElement::has_class called"); + debug!("ServoThreadSafeLayoutElement::has_class called"); false } From 44293184501b726aa3895f7af268bc33a65bd9ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 3 May 2016 23:38:55 +0200 Subject: [PATCH 7/8] docs: Ammend the lazy pseudo-element section to be clearer Thanks to Simon for the feedback :) --- docs/components/style.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/components/style.md b/docs/components/style.md index a85816e6fef..a3d657e53e0 100644 --- a/docs/components/style.md +++ b/docs/components/style.md @@ -118,8 +118,8 @@ Lazy cascading allows to compute pseudo-element styles lazily, that is, just when needed. Currently (for Servo, not that much for stylo), **selectors supported for this -kind of pseudo-elements are only a subset of selectors that can be computed -in a thread-safe way in layout**. +kind of pseudo-elements are only a subset of selectors that can be matched on +the layout tree, which does not hold all data from the DOM tree**. This subset includes tags and attribute selectors, enough for making `::-servo-details-summary` a lazy pseudo-element (that only needs to know From 0f7b70c0b7836800ae44e714f7ac19ff50e1e34b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 4 May 2016 00:26:39 +0200 Subject: [PATCH 8/8] geckolib: Implement Servo_GetComputedValuesForPseudoElement --- ports/geckolib/bindings.rs | 12 +++++-- ports/geckolib/glue.rs | 65 +++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/ports/geckolib/bindings.rs b/ports/geckolib/bindings.rs index 16724678a80..234f309378d 100644 --- a/ports/geckolib/bindings.rs +++ b/ports/geckolib/bindings.rs @@ -78,11 +78,11 @@ extern "C" { set: *mut RawServoStyleSet); pub fn Servo_PrependStyleSheet(sheet: *mut RawServoStyleSheet, set: *mut RawServoStyleSet); + pub fn Servo_RemoveStyleSheet(sheet: *mut RawServoStyleSheet, + set: *mut RawServoStyleSet); pub fn Servo_InsertStyleSheetBefore(sheet: *mut RawServoStyleSheet, reference: *mut RawServoStyleSheet, set: *mut RawServoStyleSet); - pub fn Servo_RemoveStyleSheet(sheet: *mut RawServoStyleSheet, - set: *mut RawServoStyleSet); pub fn Servo_StyleSheetHasRules(sheet: *mut RawServoStyleSheet) -> bool; pub fn Servo_InitStyleSet() -> *mut RawServoStyleSet; pub fn Servo_DropStyleSet(set: *mut RawServoStyleSet); @@ -93,6 +93,14 @@ extern "C" { pseudoTag: *mut nsIAtom, set: *mut RawServoStyleSet) -> *mut ServoComputedValues; + pub fn Servo_GetComputedValuesForPseudoElement(parent_style: + *mut ServoComputedValues, + match_element: + *mut RawGeckoElement, + pseudo_tag: *mut nsIAtom, + set: *mut RawServoStyleSet, + is_probe: bool) + -> *mut ServoComputedValues; pub fn Servo_AddRefComputedValues(arg1: *mut ServoComputedValues); pub fn Servo_ReleaseComputedValues(arg1: *mut ServoComputedValues); pub fn Gecko_GetAttrAsUTF8(element: *mut RawGeckoElement, ns: *const u8, diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index ac5e1b74b2b..96939a3f76f 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -5,7 +5,7 @@ #![allow(unsafe_code)] use app_units::Au; -use bindings::{RawGeckoDocument, RawGeckoNode}; +use bindings::{RawGeckoDocument, RawGeckoElement, RawGeckoNode}; use bindings::{RawServoStyleSet, RawServoStyleSheet, ServoComputedValues, ServoNodeData}; use bindings::{nsIAtom}; use data::PerDocumentStyleData; @@ -20,15 +20,16 @@ use std::slice; use std::str::from_utf8_unchecked; use std::sync::{Arc, Mutex}; use style::context::{ReflowGoal}; -use style::dom::{TDocument, TNode}; +use style::dom::{TDocument, TElement, TNode}; use style::error_reporting::StdoutErrorReporter; use style::parallel; use style::properties::ComputedValues; +use style::selector_impl::{SelectorImplExt, PseudoElementCascadeType}; use style::stylesheets::Origin; use traversal::RecalcStyleOnly; use url::Url; use util::arc_ptr_eq; -use wrapper::{GeckoDocument, GeckoNode, NonOpaqueStyleData}; +use wrapper::{GeckoDocument, GeckoElement, GeckoNode, NonOpaqueStyleData}; // TODO: This is ugly and should go away once we get an atom back-end. pub fn pseudo_element_from_atom(pseudo: *mut nsIAtom, @@ -256,7 +257,7 @@ pub extern "C" fn Servo_GetComputedValuesForAnonymousBox(parent_style_or_null: * -> *mut ServoComputedValues { let data = PerDocumentStyleData::borrow_mut_from_raw(raw_data); - let pseudo = match pseudo_element_from_atom(pseudo_tag, true) { + let pseudo = match pseudo_element_from_atom(pseudo_tag, /* ua_stylesheet = */ true) { Ok(pseudo) => pseudo, Err(pseudo) => { warn!("stylo: Unable to parse anonymous-box pseudo-element: {}", pseudo); @@ -272,6 +273,62 @@ pub extern "C" fn Servo_GetComputedValuesForAnonymousBox(parent_style_or_null: * }) } +#[no_mangle] +pub extern "C" fn Servo_GetComputedValuesForPseudoElement(parent_style: *mut ServoComputedValues, + match_element: *mut RawGeckoElement, + pseudo_tag: *mut nsIAtom, + raw_data: *mut RawServoStyleSet, + is_probe: bool) + -> *mut ServoComputedValues { + debug_assert!(!match_element.is_null()); + + let parent_or_null = || { + if is_probe { + ptr::null_mut() + } else { + Servo_AddRefComputedValues(parent_style); + parent_style + } + }; + + let pseudo = match pseudo_element_from_atom(pseudo_tag, /* ua_stylesheet = */ true) { + Ok(pseudo) => pseudo, + Err(pseudo) => { + warn!("stylo: Unable to parse anonymous-box pseudo-element: {}", pseudo); + return parent_or_null(); + } + }; + + + let data = PerDocumentStyleData::borrow_mut_from_raw(raw_data); + + let element = unsafe { GeckoElement::from_raw(match_element) }; + + type Helpers = ArcHelpers; + + match GeckoSelectorImpl::pseudo_element_cascade_type(&pseudo) { + PseudoElementCascadeType::Eager => { + let node = element.as_node(); + let maybe_computed = node.borrow_data() + .and_then(|data| { + data.per_pseudo.get(&pseudo).map(|c| c.clone()) + }); + maybe_computed.map_or_else(parent_or_null, Helpers::from) + } + PseudoElementCascadeType::Lazy => { + Helpers::with(parent_style, |parent| { + data.stylist + .lazily_compute_pseudo_element_style(&element, &pseudo, parent) + .map_or_else(parent_or_null, Helpers::from) + }) + } + PseudoElementCascadeType::Precomputed => { + unreachable!("Anonymous pseudo found in \ + Servo_GetComputedValuesForPseudoElement"); + } + } +} + #[no_mangle] pub extern "C" fn Servo_AddRefComputedValues(ptr: *mut ServoComputedValues) -> () { type Helpers = ArcHelpers;