<commit_before>/* 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 std::ascii::AsciiExt;
use std::collections::hashmap::HashMap;
use std::hash::Hash;
use std::num::div_rem;
use sync::Arc;

use url::Url;

use servo_util::bloom::BloomFilter;
use servo_util::geometry::Au;
use servo_util::resource_files::read_resource_file;
use servo_util::smallvec::VecLike;
use servo_util::sort;
use servo_util::str::{AutoLpa, LengthLpa, PercentageLpa};
use string_cache::Atom;

use legacy::{SizeIntegerAttribute, WidthLengthAttribute};
use media_queries::Device;
use node::{TElement, TElementAttributes, TNode};
use properties::{PropertyDeclaration, PropertyDeclarationBlock, SpecifiedValue, WidthDeclaration};
use properties::{specified};
use selectors::*;
use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules};

pub enum StylesheetOrigin {
    UserAgentOrigin,
    AuthorOrigin,
    UserOrigin,
}

/// The definition of whitespace per CSS Selectors Level 3 § 4.
pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];

/// Map node attributes 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 a node), we need to compare the last simple selector in the
/// Rule with the node.
///
/// So, if a node 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 node's classes, ID,
/// element name, etc. will contain the Rules that actually match that
/// node.
struct SelectorMap {
    // TODO: Tune the initial capacity of the HashMap
    id_hash: HashMap<Atom, Vec<Rule>>,
    class_hash: HashMap<Atom, Vec<Rule>>,
    local_name_hash: HashMap<Atom, Vec<Rule>>,
    /// Same as local_name_hash, but keys are lower-cased.
    /// For HTML elements in HTML documents.
    lower_local_name_hash: HashMap<Atom, Vec<Rule>>,
    // For Rules that don't have ID, class, or element selectors.
    universal_rules: Vec<Rule>,
    /// Whether this hash is empty.
    empty: bool,
}

impl SelectorMap {
    fn new() -> SelectorMap {
        SelectorMap {
            id_hash: HashMap::new(),
            class_hash: HashMap::new(),
            local_name_hash: HashMap::new(),
            lower_local_name_hash: HashMap::new(),
            universal_rules: vec!(),
            empty: true,
        }
    }

    /// Append to `rule_list` all Rules in `self` that match node.
    ///
    /// Extract matching rules as per node's ID, classes, tag name, etc..
    /// Sort the Rules at the end to maintain cascading order.
    fn get_all_matching_rules<'a,E,N,V>(&self,
                                        node: &N,
                                        parent_bf: &Option<Box<BloomFilter>>,
                                        matching_rules_list: &mut V,
                                        shareable: &mut bool)
                                        where E: TElement<'a> + TElementAttributes,
                                              N: TNode<'a,E>,
                                              V: VecLike<DeclarationBlock> {
        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.vec_len();
        let element = node.as_element();
        match element.get_id() {
            Some(id) => {
                SelectorMap::get_matching_rules_from_hash(node,
                                                          parent_bf,
                                                          &self.id_hash,
                                                          &id,
                                                          matching_rules_list,
                                                          shareable)
            }
            None => {}
        }

        element.each_class(|class| {
            SelectorMap::get_matching_rules_from_hash(node,
                                                      parent_bf,
                                                      &self.class_hash,
                                                      class,
                                                      matching_rules_list,
                                                      shareable);
        });

        let local_name_hash = if node.is_html_element_in_html_document() {
            &self.lower_local_name_hash
        } else {
            &self.local_name_hash
        };
        SelectorMap::get_matching_rules_from_hash(node,
                                                  parent_bf,
                                                  local_name_hash,
                                                  element.get_local_name(),
                                                  matching_rules_list,
                                                  shareable);

        SelectorMap::get_matching_rules(node,
                                        parent_bf,
                                        self.universal_rules.as_slice(),
                                        matching_rules_list,
                                        shareable);

        // Sort only the rules we just added.
        sort::quicksort_by(matching_rules_list.vec_slice_from_mut(init_len), compare);

        fn compare(a: &DeclarationBlock, b: &DeclarationBlock) -> Ordering {
            (a.specificity, a.source_order).cmp(&(b.specificity, b.source_order))
        }
    }

    fn get_matching_rules_from_hash<'a,E,N,V>(node: &N,
                                              parent_bf: &Option<Box<BloomFilter>>,
                                              hash: &HashMap<Atom, Vec<Rule>>,
                                              key: &Atom,
                                              matching_rules: &mut V,
                                              shareable: &mut bool)
                                              where E: TElement<'a> + TElementAttributes,
                                                    N: TNode<'a,E>,
                                                    V: VecLike<DeclarationBlock> {
        match hash.find(key) {
            Some(rules) => {
                SelectorMap::get_matching_rules(node,
                                                parent_bf,
                                                rules.as_slice(),
                                                matching_rules,
                                                shareable)
            }
            None => {}
        }
    }

    /// Adds rules in `rules` that match `node` to the `matching_rules` list.
    fn get_matching_rules<'a,E,N,V>(node: &N,
                                    parent_bf: &Option<Box<BloomFilter>>,
                                    rules: &[Rule],
                                    matching_rules: &mut V,
                                    shareable: &mut bool)
                                    where E: TElement<'a> + TElementAttributes,
                                          N: TNode<'a,E>,
                                          V: VecLike<DeclarationBlock> {
        for rule in rules.iter() {
            if matches_compound_selector(&*rule.selector, node, parent_bf, shareable) {
                matching_rules.vec_push(rule.declarations.clone());
            }
        }
    }

    /// Insert rule into the correct hash.
    /// Order in which to try: id_hash, class_hash, local_name_hash, universal_rules.
    fn insert(&mut self, rule: Rule) {
        self.empty = false;

        match SelectorMap::get_id_name(&rule) {
            Some(id_name) => {
                self.id_hash.find_push(id_name, rule);
                return;
            }
            None => {}
        }
        match SelectorMap::get_class_name(&rule) {
            Some(class_name) => {
                self.class_hash.find_push(class_name, rule);
                return;
            }
            None => {}
        }

        match SelectorMap::get_local_name(&rule) {
            Some(LocalName { name, lower_name }) => {
                self.local_name_hash.find_push(name, rule.clone());
                self.lower_local_name_hash.find_push(lower_name, rule);
                return;
            }
            None => {}
        }

        self.universal_rules.push(rule);
    }

    /// Retrieve the first ID name in Rule, or None otherwise.
    fn get_id_name(rule: &Rule) -> Option<Atom> {
        let simple_selector_sequence = &rule.selector.simple_selectors;
        for ss in simple_selector_sequence.iter() {
            match *ss {
                // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
                // mode.
                IDSelector(ref id) => return Some(id.clone()),
                _ => {}
            }
        }
        return None
    }

    /// Retrieve the FIRST class name in Rule, or None otherwise.
    fn get_class_name(rule: &Rule) -> Option<Atom> {
        let simple_selector_sequence = &rule.selector.simple_selectors;
        for ss in simple_selector_sequence.iter() {
            match *ss {
                // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
                // mode.
                ClassSelector(ref class) => return Some(class.clone()),
                _ => {}
            }
        }
        return None
    }

    /// Retrieve the name if it is a type selector, or None otherwise.
    fn get_local_name(rule: &Rule) -> Option<LocalName> {
        let simple_selector_sequence = &rule.selector.simple_selectors;
        for ss in simple_selector_sequence.iter() {
            match *ss {
                LocalNameSelector(ref name) => {
                    return Some(name.clone())
                }
                _ => {}
            }
        }
        return None
    }
}

// The bloom filter for descendant CSS selectors will have a <1% false
// positive rate until it has this many selectors in it, then it will
// rapidly increase.
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: uint = 4096;

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 quirks-mode.css in quirks mode.
        // 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().as_slice(),
                Url::parse(format!("chrome:///{}", filename).as_slice()).unwrap(),
                None,
                None,
                UserAgentOrigin);
            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 {
                    UserAgentOrigin => (
                        &mut self.element_map.user_agent,
                        &mut self.before_map.user_agent,
                        &mut self.after_map.user_agent,
                    ),
                    AuthorOrigin => (
                        &mut self.element_map.author,
                        &mut self.before_map.author,
                        &mut self.after_map.author,
                    ),
                    UserOrigin => (
                        &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(Before) => &mut before_map,
                                    Some(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.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_stylesheet(&mut self, stylesheet: Stylesheet) {
        self.stylesheets.push(stylesheet);<commit_msg>Short circuit media queries test if stylist is already dirty.<commit_after>/* 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 std::ascii::AsciiExt;
use std::collections::hashmap::HashMap;
use std::hash::Hash;
use std::num::div_rem;
use sync::Arc;

use url::Url;

use servo_util::bloom::BloomFilter;
use servo_util::geometry::Au;
use servo_util::resource_files::read_resource_file;
use servo_util::smallvec::VecLike;
use servo_util::sort;
use servo_util::str::{AutoLpa, LengthLpa, PercentageLpa};
use string_cache::Atom;

use legacy::{SizeIntegerAttribute, WidthLengthAttribute};
use media_queries::Device;
use node::{TElement, TElementAttributes, TNode};
use properties::{PropertyDeclaration, PropertyDeclarationBlock, SpecifiedValue, WidthDeclaration};
use properties::{specified};
use selectors::*;
use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules};

pub enum StylesheetOrigin {
    UserAgentOrigin,
    AuthorOrigin,
    UserOrigin,
}

/// The definition of whitespace per CSS Selectors Level 3 § 4.
pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];

/// Map node attributes 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 a node), we need to compare the last simple selector in the
/// Rule with the node.
///
/// So, if a node 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 node's classes, ID,
/// element name, etc. will contain the Rules that actually match that
/// node.
struct SelectorMap {
    // TODO: Tune the initial capacity of the HashMap
    id_hash: HashMap<Atom, Vec<Rule>>,
    class_hash: HashMap<Atom, Vec<Rule>>,
    local_name_hash: HashMap<Atom, Vec<Rule>>,
    /// Same as local_name_hash, but keys are lower-cased.
    /// For HTML elements in HTML documents.
    lower_local_name_hash: HashMap<Atom, Vec<Rule>>,
    // For Rules that don't have ID, class, or element selectors.
    universal_rules: Vec<Rule>,
    /// Whether this hash is empty.
    empty: bool,
}

impl SelectorMap {
    fn new() -> SelectorMap {
        SelectorMap {
            id_hash: HashMap::new(),
            class_hash: HashMap::new(),
            local_name_hash: HashMap::new(),
            lower_local_name_hash: HashMap::new(),
            universal_rules: vec!(),
            empty: true,
        }
    }

    /// Append to `rule_list` all Rules in `self` that match node.
    ///
    /// Extract matching rules as per node's ID, classes, tag name, etc..
    /// Sort the Rules at the end to maintain cascading order.
    fn get_all_matching_rules<'a,E,N,V>(&self,
                                        node: &N,
                                        parent_bf: &Option<Box<BloomFilter>>,
                                        matching_rules_list: &mut V,
                                        shareable: &mut bool)
                                        where E: TElement<'a> + TElementAttributes,
                                              N: TNode<'a,E>,
                                              V: VecLike<DeclarationBlock> {
        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.vec_len();
        let element = node.as_element();
        match element.get_id() {
            Some(id) => {
                SelectorMap::get_matching_rules_from_hash(node,
                                                          parent_bf,
                                                          &self.id_hash,
                                                          &id,
                                                          matching_rules_list,
                                                          shareable)
            }
            None => {}
        }

        element.each_class(|class| {
            SelectorMap::get_matching_rules_from_hash(node,
                                                      parent_bf,
                                                      &self.class_hash,
                                                      class,
                                                      matching_rules_list,
                                                      shareable);
        });

        let local_name_hash = if node.is_html_element_in_html_document() {
            &self.lower_local_name_hash
        } else {
            &self.local_name_hash
        };
        SelectorMap::get_matching_rules_from_hash(node,
                                                  parent_bf,
                                                  local_name_hash,
                                                  element.get_local_name(),
                                                  matching_rules_list,
                                                  shareable);

        SelectorMap::get_matching_rules(node,
                                        parent_bf,
                                        self.universal_rules.as_slice(),
                                        matching_rules_list,
                                        shareable);

        // Sort only the rules we just added.
        sort::quicksort_by(matching_rules_list.vec_slice_from_mut(init_len), compare);

        fn compare(a: &DeclarationBlock, b: &DeclarationBlock) -> Ordering {
            (a.specificity, a.source_order).cmp(&(b.specificity, b.source_order))
        }
    }

    fn get_matching_rules_from_hash<'a,E,N,V>(node: &N,
                                              parent_bf: &Option<Box<BloomFilter>>,
                                              hash: &HashMap<Atom, Vec<Rule>>,
                                              key: &Atom,
                                              matching_rules: &mut V,
                                              shareable: &mut bool)
                                              where E: TElement<'a> + TElementAttributes,
                                                    N: TNode<'a,E>,
                                                    V: VecLike<DeclarationBlock> {
        match hash.find(key) {
            Some(rules) => {
                SelectorMap::get_matching_rules(node,
                                                parent_bf,
                                                rules.as_slice(),
                                                matching_rules,
                                                shareable)
            }
            None => {}
        }
    }

    /// Adds rules in `rules` that match `node` to the `matching_rules` list.
    fn get_matching_rules<'a,E,N,V>(node: &N,
                                    parent_bf: &Option<Box<BloomFilter>>,
                                    rules: &[Rule],
                                    matching_rules: &mut V,
                                    shareable: &mut bool)
                                    where E: TElement<'a> + TElementAttributes,
                                          N: TNode<'a,E>,
                                          V: VecLike<DeclarationBlock> {
        for rule in rules.iter() {
            if matches_compound_selector(&*rule.selector, node, parent_bf, shareable) {
                matching_rules.vec_push(rule.declarations.clone());
            }
        }
    }

    /// Insert rule into the correct hash.
    /// Order in which to try: id_hash, class_hash, local_name_hash, universal_rules.
    fn insert(&mut self, rule: Rule) {
        self.empty = false;

        match SelectorMap::get_id_name(&rule) {
            Some(id_name) => {
                self.id_hash.find_push(id_name, rule);
                return;
            }
            None => {}
        }
        match SelectorMap::get_class_name(&rule) {
            Some(class_name) => {
                self.class_hash.find_push(class_name, rule);
                return;
            }
            None => {}
        }

        match SelectorMap::get_local_name(&rule) {
            Some(LocalName { name, lower_name }) => {
                self.local_name_hash.find_push(name, rule.clone());
                self.lower_local_name_hash.find_push(lower_name, rule);
                return;
            }
            None => {}
        }

        self.universal_rules.push(rule);
    }

    /// Retrieve the first ID name in Rule, or None otherwise.
    fn get_id_name(rule: &Rule) -> Option<Atom> {
        let simple_selector_sequence = &rule.selector.simple_selectors;
        for ss in simple_selector_sequence.iter() {
            match *ss {
                // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
                // mode.
                IDSelector(ref id) => return Some(id.clone()),
                _ => {}
            }
        }
        return None
    }

    /// Retrieve the FIRST class name in Rule, or None otherwise.
    fn get_class_name(rule: &Rule) -> Option<Atom> {
        let simple_selector_sequence = &rule.selector.simple_selectors;
        for ss in simple_selector_sequence.iter() {
            match *ss {
                // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
                // mode.
                ClassSelector(ref class) => return Some(class.clone()),
                _ => {}
            }
        }
        return None
    }

    /// Retrieve the name if it is a type selector, or None otherwise.
    fn get_local_name(rule: &Rule) -> Option<LocalName> {
        let simple_selector_sequence = &rule.selector.simple_selectors;
        for ss in simple_selector_sequence.iter() {
            match *ss {
                LocalNameSelector(ref name) => {
                    return Some(name.clone())
                }
                _ => {}
            }
        }
        return None
    }
}

// The bloom filter for descendant CSS selectors will have a <1% false
// positive rate until it has this many selectors in it, then it will
// rapidly increase.
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: uint = 4096;

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 quirks-mode.css in quirks mode.
        // 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().as_slice(),
                Url::parse(format!("chrome:///{}", filename).as_slice()).unwrap(),
                None,
                None,
                UserAgentOrigin);
            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 {
                    UserAgentOrigin => (
                        &mut self.element_map.user_agent,
                        &mut self.before_map.user_agent,
                        &mut self.after_map.user_agent,
                    ),
                    AuthorOrigin => (
                        &mut self.element_map.author,
                        &mut self.before_map.author,
                        &mut self.after_map.author,
                    ),
                    UserOrigin => (
                        &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(Before) => &mut before_map,
                                    Some(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_stylesheet(&mut self, stylesheet: Stylesheet) {
        self.stylesheets.push(stylesheet);
{'fraction_non_alphanumeric': 0.09188935771214252, 'fraction_numerical': 0.001750273480231286, 'mean_word_length': 1.3478133255063105, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 133, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 182, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1065718', 'n_tokens_mistral': 7987, 'n_tokens_neox': 7660, 'n_words': 2001}