mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1818151 - Record attribute dependencies within the selector list of :nth-child(... of <selector list>) r=emilio
There are separate filters for IDs, classes, attribute local names, and element state. Also, we invalidate siblings of elements matched against the selector list of :nth-child(... of <selector list>) by marking matched elements with NODE_HAS_SLOW_SELECTOR_NTH_OF. The only remaining invalidation case invalidation case is `:nth-child(An+B of :has())` (bug 1818155), which should not block shipping `layout.css.nth-child-of.enabled`, because :has(...) is still being implemented (bug 418039). Depends on D172352 Differential Revision: https://phabricator.services.mozilla.com/D171936
This commit is contained in:
parent
e6a03adbf7
commit
4f5c70443d
@ -163,41 +163,48 @@ enum {
|
||||
// child's later siblings must also be restyled.
|
||||
NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS = NODE_FLAG_BIT(9),
|
||||
|
||||
// A child of this node might be matched by :nth-child(.. of <selector>) or
|
||||
// :nth-last-child(.. of <selector>). If a DOM mutation may have caused the
|
||||
// selector to either match or no longer match that child, the child's
|
||||
// siblings are restyled.
|
||||
NODE_HAS_SLOW_SELECTOR_NTH_OF = NODE_FLAG_BIT(10),
|
||||
|
||||
NODE_ALL_SELECTOR_FLAGS = NODE_HAS_EMPTY_SELECTOR | NODE_HAS_SLOW_SELECTOR |
|
||||
NODE_HAS_EDGE_CHILD_SELECTOR |
|
||||
NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS,
|
||||
NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS |
|
||||
NODE_HAS_SLOW_SELECTOR_NTH_OF,
|
||||
|
||||
// This node needs to go through frame construction to get a frame (or
|
||||
// undisplayed entry).
|
||||
NODE_NEEDS_FRAME = NODE_FLAG_BIT(10),
|
||||
NODE_NEEDS_FRAME = NODE_FLAG_BIT(11),
|
||||
|
||||
// At least one descendant in the flattened tree has NODE_NEEDS_FRAME set.
|
||||
// This should be set on every node on the flattened tree path between the
|
||||
// node(s) with NODE_NEEDS_FRAME and the root content.
|
||||
NODE_DESCENDANTS_NEED_FRAMES = NODE_FLAG_BIT(11),
|
||||
NODE_DESCENDANTS_NEED_FRAMES = NODE_FLAG_BIT(12),
|
||||
|
||||
// Set if the node has the accesskey attribute set.
|
||||
NODE_HAS_ACCESSKEY = NODE_FLAG_BIT(12),
|
||||
NODE_HAS_ACCESSKEY = NODE_FLAG_BIT(13),
|
||||
|
||||
// Set if the node has right-to-left directionality
|
||||
NODE_HAS_DIRECTION_RTL = NODE_FLAG_BIT(13),
|
||||
NODE_HAS_DIRECTION_RTL = NODE_FLAG_BIT(14),
|
||||
|
||||
// Set if the node has left-to-right directionality
|
||||
NODE_HAS_DIRECTION_LTR = NODE_FLAG_BIT(14),
|
||||
NODE_HAS_DIRECTION_LTR = NODE_FLAG_BIT(15),
|
||||
|
||||
NODE_ALL_DIRECTION_FLAGS = NODE_HAS_DIRECTION_LTR | NODE_HAS_DIRECTION_RTL,
|
||||
|
||||
NODE_HAS_BEEN_IN_UA_WIDGET = NODE_FLAG_BIT(15),
|
||||
NODE_HAS_BEEN_IN_UA_WIDGET = NODE_FLAG_BIT(16),
|
||||
|
||||
// Set if the node has a nonce value and a header delivered CSP.
|
||||
NODE_HAS_NONCE_AND_HEADER_CSP = NODE_FLAG_BIT(16),
|
||||
NODE_HAS_NONCE_AND_HEADER_CSP = NODE_FLAG_BIT(17),
|
||||
|
||||
NODE_KEEPS_DOMARENA = NODE_FLAG_BIT(17),
|
||||
NODE_KEEPS_DOMARENA = NODE_FLAG_BIT(18),
|
||||
|
||||
NODE_MAY_HAVE_ELEMENT_CHILDREN = NODE_FLAG_BIT(18),
|
||||
NODE_MAY_HAVE_ELEMENT_CHILDREN = NODE_FLAG_BIT(19),
|
||||
|
||||
// Remaining bits are node type specific.
|
||||
NODE_TYPE_SPECIFIC_BITS_OFFSET = 19
|
||||
NODE_TYPE_SPECIFIC_BITS_OFFSET = 20
|
||||
};
|
||||
|
||||
// Make sure we have space for our bits
|
||||
|
@ -3362,6 +3362,23 @@ void RestyleManager::ElementStateChanged(Element* aElement,
|
||||
ServoElementSnapshot& snapshot = SnapshotFor(*aElement);
|
||||
ElementState previousState = aElement->StyleState() ^ aChangedBits;
|
||||
snapshot.AddState(previousState);
|
||||
|
||||
MaybeRestyleForNthOfState(*StyleSet(), aElement, aChangedBits);
|
||||
}
|
||||
|
||||
void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
|
||||
Element* aChild,
|
||||
ElementState aChangedBits) {
|
||||
const auto* parentNode = aChild->GetParentNode();
|
||||
MOZ_ASSERT(parentNode);
|
||||
const auto parentFlags = parentNode->GetFlags();
|
||||
if (!(parentFlags & NODE_HAS_SLOW_SELECTOR_NTH_OF)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) {
|
||||
RestyleSiblings(aChild, parentFlags);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool AttributeInfluencesOtherPseudoClassState(
|
||||
@ -3492,6 +3509,8 @@ void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
|
||||
|
||||
changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
|
||||
|
||||
MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue);
|
||||
|
||||
if (aAttribute == nsGkAtoms::style) {
|
||||
restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
|
||||
} else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) {
|
||||
@ -3539,6 +3558,59 @@ void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
|
||||
}
|
||||
}
|
||||
|
||||
void RestyleManager::RestyleSiblings(
|
||||
Element* aChild, nsBaseContentList::FlagsType aParentFlags) {
|
||||
const DebugOnly<nsINode*> parentNode = aChild->GetParentNode();
|
||||
MOZ_ASSERT(parentNode->IsElement() || parentNode->IsShadowRoot());
|
||||
|
||||
DebugOnly<bool> restyledSiblings = false;
|
||||
// NODE_HAS_SLOW_SELECTOR typically indicates restyling the parent, but since
|
||||
// we know we're restyling for :nth-last-child(.. of <selector>), we can
|
||||
// restyle only previous siblings without under-invalidating.
|
||||
if (aParentFlags & NODE_HAS_SLOW_SELECTOR) {
|
||||
RestylePreviousSiblings(aChild->GetPreviousSibling());
|
||||
restyledSiblings = true;
|
||||
}
|
||||
if (aParentFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
|
||||
RestyleSiblingsStartingWith(aChild->GetNextSibling());
|
||||
restyledSiblings = true;
|
||||
}
|
||||
MOZ_ASSERT(restyledSiblings,
|
||||
"How can we restyle siblings without a slow selector flag?");
|
||||
}
|
||||
|
||||
void RestyleManager::MaybeRestyleForNthOfAttribute(
|
||||
Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
|
||||
const auto* parentNode = aChild->GetParentNode();
|
||||
MOZ_ASSERT(parentNode);
|
||||
const auto parentFlags = parentNode->GetFlags();
|
||||
if (!(parentFlags & NODE_HAS_SLOW_SELECTOR_NTH_OF)) {
|
||||
return;
|
||||
}
|
||||
if (!aChild->HasServoData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool mightHaveNthOfDependency;
|
||||
auto& styleSet = *StyleSet();
|
||||
if (aAttribute == nsGkAtoms::id) {
|
||||
auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
|
||||
? aOldValue->GetAtomValue()
|
||||
: nullptr;
|
||||
mightHaveNthOfDependency =
|
||||
styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID());
|
||||
} else if (aAttribute == nsGkAtoms::_class) {
|
||||
mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild);
|
||||
} else {
|
||||
mightHaveNthOfDependency =
|
||||
styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute);
|
||||
}
|
||||
|
||||
if (mightHaveNthOfDependency) {
|
||||
RestyleSiblings(aChild, parentFlags);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -360,6 +360,15 @@ class RestyleManager {
|
||||
void ProcessAllPendingAttributeAndStateInvalidations();
|
||||
|
||||
void ElementStateChanged(Element*, dom::ElementState);
|
||||
|
||||
/**
|
||||
* Posts restyle hints for siblings of an element and their descendants if the
|
||||
* element's parent has NODE_HAS_SLOW_SELECTOR_NTH_OF and the element has a
|
||||
* relevant state dependency.
|
||||
*/
|
||||
void MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet, dom::Element* aChild,
|
||||
dom::ElementState aChangedBits);
|
||||
|
||||
void AttributeWillChange(Element* aElement, int32_t aNameSpaceID,
|
||||
nsAtom* aAttribute, int32_t aModType);
|
||||
void ClassAttributeWillBeChangedBySMIL(dom::Element* aElement);
|
||||
@ -367,6 +376,20 @@ class RestyleManager {
|
||||
nsAtom* aAttribute, int32_t aModType,
|
||||
const nsAttrValue* aOldValue);
|
||||
|
||||
/**
|
||||
* Restyle an element's previous and/or next siblings.
|
||||
*/
|
||||
void RestyleSiblings(dom::Element* aChild,
|
||||
nsBaseContentList::FlagsType aParentFlags);
|
||||
|
||||
/**
|
||||
* Posts restyle hints for siblings of an element and their descendants if the
|
||||
* element's parent has NODE_HAS_SLOW_SELECTOR_NTH_OF and the element has a
|
||||
* relevant attribute dependency.
|
||||
*/
|
||||
void MaybeRestyleForNthOfAttribute(dom::Element* aChild, nsAtom* aAttribute,
|
||||
const nsAttrValue* aOldValue);
|
||||
|
||||
// This is only used to reparent things when moving them in/out of the
|
||||
// ::first-line.
|
||||
void ReparentComputedStyleForFirstLine(nsIFrame*);
|
||||
|
@ -1395,12 +1395,36 @@ bool ServoStyleSet::MightHaveAttributeDependency(const Element& aElement,
|
||||
aAttribute);
|
||||
}
|
||||
|
||||
bool ServoStyleSet::MightHaveNthOfIDDependency(const Element& aElement,
|
||||
nsAtom* aOldID,
|
||||
nsAtom* aNewID) const {
|
||||
return Servo_StyleSet_MightHaveNthOfIDDependency(mRawSet.get(), &aElement,
|
||||
aOldID, aNewID);
|
||||
}
|
||||
|
||||
bool ServoStyleSet::MightHaveNthOfClassDependency(const Element& aElement) {
|
||||
return Servo_StyleSet_MightHaveNthOfClassDependency(mRawSet.get(), &aElement,
|
||||
&Snapshots());
|
||||
}
|
||||
|
||||
bool ServoStyleSet::MightHaveNthOfAttributeDependency(
|
||||
const Element& aElement, nsAtom* aAttribute) const {
|
||||
return Servo_StyleSet_MightHaveNthOfAttributeDependency(
|
||||
mRawSet.get(), &aElement, aAttribute);
|
||||
}
|
||||
|
||||
bool ServoStyleSet::HasStateDependency(const Element& aElement,
|
||||
dom::ElementState aState) const {
|
||||
return Servo_StyleSet_HasStateDependency(mRawSet.get(), &aElement,
|
||||
aState.GetInternalValue());
|
||||
}
|
||||
|
||||
bool ServoStyleSet::HasNthOfStateDependency(const Element& aElement,
|
||||
dom::ElementState aState) const {
|
||||
return Servo_StyleSet_HasNthOfStateDependency(mRawSet.get(), &aElement,
|
||||
aState.GetInternalValue());
|
||||
}
|
||||
|
||||
bool ServoStyleSet::HasDocumentStateDependency(
|
||||
dom::DocumentState aState) const {
|
||||
return Servo_StyleSet_HasDocumentStateDependency(mRawSet.get(),
|
||||
|
@ -461,6 +461,26 @@ class ServoStyleSet {
|
||||
bool MightHaveAttributeDependency(const dom::Element&,
|
||||
nsAtom* aAttribute) const;
|
||||
|
||||
/**
|
||||
* Returns true if a modification to an attribute with the specified local
|
||||
* name might require us to restyle the element's siblings.
|
||||
*/
|
||||
bool MightHaveNthOfAttributeDependency(const dom::Element&,
|
||||
nsAtom* aAttribute) const;
|
||||
|
||||
/**
|
||||
* Returns true if a modification to a class might require us to restyle the
|
||||
* element's siblings.
|
||||
*/
|
||||
bool MightHaveNthOfClassDependency(const dom::Element&);
|
||||
|
||||
/**
|
||||
* Returns true if a modification to an ID might require us to restyle the
|
||||
* element's siblings.
|
||||
*/
|
||||
bool MightHaveNthOfIDDependency(const dom::Element&, nsAtom* aOldID,
|
||||
nsAtom* aNewID) const;
|
||||
|
||||
/**
|
||||
* Returns true if a change in event state on an element might require
|
||||
* us to restyle the element.
|
||||
@ -471,6 +491,12 @@ class ServoStyleSet {
|
||||
*/
|
||||
bool HasStateDependency(const dom::Element&, dom::ElementState) const;
|
||||
|
||||
/**
|
||||
* Returns true if a change in event state on an element might require
|
||||
* us to restyle the element's siblings.
|
||||
*/
|
||||
bool HasNthOfStateDependency(const dom::Element&, dom::ElementState) const;
|
||||
|
||||
/**
|
||||
* Returns true if a change in document state might require us to restyle the
|
||||
* document.
|
||||
|
@ -35,14 +35,24 @@ bitflags! {
|
||||
/// :first-of-type, or :nth-of-type.
|
||||
const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1;
|
||||
|
||||
/// When a DOM mutation occurs on a child that might be matched by
|
||||
/// :nth-last-child(.. of <selector list>), earlier children must be
|
||||
/// restyled, and HAS_SLOW_SELECTOR will be set (which normally
|
||||
/// indicates that all children will be restyled).
|
||||
///
|
||||
/// Similarly, when a DOM mutation occurs on a child that might be
|
||||
/// matched by :nth-child(.. of <selector list>), later children must be
|
||||
/// restyled, and HAS_SLOW_SELECTOR_LATER_SIBLINGS will be set.
|
||||
const HAS_SLOW_SELECTOR_NTH_OF = 1 << 2;
|
||||
|
||||
/// When a child is added or removed from the parent, the first and
|
||||
/// last children must be restyled, because they may match :first-child,
|
||||
/// :last-child, or :only-child.
|
||||
const HAS_EDGE_CHILD_SELECTOR = 1 << 2;
|
||||
const HAS_EDGE_CHILD_SELECTOR = 1 << 3;
|
||||
|
||||
/// The element has an empty selector, so when a child is appended we
|
||||
/// might need to restyle the parent completely.
|
||||
const HAS_EMPTY_SELECTOR = 1 << 3;
|
||||
const HAS_EMPTY_SELECTOR = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +66,7 @@ impl ElementSelectorFlags {
|
||||
pub fn for_parent(self) -> ElementSelectorFlags {
|
||||
self & (ElementSelectorFlags::HAS_SLOW_SELECTOR |
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS |
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF |
|
||||
ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR)
|
||||
}
|
||||
}
|
||||
@ -936,13 +947,17 @@ where
|
||||
let is_edge_child_selector = a == 0 && b == 1 && !is_of_type && selectors.is_empty();
|
||||
|
||||
if context.needs_selector_flags() {
|
||||
element.apply_selector_flags(if is_edge_child_selector {
|
||||
let mut flags = if is_edge_child_selector {
|
||||
ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR
|
||||
} else if is_from_end {
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR
|
||||
} else {
|
||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
|
||||
});
|
||||
};
|
||||
if !selectors.is_empty() {
|
||||
flags |= ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF;
|
||||
}
|
||||
element.apply_selector_flags(flags);
|
||||
}
|
||||
|
||||
if !selectors.is_empty() && !list_matches_complex_selector(selectors, element, context) {
|
||||
|
@ -12,6 +12,7 @@ use crate::builder::{
|
||||
};
|
||||
use crate::context::QuirksMode;
|
||||
use crate::sink::Push;
|
||||
use crate::visitor::SelectorListKind;
|
||||
pub use crate::visitor::SelectorVisitor;
|
||||
use cssparser::parse_nth;
|
||||
use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind};
|
||||
@ -1608,14 +1609,15 @@ impl<Impl: SelectorImpl> Component<Impl> {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
Negation(ref list) | Is(ref list) | Where(ref list) => {
|
||||
if !visitor.visit_selector_list(&list) {
|
||||
let list_kind = SelectorListKind::from_component(self);
|
||||
debug_assert!(!list_kind.is_empty());
|
||||
if !visitor.visit_selector_list(list_kind, &list) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
NthOf(ref nth_of_data) => {
|
||||
if !visitor.visit_selector_list(nth_of_data.selectors()) {
|
||||
if !visitor.visit_selector_list(SelectorListKind::NTH_OF, nth_of_data.selectors()) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
@ -38,7 +38,11 @@ pub trait SelectorVisitor: Sized {
|
||||
/// into the internal selectors if / as needed.
|
||||
///
|
||||
/// The default implementation does this.
|
||||
fn visit_selector_list(&mut self, list: &[Selector<Self::Impl>]) -> bool {
|
||||
fn visit_selector_list(
|
||||
&mut self,
|
||||
_list_kind: SelectorListKind,
|
||||
list: &[Selector<Self::Impl>],
|
||||
) -> bool {
|
||||
for nested in list {
|
||||
if !nested.visit(self) {
|
||||
return false;
|
||||
@ -55,3 +59,53 @@ pub trait SelectorVisitor: Sized {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// The kinds of components the visitor is visiting the selector list of, if any
|
||||
#[derive(Default)]
|
||||
pub struct SelectorListKind: u8 {
|
||||
/// The visitor is inside :not(..)
|
||||
const NEGATION = 1 << 0;
|
||||
/// The visitor is inside :is(..)
|
||||
const IS = 1 << 1;
|
||||
/// The visitor is inside :where(..)
|
||||
const WHERE = 1 << 2;
|
||||
/// The visitor is inside :nth-child(.. of <selector list>) or
|
||||
/// :nth-last-child(.. of <selector list>)
|
||||
const NTH_OF = 1 << 3;
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectorListKind {
|
||||
/// Construct a SelectorListKind for the corresponding component.
|
||||
pub fn from_component<Impl: SelectorImpl>(component: &Component<Impl>) -> Self {
|
||||
match component {
|
||||
Component::Negation(_) => SelectorListKind::NEGATION,
|
||||
Component::Is(_) => SelectorListKind::IS,
|
||||
Component::Where(_) => SelectorListKind::WHERE,
|
||||
Component::NthOf(_) => SelectorListKind::NTH_OF,
|
||||
_ => SelectorListKind::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the visitor is inside :not(..)
|
||||
pub fn in_negation(&self) -> bool {
|
||||
self.intersects(SelectorListKind::NEGATION)
|
||||
}
|
||||
|
||||
/// Whether the visitor is inside :is(..)
|
||||
pub fn in_is(&self) -> bool {
|
||||
self.intersects(SelectorListKind::IS)
|
||||
}
|
||||
|
||||
/// Whether the visitor is inside :where(..)
|
||||
pub fn in_where(&self) -> bool {
|
||||
self.intersects(SelectorListKind::WHERE)
|
||||
}
|
||||
|
||||
/// Whether the visitor is inside :nth-child(.. of <selector list>) or
|
||||
/// :nth-last-child(.. of <selector list>)
|
||||
pub fn in_nth_of(&self) -> bool {
|
||||
self.intersects(SelectorListKind::NTH_OF)
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,17 @@
|
||||
|
||||
//! Element an snapshot common logic.
|
||||
|
||||
use crate::dom::TElement;
|
||||
use crate::gecko_bindings::bindings;
|
||||
use crate::gecko_bindings::structs::{self, nsAtom};
|
||||
use crate::invalidation::element::element_wrapper::ElementSnapshot;
|
||||
use crate::selector_parser::SnapshotMap;
|
||||
use crate::string_cache::WeakAtom;
|
||||
use crate::values::AtomIdent;
|
||||
use crate::Atom;
|
||||
use crate::CaseSensitivityExt;
|
||||
use selectors::attr::CaseSensitivity;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A function that, given an element of type `T`, allows you to get a single
|
||||
/// class or a class list.
|
||||
@ -166,3 +170,27 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of classes that were either added to or removed from the
|
||||
/// element since the snapshot.
|
||||
pub fn classes_changed<E: TElement>(element: &E, snapshots: &SnapshotMap) -> SmallVec<[Atom; 8]> {
|
||||
debug_assert!(element.has_snapshot(), "Why bothering?");
|
||||
let snapshot = snapshots.get(element).expect("has_snapshot lied");
|
||||
if !snapshot.class_changed() {
|
||||
return SmallVec::new();
|
||||
}
|
||||
|
||||
let mut classes_changed = SmallVec::<[Atom; 8]>::new();
|
||||
snapshot.each_class(|c| {
|
||||
if !element.has_class(c, CaseSensitivity::CaseSensitive) {
|
||||
classes_changed.push(c.0.clone());
|
||||
}
|
||||
});
|
||||
element.each_class(|c| {
|
||||
if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) {
|
||||
classes_changed.push(c.0.clone());
|
||||
}
|
||||
});
|
||||
|
||||
classes_changed
|
||||
}
|
||||
|
@ -910,6 +910,9 @@ fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
|
||||
if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
|
||||
gecko_flags |= NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS as u32;
|
||||
}
|
||||
if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF) {
|
||||
gecko_flags |= NODE_HAS_SLOW_SELECTOR_NTH_OF as u32;
|
||||
}
|
||||
if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) {
|
||||
gecko_flags |= NODE_HAS_EDGE_CHILD_SELECTOR as u32;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use dom::{DocumentState, ElementState};
|
||||
use selectors::attr::NamespaceConstraint;
|
||||
use selectors::parser::{Combinator, Component};
|
||||
use selectors::parser::{Selector, SelectorIter};
|
||||
use selectors::visitor::SelectorVisitor;
|
||||
use selectors::visitor::{SelectorListKind, SelectorVisitor};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Mapping between (partial) CompoundSelectors (and the combinator to their
|
||||
@ -428,7 +428,11 @@ impl<'a> SelectorDependencyCollector<'a> {
|
||||
impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> {
|
||||
type Impl = SelectorImpl;
|
||||
|
||||
fn visit_selector_list(&mut self, list: &[Selector<SelectorImpl>]) -> bool {
|
||||
fn visit_selector_list(
|
||||
&mut self,
|
||||
_list_kind: SelectorListKind,
|
||||
list: &[Selector<SelectorImpl>],
|
||||
) -> bool {
|
||||
for selector in list {
|
||||
// Here we cheat a bit: We can visit the rightmost compound with
|
||||
// the "outer" visitor, and it'd be fine. This reduces the amount of
|
||||
|
@ -51,7 +51,7 @@ use selectors::bloom::BloomFilter;
|
||||
use selectors::matching::VisitedHandlingMode;
|
||||
use selectors::matching::{matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags};
|
||||
use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorIter};
|
||||
use selectors::visitor::SelectorVisitor;
|
||||
use selectors::visitor::{SelectorListKind, SelectorVisitor};
|
||||
use selectors::NthIndexCache;
|
||||
use servo_arc::{Arc, ArcBorrow};
|
||||
use smallbitvec::SmallBitVec;
|
||||
@ -1886,15 +1886,41 @@ struct StylistSelectorVisitor<'a> {
|
||||
/// Whether we've past the rightmost compound selector, not counting
|
||||
/// pseudo-elements.
|
||||
passed_rightmost_selector: bool,
|
||||
|
||||
/// Whether the selector needs revalidation for the style sharing cache.
|
||||
needs_revalidation: &'a mut bool,
|
||||
|
||||
/// Flags for which selector list-containing components the visitor is
|
||||
/// inside of, if any
|
||||
in_selector_list_of: SelectorListKind,
|
||||
|
||||
/// The filter with all the id's getting referenced from rightmost
|
||||
/// selectors.
|
||||
mapped_ids: &'a mut PrecomputedHashSet<Atom>,
|
||||
|
||||
/// The filter with the IDs getting referenced from the selector list of
|
||||
/// :nth-child(... of <selector list>) selectors.
|
||||
nth_of_mapped_ids: &'a mut PrecomputedHashSet<Atom>,
|
||||
|
||||
/// The filter with the local names of attributes there are selectors for.
|
||||
attribute_dependencies: &'a mut PrecomputedHashSet<LocalName>,
|
||||
|
||||
/// The filter with the classes getting referenced from the selector list of
|
||||
/// :nth-child(... of <selector list>) selectors.
|
||||
nth_of_class_dependencies: &'a mut PrecomputedHashSet<Atom>,
|
||||
|
||||
/// The filter with the local names of attributes there are selectors for
|
||||
/// within the selector list of :nth-child(... of <selector list>)
|
||||
/// selectors.
|
||||
nth_of_attribute_dependencies: &'a mut PrecomputedHashSet<LocalName>,
|
||||
|
||||
/// All the states selectors in the page reference.
|
||||
state_dependencies: &'a mut ElementState,
|
||||
|
||||
/// All the state selectors in the page reference within the selector list
|
||||
/// of :nth-child(... of <selector list>) selectors.
|
||||
nth_of_state_dependencies: &'a mut ElementState,
|
||||
|
||||
/// All the document states selectors in the page reference.
|
||||
document_state_dependencies: &'a mut DocumentState,
|
||||
}
|
||||
@ -1939,15 +1965,25 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> {
|
||||
true
|
||||
}
|
||||
|
||||
fn visit_selector_list(&mut self, list: &[Selector<Self::Impl>]) -> bool {
|
||||
fn visit_selector_list(
|
||||
&mut self,
|
||||
list_kind: SelectorListKind,
|
||||
list: &[Selector<Self::Impl>],
|
||||
) -> bool {
|
||||
let in_selector_list_of = self.in_selector_list_of | list_kind;
|
||||
for selector in list {
|
||||
let mut nested = StylistSelectorVisitor {
|
||||
passed_rightmost_selector: false,
|
||||
needs_revalidation: &mut *self.needs_revalidation,
|
||||
attribute_dependencies: &mut *self.attribute_dependencies,
|
||||
state_dependencies: &mut *self.state_dependencies,
|
||||
document_state_dependencies: &mut *self.document_state_dependencies,
|
||||
in_selector_list_of,
|
||||
mapped_ids: &mut *self.mapped_ids,
|
||||
nth_of_mapped_ids: &mut *self.nth_of_mapped_ids,
|
||||
attribute_dependencies: &mut *self.attribute_dependencies,
|
||||
nth_of_class_dependencies: &mut *self.nth_of_class_dependencies,
|
||||
nth_of_attribute_dependencies: &mut *self.nth_of_attribute_dependencies,
|
||||
state_dependencies: &mut *self.state_dependencies,
|
||||
nth_of_state_dependencies: &mut *self.nth_of_state_dependencies,
|
||||
document_state_dependencies: &mut *self.document_state_dependencies,
|
||||
};
|
||||
let _ret = selector.visit(&mut nested);
|
||||
debug_assert!(_ret, "We never return false");
|
||||
@ -1961,8 +1997,15 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> {
|
||||
name: &LocalName,
|
||||
lower_name: &LocalName,
|
||||
) -> bool {
|
||||
if self.in_selector_list_of.in_nth_of() {
|
||||
self.nth_of_attribute_dependencies.insert(name.clone());
|
||||
self.nth_of_attribute_dependencies
|
||||
.insert(lower_name.clone());
|
||||
}
|
||||
|
||||
self.attribute_dependencies.insert(name.clone());
|
||||
self.attribute_dependencies.insert(lower_name.clone());
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@ -1975,8 +2018,12 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> {
|
||||
self.state_dependencies.insert(p.state_flag());
|
||||
self.document_state_dependencies
|
||||
.insert(p.document_state_flag());
|
||||
|
||||
if self.in_selector_list_of.in_nth_of() {
|
||||
self.nth_of_state_dependencies.insert(p.state_flag());
|
||||
}
|
||||
},
|
||||
Component::ID(ref id) if !self.passed_rightmost_selector => {
|
||||
Component::ID(ref id) => {
|
||||
// We want to stop storing mapped ids as soon as we've moved off
|
||||
// the rightmost ComplexSelector that is not a pseudo-element.
|
||||
//
|
||||
@ -1988,7 +2035,16 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> {
|
||||
//
|
||||
// NOTE(emilio): See the comment regarding on when this may
|
||||
// break in visit_complex_selector.
|
||||
self.mapped_ids.insert(id.0.clone());
|
||||
if !self.passed_rightmost_selector {
|
||||
self.mapped_ids.insert(id.0.clone());
|
||||
}
|
||||
|
||||
if self.in_selector_list_of.in_nth_of() {
|
||||
self.nth_of_mapped_ids.insert(id.0.clone());
|
||||
}
|
||||
},
|
||||
Component::Class(ref class) if self.in_selector_list_of.in_nth_of() => {
|
||||
self.nth_of_class_dependencies.insert(class.0.clone());
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
@ -2176,11 +2232,25 @@ pub struct CascadeData {
|
||||
/// rare.)
|
||||
attribute_dependencies: PrecomputedHashSet<LocalName>,
|
||||
|
||||
/// The classes that appear in the selector list of
|
||||
/// :nth-child(... of <selector list>). Used to avoid restyling siblings of
|
||||
/// an element when an irrelevant class changes.
|
||||
nth_of_class_dependencies: PrecomputedHashSet<Atom>,
|
||||
|
||||
/// The attributes that appear in the selector list of
|
||||
/// :nth-child(... of <selector list>). Used to avoid restyling siblings of
|
||||
/// an element when an irrelevant attribute changes.
|
||||
nth_of_attribute_dependencies: PrecomputedHashSet<LocalName>,
|
||||
|
||||
/// The element state bits that are relied on by selectors. Like
|
||||
/// `attribute_dependencies`, this is used to avoid taking element snapshots
|
||||
/// when an irrelevant element state bit changes.
|
||||
state_dependencies: ElementState,
|
||||
|
||||
/// The element state bits that are relied on by selectors that appear in
|
||||
/// the selector list of :nth-child(... of <selector list>).
|
||||
nth_of_state_dependencies: ElementState,
|
||||
|
||||
/// The document state bits that are relied on by selectors. This is used
|
||||
/// to tell whether we need to restyle the entire document when a document
|
||||
/// state bit changes.
|
||||
@ -2192,6 +2262,11 @@ pub struct CascadeData {
|
||||
/// filter, and hence might be in one of our selector maps.
|
||||
mapped_ids: PrecomputedHashSet<Atom>,
|
||||
|
||||
/// The IDs that appear in the selector list of
|
||||
/// :nth-child(... of <selector list>). Used to avoid restyling siblings
|
||||
/// of an element when an irrelevant ID changes.
|
||||
nth_of_mapped_ids: PrecomputedHashSet<Atom>,
|
||||
|
||||
/// Selectors that require explicit cache revalidation (i.e. which depend
|
||||
/// on state that is not otherwise visible to the cache, like attributes or
|
||||
/// tree-structural state like child index and pseudos).
|
||||
@ -2237,6 +2312,10 @@ impl CascadeData {
|
||||
slotted_rules: None,
|
||||
part_rules: None,
|
||||
invalidation_map: InvalidationMap::new(),
|
||||
nth_of_mapped_ids: PrecomputedHashSet::default(),
|
||||
nth_of_class_dependencies: PrecomputedHashSet::default(),
|
||||
nth_of_attribute_dependencies: PrecomputedHashSet::default(),
|
||||
nth_of_state_dependencies: ElementState::empty(),
|
||||
attribute_dependencies: PrecomputedHashSet::default(),
|
||||
state_dependencies: ElementState::empty(),
|
||||
document_state_dependencies: DocumentState::empty(),
|
||||
@ -2317,6 +2396,13 @@ impl CascadeData {
|
||||
self.state_dependencies.intersects(state)
|
||||
}
|
||||
|
||||
/// Returns whether the given ElementState bit is relied upon by a selector
|
||||
/// of some rule in the selector list of :nth-child(... of <selector list>).
|
||||
#[inline]
|
||||
pub fn has_nth_of_state_dependency(&self, state: ElementState) -> bool {
|
||||
self.nth_of_state_dependencies.intersects(state)
|
||||
}
|
||||
|
||||
/// Returns whether the given attribute might appear in an attribute
|
||||
/// selector of some rule.
|
||||
#[inline]
|
||||
@ -2324,6 +2410,27 @@ impl CascadeData {
|
||||
self.attribute_dependencies.contains(local_name)
|
||||
}
|
||||
|
||||
/// Returns whether the given ID might appear in an ID selector in the
|
||||
/// selector list of :nth-child(... of <selector list>).
|
||||
#[inline]
|
||||
pub fn might_have_nth_of_id_dependency(&self, id: &Atom) -> bool {
|
||||
self.nth_of_mapped_ids.contains(id)
|
||||
}
|
||||
|
||||
/// Returns whether the given class might appear in a class selector in the
|
||||
/// selector list of :nth-child(... of <selector list>).
|
||||
#[inline]
|
||||
pub fn might_have_nth_of_class_dependency(&self, class: &Atom) -> bool {
|
||||
self.nth_of_class_dependencies.contains(class)
|
||||
}
|
||||
|
||||
/// Returns whether the given attribute might appear in an attribute
|
||||
/// selector in the selector list of :nth-child(... of <selector list>).
|
||||
#[inline]
|
||||
pub fn might_have_nth_of_attribute_dependency(&self, local_name: &LocalName) -> bool {
|
||||
self.nth_of_attribute_dependencies.contains(local_name)
|
||||
}
|
||||
|
||||
/// Returns the normal rule map for a given pseudo-element.
|
||||
#[inline]
|
||||
pub fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
|
||||
@ -2414,6 +2521,9 @@ impl CascadeData {
|
||||
}
|
||||
self.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();
|
||||
self.nth_of_mapped_ids.shrink_if_needed();
|
||||
self.mapped_ids.shrink_if_needed();
|
||||
self.layer_id.shrink_if_needed();
|
||||
self.selectors_for_cache_revalidation.shrink_if_needed();
|
||||
@ -2576,12 +2686,17 @@ impl CascadeData {
|
||||
let mut visitor = StylistSelectorVisitor {
|
||||
needs_revalidation: &mut needs_revalidation,
|
||||
passed_rightmost_selector: false,
|
||||
attribute_dependencies: &mut self.attribute_dependencies,
|
||||
state_dependencies: &mut self.state_dependencies,
|
||||
document_state_dependencies: &mut self.document_state_dependencies,
|
||||
in_selector_list_of: SelectorListKind::default(),
|
||||
mapped_ids: &mut self.mapped_ids,
|
||||
nth_of_mapped_ids: &mut self.nth_of_mapped_ids,
|
||||
attribute_dependencies: &mut self.attribute_dependencies,
|
||||
nth_of_class_dependencies: &mut self.nth_of_class_dependencies,
|
||||
nth_of_attribute_dependencies: &mut self
|
||||
.nth_of_attribute_dependencies,
|
||||
state_dependencies: &mut self.state_dependencies,
|
||||
nth_of_state_dependencies: &mut self.nth_of_state_dependencies,
|
||||
document_state_dependencies: &mut self.document_state_dependencies,
|
||||
};
|
||||
|
||||
rule.selector.visit(&mut visitor);
|
||||
|
||||
if needs_revalidation {
|
||||
@ -3007,9 +3122,13 @@ impl CascadeData {
|
||||
self.clear_cascade_data();
|
||||
self.invalidation_map.clear();
|
||||
self.attribute_dependencies.clear();
|
||||
self.nth_of_attribute_dependencies.clear();
|
||||
self.nth_of_class_dependencies.clear();
|
||||
self.state_dependencies = ElementState::empty();
|
||||
self.nth_of_state_dependencies = ElementState::empty();
|
||||
self.document_state_dependencies = DocumentState::empty();
|
||||
self.mapped_ids.clear();
|
||||
self.nth_of_mapped_ids.clear();
|
||||
self.selectors_for_cache_revalidation.clear();
|
||||
self.effective_media_query_results.clear();
|
||||
}
|
||||
@ -3157,18 +3276,27 @@ size_of_test!(Rule, 40);
|
||||
|
||||
/// A function to be able to test the revalidation stuff.
|
||||
pub fn needs_revalidation_for_testing(s: &Selector<SelectorImpl>) -> bool {
|
||||
let mut attribute_dependencies = Default::default();
|
||||
let mut mapped_ids = Default::default();
|
||||
let mut state_dependencies = ElementState::empty();
|
||||
let mut document_state_dependencies = DocumentState::empty();
|
||||
let mut needs_revalidation = false;
|
||||
let mut mapped_ids = Default::default();
|
||||
let mut nth_of_mapped_ids = Default::default();
|
||||
let mut attribute_dependencies = Default::default();
|
||||
let mut nth_of_class_dependencies = Default::default();
|
||||
let mut nth_of_attribute_dependencies = Default::default();
|
||||
let mut state_dependencies = ElementState::empty();
|
||||
let mut nth_of_state_dependencies = ElementState::empty();
|
||||
let mut document_state_dependencies = DocumentState::empty();
|
||||
let mut visitor = StylistSelectorVisitor {
|
||||
passed_rightmost_selector: false,
|
||||
needs_revalidation: &mut needs_revalidation,
|
||||
attribute_dependencies: &mut attribute_dependencies,
|
||||
state_dependencies: &mut state_dependencies,
|
||||
document_state_dependencies: &mut document_state_dependencies,
|
||||
in_selector_list_of: SelectorListKind::default(),
|
||||
mapped_ids: &mut mapped_ids,
|
||||
nth_of_mapped_ids: &mut nth_of_mapped_ids,
|
||||
attribute_dependencies: &mut attribute_dependencies,
|
||||
nth_of_class_dependencies: &mut nth_of_class_dependencies,
|
||||
nth_of_attribute_dependencies: &mut nth_of_attribute_dependencies,
|
||||
state_dependencies: &mut state_dependencies,
|
||||
nth_of_state_dependencies: &mut nth_of_state_dependencies,
|
||||
document_state_dependencies: &mut document_state_dependencies,
|
||||
};
|
||||
s.visit(&mut visitor);
|
||||
needs_revalidation
|
||||
|
@ -33,6 +33,7 @@ use style::font_face::{self, FontFaceSourceFormat, FontFaceSourceListComponent,
|
||||
use style::gecko::data::{GeckoStyleSheet, PerDocumentStyleData, PerDocumentStyleDataImpl};
|
||||
use style::gecko::restyle_damage::GeckoRestyleDamage;
|
||||
use style::gecko::selector_parser::{NonTSPseudoClass, PseudoElement};
|
||||
use style::gecko::snapshot_helpers::classes_changed;
|
||||
use style::gecko::traversal::RecalcStyleOnly;
|
||||
use style::gecko::url;
|
||||
use style::gecko::wrapper::{GeckoElement, GeckoNode};
|
||||
@ -6775,6 +6776,62 @@ pub extern "C" fn Servo_StyleSet_MightHaveAttributeDependency(
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_MightHaveNthOfIDDependency(
|
||||
raw_data: &RawServoStyleSet,
|
||||
element: &RawGeckoElement,
|
||||
old_id: *mut nsAtom,
|
||||
new_id: *mut nsAtom,
|
||||
) -> bool {
|
||||
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
||||
let element = GeckoElement(element);
|
||||
|
||||
data.stylist.any_applicable_rule_data(element, |data| {
|
||||
[old_id, new_id]
|
||||
.iter()
|
||||
.filter(|id| !id.is_null())
|
||||
.any(|id| unsafe {
|
||||
AtomIdent::with(*id, |atom| data.might_have_nth_of_id_dependency(atom))
|
||||
}) ||
|
||||
data.might_have_nth_of_attribute_dependency(&AtomIdent(atom!("id")))
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_MightHaveNthOfClassDependency(
|
||||
raw_data: &RawServoStyleSet,
|
||||
element: &RawGeckoElement,
|
||||
snapshots: &ServoElementSnapshotTable,
|
||||
) -> bool {
|
||||
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
||||
let element = GeckoElement(element);
|
||||
|
||||
data.stylist.any_applicable_rule_data(element, |data| {
|
||||
classes_changed(&element, snapshots)
|
||||
.iter()
|
||||
.any(|atom| data.might_have_nth_of_class_dependency(atom)) ||
|
||||
data.might_have_nth_of_attribute_dependency(&AtomIdent(atom!("class")))
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_MightHaveNthOfAttributeDependency(
|
||||
raw_data: &RawServoStyleSet,
|
||||
element: &RawGeckoElement,
|
||||
local_name: *mut nsAtom,
|
||||
) -> bool {
|
||||
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
||||
let element = GeckoElement(element);
|
||||
|
||||
unsafe {
|
||||
AtomIdent::with(local_name, |atom| {
|
||||
data.stylist.any_applicable_rule_data(element, |data| {
|
||||
data.might_have_nth_of_attribute_dependency(atom)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_HasStateDependency(
|
||||
raw_data: &RawServoStyleSet,
|
||||
@ -6790,6 +6847,21 @@ pub extern "C" fn Servo_StyleSet_HasStateDependency(
|
||||
.any_applicable_rule_data(element, |data| data.has_state_dependency(state))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_HasNthOfStateDependency(
|
||||
raw_data: &RawServoStyleSet,
|
||||
element: &RawGeckoElement,
|
||||
state: u64,
|
||||
) -> bool {
|
||||
let element = GeckoElement(element);
|
||||
|
||||
let state = ElementState::from_bits_truncate(state);
|
||||
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
||||
|
||||
data.stylist
|
||||
.any_applicable_rule_data(element, |data| data.has_nth_of_state_dependency(state))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Servo_StyleSet_HasDocumentStateDependency(
|
||||
raw_data: &RawServoStyleSet,
|
||||
|
@ -1,2 +0,0 @@
|
||||
[nth-child-of-attr.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-child-of-class.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-child-of-in-ancestor.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-child-of-in-shadow-root.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-child-of-sibling.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-last-child-of-attr.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-last-child-of-class.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-last-child-of-in-ancestor.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-last-child-of-in-shadow-root.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[nth-last-child-of-sibling.html]
|
||||
expected: FAIL
|
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8" />
|
||||
<title>CSS Selectors Invalidation: :nth-child(... of class prefix)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-child(even of [class^=t]) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p class="t1">Not ignored</p>
|
||||
<p id="toggler" class="t2">Selectively ignored</p>
|
||||
<p class="t3">Not ignored</p>
|
||||
<p class="t4">Not ignored</p>
|
||||
<p class="t5">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
toggler.setAttribute("class", "new-class");
|
||||
</script>
|
||||
|
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8" />
|
||||
<title>CSS Selectors Invalidation: :nth-child(... of ID prefix)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-child(even of [id^=t]) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p id="t1">Not ignored</p>
|
||||
<p id="t2">Selectively ignored</p>
|
||||
<p id="t3">Not ignored</p>
|
||||
<p id="t4">Not ignored</p>
|
||||
<p id="t5">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
t2.id = "new-id";
|
||||
</script>
|
||||
|
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8" />
|
||||
<title>CSS Selectors Invalidation: :nth-child(... of IDs)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-child(even of #t1, #t2, #t3, #t4, #t5) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p id="t1">Not ignored</p>
|
||||
<p id="t2">Selectively ignored</p>
|
||||
<p id="t3">Not ignored</p>
|
||||
<p id="t4">Not ignored</p>
|
||||
<p id="t5">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
t2.id = "new-id";
|
||||
</script>
|
||||
|
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Selectors Invalidation: :is(:nth-child(... of class))</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:is(:nth-child(even of .c)) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c" id="toggler">Selectively ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
toggler.classList.toggle("c");
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Selectors Invalidation: :nth-child(... of :is)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-child(even of :is(.c)) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c" id="toggler">Selectively ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
toggler.classList.toggle("c");
|
||||
</script>
|
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>CSS Test Reference</title>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p style="color: green">Not ignored</p>
|
||||
<p>Selectively ignored</p>
|
||||
<p style="color: green">Not ignored</p>
|
||||
<p>Not ignored</p>
|
||||
<p style="color: green">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Selectors Invalidation: :nth-child(... of pseudo-class)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-child-of-pseudo-class-ref.html">
|
||||
<link rel="help" href="https://drafts.csswg.org/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-child(odd of :defined) {
|
||||
color: green;
|
||||
}
|
||||
|
||||
not-defined, my-element {
|
||||
display: block;
|
||||
margin-block: 1em;
|
||||
margin-inline: 0;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<not-defined>Ignored</not-defined>
|
||||
<not-defined>Ignored</not-defined>
|
||||
<p>Not ignored</p>
|
||||
<my-element>Selectively ignored</my-element>
|
||||
<p>Not ignored</p>
|
||||
<p>Not ignored</p>
|
||||
<p>Not ignored</p>
|
||||
<not-defined>Ignored</not-defined>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
customElements.define("my-element", class MyElement extends HTMLElement{});
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8" />
|
||||
<title>CSS Selectors Invalidation: :nth-last-child(... of class prefix)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-last-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-last-child(even of [class^=t]) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p class="t5">Not ignored</p>
|
||||
<p class="t4">Not ignored</p>
|
||||
<p class="t3">Not ignored</p>
|
||||
<p id="toggler" class="t2">Selectively ignored</p>
|
||||
<p class="t1">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
toggler.setAttribute("class", "new-class");
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8" />
|
||||
<title>CSS Selectors Invalidation: :nth-last-child(... of ID prefix)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-last-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-last-child(even of [id^=t]) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p id="t5">Not ignored</p>
|
||||
<p id="t4">Not ignored</p>
|
||||
<p id="t3">Not ignored</p>
|
||||
<p id="t2">Selectively ignored</p>
|
||||
<p id="t1">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
t2.id = "new-id";
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8" />
|
||||
<title>CSS Selectors Invalidation: :nth-last-child(... of IDs)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-last-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-last-child(even of #t1, #t2, #t3, #t4, #t5) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p id="t5">Not ignored</p>
|
||||
<p id="t4">Not ignored</p>
|
||||
<p id="t3">Not ignored</p>
|
||||
<p id="t2">Selectively ignored</p>
|
||||
<p id="t1">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
t2.id = "new-id";
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Selectors Invalidation: :is(:nth-last-child(... of class))</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-last-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:is(:nth-last-child(even of .c)) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c" id="toggler">Selectively ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
toggler.classList.toggle("c");
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Selectors Invalidation: :nth-last-child(... of :is)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-last-child-of-class-ref.html">
|
||||
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-last-child(even of :is(.c)) {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p class="c" id="toggler">Selectively ignored</p>
|
||||
<p class="c">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
toggler.classList.toggle("c");
|
||||
</script>
|
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>CSS Test Reference</title>
|
||||
<div>
|
||||
<p>Ignored</p>
|
||||
<p style="color: green">Not ignored</p>
|
||||
<p>Not ignored</p>
|
||||
<p style="color: green">Not ignored</p>
|
||||
<p>Selectively ignored</p>
|
||||
<p style="color: green">Not ignored</p>
|
||||
<p>Ignored</p>
|
||||
<p>Ignored</p>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Selectors Invalidation: :nth-last-child(... of pseudo-class)</title>
|
||||
<link rel="author" title="Zach Hoffman" href="mailto:zach@zrhoffman.net">
|
||||
<link rel="match" href="nth-last-child-of-pseudo-class-ref.html">
|
||||
<link rel="help" href="https://drafts.csswg.org/selectors-4/#child-index">
|
||||
<style>
|
||||
p:nth-last-child(odd of :defined) {
|
||||
color: green;
|
||||
}
|
||||
|
||||
not-defined, my-element {
|
||||
display: block;
|
||||
margin-block: 1em;
|
||||
margin-inline: 0;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<not-defined>Ignored</not-defined>
|
||||
<p>Not ignored</p>
|
||||
<p>Not ignored</p>
|
||||
<p>Not ignored</p>
|
||||
<my-element>Selectively ignored</my-element>
|
||||
<p>Not ignored</p>
|
||||
<not-defined>Ignored</not-defined>
|
||||
<not-defined>Ignored</not-defined>
|
||||
</div>
|
||||
<script>
|
||||
document.documentElement.offsetTop;
|
||||
customElements.define("my-element", class MyElement extends HTMLElement{});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user