blob: 189b800c331d9a8942e4404d0cb3d93409247ba2 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_HAS_INVALIDATION_FLAGS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_HAS_INVALIDATION_FLAGS_H_
namespace blink {
// Flags for :has() invalidation.
//
// The flags can be categorized 3 types.
//
// 1. Flags for the :has() anchor elements.
// - AffectedBySubjectHas
// Indicates that this element may match a subject :has() selector, which
// means we need to invalidate the element when the :has() state changes.
// - AffectedByNonSubjectHas
// Indicates that this element may match a non-subject :has() selector,
// which means we need to schedule descendant and sibling invalidation
// sets on this element when the :has() state changes.
// - AffectedByPseudosInHas
// Indicates that this element can be affected by the state change of the
// pseudo class in the :has() argument selector. For every pseudo state
// change mutation, if an element doesn't have the flag set, the element
// will not be invalidated or scheduled on even if the element has the
// AffectedBySubjectHas or AffectedByNonSubjectHas flag set.
// - AffectedByMultipleHas
// Indicate that this element can be affected by multiple :has() pseudo
// classes.
// SelectorChecker uses CheckPseudoHasFastRejectFilter to preemtively
// skip non-matching :has() pseudo class checks only if there are
// multiple :has() to check on the same anchor element. SelectorChecker
// would not use the reject filter for a single :has() because it would
// have worse performance caused by the bloom filter memory allocation
// and the tree traversal for collecting element identifier hashes.
// To avoid the unnecessary overhead, bloom filter creation and element
// identifier hash collection are performed on the second check, and at
// this time AffectedByMultipleHas flag is set.
// This flag is used to determine whether SelectorChecker can use the
// reject filter even if on the first check since the flag indicates that
// there can be additional checks on the same anchor element.
//
// SelectorChecker::CheckPseudoClass() set the flags on an element when it
// checks a :has() pseudo class on the element.
//
// 2. Flags for the elements that a :has() argument selector can be tested on.
// (The elements that can affect a :has() pseudo class state)
//
// - SiblingsAffectedByHas :
// Indicates that this element possibly matches any of the :has()
// argument selectors, and we need to traverse siblings to find the
// subject or non-subject :has() anchor element.
// The SiblingsAffectedByHas consists of two flags.
// - SiblingsAffectedByHasForSiblingRelationship
// Indicates that the `:has()` argument selector is to check the
// sibling relationship. The argument selector starts with a direct
// or indirect adjacent combinator and it doesn't have any descendant
// or child combinator(s).
// - SiblingsAffectedByHasForSiblingDescendantRelationship
// Indicates that the `:has()` argument selector is to check the
// sibling-descendant relationship. The argument selector starts with
// a direct or indirect adjacent combinator and it has descendant or
// child combinator(s).
// - AncestorsOrAncestorSiblingsAffectedByHas :
// Indicates that this element possibly matches any of the :has()
// argument selectors, and we need to traverse ancestors or siblings of
// ancestors to find the subject or non-subject :has() anchor element.
//
// SelectorChecker::CheckPseudoHas() set the flags on some elements when it
// checks the :has() argument selectors. (StyleEngine also set the flags
// on the elements to be inserted if the inserted elements possibly affecting
// a :has() state change)
//
// Before starting the subtree traversal for checking the :has() argument
// selector, the SelectorChecker::CheckPseudoHas() set the flags on the
// :has() anchor element or its next siblings (The :has() anchor element
// should have the flags set so that the StyleEngine can determine whether an
// inserted element is possibly affecting :has() state).
//
// If the :has() argument selector starts with child or descendant
// combinator, the :has() anchor element will have the
// AncestorsOrAncestorSiblingsAffectedByHas flag set. If the :has() argument
// starts with adjacent combinators, the :has() anchor element and its next
// siblings will have the SiblingsAffectedByHas flag set.
//
// If the :has() argument selector checks descendant or sibling descendant
// relationship (child or descendant combinator exists in the argument), for
// every elements in the argument checking traversal, the
// AncestorsOrAncestorSiblingsAffectedByHas flag will be set so that the
// StyleEngine can traverse to ancestors for :has() invalidation.
//
// StyleEngine tries to find the :has() anchor elements by traversing
// siblings or ancestors of a mutated element only when an element has the
// xxx-affected-by-has flags set. If an element doesn't have those flags set,
// then the StyleEngine will stop the traversal at the element.
//
// CheckPseudoHasArgumentTraversalIterator traverses the subtree in the
// reversed DOM tree order to prevent duplicated subtree traversal caused by
// the multiple :has() anchor elements. If there is an argument matched
// element in the traversal, it returns early because the :has() pseudo class
// matches.
//
// Due to the traversal order and the early returning, the :has()
// invalidation traversal can be broken when the :has() argument selector
// matches on an element because the ancestors or previous siblings of the
// element will not have the AncestorsOrAncestorSiblingsAffectedByHas flag
// set.
//
// To prevent the problem, when the :has() argument matches on an element,
// the SelectorChecker::CheckPseudoHas traverses to siblings, ancestors or
// ancestor siblings of the argument matched element and set the
// AncestorsOrAncestorSiblingsAffectedByHas flag on the elements until reach
// to the :has() anchor element or sibling of :has() anchor element.
//
// 3. Flags for the elements that the particular pseudo classes in the :has()
// argument selector can be tested on.
// (The elements that can affect a :has() pseudo class state by their own
// state change for the particular pseudo classes)
//
// - AncestorsOrSiblingsAffectedByHoverInHas :
// Indicates that this element may matched a :hover inside :has().
// - AncestorsOrSiblingsAffectedByActiveInHas :
// Indicates that this element may matched a :active inside :has().
// - AncestorsOrSiblingsAffectedByFocusInHas :
// Indicates that this element may matched a :focus inside :has().
// - AncestorsOrSiblingsAffectedByFocusVisibleInHas :
// Indicates that this element may matched a :focus-visible inside
// :has().
//
// SelectorChecker::CheckPseudoClass check the flags on an element when it
// checks the pseudo classes on the element.
//
// Similar to the DynamicRestyleFlags in the ContainerNode, these flags will
// never be reset. (except the AffectedBySubjectHas flag which is defined at
// the computed style extra flags)
//
// Example 1) Subject :has() (has only descendant relationship)
// <style> .a:has(.b) {...} </style>
// <div>
// <div class=a> <!-- AffectedBySubjectHas (computed style extra flag) -->
// <div> <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// <div></div> <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// <div></div> <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// </div>
// </div>
// </div>
//
//
// Example 2) Non-subject :has()
// <style> .a:has(.b) .c {...} </style>
// <div>
// <div class=a> <!-- AffectedByNonSubjectHas -->
// <div> <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// <div></div> <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// <div class=c></div><!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// </div>
// </div>
// </div>
//
//
// Example 3) Subject :has() (has only sibling relationship)
// <style> .a:has(~ .b) {...} </style>
// <div>
// <div></div>
// <div class=a> <!-- AffectedBySubjectHas (computed style extra flag) -->
// <div></div>
// </div>
// <div></div> <!-- SiblingsAffectedByHasForSiblingRelationship -->
// <div></div> <!-- SiblingsAffectedByHasForSiblingRelationship -->
// </div>
//
//
// Example 4) Subject :has() (has both sibling and descendant relationship)
// <style> .a:has(~ .b .c) {...} </style>
// <div>
// <div></div>
// <div class=a> <!-- AffectedBySubjectHas (computed style extra flag) -->
// </div>
// <div> <!-- SiblingsAffectedByHasForSiblingDescendantRelationship -->
// <div></div> <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// <div></div> <!-- AncestorsOrAncestorSiblingsAffectedByHas -->
// </div>
// </div>
enum SiblingsAffectedByHasFlags : unsigned {
kFlagForSiblingRelationship = 1 << 0,
kFlagForSiblingDescendantRelationship = 1 << 1,
kNoSiblingsAffectedByHasFlags = 0,
};
struct HasInvalidationFlags {
unsigned affected_by_subject_has : 1;
unsigned affected_by_non_subject_has : 1;
unsigned affected_by_pseudos_in_has : 1;
unsigned siblings_affected_by_has : 2;
unsigned ancestors_or_ancestor_siblings_affected_by_has : 1;
unsigned ancestors_or_siblings_affected_by_hover_in_has : 1;
unsigned ancestors_or_siblings_affected_by_active_in_has : 1;
unsigned ancestors_or_siblings_affected_by_focus_in_has : 1;
unsigned ancestors_or_siblings_affected_by_focus_visible_in_has : 1;
unsigned affected_by_logical_combinations_in_has : 1;
unsigned affected_by_multiple_has : 1;
HasInvalidationFlags()
: affected_by_subject_has(false),
affected_by_non_subject_has(false),
affected_by_pseudos_in_has(false),
siblings_affected_by_has(0),
ancestors_or_ancestor_siblings_affected_by_has(false),
ancestors_or_siblings_affected_by_hover_in_has(false),
ancestors_or_siblings_affected_by_active_in_has(false),
ancestors_or_siblings_affected_by_focus_in_has(false),
ancestors_or_siblings_affected_by_focus_visible_in_has(false),
affected_by_multiple_has(false) {}
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_HAS_INVALIDATION_FLAGS_H_