// 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 { … }; struct HasInvalidationFlags { … }; } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_HAS_INVALIDATION_FLAGS_H_