mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 16:55:40 +00:00
79a6daaf67
Source-Repo: https://github.com/servo/servo Source-Revision: 124a78fb2e4d324a7d0ed54da1cca17839d406b7
284 lines
11 KiB
Rust
284 lines
11 KiB
Rust
/* 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: uint,
|
||
}
|
||
|
||
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: 0u,
|
||
};
|
||
// 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(
|
||
&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(),
|
||
}
|
||
}
|
||
}
|
||
|