| /* |
| * 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; |
| } |
| |
| } |