2013-10-02 19:21:53 +00:00
|
|
|
|
/* 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/. */
|
|
|
|
|
|
2014-08-16 11:24:34 +00:00
|
|
|
|
use url::Url;
|
|
|
|
|
|
2015-02-23 15:39:47 +00:00
|
|
|
|
use selectors::bloom::BloomFilter;
|
|
|
|
|
use selectors::matching::{SelectorMap, Rule};
|
|
|
|
|
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
|
|
|
|
|
use selectors::parser::PseudoElement;
|
|
|
|
|
use selectors::smallvec::VecLike;
|
2015-03-18 21:21:49 +00:00
|
|
|
|
use selectors::tree::TNode;
|
2015-01-29 12:12:49 +00:00
|
|
|
|
use util::resource_files::read_resource_file;
|
2014-01-25 21:19:02 +00:00
|
|
|
|
|
2014-12-16 02:33:46 +00:00
|
|
|
|
use legacy::PresentationalHintSynthesis;
|
2014-10-15 00:36:29 +00:00
|
|
|
|
use media_queries::Device;
|
2015-02-23 15:39:47 +00:00
|
|
|
|
use node::TElementAttributes;
|
2014-12-16 02:33:46 +00:00
|
|
|
|
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
|
2015-01-21 21:27:48 +00:00
|
|
|
|
use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules, Origin};
|
2013-10-02 19:21:53 +00:00
|
|
|
|
|
|
|
|
|
|
2015-02-23 15:39:47 +00:00
|
|
|
|
pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
|
2013-11-14 04:13:34 +00:00
|
|
|
|
|
2014-09-16 04:27:24 +00:00
|
|
|
|
|
2013-11-14 04:13:34 +00:00
|
|
|
|
pub struct Stylist {
|
2014-11-07 00:24:28 +00:00
|
|
|
|
// 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.
|
2014-04-27 22:52:39 +00:00
|
|
|
|
element_map: PerPseudoElementSelectorMap,
|
|
|
|
|
before_map: PerPseudoElementSelectorMap,
|
|
|
|
|
after_map: PerPseudoElementSelectorMap,
|
2015-04-06 14:09:52 +00:00
|
|
|
|
rules_source_order: usize,
|
2013-11-14 04:13:34 +00:00
|
|
|
|
}
|
2013-10-02 19:21:53 +00:00
|
|
|
|
|
|
|
|
|
impl Stylist {
|
|
|
|
|
#[inline]
|
2014-11-07 00:24:28 +00:00
|
|
|
|
pub fn new(device: Device) -> Stylist {
|
2014-08-16 11:24:34 +00:00
|
|
|
|
let mut stylist = Stylist {
|
2014-11-07 00:24:28 +00:00
|
|
|
|
stylesheets: vec!(),
|
|
|
|
|
device: device,
|
|
|
|
|
is_dirty: true,
|
|
|
|
|
|
2014-01-08 17:58:29 +00:00
|
|
|
|
element_map: PerPseudoElementSelectorMap::new(),
|
|
|
|
|
before_map: PerPseudoElementSelectorMap::new(),
|
|
|
|
|
after_map: PerPseudoElementSelectorMap::new(),
|
2015-04-06 14:09:52 +00:00
|
|
|
|
rules_source_order: 0,
|
2014-08-16 11:24:34 +00:00
|
|
|
|
};
|
2014-10-13 18:21:44 +00:00
|
|
|
|
// FIXME: Add iso-8859-9.css when the document’s 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(
|
2015-02-13 10:06:49 +00:00
|
|
|
|
&read_resource_file(&[filename]).unwrap(),
|
|
|
|
|
Url::parse(&format!("chrome:///{:?}", filename)).unwrap(),
|
2014-10-13 18:21:44 +00:00
|
|
|
|
None,
|
2014-11-07 00:24:28 +00:00
|
|
|
|
None,
|
2015-01-21 21:27:48 +00:00
|
|
|
|
Origin::UserAgent);
|
2014-11-07 00:24:28 +00:00
|
|
|
|
stylist.add_stylesheet(ua_stylesheet);
|
2014-10-13 18:21:44 +00:00
|
|
|
|
}
|
2014-08-16 11:24:34 +00:00
|
|
|
|
stylist
|
2013-10-02 19:21:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-07 00:24:28 +00:00
|
|
|
|
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 {
|
2015-01-21 21:27:48 +00:00
|
|
|
|
Origin::UserAgent => (
|
2014-11-07 00:24:28 +00:00
|
|
|
|
&mut self.element_map.user_agent,
|
|
|
|
|
&mut self.before_map.user_agent,
|
|
|
|
|
&mut self.after_map.user_agent,
|
|
|
|
|
),
|
2015-01-21 21:27:48 +00:00
|
|
|
|
Origin::Author => (
|
2014-11-07 00:24:28 +00:00
|
|
|
|
&mut self.element_map.author,
|
|
|
|
|
&mut self.before_map.author,
|
|
|
|
|
&mut self.after_map.author,
|
|
|
|
|
),
|
2015-01-21 21:27:48 +00:00
|
|
|
|
Origin::User => (
|
2014-11-07 00:24:28 +00:00
|
|
|
|
&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,
|
2014-12-18 01:45:49 +00:00
|
|
|
|
Some(PseudoElement::Before) => &mut before_map,
|
|
|
|
|
Some(PseudoElement::After) => &mut after_map,
|
2014-11-07 00:24:28 +00:00
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
|
}
|
2013-10-02 19:21:53 +00:00
|
|
|
|
|
2014-11-07 00:24:28 +00:00
|
|
|
|
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
|
2014-01-13 03:16:21 +00:00
|
|
|
|
});
|
2014-11-07 00:24:28 +00:00
|
|
|
|
|
|
|
|
|
self.device = device;
|
|
|
|
|
self.is_dirty |= is_dirty;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-16 02:33:46 +00:00
|
|
|
|
pub fn add_quirks_mode_stylesheet(&mut self) {
|
|
|
|
|
self.add_stylesheet(Stylesheet::from_bytes(
|
2015-02-13 10:06:49 +00:00
|
|
|
|
&read_resource_file(&["quirks-mode.css"]).unwrap(),
|
2014-12-16 02:33:46 +00:00
|
|
|
|
Url::parse("chrome:///quirks-mode.css").unwrap(),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
2015-01-21 21:27:48 +00:00
|
|
|
|
Origin::UserAgent))
|
2014-12-16 02:33:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-11-07 00:24:28 +00:00
|
|
|
|
pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) {
|
|
|
|
|
self.stylesheets.push(stylesheet);
|
|
|
|
|
self.is_dirty = true;
|
2013-10-02 19:21:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-12-15 19:49:24 +00:00
|
|
|
|
/// Returns the applicable CSS declarations for the given element. This corresponds to
|
|
|
|
|
/// `ElementRuleCollector` in WebKit.
|
2014-02-15 01:47:01 +00:00
|
|
|
|
///
|
|
|
|
|
/// 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`.
|
2015-03-18 17:25:00 +00:00
|
|
|
|
pub fn push_applicable_declarations<'a,N,V>(
|
2014-02-08 20:43:41 +00:00
|
|
|
|
&self,
|
|
|
|
|
element: &N,
|
2014-10-11 02:27:39 +00:00
|
|
|
|
parent_bf: &Option<Box<BloomFilter>>,
|
2014-02-08 20:43:41 +00:00
|
|
|
|
style_attribute: Option<&PropertyDeclarationBlock>,
|
|
|
|
|
pseudo_element: Option<PseudoElement>,
|
2014-02-15 01:47:01 +00:00
|
|
|
|
applicable_declarations: &mut V)
|
2014-10-14 20:06:36 +00:00
|
|
|
|
-> bool
|
2015-03-18 17:25:00 +00:00
|
|
|
|
where N: TNode<'a>,
|
|
|
|
|
N::Element: TElementAttributes,
|
2014-10-14 20:06:36 +00:00
|
|
|
|
V: VecLike<DeclarationBlock> {
|
2014-11-07 00:24:28 +00:00
|
|
|
|
assert!(!self.is_dirty);
|
2013-11-14 04:13:34 +00:00
|
|
|
|
assert!(element.is_element());
|
2014-01-08 17:58:29 +00:00
|
|
|
|
assert!(style_attribute.is_none() || pseudo_element.is_none(),
|
2013-11-14 04:13:34 +00:00
|
|
|
|
"Style attributes do not apply to pseudo-elements");
|
2014-01-08 17:58:29 +00:00
|
|
|
|
|
|
|
|
|
let map = match pseudo_element {
|
|
|
|
|
None => &self.element_map,
|
2014-12-18 01:45:49 +00:00
|
|
|
|
Some(PseudoElement::Before) => &self.before_map,
|
|
|
|
|
Some(PseudoElement::After) => &self.after_map,
|
2014-01-08 17:58:29 +00:00
|
|
|
|
};
|
2013-11-14 04:13:34 +00:00
|
|
|
|
|
2014-02-15 01:47:01 +00:00
|
|
|
|
let mut shareable = true;
|
2013-12-15 19:49:24 +00:00
|
|
|
|
|
2014-10-14 20:06:36 +00:00
|
|
|
|
|
2015-03-12 19:03:49 +00:00
|
|
|
|
// Step 1: Normal user-agent rules.
|
2014-02-15 01:47:01 +00:00
|
|
|
|
map.user_agent.normal.get_all_matching_rules(element,
|
2014-09-16 04:27:24 +00:00
|
|
|
|
parent_bf,
|
2014-02-15 01:47:01 +00:00
|
|
|
|
applicable_declarations,
|
|
|
|
|
&mut shareable);
|
2015-03-12 19:03:49 +00:00
|
|
|
|
|
|
|
|
|
// Step 2: Presentational hints.
|
|
|
|
|
self.synthesize_presentational_hints_for_legacy_attributes(element,
|
|
|
|
|
applicable_declarations,
|
|
|
|
|
&mut shareable);
|
|
|
|
|
|
|
|
|
|
// Step 3: User and author normal rules.
|
2014-10-14 20:06:36 +00:00
|
|
|
|
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);
|
|
|
|
|
|
2015-03-12 19:03:49 +00:00
|
|
|
|
// Step 4: Normal style attributes.
|
2014-02-15 01:47:01 +00:00
|
|
|
|
style_attribute.map(|sa| {
|
|
|
|
|
shareable = false;
|
2015-02-23 15:39:47 +00:00
|
|
|
|
applicable_declarations.vec_push(
|
|
|
|
|
GenericDeclarationBlock::from_declarations(sa.normal.clone()))
|
2014-02-15 01:47:01 +00:00
|
|
|
|
});
|
2013-12-15 19:49:24 +00:00
|
|
|
|
|
2015-03-12 19:03:49 +00:00
|
|
|
|
// Step 5: Author-supplied `!important` rules.
|
2014-02-15 01:47:01 +00:00
|
|
|
|
map.author.important.get_all_matching_rules(element,
|
2014-09-16 04:27:24 +00:00
|
|
|
|
parent_bf,
|
2014-02-15 01:47:01 +00:00
|
|
|
|
applicable_declarations,
|
|
|
|
|
&mut shareable);
|
2013-12-15 19:49:24 +00:00
|
|
|
|
|
2015-03-12 19:03:49 +00:00
|
|
|
|
// Step 6: `!important` style attributes.
|
2014-02-15 01:47:01 +00:00
|
|
|
|
style_attribute.map(|sa| {
|
|
|
|
|
shareable = false;
|
2015-02-23 15:39:47 +00:00
|
|
|
|
applicable_declarations.vec_push(
|
|
|
|
|
GenericDeclarationBlock::from_declarations(sa.important.clone()))
|
2014-02-15 01:47:01 +00:00
|
|
|
|
});
|
2013-12-15 19:49:24 +00:00
|
|
|
|
|
2015-03-12 19:03:49 +00:00
|
|
|
|
// Step 7: User and UA `!important` rules.
|
2014-02-15 01:47:01 +00:00
|
|
|
|
map.user.important.get_all_matching_rules(element,
|
2014-09-16 04:27:24 +00:00
|
|
|
|
parent_bf,
|
2014-02-15 01:47:01 +00:00
|
|
|
|
applicable_declarations,
|
|
|
|
|
&mut shareable);
|
|
|
|
|
map.user_agent.important.get_all_matching_rules(element,
|
2014-09-16 04:27:24 +00:00
|
|
|
|
parent_bf,
|
2014-02-15 01:47:01 +00:00
|
|
|
|
applicable_declarations,
|
|
|
|
|
&mut shareable);
|
|
|
|
|
|
|
|
|
|
shareable
|
2013-10-02 19:21:53 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-14 04:13:34 +00:00
|
|
|
|
struct PerOriginSelectorMap {
|
2015-02-23 15:39:47 +00:00
|
|
|
|
normal: SelectorMap<Vec<PropertyDeclaration>>,
|
|
|
|
|
important: SelectorMap<Vec<PropertyDeclaration>>,
|
2013-11-14 04:13:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PerOriginSelectorMap {
|
|
|
|
|
#[inline]
|
|
|
|
|
fn new() -> PerOriginSelectorMap {
|
2013-12-15 19:49:24 +00:00
|
|
|
|
PerOriginSelectorMap {
|
|
|
|
|
normal: SelectorMap::new(),
|
|
|
|
|
important: SelectorMap::new(),
|
|
|
|
|
}
|
2013-11-14 04:13:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-01-08 17:58:29 +00:00
|
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-12 19:03:49 +00:00
|
|
|
|
|