620 lines
25 KiB
C++
620 lines
25 KiB
C++
/*
|
|
* 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;
|
|
return selectorChecker.match(*selectorData.selector, element, selectorCheckingContext);
|
|
}
|
|
|
|
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;
|
|
if (!selectorChecker.match(*selectorData.selector, element, selectorCheckingContext))
|
|
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, typename Checker>
|
|
ALWAYS_INLINE void SelectorDataList::executeCompiledSimpleSelectorChecker(const ContainerNode& searchRootNode, Checker 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, typename Checker>
|
|
ALWAYS_INLINE void SelectorDataList::executeCompiledSelectorCheckerWithCheckingContext(const ContainerNode& rootNode, const ContainerNode& searchRootNode, Checker 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)
|
|
matched = SelectorCompiler::querySelectorSimpleSelectorChecker(selector.compiledSelector, &element);
|
|
else {
|
|
ASSERT(selector.compiledSelector.status == SelectorCompilationStatus::SelectorCheckerWithCheckingContext);
|
|
matched = SelectorCompiler::querySelectorSelectorCheckerWithCheckingContext(selector.compiledSelector, &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) {
|
|
executeCompiledSimpleSelectorChecker<SelectorQueryTrait>(*searchRootNode, [&] (const Element* element) {
|
|
return SelectorCompiler::querySelectorSimpleSelectorChecker(selectorData.compiledSelector, element);
|
|
}, output, selectorData);
|
|
} else {
|
|
ASSERT(selectorData.compiledSelector.status == SelectorCompilationStatus::SelectorCheckerWithCheckingContext);
|
|
executeCompiledSelectorCheckerWithCheckingContext<SelectorQueryTrait>(rootNode, *searchRootNode, [&] (const Element* element, const SelectorChecker::CheckingContext* context) {
|
|
return SelectorCompiler::querySelectorSelectorCheckerWithCheckingContext(selectorData.compiledSelector, element, context);
|
|
}, 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);
|
|
auto selectorList = parser.parseSelector(selectors);
|
|
|
|
if (!selectorList || 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;
|
|
}
|
|
|
|
}
|