chromium/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.h

// 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_CACHE_SCOPE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CHECK_PSEUDO_HAS_CACHE_SCOPE_H_

#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/css/check_pseudo_has_argument_context.h"
#include "third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"

namespace blink {

class CSSSelector;
class Document;
class CheckPseudoHasArgumentContext;

// To determine whether a :has() pseudo class matches an element or not, we need
// to check the :has() argument selector on the descendants, next siblings or
// next sibling descendants. While checking the :has() argument selector in
// reversed DOM tree traversal order, we can get the :has() pseudo class
// checking result on the elements in the subtree. By caching these results, we
// can prevent unnecessary :has() pseudo class checking operations. (Please
// refer the comments of CheckPseudoHasArgumentTraversalIterator)
//
// Caching the results on all elements in the subtree is a very memory consuming
// approach. To prevent the large and inefficient cache memory consumption,
// ElementCheckPseudoHasResultMap stores following flags for an element.
//
// - flag1 (Checked) : Indicates that the :has() pseudo class was already
//     checked on the element.
//
// - flag2 (Matched) : Indicates that the :has() pseudo class was already
//     checked on the element and it matched.
//
// - flag3 (AllDescendantsOrNextSiblingsChecked) : Indicates that all the
//     not-cached descendant elements (or all the not-cached next sibling
//     elements) of the element were already checked as not-matched.
//     When the :has() argument checking traversal is stopped, this flag is set
//     on the stopped element and the next sibling element of its ancestors to
//     mark already traversed subtree.
//
// - flag4 (SomeChildrenChecked) : Indicates that some children of the element
//     were already checked. This flag is set on the parent of the
//     kCheckPseudoHasResultAllDescendantsOrNextSiblingsChecked element.
//     If the parent of an not-cached element has this flag set, we can
//     determine whether the element is 'already checked as not-matched' or
//     'not yet checked' by checking the AllDescendantsOrNextSiblingsChecked
//     flag of its previous sibling elements.
//
// Example)  subject.match(':has(.a)')
//  - DOM
//      <div id=subject>
//        <div id=d1>
//          <div id=d11></div>
//        </div>
//        <div id=d2>
//          <div id=d21></div>
//          <div id=d22 class=a>
//            <div id=d221></div>
//          </div>
//          <div id=d23></div>
//        </div>
//        <div id=d3>
//          <div id=d31></div>
//        </div>
//        <div id=d4></div>
//      </div>
//
//  - Cache
//      |    id    |  flag1  |  flag2  |  flag3  |  flag4  | actual state |
//      | -------- | ------- | ------- | ------- | ------- | ------------ |
//      |  subject |    1    |    1    |    0    |    1    |    matched   |
//      |    d1    |    -    |    -    |    -    |    -    |  not checked |
//      |    d11   |    -    |    -    |    -    |    -    |  not checked |
//      |    d2    |    1    |    1    |    0    |    1    |    matched   |
//      |    d21   |    -    |    -    |    -    |    -    |  not checked |
//      |    d22   |    1    |    0    |    1    |    0    |  not matched |
//      |    d221  |    -    |    -    |    -    |    -    |  not matched |
//      |    d23   |    -    |    -    |    -    |    -    |  not matched |
//      |    d3    |    1    |    0    |    1    |    0    |  not matched |
//      |    d31   |    -    |    -    |    -    |    -    |  not matched |
//      |    d4    |    -    |    -    |    -    |    -    |  not matched |
//
//  - How to check elements that are not in the cache.
//    - d1 :   1. Check parent(subject). Parent is 'SomeChildrenChecked'.
//             2. Traverse to previous siblings to find an element with the
//                flag3 (AllDescendantsOrNextSiblingsChecked).
//             >> not checked because no previous sibling with the flag set.
//    - d11 :  1. Check parent(d1). Parent is not cached.
//             2. Traverse to the parent(p1).
//             3. Check parent(subject). Parent is 'SomeChildrenChecked'.
//             4. Traverse to previous siblings to find an element with the
//                flag3 (AllDescendantsOrNextSiblingsChecked).
//             >> not checked because no previous sibling with the flag set.
//    - d21 :  1. Check parent(d2). Parent is 'SomeChildrenChecked'.
//             2. Traverse to previous siblings to find an element with the
//                flag3 (AllDescendantsOrNextSiblingsChecked).
//             >> not checked because no previous sibling with the flag set.
//    - d221 : 1. Check parent(d2).
//                Parent is 'AllDescendantsOrNextSiblingsChecked'.
//             >> not matched
//    - d23 :  1. Check parent(d2). Parent is 'SomeChildrenChecked'.
//             2. Traverse to previous siblings to find an element with the
//                flag3 (AllDescendantsOrNextSiblingsChecked).
//             >> not matched because d22 is
//                'AllDescendantsOrNextSiblingsChecked'.
//    - d31 :  1. Check parent(d3).
//                Parent is 'AllDescendantsOrNextSiblingsChecked'.
//             >> not matched
//    - d4 :   1. Check parent(subject). Parent is 'SomeChildrenChecked'.
//             2. Traverse to previous siblings to find an element with the
//                flag3 (AllDescendantsOrNextSiblingsChecked).
//             >> not matched because d3 is
//                'AllDescendantsOrNextSiblingsChecked'.
//
// Please refer the check_pseudo_has_cache_scope_context_test.cc for other
// cases.
CheckPseudoHasResult;
constexpr CheckPseudoHasResult kCheckPseudoHasResultNotCached =;
constexpr CheckPseudoHasResult kCheckPseudoHasResultChecked =;
constexpr CheckPseudoHasResult kCheckPseudoHasResultMatched =;
constexpr CheckPseudoHasResult
    kCheckPseudoHasResultAllDescendantsOrNextSiblingsChecked =;
constexpr CheckPseudoHasResult kCheckPseudoHasResultSomeChildrenChecked =;

// The :has() result cache keeps the :has() pseudo class checking result
// regardless of the :has() pseudo class location (whether it is for subject or
// not).
// (e.g. '.a:has(.b) .c', '.a .b:has(.c)', ':is(.a:has(.b) .c) .d', ...)
//
// It stores the checking result of a :has() pseudo class. For example, when we
// have the selector '.a:has(.b) .c', during the selector checking sequence,
// checking result for ':has(.b)' will be inserted into the cache.
//
// To differentiate multiple :has() pseudo classes, the argument selector
// text is selected as a cache key. For example, if we already have the result
// of ':has(.a)' in the cache with cache key '.a', and we have the selectors
// '.b:has(.a) .c' and '.b .c:has(a)' to be checked, then the selector checking
// overhead of those 2 selectors will be similar with the overhead of '.a .c'
// because we can get the result of ':has(.a)' from the cache with the cache
// key '.a'.
//
// The :has() checking result cache uses a 2 dimensional hash map to store the
// result.
// - hashmap[<argument-selector>][<element>] = <result>
//
// ElementCheckPseudoHasResultMap is a hash map that stores the
// :has(<argument-selector>) checking result on each element.
// - hashmap[<element>] = <result>
ElementCheckPseudoHasResultMap;
CheckPseudoHasResultCache;

// The :has() result cache keeps a bloom filter for rejecting :has() argument
// selector checking.
//
// The element identifier hashes in the bloom filter depend on the relationship
// between the :has() anchor element and the :has() argument subject element.
// The relationship can be categorized by this information in
// CheckPseudoHasArgumentContext.
// - traversal scope
// - adjacent limit
// - depth limit
// (Please refer the comment of CheckPseudoHasArgumentTraversalType)
//
// The CheckPseudoHasFastRejectFilterCache uses a 2 dimensional hash map to
// store the filter.
// - hashmap[<traversal type>][<element>] = <filter>
//
// ElementCheckPseudoHasFastRejectFilterMap is a hash map that stores the
// filter for each element.
// - hashmap[<element>] = <filter>
ElementCheckPseudoHasFastRejectFilterMap;
CheckPseudoHasFastRejectFilterCache;

// CheckPseudoHasCacheScope is the stack-allocated scoping class for :has()
// pseudo class checking result cache and :has() pseudo class checking fast
// reject filter cache. It also manages checking for recursive :has().
//
// This class has hashmap to hold the checking result and filter, so the
// lifecycle of the caches follow the lifecycle of the CheckPseudoHasCacheScope
// instance. (The hashmap for caching will be created at the construction of a
// CheckPseudoHasCacheScope instance, and removed at the destruction of the
// instance)
//
// void SomeFunction() {
//   CheckPseudoHasCacheScope cache_scope; // A cache will be created here.
//   // Can use the created cache here.
// } // The cache will be deleted here.
//
// The scope instance is allocated in the function-call stack, so the
// allocation can be nested. In this case, nested cache scope should not
// override the previous cache scope for a better cache hit ratio.
//
// void SomeFunction2() {
//   CheckPseudoHasCacheScope cache_scope2;
//   // Use the cache in the cache_scope1.
//   // The cache in the cache_scope2 will not be used.
// }
//
// void SomeFunction1() {
//   CheckPseudoHasCacheScope cache_scope1;
//   // Use the cache in the cache_scope1
//   SomeFunction2();
// }
//
// To make this simple, the first allocated instance on the call stack will
// be held in the Document instance. (The instance registers itself in the
// constructor and deregisters itself in the destructor) This is based on
// the restriction : The CheckPseudoHasCacheScope is allowed to use only in the
// sequences on the blink main thread.
//
// The cached results are valid until the DOM doesn't mutate, so any DOM
// mutations inside the cache scope is not allowed for the consistency.
class CORE_EXPORT CheckPseudoHasCacheScope {};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CHECK_PSEUDO_HAS_CACHE_SCOPE_H_