gecko-dev/servo/components/style/stylist.rs
Emilio Cobos Álvarez 10712a5f45 servo: Merge #15736 - Bug 1341083: Implement dynamic restyling for display: contents (from servo:display-contents); r=heycam
Reviewed upstream by @heycam

cc @bholley, didn't end up renaming the `layout_parent` thing, because it made the cascade way more verbose (and difficult to understand IMO) unnecessarily, and you said you were ok-ish with it.

Source-Repo: https://github.com/servo/servo
Source-Revision: b77140a0375dcad9e15d4692edf8f15d5fe4597c

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : a6f4d687c180b50710f3b3849a7434a5a6cabb99
2017-02-25 11:00:07 -08:00

1201 lines
50 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Selector matching.
#![deny(missing_docs)]
use {Atom, LocalName};
use data::ComputedStyle;
use dom::{AnimationRules, PresentationalHintsSynthetizer, TElement};
use error_reporting::StdoutErrorReporter;
use keyframes::KeyframesAnimation;
use media_queries::Device;
use parking_lot::RwLock;
use pdqsort::sort_by;
use properties::{self, CascadeFlags, ComputedValues, INHERIT_ALL};
use properties::PropertyDeclarationBlock;
use restyle_hints::{RestyleHint, DependencySet};
use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
use selector_parser::{SelectorImpl, PseudoElement, Snapshot};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::{AFFECTED_BY_ANIMATIONS, AFFECTED_BY_TRANSITIONS};
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
use selectors::matching::{ElementSelectorFlags, StyleRelations, matches_complex_selector};
use selectors::parser::{Selector, SimpleSelector, LocalName as LocalNameSelector, ComplexSelector};
use sink::Push;
use smallvec::VecLike;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;
use std::sync::Arc;
use style_traits::viewport::ViewportConstraints;
use stylesheets::{CssRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets};
use thread_state;
use viewport::{self, MaybeNew, ViewportRule};
pub use ::fnv::FnvHashMap;
/// This structure holds all the selectors and device characteristics
/// for a given document. The selectors are converted into `Rule`s
/// (defined in rust-selectors), and introduced in a `SelectorMap`
/// depending on the pseudo-element (see `PerPseudoElementSelectorMap`),
/// and stylesheet origin (see the fields of `PerPseudoElementSelectorMap`).
///
/// This structure is effectively created once per pipeline, in the
/// LayoutThread corresponding to that pipeline.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Stylist {
/// Device that the stylist is currently evaluating against.
///
/// This field deserves a bigger comment due to the different use that Gecko
/// and Servo give to it (that we should eventually unify).
///
/// With Gecko, the device is never changed. Gecko manually tracks whether
/// the device data should be reconstructed, and "resets" the state of the
/// device.
///
/// On Servo, on the other hand, the device is a really cheap representation
/// that is recreated each time some constraint changes and calling
/// `set_device`.
///
/// In both cases, the device is actually _owned_ by the Stylist, and it's
/// only an `Arc` so we can implement `add_stylesheet` more idiomatically.
pub device: Arc<Device>,
/// Viewport constraints based on the current device.
viewport_constraints: Option<ViewportConstraints>,
/// If true, the quirks-mode stylesheet is applied.
quirks_mode: bool,
/// If true, the device has changed, and the stylist needs to be updated.
is_device_dirty: bool,
/// The current selector maps, after evaluating media
/// rules against the current device.
element_map: PerPseudoElementSelectorMap,
/// The rule tree, that stores the results of selector matching.
///
/// FIXME(emilio): Not `pub`!
pub rule_tree: RuleTree,
/// The selector maps corresponding to a given pseudo-element
/// (depending on the implementation)
pseudos_map: FnvHashMap<PseudoElement, PerPseudoElementSelectorMap>,
/// A map with all the animations indexed by name.
animations: FnvHashMap<Atom, KeyframesAnimation>,
/// 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.
///
/// FIXME(emilio): Use the rule tree!
precomputed_pseudo_element_decls: FnvHashMap<PseudoElement, Vec<ApplicableDeclarationBlock>>,
/// A monotonically increasing counter to represent the order on which a
/// style rule appears in a stylesheet, needed to sort them by source order.
rules_source_order: usize,
/// Selector dependencies used to compute restyle hints.
state_deps: DependencySet,
/// Selectors in the page affecting siblings
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
sibling_affecting_selectors: Vec<Selector<SelectorImpl>>,
/// Selectors in the page matching elements with non-common style-affecting
/// attributes.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
non_common_style_affecting_attributes_selectors: Vec<Selector<SelectorImpl>>,
}
impl Stylist {
/// Construct a new `Stylist`, using a given `Device`.
#[inline]
pub fn new(device: Device) -> Self {
let mut stylist = Stylist {
viewport_constraints: None,
device: Arc::new(device),
is_device_dirty: true,
quirks_mode: false,
element_map: PerPseudoElementSelectorMap::new(),
pseudos_map: Default::default(),
animations: Default::default(),
precomputed_pseudo_element_decls: Default::default(),
rules_source_order: 0,
rule_tree: RuleTree::new(),
state_deps: DependencySet::new(),
// XXX remember resetting them!
sibling_affecting_selectors: vec![],
non_common_style_affecting_attributes_selectors: vec![]
};
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
stylist.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
});
// FIXME: Add iso-8859-9.css when the documents encoding is ISO-8859-8.
stylist
}
/// Update the stylist for the given document stylesheets, and optionally
/// with a set of user agent stylesheets.
///
/// This method resets all the style data each time the stylesheets change
/// (which is indicated by the `stylesheets_changed` parameter), or the
/// device is dirty, which means we need to re-evaluate media queries.
pub fn update(&mut self,
doc_stylesheets: &[Arc<Stylesheet>],
ua_stylesheets: Option<&UserAgentStylesheets>,
stylesheets_changed: bool) -> bool {
if !(self.is_device_dirty || stylesheets_changed) {
return false;
}
let cascaded_rule = ViewportRule {
declarations: viewport::Cascade::from_stylesheets(doc_stylesheets, &self.device).finish(),
};
self.viewport_constraints =
ViewportConstraints::maybe_new(&self.device, &cascaded_rule);
if let Some(ref constraints) = self.viewport_constraints {
Arc::get_mut(&mut self.device).unwrap()
.account_for_viewport_rule(constraints);
}
self.element_map = PerPseudoElementSelectorMap::new();
self.pseudos_map = Default::default();
self.animations = Default::default();
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
});
self.precomputed_pseudo_element_decls = Default::default();
self.rules_source_order = 0;
self.state_deps.clear();
self.animations.clear();
self.sibling_affecting_selectors.clear();
self.non_common_style_affecting_attributes_selectors.clear();
if let Some(ua_stylesheets) = ua_stylesheets {
for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
self.add_stylesheet(&stylesheet);
}
if self.quirks_mode {
self.add_stylesheet(&ua_stylesheets.quirks_mode_stylesheet);
}
}
for ref stylesheet in doc_stylesheets.iter() {
self.add_stylesheet(stylesheet);
}
debug!("Stylist stats:");
debug!(" - Got {} sibling-affecting selectors",
self.sibling_affecting_selectors.len());
debug!(" - Got {} non-common-style-attribute-affecting selectors",
self.non_common_style_affecting_attributes_selectors.len());
debug!(" - Got {} deps for style-hint calculation",
self.state_deps.len());
SelectorImpl::each_precomputed_pseudo_element(|pseudo| {
if let Some(map) = self.pseudos_map.remove(&pseudo) {
let declarations =
map.user_agent.get_universal_rules(CascadeLevel::UANormal,
CascadeLevel::UAImportant);
self.precomputed_pseudo_element_decls.insert(pseudo, declarations);
}
});
self.is_device_dirty = false;
true
}
fn add_stylesheet(&mut self, stylesheet: &Stylesheet) {
if stylesheet.disabled() || !stylesheet.is_effective_for_device(&self.device) {
return;
}
// Cheap `Arc` clone so that the closure below can borrow `&mut Stylist`.
let device = self.device.clone();
stylesheet.effective_rules(&device, |rule| {
match *rule {
CssRule::Style(ref style_rule) => {
let guard = style_rule.read();
for selector in &guard.selectors.0 {
let map = if let Some(ref pseudo) = selector.pseudo_element {
self.pseudos_map
.entry(pseudo.clone())
.or_insert_with(PerPseudoElementSelectorMap::new)
.borrow_for_origin(&stylesheet.origin)
} else {
self.element_map.borrow_for_origin(&stylesheet.origin)
};
map.insert(Rule {
selector: selector.complex_selector.clone(),
style_rule: style_rule.clone(),
specificity: selector.specificity,
source_order: self.rules_source_order,
});
}
self.rules_source_order += 1;
for selector in &guard.selectors.0 {
self.state_deps.note_selector(&selector.complex_selector);
if selector.affects_siblings() {
self.sibling_affecting_selectors.push(selector.clone());
}
if selector.matches_non_common_style_affecting_attribute() {
self.non_common_style_affecting_attributes_selectors.push(selector.clone());
}
}
}
CssRule::Import(ref import) => {
let import = import.read();
self.add_stylesheet(&import.stylesheet)
}
CssRule::Keyframes(ref keyframes_rule) => {
let keyframes_rule = keyframes_rule.read();
debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
let animation = KeyframesAnimation::from_keyframes(&keyframes_rule.keyframes);
debug!("Found valid keyframe animation: {:?}", animation);
self.animations.insert(keyframes_rule.name.clone(), animation);
}
// We don't care about any other rule.
_ => {}
}
});
}
/// Computes the style for a given "precomputed" pseudo-element, taking the
/// universal rules and applying them.
///
/// If `inherit_all` is true, then all properties are inherited from the
/// parent; otherwise, non-inherited properties are reset to their initial
/// values. The flow constructor uses this flag when constructing anonymous
/// flows.
pub fn precomputed_values_for_pseudo(&self,
pseudo: &PseudoElement,
parent: Option<&Arc<ComputedValues>>,
default: &Arc<ComputedValues>,
inherit_all: bool)
-> ComputedStyle {
debug_assert!(SelectorImpl::pseudo_element_cascade_type(pseudo).is_precomputed());
let rule_node = match self.precomputed_pseudo_element_decls.get(pseudo) {
Some(declarations) => {
// FIXME(emilio): When we've taken rid of the cascade we can just
// use into_iter.
self.rule_tree.insert_ordered_rules(
declarations.into_iter().map(|a| (a.source.clone(), a.level)))
}
None => self.rule_tree.root(),
};
let mut flags = CascadeFlags::empty();
if inherit_all {
flags.insert(INHERIT_ALL)
}
// NOTE(emilio): We skip calculating the proper layout parent style
// here.
//
// It'd be fine to assert that this isn't called with a parent style
// where display contents is in effect, but in practice this is hard to
// do for stuff like :-moz-fieldset-content with a
// <fieldset style="display: contents">. That is, the computed value of
// display for the fieldset is "contents", even though it's not the used
// value, so we don't need to adjust in a different way anyway.
//
// In practice, I don't think any anonymous content can be a direct
// descendant of a display: contents element where display: contents is
// the actual used value, and the computed value of it would need
// blockification.
let computed =
properties::cascade(self.device.au_viewport_size(),
&rule_node,
parent.map(|p| &**p),
parent.map(|p| &**p),
default,
None,
Box::new(StdoutErrorReporter),
flags);
ComputedStyle::new(rule_node, Arc::new(computed))
}
/// Returns the style for an anonymous box of the given type.
#[cfg(feature = "servo")]
pub fn style_for_anonymous_box(&self,
pseudo: &PseudoElement,
parent_style: &Arc<ComputedValues>,
default_style: &Arc<ComputedValues>)
-> Arc<ComputedValues> {
// For most (but not all) pseudo-elements, we inherit all values from the parent.
let inherit_all = match *pseudo {
PseudoElement::ServoInputText => false,
PseudoElement::ServoAnonymousBlock |
PseudoElement::ServoAnonymousTable |
PseudoElement::ServoAnonymousTableCell |
PseudoElement::ServoAnonymousTableRow |
PseudoElement::ServoAnonymousTableWrapper |
PseudoElement::ServoTableWrapper => true,
PseudoElement::Before |
PseudoElement::After |
PseudoElement::Selection |
PseudoElement::DetailsSummary |
PseudoElement::DetailsContent => {
unreachable!("That pseudo doesn't represent an anonymous box!")
}
};
self.precomputed_values_for_pseudo(&pseudo, Some(parent_style), default_style, inherit_all)
.values.unwrap()
}
/// Computes a pseudo-element style lazily during layout.
///
/// This can only be done for a certain set of pseudo-elements, like
/// :selection.
///
/// Check the documentation on lazy pseudo-elements in
/// docs/components/style.md
pub fn lazily_compute_pseudo_element_style<E>(&self,
element: &E,
pseudo: &PseudoElement,
parent: &Arc<ComputedValues>,
default: &Arc<ComputedValues>)
-> Option<ComputedStyle>
where E: TElement +
fmt::Debug +
PresentationalHintsSynthetizer
{
debug_assert!(SelectorImpl::pseudo_element_cascade_type(pseudo).is_lazy());
if self.pseudos_map.get(pseudo).is_none() {
return None;
}
let mut declarations = vec![];
let mut flags = ElementSelectorFlags::empty();
self.push_applicable_declarations(element,
None,
None,
AnimationRules(None, None),
Some(pseudo),
&mut declarations,
&mut flags);
let rule_node =
self.rule_tree.insert_ordered_rules(
declarations.into_iter().map(|a| (a.source, a.level)));
// Read the comment on `precomputed_values_for_pseudo` to see why it's
// difficult to assert that display: contents nodes never arrive here
// (tl;dr: It doesn't apply for replaced elements and such, but the
// computed value is still "contents").
let computed =
properties::cascade(self.device.au_viewport_size(),
&rule_node,
Some(&**parent),
Some(&**parent),
default,
None,
Box::new(StdoutErrorReporter),
CascadeFlags::empty());
// Apply the selector flags. We should be in sequential mode already,
// so we can directly apply the parent flags.
if cfg!(feature = "servo") {
// Servo calls this function from the worker, but only for internal
// pseudos, so we should never generate selector flags here.
debug_assert!(flags.is_empty());
} else {
// Gecko calls this from sequential mode, so we can directly apply
// the flags.
debug_assert!(thread_state::get() == thread_state::LAYOUT);
let self_flags = flags.for_self();
if !self_flags.is_empty() {
unsafe { element.set_selector_flags(self_flags); }
}
let parent_flags = flags.for_parent();
if !parent_flags.is_empty() {
if let Some(p) = element.parent_element() {
unsafe { p.set_selector_flags(parent_flags); }
}
}
}
Some(ComputedStyle::new(rule_node, Arc::new(computed)))
}
/// Set a given device, which may change the styles that apply to the
/// document.
///
/// This means that we may need to rebuild style data even if the
/// stylesheets haven't changed.
///
/// Also, the device that arrives here may need to take the viewport rules
/// into account.
///
/// TODO(emilio): Probably should be unified with `update`, right now I
/// don't think we take into account dynamic updates to viewport rules.
///
/// Probably worth to make the stylist own a single `Device`, and have a
/// `update_device` function?
///
/// feature = "servo" because gecko only has one device, and manually tracks
/// when the device is dirty.
///
/// FIXME(emilio): The semantics of the device for Servo and Gecko are
/// different enough we may want to unify them.
#[cfg(feature = "servo")]
pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet>]) {
let cascaded_rule = ViewportRule {
declarations: viewport::Cascade::from_stylesheets(stylesheets, &device).finish(),
};
self.viewport_constraints =
ViewportConstraints::maybe_new(&device, &cascaded_rule);
if let Some(ref constraints) = self.viewport_constraints {
device.account_for_viewport_rule(constraints);
}
fn mq_eval_changed(rules: &[CssRule], before: &Device, after: &Device) -> bool {
for rule in rules {
let changed = rule.with_nested_rules_and_mq(|rules, mq| {
if let Some(mq) = mq {
if mq.evaluate(before) != mq.evaluate(after) {
return true
}
}
mq_eval_changed(rules, before, after)
});
if changed {
return true
}
}
false
}
self.is_device_dirty |= stylesheets.iter().any(|stylesheet| {
let mq = stylesheet.media.read();
if mq.evaluate(&self.device) != mq.evaluate(&device) {
return true
}
mq_eval_changed(&stylesheet.rules.read().0, &self.device, &device)
});
self.device = Arc::new(device);
}
/// Returns the viewport constraints that apply to this document because of
/// a @viewport rule.
pub fn viewport_constraints(&self) -> Option<&ViewportConstraints> {
self.viewport_constraints.as_ref()
}
/// Sets the quirks mode of the document.
pub fn set_quirks_mode(&mut self, enabled: bool) {
// FIXME(emilio): We don't seem to change the quirks mode dynamically
// during multiple layout passes, but this is totally bogus, in the
// sense that it's updated asynchronously.
//
// This should probably be an argument to `update`, and use the quirks
// mode info in the `SharedLayoutContext`.
self.quirks_mode = enabled;
}
/// Returns the applicable CSS declarations for the given element.
///
/// This corresponds to `ElementRuleCollector` in WebKit.
///
/// The returned `StyleRelations` indicate hints about which kind of rules
/// have matched.
pub fn push_applicable_declarations<E, V>(
&self,
element: &E,
parent_bf: Option<&BloomFilter>,
style_attribute: Option<&Arc<RwLock<PropertyDeclarationBlock>>>,
animation_rules: AnimationRules,
pseudo_element: Option<&PseudoElement>,
applicable_declarations: &mut V,
flags: &mut ElementSelectorFlags) -> StyleRelations
where E: TElement +
fmt::Debug +
PresentationalHintsSynthetizer,
V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>
{
debug_assert!(!self.is_device_dirty);
debug_assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
debug_assert!(pseudo_element.is_none() ||
!SelectorImpl::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(),
None => &self.element_map,
};
let mut relations = StyleRelations::empty();
debug!("Determining if style is shareable: pseudo: {}", pseudo_element.is_some());
// Step 1: Normal user-agent rules.
map.user_agent.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut relations,
flags,
CascadeLevel::UANormal);
debug!("UA normal: {:?}", relations);
// Step 2: Presentational hints.
let length_before_preshints = applicable_declarations.len();
element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
if applicable_declarations.len() != length_before_preshints {
if cfg!(debug_assertions) {
for declaration in &applicable_declarations[length_before_preshints..] {
assert_eq!(declaration.level, CascadeLevel::PresHints);
}
}
// Never share style for elements with preshints
relations |= AFFECTED_BY_PRESENTATIONAL_HINTS;
}
debug!("preshints: {:?}", relations);
if element.matches_user_and_author_rules() {
// Step 3: User and author normal rules.
map.user.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut relations,
flags,
CascadeLevel::UserNormal);
debug!("user normal: {:?}", relations);
map.author.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut relations,
flags,
CascadeLevel::AuthorNormal);
debug!("author normal: {:?}", relations);
// Step 4: Normal style attributes.
if let Some(sa) = style_attribute {
if sa.read().any_normal() {
relations |= AFFECTED_BY_STYLE_ATTRIBUTE;
Push::push(
applicable_declarations,
ApplicableDeclarationBlock::from_declarations(sa.clone(),
CascadeLevel::StyleAttributeNormal));
}
}
debug!("style attr: {:?}", relations);
// Step 5: Animations.
// The animations sheet (CSS animations, script-generated animations,
// and CSS transitions that are no longer tied to CSS markup)
if let Some(anim) = animation_rules.0 {
relations |= AFFECTED_BY_ANIMATIONS;
Push::push(
applicable_declarations,
ApplicableDeclarationBlock::from_declarations(anim.clone(),
CascadeLevel::Animations));
}
debug!("animation: {:?}", relations);
// Step 6: Author-supplied `!important` rules.
map.author.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut relations,
flags,
CascadeLevel::AuthorImportant);
debug!("author important: {:?}", relations);
// Step 7: `!important` style attributes.
if let Some(sa) = style_attribute {
if sa.read().any_important() {
relations |= AFFECTED_BY_STYLE_ATTRIBUTE;
Push::push(
applicable_declarations,
ApplicableDeclarationBlock::from_declarations(sa.clone(),
CascadeLevel::StyleAttributeImportant));
}
}
debug!("style attr important: {:?}", relations);
// Step 8: User `!important` rules.
map.user.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut relations,
flags,
CascadeLevel::UserImportant);
debug!("user important: {:?}", relations);
} else {
debug!("skipping non-agent rules");
}
// Step 9: UA `!important` rules.
map.user_agent.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut relations,
flags,
CascadeLevel::UAImportant);
debug!("UA important: {:?}", relations);
// Step 10: Transitions.
// The transitions sheet (CSS transitions that are tied to CSS markup)
if let Some(anim) = animation_rules.1 {
relations |= AFFECTED_BY_TRANSITIONS;
Push::push(
applicable_declarations,
ApplicableDeclarationBlock::from_declarations(anim.clone(), CascadeLevel::Transitions));
}
debug!("transition: {:?}", relations);
debug!("push_applicable_declarations: shareable: {:?}", relations);
relations
}
/// Return whether the device is dirty, that is, whether the screen size or
/// media type have changed (for now).
#[inline]
pub fn is_device_dirty(&self) -> bool {
self.is_device_dirty
}
/// Returns the map of registered `@keyframes` animations.
#[inline]
pub fn animations(&self) -> &FnvHashMap<Atom, KeyframesAnimation> {
&self.animations
}
/// Whether two elements match the same not-common style-affecting attribute
/// rules.
///
/// This is used to test elements and candidates in the style-sharing
/// candidate cache.
pub fn match_same_not_common_style_affecting_attributes_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: TElement,
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_complex_selector;
// TODO(emilio): we can probably do better, the candidate should already
// know what rules it matches. Also, we should only match until we find
// a descendant combinator, the rest should be ok, since the parent is
// the same.
//
// TODO(emilio): Use the bloom filter, since they contain the element's
// ancestor chain and it's correct for the candidate too.
for ref selector in self.non_common_style_affecting_attributes_selectors.iter() {
let element_matches =
matches_complex_selector(&selector.complex_selector, element,
None, &mut StyleRelations::empty(),
&mut ElementSelectorFlags::empty());
let candidate_matches =
matches_complex_selector(&selector.complex_selector, candidate,
None, &mut StyleRelations::empty(),
&mut ElementSelectorFlags::empty());
if element_matches != candidate_matches {
return false;
}
}
true
}
/// Returns the rule root node.
#[inline]
pub fn rule_tree_root(&self) -> StrongRuleNode {
self.rule_tree.root()
}
/// Returns whether two elements match the same sibling-affecting rules.
///
/// This is also for the style sharing candidate cache.
pub fn match_same_sibling_affecting_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: TElement,
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_complex_selector;
// TODO(emilio): we can probably do better, the candidate should already
// know what rules it matches.
//
// TODO(emilio): Use the bloom filter, since they contain the element's
// ancestor chain and it's correct for the candidate too.
for ref selector in self.sibling_affecting_selectors.iter() {
let element_matches =
matches_complex_selector(&selector.complex_selector, element,
None, &mut StyleRelations::empty(),
&mut ElementSelectorFlags::empty());
let candidate_matches =
matches_complex_selector(&selector.complex_selector, candidate,
None, &mut StyleRelations::empty(),
&mut ElementSelectorFlags::empty());
if element_matches != candidate_matches {
debug!("match_same_sibling_affecting_rules: Failure due to {:?}",
selector.complex_selector);
return false;
}
}
true
}
/// Given an element, and a snapshot that represents a previous state of the
/// element, compute the appropriate restyle hint, that is, the kind of
/// restyle we need to do.
pub fn compute_restyle_hint<E>(&self,
element: &E,
snapshot: &Snapshot)
-> RestyleHint
where E: TElement,
{
self.state_deps.compute_hint(element, snapshot)
}
}
impl Drop for Stylist {
fn drop(&mut self) {
// This is the last chance to GC the rule tree. If we have dropped all
// strong rule node references before the Stylist is dropped, then this
// will cause the rule tree to be destroyed correctly. If we haven't
// dropped all strong rule node references before now, then we will
// leak them, since there will be no way to call gc() on the rule tree
// after this point.
//
// TODO(emilio): We can at least assert all the elements in the free
// list are indeed free.
unsafe { self.rule_tree.gc(); }
}
}
/// Map that contains the CSS rules for a specific PseudoElement
/// (or lack of PseudoElement).
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
struct PerPseudoElementSelectorMap {
/// Rules from user agent stylesheets
user_agent: SelectorMap,
/// Rules from author stylesheets
author: SelectorMap,
/// Rules from user stylesheets
user: SelectorMap,
}
impl PerPseudoElementSelectorMap {
#[inline]
fn new() -> Self {
PerPseudoElementSelectorMap {
user_agent: SelectorMap::new(),
author: SelectorMap::new(),
user: SelectorMap::new(),
}
}
#[inline]
fn borrow_for_origin(&mut self, origin: &Origin) -> &mut SelectorMap {
match *origin {
Origin::UserAgent => &mut self.user_agent,
Origin::Author => &mut self.author,
Origin::User => &mut self.user,
}
}
}
/// Map element data to Rules whose last simple selector starts with them.
///
/// e.g.,
/// "p > img" would go into the set of Rules corresponding to the
/// element "img"
/// "a .foo .bar.baz" would go into the set of Rules corresponding to
/// the class "bar"
///
/// Because we match Rules right-to-left (i.e., moving up the tree
/// from an element), we need to compare the last simple selector in the
/// Rule with the element.
///
/// So, if an element has ID "id1" and classes "foo" and "bar", then all
/// the rules it matches will have their last simple selector starting
/// either with "#id1" or with ".foo" or with ".bar".
///
/// Hence, the union of the rules keyed on each of element's classes, ID,
/// element name, etc. will contain the Rules that actually match that
/// element.
///
/// TODO: Tune the initial capacity of the HashMap
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct SelectorMap {
/// A hash from an ID to rules which contain that ID selector.
pub id_hash: FnvHashMap<Atom, Vec<Rule>>,
/// A hash from a class name to rules which contain that class selector.
pub class_hash: FnvHashMap<Atom, Vec<Rule>>,
/// A hash from local name to rules which contain that local name selector.
pub local_name_hash: FnvHashMap<LocalName, Vec<Rule>>,
/// Same as local_name_hash, but keys are lower-cased.
/// For HTML elements in HTML documents.
pub lower_local_name_hash: FnvHashMap<LocalName, Vec<Rule>>,
/// Rules that don't have ID, class, or element selectors.
pub other_rules: Vec<Rule>,
/// Whether this hash is empty.
pub empty: bool,
}
#[inline]
fn sort_by_key<T, F: Fn(&T) -> K, K: Ord>(v: &mut [T], f: F) {
sort_by(v, |a, b| f(a).cmp(&f(b)))
}
impl SelectorMap {
/// Trivially constructs an empty `SelectorMap`.
pub fn new() -> Self {
SelectorMap {
id_hash: HashMap::default(),
class_hash: HashMap::default(),
local_name_hash: HashMap::default(),
lower_local_name_hash: HashMap::default(),
other_rules: Vec::new(),
empty: true,
}
}
/// Append to `rule_list` all Rules in `self` that match element.
///
/// Extract matching rules as per element's ID, classes, tag name, etc..
/// Sort the Rules at the end to maintain cascading order.
pub fn get_all_matching_rules<E, V>(&self,
element: &E,
parent_bf: Option<&BloomFilter>,
matching_rules_list: &mut V,
relations: &mut StyleRelations,
flags: &mut ElementSelectorFlags,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
V: VecLike<ApplicableDeclarationBlock>
{
if self.empty {
return
}
// At the end, we're going to sort the rules that we added, so remember where we began.
let init_len = matching_rules_list.len();
if let Some(id) = element.get_id() {
SelectorMap::get_matching_rules_from_hash(element,
parent_bf,
&self.id_hash,
&id,
matching_rules_list,
relations,
flags,
cascade_level)
}
element.each_class(|class| {
SelectorMap::get_matching_rules_from_hash(element,
parent_bf,
&self.class_hash,
class,
matching_rules_list,
relations,
flags,
cascade_level);
});
let local_name_hash = if element.is_html_element_in_html_document() {
&self.lower_local_name_hash
} else {
&self.local_name_hash
};
SelectorMap::get_matching_rules_from_hash(element,
parent_bf,
local_name_hash,
element.get_local_name(),
matching_rules_list,
relations,
flags,
cascade_level);
SelectorMap::get_matching_rules(element,
parent_bf,
&self.other_rules,
matching_rules_list,
relations,
flags,
cascade_level);
// Sort only the rules we just added.
sort_by_key(&mut matching_rules_list[init_len..],
|block| (block.specificity, block.source_order));
}
/// Append to `rule_list` all universal Rules (rules with selector `*|*`) in
/// `self` sorted by specificity and source order.
pub fn get_universal_rules(&self,
cascade_level: CascadeLevel,
important_cascade_level: CascadeLevel)
-> Vec<ApplicableDeclarationBlock> {
debug_assert!(!cascade_level.is_important());
debug_assert!(important_cascade_level.is_important());
if self.empty {
return vec![];
}
let mut matching_rules_list = vec![];
// We need to insert important rules _after_ normal rules for this to be
// correct, and also to not trigger rule tree assertions.
let mut important = vec![];
for rule in self.other_rules.iter() {
if rule.selector.compound_selector.is_empty() &&
rule.selector.next.is_none() {
let guard = rule.style_rule.read();
let block = guard.block.read();
if block.any_normal() {
matching_rules_list.push(
rule.to_applicable_declaration_block(cascade_level));
}
if block.any_important() {
important.push(
rule.to_applicable_declaration_block(important_cascade_level));
}
}
}
let normal_len = matching_rules_list.len();
matching_rules_list.extend(important.into_iter());
sort_by_key(&mut matching_rules_list[0..normal_len],
|block| (block.specificity, block.source_order));
sort_by_key(&mut matching_rules_list[normal_len..],
|block| (block.specificity, block.source_order));
matching_rules_list
}
fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector>(
element: &E,
parent_bf: Option<&BloomFilter>,
hash: &FnvHashMap<Str, Vec<Rule>>,
key: &BorrowedStr,
matching_rules: &mut Vector,
relations: &mut StyleRelations,
flags: &mut ElementSelectorFlags,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
Str: Borrow<BorrowedStr> + Eq + Hash,
BorrowedStr: Eq + Hash,
Vector: VecLike<ApplicableDeclarationBlock>
{
if let Some(rules) = hash.get(key) {
SelectorMap::get_matching_rules(element,
parent_bf,
rules,
matching_rules,
relations,
flags,
cascade_level)
}
}
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
fn get_matching_rules<E, V>(element: &E,
parent_bf: Option<&BloomFilter>,
rules: &[Rule],
matching_rules: &mut V,
relations: &mut StyleRelations,
flags: &mut ElementSelectorFlags,
cascade_level: CascadeLevel)
where E: Element<Impl=SelectorImpl>,
V: VecLike<ApplicableDeclarationBlock>
{
for rule in rules.iter() {
let guard = rule.style_rule.read();
let block = guard.block.read();
let any_declaration_for_importance = if cascade_level.is_important() {
block.any_important()
} else {
block.any_normal()
};
if any_declaration_for_importance &&
matches_complex_selector(&*rule.selector, element, parent_bf,
relations, flags) {
matching_rules.push(
rule.to_applicable_declaration_block(cascade_level));
}
}
}
/// Insert rule into the correct hash.
/// Order in which to try: id_hash, class_hash, local_name_hash, other_rules.
pub fn insert(&mut self, rule: Rule) {
self.empty = false;
if let Some(id_name) = SelectorMap::get_id_name(&rule) {
find_push(&mut self.id_hash, id_name, rule);
return;
}
if let Some(class_name) = SelectorMap::get_class_name(&rule) {
find_push(&mut self.class_hash, class_name, rule);
return;
}
if let Some(LocalNameSelector { name, lower_name }) = SelectorMap::get_local_name(&rule) {
find_push(&mut self.local_name_hash, name, rule.clone());
find_push(&mut self.lower_local_name_hash, lower_name, rule);
return;
}
self.other_rules.push(rule);
}
/// Retrieve the first ID name in Rule, or None otherwise.
pub fn get_id_name(rule: &Rule) -> Option<Atom> {
for ss in &rule.selector.compound_selector {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let SimpleSelector::ID(ref id) = *ss {
return Some(id.clone());
}
}
None
}
/// Retrieve the FIRST class name in Rule, or None otherwise.
pub fn get_class_name(rule: &Rule) -> Option<Atom> {
for ss in &rule.selector.compound_selector {
// TODO(pradeep): Implement case-sensitivity based on the
// document type and quirks mode.
if let SimpleSelector::Class(ref class) = *ss {
return Some(class.clone());
}
}
None
}
/// Retrieve the name if it is a type selector, or None otherwise.
pub fn get_local_name(rule: &Rule) -> Option<LocalNameSelector<SelectorImpl>> {
for ss in &rule.selector.compound_selector {
if let SimpleSelector::LocalName(ref n) = *ss {
return Some(LocalNameSelector {
name: n.name.clone(),
lower_name: n.lower_name.clone(),
})
}
}
None
}
}
/// A rule, that wraps a style rule, but represents a single selector of the
/// rule.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Debug)]
pub struct Rule {
/// The selector this struct represents.
/// This is an Arc because Rule will essentially be cloned for every element
/// that it matches. Selector contains an owned vector (through
/// ComplexSelector) and we want to avoid the allocation.
///
/// FIXME(emilio): We should be able to get rid of it and just use the style
/// rule? This predates the time where the rule was in `selectors`, and the
/// style rule was a generic parameter to it. It's not trivial though, due
/// to the specificity.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
pub selector: Arc<ComplexSelector<SelectorImpl>>,
/// The actual style rule.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
pub style_rule: Arc<RwLock<StyleRule>>,
/// The source order this style rule appears in.
pub source_order: usize,
/// The specificity of the rule this selector represents.
pub specificity: u32,
}
impl Rule {
fn to_applicable_declaration_block(&self, level: CascadeLevel) -> ApplicableDeclarationBlock {
ApplicableDeclarationBlock {
source: StyleSource::Style(self.style_rule.clone()),
level: level,
source_order: self.source_order,
specificity: self.specificity,
}
}
}
/// A property declaration together with its precedence among rules of equal
/// specificity so that we can sort them.
///
/// This represents the declarations in a given declaration block for a given
/// importance.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Debug, Clone)]
pub struct ApplicableDeclarationBlock {
/// The style source, either a style rule, or a property declaration block.
#[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
pub source: StyleSource,
/// The cascade level this applicable declaration block is in.
pub level: CascadeLevel,
/// The source order of this block.
pub source_order: usize,
/// The specificity of the selector this block is represented by.
pub specificity: u32,
}
impl ApplicableDeclarationBlock {
/// Constructs an applicable declaration block from a given property
/// declaration block and importance.
#[inline]
pub fn from_declarations(declarations: Arc<RwLock<PropertyDeclarationBlock>>,
level: CascadeLevel)
-> Self {
ApplicableDeclarationBlock {
source: StyleSource::Declarations(declarations),
level: level,
source_order: 0,
specificity: 0,
}
}
}
#[inline]
fn find_push<Str: Eq + Hash>(map: &mut FnvHashMap<Str, Vec<Rule>>, key: Str,
value: Rule) {
map.entry(key).or_insert_with(Vec::new).push(value)
}