blob: c061a3de2bc55825f95cf72d48033a1ecd19d26e [file] [log] [blame]
/*
* Copyright (C) 2011-2020 Apple Inc. All rights reserved.
* Copyright (C) 2014 Yusuke Suzuki <utatane.tea@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "SelectorQuery.h"
#include "CSSParser.h"
#include "ElementIterator.h"
#include "HTMLNames.h"
#include "SelectorChecker.h"
#include "StaticNodeList.h"
#include "StyledElement.h"
namespace WebCore {
#if ASSERT_ENABLED
static bool isSingleTagNameSelector(const CSSSelector& selector)
{
return selector.isLastInTagHistory() && selector.match() == CSSSelector::Tag;
}
static bool isSingleClassNameSelector(const CSSSelector& selector)
{
return selector.isLastInTagHistory() && selector.match() == CSSSelector::Class;
}
#endif // ASSERT_ENABLED
enum class IdMatchingType : uint8_t {
None,
Rightmost,
Filter
};
static bool canBeUsedForIdFastPath(const CSSSelector& selector)
{
return selector.match() == CSSSelector::Id
|| (selector.match() == CSSSelector::Exact && selector.attribute() == HTMLNames::idAttr && !selector.attributeValueMatchingIsCaseInsensitive());
}
static IdMatchingType findIdMatchingType(const CSSSelector& firstSelector)
{
bool inRightmost = true;
for (const CSSSelector* selector = &firstSelector; selector; selector = selector->tagHistory()) {
if (canBeUsedForIdFastPath(*selector)) {
if (inRightmost)
return IdMatchingType::Rightmost;
return IdMatchingType::Filter;
}
if (selector->relation() != CSSSelector::Subselector)
inRightmost = false;
}
return IdMatchingType::None;
}
SelectorDataList::SelectorDataList(const CSSSelectorList& selectorList)
{
unsigned selectorCount = 0;
for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector))
selectorCount++;
m_selectors.reserveInitialCapacity(selectorCount);
for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector))
m_selectors.uncheckedAppend({ selector });
if (selectorCount == 1) {
const CSSSelector& selector = *m_selectors.first().selector;
if (selector.isLastInTagHistory()) {
switch (selector.match()) {
case CSSSelector::Tag:
m_matchType = TagNameMatch;
break;
case CSSSelector::Class:
m_matchType = ClassNameMatch;
break;
default:
if (canBeUsedForIdFastPath(selector))
m_matchType = RightMostWithIdMatch;
else
m_matchType = CompilableSingle;
break;
}
} else {
switch (findIdMatchingType(selector)) {
case IdMatchingType::None:
m_matchType = CompilableSingle;
break;
case IdMatchingType::Rightmost:
m_matchType = RightMostWithIdMatch;
break;
case IdMatchingType::Filter:
m_matchType = CompilableSingleWithRootFilter;
break;
}
}
} else
m_matchType = CompilableMultipleSelectorMatch;
}
inline bool SelectorDataList::selectorMatches(const SelectorData& selectorData, Element& element, const ContainerNode& rootNode) const
{
SelectorChecker selectorChecker(element.document());
SelectorChecker::CheckingContext selectorCheckingContext(SelectorChecker::Mode::QueryingRules);
selectorCheckingContext.scope = rootNode.isDocumentNode() ? nullptr : &rootNode;
unsigned ignoredSpecificity;
return selectorChecker.match(*selectorData.selector, element, selectorCheckingContext, ignoredSpecificity);
}
inline Element* SelectorDataList::selectorClosest(const SelectorData& selectorData, Element& element, const ContainerNode& rootNode) const
{
SelectorChecker selectorChecker(element.document());
SelectorChecker::CheckingContext selectorCheckingContext(SelectorChecker::Mode::QueryingRules);
selectorCheckingContext.scope = rootNode.isDocumentNode() ? nullptr : &rootNode;
unsigned ignoredSpecificity;
if (!selectorChecker.match(*selectorData.selector, element, selectorCheckingContext, ignoredSpecificity))
return nullptr;
return &element;
}
bool SelectorDataList::matches(Element& targetElement) const
{
for (auto& selctor : m_selectors) {
if (selectorMatches(selctor, targetElement, targetElement))
return true;
}
return false;
}
Element* SelectorDataList::closest(Element& targetElement) const
{
for (auto& currentElement : lineageOfType<Element>(targetElement)) {
for (auto& selector : m_selectors) {
if (auto* candidateElement = selectorClosest(selector, currentElement, targetElement))
return candidateElement;
}
}
return nullptr;
}
struct AllElementExtractorSelectorQueryTrait {
typedef Vector<Ref<Element>> OutputType;
static const bool shouldOnlyMatchFirstElement = false;
ALWAYS_INLINE static void appendOutputForElement(OutputType& output, Element* element) { ASSERT(element); output.append(*element); }
};
Ref<NodeList> SelectorDataList::queryAll(ContainerNode& rootNode) const
{
Vector<Ref<Element>> result;
execute<AllElementExtractorSelectorQueryTrait>(rootNode, result);
return StaticElementList::create(WTFMove(result));
}
struct SingleElementExtractorSelectorQueryTrait {
typedef Element* OutputType;
static const bool shouldOnlyMatchFirstElement = true;
ALWAYS_INLINE static void appendOutputForElement(OutputType& output, Element* element)
{
ASSERT(element);
ASSERT(!output);
output = element;
}
};
Element* SelectorDataList::queryFirst(ContainerNode& rootNode) const
{
Element* result = nullptr;
execute<SingleElementExtractorSelectorQueryTrait>(rootNode, result);
return result;
}
static const CSSSelector* selectorForIdLookup(const ContainerNode& rootNode, const CSSSelector& firstSelector)
{
if (!rootNode.isConnected())
return nullptr;
if (rootNode.document().inQuirksMode())
return nullptr;
for (const CSSSelector* selector = &firstSelector; selector; selector = selector->tagHistory()) {
if (canBeUsedForIdFastPath(*selector))
return selector;
if (selector->relation() != CSSSelector::Subselector)
break;
}
return nullptr;
}
static inline bool isTreeScopeRoot(const ContainerNode& node)
{
return node.isDocumentNode() || node.isShadowRoot();
}
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeFastPathForIdSelector(const ContainerNode& rootNode, const SelectorData& selectorData, const CSSSelector* idSelector, typename SelectorQueryTrait::OutputType& output) const
{
ASSERT(m_selectors.size() == 1);
ASSERT(idSelector);
const AtomString& idToMatch = idSelector->value();
if (UNLIKELY(rootNode.treeScope().containsMultipleElementsWithId(idToMatch))) {
const Vector<Element*>* elements = rootNode.treeScope().getAllElementsById(idToMatch);
ASSERT(elements);
bool rootNodeIsTreeScopeRoot = isTreeScopeRoot(rootNode);
for (auto& element : *elements) {
if ((rootNodeIsTreeScopeRoot || element->isDescendantOf(rootNode)) && selectorMatches(selectorData, *element, rootNode)) {
SelectorQueryTrait::appendOutputForElement(output, element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
return;
}
Element* element = rootNode.treeScope().getElementById(idToMatch);
if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
return;
if (selectorMatches(selectorData, *element, rootNode))
SelectorQueryTrait::appendOutputForElement(output, element);
}
static ContainerNode& filterRootById(ContainerNode& rootNode, const CSSSelector& firstSelector)
{
if (!rootNode.isConnected())
return rootNode;
if (rootNode.document().inQuirksMode())
return rootNode;
// If there was an Id match in the rightmost Simple Selector, we should be in a RightMostWithIdMatch, not in filter.
// Thus we can skip the rightmost match.
const CSSSelector* selector = &firstSelector;
do {
ASSERT(!canBeUsedForIdFastPath(*selector));
if (selector->relation() != CSSSelector::Subselector)
break;
selector = selector->tagHistory();
} while (selector);
bool inAdjacentChain = false;
for (; selector; selector = selector->tagHistory()) {
if (canBeUsedForIdFastPath(*selector)) {
const AtomString& idToMatch = selector->value();
if (ContainerNode* searchRoot = rootNode.treeScope().getElementById(idToMatch)) {
if (LIKELY(!rootNode.treeScope().containsMultipleElementsWithId(idToMatch))) {
if (inAdjacentChain)
searchRoot = searchRoot->parentNode();
if (searchRoot && (isTreeScopeRoot(rootNode) || searchRoot == &rootNode || searchRoot->isDescendantOf(rootNode)))
return *searchRoot;
}
}
}
if (selector->relation() == CSSSelector::Subselector)
continue;
if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
inAdjacentChain = true;
else
inAdjacentChain = false;
}
return rootNode;
}
static ALWAYS_INLINE bool localNameMatches(const Element& element, const AtomString& localName, const AtomString& lowercaseLocalName)
{
if (element.isHTMLElement() && element.document().isHTMLDocument())
return element.localName() == lowercaseLocalName;
return element.localName() == localName;
}
template <typename SelectorQueryTrait>
static inline void elementsForLocalName(const ContainerNode& rootNode, const AtomString& localName, const AtomString& lowercaseLocalName, typename SelectorQueryTrait::OutputType& output)
{
if (localName == lowercaseLocalName) {
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(rootNode))) {
if (element.tagQName().localName() == localName) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
} else {
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(rootNode))) {
if (localNameMatches(element, localName, lowercaseLocalName)) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
}
}
template <typename SelectorQueryTrait>
static inline void anyElement(const ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output)
{
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(rootNode))) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeSingleTagNameSelectorData(const ContainerNode& rootNode, const SelectorData& selectorData, typename SelectorQueryTrait::OutputType& output) const
{
ASSERT(m_selectors.size() == 1);
ASSERT(isSingleTagNameSelector(*selectorData.selector));
const QualifiedName& tagQualifiedName = selectorData.selector->tagQName();
const AtomString& selectorLocalName = tagQualifiedName.localName();
const AtomString& selectorLowercaseLocalName = selectorData.selector->tagLowercaseLocalName();
const AtomString& selectorNamespaceURI = tagQualifiedName.namespaceURI();
if (selectorNamespaceURI == starAtom()) {
if (selectorLocalName != starAtom()) {
// Common case: name defined, selectorNamespaceURI is a wildcard.
elementsForLocalName<SelectorQueryTrait>(rootNode, selectorLocalName, selectorLowercaseLocalName, output);
} else {
// Other fairly common case: both are wildcards.
anyElement<SelectorQueryTrait>(rootNode, output);
}
} else {
// Fallback: NamespaceURI is set, selectorLocalName may be starAtom().
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(rootNode))) {
if (element.namespaceURI() == selectorNamespaceURI && localNameMatches(element, selectorLocalName, selectorLowercaseLocalName)) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
}
}
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeSingleClassNameSelectorData(const ContainerNode& rootNode, const SelectorData& selectorData, typename SelectorQueryTrait::OutputType& output) const
{
ASSERT(m_selectors.size() == 1);
ASSERT(isSingleClassNameSelector(*selectorData.selector));
const AtomString& className = selectorData.selector->value();
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(rootNode))) {
if (element.hasClass() && element.classNames().contains(className)) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
}
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeSingleSelectorData(const ContainerNode& rootNode, const ContainerNode& searchRootNode, const SelectorData& selectorData, typename SelectorQueryTrait::OutputType& output) const
{
ASSERT(m_selectors.size() == 1);
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(searchRootNode))) {
if (selectorMatches(selectorData, element, rootNode)) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
}
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeSingleMultiSelectorData(const ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
{
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(rootNode))) {
for (auto& selector : m_selectors) {
if (selectorMatches(selector, element, rootNode)) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
break;
}
}
}
}
#if ENABLE(CSS_SELECTOR_JIT)
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeCompiledSimpleSelectorChecker(const ContainerNode& searchRootNode, SelectorCompiler::QuerySelectorSimpleSelectorChecker selectorChecker, typename SelectorQueryTrait::OutputType& output, const SelectorData& selectorData) const
{
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(searchRootNode))) {
selectorData.compiledSelector.wasUsed();
if (selectorChecker(&element)) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
}
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeCompiledSelectorCheckerWithCheckingContext(const ContainerNode& rootNode, const ContainerNode& searchRootNode, SelectorCompiler::QuerySelectorSelectorCheckerWithCheckingContext selectorChecker, typename SelectorQueryTrait::OutputType& output, const SelectorData& selectorData) const
{
SelectorChecker::CheckingContext checkingContext(SelectorChecker::Mode::QueryingRules);
checkingContext.scope = rootNode.isDocumentNode() ? nullptr : &rootNode;
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(searchRootNode))) {
selectorData.compiledSelector.wasUsed();
if (selectorChecker(&element, &checkingContext)) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
}
}
}
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::executeCompiledSingleMultiSelectorData(const ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
{
SelectorChecker::CheckingContext checkingContext(SelectorChecker::Mode::QueryingRules);
checkingContext.scope = rootNode.isDocumentNode() ? nullptr : &rootNode;
for (auto& element : descendantsOfType<Element>(const_cast<ContainerNode&>(rootNode))) {
for (auto& selector : m_selectors) {
selector.compiledSelector.wasUsed();
bool matched = false;
if (selector.compiledSelector.status == SelectorCompilationStatus::SimpleSelectorChecker) {
auto selectorChecker = SelectorCompiler::querySelectorSimpleSelectorCheckerFunction(selector.compiledSelector);
matched = selectorChecker(&element);
} else {
ASSERT(selector.compiledSelector.status == SelectorCompilationStatus::SelectorCheckerWithCheckingContext);
auto selectorChecker = SelectorCompiler::querySelectorSelectorCheckerFunctionWithCheckingContext(selector.compiledSelector);
matched = selectorChecker(&element, &checkingContext);
}
if (matched) {
SelectorQueryTrait::appendOutputForElement(output, &element);
if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
return;
break;
}
}
}
}
bool SelectorDataList::compileSelector(const SelectorData& selectorData)
{
auto& compiledSelector = selectorData.compiledSelector;
if (compiledSelector.status == SelectorCompilationStatus::NotCompiled)
SelectorCompiler::compileSelector(compiledSelector, selectorData.selector, SelectorCompiler::SelectorContext::QuerySelector);
return compiledSelector.status != SelectorCompilationStatus::CannotCompile;
}
#endif // ENABLE(CSS_SELECTOR_JIT)
template <typename SelectorQueryTrait>
ALWAYS_INLINE void SelectorDataList::execute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
{
ContainerNode* searchRootNode = &rootNode;
switch (m_matchType) {
case RightMostWithIdMatch:
{
const SelectorData& selectorData = m_selectors.first();
if (const CSSSelector* idSelector = selectorForIdLookup(*searchRootNode, *selectorData.selector)) {
executeFastPathForIdSelector<SelectorQueryTrait>(*searchRootNode, m_selectors.first(), idSelector, output);
break;
}
#if ENABLE(CSS_SELECTOR_JIT)
if (compileSelector(selectorData))
goto CompiledSingleCase;
#endif // ENABLE(CSS_SELECTOR_JIT)
goto SingleSelectorCase;
ASSERT_NOT_REACHED();
}
case CompilableSingleWithRootFilter:
case CompilableSingle:
{
#if ENABLE(CSS_SELECTOR_JIT)
const SelectorData& selectorData = m_selectors.first();
ASSERT(selectorData.compiledSelector.status == SelectorCompilationStatus::NotCompiled);
ASSERT(m_matchType == CompilableSingle || m_matchType == CompilableSingleWithRootFilter);
if (compileSelector(selectorData)) {
if (m_matchType == CompilableSingle) {
m_matchType = CompiledSingle;
goto CompiledSingleCase;
}
ASSERT(m_matchType == CompilableSingleWithRootFilter);
m_matchType = CompiledSingleWithRootFilter;
goto CompiledSingleWithRootFilterCase;
}
#endif // ENABLE(CSS_SELECTOR_JIT)
if (m_matchType == CompilableSingle) {
m_matchType = SingleSelector;
goto SingleSelectorCase;
}
ASSERT(m_matchType == CompilableSingleWithRootFilter);
m_matchType = SingleSelectorWithRootFilter;
goto SingleSelectorWithRootFilterCase;
ASSERT_NOT_REACHED();
}
#if ENABLE(CSS_SELECTOR_JIT)
case CompiledSingleWithRootFilter:
CompiledSingleWithRootFilterCase:
searchRootNode = &filterRootById(*searchRootNode, *m_selectors.first().selector);
FALLTHROUGH;
case CompiledSingle:
{
CompiledSingleCase:
const SelectorData& selectorData = m_selectors.first();
if (selectorData.compiledSelector.status == SelectorCompilationStatus::SimpleSelectorChecker) {
auto selectorChecker = SelectorCompiler::querySelectorSimpleSelectorCheckerFunction(selectorData.compiledSelector);
executeCompiledSimpleSelectorChecker<SelectorQueryTrait>(*searchRootNode, selectorChecker, output, selectorData);
} else {
ASSERT(selectorData.compiledSelector.status == SelectorCompilationStatus::SelectorCheckerWithCheckingContext);
auto selectorChecker = SelectorCompiler::querySelectorSelectorCheckerFunctionWithCheckingContext(selectorData.compiledSelector);
executeCompiledSelectorCheckerWithCheckingContext<SelectorQueryTrait>(rootNode, *searchRootNode, selectorChecker, output, selectorData);
}
break;
}
#else
case CompiledSingleWithRootFilter:
case CompiledSingle:
ASSERT_NOT_REACHED();
#if !ASSERT_ENABLED
FALLTHROUGH;
#endif
#endif // ENABLE(CSS_SELECTOR_JIT)
case SingleSelectorWithRootFilter:
SingleSelectorWithRootFilterCase:
searchRootNode = &filterRootById(*searchRootNode, *m_selectors.first().selector);
FALLTHROUGH;
case SingleSelector:
SingleSelectorCase:
executeSingleSelectorData<SelectorQueryTrait>(rootNode, *searchRootNode, m_selectors.first(), output);
break;
case TagNameMatch:
executeSingleTagNameSelectorData<SelectorQueryTrait>(*searchRootNode, m_selectors.first(), output);
break;
case ClassNameMatch:
executeSingleClassNameSelectorData<SelectorQueryTrait>(*searchRootNode, m_selectors.first(), output);
break;
case CompilableMultipleSelectorMatch:
#if ENABLE(CSS_SELECTOR_JIT)
{
for (auto& selector : m_selectors) {
if (!compileSelector(selector)) {
m_matchType = MultipleSelectorMatch;
goto MultipleSelectorMatch;
}
}
m_matchType = CompiledMultipleSelectorMatch;
goto CompiledMultipleSelectorMatch;
}
#else
FALLTHROUGH;
#endif // ENABLE(CSS_SELECTOR_JIT)
case CompiledMultipleSelectorMatch:
#if ENABLE(CSS_SELECTOR_JIT)
CompiledMultipleSelectorMatch:
executeCompiledSingleMultiSelectorData<SelectorQueryTrait>(*searchRootNode, output);
break;
#else
FALLTHROUGH;
#endif // ENABLE(CSS_SELECTOR_JIT)
case MultipleSelectorMatch:
#if ENABLE(CSS_SELECTOR_JIT)
MultipleSelectorMatch:
#endif
executeSingleMultiSelectorData<SelectorQueryTrait>(*searchRootNode, output);
break;
}
}
SelectorQuery::SelectorQuery(CSSSelectorList&& selectorList)
: m_selectorList(WTFMove(selectorList))
, m_selectors(m_selectorList)
{
}
ExceptionOr<SelectorQuery&> SelectorQueryCache::add(const String& selectors, Document& document)
{
if (auto* entry = m_entries.get(selectors))
return *entry;
CSSParser parser(document);
CSSSelectorList selectorList;
parser.parseSelector(selectors, selectorList);
if (!selectorList.first() || selectorList.hasInvalidSelector())
return Exception { SyntaxError };
if (selectorList.selectorsNeedNamespaceResolution())
return Exception { SyntaxError };
const int maximumSelectorQueryCacheSize = 256;
if (m_entries.size() == maximumSelectorQueryCacheSize)
m_entries.remove(m_entries.random());
return *m_entries.add(selectors, makeUnique<SelectorQuery>(WTFMove(selectorList))).iterator->value;
}
}