/* 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/. */

//! The struct that takes care of encapsulating all the logic on where and how
//! element styles need to be invalidated.

use Atom;
use context::SharedStyleContext;
use data::ElementData;
use dom::{TElement, TNode};
use element_state::{ElementState, IN_VISITED_OR_UNVISITED_STATE};
use invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
use invalidation::element::invalidation_map::*;
use invalidation::element::restyle_hints::*;
use selector_map::SelectorMap;
use selector_parser::{SelectorImpl, Snapshot};
use selectors::attr::CaseSensitivity;
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
use selectors::matching::{matches_selector, matches_compound_selector};
use selectors::matching::CompoundSelectorMatchingResult;
use selectors::parser::{Combinator, Component, Selector};
use smallvec::SmallVec;
use std::fmt;

/// The struct that takes care of encapsulating all the logic on where and how
/// element styles need to be invalidated.
pub struct TreeStyleInvalidator<'a, 'b: 'a, E>
    where E: TElement,
{
    element: E,
    data: Option<&'a mut ElementData>,
    shared_context: &'a SharedStyleContext<'b>,
}

type InvalidationVector = SmallVec<[Invalidation; 10]>;

/// The kind of invalidation we're processing.
///
/// We can use this to avoid pushing invalidations of the same kind to our
/// descendants or siblings.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum InvalidationKind {
    Descendant,
    Sibling,
}

/// An `Invalidation` is a complex selector that describes which elements,
/// relative to a current element we are processing, must be restyled.
///
/// When `offset` points to the right-most compound selector in `selector`,
/// then the Invalidation `represents` the fact that the current element
/// must be restyled if the compound selector matches.  Otherwise, if
/// describes which descendants (or later siblings) must be restyled.
#[derive(Clone)]
struct Invalidation {
    selector: Selector<SelectorImpl>,
    offset: usize,
    /// Whether the invalidation was already matched by any previous sibling or
    /// ancestor.
    ///
    /// If this is the case, we can avoid pushing invalidations generated by
    /// this one if the generated invalidation is effective for all the siblings
    /// or descendants after us.
    matched_by_any_previous: bool,
}

impl Invalidation {
    /// Whether this invalidation is effective for the next sibling or
    /// descendant after us.
    fn effective_for_next(&self) -> bool {
        // TODO(emilio): For pseudo-elements this should be mostly false, except
        // for the weird pseudos in <input type="number">.
        //
        // We should be able to do better here!
        match self.selector.combinator_at(self.offset) {
            Combinator::NextSibling |
            Combinator::Child => false,
            _ => true,
        }
    }

    fn kind(&self) -> InvalidationKind {
        if self.selector.combinator_at(self.offset).is_ancestor() {
            InvalidationKind::Descendant
        } else {
            InvalidationKind::Sibling
        }
    }
}

impl fmt::Debug for Invalidation {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use cssparser::ToCss;

        f.write_str("Invalidation(")?;
        for component in self.selector.iter_raw_parse_order_from(self.offset - 1) {
            if matches!(*component, Component::Combinator(..)) {
                break;
            }
            component.to_css(f)?;
        }
        f.write_str(")")
    }
}

/// The result of processing a single invalidation for a given element.
struct InvalidationResult {
    /// Whether the element itself was invalidated.
    invalidated_self: bool,
    /// Whether the invalidation matched, either invalidating the element or
    /// generating another invalidation.
    matched: bool,
}

impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
    where E: TElement,
{
    /// Trivially constructs a new `TreeStyleInvalidator`.
    pub fn new(
        element: E,
        data: Option<&'a mut ElementData>,
        shared_context: &'a SharedStyleContext<'b>,
    ) -> Self {
        Self {
            element: element,
            data: data,
            shared_context: shared_context,
        }
    }

    /// Perform the invalidation pass.
    pub fn invalidate(mut self) {
        debug!("StyleTreeInvalidator::invalidate({:?})", self.element);
        debug_assert!(self.element.has_snapshot(), "Why bothering?");
        debug_assert!(self.data.is_some(), "How exactly?");

        let shared_context = self.shared_context;

        let wrapper =
            ElementWrapper::new(self.element, shared_context.snapshot_map);
        let state_changes = wrapper.state_changes();
        let snapshot = wrapper.snapshot().expect("has_snapshot lied");

        if !snapshot.has_attrs() && state_changes.is_empty() {
            return;
        }

        // If we are sensitive to visitedness and the visited state changed, we
        // force a restyle here. Matching doesn't depend on the actual visited
        // state at all, so we can't look at matching results to decide what to
        // do for this case.
        if state_changes.intersects(IN_VISITED_OR_UNVISITED_STATE) {
            trace!(" > visitedness change, force subtree restyle");
            // We can't just return here because there may also be attribute
            // changes as well that imply additional hints.
            let mut data = self.data.as_mut().unwrap();
            data.restyle.hint.insert(RestyleHint::restyle_subtree());
        }

        let mut classes_removed = SmallVec::<[Atom; 8]>::new();
        let mut classes_added = SmallVec::<[Atom; 8]>::new();
        if snapshot.class_changed() {
            // TODO(emilio): Do this more efficiently!
            snapshot.each_class(|c| {
                if !self.element.has_class(c, CaseSensitivity::CaseSensitive) {
                    classes_removed.push(c.clone())
                }
            });

            self.element.each_class(|c| {
                if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) {
                    classes_added.push(c.clone())
                }
            })
        }

        let mut id_removed = None;
        let mut id_added = None;
        if snapshot.id_changed() {
            let old_id = snapshot.id_attr();
            let current_id = self.element.get_id();

            if old_id != current_id {
                id_removed = old_id;
                id_added = current_id;
            }
        }

        let lookup_element =
            if self.element.implemented_pseudo_element().is_some() {
                self.element.pseudo_element_originating_element().unwrap()
            } else {
                self.element
            };

        let mut descendant_invalidations = InvalidationVector::new();
        let mut sibling_invalidations = InvalidationVector::new();
        let invalidated_self = {
            let mut collector = InvalidationCollector {
                wrapper: wrapper,
                element: self.element,
                snapshot: &snapshot,
                shared_context: self.shared_context,
                lookup_element: lookup_element,
                removed_id: id_removed.as_ref(),
                added_id: id_added.as_ref(),
                classes_removed: &classes_removed,
                classes_added: &classes_added,
                state_changes: state_changes,
                descendant_invalidations: &mut descendant_invalidations,
                sibling_invalidations: &mut sibling_invalidations,
                invalidates_self: false,
            };

            collector.collect_dependencies_in_invalidation_map(
                shared_context.stylist.invalidation_map(),
            );

            // TODO(emilio): Consider storing dependencies from the UA sheet in
            // a different map. If we do that, we can skip the stuff on the
            // shared stylist iff cut_off_inheritance is true, and we can look
            // just at that map.
            let _cut_off_inheritance =
                self.element.each_xbl_stylist(|stylist| {
                    collector.collect_dependencies_in_invalidation_map(
                        stylist.invalidation_map(),
                    );
                });

            collector.invalidates_self
        };

        if invalidated_self {
            if let Some(ref mut data) = self.data {
                data.restyle.hint.insert(RESTYLE_SELF);
            }
        }

        debug!("Collected invalidations (self: {}): ", invalidated_self);
        debug!(" > descendants: {:?}", descendant_invalidations);
        debug!(" > siblings: {:?}", sibling_invalidations);
        self.invalidate_descendants(&descendant_invalidations);
        self.invalidate_siblings(&mut sibling_invalidations);
    }

    /// Go through later DOM siblings, invalidating style as needed using the
    /// `sibling_invalidations` list.
    ///
    /// Returns whether any sibling's style or any sibling descendant's style
    /// was invalidated.
    fn invalidate_siblings(
        &mut self,
        sibling_invalidations: &mut InvalidationVector,
    ) -> bool {
        if sibling_invalidations.is_empty() {
            return false;
        }

        let mut current = self.element.next_sibling_element();
        let mut any_invalidated = false;

        while let Some(sibling) = current {
            let mut sibling_data = sibling.mutate_data();
            let sibling_data = sibling_data.as_mut().map(|d| &mut **d);

            let mut sibling_invalidator = TreeStyleInvalidator::new(
                sibling,
                sibling_data,
                self.shared_context
            );

            let mut invalidations_for_descendants = InvalidationVector::new();
            any_invalidated |=
                sibling_invalidator.process_sibling_invalidations(
                    &mut invalidations_for_descendants,
                    sibling_invalidations,
                );

            any_invalidated |=
                sibling_invalidator.invalidate_descendants(
                    &invalidations_for_descendants
                );

            if sibling_invalidations.is_empty() {
                break;
            }

            current = sibling.next_sibling_element();
        }

        any_invalidated
    }

    fn invalidate_pseudo_element_or_nac(
        &mut self,
        child: E,
        invalidations: &InvalidationVector
    ) -> bool {
        let mut sibling_invalidations = InvalidationVector::new();

        let result = self.invalidate_child(
            child,
            invalidations,
            &mut sibling_invalidations
        );

        // Roots of NAC subtrees can indeed generate sibling invalidations, but
        // they can be just ignored, since they have no siblings.
        //
        // Note that we can end up testing selectors that wouldn't end up
        // matching due to this being NAC, like those coming from document
        // rules, but we overinvalidate instead of checking this.

        result
    }

    /// Invalidate a child and recurse down invalidating its descendants if
    /// needed.
    fn invalidate_child(
        &mut self,
        child: E,
        invalidations: &InvalidationVector,
        sibling_invalidations: &mut InvalidationVector,
    ) -> bool {
        let mut child_data = child.mutate_data();
        let child_data = child_data.as_mut().map(|d| &mut **d);

        let mut child_invalidator = TreeStyleInvalidator::new(
            child,
            child_data,
            self.shared_context
        );

        let mut invalidations_for_descendants = InvalidationVector::new();
        let mut invalidated_child = false;

        invalidated_child |=
            child_invalidator.process_sibling_invalidations(
                &mut invalidations_for_descendants,
                sibling_invalidations,
            );

        invalidated_child |=
            child_invalidator.process_descendant_invalidations(
                invalidations,
                &mut invalidations_for_descendants,
                sibling_invalidations,
            );

        // The child may not be a flattened tree child of the current element,
        // but may be arbitrarily deep.
        //
        // Since we keep the traversal flags in terms of the flattened tree,
        // we need to propagate it as appropriate.
        if invalidated_child {
            let mut current = child.traversal_parent();
            while let Some(parent) = current.take() {
                if parent == self.element {
                    break;
                }

                unsafe { parent.set_dirty_descendants() };
                current = parent.traversal_parent();
            }
        }

        let invalidated_descendants = child_invalidator.invalidate_descendants(
            &invalidations_for_descendants
        );

        invalidated_child || invalidated_descendants
    }

    fn invalidate_nac(
        &mut self,
        invalidations: &InvalidationVector,
    ) -> bool {
        let mut any_nac_root = false;

        let element = self.element;
        element.each_anonymous_content_child(|nac| {
            any_nac_root |=
                self.invalidate_pseudo_element_or_nac(nac, invalidations);
        });

        any_nac_root
    }

    // NB: It's important that this operates on DOM children, which is what
    // selector-matching operates on.
    fn invalidate_dom_descendants_of(
        &mut self,
        parent: E::ConcreteNode,
        invalidations: &InvalidationVector,
    ) -> bool {
        let mut any_descendant = false;

        let mut sibling_invalidations = InvalidationVector::new();
        for child in parent.children() {
            // TODO(emilio): We handle <xbl:children> fine, because they appear
            // in selector-matching (note bug 1374247, though).
            //
            // This probably needs a shadow root check on `child` here, and
            // recursing if that's the case.
            //
            // Also, what's the deal with HTML <content>? We don't need to
            // support that for now, though we probably need to recurse into the
            // distributed children too.
            let child = match child.as_element() {
                Some(e) => e,
                None => continue,
            };

            any_descendant |= self.invalidate_child(
                child,
                invalidations,
                &mut sibling_invalidations,
            );
        }

        any_descendant
    }

    /// Given a descendant invalidation list, go through the current element's
    /// descendants, and invalidate style on them.
    fn invalidate_descendants(
        &mut self,
        invalidations: &InvalidationVector,
    ) -> bool {
        if invalidations.is_empty() {
            return false;
        }

        debug!("StyleTreeInvalidator::invalidate_descendants({:?})",
               self.element);
        debug!(" > {:?}", invalidations);

        match self.data {
            None => return false,
            Some(ref data) => {
                if data.restyle.hint.contains_subtree() {
                    return false;
                }
            }
        }

        let mut any_descendant = false;

        if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
            any_descendant |=
                self.invalidate_dom_descendants_of(anon_content, invalidations);
        }

        // TODO(emilio): Having a list of invalidations just for pseudo-elements
        // may save some work here and there.
        if let Some(before) = self.element.before_pseudo_element() {
            any_descendant |=
                self.invalidate_pseudo_element_or_nac(before, invalidations);
        }

        let node = self.element.as_node();
        any_descendant |=
            self.invalidate_dom_descendants_of(node, invalidations);

        if let Some(after) = self.element.after_pseudo_element() {
            any_descendant |=
                self.invalidate_pseudo_element_or_nac(after, invalidations);
        }

        any_descendant |= self.invalidate_nac(invalidations);

        if any_descendant {
            unsafe { self.element.set_dirty_descendants() };
        }

        any_descendant
    }

    /// Process the given sibling invalidations coming from our previous
    /// sibling.
    ///
    /// The sibling invalidations are somewhat special because they can be
    /// modified on the fly. New invalidations may be added and removed.
    ///
    /// In particular, all descendants get the same set of invalidations from
    /// the parent, but the invalidations from a given sibling depend on the
    /// ones we got from the previous one.
    ///
    /// Returns whether invalidated the current element's style.
    fn process_sibling_invalidations(
        &mut self,
        descendant_invalidations: &mut InvalidationVector,
        sibling_invalidations: &mut InvalidationVector,
    ) -> bool {
        let mut i = 0;
        let mut new_sibling_invalidations = InvalidationVector::new();
        let mut invalidated_self = false;

        while i < sibling_invalidations.len() {
            let result = self.process_invalidation(
                &sibling_invalidations[i],
                descendant_invalidations,
                &mut new_sibling_invalidations,
                InvalidationKind::Sibling,
            );

            invalidated_self |= result.invalidated_self;
            sibling_invalidations[i].matched_by_any_previous |= result.matched;
            if sibling_invalidations[i].effective_for_next() {
                i += 1;
            } else {
                sibling_invalidations.remove(i);
            }
        }

        sibling_invalidations.extend(new_sibling_invalidations.drain());
        invalidated_self
    }

    /// Process a given invalidation list coming from our parent,
    /// adding to `descendant_invalidations` and `sibling_invalidations` as
    /// needed.
    ///
    /// Returns whether our style was invalidated as a result.
    fn process_descendant_invalidations(
        &mut self,
        invalidations: &InvalidationVector,
        descendant_invalidations: &mut InvalidationVector,
        sibling_invalidations: &mut InvalidationVector,
    ) -> bool {
        let mut invalidated = false;

        for invalidation in invalidations {
            let result = self.process_invalidation(
                invalidation,
                descendant_invalidations,
                sibling_invalidations,
                InvalidationKind::Descendant,
            );

            invalidated |= result.invalidated_self;
            if invalidation.effective_for_next() {
                let mut invalidation = invalidation.clone();
                invalidation.matched_by_any_previous |= result.matched;
                descendant_invalidations.push(invalidation.clone());
            }
        }

        invalidated
    }

    /// Processes a given invalidation, potentially invalidating the style of
    /// the current element.
    ///
    /// Returns whether invalidated the style of the element, and whether the
    /// invalidation should be effective to subsequent siblings or descendants
    /// down in the tree.
    fn process_invalidation(
        &mut self,
        invalidation: &Invalidation,
        descendant_invalidations: &mut InvalidationVector,
        sibling_invalidations: &mut InvalidationVector,
        invalidation_kind: InvalidationKind,
    ) -> InvalidationResult {
        debug!("TreeStyleInvalidator::process_invalidation({:?}, {:?}, {:?})",
               self.element, invalidation, invalidation_kind);

        let mut context =
            MatchingContext::new_for_visited(
                MatchingMode::Normal,
                None,
                VisitedHandlingMode::AllLinksVisitedAndUnvisited,
                self.shared_context.quirks_mode,
            );

        let matching_result = matches_compound_selector(
            &invalidation.selector,
            invalidation.offset,
            &mut context,
            &self.element
        );

        let mut invalidated_self = false;
        let mut matched = false;
        match matching_result {
            CompoundSelectorMatchingResult::Matched { next_combinator_offset: 0 } => {
                debug!(" > Invalidation matched completely");
                matched = true;
                invalidated_self = true;
            }
            CompoundSelectorMatchingResult::Matched { next_combinator_offset } => {
                let next_combinator =
                    invalidation.selector.combinator_at(next_combinator_offset);
                matched = true;

                if matches!(next_combinator, Combinator::PseudoElement) {
                    // This will usually be the very next component, except for
                    // the fact that we store compound selectors the other way
                    // around, so there could also be state pseudo-classes.
                    let pseudo_selector =
                        invalidation.selector
                            .iter_raw_parse_order_from(next_combinator_offset - 1)
                            .skip_while(|c| matches!(**c, Component::NonTSPseudoClass(..)))
                            .next()
                            .unwrap();

                    let pseudo = match *pseudo_selector {
                        Component::PseudoElement(ref pseudo) => pseudo,
                        _ => {
                            unreachable!(
                                "Someone seriously messed up selector parsing: \
                                {:?} at offset {:?}: {:?}",
                                invalidation.selector,
                                next_combinator_offset,
                                pseudo_selector,
                            )
                        }
                    };

                    // FIXME(emilio): This is not ideal, and could not be
                    // accurate if we ever have stateful element-backed eager
                    // pseudos.
                    //
                    // Ideally, we'd just remove element-backed eager pseudos
                    // altogether, given they work fine without it. Only gotcha
                    // is that we wouldn't style them in parallel, which may or
                    // may not be an issue.
                    //
                    // Also, this could be more fine grained now (perhaps a
                    // RESTYLE_PSEUDOS hint?).
                    //
                    // Note that we'll also restyle the pseudo-element because
                    // it would match this invalidation.
                    if pseudo.is_eager() {
                        invalidated_self = true;
                    }
                }


                let next_invalidation = Invalidation {
                    selector: invalidation.selector.clone(),
                    offset: next_combinator_offset,
                    matched_by_any_previous: false,
                };

                debug!(" > Invalidation matched, next: {:?}, ({:?})",
                        next_invalidation, next_combinator);

                let next_invalidation_kind = next_invalidation.kind();

                // We can skip pushing under some circumstances, and we should
                // because otherwise the invalidation list could grow
                // exponentially.
                //
                //  * First of all, both invalidations need to be of the same
                //    kind. This is because of how we propagate them going to
                //    the right of the tree for sibling invalidations and going
                //    down the tree for children invalidations. A sibling
                //    invalidation that ends up generating a children
                //    invalidation ends up (correctly) in five different lists,
                //    not in the same list five different times.
                //
                //  * Then, the invalidation needs to be matched by a previous
                //    ancestor/sibling, in order to know that this invalidation
                //    has been generated already.
                //
                //  * Finally, the new invalidation needs to be
                //    `effective_for_next()`, in order for us to know that it is
                //    still in the list, since we remove the dependencies that
                //    aren't from the lists for our children / siblings.
                //
                // To go through an example, let's imagine we are processing a
                // dom subtree like:
                //
                //   <div><address><div><div/></div></address></div>
                //
                // And an invalidation list with a single invalidation like:
                //
                //   [div div div]
                //
                // When we process the invalidation list for the outer div, we
                // match it, and generate a `div div` invalidation, so for the
                // <address> child we have:
                //
                //   [div div div, div div]
                //
                // With the first of them marked as `matched`.
                //
                // When we process the <address> child, we don't match any of
                // them, so both invalidations go untouched to our children.
                //
                // When we process the second <div>, we match _both_
                // invalidations.
                //
                // However, when matching the first, we can tell it's been
                // matched, and not push the corresponding `div div`
                // invalidation, since we know it's necessarily already on the
                // list.
                //
                // Thus, without skipping the push, we'll arrive to the
                // innermost <div> with:
                //
                //   [div div div, div div, div div, div]
                //
                // While skipping it, we won't arrive here with duplicating
                // dependencies:
                //
                //   [div div div, div div, div]
                //
                let can_skip_pushing =
                    next_invalidation_kind == invalidation_kind &&
                    invalidation.matched_by_any_previous &&
                    next_invalidation.effective_for_next();

                if can_skip_pushing {
                    debug!(" > Can avoid push, since the invalidation had \
                           already been matched before");
                } else {
                    match next_invalidation_kind {
                        InvalidationKind::Descendant => {
                            descendant_invalidations.push(next_invalidation);
                        }
                        InvalidationKind::Sibling => {
                            sibling_invalidations.push(next_invalidation);
                        }
                    }
                }
            }
            CompoundSelectorMatchingResult::NotMatched => {}
        }

        if invalidated_self {
            if let Some(ref mut data) = self.data {
                data.restyle.hint.insert(RESTYLE_SELF);
            }
        }

        InvalidationResult { invalidated_self, matched, }
    }
}

struct InvalidationCollector<'a, 'b: 'a, E>
    where E: TElement,
{
    element: E,
    wrapper: ElementWrapper<'b, E>,
    snapshot: &'a Snapshot,
    shared_context: &'a SharedStyleContext<'b>,
    lookup_element: E,
    removed_id: Option<&'a Atom>,
    added_id: Option<&'a Atom>,
    classes_removed: &'a SmallVec<[Atom; 8]>,
    classes_added: &'a SmallVec<[Atom; 8]>,
    state_changes: ElementState,
    descendant_invalidations: &'a mut InvalidationVector,
    sibling_invalidations: &'a mut InvalidationVector,
    invalidates_self: bool,
}

impl<'a, 'b: 'a, E> InvalidationCollector<'a, 'b, E>
    where E: TElement,
{
    fn collect_dependencies_in_invalidation_map(
        &mut self,
        map: &InvalidationMap,
    ) {
        let quirks_mode = self.shared_context.quirks_mode;
        let removed_id = self.removed_id;
        if let Some(ref id) = removed_id {
            if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
                self.collect_dependencies_in_map(deps)
            }
        }

        let added_id = self.added_id;
        if let Some(ref id) = added_id {
            if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
                self.collect_dependencies_in_map(deps)
            }
        }

        for class in self.classes_added.iter().chain(self.classes_removed.iter()) {
            if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
                self.collect_dependencies_in_map(deps)
            }
        }

        let should_examine_attribute_selector_map =
            self.snapshot.other_attr_changed() ||
            (self.snapshot.class_changed() && map.has_class_attribute_selectors) ||
            (self.snapshot.id_changed() && map.has_id_attribute_selectors);

        if should_examine_attribute_selector_map {
            self.collect_dependencies_in_map(
                &map.other_attribute_affecting_selectors
            )
        }

        let state_changes = self.state_changes;
        if !state_changes.is_empty() {
            self.collect_state_dependencies(
                &map.state_affecting_selectors,
                state_changes,
            )
        }
    }

    fn collect_dependencies_in_map(
        &mut self,
        map: &SelectorMap<Dependency>,
    ) {
        map.lookup_with_additional(
            self.lookup_element,
            self.shared_context.quirks_mode,
            self.removed_id,
            self.classes_removed,
            &mut |dependency| {
                self.scan_dependency(
                    dependency,
                    /* is_visited_dependent = */ false
                );
                true
            },
        );
    }

    fn collect_state_dependencies(
        &mut self,
        map: &SelectorMap<StateDependency>,
        state_changes: ElementState,
    ) {
        map.lookup_with_additional(
            self.lookup_element,
            self.shared_context.quirks_mode,
            self.removed_id,
            self.classes_removed,
            &mut |dependency| {
                if !dependency.state.intersects(state_changes) {
                    return true;
                }
                self.scan_dependency(
                    &dependency.dep,
                    dependency.state.intersects(IN_VISITED_OR_UNVISITED_STATE)
                );
                true
            },
        );
    }

    fn scan_dependency(
        &mut self,
        dependency: &Dependency,
        is_visited_dependent: bool
    ) {
        debug!("TreeStyleInvalidator::scan_dependency({:?}, {:?}, {})",
               self.element,
               dependency,
               is_visited_dependent);

        if !self.dependency_may_be_relevant(dependency) {
            return;
        }

        // TODO(emilio): Add a bloom filter here?
        //
        // If we decide to do so, we can't use the bloom filter for snapshots,
        // given that arbitrary elements in the parent chain may have mutated
        // their id's/classes, which means that they won't be in the filter, and
        // as such we may fast-reject selectors incorrectly.
        //
        // We may be able to improve this if we record as we go down the tree
        // whether any parent had a snapshot, and whether those snapshots were
        // taken due to an element class/id change, but it's not clear it'd be
        // worth it.
        let mut now_context =
            MatchingContext::new_for_visited(MatchingMode::Normal, None,
                                             VisitedHandlingMode::AllLinksUnvisited,
                                             self.shared_context.quirks_mode);
        let mut then_context =
            MatchingContext::new_for_visited(MatchingMode::Normal, None,
                                             VisitedHandlingMode::AllLinksUnvisited,
                                             self.shared_context.quirks_mode);

        let matched_then =
            matches_selector(&dependency.selector,
                             dependency.selector_offset,
                             None,
                             &self.wrapper,
                             &mut then_context,
                             &mut |_, _| {});
        let matches_now =
            matches_selector(&dependency.selector,
                             dependency.selector_offset,
                             None,
                             &self.element,
                             &mut now_context,
                             &mut |_, _| {});

        // Check for mismatches in both the match result and also the status
        // of whether a relevant link was found.
        if matched_then != matches_now ||
           then_context.relevant_link_found != now_context.relevant_link_found {
            return self.note_dependency(dependency);
        }

        // If there is a relevant link, then we also matched in visited
        // mode.
        //
        // Match again in this mode to ensure this also matches.
        //
        // Note that we never actually match directly against the element's true
        // visited state at all, since that would expose us to timing attacks.
        //
        // The matching process only considers the relevant link state and
        // visited handling mode when deciding if visited matches.  Instead, we
        // are rematching here in case there is some :visited selector whose
        // matching result changed for some other state or attribute change of
        // this element (for example, for things like [foo]:visited).
        //
        // NOTE: This thing is actually untested because testing it is flaky,
        // see the tests that were added and then backed out in bug 1328509.
        if is_visited_dependent && now_context.relevant_link_found {
            then_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited;
            let matched_then =
                matches_selector(&dependency.selector,
                                 dependency.selector_offset,
                                 None,
                                 &self.wrapper,
                                 &mut then_context,
                                 &mut |_, _| {});

            now_context.visited_handling = VisitedHandlingMode::RelevantLinkVisited;
            let matches_now =
                matches_selector(&dependency.selector,
                                 dependency.selector_offset,
                                 None,
                                 &self.element,
                                 &mut now_context,
                                 &mut |_, _| {});
            if matched_then != matches_now {
                return self.note_dependency(dependency);
            }
        }
    }

    fn note_dependency(&mut self, dependency: &Dependency) {
        if dependency.affects_self() {
            self.invalidates_self = true;
        }

        if dependency.affects_descendants() {
            debug_assert_ne!(dependency.selector_offset, 0);
            debug_assert!(!dependency.affects_later_siblings());
            self.descendant_invalidations.push(Invalidation {
                selector: dependency.selector.clone(),
                offset: dependency.selector_offset,
                matched_by_any_previous: false,
            });
        } else if dependency.affects_later_siblings() {
            debug_assert_ne!(dependency.selector_offset, 0);
            self.sibling_invalidations.push(Invalidation {
                selector: dependency.selector.clone(),
                offset: dependency.selector_offset,
                matched_by_any_previous: false,
            });
        }
    }

    /// Returns whether `dependency` may cause us to invalidate the style of
    /// more elements than what we've already invalidated.
    fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool {
        if dependency.affects_descendants() || dependency.affects_later_siblings() {
            return true;
        }

        debug_assert!(dependency.affects_self());
        !self.invalidates_self
    }
}
