/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com) * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Apple Inc. All rights reserved. * Copyright (C) 2007 Alexey Proskuryakov * Copyright (C) 2007, 2008 Eric Seidel * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * Copyright (c) 2011, Code Aurora Forum. All rights reserved. * Copyright (C) Research In Motion Limited 2011. All rights reserved. * Copyright (C) 2012 Google Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "ElementRuleCollector.h" #include "CSSDefaultStyleSheets.h" #include "CSSRule.h" #include "CSSRuleList.h" #include "CSSSelector.h" #include "CSSSelectorList.h" #include "CSSValueKeywords.h" #include "HTMLElement.h" #include "HTMLSlotElement.h" #include "NodeRenderStyle.h" #include "RenderRegion.h" #include "SVGElement.h" #include "SelectorCompiler.h" #include "SelectorFilter.h" #include "ShadowRoot.h" #include "StyleProperties.h" #include "StyleScope.h" #include "StyledElement.h" #include namespace WebCore { static const StyleProperties& leftToRightDeclaration() { static NeverDestroyed> leftToRightDecl(MutableStyleProperties::create()); if (leftToRightDecl.get()->isEmpty()) leftToRightDecl.get()->setProperty(CSSPropertyDirection, CSSValueLtr); return leftToRightDecl.get(); } static const StyleProperties& rightToLeftDeclaration() { static NeverDestroyed> rightToLeftDecl(MutableStyleProperties::create()); if (rightToLeftDecl.get()->isEmpty()) rightToLeftDecl.get()->setProperty(CSSPropertyDirection, CSSValueRtl); return rightToLeftDecl.get(); } class MatchRequest { public: MatchRequest(const RuleSet* ruleSet, bool includeEmptyRules = false, Style::ScopeOrdinal styleScopeOrdinal = Style::ScopeOrdinal::Element) : ruleSet(ruleSet) , includeEmptyRules(includeEmptyRules) , styleScopeOrdinal(styleScopeOrdinal) { } const RuleSet* ruleSet; const bool includeEmptyRules; Style::ScopeOrdinal styleScopeOrdinal; }; ElementRuleCollector::ElementRuleCollector(const Element& element, const DocumentRuleSets& ruleSets, const SelectorFilter* selectorFilter) : m_element(element) , m_authorStyle(ruleSets.authorStyle()) , m_userStyle(ruleSets.userStyle()) , m_selectorFilter(selectorFilter) { ASSERT(!m_selectorFilter || m_selectorFilter->parentStackIsConsistent(element.parentNode())); } ElementRuleCollector::ElementRuleCollector(const Element& element, const RuleSet& authorStyle, const SelectorFilter* selectorFilter) : m_element(element) , m_authorStyle(authorStyle) , m_selectorFilter(selectorFilter) { ASSERT(!m_selectorFilter || m_selectorFilter->parentStackIsConsistent(element.parentNode())); } StyleResolver::MatchResult& ElementRuleCollector::matchedResult() { ASSERT(m_mode == SelectorChecker::Mode::ResolvingStyle); return m_result; } const Vector>& ElementRuleCollector::matchedRuleList() const { ASSERT(m_mode == SelectorChecker::Mode::CollectingRules); return m_matchedRuleList; } inline void ElementRuleCollector::addMatchedRule(const RuleData& ruleData, unsigned specificity, Style::ScopeOrdinal styleScopeOrdinal, StyleResolver::RuleRange& ruleRange) { // Update our first/last rule indices in the matched rules array. ++ruleRange.lastRuleIndex; if (ruleRange.firstRuleIndex == -1) ruleRange.firstRuleIndex = ruleRange.lastRuleIndex; m_matchedRules.append({ &ruleData, specificity, styleScopeOrdinal }); } void ElementRuleCollector::clearMatchedRules() { m_matchedRules.clear(); m_keepAliveSlottedPseudoElementRules.clear(); } inline void ElementRuleCollector::addElementStyleProperties(const StyleProperties* propertySet, bool isCacheable) { if (!propertySet) return; m_result.ranges.lastAuthorRule = m_result.matchedProperties().size(); if (m_result.ranges.firstAuthorRule == -1) m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule; m_result.addMatchedProperties(*propertySet); if (!isCacheable) m_result.isCacheable = false; } void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) { ASSERT(matchRequest.ruleSet); ASSERT_WITH_MESSAGE(!(m_mode == SelectorChecker::Mode::CollectingRulesIgnoringVirtualPseudoElements && m_pseudoStyleRequest.pseudoId != NOPSEUDO), "When in StyleInvalidation or SharingRules, SelectorChecker does not try to match the pseudo ID. While ElementRuleCollector supports matching a particular pseudoId in this case, this would indicate a error at the call site since matching a particular element should be unnecessary."); auto* shadowRoot = m_element.containingShadowRoot(); if (shadowRoot && shadowRoot->mode() == ShadowRootMode::UserAgent) collectMatchingShadowPseudoElementRules(matchRequest, ruleRange); // We need to collect the rules for id, class, tag, and everything else into a buffer and // then sort the buffer. auto& id = m_element.idForStyleResolution(); if (!id.isNull()) collectMatchingRulesForList(matchRequest.ruleSet->idRules(id), matchRequest, ruleRange); if (m_element.hasClass()) { for (size_t i = 0; i < m_element.classNames().size(); ++i) collectMatchingRulesForList(matchRequest.ruleSet->classRules(m_element.classNames()[i]), matchRequest, ruleRange); } if (m_element.isLink()) collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), matchRequest, ruleRange); if (SelectorChecker::matchesFocusPseudoClass(m_element)) collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), matchRequest, ruleRange); collectMatchingRulesForList(matchRequest.ruleSet->tagRules(m_element.localName(), m_element.isHTMLElement() && m_element.document().isHTMLDocument()), matchRequest, ruleRange); collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), matchRequest, ruleRange); } void ElementRuleCollector::collectMatchingRulesForRegion(const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) { if (!m_regionForStyling) return; unsigned size = matchRequest.ruleSet->regionSelectorsAndRuleSets().size(); for (unsigned i = 0; i < size; ++i) { const CSSSelector* regionSelector = matchRequest.ruleSet->regionSelectorsAndRuleSets().at(i).selector; if (checkRegionSelector(regionSelector, m_regionForStyling->generatingElement())) { RuleSet* regionRules = matchRequest.ruleSet->regionSelectorsAndRuleSets().at(i).ruleSet.get(); ASSERT(regionRules); collectMatchingRules(MatchRequest(regionRules, matchRequest.includeEmptyRules), ruleRange); } } } void ElementRuleCollector::sortAndTransferMatchedRules() { if (m_matchedRules.isEmpty()) return; sortMatchedRules(); if (m_mode == SelectorChecker::Mode::CollectingRules) { for (const MatchedRule& matchedRule : m_matchedRules) m_matchedRuleList.append(matchedRule.ruleData->rule()); return; } for (const MatchedRule& matchedRule : m_matchedRules) { m_result.addMatchedProperties(matchedRule.ruleData->rule()->properties(), matchedRule.ruleData->rule(), matchedRule.ruleData->linkMatchType(), matchedRule.ruleData->propertyWhitelistType(), matchedRule.styleScopeOrdinal); } } void ElementRuleCollector::matchAuthorRules(bool includeEmptyRules) { clearMatchedRules(); m_result.ranges.lastAuthorRule = m_result.matchedProperties().size() - 1; StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange(); { MatchRequest matchRequest(&m_authorStyle, includeEmptyRules); collectMatchingRules(matchRequest, ruleRange); collectMatchingRulesForRegion(matchRequest, ruleRange); } auto* parent = m_element.parentElement(); if (parent && parent->shadowRoot()) matchSlottedPseudoElementRules(includeEmptyRules, ruleRange); if (m_element.shadowRoot()) matchHostPseudoClassRules(includeEmptyRules, ruleRange); if (m_element.isInShadowTree()) matchAuthorShadowPseudoElementRules(includeEmptyRules, ruleRange); sortAndTransferMatchedRules(); } void ElementRuleCollector::matchAuthorShadowPseudoElementRules(bool includeEmptyRules, StyleResolver::RuleRange& ruleRange) { ASSERT(m_element.isInShadowTree()); auto& shadowRoot = *m_element.containingShadowRoot(); if (shadowRoot.mode() != ShadowRootMode::UserAgent) return; // Look up shadow pseudo elements also from the host scope author style as they are web-exposed. auto& hostAuthorRules = Style::Scope::forNode(*shadowRoot.host()).resolver().ruleSets().authorStyle(); MatchRequest hostAuthorRequest { &hostAuthorRules, includeEmptyRules, Style::ScopeOrdinal::ContainingHost }; collectMatchingShadowPseudoElementRules(hostAuthorRequest, ruleRange); } void ElementRuleCollector::matchHostPseudoClassRules(bool includeEmptyRules, StyleResolver::RuleRange& ruleRange) { ASSERT(m_element.shadowRoot()); auto& shadowAuthorStyle = m_element.shadowRoot()->styleScope().resolver().ruleSets().authorStyle(); auto& shadowHostRules = shadowAuthorStyle.hostPseudoClassRules(); if (shadowHostRules.isEmpty()) return; SetForScope change(m_isMatchingHostPseudoClass, true); MatchRequest hostMatchRequest { nullptr, includeEmptyRules, Style::ScopeOrdinal::Shadow }; collectMatchingRulesForList(&shadowHostRules, hostMatchRequest, ruleRange); } void ElementRuleCollector::matchSlottedPseudoElementRules(bool includeEmptyRules, StyleResolver::RuleRange& ruleRange) { auto* slot = m_element.assignedSlot(); auto styleScopeOrdinal = Style::ScopeOrdinal::FirstSlot; for (; slot; slot = slot->assignedSlot(), ++styleScopeOrdinal) { auto& styleScope = Style::Scope::forNode(*slot); if (!styleScope.resolver().ruleSets().isAuthorStyleDefined()) continue; // Find out if there are any ::slotted rules in the shadow tree matching the current slot. // FIXME: This is really part of the slot style and could be cached when resolving it. ElementRuleCollector collector(*slot, styleScope.resolver().ruleSets().authorStyle(), nullptr); auto slottedPseudoElementRules = collector.collectSlottedPseudoElementRulesForSlot(includeEmptyRules); if (!slottedPseudoElementRules) continue; // Match in the current scope. SetForScope change(m_isMatchingSlottedPseudoElements, true); MatchRequest scopeMatchRequest(nullptr, includeEmptyRules, styleScopeOrdinal); collectMatchingRulesForList(slottedPseudoElementRules.get(), scopeMatchRequest, ruleRange); m_keepAliveSlottedPseudoElementRules.append(WTFMove(slottedPseudoElementRules)); } } void ElementRuleCollector::collectMatchingShadowPseudoElementRules(const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) { ASSERT(matchRequest.ruleSet); ASSERT(m_element.containingShadowRoot()->mode() == ShadowRootMode::UserAgent); auto& rules = *matchRequest.ruleSet; #if ENABLE(VIDEO_TRACK) // FXIME: WebVTT should not be done by styling UA shadow trees like this. if (m_element.isWebVTTElement()) collectMatchingRulesForList(rules.cuePseudoRules(), matchRequest, ruleRange); #endif auto& pseudoId = m_element.shadowPseudoId(); if (!pseudoId.isEmpty()) collectMatchingRulesForList(rules.shadowPseudoElementRules(pseudoId), matchRequest, ruleRange); } std::unique_ptr ElementRuleCollector::collectSlottedPseudoElementRulesForSlot(bool includeEmptyRules) { ASSERT(is(m_element)); clearMatchedRules(); m_mode = SelectorChecker::Mode::CollectingRules; // Match global author rules. MatchRequest matchRequest(&m_authorStyle, includeEmptyRules); StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange(); collectMatchingRulesForList(&m_authorStyle.slottedPseudoElementRules(), matchRequest, ruleRange); if (m_matchedRules.isEmpty()) return { }; auto ruleDataVector = std::make_unique(); ruleDataVector->reserveInitialCapacity(m_matchedRules.size()); for (auto& matchedRule : m_matchedRules) ruleDataVector->uncheckedAppend(*matchedRule.ruleData); return ruleDataVector; } void ElementRuleCollector::matchUserRules(bool includeEmptyRules) { if (!m_userStyle) return; clearMatchedRules(); m_result.ranges.lastUserRule = m_result.matchedProperties().size() - 1; MatchRequest matchRequest(m_userStyle, includeEmptyRules); StyleResolver::RuleRange ruleRange = m_result.ranges.userRuleRange(); collectMatchingRules(matchRequest, ruleRange); collectMatchingRulesForRegion(matchRequest, ruleRange); sortAndTransferMatchedRules(); } void ElementRuleCollector::matchUARules() { // First we match rules from the user agent sheet. if (CSSDefaultStyleSheets::simpleDefaultStyleSheet) m_result.isCacheable = false; RuleSet* userAgentStyleSheet = m_isPrintStyle ? CSSDefaultStyleSheets::defaultPrintStyle : CSSDefaultStyleSheets::defaultStyle; matchUARules(userAgentStyleSheet); // In quirks mode, we match rules from the quirks user agent sheet. if (m_element.document().inQuirksMode()) matchUARules(CSSDefaultStyleSheets::defaultQuirksStyle); } void ElementRuleCollector::matchUARules(RuleSet* rules) { clearMatchedRules(); m_result.ranges.lastUARule = m_result.matchedProperties().size() - 1; StyleResolver::RuleRange ruleRange = m_result.ranges.UARuleRange(); collectMatchingRules(MatchRequest(rules), ruleRange); sortAndTransferMatchedRules(); } static const CSSSelector* findSlottedPseudoElementSelector(const CSSSelector* selector) { for (; selector; selector = selector->tagHistory()) { if (selector->match() == CSSSelector::PseudoElement && selector->pseudoElementType() == CSSSelector::PseudoElementSlotted) { if (auto* list = selector->selectorList()) return list->first(); break; } }; return nullptr; } inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, unsigned& specificity) { // We know a sufficiently simple single part selector matches simply because we found it from the rule hash when filtering the RuleSet. // This is limited to HTML only so we don't need to check the namespace (because of tag name match). MatchBasedOnRuleHash matchBasedOnRuleHash = ruleData.matchBasedOnRuleHash(); if (matchBasedOnRuleHash != MatchBasedOnRuleHash::None && m_element.isHTMLElement()) { ASSERT_WITH_MESSAGE(m_pseudoStyleRequest.pseudoId == NOPSEUDO, "If we match based on the rule hash while collecting for a particular pseudo element ID, we would add incorrect rules for that pseudo element ID. We should never end in ruleMatches() with a pseudo element if the ruleData cannot match any pseudo element."); switch (matchBasedOnRuleHash) { case MatchBasedOnRuleHash::None: ASSERT_NOT_REACHED(); break; case MatchBasedOnRuleHash::Universal: specificity = 0; break; case MatchBasedOnRuleHash::ClassA: specificity = static_cast(SelectorSpecificityIncrement::ClassA); break; case MatchBasedOnRuleHash::ClassB: specificity = static_cast(SelectorSpecificityIncrement::ClassB); break; case MatchBasedOnRuleHash::ClassC: specificity = static_cast(SelectorSpecificityIncrement::ClassC); break; } return true; } #if ENABLE(CSS_SELECTOR_JIT) void* compiledSelectorChecker = ruleData.compiledSelectorCodeRef().code().executableAddress(); if (!compiledSelectorChecker && ruleData.compilationStatus() == SelectorCompilationStatus::NotCompiled) { JSC::VM& vm = m_element.document().scriptExecutionContext()->vm(); SelectorCompilationStatus compilationStatus; JSC::MacroAssemblerCodeRef compiledSelectorCodeRef; compilationStatus = SelectorCompiler::compileSelector(ruleData.selector(), &vm, SelectorCompiler::SelectorContext::RuleCollector, compiledSelectorCodeRef); ruleData.setCompiledSelector(compilationStatus, compiledSelectorCodeRef); compiledSelectorChecker = ruleData.compiledSelectorCodeRef().code().executableAddress(); } if (compiledSelectorChecker && ruleData.compilationStatus() == SelectorCompilationStatus::SimpleSelectorChecker) { SelectorCompiler::RuleCollectorSimpleSelectorChecker selectorChecker = SelectorCompiler::ruleCollectorSimpleSelectorCheckerFunction(compiledSelectorChecker, ruleData.compilationStatus()); #if !ASSERT_MSG_DISABLED unsigned ignoreSpecificity; ASSERT_WITH_MESSAGE(!selectorChecker(&m_element, &ignoreSpecificity) || m_pseudoStyleRequest.pseudoId == NOPSEUDO, "When matching pseudo elements, we should never compile a selector checker without context unless it cannot match anything."); #endif #if CSS_SELECTOR_JIT_PROFILING ruleData.compiledSelectorUsed(); #endif bool selectorMatches = selectorChecker(&m_element, &specificity); if (selectorMatches && ruleData.containsUncommonAttributeSelector()) m_didMatchUncommonAttributeSelector = true; return selectorMatches; } #endif // ENABLE(CSS_SELECTOR_JIT) SelectorChecker::CheckingContext context(m_mode); context.pseudoId = m_pseudoStyleRequest.pseudoId; context.scrollbar = m_pseudoStyleRequest.scrollbar; context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart; context.isMatchingHostPseudoClass = m_isMatchingHostPseudoClass; bool selectorMatches; #if ENABLE(CSS_SELECTOR_JIT) if (compiledSelectorChecker) { ASSERT(ruleData.compilationStatus() == SelectorCompilationStatus::SelectorCheckerWithCheckingContext); SelectorCompiler::RuleCollectorSelectorCheckerWithCheckingContext selectorChecker = SelectorCompiler::ruleCollectorSelectorCheckerFunctionWithCheckingContext(compiledSelectorChecker, ruleData.compilationStatus()); #if CSS_SELECTOR_JIT_PROFILING ruleData.compiledSelectorUsed(); #endif selectorMatches = selectorChecker(&m_element, &context, &specificity); } else #endif // ENABLE(CSS_SELECTOR_JIT) { auto* selector = ruleData.selector(); if (m_isMatchingSlottedPseudoElements) { selector = findSlottedPseudoElementSelector(ruleData.selector()); if (!selector) return false; } // Slow path. SelectorChecker selectorChecker(m_element.document()); selectorMatches = selectorChecker.match(*selector, m_element, context, specificity); } if (ruleData.containsUncommonAttributeSelector()) { if (selectorMatches || context.pseudoIDSet) m_didMatchUncommonAttributeSelector = true; } m_matchedPseudoElementIds.merge(context.pseudoIDSet); m_styleRelations.appendVector(context.styleRelations); return selectorMatches; } void ElementRuleCollector::collectMatchingRulesForList(const RuleSet::RuleDataVector* rules, const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) { if (!rules) return; for (unsigned i = 0, size = rules->size(); i < size; ++i) { const RuleData& ruleData = rules->data()[i]; if (!ruleData.canMatchPseudoElement() && m_pseudoStyleRequest.pseudoId != NOPSEUDO) continue; if (m_selectorFilter && m_selectorFilter->fastRejectSelector(ruleData.descendantSelectorIdentifierHashes())) continue; StyleRule* rule = ruleData.rule(); // If the rule has no properties to apply, then ignore it in the non-debug mode. // Note that if we get null back here, it means we have a rule with deferred properties, // and that means we always have to consider it. const StyleProperties* properties = rule->propertiesWithoutDeferredParsing(); if (properties && properties->isEmpty() && !matchRequest.includeEmptyRules) continue; // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed. if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin()) continue; unsigned specificity; if (ruleMatches(ruleData, specificity)) addMatchedRule(ruleData, specificity, matchRequest.styleScopeOrdinal, ruleRange); } } static inline bool compareRules(MatchedRule r1, MatchedRule r2) { // For normal properties the earlier scope wins. This may be reversed by !important which is handled when resolving cascade. if (r1.styleScopeOrdinal != r2.styleScopeOrdinal) return r1.styleScopeOrdinal > r2.styleScopeOrdinal; if (r1.specificity != r2.specificity) return r1.specificity < r2.specificity; return r1.ruleData->position() < r2.ruleData->position(); } void ElementRuleCollector::sortMatchedRules() { std::sort(m_matchedRules.begin(), m_matchedRules.end(), compareRules); } void ElementRuleCollector::matchAllRules(bool matchAuthorAndUserStyles, bool includeSMILProperties) { matchUARules(); // Now we check user sheet rules. if (matchAuthorAndUserStyles) matchUserRules(false); // Now check author rules, beginning first with presentational attributes mapped from HTML. if (is(m_element)) { auto& styledElement = downcast(m_element); addElementStyleProperties(styledElement.presentationAttributeStyle()); // Now we check additional mapped declarations. // Tables and table cells share an additional mapped rule that must be applied // after all attributes, since their mapped style depends on the values of multiple attributes. addElementStyleProperties(styledElement.additionalPresentationAttributeStyle()); if (is(styledElement)) { bool isAuto; TextDirection textDirection = downcast(styledElement).directionalityIfhasDirAutoAttribute(isAuto); if (isAuto) m_result.addMatchedProperties(textDirection == LTR ? leftToRightDeclaration() : rightToLeftDeclaration()); } } // Check the rules in author sheets next. if (matchAuthorAndUserStyles) matchAuthorRules(false); if (matchAuthorAndUserStyles && is(m_element)) { auto& styledElement = downcast(m_element); // Now check our inline style attribute. if (styledElement.inlineStyle()) { // Inline style is immutable as long as there is no CSSOM wrapper. // FIXME: Media control shadow trees seem to have problems with caching. bool isInlineStyleCacheable = !styledElement.inlineStyle()->isMutable() && !styledElement.isInShadowTree(); // FIXME: Constify. addElementStyleProperties(styledElement.inlineStyle(), isInlineStyleCacheable); } // Now check SMIL animation override style. if (includeSMILProperties && is(styledElement)) addElementStyleProperties(downcast(styledElement).animatedSMILStyleProperties(), false /* isCacheable */); } } bool ElementRuleCollector::hasAnyMatchingRules(const RuleSet* ruleSet) { clearMatchedRules(); m_mode = SelectorChecker::Mode::CollectingRulesIgnoringVirtualPseudoElements; int firstRuleIndex = -1, lastRuleIndex = -1; StyleResolver::RuleRange ruleRange(firstRuleIndex, lastRuleIndex); collectMatchingRules(MatchRequest(ruleSet), ruleRange); return !m_matchedRules.isEmpty(); } } // namespace WebCore