1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 2004-2005 Allan Sandfeld Jensen (kde (at) carewolf.com) 4 * Copyright (C) 2006, 2007 Nicholas Shanks (webkit (at) nickshanks.com) 5 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved. 6 * Copyright (C) 2007 Alexey Proskuryakov <ap (at) webkit.org> 7 * Copyright (C) 2007, 2008 Eric Seidel <eric (at) webkit.org> 8 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 9 * Copyright (c) 2011, Code Aurora Forum. All rights reserved. 10 * Copyright (C) Research In Motion Limited 2011. All rights reserved. 11 * Copyright (C) 2012 Google Inc. All rights reserved. 12 * 13 * This library is free software; you can redistribute it and/or 14 * modify it under the terms of the GNU Library General Public 15 * License as published by the Free Software Foundation; either 16 * version 2 of the License, or (at your option) any later version. 17 * 18 * This library is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 * Library General Public License for more details. 22 * 23 * You should have received a copy of the GNU Library General Public License 24 * along with this library; see the file COPYING.LIB. If not, write to 25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 26 * Boston, MA 02110-1301, USA. 27 */ 28 29 #include "config.h" 30 #include "core/css/ElementRuleCollector.h" 31 32 #include "core/css/CSSRuleList.h" 33 #include "core/css/CSSSelector.h" 34 #include "core/css/SelectorCheckerFastPath.h" 35 #include "core/css/SiblingTraversalStrategies.h" 36 #include "core/css/StylePropertySet.h" 37 #include "core/css/resolver/StyleResolver.h" 38 #include "core/dom/shadow/ShadowRoot.h" 39 #include "core/rendering/RenderRegion.h" 40 41 namespace WebCore { 42 43 ElementRuleCollector::ElementRuleCollector(const ElementResolveContext& context, 44 const SelectorFilter& filter, RenderStyle* style) 45 : m_context(context) 46 , m_selectorFilter(filter) 47 , m_style(style) 48 , m_regionForStyling(0) 49 , m_pseudoStyleRequest(NOPSEUDO) 50 , m_mode(SelectorChecker::ResolvingStyle) 51 , m_behaviorAtBoundary(SelectorChecker::DoesNotCrossBoundary) 52 , m_canUseFastReject(m_selectorFilter.parentStackIsConsistent(context.parentNode())) 53 , m_sameOriginOnly(false) 54 , m_matchingUARules(false) 55 { } 56 57 MatchResult& ElementRuleCollector::matchedResult() 58 { 59 return m_result; 60 } 61 62 PassRefPtr<CSSRuleList> ElementRuleCollector::matchedRuleList() 63 { 64 ASSERT(m_mode == SelectorChecker::CollectingRules); 65 return m_ruleList.release(); 66 } 67 68 inline void ElementRuleCollector::addMatchedRule(const RuleData* rule, CascadeScope cascadeScope, CascadeOrder cascadeOrder) 69 { 70 if (!m_matchedRules) 71 m_matchedRules = adoptPtr(new Vector<MatchedRule, 32>); 72 m_matchedRules->append(MatchedRule(rule, cascadeScope, cascadeOrder)); 73 } 74 75 void ElementRuleCollector::clearMatchedRules() 76 { 77 if (!m_matchedRules) 78 return; 79 m_matchedRules->clear(); 80 } 81 82 inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList() 83 { 84 if (!m_ruleList) 85 m_ruleList = StaticCSSRuleList::create(); 86 return m_ruleList.get(); 87 } 88 89 void ElementRuleCollector::addElementStyleProperties(const StylePropertySet* propertySet, bool isCacheable) 90 { 91 if (!propertySet) 92 return; 93 m_result.ranges.lastAuthorRule = m_result.matchedProperties.size(); 94 if (m_result.ranges.firstAuthorRule == -1) 95 m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule; 96 m_result.addMatchedProperties(propertySet); 97 if (!isCacheable) 98 m_result.isCacheable = false; 99 } 100 101 static bool rulesApplicableInCurrentTreeScope(const Element* element, const ContainerNode* scopingNode, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, bool elementApplyAuthorStyles) 102 { 103 TreeScope* treeScope = element->treeScope(); 104 105 // [skipped, because already checked] a) it's a UA rule 106 // b) element is allowed to apply author rules 107 if (elementApplyAuthorStyles) 108 return true; 109 // c) the rules comes from a scoped style sheet within the same tree scope 110 if (!scopingNode || treeScope == scopingNode->treeScope()) 111 return true; 112 // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element 113 if (element->isInShadowTree() && (behaviorAtBoundary & SelectorChecker::ScopeIsShadowHost) && scopingNode == element->containingShadowRoot()->host()) 114 return true; 115 // e) the rules can cross boundaries 116 if ((behaviorAtBoundary & SelectorChecker::BoundaryBehaviorMask) == SelectorChecker::CrossesBoundary) 117 return true; 118 return false; 119 } 120 121 void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, RuleRange& ruleRange, CascadeScope cascadeScope, CascadeOrder cascadeOrder) 122 { 123 ASSERT(matchRequest.ruleSet); 124 ASSERT(m_context.element()); 125 126 Element* element = m_context.element(); 127 const AtomicString& pseudoId = element->shadowPseudoId(); 128 if (!pseudoId.isEmpty()) { 129 ASSERT(element->isStyledElement()); 130 collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId.impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange); 131 } 132 133 if (element->isWebVTTElement()) 134 collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange); 135 // Check whether other types of rules are applicable in the current tree scope. Criteria for this: 136 // a) it's a UA rule 137 // b) the tree scope allows author rules 138 // c) the rules comes from a scoped style sheet within the same tree scope 139 // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element 140 // e) the rules can cross boundaries 141 // b)-e) is checked in rulesApplicableInCurrentTreeScope. 142 if (!m_matchingUARules && !rulesApplicableInCurrentTreeScope(element, matchRequest.scope, m_behaviorAtBoundary, matchRequest.elementApplyAuthorStyles)) 143 return; 144 145 // We need to collect the rules for id, class, tag, and everything else into a buffer and 146 // then sort the buffer. 147 if (element->hasID()) 148 collectMatchingRulesForList(matchRequest.ruleSet->idRules(element->idForStyleResolution().impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange); 149 if (element->isStyledElement() && element->hasClass()) { 150 for (size_t i = 0; i < element->classNames().size(); ++i) 151 collectMatchingRulesForList(matchRequest.ruleSet->classRules(element->classNames()[i].impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange); 152 } 153 154 if (element->isLink()) 155 collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange); 156 if (SelectorChecker::matchesFocusPseudoClass(element)) 157 collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange); 158 collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element->localName().impl()), cascadeScope, cascadeOrder, matchRequest, ruleRange); 159 collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), cascadeScope, cascadeOrder, matchRequest, ruleRange); 160 } 161 162 void ElementRuleCollector::collectMatchingRulesForRegion(const MatchRequest& matchRequest, RuleRange& ruleRange, CascadeScope cascadeScope, CascadeOrder cascadeOrder) 163 { 164 if (!m_regionForStyling) 165 return; 166 167 unsigned size = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.size(); 168 for (unsigned i = 0; i < size; ++i) { 169 const CSSSelector* regionSelector = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).selector; 170 if (checkRegionSelector(regionSelector, toElement(m_regionForStyling->node()))) { 171 RuleSet* regionRules = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).ruleSet.get(); 172 ASSERT(regionRules); 173 collectMatchingRules(MatchRequest(regionRules, matchRequest.includeEmptyRules, matchRequest.scope), ruleRange, cascadeScope, cascadeOrder); 174 } 175 } 176 } 177 178 void ElementRuleCollector::sortAndTransferMatchedRules() 179 { 180 if (!m_matchedRules || m_matchedRules->isEmpty()) 181 return; 182 183 sortMatchedRules(); 184 185 Vector<MatchedRule, 32>& matchedRules = *m_matchedRules; 186 if (m_mode == SelectorChecker::CollectingRules) { 187 for (unsigned i = 0; i < matchedRules.size(); ++i) 188 ensureRuleList()->rules().append(matchedRules[i].ruleData()->rule()->createCSSOMWrapper()); 189 return; 190 } 191 192 // Now transfer the set of matched rules over to our list of declarations. 193 for (unsigned i = 0; i < matchedRules.size(); i++) { 194 // FIXME: Matching should not modify the style directly. 195 const RuleData* ruleData = matchedRules[i].ruleData(); 196 if (m_style && ruleData->containsUncommonAttributeSelector()) 197 m_style->setUnique(); 198 m_result.addMatchedProperties(ruleData->rule()->properties(), ruleData->rule(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules)); 199 } 200 } 201 202 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, PseudoId& dynamicPseudo) 203 { 204 if (ruleData.hasFastCheckableSelector()) { 205 // We know this selector does not include any pseudo elements. 206 if (m_pseudoStyleRequest.pseudoId != NOPSEUDO) 207 return false; 208 // We know a sufficiently simple single part selector matches simply because we found it from the rule hash. 209 // This is limited to HTML only so we don't need to check the namespace. 210 if (ruleData.hasRightmostSelectorMatchingHTMLBasedOnRuleHash() && m_context.element()->isHTMLElement()) { 211 if (!ruleData.hasMultipartSelector()) 212 return true; 213 } 214 if (ruleData.selector()->m_match == CSSSelector::Tag && !SelectorChecker::tagMatches(m_context.element(), ruleData.selector()->tagQName())) 215 return false; 216 SelectorCheckerFastPath selectorCheckerFastPath(ruleData.selector(), m_context.element()); 217 if (!selectorCheckerFastPath.matchesRightmostAttributeSelector()) 218 return false; 219 220 return selectorCheckerFastPath.matches(); 221 } 222 223 // Slow path. 224 SelectorChecker selectorChecker(document(), m_mode); 225 SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled); 226 context.elementStyle = m_style.get(); 227 context.scope = scope; 228 context.pseudoId = m_pseudoStyleRequest.pseudoId; 229 context.scrollbar = m_pseudoStyleRequest.scrollbar; 230 context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart; 231 context.behaviorAtBoundary = m_behaviorAtBoundary; 232 SelectorChecker::Match match = selectorChecker.match(context, dynamicPseudo, DOMSiblingTraversalStrategy()); 233 if (match != SelectorChecker::SelectorMatches) 234 return false; 235 if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != dynamicPseudo) 236 return false; 237 return true; 238 } 239 240 void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange) 241 { 242 if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes())) 243 return; 244 245 StyleRule* rule = ruleData.rule(); 246 PseudoId dynamicPseudo = NOPSEUDO; 247 if (ruleMatches(ruleData, matchRequest.scope, dynamicPseudo)) { 248 // If the rule has no properties to apply, then ignore it in the non-debug mode. 249 const StylePropertySet* properties = rule->properties(); 250 if (!properties || (properties->isEmpty() && !matchRequest.includeEmptyRules)) 251 return; 252 // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed. 253 if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin()) 254 return; 255 // If we're matching normal rules, set a pseudo bit if 256 // we really just matched a pseudo-element. 257 if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) { 258 if (m_mode == SelectorChecker::CollectingRules) 259 return; 260 // FIXME: Matching should not modify the style directly. 261 if (dynamicPseudo < FIRST_INTERNAL_PSEUDOID) 262 m_style->setHasPseudoStyle(dynamicPseudo); 263 } else { 264 // Update our first/last rule indices in the matched rules array. 265 ++ruleRange.lastRuleIndex; 266 if (ruleRange.firstRuleIndex == -1) 267 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex; 268 269 // Add this rule to our list of matched rules. 270 addMatchedRule(&ruleData, cascadeScope, cascadeOrder); 271 return; 272 } 273 } 274 } 275 276 void ElementRuleCollector::collectMatchingRulesForList(const RuleData* rules, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange) 277 { 278 if (!rules) 279 return; 280 while (!rules->isLastInArray()) 281 collectRuleIfMatches(*rules++, cascadeScope, cascadeOrder, matchRequest, ruleRange); 282 collectRuleIfMatches(*rules, cascadeScope, cascadeOrder, matchRequest, ruleRange); 283 } 284 285 void ElementRuleCollector::collectMatchingRulesForList(const Vector<RuleData>* rules, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange) 286 { 287 if (!rules) 288 return; 289 unsigned size = rules->size(); 290 for (unsigned i = 0; i < size; ++i) 291 collectRuleIfMatches(rules->at(i), cascadeScope, cascadeOrder, matchRequest, ruleRange); 292 } 293 294 static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2) 295 { 296 unsigned specificity1 = matchedRule1.ruleData()->specificity(); 297 unsigned specificity2 = matchedRule2.ruleData()->specificity(); 298 return matchedRule1.cascadeScope() == matchedRule2.cascadeScope() ? 299 ((specificity1 == specificity2) ? matchedRule1.position() < matchedRule2.position() : specificity1 < specificity2) : 300 matchedRule1.cascadeScope() < matchedRule2.cascadeScope(); 301 } 302 303 void ElementRuleCollector::sortMatchedRules() 304 { 305 ASSERT(m_matchedRules); 306 std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules); 307 } 308 309 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet) 310 { 311 clearMatchedRules(); 312 313 m_mode = SelectorChecker::SharingRules; 314 // To check whether a given RuleSet has any rule matching a given element, 315 // should not see the element's treescope. Because RuleSet has no 316 // information about "scope". 317 m_behaviorAtBoundary = SelectorChecker::StaysWithinTreeScope; 318 int firstRuleIndex = -1, lastRuleIndex = -1; 319 RuleRange ruleRange(firstRuleIndex, lastRuleIndex); 320 // FIXME: Verify whether it's ok to ignore CascadeScope here. 321 collectMatchingRules(MatchRequest(ruleSet), ruleRange); 322 323 return m_matchedRules && !m_matchedRules->isEmpty(); 324 } 325 326 } // namespace WebCore 327