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/CSSImportRule.h" 33 #include "core/css/CSSKeyframesRule.h" 34 #include "core/css/CSSMediaRule.h" 35 #include "core/css/CSSRuleList.h" 36 #include "core/css/CSSSelector.h" 37 #include "core/css/CSSStyleRule.h" 38 #include "core/css/CSSStyleSheet.h" 39 #include "core/css/CSSSupportsRule.h" 40 #include "core/css/SiblingTraversalStrategies.h" 41 #include "core/css/StylePropertySet.h" 42 #include "core/css/resolver/StyleResolver.h" 43 #include "core/dom/shadow/ShadowRoot.h" 44 #include "core/rendering/style/StyleInheritedData.h" 45 46 namespace WebCore { 47 48 ElementRuleCollector::ElementRuleCollector(const ElementResolveContext& context, 49 const SelectorFilter& filter, RenderStyle* style) 50 : m_context(context) 51 , m_selectorFilter(filter) 52 , m_style(style) 53 , m_pseudoStyleRequest(NOPSEUDO) 54 , m_mode(SelectorChecker::ResolvingStyle) 55 , m_canUseFastReject(m_selectorFilter.parentStackIsConsistent(context.parentNode())) 56 , m_sameOriginOnly(false) 57 , m_matchingUARules(false) 58 { } 59 60 ElementRuleCollector::~ElementRuleCollector() 61 { 62 } 63 64 MatchResult& ElementRuleCollector::matchedResult() 65 { 66 return m_result; 67 } 68 69 PassRefPtrWillBeRawPtr<StyleRuleList> ElementRuleCollector::matchedStyleRuleList() 70 { 71 ASSERT(m_mode == SelectorChecker::CollectingStyleRules); 72 return m_styleRuleList.release(); 73 } 74 75 PassRefPtrWillBeRawPtr<CSSRuleList> ElementRuleCollector::matchedCSSRuleList() 76 { 77 ASSERT(m_mode == SelectorChecker::CollectingCSSRules); 78 return m_cssRuleList.release(); 79 } 80 81 inline void ElementRuleCollector::addMatchedRule(const RuleData* rule, unsigned specificity, CascadeScope cascadeScope, CascadeOrder cascadeOrder, unsigned styleSheetIndex, const CSSStyleSheet* parentStyleSheet) 82 { 83 if (!m_matchedRules) 84 m_matchedRules = adoptPtrWillBeNoop(new WillBeHeapVector<MatchedRule, 32>); 85 m_matchedRules->append(MatchedRule(rule, specificity, cascadeScope, cascadeOrder, styleSheetIndex, parentStyleSheet)); 86 } 87 88 void ElementRuleCollector::clearMatchedRules() 89 { 90 if (!m_matchedRules) 91 return; 92 m_matchedRules->clear(); 93 } 94 95 inline StyleRuleList* ElementRuleCollector::ensureStyleRuleList() 96 { 97 if (!m_styleRuleList) 98 m_styleRuleList = StyleRuleList::create(); 99 return m_styleRuleList.get(); 100 } 101 102 inline StaticCSSRuleList* ElementRuleCollector::ensureRuleList() 103 { 104 if (!m_cssRuleList) 105 m_cssRuleList = StaticCSSRuleList::create(); 106 return m_cssRuleList.get(); 107 } 108 109 void ElementRuleCollector::addElementStyleProperties(const StylePropertySet* propertySet, bool isCacheable) 110 { 111 if (!propertySet) 112 return; 113 m_result.ranges.lastAuthorRule = m_result.matchedProperties.size(); 114 if (m_result.ranges.firstAuthorRule == -1) 115 m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule; 116 m_result.addMatchedProperties(propertySet); 117 if (!isCacheable) 118 m_result.isCacheable = false; 119 } 120 121 static bool rulesApplicableInCurrentTreeScope(const Element* element, const ContainerNode* scopingNode, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, bool elementApplyAuthorStyles) 122 { 123 TreeScope& treeScope = element->treeScope(); 124 125 // [skipped, because already checked] a) it's a UA rule 126 // b) element is allowed to apply author rules 127 if (elementApplyAuthorStyles) 128 return true; 129 // c) the rules comes from a scoped style sheet within the same tree scope 130 if (!scopingNode || treeScope == scopingNode->treeScope()) 131 return true; 132 // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element 133 if (SelectorChecker::isHostInItsShadowTree(*element, behaviorAtBoundary, scopingNode)) 134 return true; 135 return false; 136 } 137 138 void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, RuleRange& ruleRange, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder) 139 { 140 ASSERT(matchRequest.ruleSet); 141 ASSERT(m_context.element()); 142 143 Element& element = *m_context.element(); 144 const AtomicString& pseudoId = element.shadowPseudoId(); 145 if (!pseudoId.isEmpty()) { 146 ASSERT(element.isStyledElement()); 147 collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId), behaviorAtBoundary, ignoreCascadeScope, cascadeOrder, matchRequest, ruleRange); 148 } 149 150 if (element.isVTTElement()) 151 collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange); 152 // Check whether other types of rules are applicable in the current tree scope. Criteria for this: 153 // a) it's a UA rule 154 // b) the tree scope allows author rules 155 // c) the rules comes from a scoped style sheet within the same tree scope 156 // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element 157 // e) the rules can cross boundaries 158 // b)-e) is checked in rulesApplicableInCurrentTreeScope. 159 if (!m_matchingUARules && !rulesApplicableInCurrentTreeScope(&element, matchRequest.scope, behaviorAtBoundary, matchRequest.elementApplyAuthorStyles)) 160 return; 161 162 // We need to collect the rules for id, class, tag, and everything else into a buffer and 163 // then sort the buffer. 164 if (element.hasID()) 165 collectMatchingRulesForList(matchRequest.ruleSet->idRules(element.idForStyleResolution()), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange); 166 if (element.isStyledElement() && element.hasClass()) { 167 for (size_t i = 0; i < element.classNames().size(); ++i) 168 collectMatchingRulesForList(matchRequest.ruleSet->classRules(element.classNames()[i]), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange); 169 } 170 171 if (element.isLink()) 172 collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange); 173 if (SelectorChecker::matchesFocusPseudoClass(element)) 174 collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange); 175 collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element.localName()), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange); 176 collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), behaviorAtBoundary, cascadeScope, cascadeOrder, matchRequest, ruleRange); 177 } 178 179 CSSRuleList* ElementRuleCollector::nestedRuleList(CSSRule* rule) 180 { 181 switch (rule->type()) { 182 case CSSRule::MEDIA_RULE: 183 return toCSSMediaRule(rule)->cssRules(); 184 case CSSRule::KEYFRAMES_RULE: 185 return toCSSKeyframesRule(rule)->cssRules(); 186 case CSSRule::SUPPORTS_RULE: 187 return toCSSSupportsRule(rule)->cssRules(); 188 default: 189 return 0; 190 } 191 } 192 193 template<class CSSRuleCollection> 194 CSSRule* ElementRuleCollector::findStyleRule(CSSRuleCollection* cssRules, StyleRule* styleRule) 195 { 196 if (!cssRules) 197 return 0; 198 CSSRule* result = 0; 199 for (unsigned i = 0; i < cssRules->length() && !result; ++i) { 200 CSSRule* cssRule = cssRules->item(i); 201 CSSRule::Type cssRuleType = cssRule->type(); 202 if (cssRuleType == CSSRule::STYLE_RULE) { 203 CSSStyleRule* cssStyleRule = toCSSStyleRule(cssRule); 204 if (cssStyleRule->styleRule() == styleRule) 205 result = cssRule; 206 } else if (cssRuleType == CSSRule::IMPORT_RULE) { 207 CSSImportRule* cssImportRule = toCSSImportRule(cssRule); 208 result = findStyleRule(cssImportRule->styleSheet(), styleRule); 209 } else { 210 result = findStyleRule(nestedRuleList(cssRule), styleRule); 211 } 212 } 213 return result; 214 } 215 216 void ElementRuleCollector::appendCSSOMWrapperForRule(CSSStyleSheet* parentStyleSheet, StyleRule* rule) 217 { 218 // |parentStyleSheet| is 0 if and only if the |rule| is coming from User Agent. In this case, 219 // it is safe to create CSSOM wrappers without parentStyleSheets as they will be used only 220 // by inspector which will not try to edit them. 221 RefPtrWillBeRawPtr<CSSRule> cssRule = nullptr; 222 if (parentStyleSheet) 223 cssRule = findStyleRule(parentStyleSheet, rule); 224 else 225 cssRule = rule->createCSSOMWrapper(); 226 ASSERT(!parentStyleSheet || cssRule); 227 ensureRuleList()->rules().append(cssRule); 228 } 229 230 void ElementRuleCollector::sortAndTransferMatchedRules() 231 { 232 if (!m_matchedRules || m_matchedRules->isEmpty()) 233 return; 234 235 sortMatchedRules(); 236 237 WillBeHeapVector<MatchedRule, 32>& matchedRules = *m_matchedRules; 238 if (m_mode == SelectorChecker::CollectingStyleRules) { 239 for (unsigned i = 0; i < matchedRules.size(); ++i) 240 ensureStyleRuleList()->m_list.append(matchedRules[i].ruleData()->rule()); 241 return; 242 } 243 244 if (m_mode == SelectorChecker::CollectingCSSRules) { 245 for (unsigned i = 0; i < matchedRules.size(); ++i) 246 appendCSSOMWrapperForRule(const_cast<CSSStyleSheet*>(matchedRules[i].parentStyleSheet()), matchedRules[i].ruleData()->rule()); 247 return; 248 } 249 250 // Now transfer the set of matched rules over to our list of declarations. 251 for (unsigned i = 0; i < matchedRules.size(); i++) { 252 // FIXME: Matching should not modify the style directly. 253 const RuleData* ruleData = matchedRules[i].ruleData(); 254 if (m_style && ruleData->containsUncommonAttributeSelector()) 255 m_style->setUnique(); 256 m_result.addMatchedProperties(&ruleData->rule()->properties(), ruleData->rule(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules)); 257 } 258 } 259 260 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, SelectorChecker::MatchResult* result) 261 { 262 SelectorChecker selectorChecker(m_context.element()->document(), m_mode); 263 SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled); 264 context.elementStyle = m_style.get(); 265 context.scope = scope; 266 context.pseudoId = m_pseudoStyleRequest.pseudoId; 267 context.scrollbar = m_pseudoStyleRequest.scrollbar; 268 context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart; 269 context.behaviorAtBoundary = behaviorAtBoundary; 270 SelectorChecker::Match match = selectorChecker.match(context, DOMSiblingTraversalStrategy(), result); 271 if (match != SelectorChecker::SelectorMatches) 272 return false; 273 if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != result->dynamicPseudo) 274 return false; 275 return true; 276 } 277 278 void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, SelectorChecker::BehaviorAtBoundary behaviorAtBoundary, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange) 279 { 280 if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes())) 281 return; 282 283 StyleRule* rule = ruleData.rule(); 284 SelectorChecker::MatchResult result; 285 if (ruleMatches(ruleData, matchRequest.scope, behaviorAtBoundary, &result)) { 286 // If the rule has no properties to apply, then ignore it in the non-debug mode. 287 const StylePropertySet& properties = rule->properties(); 288 if (properties.isEmpty() && !matchRequest.includeEmptyRules) 289 return; 290 // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed. 291 if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin()) 292 return; 293 294 PseudoId dynamicPseudo = result.dynamicPseudo; 295 // If we're matching normal rules, set a pseudo bit if 296 // we really just matched a pseudo-element. 297 if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) { 298 if (m_mode == SelectorChecker::CollectingCSSRules || m_mode == SelectorChecker::CollectingStyleRules) 299 return; 300 // FIXME: Matching should not modify the style directly. 301 if (m_style && dynamicPseudo < FIRST_INTERNAL_PSEUDOID) 302 m_style->setHasPseudoStyle(dynamicPseudo); 303 } else { 304 // Update our first/last rule indices in the matched rules array. 305 ++ruleRange.lastRuleIndex; 306 if (ruleRange.firstRuleIndex == -1) 307 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex; 308 309 // Add this rule to our list of matched rules. 310 addMatchedRule(&ruleData, result.specificity, cascadeScope, cascadeOrder, matchRequest.styleSheetIndex, matchRequest.styleSheet); 311 return; 312 } 313 } 314 } 315 316 static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2) 317 { 318 if (matchedRule1.cascadeScope() != matchedRule2.cascadeScope()) 319 return matchedRule1.cascadeScope() > matchedRule2.cascadeScope(); 320 321 unsigned specificity1 = matchedRule1.specificity(); 322 unsigned specificity2 = matchedRule2.specificity(); 323 if (specificity1 != specificity2) 324 return specificity1 < specificity2; 325 326 return matchedRule1.position() < matchedRule2.position(); 327 } 328 329 void ElementRuleCollector::sortMatchedRules() 330 { 331 ASSERT(m_matchedRules); 332 std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules); 333 } 334 335 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet) 336 { 337 clearMatchedRules(); 338 339 m_mode = SelectorChecker::SharingRules; 340 // To check whether a given RuleSet has any rule matching a given element, 341 // should not see the element's treescope. Because RuleSet has no 342 // information about "scope". 343 int firstRuleIndex = -1, lastRuleIndex = -1; 344 RuleRange ruleRange(firstRuleIndex, lastRuleIndex); 345 // FIXME: Verify whether it's ok to ignore CascadeScope here. 346 collectMatchingRules(MatchRequest(ruleSet), ruleRange, SelectorChecker::StaysWithinTreeScope); 347 348 return m_matchedRules && !m_matchedRules->isEmpty(); 349 } 350 351 } // namespace WebCore 352