gecko-dev/servo/components/style/selector_matching.rs

284 lines
11 KiB
Rust
Raw Normal View History

/* 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/. */
use url::Url;
use selectors::bloom::BloomFilter;
use selectors::matching::{SelectorMap, Rule};
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
use selectors::parser::PseudoElement;
use selectors::smallvec::VecLike;
use selectors::tree::TNode;
use util::resource_files::read_resource_file;
use legacy::PresentationalHintSynthesis;
use media_queries::Device;
use node::TElementAttributes;
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules, Origin};
pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
pub struct Stylist {
// List of stylesheets (including all media rules)
stylesheets: Vec<Stylesheet>,
// Device that the stylist is currently evaluating against.
pub device: Device,
// If true, a stylesheet has been added or the device has
// changed, and the stylist needs to be updated.
is_dirty: bool,
// The current selector maps, after evaluating media
// rules against the current device.
element_map: PerPseudoElementSelectorMap,
before_map: PerPseudoElementSelectorMap,
after_map: PerPseudoElementSelectorMap,
rules_source_order: usize,
}
impl Stylist {
#[inline]
pub fn new(device: Device) -> Stylist {
let mut stylist = Stylist {
stylesheets: vec!(),
device: device,
is_dirty: true,
element_map: PerPseudoElementSelectorMap::new(),
before_map: PerPseudoElementSelectorMap::new(),
after_map: PerPseudoElementSelectorMap::new(),
rules_source_order: 0,
};
// FIXME: Add iso-8859-9.css when the documents encoding is ISO-8859-8.
// FIXME: presentational-hints.css should be at author origin with zero specificity.
// (Does it make a difference?)
for &filename in ["user-agent.css", "servo.css", "presentational-hints.css"].iter() {
let ua_stylesheet = Stylesheet::from_bytes(
&read_resource_file(&[filename]).unwrap(),
Url::parse(&format!("chrome:///{:?}", filename)).unwrap(),
None,
None,
Origin::UserAgent);
stylist.add_stylesheet(ua_stylesheet);
}
stylist
}
pub fn update(&mut self) -> bool {
if self.is_dirty {
self.element_map = PerPseudoElementSelectorMap::new();
self.before_map = PerPseudoElementSelectorMap::new();
self.after_map = PerPseudoElementSelectorMap::new();
self.rules_source_order = 0;
for stylesheet in self.stylesheets.iter() {
let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin {
Origin::UserAgent => (
&mut self.element_map.user_agent,
&mut self.before_map.user_agent,
&mut self.after_map.user_agent,
),
Origin::Author => (
&mut self.element_map.author,
&mut self.before_map.author,
&mut self.after_map.author,
),
Origin::User => (
&mut self.element_map.user,
&mut self.before_map.user,
&mut self.after_map.user,
),
};
let mut rules_source_order = self.rules_source_order;
// Take apart the StyleRule into individual Rules and insert
// them into the SelectorMap of that priority.
macro_rules! append(
($style_rule: ident, $priority: ident) => {
if $style_rule.declarations.$priority.len() > 0 {
for selector in $style_rule.selectors.iter() {
let map = match selector.pseudo_element {
None => &mut element_map,
Some(PseudoElement::Before) => &mut before_map,
Some(PseudoElement::After) => &mut after_map,
};
map.$priority.insert(Rule {
selector: selector.compound_selectors.clone(),
declarations: DeclarationBlock {
specificity: selector.specificity,
declarations: $style_rule.declarations.$priority.clone(),
source_order: rules_source_order,
},
});
}
}
};
);
iter_stylesheet_style_rules(stylesheet, &self.device, |style_rule| {
append!(style_rule, normal);
append!(style_rule, important);
rules_source_order += 1;
});
self.rules_source_order = rules_source_order;
}
self.is_dirty = false;
return true;
}
false
}
pub fn set_device(&mut self, device: Device) {
let is_dirty = self.is_dirty || self.stylesheets.iter().any(|stylesheet| {
let mut stylesheet_dirty = false;
iter_stylesheet_media_rules(stylesheet, |rule| {
stylesheet_dirty |= rule.media_queries.evaluate(&self.device) !=
rule.media_queries.evaluate(&device);
});
stylesheet_dirty
});
self.device = device;
self.is_dirty |= is_dirty;
}
pub fn add_quirks_mode_stylesheet(&mut self) {
self.add_stylesheet(Stylesheet::from_bytes(
&read_resource_file(&["quirks-mode.css"]).unwrap(),
Url::parse("chrome:///quirks-mode.css").unwrap(),
None,
None,
Origin::UserAgent))
}
pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) {
self.stylesheets.push(stylesheet);
self.is_dirty = true;
}
/// Returns the applicable CSS declarations for the given element. This corresponds to
/// `ElementRuleCollector` in WebKit.
///
/// The returned boolean indicates whether the style is *shareable*; that is, whether the
/// matched selectors are simple enough to allow the matching logic to be reduced to the logic
/// in `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`.
pub fn push_applicable_declarations<'a,N,V>(
&self,
element: &N,
parent_bf: &Option<Box<BloomFilter>>,
style_attribute: Option<&PropertyDeclarationBlock>,
pseudo_element: Option<PseudoElement>,
applicable_declarations: &mut V)
-> bool
where N: TNode<'a>,
N::Element: TElementAttributes,
V: VecLike<DeclarationBlock> {
assert!(!self.is_dirty);
assert!(element.is_element());
assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
let map = match pseudo_element {
None => &self.element_map,
Some(PseudoElement::Before) => &self.before_map,
Some(PseudoElement::After) => &self.after_map,
};
let mut shareable = true;
// Step 1: Normal user-agent rules.
map.user_agent.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
// Step 2: Presentational hints.
self.synthesize_presentational_hints_for_legacy_attributes(element,
applicable_declarations,
&mut shareable);
// Step 3: User and author normal rules.
map.user.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
map.author.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
// Step 4: Normal style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(
GenericDeclarationBlock::from_declarations(sa.normal.clone()))
});
// Step 5: Author-supplied `!important` rules.
map.author.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
// Step 6: `!important` style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(
GenericDeclarationBlock::from_declarations(sa.important.clone()))
});
// Step 7: User and UA `!important` rules.
map.user.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
map.user_agent.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
shareable
}
}
struct PerOriginSelectorMap {
normal: SelectorMap<Vec<PropertyDeclaration>>,
important: SelectorMap<Vec<PropertyDeclaration>>,
}
impl PerOriginSelectorMap {
#[inline]
fn new() -> PerOriginSelectorMap {
PerOriginSelectorMap {
normal: SelectorMap::new(),
important: SelectorMap::new(),
}
}
}
struct PerPseudoElementSelectorMap {
user_agent: PerOriginSelectorMap,
author: PerOriginSelectorMap,
user: PerOriginSelectorMap,
}
impl PerPseudoElementSelectorMap {
#[inline]
fn new() -> PerPseudoElementSelectorMap {
PerPseudoElementSelectorMap {
user_agent: PerOriginSelectorMap::new(),
author: PerOriginSelectorMap::new(),
user: PerOriginSelectorMap::new(),
}
}
}