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 + } + } } }