зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1792501: Part 4 - Basic :has invalidation. r=emilio,layout-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D185677
This commit is contained in:
Родитель
aa280d1e29
Коммит
018c4ad794
|
@ -3354,7 +3354,9 @@ void RestyleManager::ElementStateChanged(Element* aElement,
|
|||
ElementState previousState = aElement->StyleState() ^ aChangedBits;
|
||||
snapshot.AddState(previousState);
|
||||
|
||||
MaybeRestyleForNthOfState(*StyleSet(), aElement, aChangedBits);
|
||||
ServoStyleSet& styleSet = *StyleSet();
|
||||
MaybeRestyleForNthOfState(styleSet, aElement, aChangedBits);
|
||||
MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits);
|
||||
}
|
||||
|
||||
void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
|
||||
|
@ -3501,6 +3503,7 @@ void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
|
|||
changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
|
||||
|
||||
MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue);
|
||||
MaybeRestyleForRelativeSelectorAttribute(aElement, aAttribute, aOldValue);
|
||||
|
||||
if (aAttribute == nsGkAtoms::style) {
|
||||
restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
|
||||
|
@ -3600,6 +3603,35 @@ void RestyleManager::MaybeRestyleForNthOfAttribute(
|
|||
}
|
||||
}
|
||||
|
||||
void RestyleManager::MaybeRestyleForRelativeSelectorAttribute(
|
||||
Element* aElement, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
|
||||
if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
|
||||
return;
|
||||
}
|
||||
auto& styleSet = *StyleSet();
|
||||
if (aAttribute == nsGkAtoms::id) {
|
||||
auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
|
||||
? aOldValue->GetAtomValue()
|
||||
: nullptr;
|
||||
styleSet.MaybeInvalidateRelativeSelectorIDDependency(*aElement, oldAtom,
|
||||
aElement->GetID());
|
||||
} else if (aAttribute == nsGkAtoms::_class) {
|
||||
styleSet.MaybeInvalidateRelativeSelectorClassDependency(*aElement);
|
||||
} else {
|
||||
styleSet.MaybeInvalidateRelativeSelectorAttributeDependency(*aElement,
|
||||
aAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
void RestyleManager::MaybeRestyleForRelativeSelectorState(
|
||||
ServoStyleSet& aStyleSet, Element* aElement, ElementState aChangedBits) {
|
||||
if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
|
||||
return;
|
||||
}
|
||||
aStyleSet.MaybeInvalidateRelativeSelectorStateDependency(*aElement,
|
||||
aChangedBits);
|
||||
}
|
||||
|
||||
void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) {
|
||||
// This is only called when moving frames in or out of the first-line
|
||||
// pseudo-element (or one of its descendants). We can't say much about
|
||||
|
|
|
@ -389,6 +389,13 @@ class RestyleManager {
|
|||
void MaybeRestyleForNthOfAttribute(dom::Element* aChild, nsAtom* aAttribute,
|
||||
const nsAttrValue* aOldValue);
|
||||
|
||||
void MaybeRestyleForRelativeSelectorAttribute(dom::Element* aElement,
|
||||
nsAtom* aAttribute,
|
||||
const nsAttrValue* aOldValue);
|
||||
void MaybeRestyleForRelativeSelectorState(ServoStyleSet& aStyleSet,
|
||||
dom::Element* aElement,
|
||||
dom::ElementState aChangedBits);
|
||||
|
||||
// This is only used to reparent things when moving them in/out of the
|
||||
// ::first-line.
|
||||
void ReparentComputedStyleForFirstLine(nsIFrame*);
|
||||
|
|
|
@ -1384,6 +1384,30 @@ bool ServoStyleSet::MightHaveNthOfClassDependency(const Element& aElement) {
|
|||
&Snapshots());
|
||||
}
|
||||
|
||||
void ServoStyleSet::MaybeInvalidateRelativeSelectorIDDependency(
|
||||
const Element& aElement, nsAtom* aOldID, nsAtom* aNewID) {
|
||||
Servo_StyleSet_MaybeInvalidateRelativeSelectorIDDependency(
|
||||
mRawData.get(), &aElement, aOldID, aNewID);
|
||||
}
|
||||
|
||||
void ServoStyleSet::MaybeInvalidateRelativeSelectorClassDependency(
|
||||
const Element& aElement) {
|
||||
Servo_StyleSet_MaybeInvalidateRelativeSelectorClassDependency(
|
||||
mRawData.get(), &aElement, &Snapshots());
|
||||
}
|
||||
|
||||
void ServoStyleSet::MaybeInvalidateRelativeSelectorAttributeDependency(
|
||||
const Element& aElement, nsAtom* aAttribute) {
|
||||
Servo_StyleSet_MaybeInvalidateRelativeSelectorAttributeDependency(
|
||||
mRawData.get(), &aElement, aAttribute);
|
||||
}
|
||||
|
||||
void ServoStyleSet::MaybeInvalidateRelativeSelectorStateDependency(
|
||||
const Element& aElement, ElementState aState) {
|
||||
Servo_StyleSet_MaybeInvalidateRelativeSelectorStateDependency(
|
||||
mRawData.get(), &aElement, aState.GetInternalValue());
|
||||
}
|
||||
|
||||
bool ServoStyleSet::MightHaveNthOfAttributeDependency(
|
||||
const Element& aElement, nsAtom* aAttribute) const {
|
||||
return Servo_StyleSet_MightHaveNthOfAttributeDependency(
|
||||
|
|
|
@ -462,6 +462,34 @@ class ServoStyleSet {
|
|||
bool MightHaveNthOfIDDependency(const dom::Element&, nsAtom* aOldID,
|
||||
nsAtom* aNewID) const;
|
||||
|
||||
/**
|
||||
* Maybe invalidate if a modification to an ID might require us to restyle
|
||||
* the relative selector it refers to.
|
||||
*/
|
||||
void MaybeInvalidateRelativeSelectorIDDependency(const dom::Element&,
|
||||
nsAtom* aOldID,
|
||||
nsAtom* aNewID);
|
||||
|
||||
/**
|
||||
* Maybe invalidate if a modification to an attribute with the specified local
|
||||
* name might require us to restyle the relative selector it refers to.
|
||||
*/
|
||||
void MaybeInvalidateRelativeSelectorClassDependency(const dom::Element&);
|
||||
|
||||
/**
|
||||
* Maybe invalidate if a modification to an ID might require us to restyle
|
||||
* the relative selector it refers to.
|
||||
*/
|
||||
void MaybeInvalidateRelativeSelectorAttributeDependency(const dom::Element&,
|
||||
nsAtom* aAttribute);
|
||||
|
||||
/**
|
||||
* Maybe invalidate if a change in event state on an element might require us
|
||||
* to restyle the relative selector it refers to.
|
||||
*/
|
||||
void MaybeInvalidateRelativeSelectorStateDependency(const dom::Element&,
|
||||
dom::ElementState);
|
||||
|
||||
/**
|
||||
* Returns true if a change in event state on an element might require
|
||||
* us to restyle the element.
|
||||
|
|
|
@ -1543,6 +1543,51 @@ pub enum RelativeSelectorMatchHint {
|
|||
}
|
||||
|
||||
impl RelativeSelectorMatchHint {
|
||||
/// Create a new relative selector match hint based on its composition.
|
||||
pub fn new(
|
||||
relative_combinator: Combinator,
|
||||
has_child_or_descendants: bool,
|
||||
has_adjacent_or_next_siblings: bool,
|
||||
) -> Self {
|
||||
match relative_combinator {
|
||||
Combinator::Descendant => RelativeSelectorMatchHint::InSubtree,
|
||||
Combinator::Child => {
|
||||
if !has_child_or_descendants {
|
||||
RelativeSelectorMatchHint::InChild
|
||||
} else {
|
||||
// Technically, for any composition that consists of child combinators only,
|
||||
// the search space is depth-constrained, but it's probably not worth optimizing for.
|
||||
RelativeSelectorMatchHint::InSubtree
|
||||
}
|
||||
},
|
||||
Combinator::NextSibling => {
|
||||
if !has_child_or_descendants && !has_adjacent_or_next_siblings {
|
||||
RelativeSelectorMatchHint::InNextSibling
|
||||
} else if !has_child_or_descendants && has_adjacent_or_next_siblings {
|
||||
RelativeSelectorMatchHint::InSibling
|
||||
} else if has_child_or_descendants && !has_adjacent_or_next_siblings {
|
||||
// Match won't cross multiple siblings.
|
||||
RelativeSelectorMatchHint::InNextSiblingSubtree
|
||||
} else {
|
||||
RelativeSelectorMatchHint::InSiblingSubtree
|
||||
}
|
||||
},
|
||||
Combinator::LaterSibling => {
|
||||
if !has_child_or_descendants {
|
||||
RelativeSelectorMatchHint::InSibling
|
||||
} else {
|
||||
// Even if the match may not cross multiple siblings, we have to look until
|
||||
// we find a match anyway.
|
||||
RelativeSelectorMatchHint::InSiblingSubtree
|
||||
}
|
||||
},
|
||||
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
|
||||
debug_assert!(false, "Unexpected relative combinator");
|
||||
RelativeSelectorMatchHint::InSubtree
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the match traversal direction towards the descendant of this element (As opposed to siblings)?
|
||||
pub fn is_descendant_direction(&self) -> bool {
|
||||
matches!(*self, Self::InChild | Self::InSubtree)
|
||||
|
@ -1562,6 +1607,53 @@ impl RelativeSelectorMatchHint {
|
|||
}
|
||||
}
|
||||
|
||||
/// Count of combinators in a given relative selector, not traversing selectors of pseudoclasses.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RelativeSelectorCombinatorCount {
|
||||
relative_combinator: Combinator,
|
||||
pub child_or_descendants: usize,
|
||||
pub adjacent_or_next_siblings: usize,
|
||||
}
|
||||
|
||||
impl RelativeSelectorCombinatorCount {
|
||||
/// Create a new relative selector combinator count from a given relative selector.
|
||||
pub fn new<Impl: SelectorImpl>(relative_selector: &RelativeSelector<Impl>) -> Self {
|
||||
let mut result = RelativeSelectorCombinatorCount {
|
||||
relative_combinator: relative_selector.selector.combinator_at_parse_order(1),
|
||||
child_or_descendants: 0,
|
||||
adjacent_or_next_siblings: 0,
|
||||
};
|
||||
|
||||
for combinator in CombinatorIter::new(
|
||||
relative_selector
|
||||
.selector
|
||||
.iter_skip_relative_selector_anchor(),
|
||||
) {
|
||||
match combinator {
|
||||
Combinator::Descendant | Combinator::Child => {
|
||||
result.child_or_descendants += 1;
|
||||
},
|
||||
Combinator::NextSibling | Combinator::LaterSibling => {
|
||||
result.adjacent_or_next_siblings += 1;
|
||||
},
|
||||
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
|
||||
continue
|
||||
},
|
||||
};
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Get the match hint based on the current combinator count.
|
||||
pub fn get_match_hint(&self) -> RelativeSelectorMatchHint {
|
||||
RelativeSelectorMatchHint::new(
|
||||
self.relative_combinator,
|
||||
self.child_or_descendants != 0,
|
||||
self.adjacent_or_next_siblings != 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage for a relative selector.
|
||||
#[derive(Clone, Eq, PartialEq, ToShmem)]
|
||||
#[shmem(no_bounds)]
|
||||
|
@ -1627,48 +1719,12 @@ impl<Impl: SelectorImpl> RelativeSelector<Impl> {
|
|||
);
|
||||
}
|
||||
// Leave a hint for narrowing down the search space when we're matching.
|
||||
let match_hint = match selector.combinator_at_parse_order(1) {
|
||||
Combinator::Descendant => RelativeSelectorMatchHint::InSubtree,
|
||||
Combinator::Child => {
|
||||
let composition = CombinatorComposition::for_relative_selector(&selector);
|
||||
if composition.is_empty() || composition == CombinatorComposition::SIBLINGS
|
||||
{
|
||||
RelativeSelectorMatchHint::InChild
|
||||
} else {
|
||||
// Technically, for any composition that consists of child combinators only,
|
||||
// the search space is depth-constrained, but it's probably not worth optimizing for.
|
||||
RelativeSelectorMatchHint::InSubtree
|
||||
}
|
||||
},
|
||||
Combinator::NextSibling => {
|
||||
let composition = CombinatorComposition::for_relative_selector(&selector);
|
||||
if composition.is_empty() {
|
||||
RelativeSelectorMatchHint::InNextSibling
|
||||
} else if composition == CombinatorComposition::SIBLINGS {
|
||||
RelativeSelectorMatchHint::InSibling
|
||||
} else if composition == CombinatorComposition::DESCENDANTS {
|
||||
// Match won't cross multiple siblings.
|
||||
RelativeSelectorMatchHint::InNextSiblingSubtree
|
||||
} else {
|
||||
RelativeSelectorMatchHint::InSiblingSubtree
|
||||
}
|
||||
},
|
||||
Combinator::LaterSibling => {
|
||||
let composition = CombinatorComposition::for_relative_selector(&selector);
|
||||
if composition.is_empty() || composition == CombinatorComposition::SIBLINGS
|
||||
{
|
||||
RelativeSelectorMatchHint::InSibling
|
||||
} else {
|
||||
// Even if the match may not cross multiple siblings, we have to look until
|
||||
// we find a match anyway.
|
||||
RelativeSelectorMatchHint::InSiblingSubtree
|
||||
}
|
||||
},
|
||||
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
|
||||
debug_assert!(false, "Unexpected relative combinator");
|
||||
RelativeSelectorMatchHint::InSubtree
|
||||
},
|
||||
};
|
||||
let composition = CombinatorComposition::for_relative_selector(&selector);
|
||||
let match_hint = RelativeSelectorMatchHint::new(
|
||||
selector.combinator_at_parse_order(1),
|
||||
composition.intersects(CombinatorComposition::DESCENDANTS),
|
||||
composition.intersects(CombinatorComposition::SIBLINGS),
|
||||
);
|
||||
RelativeSelector {
|
||||
match_hint,
|
||||
selector,
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::values::AtomIdent;
|
|||
use crate::WeakAtom;
|
||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||
use dom::ElementState;
|
||||
use selectors::matching::{QuirksMode, VisitedHandlingMode};
|
||||
use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode};
|
||||
use selectors::sink::Push;
|
||||
use selectors::Element as SelectorsElement;
|
||||
use servo_arc::{Arc, ArcBorrow};
|
||||
|
@ -897,6 +897,12 @@ pub trait TElement:
|
|||
&self,
|
||||
display: &Display,
|
||||
) -> euclid::default::Size2D<Option<app_units::Au>>;
|
||||
|
||||
/// Returns true if the element has all of specified selector flags.
|
||||
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
|
||||
|
||||
/// Returns the search direction for relative selector invalidation, if it is on the search path.
|
||||
fn relative_selector_search_direction(&self) -> Option<ElementSelectorFlags>;
|
||||
}
|
||||
|
||||
/// TNode and TElement aren't Send because we want to be careful and explicit
|
||||
|
|
|
@ -297,6 +297,11 @@ impl<'ln> GeckoNode<'ln> {
|
|||
Self::flags_atomic_for(&self.0.mSelectorFlags)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn selector_flags(&self) -> u32 {
|
||||
self.selector_flags_atomic().load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_selector_flags(&self, flags: u32) {
|
||||
self.selector_flags_atomic().fetch_or(flags, Ordering::Relaxed);
|
||||
|
@ -1719,6 +1724,25 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
|
||||
let node_flags = selector_flags_to_node_flags(flags);
|
||||
self.as_node().selector_flags() & node_flags == node_flags
|
||||
}
|
||||
|
||||
fn relative_selector_search_direction(&self) -> Option<ElementSelectorFlags> {
|
||||
use crate::gecko_bindings::structs::NodeSelectorFlags;
|
||||
let flags = self.as_node().selector_flags();
|
||||
if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling.0) != 0 {
|
||||
Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
|
||||
} else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionAncestor.0) != 0 {
|
||||
Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
|
||||
} else if (flags & NodeSelectorFlags::RelativeSelectorSearchDirectionSibling.0) != 0 {
|
||||
Some(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'le> PartialEq for GeckoElement<'le> {
|
||||
|
|
|
@ -8,12 +8,15 @@ use crate::context::QuirksMode;
|
|||
use crate::selector_map::{
|
||||
MaybeCaseInsensitiveHashMap, PrecomputedHashMap, SelectorMap, SelectorMapEntry,
|
||||
};
|
||||
use crate::selector_parser::SelectorImpl;
|
||||
use crate::selector_parser::{NonTSPseudoClass, SelectorImpl};
|
||||
use crate::AllocErr;
|
||||
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded};
|
||||
use dom::{DocumentState, ElementState};
|
||||
use selectors::attr::NamespaceConstraint;
|
||||
use selectors::parser::{Combinator, Component};
|
||||
use selectors::parser::{
|
||||
Combinator, Component, RelativeSelector, RelativeSelectorCombinatorCount,
|
||||
RelativeSelectorMatchHint,
|
||||
};
|
||||
use selectors::parser::{Selector, SelectorIter};
|
||||
use selectors::visitor::{SelectorListKind, SelectorVisitor};
|
||||
use servo_arc::Arc;
|
||||
|
@ -76,7 +79,7 @@ impl SelectorMapEntry for Dependency {
|
|||
}
|
||||
|
||||
/// The kind of elements down the tree this dependency may affect.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, MallocSizeOf)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)]
|
||||
pub enum NormalDependencyInvalidationKind {
|
||||
/// This dependency may affect the element that changed itself.
|
||||
Element,
|
||||
|
@ -99,7 +102,7 @@ pub enum NormalDependencyInvalidationKind {
|
|||
/// The kind of elements up the tree this relative selector dependency may
|
||||
/// affect. Because this travels upwards, it's not viable for parallel subtree
|
||||
/// traversal, and is handled separately.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, MallocSizeOf)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)]
|
||||
pub enum RelativeDependencyInvalidationKind {
|
||||
/// This dependency may affect relative selector anchors for ancestors.
|
||||
Ancestors,
|
||||
|
@ -116,7 +119,7 @@ pub enum RelativeDependencyInvalidationKind {
|
|||
}
|
||||
|
||||
/// Invalidation kind merging normal and relative dependencies.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, MallocSizeOf)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)]
|
||||
pub enum DependencyInvalidationKind {
|
||||
/// This dependency is a normal dependency.
|
||||
Normal(NormalDependencyInvalidationKind),
|
||||
|
@ -217,6 +220,13 @@ pub struct DocumentStateDependency {
|
|||
pub state: DocumentState,
|
||||
}
|
||||
|
||||
/// Dependency mapping for classes or IDs.
|
||||
pub type IdOrClassDependencyMap = MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>;
|
||||
/// Dependency mapping for pseudo-class states.
|
||||
pub type StateDependencyMap = SelectorMap<StateDependency>;
|
||||
/// Dependency mapping for attributes.
|
||||
pub type AttributeDependencyMap = PrecomputedHashMap<LocalName, SmallVec<[Dependency; 1]>>;
|
||||
|
||||
/// A map where we store invalidations.
|
||||
///
|
||||
/// This is slightly different to a SelectorMap, in the sense of that the same
|
||||
|
@ -229,28 +239,64 @@ pub struct DocumentStateDependency {
|
|||
pub struct InvalidationMap {
|
||||
/// A map from a given class name to all the selectors with that class
|
||||
/// selector.
|
||||
pub class_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>,
|
||||
pub class_to_selector: IdOrClassDependencyMap,
|
||||
/// A map from a given id to all the selectors with that ID in the
|
||||
/// stylesheets currently applying to the document.
|
||||
pub id_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>,
|
||||
pub id_to_selector: IdOrClassDependencyMap,
|
||||
/// A map of all the state dependencies.
|
||||
pub state_affecting_selectors: SelectorMap<StateDependency>,
|
||||
pub state_affecting_selectors: StateDependencyMap,
|
||||
/// 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:
|
||||
PrecomputedHashMap<LocalName, SmallVec<[Dependency; 1]>>,
|
||||
pub other_attribute_affecting_selectors: AttributeDependencyMap,
|
||||
}
|
||||
|
||||
/// A map to store all relative selector invalidations.
|
||||
#[derive(Clone, Debug, MallocSizeOf)]
|
||||
pub struct RelativeSelectorInvalidationMap {
|
||||
/// Portion common to the normal invalidation map, except that this is for relative selectors and their inner selectors.
|
||||
pub map: InvalidationMap,
|
||||
/// Flag indicating if any relative selector is used.
|
||||
pub used: bool,
|
||||
/// Flag indicating if invalidating a relative selector requires ancestor traversal.
|
||||
pub needs_ancestors_traversal: bool,
|
||||
}
|
||||
|
||||
impl RelativeSelectorInvalidationMap {
|
||||
/// Creates an empty `InvalidationMap`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: InvalidationMap::new(),
|
||||
used: false,
|
||||
needs_ancestors_traversal: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of dependencies stored in the invalidation map.
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
/// Clears this map, leaving it empty.
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
}
|
||||
|
||||
/// Shrink the capacity of hash maps if needed.
|
||||
pub fn shrink_if_needed(&mut self) {
|
||||
self.map.shrink_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
impl InvalidationMap {
|
||||
/// Creates an empty `InvalidationMap`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
class_to_selector: MaybeCaseInsensitiveHashMap::new(),
|
||||
id_to_selector: MaybeCaseInsensitiveHashMap::new(),
|
||||
state_affecting_selectors: SelectorMap::new(),
|
||||
class_to_selector: IdOrClassDependencyMap::new(),
|
||||
id_to_selector: IdOrClassDependencyMap::new(),
|
||||
state_affecting_selectors: StateDependencyMap::new(),
|
||||
document_state_selectors: Vec::new(),
|
||||
other_attribute_affecting_selectors: PrecomputedHashMap::default(),
|
||||
other_attribute_affecting_selectors: AttributeDependencyMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,15 +338,17 @@ pub fn note_selector_for_invalidation(
|
|||
selector: &Selector<SelectorImpl>,
|
||||
quirks_mode: QuirksMode,
|
||||
map: &mut InvalidationMap,
|
||||
relative_selector_invalidation_map: &mut RelativeSelectorInvalidationMap,
|
||||
) -> Result<(), AllocErr> {
|
||||
debug!("note_selector_for_invalidation({:?})", selector);
|
||||
|
||||
let mut document_state = DocumentState::empty();
|
||||
{
|
||||
let mut parent_stack = SmallVec::new();
|
||||
let mut parent_stack = ParentSelectors::new();
|
||||
let mut alloc_error = None;
|
||||
let mut collector = SelectorDependencyCollector {
|
||||
map,
|
||||
relative_selector_invalidation_map,
|
||||
document_state: &mut document_state,
|
||||
selector,
|
||||
parent_selectors: &mut parent_stack,
|
||||
|
@ -349,9 +397,101 @@ struct ParentDependencyEntry {
|
|||
cached_dependency: Option<Arc<Dependency>>,
|
||||
}
|
||||
|
||||
trait Collector {
|
||||
fn dependency(&mut self) -> Dependency;
|
||||
fn id_map(&mut self) -> &mut IdOrClassDependencyMap;
|
||||
fn class_map(&mut self) -> &mut IdOrClassDependencyMap;
|
||||
fn state_map(&mut self) -> &mut StateDependencyMap;
|
||||
fn attribute_map(&mut self) -> &mut AttributeDependencyMap;
|
||||
fn update_states(&mut self, element_state: ElementState, document_state: DocumentState);
|
||||
}
|
||||
|
||||
fn on_attribute<C: Collector>(
|
||||
local_name: &LocalName,
|
||||
local_name_lower: &LocalName,
|
||||
collector: &mut C,
|
||||
) -> Result<(), AllocErr> {
|
||||
add_attr_dependency(local_name.clone(), collector)?;
|
||||
|
||||
if local_name != local_name_lower {
|
||||
add_attr_dependency(local_name_lower.clone(), collector)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_id_or_class<C: Collector>(
|
||||
s: &Component<SelectorImpl>,
|
||||
quirks_mode: QuirksMode,
|
||||
collector: &mut C,
|
||||
) -> Result<(), AllocErr> {
|
||||
let dependency = collector.dependency();
|
||||
let (atom, map) = match *s {
|
||||
Component::ID(ref atom) => (atom, collector.id_map()),
|
||||
Component::Class(ref atom) => (atom, collector.class_map()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let entry = map.try_entry(atom.0.clone(), quirks_mode)?;
|
||||
let vec = entry.or_insert_with(SmallVec::new);
|
||||
vec.try_reserve(1)?;
|
||||
vec.push(dependency);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_attr_dependency<C: Collector>(name: LocalName, collector: &mut C) -> Result<(), AllocErr> {
|
||||
let dependency = collector.dependency();
|
||||
let map = collector.attribute_map();
|
||||
map.try_reserve(1)?;
|
||||
let vec = map.entry(name).or_default();
|
||||
vec.try_reserve(1)?;
|
||||
vec.push(dependency);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_pseudo_class<C: Collector>(pc: &NonTSPseudoClass, collector: &mut C) -> Result<(), AllocErr> {
|
||||
collector.update_states(pc.state_flag(), pc.document_state_flag());
|
||||
|
||||
let attr_name = match *pc {
|
||||
#[cfg(feature = "gecko")]
|
||||
NonTSPseudoClass::MozTableBorderNonzero => local_name!("border"),
|
||||
#[cfg(feature = "gecko")]
|
||||
NonTSPseudoClass::MozBrowserFrame => local_name!("mozbrowser"),
|
||||
#[cfg(feature = "gecko")]
|
||||
NonTSPseudoClass::MozSelectListBox => {
|
||||
// This depends on two attributes.
|
||||
add_attr_dependency(local_name!("multiple"), collector)?;
|
||||
return add_attr_dependency(local_name!("size"), collector);
|
||||
},
|
||||
NonTSPseudoClass::Lang(..) => local_name!("lang"),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
add_attr_dependency(attr_name, collector)
|
||||
}
|
||||
|
||||
fn add_pseudo_class_dependency<C: Collector>(
|
||||
element_state: ElementState,
|
||||
quirks_mode: QuirksMode,
|
||||
collector: &mut C,
|
||||
) -> Result<(), AllocErr> {
|
||||
if element_state.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let dependency = collector.dependency();
|
||||
collector.state_map().insert(
|
||||
StateDependency {
|
||||
dep: dependency,
|
||||
state: element_state,
|
||||
},
|
||||
quirks_mode,
|
||||
)
|
||||
}
|
||||
|
||||
type ParentSelectors = SmallVec<[ParentDependencyEntry; 5]>;
|
||||
|
||||
/// A struct that collects invalidations for a given compound selector.
|
||||
struct SelectorDependencyCollector<'a> {
|
||||
map: &'a mut InvalidationMap,
|
||||
relative_selector_invalidation_map: &'a mut RelativeSelectorInvalidationMap,
|
||||
|
||||
/// The document this _complex_ selector is affected by.
|
||||
///
|
||||
|
@ -367,7 +507,7 @@ struct SelectorDependencyCollector<'a> {
|
|||
///
|
||||
/// This starts empty. It grows when we find nested :is and :where selector
|
||||
/// lists. The dependency field is cached and reference counted.
|
||||
parent_selectors: &'a mut SmallVec<[ParentDependencyEntry; 5]>,
|
||||
parent_selectors: &'a mut ParentSelectors,
|
||||
|
||||
/// The quirks mode of the document where we're inserting dependencies.
|
||||
quirks_mode: QuirksMode,
|
||||
|
@ -379,6 +519,71 @@ struct SelectorDependencyCollector<'a> {
|
|||
alloc_error: &'a mut Option<AllocErr>,
|
||||
}
|
||||
|
||||
fn parent_dependency(parent_selectors: &mut ParentSelectors) -> Option<Arc<Dependency>> {
|
||||
if parent_selectors.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn dependencies_from(entries: &mut [ParentDependencyEntry]) -> Option<Arc<Dependency>> {
|
||||
if entries.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let last_index = entries.len() - 1;
|
||||
let (previous, last) = entries.split_at_mut(last_index);
|
||||
let last = &mut last[0];
|
||||
let selector = &last.selector;
|
||||
let selector_offset = last.offset;
|
||||
Some(
|
||||
last.cached_dependency
|
||||
.get_or_insert_with(|| {
|
||||
Arc::new(Dependency {
|
||||
selector: selector.clone(),
|
||||
selector_offset,
|
||||
parent: dependencies_from(previous),
|
||||
relative_kind: None,
|
||||
})
|
||||
})
|
||||
.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
dependencies_from(parent_selectors)
|
||||
}
|
||||
|
||||
impl<'a> Collector for SelectorDependencyCollector<'a> {
|
||||
fn dependency(&mut self) -> Dependency {
|
||||
let parent = parent_dependency(self.parent_selectors);
|
||||
Dependency {
|
||||
selector: self.selector.clone(),
|
||||
selector_offset: self.compound_state.offset,
|
||||
parent,
|
||||
relative_kind: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn id_map(&mut self) -> &mut IdOrClassDependencyMap {
|
||||
&mut self.map.id_to_selector
|
||||
}
|
||||
|
||||
fn class_map(&mut self) -> &mut IdOrClassDependencyMap {
|
||||
&mut self.map.class_to_selector
|
||||
}
|
||||
|
||||
fn state_map(&mut self) -> &mut StateDependencyMap {
|
||||
&mut self.map.state_affecting_selectors
|
||||
}
|
||||
|
||||
fn attribute_map(&mut self) -> &mut AttributeDependencyMap {
|
||||
&mut self.map.other_attribute_affecting_selectors
|
||||
}
|
||||
|
||||
fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) {
|
||||
self.compound_state.element_state |= element_state;
|
||||
*self.document_state |= document_state;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SelectorDependencyCollector<'a> {
|
||||
fn visit_whole_selector(&mut self) -> bool {
|
||||
let iter = self.selector.iter();
|
||||
|
@ -402,19 +607,13 @@ impl<'a> SelectorDependencyCollector<'a> {
|
|||
index += 1; // Account for the simple selector.
|
||||
}
|
||||
|
||||
if !self.compound_state.element_state.is_empty() {
|
||||
let dependency = self.dependency();
|
||||
let result = self.map.state_affecting_selectors.insert(
|
||||
StateDependency {
|
||||
dep: dependency,
|
||||
state: self.compound_state.element_state,
|
||||
},
|
||||
self.quirks_mode,
|
||||
);
|
||||
if let Err(alloc_error) = result {
|
||||
*self.alloc_error = Some(alloc_error.into());
|
||||
return false;
|
||||
}
|
||||
if let Err(err) = add_pseudo_class_dependency(
|
||||
self.compound_state.element_state,
|
||||
self.quirks_mode,
|
||||
self,
|
||||
) {
|
||||
*self.alloc_error = Some(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
let combinator = iter.next_sequence();
|
||||
|
@ -424,65 +623,6 @@ impl<'a> SelectorDependencyCollector<'a> {
|
|||
index += 1; // account for the combinator
|
||||
}
|
||||
}
|
||||
|
||||
fn add_attr_dependency(&mut self, name: LocalName) -> bool {
|
||||
let dependency = self.dependency();
|
||||
|
||||
let map = &mut self.map.other_attribute_affecting_selectors;
|
||||
if let Err(err) = map.try_reserve(1) {
|
||||
*self.alloc_error = Some(err.into());
|
||||
return false;
|
||||
}
|
||||
let vec = map.entry(name).or_default();
|
||||
if let Err(err) = vec.try_reserve(1) {
|
||||
*self.alloc_error = Some(err.into());
|
||||
return false;
|
||||
}
|
||||
vec.push(dependency);
|
||||
true
|
||||
}
|
||||
|
||||
fn parent_dependency(&mut self) -> Option<Arc<Dependency>> {
|
||||
if self.parent_selectors.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn dependencies_from(entries: &mut [ParentDependencyEntry]) -> Option<Arc<Dependency>> {
|
||||
if entries.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let last_index = entries.len() - 1;
|
||||
let (previous, last) = entries.split_at_mut(last_index);
|
||||
let last = &mut last[0];
|
||||
let selector = &last.selector;
|
||||
let selector_offset = last.offset;
|
||||
Some(
|
||||
last.cached_dependency
|
||||
.get_or_insert_with(|| {
|
||||
Arc::new(Dependency {
|
||||
selector: selector.clone(),
|
||||
selector_offset,
|
||||
parent: dependencies_from(previous),
|
||||
relative_kind: None,
|
||||
})
|
||||
})
|
||||
.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
dependencies_from(&mut self.parent_selectors)
|
||||
}
|
||||
|
||||
fn dependency(&mut self) -> Dependency {
|
||||
let parent = self.parent_dependency();
|
||||
Dependency {
|
||||
selector: self.selector.clone(),
|
||||
selector_offset: self.compound_state.offset,
|
||||
parent,
|
||||
relative_kind: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> {
|
||||
|
@ -523,6 +663,7 @@ impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> {
|
|||
});
|
||||
let mut nested = SelectorDependencyCollector {
|
||||
map: &mut *self.map,
|
||||
relative_selector_invalidation_map: &mut *self.relative_selector_invalidation_map,
|
||||
document_state: &mut *self.document_state,
|
||||
selector,
|
||||
parent_selectors: &mut *self.parent_selectors,
|
||||
|
@ -538,52 +679,52 @@ impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> {
|
|||
true
|
||||
}
|
||||
|
||||
fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
|
||||
use crate::selector_parser::NonTSPseudoClass;
|
||||
fn visit_relative_selector_list(
|
||||
&mut self,
|
||||
list: &[selectors::parser::RelativeSelector<Self::Impl>],
|
||||
) -> bool {
|
||||
self.relative_selector_invalidation_map.used = true;
|
||||
for relative_selector in list {
|
||||
// We can't cheat here like we do with other selector lists - the rightmost
|
||||
// compound of a relative selector is not the subject of the invalidation.
|
||||
self.parent_selectors.push(ParentDependencyEntry {
|
||||
selector: self.selector.clone(),
|
||||
offset: self.compound_state.offset,
|
||||
cached_dependency: None,
|
||||
});
|
||||
let mut nested = RelativeSelectorDependencyCollector {
|
||||
map: &mut *self.relative_selector_invalidation_map,
|
||||
document_state: &mut *self.document_state,
|
||||
selector: &relative_selector,
|
||||
combinator_count: RelativeSelectorCombinatorCount::new(relative_selector),
|
||||
parent_selectors: &mut *self.parent_selectors,
|
||||
quirks_mode: self.quirks_mode,
|
||||
compound_state: PerCompoundState::new(0),
|
||||
alloc_error: &mut *self.alloc_error,
|
||||
};
|
||||
if !nested.visit_whole_selector() {
|
||||
return false;
|
||||
}
|
||||
self.parent_selectors.pop();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
|
||||
match *s {
|
||||
Component::ID(ref atom) | Component::Class(ref atom) => {
|
||||
let dependency = self.dependency();
|
||||
let map = match *s {
|
||||
Component::ID(..) => &mut self.map.id_to_selector,
|
||||
Component::Class(..) => &mut self.map.class_to_selector,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let entry = match map.try_entry(atom.0.clone(), self.quirks_mode) {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
*self.alloc_error = Some(err.into());
|
||||
return false;
|
||||
},
|
||||
};
|
||||
let vec = entry.or_insert_with(SmallVec::new);
|
||||
if let Err(err) = vec.try_reserve(1) {
|
||||
Component::ID(..) | Component::Class(..) => {
|
||||
if let Err(err) = on_id_or_class(s, self.quirks_mode, self) {
|
||||
*self.alloc_error = Some(err.into());
|
||||
return false;
|
||||
}
|
||||
vec.push(dependency);
|
||||
true
|
||||
},
|
||||
Component::NonTSPseudoClass(ref pc) => {
|
||||
self.compound_state.element_state |= pc.state_flag();
|
||||
*self.document_state |= pc.document_state_flag();
|
||||
|
||||
let attr_name = match *pc {
|
||||
#[cfg(feature = "gecko")]
|
||||
NonTSPseudoClass::MozTableBorderNonzero => local_name!("border"),
|
||||
#[cfg(feature = "gecko")]
|
||||
NonTSPseudoClass::MozBrowserFrame => local_name!("mozbrowser"),
|
||||
#[cfg(feature = "gecko")]
|
||||
NonTSPseudoClass::MozSelectListBox => {
|
||||
// This depends on two attributes.
|
||||
return self.add_attr_dependency(local_name!("multiple")) &&
|
||||
self.add_attr_dependency(local_name!("size"));
|
||||
},
|
||||
NonTSPseudoClass::Lang(..) => local_name!("lang"),
|
||||
_ => return true,
|
||||
};
|
||||
|
||||
self.add_attr_dependency(attr_name)
|
||||
if let Err(err) = on_pseudo_class(pc, self) {
|
||||
*self.alloc_error = Some(err.into());
|
||||
return false;
|
||||
}
|
||||
true
|
||||
},
|
||||
_ => true,
|
||||
}
|
||||
|
@ -595,14 +736,194 @@ impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> {
|
|||
local_name: &LocalName,
|
||||
local_name_lower: &LocalName,
|
||||
) -> bool {
|
||||
if !self.add_attr_dependency(local_name.clone()) {
|
||||
if let Err(err) = on_attribute(local_name, local_name_lower, self) {
|
||||
*self.alloc_error = Some(err);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct that collects invalidations for a given compound selector.
|
||||
struct RelativeSelectorDependencyCollector<'a> {
|
||||
map: &'a mut RelativeSelectorInvalidationMap,
|
||||
|
||||
/// 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 current inner relative selector and offset we're iterating.
|
||||
selector: &'a RelativeSelector<SelectorImpl>,
|
||||
/// Running combinator for this inner relative selector.
|
||||
combinator_count: RelativeSelectorCombinatorCount,
|
||||
|
||||
/// The stack of parent selectors that we have, and at which offset of the
|
||||
/// sequence.
|
||||
///
|
||||
/// This starts empty. It grows when we find nested :is and :where selector
|
||||
/// lists. The dependency field is cached and reference counted.
|
||||
parent_selectors: &'a mut ParentSelectors,
|
||||
|
||||
/// The quirks mode of the document where we're inserting dependencies.
|
||||
quirks_mode: QuirksMode,
|
||||
|
||||
/// State relevant to a given compound selector.
|
||||
compound_state: PerCompoundState,
|
||||
|
||||
/// The allocation error, if we OOM.
|
||||
alloc_error: &'a mut Option<AllocErr>,
|
||||
}
|
||||
|
||||
impl<'a> RelativeSelectorDependencyCollector<'a> {
|
||||
fn visit_whole_selector(&mut self) -> bool {
|
||||
let mut iter = self.selector.selector.iter_skip_relative_selector_anchor();
|
||||
let mut index = 0;
|
||||
|
||||
self.map.needs_ancestors_traversal |= match self.selector.match_hint {
|
||||
RelativeSelectorMatchHint::InNextSiblingSubtree |
|
||||
RelativeSelectorMatchHint::InSiblingSubtree |
|
||||
RelativeSelectorMatchHint::InSubtree => true,
|
||||
_ => false,
|
||||
};
|
||||
loop {
|
||||
// Reset the compound state.
|
||||
self.compound_state = PerCompoundState::new(index);
|
||||
|
||||
// Visit all the simple selectors in this sequence.
|
||||
for ss in &mut iter {
|
||||
if !ss.visit(self) {
|
||||
return false;
|
||||
}
|
||||
index += 1; // Account for the simple selector.
|
||||
}
|
||||
|
||||
if let Err(err) = add_pseudo_class_dependency(
|
||||
self.compound_state.element_state,
|
||||
self.quirks_mode,
|
||||
self,
|
||||
) {
|
||||
*self.alloc_error = Some(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
let combinator = iter.next_sequence();
|
||||
if let Some(c) = combinator {
|
||||
match c {
|
||||
Combinator::Child | Combinator::Descendant => {
|
||||
self.combinator_count.child_or_descendants -= 1
|
||||
},
|
||||
Combinator::NextSibling | Combinator::LaterSibling => {
|
||||
self.combinator_count.adjacent_or_next_siblings -= 1
|
||||
},
|
||||
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => (),
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
index += 1; // account for the combinator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collector for RelativeSelectorDependencyCollector<'a> {
|
||||
fn dependency(&mut self) -> Dependency {
|
||||
let parent = parent_dependency(self.parent_selectors);
|
||||
Dependency {
|
||||
selector: self.selector.selector.clone(),
|
||||
selector_offset: self.compound_state.offset,
|
||||
relative_kind: Some(match self.combinator_count.get_match_hint() {
|
||||
RelativeSelectorMatchHint::InChild => RelativeDependencyInvalidationKind::Parent,
|
||||
RelativeSelectorMatchHint::InSubtree => RelativeDependencyInvalidationKind::Ancestors,
|
||||
RelativeSelectorMatchHint::InNextSibling => {
|
||||
RelativeDependencyInvalidationKind::PrevSibling
|
||||
},
|
||||
RelativeSelectorMatchHint::InSibling => {
|
||||
RelativeDependencyInvalidationKind::EarlierSibling
|
||||
},
|
||||
RelativeSelectorMatchHint::InNextSiblingSubtree => {
|
||||
RelativeDependencyInvalidationKind::AncestorPrevSibling
|
||||
},
|
||||
RelativeSelectorMatchHint::InSiblingSubtree => {
|
||||
RelativeDependencyInvalidationKind::AncestorEarlierSibling
|
||||
},
|
||||
}),
|
||||
parent,
|
||||
}
|
||||
}
|
||||
|
||||
fn id_map(&mut self) -> &mut IdOrClassDependencyMap {
|
||||
&mut self.map.map.id_to_selector
|
||||
}
|
||||
|
||||
fn class_map(&mut self) -> &mut IdOrClassDependencyMap {
|
||||
&mut self.map.map.class_to_selector
|
||||
}
|
||||
|
||||
fn state_map(&mut self) -> &mut StateDependencyMap {
|
||||
&mut self.map.map.state_affecting_selectors
|
||||
}
|
||||
|
||||
fn attribute_map(&mut self) -> &mut AttributeDependencyMap {
|
||||
&mut self.map.map.other_attribute_affecting_selectors
|
||||
}
|
||||
|
||||
fn update_states(&mut self, element_state: ElementState, document_state: DocumentState) {
|
||||
self.compound_state.element_state |= element_state;
|
||||
*self.document_state |= document_state;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SelectorVisitor for RelativeSelectorDependencyCollector<'a> {
|
||||
type Impl = SelectorImpl;
|
||||
|
||||
fn visit_selector_list(
|
||||
&mut self,
|
||||
_list_kind: SelectorListKind,
|
||||
_list: &[Selector<SelectorImpl>],
|
||||
) -> bool {
|
||||
// TODO(dshin): Inner selector invalidation.
|
||||
true
|
||||
}
|
||||
|
||||
fn visit_relative_selector_list(
|
||||
&mut self,
|
||||
_list: &[selectors::parser::RelativeSelector<Self::Impl>],
|
||||
) -> bool {
|
||||
unreachable!("Nested relative selector?");
|
||||
}
|
||||
|
||||
fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
|
||||
match *s {
|
||||
Component::ID(..) | Component::Class(..) => {
|
||||
if let Err(err) = on_id_or_class(s, self.quirks_mode, self) {
|
||||
*self.alloc_error = Some(err.into());
|
||||
return false;
|
||||
}
|
||||
true
|
||||
},
|
||||
Component::NonTSPseudoClass(ref pc) => {
|
||||
if let Err(err) = on_pseudo_class(pc, self) {
|
||||
*self.alloc_error = Some(err.into());
|
||||
return false;
|
||||
}
|
||||
true
|
||||
},
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_attribute_selector(
|
||||
&mut self,
|
||||
_: &NamespaceConstraint<&Namespace>,
|
||||
local_name: &LocalName,
|
||||
local_name_lower: &LocalName,
|
||||
) -> bool {
|
||||
if let Err(err) = on_attribute(local_name, local_name_lower, self) {
|
||||
*self.alloc_error = Some(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if local_name != local_name_lower && !self.add_attr_dependency(local_name_lower.clone()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,3 +10,4 @@ pub mod invalidation_map;
|
|||
pub mod invalidator;
|
||||
pub mod restyle_hints;
|
||||
pub mod state_and_attributes;
|
||||
pub mod relative_selector;
|
||||
|
|
|
@ -0,0 +1,508 @@
|
|||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Invalidation of element styles relative selectors.
|
||||
|
||||
use crate::data::ElementData;
|
||||
use crate::dom::TElement;
|
||||
use crate::invalidation::element::restyle_hints::RestyleHint;
|
||||
use crate::invalidation::element::invalidator::{TreeStyleInvalidator, InvalidationResult, InvalidationProcessor, InvalidationVector, DescendantInvalidationLists, Invalidation, SiblingTraversalMap,};
|
||||
use crate::invalidation::element::invalidation_map::{Dependency, RelativeDependencyInvalidationKind, DependencyInvalidationKind, NormalDependencyInvalidationKind};
|
||||
use crate::invalidation::element::state_and_attributes::{invalidated_descendants, invalidated_self, invalidated_sibling, should_process_descendants, push_invalidation, dependency_may_be_relevant};
|
||||
use crate::stylist::{CascadeData, Stylist};
|
||||
use fxhash::FxHashMap;
|
||||
use selectors::OpaqueElement;
|
||||
use selectors::matching::{QuirksMode, ElementSelectorFlags, MatchingContext, SelectorCaches, MatchingForInvalidation, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags};
|
||||
use selectors::parser::SelectorKey;
|
||||
use smallvec::SmallVec;
|
||||
use std::ops::DerefMut;
|
||||
|
||||
/// Overall invalidator for handling relative selector invalidations.
|
||||
pub struct RelativeSelectorInvalidator<'a, E>
|
||||
where
|
||||
E: TElement + 'a,
|
||||
{
|
||||
/// Element triggering the invalidation.
|
||||
pub element: E,
|
||||
/// Quirks mode of the current invalidation.
|
||||
pub quirks_mode: QuirksMode,
|
||||
/// Callback to trigger when the subject element is invalidated.
|
||||
pub invalidated: fn(E, &InvalidationResult),
|
||||
/// Marker for 'a lifetime.
|
||||
pub _marker: ::std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
struct RelativeSelectorInvalidation<'a, E>
|
||||
where
|
||||
E: TElement + 'a,
|
||||
{
|
||||
host: Option<E>,
|
||||
kind: RelativeDependencyInvalidationKind,
|
||||
dependency: &'a Dependency,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
struct InvalidationKey(SelectorKey, DependencyInvalidationKind);
|
||||
|
||||
/// Interface for collecting relative selector dependencies.
|
||||
pub struct RelativeSelectorDependencyCollector<'a, E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
/// Maps an invalidation into its scope, selector offset, and its outer dependency.
|
||||
invalidations: FxHashMap<InvalidationKey, (Option<E>, usize, &'a Dependency)>,
|
||||
/// The top element in the subtree being invalidated.
|
||||
top: E,
|
||||
}
|
||||
|
||||
type Invalidations<'a, E> = SmallVec<[RelativeSelectorInvalidation<'a, E>; 1]>;
|
||||
|
||||
struct ToInvalidate<'a, E: TElement + 'a> {
|
||||
// Dependencies already invalidated.
|
||||
invalidations: Invalidations<'a, E>,
|
||||
}
|
||||
|
||||
impl<'a, E: TElement + 'a> Default for ToInvalidate<'a, E> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
invalidations: Invalidations::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E> RelativeSelectorDependencyCollector<'a, E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
fn new(top: E) -> Self {
|
||||
Self {
|
||||
invalidations: FxHashMap::default(),
|
||||
top,
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_invalidation(
|
||||
&mut self,
|
||||
key: InvalidationKey,
|
||||
offset: usize,
|
||||
outer: &'a Dependency,
|
||||
host: Option<E>,
|
||||
) {
|
||||
self.invalidations.entry(key).and_modify(|(h, o, d)| {
|
||||
// Just keep one.
|
||||
if *o <= offset {
|
||||
return;
|
||||
}
|
||||
(*h, *o, *d) = (host, offset, outer);
|
||||
}).or_insert_with(|| (host, offset, outer));
|
||||
}
|
||||
|
||||
/// Add this dependency, if it is unique (i.e. Different outer dependency or same outer dependency
|
||||
/// but requires a different invalidation traversal).
|
||||
pub fn add_dependency(
|
||||
&mut self,
|
||||
dependency: &'a Dependency,
|
||||
element: E,
|
||||
host: Option<E>,
|
||||
) {
|
||||
match dependency.invalidation_kind() {
|
||||
DependencyInvalidationKind::Normal(..) => unreachable!("TODO inner selector"),
|
||||
DependencyInvalidationKind::Relative(kind) => {
|
||||
debug_assert!(dependency.parent.is_some(), "Orphaned inner relative selector?");
|
||||
if element.relative_selector_search_direction().is_none() {
|
||||
return;
|
||||
}
|
||||
if element != self.top && matches!(kind, RelativeDependencyInvalidationKind::Parent |
|
||||
RelativeDependencyInvalidationKind::PrevSibling |
|
||||
RelativeDependencyInvalidationKind::EarlierSibling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.insert_invalidation(
|
||||
InvalidationKey(SelectorKey::new(&dependency.selector), dependency.invalidation_kind()),
|
||||
dependency.selector_offset,
|
||||
dependency.parent.as_ref().unwrap(),
|
||||
host);
|
||||
// We move the invalidation up to the top of the subtree to avoid unnecessary traveral, but
|
||||
// this means that we need to take ancestor-earlier sibling invalidations into account, as
|
||||
// they'd look into earlier siblings of the top of the subtree as well.
|
||||
if element != self.top && matches!(kind, RelativeDependencyInvalidationKind::AncestorEarlierSibling |
|
||||
RelativeDependencyInvalidationKind::AncestorPrevSibling)
|
||||
{
|
||||
self.insert_invalidation(
|
||||
InvalidationKey(
|
||||
SelectorKey::new(&dependency.selector),
|
||||
if matches!(kind, RelativeDependencyInvalidationKind::AncestorPrevSibling) {
|
||||
DependencyInvalidationKind::Relative(RelativeDependencyInvalidationKind::PrevSibling)
|
||||
} else {
|
||||
DependencyInvalidationKind::Relative(RelativeDependencyInvalidationKind::EarlierSibling)
|
||||
}
|
||||
),
|
||||
dependency.selector_offset,
|
||||
dependency.parent.as_ref().unwrap(),
|
||||
host);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the dependencies in a list format.
|
||||
fn get(self) -> ToInvalidate<'a, E> {
|
||||
let mut result = ToInvalidate::default();
|
||||
for (key, (host, _offset, dependency)) in self.invalidations {
|
||||
match key.1 {
|
||||
DependencyInvalidationKind::Normal(_) => unreachable!("TODO inner selector"),
|
||||
DependencyInvalidationKind::Relative(kind) =>
|
||||
result.invalidations.push(RelativeSelectorInvalidation { kind, host, dependency }),
|
||||
};
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E> RelativeSelectorInvalidator<'a, E>
|
||||
where
|
||||
E: TElement + 'a,
|
||||
{
|
||||
/// Gather relative selector dependencies for the given element, and invalidate as necessary.
|
||||
pub fn invalidate_relative_selectors_for_this<F>(
|
||||
self,
|
||||
stylist: &'a Stylist,
|
||||
gather_dependencies: F,
|
||||
) where
|
||||
F: Fn(
|
||||
&E,
|
||||
&Option<E>,
|
||||
&'a CascadeData,
|
||||
QuirksMode,
|
||||
&mut RelativeSelectorDependencyCollector<'a, E>,
|
||||
),
|
||||
{
|
||||
let mut collector = RelativeSelectorDependencyCollector::new(self.element);
|
||||
stylist.for_each_cascade_data_with_scope(self.element, |data, scope| {
|
||||
let map = data.relative_selector_invalidation_map();
|
||||
if !map.used {
|
||||
return;
|
||||
}
|
||||
gather_dependencies(
|
||||
&self.element,
|
||||
&scope,
|
||||
data,
|
||||
self.quirks_mode,
|
||||
&mut collector,
|
||||
);
|
||||
});
|
||||
self.invalidate_from_dependencies(collector.get());
|
||||
}
|
||||
|
||||
/// Carry out complete invalidation triggered by a relative selector invalidation.
|
||||
/// Updates the relative selector search path if provided.
|
||||
fn invalidate_from_dependencies(&self, to_invalidate: ToInvalidate<'a, E>) {
|
||||
for invalidation in to_invalidate.invalidations {
|
||||
self.invalidate_upwards(&invalidation);
|
||||
}
|
||||
}
|
||||
|
||||
fn invalidate_upwards(&self, invalidation: &RelativeSelectorInvalidation<'a, E>) {
|
||||
let host = invalidation.host.map(|e| e.opaque());
|
||||
let outer_dependency = invalidation.dependency;
|
||||
// This contains the main reason for why relative selector invalidation is handled
|
||||
// separately - It travels ancestor and/or earlier sibling direction.
|
||||
match invalidation.kind {
|
||||
RelativeDependencyInvalidationKind::Parent => {
|
||||
self.element.parent_element().map(|e| {
|
||||
if !Self::in_search_direction(
|
||||
&e,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.handle_anchor(e, outer_dependency, host);
|
||||
});
|
||||
},
|
||||
RelativeDependencyInvalidationKind::Ancestors => {
|
||||
let mut parent = self.element.parent_element();
|
||||
while let Some(par) = parent {
|
||||
if !Self::in_search_direction(
|
||||
&par,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.handle_anchor(par, outer_dependency, host);
|
||||
parent = par.parent_element();
|
||||
}
|
||||
},
|
||||
RelativeDependencyInvalidationKind::PrevSibling => {
|
||||
self.element.prev_sibling_element().map(|e| {
|
||||
if !Self::in_search_direction(
|
||||
&e,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.handle_anchor(e, outer_dependency, host);
|
||||
});
|
||||
},
|
||||
RelativeDependencyInvalidationKind::AncestorPrevSibling => {
|
||||
let mut parent = self.element.parent_element();
|
||||
while let Some(par) = parent {
|
||||
if !Self::in_search_direction(
|
||||
&par,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
par.prev_sibling_element().map(|e| {
|
||||
if !Self::in_search_direction(
|
||||
&e,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.handle_anchor(e, outer_dependency, host);
|
||||
});
|
||||
parent = par.parent_element();
|
||||
}
|
||||
},
|
||||
RelativeDependencyInvalidationKind::EarlierSibling => {
|
||||
let mut sibling = self.element.prev_sibling_element();
|
||||
while let Some(sib) = sibling {
|
||||
if !Self::in_search_direction(
|
||||
&sib,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.handle_anchor(sib, outer_dependency, host);
|
||||
sibling = sib.prev_sibling_element();
|
||||
}
|
||||
},
|
||||
RelativeDependencyInvalidationKind::AncestorEarlierSibling => {
|
||||
let mut parent = self.element.parent_element();
|
||||
while let Some(par) = parent {
|
||||
if !Self::in_search_direction(
|
||||
&par,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let mut sibling = par.prev_sibling_element();
|
||||
while let Some(sib) = sibling {
|
||||
if !Self::in_search_direction(
|
||||
&sib,
|
||||
ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING,
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.handle_anchor(sib, outer_dependency, host);
|
||||
sibling = sib.prev_sibling_element();
|
||||
}
|
||||
parent = par.parent_element();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this element in the direction of the given relative selector search path?
|
||||
fn in_search_direction(element: &E, desired: ElementSelectorFlags) -> bool {
|
||||
if let Some(direction) = element.relative_selector_search_direction() {
|
||||
direction.intersects(desired)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a potential relative selector anchor.
|
||||
fn handle_anchor(
|
||||
&self,
|
||||
element: E,
|
||||
outer_dependency: &Dependency,
|
||||
host: Option<OpaqueElement>,
|
||||
) {
|
||||
let is_rightmost = Self::is_subject(outer_dependency);
|
||||
if (is_rightmost && !element.has_selector_flags(ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR)) ||
|
||||
(!is_rightmost && !element.has_selector_flags(ElementSelectorFlags::ANCHORS_RELATIVE_SELECTOR_NON_SUBJECT))
|
||||
{
|
||||
// If it was never a relative selector anchor, don't bother.
|
||||
return;
|
||||
}
|
||||
let mut selector_caches = SelectorCaches::default();
|
||||
let matching_context = MatchingContext::<'_, E::Impl>::new_for_visited(
|
||||
MatchingMode::Normal,
|
||||
None,
|
||||
&mut selector_caches,
|
||||
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
|
||||
self.quirks_mode,
|
||||
NeedsSelectorFlags::No,
|
||||
MatchingForInvalidation::Yes,
|
||||
);
|
||||
let mut data = match element.mutate_data() {
|
||||
Some(data) => data,
|
||||
None => return,
|
||||
};
|
||||
let mut processor = RelativeSelectorOuterInvalidationProcessor {
|
||||
element,
|
||||
host,
|
||||
data: data.deref_mut(),
|
||||
dependency: &*outer_dependency,
|
||||
matching_context,
|
||||
traversal_map: SiblingTraversalMap::default(),
|
||||
};
|
||||
let result = TreeStyleInvalidator::new(element, None, &mut processor).invalidate();
|
||||
(self.invalidated)(element, &result);
|
||||
}
|
||||
|
||||
/// Does this relative selector dependency have its relative selector in the subject position?
|
||||
fn is_subject(outer_dependency: &Dependency) -> bool {
|
||||
debug_assert!(
|
||||
matches!(outer_dependency.invalidation_kind(), DependencyInvalidationKind::Normal(_)),
|
||||
"Outer selector of relative selector is relative?");
|
||||
match outer_dependency.parent {
|
||||
Some(ref p) => Self::is_subject(p.as_ref()),
|
||||
None => outer_dependency.selector_offset == 0,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Blindly invalidate everything outside of a relative selector.
|
||||
/// Consider `:is(.a :has(.b) .c ~ .d) ~ .e .f`, where .b gets deleted.
|
||||
/// Since the tree mutated, we cannot rely on snapshots.
|
||||
pub struct RelativeSelectorOuterInvalidationProcessor<'a, 'b, E: TElement> {
|
||||
/// Element being invalidated.
|
||||
pub element: E,
|
||||
/// The current shadow host, if any.
|
||||
pub host: Option<OpaqueElement>,
|
||||
/// Data for the element being invalidated.
|
||||
pub data: &'a mut ElementData,
|
||||
/// Dependency to be processed.
|
||||
pub dependency: &'b Dependency,
|
||||
/// Matching context to use for invalidation.
|
||||
pub matching_context: MatchingContext<'a, E::Impl>,
|
||||
/// Traversal map for this invalidation.
|
||||
pub traversal_map: SiblingTraversalMap<E>,
|
||||
}
|
||||
|
||||
|
||||
impl<'a, 'b: 'a, E: 'a> InvalidationProcessor<'b, 'a, E>
|
||||
for RelativeSelectorOuterInvalidationProcessor<'a, 'b, E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
fn invalidates_on_pseudo_element(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn check_outer_dependency(&mut self, _dependency: &Dependency, _element: E) -> bool {
|
||||
// At this point, we know a relative selector invalidated, and are ignoring them.
|
||||
true
|
||||
}
|
||||
|
||||
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
|
||||
&mut self.matching_context
|
||||
}
|
||||
|
||||
fn sibling_traversal_map(&self) -> &SiblingTraversalMap<E> {
|
||||
&self.traversal_map
|
||||
}
|
||||
|
||||
fn collect_invalidations(
|
||||
&mut self,
|
||||
element: E,
|
||||
_self_invalidations: &mut InvalidationVector<'b>,
|
||||
descendant_invalidations: &mut DescendantInvalidationLists<'b>,
|
||||
sibling_invalidations: &mut InvalidationVector<'b>,
|
||||
) -> bool {
|
||||
debug_assert_eq!(element, self.element);
|
||||
debug_assert!(
|
||||
self.matching_context
|
||||
.matching_for_invalidation(),
|
||||
"Not matching for invalidation?"
|
||||
);
|
||||
|
||||
let invalidated_self = add_blind_invalidation(
|
||||
&element,
|
||||
self.dependency,
|
||||
&self.host,
|
||||
descendant_invalidations,
|
||||
sibling_invalidations,
|
||||
);
|
||||
if invalidated_self {
|
||||
self.data.hint.insert(RestyleHint::RESTYLE_SELF);
|
||||
}
|
||||
invalidated_self
|
||||
}
|
||||
|
||||
fn should_process_descendants(&mut self, element: E) -> bool {
|
||||
if element == self.element {
|
||||
return should_process_descendants(&self.data);
|
||||
}
|
||||
|
||||
match element.borrow_data() {
|
||||
Some(d) => should_process_descendants(&d),
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
fn recursion_limit_exceeded(&mut self, _element: E) {
|
||||
unreachable!("Unexpected recursion limit");
|
||||
}
|
||||
|
||||
fn invalidated_descendants(&mut self, element: E, child: E) {
|
||||
invalidated_descendants(element, child)
|
||||
}
|
||||
|
||||
fn invalidated_self(&mut self, element: E) {
|
||||
debug_assert_ne!(element, self.element);
|
||||
invalidated_self(element);
|
||||
}
|
||||
|
||||
fn invalidated_sibling(&mut self, element: E, of: E) {
|
||||
debug_assert_ne!(element, self.element);
|
||||
invalidated_sibling(element, of);
|
||||
}
|
||||
}
|
||||
|
||||
/// Just add this dependency as invalidation without running any selector check.
|
||||
fn add_blind_invalidation<'a, E: TElement>(
|
||||
element: &E,
|
||||
dependency: &'a Dependency,
|
||||
current_host: &Option<OpaqueElement>,
|
||||
descendant_invalidations: &mut DescendantInvalidationLists<'a>,
|
||||
sibling_invalidations: &mut InvalidationVector<'a>,
|
||||
) -> bool {
|
||||
debug_assert!(
|
||||
matches!(dependency.invalidation_kind(), DependencyInvalidationKind::Normal(_)),
|
||||
"Unexpected relative dependency"
|
||||
);
|
||||
if !dependency_may_be_relevant(dependency, element, false) {
|
||||
return false;
|
||||
}
|
||||
let invalidation_kind = dependency.normal_invalidation_kind();
|
||||
if matches!(invalidation_kind, NormalDependencyInvalidationKind::Element) {
|
||||
if let Some(ref parent) = dependency.parent {
|
||||
return add_blind_invalidation(
|
||||
element,
|
||||
parent,
|
||||
current_host,
|
||||
descendant_invalidations,
|
||||
sibling_invalidations,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
debug_assert_ne!(dependency.selector_offset, 0);
|
||||
debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
|
||||
|
||||
let invalidation = Invalidation::new(&dependency, *current_host);
|
||||
|
||||
push_invalidation(
|
||||
invalidation,
|
||||
invalidation_kind,
|
||||
descendant_invalidations,
|
||||
sibling_invalidations,
|
||||
)
|
||||
}
|
|
@ -522,31 +522,12 @@ where
|
|||
let invalidation =
|
||||
Invalidation::new(&dependency, self.matching_context.current_host.clone());
|
||||
|
||||
match invalidation_kind {
|
||||
NormalDependencyInvalidationKind::Element => unreachable!(),
|
||||
NormalDependencyInvalidationKind::ElementAndDescendants => {
|
||||
self.invalidates_self = true;
|
||||
self.descendant_invalidations
|
||||
.dom_descendants
|
||||
.push(invalidation);
|
||||
},
|
||||
NormalDependencyInvalidationKind::Descendants => {
|
||||
self.descendant_invalidations
|
||||
.dom_descendants
|
||||
.push(invalidation);
|
||||
},
|
||||
NormalDependencyInvalidationKind::Siblings => {
|
||||
self.sibling_invalidations.push(invalidation);
|
||||
},
|
||||
NormalDependencyInvalidationKind::Parts => {
|
||||
self.descendant_invalidations.parts.push(invalidation);
|
||||
},
|
||||
NormalDependencyInvalidationKind::SlottedElements => {
|
||||
self.descendant_invalidations
|
||||
.slotted_descendants
|
||||
.push(invalidation);
|
||||
},
|
||||
}
|
||||
self.invalidates_self |= push_invalidation(
|
||||
invalidation,
|
||||
invalidation_kind,
|
||||
self.descendant_invalidations,
|
||||
self.sibling_invalidations,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns whether `dependency` may cause us to invalidate the style of
|
||||
|
@ -562,3 +543,51 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push_invalidation<'a>(
|
||||
invalidation: Invalidation<'a>,
|
||||
invalidation_kind: NormalDependencyInvalidationKind,
|
||||
descendant_invalidations: &mut DescendantInvalidationLists<'a>,
|
||||
sibling_invalidations: &mut InvalidationVector<'a>,
|
||||
) -> bool {
|
||||
match invalidation_kind {
|
||||
NormalDependencyInvalidationKind::Element => unreachable!(),
|
||||
NormalDependencyInvalidationKind::ElementAndDescendants => {
|
||||
descendant_invalidations.dom_descendants.push(invalidation);
|
||||
true
|
||||
},
|
||||
NormalDependencyInvalidationKind::Descendants => {
|
||||
descendant_invalidations.dom_descendants.push(invalidation);
|
||||
false
|
||||
},
|
||||
NormalDependencyInvalidationKind::Siblings => {
|
||||
sibling_invalidations.push(invalidation);
|
||||
false
|
||||
},
|
||||
NormalDependencyInvalidationKind::Parts => {
|
||||
descendant_invalidations.parts.push(invalidation);
|
||||
false
|
||||
},
|
||||
NormalDependencyInvalidationKind::SlottedElements => {
|
||||
descendant_invalidations
|
||||
.slotted_descendants
|
||||
.push(invalidation);
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dependency_may_be_relevant<E: TElement>(
|
||||
dependency: &Dependency,
|
||||
element: &E,
|
||||
already_invalidated_self: bool,
|
||||
) -> bool {
|
||||
match dependency.normal_invalidation_kind() {
|
||||
NormalDependencyInvalidationKind::Element => !already_invalidated_self,
|
||||
NormalDependencyInvalidationKind::SlottedElements => element.is_html_slot_element(),
|
||||
NormalDependencyInvalidationKind::Parts => element.shadow_root().is_some(),
|
||||
NormalDependencyInvalidationKind::ElementAndDescendants |
|
||||
NormalDependencyInvalidationKind::Siblings |
|
||||
NormalDependencyInvalidationKind::Descendants => true,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::dom::{TElement, TShadowRoot};
|
|||
#[cfg(feature = "gecko")]
|
||||
use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion};
|
||||
use crate::invalidation::element::invalidation_map::{
|
||||
note_selector_for_invalidation, InvalidationMap,
|
||||
note_selector_for_invalidation, InvalidationMap, RelativeSelectorInvalidationMap,
|
||||
};
|
||||
use crate::invalidation::media_queries::{
|
||||
EffectiveMediaQueryResults, MediaListKey, ToMediaListKey,
|
||||
|
@ -755,7 +755,9 @@ impl Stylist {
|
|||
pub fn num_invalidations(&self) -> usize {
|
||||
self.cascade_data
|
||||
.iter_origins()
|
||||
.map(|(data, _)| data.invalidation_map.len())
|
||||
.map(|(data, _)| {
|
||||
data.invalidation_map.len() + data.relative_selector_invalidation_map.len()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
|
@ -886,6 +888,20 @@ impl Stylist {
|
|||
doc_author_rules_apply && f(&self.cascade_data.author)
|
||||
}
|
||||
|
||||
/// Execute callback for all applicable style rule data.
|
||||
pub fn for_each_cascade_data_with_scope<'a, E, F>(&'a self, element: E, mut f: F)
|
||||
where
|
||||
E: TElement + 'a,
|
||||
F: FnMut(&'a CascadeData, Option<E>),
|
||||
{
|
||||
f(&self.cascade_data.user_agent.cascade_data, None);
|
||||
element.each_applicable_non_document_style_rule_data(|data, scope| {
|
||||
f(data, Some(scope));
|
||||
});
|
||||
f(&self.cascade_data.user, None);
|
||||
f(&self.cascade_data.author, None);
|
||||
}
|
||||
|
||||
/// Computes the style for a given "precomputed" pseudo-element, taking the
|
||||
/// universal rules and applying them.
|
||||
pub fn precomputed_values_for_pseudo<E>(
|
||||
|
@ -2302,6 +2318,9 @@ pub struct CascadeData {
|
|||
/// The invalidation map for these rules.
|
||||
invalidation_map: InvalidationMap,
|
||||
|
||||
/// The relative selector equivalent of the invalidation map.
|
||||
relative_selector_invalidation_map: RelativeSelectorInvalidationMap,
|
||||
|
||||
/// The attribute local names that appear in attribute selectors. Used
|
||||
/// to avoid taking element snapshots when an irrelevant attribute changes.
|
||||
/// (We don't bother storing the namespace, since namespaced attributes are
|
||||
|
@ -2392,6 +2411,7 @@ impl CascadeData {
|
|||
slotted_rules: None,
|
||||
part_rules: None,
|
||||
invalidation_map: InvalidationMap::new(),
|
||||
relative_selector_invalidation_map: RelativeSelectorInvalidationMap::new(),
|
||||
nth_of_mapped_ids: PrecomputedHashSet::default(),
|
||||
nth_of_class_dependencies: PrecomputedHashSet::default(),
|
||||
nth_of_attribute_dependencies: PrecomputedHashSet::default(),
|
||||
|
@ -2470,6 +2490,11 @@ impl CascadeData {
|
|||
&self.invalidation_map
|
||||
}
|
||||
|
||||
/// Returns the relative selector invalidation map.
|
||||
pub fn relative_selector_invalidation_map(&self) -> &RelativeSelectorInvalidationMap {
|
||||
&self.relative_selector_invalidation_map
|
||||
}
|
||||
|
||||
/// Returns whether the given ElementState bit is relied upon by a selector
|
||||
/// of some rule.
|
||||
#[inline]
|
||||
|
@ -2603,6 +2628,7 @@ impl CascadeData {
|
|||
self.animations.shrink_if_needed();
|
||||
self.custom_property_registrations.shrink_if_needed();
|
||||
self.invalidation_map.shrink_if_needed();
|
||||
self.relative_selector_invalidation_map.shrink_if_needed();
|
||||
self.attribute_dependencies.shrink_if_needed();
|
||||
self.nth_of_attribute_dependencies.shrink_if_needed();
|
||||
self.nth_of_class_dependencies.shrink_if_needed();
|
||||
|
@ -2792,6 +2818,7 @@ impl CascadeData {
|
|||
&rule.selector,
|
||||
quirks_mode,
|
||||
&mut self.invalidation_map,
|
||||
&mut self.relative_selector_invalidation_map,
|
||||
)?;
|
||||
let mut needs_revalidation = false;
|
||||
let mut visitor = StylistSelectorVisitor {
|
||||
|
@ -3256,6 +3283,7 @@ impl CascadeData {
|
|||
fn clear(&mut self) {
|
||||
self.clear_cascade_data();
|
||||
self.invalidation_map.clear();
|
||||
self.relative_selector_invalidation_map.clear();
|
||||
self.attribute_dependencies.clear();
|
||||
self.nth_of_attribute_dependencies.clear();
|
||||
self.nth_of_class_dependencies.clear();
|
||||
|
|
|
@ -11,8 +11,10 @@ use dom::{DocumentState, ElementState};
|
|||
use malloc_size_of::MallocSizeOfOps;
|
||||
use nsstring::{nsCString, nsString};
|
||||
use selectors::matching::{MatchingForInvalidation, SelectorCaches};
|
||||
use selectors::Element;
|
||||
use servo_arc::{Arc, ArcBorrow};
|
||||
use smallvec::SmallVec;
|
||||
use style::invalidation::element::element_wrapper::{ElementWrapper, ElementSnapshot};
|
||||
use style::values::generics::color::ColorMixFlags;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt::Write;
|
||||
|
@ -94,6 +96,11 @@ use style::gecko_bindings::sugar::refptr::RefPtr;
|
|||
use style::global_style_data::{
|
||||
GlobalStyleData, PlatformThreadHandle, StyleThreadPool, GLOBAL_STYLE_DATA, STYLE_THREAD_POOL,
|
||||
};
|
||||
use style::invalidation::element::invalidation_map::RelativeSelectorInvalidationMap;
|
||||
use style::invalidation::element::invalidator::InvalidationResult;
|
||||
use style::invalidation::element::relative_selector::{
|
||||
RelativeSelectorDependencyCollector, RelativeSelectorInvalidator,
|
||||
};
|
||||
use style::invalidation::element::restyle_hints::RestyleHint;
|
||||
use style::invalidation::stylesheets::RuleChangeKind;
|
||||
use style::media_queries::MediaList;
|
||||
|
@ -5793,7 +5800,6 @@ pub extern "C" fn Servo_ResolveStyleLazily(
|
|||
can_use_cache: bool,
|
||||
raw_data: &PerDocumentStyleData,
|
||||
) -> Strong<ComputedValues> {
|
||||
use selectors::Element;
|
||||
debug_assert!(!snapshots.is_null());
|
||||
let global_style_data = &*GLOBAL_STYLE_DATA;
|
||||
let guard = global_style_data.shared_lock.read();
|
||||
|
@ -6740,6 +6746,200 @@ pub extern "C" fn Servo_StyleSet_MightHaveNthOfAttributeDependency(
|
|||
}
|
||||
}
|
||||
|
||||
fn relative_selector_invalidated_at(element: GeckoElement, result: &InvalidationResult) {
|
||||
if result.has_invalidated_siblings() {
|
||||
let parent = element
|
||||
.traversal_parent()
|
||||
.expect("How could we invalidate siblings without a common parent?");
|
||||
unsafe {
|
||||
parent.set_dirty_descendants();
|
||||
bindings::Gecko_NoteDirtySubtreeForInvalidation(parent.0);
|
||||
}
|
||||
} else if result.has_invalidated_descendants() {
|
||||
unsafe { bindings::Gecko_NoteDirtySubtreeForInvalidation(element.0) };
|
||||
} else if result.has_invalidated_self() {
|
||||
unsafe { bindings::Gecko_NoteDirtyElement(element.0) };
|
||||
}
|
||||
}
|
||||
|
||||
fn add_relative_selector_attribute_dependency<'a>(
|
||||
element: &GeckoElement<'a>,
|
||||
scope: &Option<GeckoElement<'a>>,
|
||||
invalidation_map: &'a RelativeSelectorInvalidationMap,
|
||||
attribute: &AtomIdent,
|
||||
collector: &mut RelativeSelectorDependencyCollector<'a, GeckoElement<'a>>,
|
||||
) {
|
||||
match invalidation_map
|
||||
.map
|
||||
.other_attribute_affecting_selectors
|
||||
.get(attribute)
|
||||
{
|
||||
Some(v) => {
|
||||
for dependency in v {
|
||||
collector.add_dependency(dependency, *element, *scope);
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
};
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorIDDependency(
|
||||
raw_data: &PerDocumentStyleData,
|
||||
element: &RawGeckoElement,
|
||||
old_id: *mut nsAtom,
|
||||
new_id: *mut nsAtom,
|
||||
) {
|
||||
let data = raw_data.borrow();
|
||||
let element = GeckoElement(element);
|
||||
|
||||
let quirks_mode: QuirksMode = data.stylist.quirks_mode();
|
||||
let invalidator = RelativeSelectorInvalidator {
|
||||
element,
|
||||
quirks_mode,
|
||||
invalidated: relative_selector_invalidated_at,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
invalidator.invalidate_relative_selectors_for_this(
|
||||
&data.stylist,
|
||||
|element, scope, data, quirks_mode, collector| {
|
||||
let invalidation_map = data.relative_selector_invalidation_map();
|
||||
relative_selector_dependencies_for_id(
|
||||
old_id,
|
||||
new_id,
|
||||
element,
|
||||
scope,
|
||||
quirks_mode,
|
||||
&invalidation_map,
|
||||
collector
|
||||
);
|
||||
add_relative_selector_attribute_dependency(
|
||||
element,
|
||||
&scope,
|
||||
invalidation_map,
|
||||
&AtomIdent(atom!("id")),
|
||||
collector,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorClassDependency(
|
||||
raw_data: &PerDocumentStyleData,
|
||||
element: &RawGeckoElement,
|
||||
snapshots: &ServoElementSnapshotTable,
|
||||
) {
|
||||
let data = raw_data.borrow();
|
||||
let element = GeckoElement(element);
|
||||
let quirks_mode: QuirksMode = data.stylist.quirks_mode();
|
||||
let invalidator = RelativeSelectorInvalidator {
|
||||
element,
|
||||
quirks_mode,
|
||||
invalidated: relative_selector_invalidated_at,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
invalidator.invalidate_relative_selectors_for_this(
|
||||
&data.stylist,
|
||||
|element, scope, data, quirks_mode, mut collector| {
|
||||
let invalidation_map = data.relative_selector_invalidation_map();
|
||||
|
||||
relative_selector_dependencies_for_class(
|
||||
&classes_changed(element, snapshots),
|
||||
&element,
|
||||
scope,
|
||||
quirks_mode,
|
||||
invalidation_map,
|
||||
collector
|
||||
);
|
||||
add_relative_selector_attribute_dependency(
|
||||
element,
|
||||
&scope,
|
||||
invalidation_map,
|
||||
&AtomIdent(atom!("class")),
|
||||
&mut collector,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorAttributeDependency(
|
||||
raw_data: &PerDocumentStyleData,
|
||||
element: &RawGeckoElement,
|
||||
local_name: *mut nsAtom,
|
||||
) {
|
||||
let data = raw_data.borrow();
|
||||
let element = GeckoElement(element);
|
||||
|
||||
let quirks_mode: QuirksMode = data.stylist.quirks_mode();
|
||||
unsafe {
|
||||
AtomIdent::with(local_name, |atom| {
|
||||
let invalidator = RelativeSelectorInvalidator {
|
||||
element,
|
||||
quirks_mode,
|
||||
invalidated: relative_selector_invalidated_at,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
invalidator.invalidate_relative_selectors_for_this(
|
||||
&data.stylist,
|
||||
|element, scope, data, _quirks_mode, mut collector| {
|
||||
add_relative_selector_attribute_dependency(
|
||||
element,
|
||||
&scope,
|
||||
data.relative_selector_invalidation_map(),
|
||||
atom,
|
||||
&mut collector,
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorStateDependency(
|
||||
raw_data: &PerDocumentStyleData,
|
||||
element: &RawGeckoElement,
|
||||
state: u64,
|
||||
) {
|
||||
let element = GeckoElement(element);
|
||||
|
||||
let state = match ElementState::from_bits(state) {
|
||||
Some(state) => state,
|
||||
None => return,
|
||||
};
|
||||
let data = raw_data.borrow();
|
||||
let quirks_mode: QuirksMode = data.stylist.quirks_mode();
|
||||
|
||||
let invalidator = RelativeSelectorInvalidator {
|
||||
element,
|
||||
quirks_mode,
|
||||
invalidated: relative_selector_invalidated_at,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
invalidator.invalidate_relative_selectors_for_this(
|
||||
&data.stylist,
|
||||
|element, scope, data, quirks_mode, collector| {
|
||||
let invalidation_map = data.relative_selector_invalidation_map();
|
||||
invalidation_map
|
||||
.map
|
||||
.state_affecting_selectors
|
||||
.lookup_with_additional(*element, quirks_mode, None, &[], state, |dependency| {
|
||||
if !dependency.state.intersects(state) {
|
||||
return true;
|
||||
}
|
||||
collector.add_dependency(&dependency.dep, *element, *scope);
|
||||
true
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_HasStateDependency(
|
||||
raw_data: &PerDocumentStyleData,
|
||||
|
@ -6889,6 +7089,106 @@ pub extern "C" fn Servo_CssUrl_IsLocalRef(url: &url::CssUrl) -> bool {
|
|||
url.is_fragment()
|
||||
}
|
||||
|
||||
fn relative_selector_dependencies_for_id<'a>(
|
||||
old_id: *const nsAtom,
|
||||
new_id: *const nsAtom,
|
||||
element: &GeckoElement<'a>,
|
||||
scope: &Option<GeckoElement<'a>>,
|
||||
quirks_mode: QuirksMode,
|
||||
invalidation_map: &'a RelativeSelectorInvalidationMap,
|
||||
collector: &mut RelativeSelectorDependencyCollector<'a, GeckoElement<'a>>,
|
||||
) {
|
||||
[old_id, new_id].iter().filter(|id| !id.is_null()).for_each(|id| {
|
||||
unsafe {
|
||||
AtomIdent::with(*id, |atom| {
|
||||
match invalidation_map.map.id_to_selector.get(atom, quirks_mode) {
|
||||
Some(v) => {
|
||||
for dependency in v {
|
||||
collector.add_dependency(dependency, *element, *scope);
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
};
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn relative_selector_dependencies_for_class<'a>(
|
||||
classes_changed: &SmallVec<[Atom; 8]>,
|
||||
element: &GeckoElement<'a>,
|
||||
scope: &Option<GeckoElement<'a>>,
|
||||
quirks_mode: QuirksMode,
|
||||
invalidation_map: &'a RelativeSelectorInvalidationMap,
|
||||
collector: &mut RelativeSelectorDependencyCollector<'a, GeckoElement<'a>>,
|
||||
) {
|
||||
classes_changed.iter().for_each(|atom| {
|
||||
match invalidation_map
|
||||
.map
|
||||
.class_to_selector
|
||||
.get(atom, quirks_mode)
|
||||
{
|
||||
Some(v) => {
|
||||
for dependency in v {
|
||||
collector.add_dependency(dependency, *element, *scope);
|
||||
}
|
||||
},
|
||||
None => (),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn process_relative_selector_invalidations(
|
||||
element: &GeckoElement,
|
||||
snapshot_table: &ServoElementSnapshotTable,
|
||||
data: &PerDocumentStyleDataImpl,
|
||||
) {
|
||||
let snapshot = match snapshot_table.get(element) {
|
||||
None => return,
|
||||
Some(s) => s,
|
||||
};
|
||||
let state_changes = ElementWrapper::new(*element, snapshot_table).state_changes();
|
||||
let classes_changed = classes_changed(element, snapshot_table);
|
||||
|
||||
let quirks_mode: QuirksMode = data.stylist.quirks_mode();
|
||||
let invalidator = RelativeSelectorInvalidator {
|
||||
element: *element,
|
||||
quirks_mode,
|
||||
invalidated: relative_selector_invalidated_at,
|
||||
_marker: std::marker::PhantomData,
|
||||
};
|
||||
|
||||
invalidator.invalidate_relative_selectors_for_this(
|
||||
&data.stylist,
|
||||
|element, scope, data, quirks_mode, collector| {
|
||||
let invalidation_map = data.relative_selector_invalidation_map();
|
||||
if snapshot.id_changed() {
|
||||
relative_selector_dependencies_for_id(
|
||||
element.id().map(|id| id.as_ptr().cast_const()).unwrap_or(ptr::null()),
|
||||
snapshot.id_attr().map(|id| id.as_ptr().cast_const()).unwrap_or(ptr::null()),
|
||||
element,
|
||||
scope,
|
||||
quirks_mode,
|
||||
invalidation_map,
|
||||
collector,
|
||||
);
|
||||
}
|
||||
relative_selector_dependencies_for_class(&classes_changed, element, scope, quirks_mode, invalidation_map, collector);
|
||||
snapshot.each_attr_changed(|attr| add_relative_selector_attribute_dependency(element, &scope, invalidation_map, attr, collector));
|
||||
invalidation_map
|
||||
.map
|
||||
.state_affecting_selectors
|
||||
.lookup_with_additional(*element, quirks_mode, None, &[], state_changes, |dependency| {
|
||||
if !dependency.state.intersects(state_changes) {
|
||||
return true;
|
||||
}
|
||||
collector.add_dependency(&dependency.dep, *element, *scope);
|
||||
true
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_ProcessInvalidations(
|
||||
set: &PerDocumentStyleData,
|
||||
|
@ -6901,8 +7201,16 @@ pub extern "C" fn Servo_ProcessInvalidations(
|
|||
debug_assert!(element.has_snapshot());
|
||||
debug_assert!(!element.handled_snapshot());
|
||||
|
||||
let snapshot_table = unsafe { &*snapshots };
|
||||
let per_doc_data = set.borrow();
|
||||
process_relative_selector_invalidations(&element, snapshot_table, &per_doc_data);
|
||||
|
||||
let mut data = element.mutate_data();
|
||||
debug_assert!(data.is_some());
|
||||
if data.is_none() {
|
||||
// Snapshot for unstyled element is really only meant for relative selector
|
||||
// invalidation, so this is fine.
|
||||
return;
|
||||
}
|
||||
|
||||
let global_style_data = &*GLOBAL_STYLE_DATA;
|
||||
let guard = global_style_data.shared_lock.read();
|
||||
|
@ -6912,7 +7220,7 @@ pub extern "C" fn Servo_ProcessInvalidations(
|
|||
&guard,
|
||||
&per_doc_data.stylist,
|
||||
TraversalFlags::empty(),
|
||||
unsafe { &*snapshots },
|
||||
snapshot_table,
|
||||
);
|
||||
let mut data = data.as_mut().map(|d| &mut **d);
|
||||
|
||||
|
|
|
@ -1,19 +1,4 @@
|
|||
[attribute-or-elemental-selectors-in-has.html]
|
||||
[add .child to #div_child: div#div_subject.color]
|
||||
expected: FAIL
|
||||
|
||||
[add .descendant to #div_child: div#div_subject.color]
|
||||
expected: FAIL
|
||||
|
||||
[add .descendant to #div_grandchild: div#div_subject.color]
|
||||
expected: FAIL
|
||||
|
||||
[set descendant to #div_grandchild[attrname\]: div#div_subject.color]
|
||||
expected: FAIL
|
||||
|
||||
[change #div_grandchild to #div_descendant: div#div_subject.color]
|
||||
expected: FAIL
|
||||
|
||||
[add descendant to #div_subject: div#div_subject.color]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[defined-in-has.html]
|
||||
[Test :has() invalidation with :defined pseudo-class]
|
||||
expected: FAIL
|
|
@ -1,7 +1,3 @@
|
|||
[fullscreen-pseudo-class-in-has.html]
|
||||
expected: ERROR
|
||||
[:fullscreen pseudo-class invalidation with requestFullscreen + exitFullscreen]
|
||||
expected: FAIL
|
||||
|
||||
[:fullscreen pseudo-class invalidation with requestFullscreen + remove]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,20 +1,3 @@
|
|||
[has-complexity.html]
|
||||
expected:
|
||||
if (os == "linux") and not debug and fission and (processor == "x86_64"): [OK, TIMEOUT]
|
||||
[After appending 25000 elements. This should not time out.]
|
||||
expected: FAIL
|
||||
|
||||
[After appending another 25000 elements. This should not time out.]
|
||||
expected: FAIL
|
||||
|
||||
[After appending div with 25000 elements. This should not time out.]
|
||||
expected: FAIL
|
||||
|
||||
[After removing div with 25000 elements. This should not time out.]
|
||||
expected: FAIL
|
||||
|
||||
[After removing 25000 elements one-by-one. This should not time out.]
|
||||
expected: FAIL
|
||||
|
||||
[After removing the remaining elements. This should not time out.]
|
||||
expected: FAIL
|
||||
[OK, TIMEOUT]
|
||||
|
|
|
@ -1,22 +1,4 @@
|
|||
[has-in-adjacent-position.html]
|
||||
[add .test to previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to subject]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div.test before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -83,102 +65,36 @@
|
|||
[insert tree div>div.test after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -200,9 +116,6 @@
|
|||
[insert element div>div[test_attr\] before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element in the tree inserted again before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -230,9 +143,6 @@
|
|||
[insert element div>div[test_attr\] before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element in the tree inserted again after previous_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -287,8 +197,68 @@
|
|||
[insert element div>div[test_attr\] after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[remove the class 'test' from the element in the tree inserted before subject]
|
||||
[add the class 'test' again to the element inserted before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[remove the class 'test' from the element in the tree inserted after previous_sibling]
|
||||
[add the class 'test' to the element inserted again before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,27 +1,6 @@
|
|||
[has-in-ancestor-position.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[add .test to subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to subject]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div.test before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -67,9 +46,6 @@
|
|||
[insert element div.test after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert tree div>div.test before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[insert tree div>div.test before subject]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -112,150 +88,51 @@
|
|||
[insert tree div>div.test after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_ancestor]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_ancestor]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject_ancestor]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element in the tree inserted again before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div>div[test_attr\] before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted before subject]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -381,3 +258,105 @@
|
|||
|
||||
[insert element div>div[test_attr\] after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_ancestor]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_ancestor]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert tree div>div.test before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element in the tree inserted again before subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div>div[test_attr\] before subject_parent]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
[has-in-parent-position.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[add .test to subject]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div.test before subject]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -52,66 +43,24 @@
|
|||
[insert tree div>div.test after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -177,3 +126,45 @@
|
|||
|
||||
[insert element div>div[test_attr\] after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_parent]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject_descendant]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,22 +1,4 @@
|
|||
[has-in-sibling-position.html]
|
||||
[add .test to previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to subject]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div.test before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -83,117 +65,39 @@
|
|||
[insert tree div>div.test after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[remove the class 'test' from the element inserted before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div[test_attr\] after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element in the tree inserted again before previous_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -212,9 +116,6 @@
|
|||
[insert element div>div[test_attr\] before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element in the tree inserted again before subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -242,12 +143,6 @@
|
|||
[insert element div>div[test_attr\] before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[remove the class 'test' from the element inserted after previous_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element in the tree inserted again after previous_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element in the tree inserted after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -302,11 +197,68 @@
|
|||
[insert element div>div[test_attr\] after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[remove the class 'test' from the element in the tree inserted before previous_sibling]
|
||||
[add the class 'test' again to the element inserted before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[remove the class 'test' from the element in the tree inserted before subject]
|
||||
[add the class 'test' to the element inserted again before previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[remove the class 'test' from the element in the tree inserted after previous_sibling]
|
||||
[add the class 'test' again to the element inserted before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again before next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after previous_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after subject]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' again to the element inserted after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add the class 'test' to the element inserted again after next_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,27 +1,6 @@
|
|||
[has-sibling.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[add .test to first_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to second_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to third_sibling]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to first_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to first_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to third_sibling_child]
|
||||
expected: FAIL
|
||||
|
||||
[add .test to third_sibling_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div.test before first_sibling]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
[has-with-not.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[remove .test to subject_child]
|
||||
expected: FAIL
|
||||
|
||||
[remove .test to subject_descendant]
|
||||
expected: FAIL
|
||||
|
||||
[insert element div before subject_child]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
[has-with-pseudo-class.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[Set checked on checkbox, testing subject]
|
||||
expected: FAIL
|
||||
|
||||
[Set select on option]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on checkbox, testing subject]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on checkbox, testing subject3]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on option, testing subject]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on option, testing subject3]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on optgroup, testing subject]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on optgroup, testing subject2]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on optgroup, testing subject3]
|
||||
expected: FAIL
|
||||
|
||||
[Set disabled on optgroup, testing subject4]
|
||||
expected: FAIL
|
||||
|
||||
[Set value of text_input, testing subject]
|
||||
expected: FAIL
|
||||
|
||||
[Set value of text_input, testing subject2]
|
||||
expected: FAIL
|
||||
|
||||
[Set value of text_input, testing subject3]
|
||||
expected: FAIL
|
||||
|
||||
[Set value of text_input, testing subject4]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[lang-pseudo-class-in-has-xhtml.xhtml]
|
||||
expected: FAIL
|
|
@ -1,2 +0,0 @@
|
|||
[lang-pseudo-class-in-has.html]
|
||||
expected: FAIL
|
|
@ -1,15 +1,11 @@
|
|||
[modal-pseudo-class-in-has.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [ERROR, TIMEOUT, OK]
|
||||
ERROR
|
||||
[:modal pseudo-class invalidation with showModal + close]
|
||||
expected: FAIL
|
||||
|
||||
[:modal pseudo-class invalidation with showModal + remove]
|
||||
expected: FAIL
|
||||
|
||||
[:modal pseudo-class invalidation with requestFullscreen + exitFullscreen]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
||||
[:modal pseudo-class invalidation with requestFullscreen + remove]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[subject-has-invalidation-with-display-none-anchor-element.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[CSS Selectors Invalidation: subject :has() invalidation with display: none anchor element]
|
||||
expected: FAIL
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Selector Invalidation: :has() affected by unstyled elements</title>
|
||||
<link rel="author" title="David Shin" href="mailto:dshin@mozilla.com">
|
||||
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
|
||||
<style>
|
||||
div, main { color: grey }
|
||||
.none { display: none; }
|
||||
#subject:has(.test) { color: red; }
|
||||
#subject:has(~ #sibling .test) { color: green; }
|
||||
</style>
|
||||
|
||||
<main id=main>
|
||||
<div id=subject>
|
||||
<div id=subject_child class="none">
|
||||
<div id=subject_descendant></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id=sibling class="none">
|
||||
<div id=sibling_child>
|
||||
<div id=sibling_descendant></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const grey = 'rgb(128, 128, 128)';
|
||||
const red = 'rgb(255, 0, 0)';
|
||||
const orangered = 'rgb(255, 69, 0)';
|
||||
const green = 'rgb(0, 128, 0)';
|
||||
const lightgreen = 'rgb(144, 238, 144)';
|
||||
const colors = {
|
||||
grey: {
|
||||
classTest: grey,
|
||||
},
|
||||
red: {
|
||||
classTest: red,
|
||||
},
|
||||
green: {
|
||||
classTest: green,
|
||||
},
|
||||
};
|
||||
|
||||
function testColor(test_name, color) {
|
||||
test(function() {
|
||||
assert_equals(getComputedStyle(subject).color, color);
|
||||
}, test_name);
|
||||
}
|
||||
|
||||
function testClassChange(element, expectedColorName)
|
||||
{
|
||||
const expectedColorForClassTest = colors[expectedColorName].classTest;
|
||||
element.classList.add('test');
|
||||
testColor(`add .test to ${element.id}`, expectedColorForClassTest);
|
||||
element.classList.remove('test');
|
||||
testColor(`remove .test from ${element.id}`, grey);
|
||||
}
|
||||
|
||||
testColor('Initial color', grey);
|
||||
|
||||
testClassChange(subject_descendant, 'red');
|
||||
testClassChange(sibling_descendant, 'green');
|
||||
|
||||
</script>
|
|
@ -27,7 +27,12 @@
|
|||
</div>
|
||||
<script>
|
||||
test(function() {
|
||||
let input = null;
|
||||
this.add_cleanup(() => {
|
||||
if (input) {
|
||||
// Need to put the input back for the rest of the tests.
|
||||
subject.prepend(input);
|
||||
}
|
||||
checkme.checked = false;
|
||||
});
|
||||
checkme.checked = false;
|
||||
|
@ -39,13 +44,14 @@
|
|||
checkme.indeterminate = true;
|
||||
assert_equals(getComputedStyle(subject).color, "rgb(154, 205, 50)",
|
||||
"ancestor should be yellowgreen");
|
||||
const input = checkme;
|
||||
input = checkme;
|
||||
checkme.remove();
|
||||
input.indeterminate = false;
|
||||
assert_equals(getComputedStyle(subject).color, "rgb(0, 0, 0)",
|
||||
"ancestor should be black");
|
||||
|
||||
subject.prepend(input);
|
||||
input = null;
|
||||
checkme.checked = true;
|
||||
assert_equals(getComputedStyle(subject).color, "rgb(0, 128, 0)",
|
||||
"ancestor should be green");
|
||||
|
|
Загрузка…
Ссылка в новой задаче