mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
Auto merge of #10934 - emilio:other-gecko-pseudos, r=bholley,mbrubeck
style: Add infrastructure to support lazy pseudo-elements This builds on top of #10815, so it's really just the last commit the one that should be reviewed. I tried to apply the new infrastructure to servo, but failed (for now?). The problem with it is that it'd require `ThreadSafeLayoutElement` to implement `selectors::Element`, which is a lot of work and might be racy (not totally sure about it though). Thus, I prefered to keep selectors eager until knowing that it's safe to do it. r? @mbrubeck for style changes, @bholley for the geckolib changes (minimal for now, glue + a list of lazy PEs must be added) <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10934) <!-- Reviewable:end -->
This commit is contained in:
commit
29823cb378
13 changed files with 635 additions and 171 deletions
|
@ -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).
|
||||
|
|
|
@ -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<V>(&self, hints: &mut V)
|
||||
where V: VecLike<DeclarationBlock<Vec<PropertyDeclaration>>>;
|
||||
}
|
||||
|
||||
pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
|
||||
type ConcreteNode: TNode<ConcreteElement = Self, ConcreteDocument = Self::ConcreteDocument>;
|
||||
type ConcreteDocument: TDocument<ConcreteNode = Self::ConcreteNode, ConcreteElement = Self>;
|
||||
|
||||
|
@ -205,9 +210,6 @@ pub trait TElement : Sized + Copy + Clone + ElementExt {
|
|||
|
||||
fn get_state(&self) -> ElementState;
|
||||
|
||||
fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, &mut V)
|
||||
where V: VecLike<DeclarationBlock<Vec<PropertyDeclaration>>>;
|
||||
|
||||
fn get_attr<'a>(&'a self, namespace: &Namespace, attr: &Atom) -> Option<&'a str>;
|
||||
fn get_attrs<'a>(&'a self, attr: &Atom) -> Vec<&'a str>;
|
||||
|
||||
|
|
|
@ -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![]));
|
||||
});
|
||||
|
||||
|
|
|
@ -117,6 +117,13 @@ impl Device {
|
|||
viewport_size: viewport_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn au_viewport_size(&self) -> Size2D<Au> {
|
||||
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| {
|
||||
|
|
|
@ -8,6 +8,53 @@ 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.
|
||||
///
|
||||
/// 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`).
|
||||
///
|
||||
/// 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 to make it eager.
|
||||
#[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 +62,32 @@ pub trait ElementExt: Element {
|
|||
pub trait SelectorImplExt : SelectorImpl + Sized {
|
||||
type ComputedValues: properties::ComputedValues;
|
||||
|
||||
fn each_pseudo_element<F>(mut fun: F)
|
||||
where F: FnMut(<Self as SelectorImpl>::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: &<Self as SelectorImpl>::PseudoElement) -> bool;
|
||||
fn each_pseudo_element<F>(mut fun: F)
|
||||
where F: FnMut(Self::PseudoElement);
|
||||
|
||||
#[inline]
|
||||
fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
|
||||
where F: FnMut(<Self as SelectorImpl>::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<F>(mut fun: F)
|
||||
fn each_precomputed_pseudo_element<F>(mut fun: F)
|
||||
where F: FnMut(<Self as SelectorImpl>::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<Self>];
|
||||
|
@ -76,13 +106,13 @@ pub enum PseudoElement {
|
|||
|
||||
impl PseudoElement {
|
||||
#[inline]
|
||||
pub fn is_eagerly_cascaded(&self) -> bool {
|
||||
pub fn cascade_type(&self) -> PseudoElementCascadeType {
|
||||
match *self {
|
||||
PseudoElement::Before |
|
||||
PseudoElement::After |
|
||||
PseudoElement::Selection |
|
||||
PseudoElement::DetailsSummary => true,
|
||||
PseudoElement::DetailsContent => false,
|
||||
PseudoElement::Selection => PseudoElementCascadeType::Eager,
|
||||
PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
|
||||
PseudoElement::DetailsContent => PseudoElementCascadeType::Precomputed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,8 +221,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]
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
// For lazy_static
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use dom::TElement;
|
||||
use dom::PresentationalHintsSynthetizer;
|
||||
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<Impl: SelectorImplExt> {
|
|||
/// 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<Impl::PseudoElement,
|
||||
Vec<DeclarationBlock>,
|
||||
BuildHasherDefault<::fnv::FnvHasher>>,
|
||||
precomputed_pseudo_element_decls: HashMap<Impl::PseudoElement,
|
||||
Vec<DeclarationBlock>,
|
||||
BuildHasherDefault<::fnv::FnvHasher>>,
|
||||
|
||||
rules_source_order: usize,
|
||||
|
||||
|
@ -148,7 +147,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
|
|||
|
||||
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<Impl: SelectorImplExt> Stylist<Impl> {
|
|||
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,71 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
|
|||
|
||||
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<Impl::ComputedValues>>) -> Option<Arc<Impl::ComputedValues>> {
|
||||
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<Impl::ComputedValues>>)
|
||||
-> Option<Arc<Impl::ComputedValues>> {
|
||||
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::<Impl::ComputedValues>(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<E>(&self,
|
||||
element: &E,
|
||||
pseudo: &Impl::PseudoElement,
|
||||
parent: &Arc<Impl::ComputedValues>)
|
||||
-> Option<Arc<Impl::ComputedValues>>
|
||||
where E: Element<Impl=Impl> +
|
||||
PresentationalHintsSynthetizer {
|
||||
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<E>(&self, element: &E,
|
||||
snapshot: &ElementSnapshot,
|
||||
// NB: We need to pass current_state as an argument because
|
||||
|
@ -325,16 +356,16 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
|
|||
element: &E,
|
||||
parent_bf: Option<&BloomFilter>,
|
||||
style_attribute: Option<&PropertyDeclarationBlock>,
|
||||
pseudo_element: Option<Impl::PseudoElement>,
|
||||
pseudo_element: Option<&Impl::PseudoElement>,
|
||||
applicable_declarations: &mut V)
|
||||
-> bool
|
||||
where E: Element<Impl=Impl> + TElement,
|
||||
where E: Element<Impl=Impl> + PresentationalHintsSynthetizer,
|
||||
V: VecLike<DeclarationBlock> {
|
||||
assert!(!self.is_device_dirty);
|
||||
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(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue