blob: db1c44fb9353a77926addf1426f02905eecfc132 [file] [log] [blame]
// Copyright 2021 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_CSS_CHECK_PSEUDO_HAS_ARGUMENT_CONTEXT_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CHECK_PSEUDO_HAS_ARGUMENT_CONTEXT_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/css/css_selector.h"
#include "third_party/blink/renderer/core/dom/element.h"
namespace blink {
enum CheckPseudoHasArgumentTraversalScope {
// Case 1: :has() argument selector starts with child or descendant
// combinator, and depth is not fixed.
// (e.g. :has(.a), :has(.a > .b), :has(.a + .b), :has(> .a .b) ...))
kSubtree,
// Case 2: :has() argument selector starts with direct or indirect adjacent
// combinator and adjacent distance is not fixed and depth is fixed
// and child combinator not exists.
// (.e.g. :has(~ .a), :has(~ .a ~ .b), :has(~ .a + .b))
kAllNextSiblings,
// Case 3: :has() argument selector starts with direct adjacent combinator
// and adjacent distance is fixed and depth is not fixed.
// (.e.g. :has(+ .a .b), :has(+ .a > .b .c)), :has(+ .a .b > .c)
// :has(+ .a .b ~ .c), :has(+ .a + .b .c))
kOneNextSiblingSubtree,
// Case 4: :has() argument selector starts with direct or indirect adjacent
// combinator and adjacent distance and depth are not fixed.
// (.e.g. :has(~ .a .b), :has(+ .a ~ .b .c))
kAllNextSiblingSubtrees,
// Case 5: :has() argument selector starts with direct adjacent combinator
// and both adjacent distance and depth are fixed and no child
// combinator.
// (.e.g. :has(+ .a), :has(+ .a + .b))
kOneNextSibling,
// Case 6: :has() argument selector starts with child combinator and depth is
// fixed.
// (.e.g. :has(> .a), :has(> .a > .b), :has(> .a + .b),
// :has(> .a ~ .b))
kFixedDepthDescendants,
// Case 7: :has() argument selector starts with direct adjacent combinator
// and both adjacent distance and depth are fixed and child combinator
// exists.
// (.e.g. :has(+ .a > .b), :has(+ .a > .b ~ .c))
kOneNextSiblingFixedDepthDescendants,
// Case 8: :has() argument selector starts with direct or indirect adjacent
// combinator and adjacent distance is not fixed and depth is fixed
// and child combinator exists.
// (.e.g. :has(~ .a > .b), :has(+ .a ~ .b > .c),
// :has(~ .a > .b ~ .c), :has(+ .a ~ .b > .c ~ .d),
kAllNextSiblingsFixedDepthDescendants,
kTraversalScopeMax = kAllNextSiblingsFixedDepthDescendants,
};
// Unique value of each traversal type. The value can be used as a key of
// fast reject filter cache.
//
// These 3 values are stored by dividing the 4-byte field by:
// - depth limit : 0 ~ 13 (14bits)
// - adjacent distance limit : 14 ~ 27 (14 bits)
// - traversal scope : 28 ~ 31 (4 bits)
using CheckPseudoHasArgumentTraversalType = uint32_t;
class CORE_EXPORT CheckPseudoHasArgumentContext {
STACK_ALLOCATED();
public:
explicit CheckPseudoHasArgumentContext(const CSSSelector* selector);
inline bool AdjacentDistanceFixed() const {
return adjacent_distance_limit_ != kInfiniteAdjacentDistance;
}
inline int AdjacentDistanceLimit() const { return adjacent_distance_limit_; }
inline bool DepthFixed() const { return depth_limit_ != kInfiniteDepth; }
inline int DepthLimit() const { return depth_limit_; }
inline CSSSelector::RelationType LeftmostRelation() const {
return leftmost_relation_;
}
inline bool SiblingCombinatorAtRightmost() const {
return sibling_combinator_at_rightmost_;
}
inline bool SiblingCombinatorBetweenChildOrDescendantCombinator() const {
return sibling_combinator_between_child_or_descendant_combinator_;
}
CheckPseudoHasArgumentTraversalScope TraversalScope() const {
return traversal_scope_;
}
SiblingsAffectedByHasFlags GetSiblingsAffectedByHasFlags() const {
return siblings_affected_by_has_flags_;
}
const CSSSelector* HasArgument() const { return has_argument_; }
const Vector<unsigned>& GetPseudoHasArgumentHashes() const {
return pseudo_has_argument_hashes_;
}
CheckPseudoHasArgumentTraversalType TraversalType() const {
return depth_limit_ | (adjacent_distance_limit_ << kDepthBits) |
(traversal_scope_ << (kDepthBits + kAdjacentBits));
}
private:
const static size_t kDepthBits = 14;
const static size_t kAdjacentBits = 14;
const static size_t kTraversalScopeBits = 4;
const static int kInfiniteDepth = (1 << kDepthBits) - 1;
const static int kInfiniteAdjacentDistance = (1 << kAdjacentBits) - 1;
static_assert(kTraversalScopeMax <= ((1 << kTraversalScopeBits) - 1),
"traversal scope size check");
static_assert((kDepthBits + kAdjacentBits + kTraversalScopeBits) <=
sizeof(CheckPseudoHasArgumentTraversalType) * 8,
"traversal type size check");
// Indicate the :has argument relative type and subtree traversal scope.
// If 'adjacent_distance_limit' is integer max, it means that all the
// adjacent subtrees need to be traversed. otherwise, it means that it is
// enough to traverse the adjacent subtree at that distance.
// If 'descendant_traversal_depth_' is integer max, it means that all of the
// descendant subtree need to be traversed. Otherwise, it means that it is
// enough to traverse elements at the certain depth.
//
// Case 1: (kDescendant, 0, max)
// - Argument selector conditions
// - Starts with descendant combinator.
// - E.g. ':has(.a)', ':has(.a ~ .b)', ':has(.a ~ .b > .c)'
// - Traverse all descendants of the :has() anchor element.
// Case 2: (kChild, 0, max)
// - Argument selector conditions
// - Starts with child combinator.
// - At least one descendant combinator.
// - E.g. ':has(> .a .b)', ':has(> .a ~ .b .c)', ':has(> .a + .b .c)'
// - Traverse all descendants of the :has() anchor element.
// Case 3: (kChild, 0, n)
// - Argument selector conditions
// - Starts with child combinator.
// - n number of child combinator. (n > 0)
// - No descendant combinator.
// - E.g.
// - ':has(> .a)' : (kChild, 0, 1)
// - ':has(> .a ~ .b > .c)' : (kChild, 0, 2)
// - Traverse the depth n descendants of the :has() anchor element.
// Case 4: (kIndirectAdjacent, max, max)
// - Argument selector conditions
// - Starts with indirect adjacent combinator.
// - At least one descendant combinator.
// - E.g. ':has(~ .a .b)', ':has(~ .a + .b > .c ~ .d .e)'
// - Traverse all the subsequent sibling subtrees of the :has() anchor
// element. (all subsequent siblings and its descendants)
// Case 5: (kIndirectAdjacent, max, 0)
// - Argument selector conditions
// - Starts with indirect adjacent combinator.
// - No descendant/child combinator.
// - E.g. ':has(~ .a)', ':has(~ .a + .b ~ .c)'
// - Traverse all subsequent siblings of the :has() anchor element.
// Case 6: (kIndirectAdjacent, max, n)
// - Argument selector conditions
// - Starts with indirect adjacent combinator.
// - n number of child combinator. (n > 0)
// - No descendant combinator.
// - E.g.
// - ':has(~ .a > .b)' : (kIndirectAdjacent, max, 1)
// - ':has(~ .a + .b > .c ~ .d > .e)' : (kIndirectAdjacent, max, 2)
// - Traverse depth n elements of all subsequent sibling subtree of the
// :has() anchor element.
// Case 7: (kDirectAdjacent, max, max)
// - Argument selector conditions
// - Starts with direct adjacent combinator.
// - At least one indirect adjacent combinator to the left of every
// descendant or child combinator.
// - At least 1 descendant combinator.
// - E.g. ':has(+ .a ~ .b .c)', ':has(+ .a ~ .b > .c + .d .e)'
// - Traverse all the subsequent sibling subtrees of the :has() anchor
// element. (all subsequent siblings and its descendants)
// Case 8: (kDirectAdjacent, max, 0)
// - Argument selector conditions
// - Starts with direct adjacent combinator.
// - At least one indirect adjacent combinator.
// - No descendant/child combinator.
// - E.g. ':has(+ .a ~ .b)', ':has(+ .a + .b ~ .c)'
// - Traverse all subsequent siblings of the :has() anchor element.
// Case 9: (kDirectAdjacent, max, n)
// - Argument selector conditions
// - Starts with direct adjacent combinator.
// - At least one indirect adjacent combinator to the left of every
// descendant or child combinator.
// - n number of child combinator. (n > 0)
// - No descendant combinator.
// - E.g.
// - ':has(+ .a ~ .b > .c)' : (kDirectAdjacent, max, 1)
// - ':has(+ .a ~ .b > .c + .d >.e)' : (kDirectAdjacent, max, 2)
// - Traverse depth n elements of all subsequent sibling subtree of the
// :has() anchor element.
// Case 10: (kDirectAdjacent, n, max)
// - Argument selector conditions
// - Starts with direct adjacent combinator.
// - n number of direct adjacent combinator to the left of the leftmost
// child(or descendant) combinator. (n > 0)
// - No indirect adjacent combinator to the left of the leftmost child
// (or descendant) combinator.
// - At least 1 descendant combinator.
// - E.g.
// - ':has(+ .a .b)' : (kDirectAdjacent, 1, max)
// - ':has(+ .a > .b + .c .d)' : (kDirectAdjacent, 1, max)
// - ':has(+ .a + .b > .c .d)' : (kDirectAdjacent, 2, max)
// - Traverse the distance n sibling subtree of the :has() anchor element.
// (sibling element at distance n, and its descendants).
// Case 11: (kDirectAdjacent, n, 0)
// - Argument selector conditions
// - Starts with direct adjacent combinator.
// - n number of direct adjacent combinator. (n > 0)
// - No child/descendant/indirect-adjacent combinator.
// - E.g.
// - ':has(+ .a)' : (kDirectAdjacent, 1, 0)
// - ':has(+ .a + .b + .c)' : (kDirectAdjacent, 3, 0)
// - Traverse the distance n sibling element of the :has() anchor element.
// Case 12: (kDirectAdjacent, n, m)
// - Argument selector conditions
// - Starts with direct adjacent combinator.
// - n number of direct adjacent combinator to the left of the leftmost
// child combinator. (n > 0)
// - No indirect adjacent combinator to the left of the leftmost child
// combinator.
// - n number of child combinator. (n > 0)
// - No descendant combinator.
// - E.g.
// - ':has(+ .a > .b)' : (kDirectAdjacent, 1, 1)
// - ':has(+ .a + .b > .c ~ .d > .e)' : (kDirectAdjacent, 2, 2)
// - Traverse the depth m elements of the distance n sibling subtree of
// the :has() anchor element. (elements at depth m of the descendant
// subtree of the sibling element at distance n)
CSSSelector::RelationType leftmost_relation_{CSSSelector::kSubSelector};
int adjacent_distance_limit_;
int depth_limit_;
// Indicates the selector's combinator information which can be used for
// sibling traversal after the :has() argument selector matched.
bool sibling_combinator_at_rightmost_{false};
bool sibling_combinator_between_child_or_descendant_combinator_{false};
CheckPseudoHasArgumentTraversalScope traversal_scope_;
SiblingsAffectedByHasFlags siblings_affected_by_has_flags_;
const CSSSelector* has_argument_;
Vector<unsigned> pseudo_has_argument_hashes_;
friend class CheckPseudoHasArgumentContextTest;
};
// Subtree traversal iterator class for :has() argument checking. To solve the
// following issues, this traversal uses the reversed DOM tree order, and
// provides a functionality to limit the traversal depth.
//
// 1. Cache 'Matched' and 'NotMatched' candidate elements while checking the
// :has() argument selector.
//
// SelectorChecker::CheckPseudoHas() can get all 'Matched' candidates (elements
// that can be a :has() anchor element) while checking the :has() argument
// selector on an element in the traversal range. And when it found the
// elements, it caches those as 'Matched' candidates.
// By following the reversed DOM tree order, we can get these two advantages.
// - Maximize the number of 'Matched' candidates that can be cached while
// checking :has() argument selector.
// - Can cache 'NotMatched' candidates (elements that cannot be a :has() anchor
// element) in case of these 4 traversal scope types:
// - kSubtree
// - kAllNextSiblings
// - kOneNextSiblingSubtree
// - kAllNextSiblingSubtrees
// While traversing, we can cache an element as 'NotMatched' if the element is
// not cached as 'Matched' because it must be cached as 'Matched' previously
// if it is a :has() anchor element. (Reversed DOM tree order guarantees that
// all the descendants, next siblings and next sibling subtrees were already
// traversed)
//
// 2. Prevent unnecessary subtree traversal when it can be limited with
// child combinator or direct adjacent combinator.
//
// We can limit the tree traversal range when we count the leftmost combinators
// of a :has() argument selector. For example, when we check ':has(> .a > .b)'
// on an element, instead of traversing all the descendants of the :has() anchor
// element, we can limit the traversal only for the elements at depth 2 of the
// :has() anchor element. When we check ':has(+ .a > .b)', we can limit the
// traversal only for the child elements of the direct adjacent sibling of the
// :has() anchor element. To implement this, we need a way to limit the
// traversal depth and a way to check whether the iterator is currently at the
// fixed depth or not.
class CORE_EXPORT CheckPseudoHasArgumentTraversalIterator {
STACK_ALLOCATED();
public:
CheckPseudoHasArgumentTraversalIterator(Element&,
CheckPseudoHasArgumentContext&);
void operator++();
Element* CurrentElement() const { return current_element_; }
bool AtEnd() const { return !current_element_; }
inline int CurrentDepth() const { return current_depth_; }
inline Element* ScopeElement() const { return has_anchor_element_; }
private:
inline Element* LastWithin(Element*);
Element* const has_anchor_element_;
int depth_limit_;
Element* last_element_{nullptr};
Element* sibling_at_fixed_distance_{nullptr};
Element* current_element_{nullptr};
int current_depth_{0};
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CHECK_PSEUDO_HAS_ARGUMENT_CONTEXT_H_