servo: Merge #19747 - style: Add a document state invalidator (from emilio:doc-state-invalidator); r=upsuper

Don't use it yet (since I was working from a Servo tree). Will hook it up and improve in the Gecko bug.

Right now it takes a `StyleRuleCascadeData`, which means that if all the origins in the document have state selectors we could do just one walk over the tree and not multiple, that will be improved.

Other than that, this is completely untested of course, but I prefer to land it, given I don't think it's complex, and work on the Gecko integration separately. The reason for this is that I also plan to fix the `<slot>` bugs, which will change `StyleRuleCascadeData` and such, and I want the two bugs to conflict as little as possible.

Source-Repo: https://github.com/servo/servo
Source-Revision: 50e4171958d4ff7f1c76d133a8f89e7d5995376f

--HG--
extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear
extra : subtree_revision : 4f1c7698b9912d4782d744a4dbd9e80afa7fe0cf
This commit is contained in:
Emilio Cobos Álvarez 2018-01-12 05:09:04 -06:00
Родитель 526d5c1370
Коммит 543216dd9a
12 изменённых файлов: 182 добавлений и 14 удалений

Просмотреть файл

@ -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<Impl>,
@ -173,6 +176,7 @@ where
scope_element: None,
nesting_level: 0,
pseudo_element_matching_fn: None,
extra_data: Default::default(),
_impl: ::std::marker::PhantomData,
}
}

Просмотреть файл

@ -84,6 +84,7 @@ macro_rules! with_all_bounds {
/// are parameterized on SelectorImpl. See
/// <https://github.com/rust-lang/rust/issues/26925>
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;

Просмотреть файл

@ -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;

Просмотреть файл

@ -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))

Просмотреть файл

@ -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;

Просмотреть файл

@ -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,

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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<SelectorImpl>,
/// 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<Atom, SmallVec<[Dependency; 1]>>,
/// A map of all the state dependencies.
pub state_affecting_selectors: SelectorMap<StateDependency>,
/// A list of document state dependencies in the rules we represent.
pub document_state_selectors: Vec<DocumentStateDependency>,
/// A map of other attribute affecting selectors.
pub other_attribute_affecting_selectors: SelectorMap<Dependency>,
/// 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<SelectorImpl>,
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<SelectorImpl>) -> bool {
@ -353,6 +387,7 @@ impl SelectorVisitor for CompoundSelectorDependencyCollector {
}
_ => pc.state_flag(),
};
*self.document_state |= pc.document_state_flag();
}
_ => {}
}

Просмотреть файл

@ -505,6 +505,9 @@ where
DescendantInvalidationKind::Slotted,
);
// FIXME(emilio): Need to handle nested slotted nodes if `element`
// is itself a <slot>.
debug_assert!(
sibling_invalidations.is_empty(),
"::slotted() shouldn't have sibling combinators to the right, \

Просмотреть файл

@ -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;

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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;