diff --git a/components/selectors/context.rs b/components/selectors/context.rs index ca7a2bfcb60..856e18497d9 100644 --- a/components/selectors/context.rs +++ b/components/selectors/context.rs @@ -130,6 +130,9 @@ where /// should match when matching_mode is ForStatelessPseudoElement. pub pseudo_element_matching_fn: Option<&'a Fn(&Impl::PseudoElement) -> bool>, + /// Extra implementation-dependent matching data. + pub extra_data: Impl::ExtraMatchingData, + quirks_mode: QuirksMode, classes_and_ids_case_sensitivity: CaseSensitivity, _impl: ::std::marker::PhantomData, @@ -173,6 +176,7 @@ where scope_element: None, nesting_level: 0, pseudo_element_matching_fn: None, + extra_data: Default::default(), _impl: ::std::marker::PhantomData, } } diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index a6902d105c7..99b73353abf 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -84,6 +84,7 @@ macro_rules! with_all_bounds { /// are parameterized on SelectorImpl. See /// pub trait SelectorImpl: Clone + Sized + 'static { + type ExtraMatchingData: Sized + Default + 'static; type AttrValue: $($InSelector)*; type Identifier: $($InSelector)* + PrecomputedHash; type ClassName: $($InSelector)* + PrecomputedHash; @@ -2027,6 +2028,7 @@ pub mod tests { } impl SelectorImpl for DummySelectorImpl { + type ExtraMatchingData = (); type AttrValue = DummyAtom; type Identifier = DummyAtom; type ClassName = DummyAtom; diff --git a/components/selectors/size_of_tests.rs b/components/selectors/size_of_tests.rs index 46b966ec5f3..8f0d6da94ef 100644 --- a/components/selectors/size_of_tests.rs +++ b/components/selectors/size_of_tests.rs @@ -24,6 +24,7 @@ impl parser::PseudoElement for gecko_like_types::PseudoElement { // Boilerplate impl SelectorImpl for Impl { + type ExtraMatchingData = u64; type AttrValue = Atom; type Identifier = Atom; type ClassName = Atom; diff --git a/components/style/data.rs b/components/style/data.rs index 56f2de5beb6..56efedc41f0 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -262,6 +262,8 @@ impl ElementData { } let mut xbl_stylists = SmallVec::<[_; 3]>::new(); + // FIXME(emilio): This is wrong, needs to account for ::slotted rules + // that may apply to elements down the tree. let cut_off_inheritance = element.each_applicable_non_document_style_rule_data(|data, quirks_mode| { xbl_stylists.push((data, quirks_mode)) diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index b70054fe84a..c87c2682aaa 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -9,6 +9,7 @@ use element_state::{DocumentState, ElementState}; use gecko_bindings::structs::{self, CSSPseudoClassType}; use gecko_bindings::structs::RawServoSelectorList; use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; +use invalidation::element::document_state::InvalidationMatchingData; use selector_parser::{Direction, SelectorParser}; use selectors::SelectorList; use selectors::parser::{self as selector_parser, Selector, SelectorMethods, SelectorParseErrorKind}; @@ -278,6 +279,7 @@ impl NonTSPseudoClass { pub struct SelectorImpl; impl ::selectors::SelectorImpl for SelectorImpl { + type ExtraMatchingData = InvalidationMatchingData; type AttrValue = Atom; type Identifier = Atom; type ClassName = Atom; diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 56ad17e6009..a2f75c4b4bb 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -2110,8 +2110,12 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.get_document_theme() == DocumentTheme::Doc_Theme_Dark } NonTSPseudoClass::MozWindowInactive => { - self.document_state() - .contains(DocumentState::NS_DOCUMENT_STATE_WINDOW_INACTIVE) + let state_bit = DocumentState::NS_DOCUMENT_STATE_WINDOW_INACTIVE; + if context.extra_data.document_state.intersects(state_bit) { + return true; + } + + self.document_state().contains(state_bit) } NonTSPseudoClass::MozPlaceholder => false, NonTSPseudoClass::MozAny(ref sels) => { @@ -2126,9 +2130,14 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.match_element_lang(None, lang_arg) } NonTSPseudoClass::MozLocaleDir(ref dir) => { - let doc_is_rtl = - self.document_state() - .contains(DocumentState::NS_DOCUMENT_STATE_RTL_LOCALE); + let state_bit = DocumentState::NS_DOCUMENT_STATE_RTL_LOCALE; + if context.extra_data.document_state.intersects(state_bit) { + // NOTE(emilio): We could still return false for + // Direction::Other(..), but we don't bother. + return true; + } + + let doc_is_rtl = self.document_state().contains(state_bit); match **dir { Direction::Ltr => !doc_is_rtl, diff --git a/components/style/invalidation/element/document_state.rs b/components/style/invalidation/element/document_state.rs new file mode 100644 index 00000000000..cf7a164ebdb --- /dev/null +++ b/components/style/invalidation/element/document_state.rs @@ -0,0 +1,109 @@ +/* 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/. */ + +//! An invalidation processor for style changes due to document state changes. + +use dom::TElement; +use element_state::DocumentState; +use invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector}; +use invalidation::element::invalidator::{Invalidation, InvalidationProcessor}; +use invalidation::element::state_and_attributes; +use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode}; +use stylist::StyleRuleCascadeData; + +/// A struct holding the members necessary to invalidate document state +/// selectors. +pub struct InvalidationMatchingData { + /// The document state that has changed, which makes it always match. + pub document_state: DocumentState, +} + +impl Default for InvalidationMatchingData { + #[inline(always)] + fn default() -> Self { + Self { + document_state: DocumentState::empty(), + } + } +} + +/// An invalidation processor for style changes due to state and attribute +/// changes. +pub struct DocumentStateInvalidationProcessor<'a, E: TElement> { + // TODO(emilio): We might want to just run everything for every possible + // binding along with the document data, or just apply the XBL stuff to the + // bound subtrees. + rules: &'a StyleRuleCascadeData, + matching_context: MatchingContext<'a, E::Impl>, + document_states_changed: DocumentState, +} + +impl<'a, E: TElement> DocumentStateInvalidationProcessor<'a, E> { + /// Creates a new DocumentStateInvalidationProcessor. + #[inline] + pub fn new( + rules: &'a StyleRuleCascadeData, + document_states_changed: DocumentState, + quirks_mode: QuirksMode, + ) -> Self { + let mut matching_context = MatchingContext::new_for_visited( + MatchingMode::Normal, + None, + None, + VisitedHandlingMode::AllLinksVisitedAndUnvisited, + quirks_mode, + ); + + matching_context.extra_data = InvalidationMatchingData { + document_state: document_states_changed, + }; + + Self { rules, document_states_changed, matching_context } + } +} + +impl<'a, E: TElement> InvalidationProcessor<'a, E> for DocumentStateInvalidationProcessor<'a, E> { + fn collect_invalidations( + &mut self, + _element: E, + self_invalidations: &mut InvalidationVector<'a>, + _descendant_invalidations: &mut DescendantInvalidationLists<'a>, + _sibling_invalidations: &mut InvalidationVector<'a>, + ) -> bool { + let map = self.rules.invalidation_map(); + + for dependency in &map.document_state_selectors { + if !dependency.state.intersects(self.document_states_changed) { + continue; + } + + self_invalidations.push(Invalidation::new(&dependency.selector, 0)); + } + + false + } + + fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { + &mut self.matching_context + } + + fn recursion_limit_exceeded(&mut self, _: E) { + unreachable!("We don't run document state invalidation with stack limits") + } + + fn should_process_descendants(&mut self, element: E) -> bool { + match element.borrow_data() { + Some(d) => state_and_attributes::should_process_descendants(&d), + None => false, + } + } + + fn invalidated_descendants(&mut self, element: E, child: E) { + state_and_attributes::invalidated_descendants(element, child) + } + + fn invalidated_self(&mut self, element: E) { + state_and_attributes::invalidated_self(element); + } +} diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index 809d3eb4a78..9cbfcb77e8b 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -6,7 +6,7 @@ use {Atom, LocalName, Namespace}; use context::QuirksMode; -use element_state::ElementState; +use element_state::{DocumentState, ElementState}; use fallible::FallibleVec; use hashglobe::FailedAllocationError; use selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry}; @@ -133,6 +133,19 @@ impl SelectorMapEntry for StateDependency { } } +/// The same, but for document state selectors. +#[derive(Clone, Debug, MallocSizeOf)] +pub struct DocumentStateDependency { + /// The selector that is affected. We don't need to track an offset, since + /// when it changes it changes for the whole document anyway. + #[cfg_attr(feature = "gecko", + ignore_malloc_size_of = "CssRules have primary refs, we measure there")] + #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] + pub selector: Selector, + /// The state this dependency is affected by. + pub state: DocumentState, +} + /// A map where we store invalidations. /// /// This is slightly different to a SelectorMap, in the sense of that the same @@ -151,6 +164,8 @@ pub struct InvalidationMap { pub id_to_selector: MaybeCaseInsensitiveHashMap>, /// A map of all the state dependencies. pub state_affecting_selectors: SelectorMap, + /// A list of document state dependencies in the rules we represent. + pub document_state_selectors: Vec, /// A map of other attribute affecting selectors. pub other_attribute_affecting_selectors: SelectorMap, /// Whether there are attribute rules of the form `[class~="foo"]` that may @@ -172,6 +187,7 @@ impl InvalidationMap { class_to_selector: MaybeCaseInsensitiveHashMap::new(), id_to_selector: MaybeCaseInsensitiveHashMap::new(), state_affecting_selectors: SelectorMap::new(), + document_state_selectors: Vec::new(), other_attribute_affecting_selectors: SelectorMap::new(), has_class_attribute_selectors: false, has_id_attribute_selectors: false, @@ -181,6 +197,7 @@ impl InvalidationMap { /// Returns the number of dependencies stored in the invalidation map. pub fn len(&self) -> usize { self.state_affecting_selectors.len() + + self.document_state_selectors.len() + self.other_attribute_affecting_selectors.len() + self.id_to_selector.iter().fold(0, |accum, (_, ref v)| { accum + v.len() @@ -195,7 +212,7 @@ impl InvalidationMap { pub fn note_selector( &mut self, selector: &Selector, - quirks_mode: QuirksMode + quirks_mode: QuirksMode, ) -> Result<(), FailedAllocationError> { self.collect_invalidations_for(selector, quirks_mode) } @@ -205,6 +222,7 @@ impl InvalidationMap { self.class_to_selector.clear(); self.id_to_selector.clear(); self.state_affecting_selectors.clear(); + self.document_state_selectors.clear(); self.other_attribute_affecting_selectors.clear(); self.has_id_attribute_selectors = false; self.has_class_attribute_selectors = false; @@ -222,6 +240,8 @@ impl InvalidationMap { let mut combinator; let mut index = 0; + let mut document_state = DocumentState::empty(); + loop { let sequence_start = index; @@ -229,6 +249,7 @@ impl InvalidationMap { classes: SmallVec::new(), ids: SmallVec::new(), state: ElementState::empty(), + document_state: &mut document_state, other_attributes: false, has_id_attribute_selectors: false, has_class_attribute_selectors: false, @@ -297,15 +318,28 @@ impl InvalidationMap { index += 1; // Account for the combinator. } + if !document_state.is_empty() { + self.document_state_selectors.try_push(DocumentStateDependency { + state: document_state, + selector: selector.clone(), + })?; + } + Ok(()) } } /// A struct that collects invalidations for a given compound selector. -struct CompoundSelectorDependencyCollector { +struct CompoundSelectorDependencyCollector<'a> { /// The state this compound selector is affected by. state: ElementState, + /// The document this _complex_ selector is affected by. + /// + /// We don't need to track state per compound selector, since it's global + /// state and it changes for everything. + document_state: &'a mut DocumentState, + /// The classes this compound selector is affected by. /// /// NB: This will be often a single class, but could be multiple in @@ -330,7 +364,7 @@ struct CompoundSelectorDependencyCollector { has_class_attribute_selectors: bool, } -impl SelectorVisitor for CompoundSelectorDependencyCollector { +impl<'a> SelectorVisitor for CompoundSelectorDependencyCollector<'a> { type Impl = SelectorImpl; fn visit_simple_selector(&mut self, s: &Component) -> bool { @@ -353,6 +387,7 @@ impl SelectorVisitor for CompoundSelectorDependencyCollector { } _ => pc.state_flag(), }; + *self.document_state |= pc.document_state_flag(); } _ => {} } diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs index 14aa084a1b0..3e2866ddca8 100644 --- a/components/style/invalidation/element/invalidator.rs +++ b/components/style/invalidation/element/invalidator.rs @@ -505,6 +505,9 @@ where DescendantInvalidationKind::Slotted, ); + // FIXME(emilio): Need to handle nested slotted nodes if `element` + // is itself a . + debug_assert!( sibling_invalidations.is_empty(), "::slotted() shouldn't have sibling combinators to the right, \ diff --git a/components/style/invalidation/element/mod.rs b/components/style/invalidation/element/mod.rs index 52e4bd38844..f80d9af65a7 100644 --- a/components/style/invalidation/element/mod.rs +++ b/components/style/invalidation/element/mod.rs @@ -4,6 +4,7 @@ //! Invalidation of element styles due to attribute or style changes. +pub mod document_state; pub mod element_wrapper; pub mod invalidation_map; pub mod invalidator; diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index 37d6fc675b2..fd93062eadd 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -298,12 +298,10 @@ where return should_process_descendants(&self.data) } - let data = match element.borrow_data() { - Some(d) => d, + match element.borrow_data() { + Some(d) => should_process_descendants(&d), None => return false, - }; - - should_process_descendants(&data) + } } fn recursion_limit_exceeded(&mut self, element: E) { diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index a06de17734e..dbedda39590 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -12,6 +12,7 @@ use cssparser::{Parser as CssParser, ToCss, serialize_identifier, CowRcStr, Sour use dom::{OpaqueNode, TElement, TNode}; use element_state::{DocumentState, ElementState}; use fnv::FnvHashMap; +use invalidation::element::document_state::InvalidationMatchingData; use invalidation::element::element_wrapper::ElementSnapshot; use properties::ComputedValues; use properties::PropertyFlags; @@ -377,6 +378,7 @@ impl ::selectors::SelectorImpl for SelectorImpl { type PseudoElement = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; + type ExtraMatchingData = InvalidationMatchingData; type AttrValue = String; type Identifier = Atom; type ClassName = Atom;