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 blink { 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, bool matchingTreeBoundaryRules) 122 { 123 // [skipped, because already checked] a) it's a UA rule 124 // b) We're mathcing tree boundary rules. 125 if (matchingTreeBoundaryRules) 126 return true; 127 // c) the rules comes from a scoped style sheet within the same tree scope 128 if (!scopingNode || element->treeScope() == scopingNode->treeScope()) 129 return true; 130 // d) the rules comes from a scoped style sheet within an active shadow root whose host is the given element 131 if (SelectorChecker::isHostInItsShadowTree(*element, scopingNode)) 132 return true; 133 return false; 134 } 135 136 void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, RuleRange& ruleRange, SelectorChecker::ContextFlags contextFlags, CascadeScope cascadeScope, CascadeOrder cascadeOrder, bool matchingTreeBoundaryRules) 137 { 138 ASSERT(matchRequest.ruleSet); 139 ASSERT(m_context.element()); 140 141 Element& element = *m_context.element(); 142 const AtomicString& pseudoId = element.shadowPseudoId(); 143 if (!pseudoId.isEmpty()) { 144 ASSERT(element.isStyledElement()); 145 collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId), contextFlags, ignoreCascadeScope, cascadeOrder, matchRequest, ruleRange); 146 } 147 148 if (element.isVTTElement()) 149 collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange); 150 // Check whether other types of rules are applicable in the current tree scope. Criteria for this: 151 // a) it's a UA rule 152 // b) the rules comes from a scoped style sheet within the same tree scope 153 // c) the rules comes from a scoped style sheet within an active shadow root whose host is the given element 154 // d) the rules can cross boundaries 155 // b)-e) is checked in rulesApplicableInCurrentTreeScope. 156 if (!m_matchingUARules && !rulesApplicableInCurrentTreeScope(&element, matchRequest.scope, matchingTreeBoundaryRules)) 157 return; 158 159 // We need to collect the rules for id, class, tag, and everything else into a buffer and 160 // then sort the buffer. 161 if (element.hasID()) 162 collectMatchingRulesForList(matchRequest.ruleSet->idRules(element.idForStyleResolution()), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange); 163 if (element.isStyledElement() && element.hasClass()) { 164 for (size_t i = 0; i < element.classNames().size(); ++i) 165 collectMatchingRulesForList(matchRequest.ruleSet->classRules(element.classNames()[i]), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange); 166 } 167 168 if (element.isLink()) 169 collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange); 170 if (SelectorChecker::matchesFocusPseudoClass(element)) 171 collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange); 172 collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element.localName()), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange); 173 collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), contextFlags, cascadeScope, cascadeOrder, matchRequest, ruleRange); 174 } 175 176 CSSRuleList* ElementRuleCollector::nestedRuleList(CSSRule* rule) 177 { 178 switch (rule->type()) { 179 case CSSRule::MEDIA_RULE: 180 return toCSSMediaRule(rule)->cssRules(); 181 case CSSRule::KEYFRAMES_RULE: 182 return toCSSKeyframesRule(rule)->cssRules(); 183 case CSSRule::SUPPORTS_RULE: 184 return toCSSSupportsRule(rule)->cssRules(); 185 default: 186 return 0; 187 } 188 } 189 190 template<class CSSRuleCollection> 191 CSSRule* ElementRuleCollector::findStyleRule(CSSRuleCollection* cssRules, StyleRule* styleRule) 192 { 193 if (!cssRules) 194 return 0; 195 CSSRule* result = 0; 196 for (unsigned i = 0; i < cssRules->length() && !result; ++i) { 197 CSSRule* cssRule = cssRules->item(i); 198 CSSRule::Type cssRuleType = cssRule->type(); 199 if (cssRuleType == CSSRule::STYLE_RULE) { 200 CSSStyleRule* cssStyleRule = toCSSStyleRule(cssRule); 201 if (cssStyleRule->styleRule() == styleRule) 202 result = cssRule; 203 } else if (cssRuleType == CSSRule::IMPORT_RULE) { 204 CSSImportRule* cssImportRule = toCSSImportRule(cssRule); 205 result = findStyleRule(cssImportRule->styleSheet(), styleRule); 206 } else { 207 result = findStyleRule(nestedRuleList(cssRule), styleRule); 208 } 209 } 210 return result; 211 } 212 213 void ElementRuleCollector::appendCSSOMWrapperForRule(CSSStyleSheet* parentStyleSheet, StyleRule* rule) 214 { 215 // |parentStyleSheet| is 0 if and only if the |rule| is coming from User Agent. In this case, 216 // it is safe to create CSSOM wrappers without parentStyleSheets as they will be used only 217 // by inspector which will not try to edit them. 218 RefPtrWillBeRawPtr<CSSRule> cssRule = nullptr; 219 if (parentStyleSheet) 220 cssRule = findStyleRule(parentStyleSheet, rule); 221 else 222 cssRule = rule->createCSSOMWrapper(); 223 ASSERT(!parentStyleSheet || cssRule); 224 ensureRuleList()->rules().append(cssRule); 225 } 226 227 void ElementRuleCollector::sortAndTransferMatchedRules() 228 { 229 if (!m_matchedRules || m_matchedRules->isEmpty()) 230 return; 231 232 sortMatchedRules(); 233 234 WillBeHeapVector<MatchedRule, 32>& matchedRules = *m_matchedRules; 235 if (m_mode == SelectorChecker::CollectingStyleRules) { 236 for (unsigned i = 0; i < matchedRules.size(); ++i) 237 ensureStyleRuleList()->m_list.append(matchedRules[i].ruleData()->rule()); 238 return; 239 } 240 241 if (m_mode == SelectorChecker::CollectingCSSRules) { 242 for (unsigned i = 0; i < matchedRules.size(); ++i) 243 appendCSSOMWrapperForRule(const_cast<CSSStyleSheet*>(matchedRules[i].parentStyleSheet()), matchedRules[i].ruleData()->rule()); 244 return; 245 } 246 247 // Now transfer the set of matched rules over to our list of declarations. 248 for (unsigned i = 0; i < matchedRules.size(); i++) { 249 // FIXME: Matching should not modify the style directly. 250 const RuleData* ruleData = matchedRules[i].ruleData(); 251 if (m_style && ruleData->containsUncommonAttributeSelector()) 252 m_style->setUnique(); 253 m_result.addMatchedProperties(&ruleData->rule()->properties(), ruleData->linkMatchType(), ruleData->propertyWhitelistType(m_matchingUARules)); 254 } 255 } 256 257 inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, SelectorChecker::ContextFlags contextFlags, SelectorChecker::MatchResult* result) 258 { 259 SelectorChecker selectorChecker(m_context.element()->document(), m_mode); 260 SelectorChecker::SelectorCheckingContext context(ruleData.selector(), m_context.element(), SelectorChecker::VisitedMatchEnabled); 261 context.elementStyle = m_style.get(); 262 context.scope = scope; 263 context.pseudoId = m_pseudoStyleRequest.pseudoId; 264 context.scrollbar = m_pseudoStyleRequest.scrollbar; 265 context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart; 266 context.isUARule = m_matchingUARules; 267 context.contextFlags = contextFlags; 268 SelectorChecker::Match match = selectorChecker.match(context, DOMSiblingTraversalStrategy(), result); 269 if (match != SelectorChecker::SelectorMatches) 270 return false; 271 if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != result->dynamicPseudo) 272 return false; 273 return true; 274 } 275 276 void ElementRuleCollector::collectRuleIfMatches(const RuleData& ruleData, SelectorChecker::ContextFlags contextFlags, CascadeScope cascadeScope, CascadeOrder cascadeOrder, const MatchRequest& matchRequest, RuleRange& ruleRange) 277 { 278 if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes())) 279 return; 280 281 StyleRule* rule = ruleData.rule(); 282 SelectorChecker::MatchResult result; 283 if (ruleMatches(ruleData, matchRequest.scope, contextFlags, &result)) { 284 // If the rule has no properties to apply, then ignore it in the non-debug mode. 285 const StylePropertySet& properties = rule->properties(); 286 if (properties.isEmpty() && !matchRequest.includeEmptyRules) 287 return; 288 // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed. 289 if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin()) 290 return; 291 292 PseudoId dynamicPseudo = result.dynamicPseudo; 293 // If we're matching normal rules, set a pseudo bit if 294 // we really just matched a pseudo-element. 295 if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) { 296 if (m_mode == SelectorChecker::CollectingCSSRules || m_mode == SelectorChecker::CollectingStyleRules) 297 return; 298 // FIXME: Matching should not modify the style directly. 299 if (!m_style || dynamicPseudo >= FIRST_INTERNAL_PSEUDOID) 300 return; 301 if ((dynamicPseudo == BEFORE || dynamicPseudo == AFTER) && !ruleData.rule()->properties().hasProperty(CSSPropertyContent)) 302 return; 303 m_style->setHasPseudoStyle(dynamicPseudo); 304 } else { 305 // Update our first/last rule indices in the matched rules array. 306 ++ruleRange.lastRuleIndex; 307 if (ruleRange.firstRuleIndex == -1) 308 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex; 309 310 // Add this rule to our list of matched rules. 311 addMatchedRule(&ruleData, result.specificity, cascadeScope, cascadeOrder, matchRequest.styleSheetIndex, matchRequest.styleSheet); 312 return; 313 } 314 } 315 } 316 317 static inline bool compareRules(const MatchedRule& matchedRule1, const MatchedRule& matchedRule2) 318 { 319 if (matchedRule1.cascadeScope() != matchedRule2.cascadeScope()) 320 return matchedRule1.cascadeScope() > matchedRule2.cascadeScope(); 321 322 unsigned specificity1 = matchedRule1.specificity(); 323 unsigned specificity2 = matchedRule2.specificity(); 324 if (specificity1 != specificity2) 325 return specificity1 < specificity2; 326 327 return matchedRule1.position() < matchedRule2.position(); 328 } 329 330 void ElementRuleCollector::sortMatchedRules() 331 { 332 ASSERT(m_matchedRules); 333 std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules); 334 } 335 336 bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet) 337 { 338 clearMatchedRules(); 339 340 m_mode = SelectorChecker::SharingRules; 341 // To check whether a given RuleSet has any rule matching a given element, 342 // should not see the element's treescope. Because RuleSet has no 343 // information about "scope". 344 int firstRuleIndex = -1, lastRuleIndex = -1; 345 RuleRange ruleRange(firstRuleIndex, lastRuleIndex); 346 // FIXME: Verify whether it's ok to ignore CascadeScope here. 347 collectMatchingRules(MatchRequest(ruleSet), ruleRange, SelectorChecker::DefaultBehavior); 348 349 return m_matchedRules && !m_matchedRules->isEmpty(); 350 } 351 352 } // namespace blink 353