/* * Copyright (C) 2011, 2013, 2014 Apple Inc. All rights reserved. * Copyright (C) 2014 Yusuke Suzuki * * 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 "ElementDescendantIterator.h" #include "SelectorChecker.h" #include "StaticNodeList.h" #include "StyledElement.h" namespace WebCore { #if !ASSERT_DISABLED 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 enum class IdMatchingType : uint8_t { None, Rightmost, Filter }; static IdMatchingType findIdMatchingType(const CSSSelector& firstSelector) { bool inRightmost = true; for (const CSSSelector* selector = &firstSelector; selector; selector = selector->tagHistory()) { if (selector->match() == CSSSelector::Id) { 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(SelectorData(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; case CSSSelector::Id: m_matchType = RightMostWithIdMatch; break; default: 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 { Element* currentNode = &targetElement; do { for (auto& selector : m_selectors) { Element* candidateElement = selectorClosest(selector, *currentNode, targetElement); if (candidateElement) return candidateElement; } currentNode = currentNode->parentElement(); } while (currentNode); return nullptr; } struct AllElementExtractorSelectorQueryTrait { typedef Vector> OutputType; static const bool shouldOnlyMatchFirstElement = false; ALWAYS_INLINE static void appendOutputForElement(OutputType& output, Element* element) { ASSERT(element); output.append(*element); } }; RefPtr SelectorDataList::queryAll(ContainerNode& rootNode) const { Vector> result; execute(rootNode, result); return StaticElementList::adopt(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(rootNode, result); return result; } static const CSSSelector* selectorForIdLookup(const ContainerNode& rootNode, const CSSSelector& firstSelector) { if (!rootNode.inDocument()) return nullptr; if (rootNode.document().inQuirksMode()) return nullptr; for (const CSSSelector* selector = &firstSelector; selector; selector = selector->tagHistory()) { if (selector->match() == CSSSelector::Id) return selector; if (selector->relation() != CSSSelector::SubSelector) break; } return nullptr; } static inline bool isTreeScopeRoot(const ContainerNode& node) { return node.isDocumentNode() || node.isShadowRoot(); } template 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 AtomicString& idToMatch = idSelector->value(); if (UNLIKELY(rootNode.treeScope().containsMultipleElementsWithId(idToMatch))) { const Vector* 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.inDocument()) 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(selector->match() != CSSSelector::Id); if (selector->relation() != CSSSelector::SubSelector) break; selector = selector->tagHistory(); } while (selector); bool inAdjacentChain = false; for (; selector; selector = selector->tagHistory()) { if (selector->match() == CSSSelector::Id) { const AtomicString& 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 AtomicString& localName, const AtomicString& lowercaseLocalName) { if (element.isHTMLElement() && element.document().isHTMLDocument()) return element.localName() == lowercaseLocalName; return element.localName() == localName; } template static inline void elementsForLocalName(const ContainerNode& rootNode, const AtomicString& localName, const AtomicString& lowercaseLocalName, typename SelectorQueryTrait::OutputType& output) { if (localName == lowercaseLocalName) { for (auto& element : elementDescendants(const_cast(rootNode))) { if (element.tagQName().localName() == localName) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } } else { for (auto& element : elementDescendants(const_cast(rootNode))) { if (localNameMatches(element, localName, lowercaseLocalName)) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } } } template static inline void anyElement(const ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) { for (auto& element : elementDescendants(const_cast(rootNode))) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } template 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 AtomicString& selectorLocalName = tagQualifiedName.localName(); const AtomicString& selectorLowercaseLocalName = selectorData.selector->tagLowercaseLocalName(); const AtomicString& selectorNamespaceURI = tagQualifiedName.namespaceURI(); if (selectorNamespaceURI == starAtom) { if (selectorLocalName != starAtom) { // Common case: name defined, selectorNamespaceURI is a wildcard. elementsForLocalName(rootNode, selectorLocalName, selectorLowercaseLocalName, output); } else { // Other fairly common case: both are wildcards. anyElement(rootNode, output); } } else { // Fallback: NamespaceURI is set, selectorLocalName may be starAtom. for (auto& element : elementDescendants(const_cast(rootNode))) { if (element.namespaceURI() == selectorNamespaceURI && localNameMatches(element, selectorLocalName, selectorLowercaseLocalName)) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } } } template 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 AtomicString& className = selectorData.selector->value(); for (auto& element : elementDescendants(const_cast(rootNode))) { if (element.hasClass() && element.classNames().contains(className)) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } } template 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 : elementDescendants(const_cast(searchRootNode))) { if (selectorMatches(selectorData, element, rootNode)) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } } template ALWAYS_INLINE void SelectorDataList::executeSingleMultiSelectorData(const ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const { for (auto& element : elementDescendants(const_cast(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 ALWAYS_INLINE void SelectorDataList::executeCompiledSimpleSelectorChecker(const ContainerNode& searchRootNode, SelectorCompiler::QuerySelectorSimpleSelectorChecker selectorChecker, typename SelectorQueryTrait::OutputType& output, const SelectorData& selectorData) const { for (auto& element : elementDescendants(const_cast(searchRootNode))) { #if CSS_SELECTOR_JIT_PROFILING selectorData.compiledSelectorUsed(); #else UNUSED_PARAM(selectorData); #endif if (selectorChecker(&element)) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } } template 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 : elementDescendants(const_cast(searchRootNode))) { #if CSS_SELECTOR_JIT_PROFILING selectorData.compiledSelectorUsed(); #else UNUSED_PARAM(selectorData); #endif if (selectorChecker(&element, &checkingContext)) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; } } } template 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 : elementDescendants(const_cast(rootNode))) { for (auto& selector : m_selectors) { #if CSS_SELECTOR_JIT_PROFILING selector.compiledSelectorUsed(); #endif bool matched = false; void* compiledSelectorChecker = selector.compiledSelectorCodeRef.code().executableAddress(); if (selector.compilationStatus == SelectorCompilationStatus::SimpleSelectorChecker) { auto selectorChecker = SelectorCompiler::querySelectorSimpleSelectorCheckerFunction(compiledSelectorChecker, selector.compilationStatus); matched = selectorChecker(&element); } else { ASSERT(selector.compilationStatus == SelectorCompilationStatus::SelectorCheckerWithCheckingContext); auto selectorChecker = SelectorCompiler::querySelectorSelectorCheckerFunctionWithCheckingContext(compiledSelectorChecker, selector.compilationStatus); matched = selectorChecker(&element, &checkingContext); } if (matched) { SelectorQueryTrait::appendOutputForElement(output, &element); if (SelectorQueryTrait::shouldOnlyMatchFirstElement) return; break; } } } } static bool isCompiledSelector(SelectorCompilationStatus compilationStatus) { return compilationStatus == SelectorCompilationStatus::SimpleSelectorChecker || compilationStatus == SelectorCompilationStatus::SelectorCheckerWithCheckingContext; } bool SelectorDataList::compileSelector(const SelectorData& selectorData, const ContainerNode& rootNode) { if (selectorData.compilationStatus != SelectorCompilationStatus::NotCompiled) return isCompiledSelector(selectorData.compilationStatus); JSC::VM& vm = rootNode.document().scriptExecutionContext()->vm(); selectorData.compilationStatus = SelectorCompiler::compileSelector(selectorData.selector, &vm, SelectorCompiler::SelectorContext::QuerySelector, selectorData.compiledSelectorCodeRef); return isCompiledSelector(selectorData.compilationStatus); } #endif // ENABLE(CSS_SELECTOR_JIT) template 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(*searchRootNode, m_selectors.first(), idSelector, output); break; } #if ENABLE(CSS_SELECTOR_JIT) if (compileSelector(selectorData, *searchRootNode)) 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.compilationStatus == SelectorCompilationStatus::NotCompiled); ASSERT(m_matchType == CompilableSingle || m_matchType == CompilableSingleWithRootFilter); if (compileSelector(selectorData, *searchRootNode)) { 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(); void* compiledSelectorChecker = selectorData.compiledSelectorCodeRef.code().executableAddress(); if (selectorData.compilationStatus == SelectorCompilationStatus::SimpleSelectorChecker) { SelectorCompiler::QuerySelectorSimpleSelectorChecker selectorChecker = SelectorCompiler::querySelectorSimpleSelectorCheckerFunction(compiledSelectorChecker, selectorData.compilationStatus); executeCompiledSimpleSelectorChecker(*searchRootNode, selectorChecker, output, selectorData); } else { ASSERT(selectorData.compilationStatus == SelectorCompilationStatus::SelectorCheckerWithCheckingContext); SelectorCompiler::QuerySelectorSelectorCheckerWithCheckingContext selectorChecker = SelectorCompiler::querySelectorSelectorCheckerFunctionWithCheckingContext(compiledSelectorChecker, selectorData.compilationStatus); executeCompiledSelectorCheckerWithCheckingContext(rootNode, *searchRootNode, selectorChecker, output, selectorData); } break; } #else case CompiledSingleWithRootFilter: case CompiledSingle: ASSERT_NOT_REACHED(); #if ASSERT_DISABLED FALLTHROUGH; #endif #endif // ENABLE(CSS_SELECTOR_JIT) case SingleSelectorWithRootFilter: SingleSelectorWithRootFilterCase: searchRootNode = &filterRootById(*searchRootNode, *m_selectors.first().selector); FALLTHROUGH; case SingleSelector: SingleSelectorCase: executeSingleSelectorData(rootNode, *searchRootNode, m_selectors.first(), output); break; case TagNameMatch: executeSingleTagNameSelectorData(*searchRootNode, m_selectors.first(), output); break; case ClassNameMatch: executeSingleClassNameSelectorData(*searchRootNode, m_selectors.first(), output); break; case CompilableMultipleSelectorMatch: #if ENABLE(CSS_SELECTOR_JIT) { for (auto& selector : m_selectors) { if (!compileSelector(selector, *searchRootNode)) { 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(*searchRootNode, output); break; #else FALLTHROUGH; #endif // ENABLE(CSS_SELECTOR_JIT) case MultipleSelectorMatch: #if ENABLE(CSS_SELECTOR_JIT) MultipleSelectorMatch: #endif executeSingleMultiSelectorData(*searchRootNode, output); break; } } SelectorQuery::SelectorQuery(CSSSelectorList&& selectorList) : m_selectorList(WTFMove(selectorList)) , m_selectors(m_selectorList) { } SelectorQuery* SelectorQueryCache::add(const String& selectors, Document& document, ExceptionCode& ec) { auto it = m_entries.find(selectors); if (it != m_entries.end()) return it->value.get(); CSSParser parser(document); CSSSelectorList selectorList; parser.parseSelector(selectors, selectorList); if (!selectorList.first() || selectorList.hasInvalidSelector()) { ec = SYNTAX_ERR; return nullptr; } // Throw a NAMESPACE_ERR if the selector includes any namespace prefixes. if (selectorList.selectorsNeedNamespaceResolution()) { ec = NAMESPACE_ERR; return nullptr; } const int maximumSelectorQueryCacheSize = 256; if (m_entries.size() == maximumSelectorQueryCacheSize) m_entries.remove(m_entries.begin()); return m_entries.add(selectors, std::make_unique(WTFMove(selectorList))).iterator->value.get(); } }