1/* 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) 4 * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@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@webkit.org> 7 * Copyright (C) 2007, 2008 Eric Seidel <eric@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 "ElementRuleCollector.h" 31 32#include "CSSDefaultStyleSheets.h" 33#include "CSSRule.h" 34#include "CSSRuleList.h" 35#include "CSSSelector.h" 36#include "CSSSelectorList.h" 37#include "CSSValueKeywords.h" 38#include "HTMLElement.h" 39#include "RenderRegion.h" 40#include "SVGElement.h" 41#include "SelectorCheckerFastPath.h" 42#include "StylePropertySet.h" 43#include "StyledElement.h" 44 45#include <wtf/TemporaryChange.h> 46 47namespace WebCore { 48 49static StylePropertySet* leftToRightDeclaration() 50{ 51 DEFINE_STATIC_LOCAL(RefPtr<MutableStylePropertySet>, leftToRightDecl, (MutableStylePropertySet::create())); 52 if (leftToRightDecl->isEmpty()) 53 leftToRightDecl->setProperty(CSSPropertyDirection, CSSValueLtr); 54 return leftToRightDecl.get(); 55} 56 57static StylePropertySet* rightToLeftDeclaration() 58{ 59 DEFINE_STATIC_LOCAL(RefPtr<MutableStylePropertySet>, rightToLeftDecl, (MutableStylePropertySet::create())); 60 if (rightToLeftDecl->isEmpty()) 61 rightToLeftDecl->setProperty(CSSPropertyDirection, CSSValueRtl); 62 return rightToLeftDecl.get(); 63} 64 65StyleResolver::MatchResult& ElementRuleCollector::matchedResult() 66{ 67 ASSERT(m_mode == SelectorChecker::ResolvingStyle); 68 return m_result; 69} 70 71const Vector<RefPtr<StyleRuleBase> >& ElementRuleCollector::matchedRuleList() const 72{ 73 ASSERT(m_mode == SelectorChecker::CollectingRules); 74 return m_matchedRuleList; 75} 76 77inline void ElementRuleCollector::addMatchedRule(const RuleData* rule) 78{ 79 if (!m_matchedRules) 80 m_matchedRules = adoptPtr(new Vector<const RuleData*, 32>); 81 m_matchedRules->append(rule); 82} 83 84inline void ElementRuleCollector::clearMatchedRules() 85{ 86 if (!m_matchedRules) 87 return; 88 m_matchedRules->clear(); 89} 90 91inline void ElementRuleCollector::addElementStyleProperties(const StylePropertySet* propertySet, bool isCacheable) 92{ 93 if (!propertySet) 94 return; 95 m_result.ranges.lastAuthorRule = m_result.matchedProperties.size(); 96 if (m_result.ranges.firstAuthorRule == -1) 97 m_result.ranges.firstAuthorRule = m_result.ranges.lastAuthorRule; 98 m_result.addMatchedProperties(propertySet); 99 if (!isCacheable) 100 m_result.isCacheable = false; 101} 102 103class MatchingUARulesScope { 104public: 105 MatchingUARulesScope(); 106 ~MatchingUARulesScope(); 107 108 static bool isMatchingUARules(); 109 110private: 111 static bool m_matchingUARules; 112}; 113 114MatchingUARulesScope::MatchingUARulesScope() 115{ 116 ASSERT(!m_matchingUARules); 117 m_matchingUARules = true; 118} 119 120MatchingUARulesScope::~MatchingUARulesScope() 121{ 122 m_matchingUARules = false; 123} 124 125inline bool MatchingUARulesScope::isMatchingUARules() 126{ 127 return m_matchingUARules; 128} 129 130bool MatchingUARulesScope::m_matchingUARules = false; 131 132void ElementRuleCollector::collectMatchingRules(const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) 133{ 134 ASSERT(matchRequest.ruleSet); 135 ASSERT(m_state.element()); 136 137 const StyleResolver::State& state = m_state; 138 Element* element = state.element(); 139 const StyledElement* styledElement = state.styledElement(); 140 const AtomicString& pseudoId = element->shadowPseudoId(); 141 if (!pseudoId.isEmpty()) { 142 ASSERT(styledElement); 143 collectMatchingRulesForList(matchRequest.ruleSet->shadowPseudoElementRules(pseudoId.impl()), matchRequest, ruleRange); 144 } 145 146#if ENABLE(VIDEO_TRACK) 147 if (element->isWebVTTElement()) 148 collectMatchingRulesForList(matchRequest.ruleSet->cuePseudoRules(), matchRequest, ruleRange); 149#endif 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 tree scope allows author rules 153 // c) the rules comes from a scoped style sheet within the same tree scope 154 TreeScope* treeScope = element->treeScope(); 155 if (!MatchingUARulesScope::isMatchingUARules() 156 && !treeScope->applyAuthorStyles() 157 && (!matchRequest.scope || matchRequest.scope->treeScope() != treeScope) 158 && m_behaviorAtBoundary == SelectorChecker::DoesNotCrossBoundary) 159 return; 160 161 // We need to collect the rules for id, class, tag, and everything else into a buffer and 162 // then sort the buffer. 163 if (element->hasID()) 164 collectMatchingRulesForList(matchRequest.ruleSet->idRules(element->idForStyleResolution().impl()), matchRequest, ruleRange); 165 if (styledElement && styledElement->hasClass()) { 166 for (size_t i = 0; i < styledElement->classNames().size(); ++i) 167 collectMatchingRulesForList(matchRequest.ruleSet->classRules(styledElement->classNames()[i].impl()), matchRequest, ruleRange); 168 } 169 170 if (element->isLink()) 171 collectMatchingRulesForList(matchRequest.ruleSet->linkPseudoClassRules(), matchRequest, ruleRange); 172 if (SelectorChecker::matchesFocusPseudoClass(element)) 173 collectMatchingRulesForList(matchRequest.ruleSet->focusPseudoClassRules(), matchRequest, ruleRange); 174 collectMatchingRulesForList(matchRequest.ruleSet->tagRules(element->localName().impl()), matchRequest, ruleRange); 175 collectMatchingRulesForList(matchRequest.ruleSet->universalRules(), matchRequest, ruleRange); 176} 177 178void ElementRuleCollector::collectMatchingRulesForRegion(const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) 179{ 180 if (!m_regionForStyling) 181 return; 182 183 unsigned size = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.size(); 184 for (unsigned i = 0; i < size; ++i) { 185 const CSSSelector* regionSelector = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).selector; 186 if (checkRegionSelector(regionSelector, toElement(m_regionForStyling->node()))) { 187 RuleSet* regionRules = matchRequest.ruleSet->m_regionSelectorsAndRuleSets.at(i).ruleSet.get(); 188 ASSERT(regionRules); 189 collectMatchingRules(MatchRequest(regionRules, matchRequest.includeEmptyRules, matchRequest.scope), ruleRange); 190 } 191 } 192} 193 194void ElementRuleCollector::sortAndTransferMatchedRules() 195{ 196 const StyleResolver::State& state = m_state; 197 198 if (!m_matchedRules || m_matchedRules->isEmpty()) 199 return; 200 201 sortMatchedRules(); 202 203 Vector<const RuleData*, 32>& matchedRules = *m_matchedRules; 204 if (m_mode == SelectorChecker::CollectingRules) { 205 for (unsigned i = 0; i < matchedRules.size(); ++i) 206 m_matchedRuleList.append(matchedRules[i]->rule()); 207 return; 208 } 209 210 // Now transfer the set of matched rules over to our list of declarations. 211 for (unsigned i = 0; i < matchedRules.size(); i++) { 212 if (state.style() && matchedRules[i]->containsUncommonAttributeSelector()) 213 state.style()->setUnique(); 214 m_result.addMatchedProperties(matchedRules[i]->rule()->properties(), matchedRules[i]->rule(), matchedRules[i]->linkMatchType(), matchedRules[i]->propertyWhitelistType(MatchingUARulesScope::isMatchingUARules())); 215 } 216} 217 218void ElementRuleCollector::matchScopedAuthorRules(bool includeEmptyRules) 219{ 220#if ENABLE(STYLE_SCOPED) || ENABLE(SHADOW_DOM) 221 if (!m_scopeResolver) 222 return; 223 224 // Match scoped author rules by traversing the scoped element stack (rebuild it if it got inconsistent). 225 if (m_scopeResolver->hasScopedStyles() && m_scopeResolver->ensureStackConsistency(m_state.element())) { 226 bool applyAuthorStyles = m_state.element()->treeScope()->applyAuthorStyles(); 227 bool documentScope = true; 228 unsigned scopeSize = m_scopeResolver->stackSize(); 229 for (unsigned i = 0; i < scopeSize; ++i) { 230 clearMatchedRules(); 231 m_result.ranges.lastAuthorRule = m_result.matchedProperties.size() - 1; 232 233 const StyleScopeResolver::StackFrame& frame = m_scopeResolver->stackFrameAt(i); 234 documentScope = documentScope && !frame.m_scope->isInShadowTree(); 235 if (documentScope) { 236 if (!applyAuthorStyles) 237 continue; 238 } else { 239 if (!m_scopeResolver->matchesStyleBounds(frame)) 240 continue; 241 } 242 243 MatchRequest matchRequest(frame.m_ruleSet, includeEmptyRules, frame.m_scope); 244 StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange(); 245 collectMatchingRules(matchRequest, ruleRange); 246 collectMatchingRulesForRegion(matchRequest, ruleRange); 247 sortAndTransferMatchedRules(); 248 } 249 } 250 251 matchHostRules(includeEmptyRules); 252#else 253 UNUSED_PARAM(includeEmptyRules); 254#endif 255} 256 257void ElementRuleCollector::matchHostRules(bool includeEmptyRules) 258{ 259#if ENABLE(SHADOW_DOM) 260 ASSERT(m_scopeResolver); 261 262 clearMatchedRules(); 263 m_result.ranges.lastAuthorRule = m_result.matchedProperties.size() - 1; 264 265 Vector<RuleSet*> matchedRules; 266 m_scopeResolver->matchHostRules(m_state.element(), matchedRules); 267 if (matchedRules.isEmpty()) 268 return; 269 270 for (unsigned i = matchedRules.size(); i > 0; --i) { 271 StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange(); 272 collectMatchingRules(MatchRequest(matchedRules.at(i-1), includeEmptyRules, m_state.element()), ruleRange); 273 } 274 sortAndTransferMatchedRules(); 275#else 276 UNUSED_PARAM(includeEmptyRules); 277#endif 278} 279 280void ElementRuleCollector::matchAuthorRules(bool includeEmptyRules) 281{ 282 clearMatchedRules(); 283 m_result.ranges.lastAuthorRule = m_result.matchedProperties.size() - 1; 284 285 if (!m_state.element()) 286 return; 287 288 // Match global author rules. 289 MatchRequest matchRequest(m_ruleSets.authorStyle(), includeEmptyRules); 290 StyleResolver::RuleRange ruleRange = m_result.ranges.authorRuleRange(); 291 collectMatchingRules(matchRequest, ruleRange); 292 collectMatchingRulesForRegion(matchRequest, ruleRange); 293 294 sortAndTransferMatchedRules(); 295 296 matchScopedAuthorRules(includeEmptyRules); 297} 298 299void ElementRuleCollector::matchUserRules(bool includeEmptyRules) 300{ 301 if (!m_ruleSets.userStyle()) 302 return; 303 304 clearMatchedRules(); 305 306 m_result.ranges.lastUserRule = m_result.matchedProperties.size() - 1; 307 MatchRequest matchRequest(m_ruleSets.userStyle(), includeEmptyRules); 308 StyleResolver::RuleRange ruleRange = m_result.ranges.userRuleRange(); 309 collectMatchingRules(matchRequest, ruleRange); 310 collectMatchingRulesForRegion(matchRequest, ruleRange); 311 312 sortAndTransferMatchedRules(); 313} 314 315void ElementRuleCollector::matchUARules() 316{ 317 MatchingUARulesScope scope; 318 319 // First we match rules from the user agent sheet. 320 if (CSSDefaultStyleSheets::simpleDefaultStyleSheet) 321 m_result.isCacheable = false; 322 RuleSet* userAgentStyleSheet = m_isPrintStyle 323 ? CSSDefaultStyleSheets::defaultPrintStyle : CSSDefaultStyleSheets::defaultStyle; 324 matchUARules(userAgentStyleSheet); 325 326 // In quirks mode, we match rules from the quirks user agent sheet. 327 if (document()->inQuirksMode()) 328 matchUARules(CSSDefaultStyleSheets::defaultQuirksStyle); 329 330 // If document uses view source styles (in view source mode or in xml viewer mode), then we match rules from the view source style sheet. 331 if (document()->isViewSource()) 332 matchUARules(CSSDefaultStyleSheets::viewSourceStyle()); 333} 334 335void ElementRuleCollector::matchUARules(RuleSet* rules) 336{ 337 clearMatchedRules(); 338 339 m_result.ranges.lastUARule = m_result.matchedProperties.size() - 1; 340 StyleResolver::RuleRange ruleRange = m_result.ranges.UARuleRange(); 341 collectMatchingRules(MatchRequest(rules), ruleRange); 342 343 sortAndTransferMatchedRules(); 344} 345 346inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, const ContainerNode* scope, PseudoId& dynamicPseudo) 347{ 348 const StyleResolver::State& state = m_state; 349 350 if (ruleData.hasFastCheckableSelector()) { 351 // We know this selector does not include any pseudo elements. 352 if (m_pseudoStyleRequest.pseudoId != NOPSEUDO) 353 return false; 354 // We know a sufficiently simple single part selector matches simply because we found it from the rule hash. 355 // This is limited to HTML only so we don't need to check the namespace. 356 if (ruleData.hasRightmostSelectorMatchingHTMLBasedOnRuleHash() && state.element()->isHTMLElement()) { 357 if (!ruleData.hasMultipartSelector()) 358 return true; 359 } 360 if (ruleData.selector()->m_match == CSSSelector::Tag && !SelectorChecker::tagMatches(state.element(), ruleData.selector()->tagQName())) 361 return false; 362 SelectorCheckerFastPath selectorCheckerFastPath(ruleData.selector(), state.element()); 363 if (!selectorCheckerFastPath.matchesRightmostAttributeSelector()) 364 return false; 365 366 return selectorCheckerFastPath.matches(); 367 } 368 369 // Slow path. 370 SelectorChecker selectorChecker(document(), m_mode); 371 SelectorChecker::SelectorCheckingContext context(ruleData.selector(), state.element(), SelectorChecker::VisitedMatchEnabled); 372 context.elementStyle = state.style(); 373 context.scope = scope; 374 context.pseudoId = m_pseudoStyleRequest.pseudoId; 375 context.scrollbar = m_pseudoStyleRequest.scrollbar; 376 context.scrollbarPart = m_pseudoStyleRequest.scrollbarPart; 377 context.behaviorAtBoundary = m_behaviorAtBoundary; 378 SelectorChecker::Match match = selectorChecker.match(context, dynamicPseudo); 379 if (match != SelectorChecker::SelectorMatches) 380 return false; 381 if (m_pseudoStyleRequest.pseudoId != NOPSEUDO && m_pseudoStyleRequest.pseudoId != dynamicPseudo) 382 return false; 383 return true; 384} 385 386void ElementRuleCollector::collectMatchingRulesForList(const Vector<RuleData>* rules, const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) 387{ 388 if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { 389 doCollectMatchingRulesForList<true>(rules, matchRequest, ruleRange); 390 return; 391 } 392 doCollectMatchingRulesForList<false>(rules, matchRequest, ruleRange); 393} 394 395template<bool hasInspectorFrontends> 396void ElementRuleCollector::doCollectMatchingRulesForList(const Vector<RuleData>* rules, const MatchRequest& matchRequest, StyleResolver::RuleRange& ruleRange) 397{ 398 if (!rules) 399 return; 400 401 const StyleResolver::State& state = m_state; 402 403 unsigned size = rules->size(); 404 for (unsigned i = 0; i < size; ++i) { 405 const RuleData& ruleData = rules->at(i); 406 if (m_canUseFastReject && m_selectorFilter.fastRejectSelector<RuleData::maximumIdentifierCount>(ruleData.descendantSelectorIdentifierHashes())) 407 continue; 408 409 StyleRule* rule = ruleData.rule(); 410 InspectorInstrumentationCookie cookie; 411 if (hasInspectorFrontends) 412 cookie = InspectorInstrumentation::willMatchRule(document(), rule, m_inspectorCSSOMWrappers, document()->styleSheetCollection()); 413 PseudoId dynamicPseudo = NOPSEUDO; 414 if (ruleMatches(ruleData, matchRequest.scope, dynamicPseudo)) { 415 // If the rule has no properties to apply, then ignore it in the non-debug mode. 416 const StylePropertySet* properties = rule->properties(); 417 if (!properties || (properties->isEmpty() && !matchRequest.includeEmptyRules)) { 418 if (hasInspectorFrontends) 419 InspectorInstrumentation::didMatchRule(cookie, false); 420 continue; 421 } 422 // FIXME: Exposing the non-standard getMatchedCSSRules API to web is the only reason this is needed. 423 if (m_sameOriginOnly && !ruleData.hasDocumentSecurityOrigin()) { 424 if (hasInspectorFrontends) 425 InspectorInstrumentation::didMatchRule(cookie, false); 426 continue; 427 } 428 // If we're matching normal rules, set a pseudo bit if 429 // we really just matched a pseudo-element. 430 if (dynamicPseudo != NOPSEUDO && m_pseudoStyleRequest.pseudoId == NOPSEUDO) { 431 if (m_mode == SelectorChecker::CollectingRules) { 432 if (hasInspectorFrontends) 433 InspectorInstrumentation::didMatchRule(cookie, false); 434 continue; 435 } 436 if (dynamicPseudo < FIRST_INTERNAL_PSEUDOID) 437 state.style()->setHasPseudoStyle(dynamicPseudo); 438 } else { 439 // Update our first/last rule indices in the matched rules array. 440 ++ruleRange.lastRuleIndex; 441 if (ruleRange.firstRuleIndex == -1) 442 ruleRange.firstRuleIndex = ruleRange.lastRuleIndex; 443 444 // Add this rule to our list of matched rules. 445 addMatchedRule(&ruleData); 446 if (hasInspectorFrontends) 447 InspectorInstrumentation::didMatchRule(cookie, true); 448 continue; 449 } 450 } 451 if (hasInspectorFrontends) 452 InspectorInstrumentation::didMatchRule(cookie, false); 453 } 454} 455 456static inline bool compareRules(const RuleData* r1, const RuleData* r2) 457{ 458 unsigned specificity1 = r1->specificity(); 459 unsigned specificity2 = r2->specificity(); 460 return (specificity1 == specificity2) ? r1->position() < r2->position() : specificity1 < specificity2; 461} 462 463void ElementRuleCollector::sortMatchedRules() 464{ 465 ASSERT(m_matchedRules); 466 std::sort(m_matchedRules->begin(), m_matchedRules->end(), compareRules); 467} 468 469void ElementRuleCollector::matchAllRules(bool matchAuthorAndUserStyles, bool includeSMILProperties) 470{ 471 matchUARules(); 472 473 // Now we check user sheet rules. 474 if (matchAuthorAndUserStyles) 475 matchUserRules(false); 476 477 // Now check author rules, beginning first with presentational attributes mapped from HTML. 478 if (m_state.styledElement()) { 479 addElementStyleProperties(m_state.styledElement()->presentationAttributeStyle()); 480 481 // Now we check additional mapped declarations. 482 // Tables and table cells share an additional mapped rule that must be applied 483 // after all attributes, since their mapped style depends on the values of multiple attributes. 484 addElementStyleProperties(m_state.styledElement()->additionalPresentationAttributeStyle()); 485 486 if (m_state.styledElement()->isHTMLElement()) { 487 bool isAuto; 488 TextDirection textDirection = toHTMLElement(m_state.styledElement())->directionalityIfhasDirAutoAttribute(isAuto); 489 if (isAuto) 490 m_result.addMatchedProperties(textDirection == LTR ? leftToRightDeclaration() : rightToLeftDeclaration()); 491 } 492 } 493 494 // Check the rules in author sheets next. 495 if (matchAuthorAndUserStyles) 496 matchAuthorRules(false); 497 498 // Now check our inline style attribute. 499 if (matchAuthorAndUserStyles && m_state.styledElement() && m_state.styledElement()->inlineStyle()) { 500 // Inline style is immutable as long as there is no CSSOM wrapper. 501 // FIXME: Media control shadow trees seem to have problems with caching. 502 bool isInlineStyleCacheable = !m_state.styledElement()->inlineStyle()->isMutable() && !m_state.styledElement()->isInShadowTree(); 503 // FIXME: Constify. 504 addElementStyleProperties(m_state.styledElement()->inlineStyle(), isInlineStyleCacheable); 505 } 506 507#if ENABLE(SVG) 508 // Now check SMIL animation override style. 509 if (includeSMILProperties && matchAuthorAndUserStyles && m_state.styledElement() && m_state.styledElement()->isSVGElement()) 510 addElementStyleProperties(toSVGElement(m_state.styledElement())->animatedSMILStyleProperties(), false /* isCacheable */); 511#else 512 UNUSED_PARAM(includeSMILProperties); 513#endif 514} 515 516bool ElementRuleCollector::hasAnyMatchingRules(RuleSet* ruleSet) 517{ 518 clearMatchedRules(); 519 520 m_mode = SelectorChecker::SharingRules; 521 int firstRuleIndex = -1, lastRuleIndex = -1; 522 StyleResolver::RuleRange ruleRange(firstRuleIndex, lastRuleIndex); 523 collectMatchingRules(MatchRequest(ruleSet), ruleRange); 524 525 return m_matchedRules && !m_matchedRules->isEmpty(); 526} 527 528} // namespace WebCore 529