Bug 1792501: Part 4 - Basic :has invalidation. r=emilio,layout-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D185677
This commit is contained in:
David Shin 2023-09-14 22:21:25 +00:00
Родитель aa280d1e29
Коммит 018c4ad794
30 изменённых файлов: 1922 добавлений и 708 удалений

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

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