/* * 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 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 "RuleSet.h" #include "CSSFontSelector.h" #include "CSSSelector.h" #include "CSSSelectorList.h" #include "HTMLNames.h" #include "MediaQueryEvaluator.h" #include "SecurityOrigin.h" #include "SelectorChecker.h" #include "StyleResolver.h" #include "StyleRule.h" #include "StyleRuleImport.h" #include "StyleSheetContents.h" #include "WebCoreMemoryInstrumentation.h" #include "WebKitCSSKeyframesRule.h" #include #include #include namespace WebCore { using namespace HTMLNames; // ----------------------------------------------------------------- static inline bool isSelectorMatchingHTMLBasedOnRuleHash(const CSSSelector* selector) { const AtomicString& selectorNamespace = selector->tag().namespaceURI(); if (selectorNamespace != starAtom && selectorNamespace != xhtmlNamespaceURI) return false; if (selector->m_match == CSSSelector::None) return true; if (selector->tag() != starAtom) return false; if (SelectorChecker::isCommonPseudoClassSelector(selector)) return true; return selector->m_match == CSSSelector::Id || selector->m_match == CSSSelector::Class; } static inline bool selectorListContainsUncommonAttributeSelector(const CSSSelector* selector) { CSSSelectorList* selectorList = selector->selectorList(); if (!selectorList) return false; for (CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) { if (subSelector->isAttributeSelector()) return true; } return false; } static inline bool isCommonAttributeSelectorAttribute(const QualifiedName& attribute) { // These are explicitly tested for equality in canShareStyleWithElement. return attribute == typeAttr || attribute == readonlyAttr; } static inline bool containsUncommonAttributeSelector(const CSSSelector* selector) { for (; selector; selector = selector->tagHistory()) { // Allow certain common attributes (used in the default style) in the selectors that match the current element. if (selector->isAttributeSelector() && !isCommonAttributeSelectorAttribute(selector->attribute())) return true; if (selectorListContainsUncommonAttributeSelector(selector)) return true; if (selector->relation() != CSSSelector::SubSelector) { selector = selector->tagHistory(); break; } } for (; selector; selector = selector->tagHistory()) { if (selector->isAttributeSelector()) return true; if (selectorListContainsUncommonAttributeSelector(selector)) return true; } return false; } RuleData::RuleData(StyleRule* rule, unsigned selectorIndex, unsigned position, bool hasDocumentSecurityOrigin, bool canUseFastCheckSelector, bool inRegionRule) : m_rule(rule) , m_selectorIndex(selectorIndex) , m_position(position) , m_specificity(selector()->specificity()) , m_hasFastCheckableSelector(canUseFastCheckSelector && SelectorChecker::isFastCheckableSelector(selector())) , m_hasMultipartSelector(!!selector()->tagHistory()) , m_hasRightmostSelectorMatchingHTMLBasedOnRuleHash(isSelectorMatchingHTMLBasedOnRuleHash(selector())) , m_containsUncommonAttributeSelector(WebCore::containsUncommonAttributeSelector(selector())) , m_linkMatchType(SelectorChecker::determineLinkMatchType(selector())) , m_hasDocumentSecurityOrigin(hasDocumentSecurityOrigin) , m_isInRegionRule(inRegionRule) { ASSERT(m_position == position); ASSERT(m_selectorIndex == selectorIndex); SelectorChecker::collectIdentifierHashes(selector(), m_descendantSelectorIdentifierHashes, maximumIdentifierCount); } void RuleData::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const { MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::CSS); } static void reportAtomRuleMap(MemoryClassInfo* info, const RuleSet::AtomRuleMap& atomicRuleMap) { info->addMember(atomicRuleMap); for (RuleSet::AtomRuleMap::const_iterator it = atomicRuleMap.begin(); it != atomicRuleMap.end(); ++it) info->addMember(*it->value); } void RuleSet::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const { MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::CSS); reportAtomRuleMap(&info, m_idRules); reportAtomRuleMap(&info, m_classRules); reportAtomRuleMap(&info, m_tagRules); reportAtomRuleMap(&info, m_shadowPseudoElementRules); info.addMember(m_linkPseudoClassRules); info.addMember(m_focusPseudoClassRules); info.addMember(m_universalRules); info.addMember(m_pageRules); info.addMember(m_regionSelectorsAndRuleSets); } void RuleSet::RuleSetSelectorPair::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const { MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::CSS); info.addMember(ruleSet); } static inline void collectFeaturesFromSelector(RuleFeatureSet& features, const CSSSelector* selector) { if (selector->m_match == CSSSelector::Id) features.idsInRules.add(selector->value().impl()); if (selector->isAttributeSelector()) features.attrsInRules.add(selector->attribute().localName().impl()); switch (selector->pseudoType()) { case CSSSelector::PseudoFirstLine: features.usesFirstLineRules = true; break; case CSSSelector::PseudoBefore: case CSSSelector::PseudoAfter: features.usesBeforeAfterRules = true; break; default: break; } } static void collectFeaturesFromRuleData(RuleFeatureSet& features, const RuleData& ruleData) { bool foundSiblingSelector = false; for (CSSSelector* selector = ruleData.selector(); selector; selector = selector->tagHistory()) { collectFeaturesFromSelector(features, selector); if (CSSSelectorList* selectorList = selector->selectorList()) { for (CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) { if (!foundSiblingSelector && selector->isSiblingSelector()) foundSiblingSelector = true; collectFeaturesFromSelector(features, subSelector); } } else if (!foundSiblingSelector && selector->isSiblingSelector()) foundSiblingSelector = true; } if (foundSiblingSelector) features.siblingRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin())); if (ruleData.containsUncommonAttributeSelector()) features.uncommonAttributeRules.append(RuleFeature(ruleData.rule(), ruleData.selectorIndex(), ruleData.hasDocumentSecurityOrigin())); } void RuleSet::addToRuleSet(AtomicStringImpl* key, AtomRuleMap& map, const RuleData& ruleData) { if (!key) return; OwnPtr >& rules = map.add(key, nullptr).iterator->value; if (!rules) rules = adoptPtr(new Vector); rules->append(ruleData); } void RuleSet::addRule(StyleRule* rule, unsigned selectorIndex, bool hasDocumentSecurityOrigin, bool canUseFastCheckSelector, bool inRegionRule) { RuleData ruleData(rule, selectorIndex, m_ruleCount++, hasDocumentSecurityOrigin, canUseFastCheckSelector, inRegionRule); collectFeaturesFromRuleData(m_features, ruleData); CSSSelector* selector = ruleData.selector(); if (selector->m_match == CSSSelector::Id) { addToRuleSet(selector->value().impl(), m_idRules, ruleData); return; } if (selector->m_match == CSSSelector::Class) { addToRuleSet(selector->value().impl(), m_classRules, ruleData); return; } if (selector->isUnknownPseudoElement()) { addToRuleSet(selector->value().impl(), m_shadowPseudoElementRules, ruleData); return; } if (SelectorChecker::isCommonPseudoClassSelector(selector)) { switch (selector->pseudoType()) { case CSSSelector::PseudoLink: case CSSSelector::PseudoVisited: case CSSSelector::PseudoAnyLink: m_linkPseudoClassRules.append(ruleData); return; case CSSSelector::PseudoFocus: m_focusPseudoClassRules.append(ruleData); return; default: ASSERT_NOT_REACHED(); } return; } const AtomicString& localName = selector->tag().localName(); if (localName != starAtom) { addToRuleSet(localName.impl(), m_tagRules, ruleData); return; } m_universalRules.append(ruleData); } void RuleSet::addPageRule(StyleRulePage* rule) { m_pageRules.append(rule); } void RuleSet::addRegionRule(StyleRuleRegion* regionRule, bool hasDocumentSecurityOrigin) { OwnPtr regionRuleSet = RuleSet::create(); // The region rule set should take into account the position inside the parent rule set. // Otherwise, the rules inside region block might be incorrectly positioned before other similar rules from // the stylesheet that contains the region block. regionRuleSet->m_ruleCount = m_ruleCount; // Collect the region rules into a rule set const Vector >& childRules = regionRule->childRules(); for (unsigned i = 0; i < childRules.size(); ++i) { StyleRuleBase* regionStylingRule = childRules[i].get(); if (regionStylingRule->isStyleRule()) regionRuleSet->addStyleRule(static_cast(regionStylingRule), hasDocumentSecurityOrigin, true, true); } // Update the "global" rule count so that proper order is maintained m_ruleCount = regionRuleSet->m_ruleCount; m_regionSelectorsAndRuleSets.append(RuleSetSelectorPair(regionRule->selectorList().first(), regionRuleSet.release())); } void RuleSet::addRulesFromSheet(StyleSheetContents* sheet, const MediaQueryEvaluator& medium, StyleResolver* resolver, const ContainerNode* scope) { ASSERT(sheet); const Vector >& importRules = sheet->importRules(); for (unsigned i = 0; i < importRules.size(); ++i) { StyleRuleImport* importRule = importRules[i].get(); if (importRule->styleSheet() && (!importRule->mediaQueries() || medium.eval(importRule->mediaQueries(), resolver))) addRulesFromSheet(importRule->styleSheet(), medium, resolver, scope); } bool hasDocumentSecurityOrigin = resolver && resolver->document()->securityOrigin()->canRequest(sheet->baseURL()); const Vector >& rules = sheet->childRules(); for (unsigned i = 0; i < rules.size(); ++i) { StyleRuleBase* rule = rules[i].get(); ASSERT(!rule->isImportRule()); if (rule->isStyleRule()) addStyleRule(static_cast(rule), hasDocumentSecurityOrigin, !scope); else if (rule->isPageRule()) addPageRule(static_cast(rule)); else if (rule->isMediaRule()) { StyleRuleMedia* mediaRule = static_cast(rule); if ((!mediaRule->mediaQueries() || medium.eval(mediaRule->mediaQueries(), resolver))) { // Traverse child elements of the @media rule. const Vector >& childRules = mediaRule->childRules(); for (unsigned j = 0; j < childRules.size(); ++j) { StyleRuleBase* childRule = childRules[j].get(); if (childRule->isStyleRule()) addStyleRule(static_cast(childRule), hasDocumentSecurityOrigin, !scope); else if (childRule->isPageRule()) addPageRule(static_cast(childRule)); else if (childRule->isFontFaceRule() && resolver) { // Add this font face to our set. // FIXME(BUG 72461): We don't add @font-face rules of scoped style sheets for the moment. if (scope) continue; const StyleRuleFontFace* fontFaceRule = static_cast(childRule); resolver->fontSelector()->addFontFaceRule(fontFaceRule); resolver->invalidateMatchedPropertiesCache(); } else if (childRule->isKeyframesRule() && resolver) { // Add this keyframe rule to our set. // FIXME(BUG 72462): We don't add @keyframe rules of scoped style sheets for the moment. if (scope) continue; resolver->addKeyframeStyle(static_cast(childRule)); } } // for rules } // if rules } else if (rule->isFontFaceRule() && resolver) { // Add this font face to our set. // FIXME(BUG 72461): We don't add @font-face rules of scoped style sheets for the moment. if (scope) continue; const StyleRuleFontFace* fontFaceRule = static_cast(rule); resolver->fontSelector()->addFontFaceRule(fontFaceRule); resolver->invalidateMatchedPropertiesCache(); } else if (rule->isKeyframesRule() && resolver) { // FIXME (BUG 72462): We don't add @keyframe rules of scoped style sheets for the moment. if (scope) continue; resolver->addKeyframeStyle(static_cast(rule)); } #if ENABLE(CSS_REGIONS) else if (rule->isRegionRule() && resolver) { // FIXME (BUG 72472): We don't add @-webkit-region rules of scoped style sheets for the moment. if (scope) continue; addRegionRule(static_cast(rule), hasDocumentSecurityOrigin); } #endif } if (m_autoShrinkToFitEnabled) shrinkToFit(); } void RuleSet::addStyleRule(StyleRule* rule, bool hasDocumentSecurityOrigin, bool canUseFastCheckSelector, bool isInRegionRule) { for (size_t selectorIndex = 0; selectorIndex != notFound; selectorIndex = rule->selectorList().indexOfNextSelectorAfter(selectorIndex)) addRule(rule, selectorIndex, hasDocumentSecurityOrigin, canUseFastCheckSelector, isInRegionRule); } static inline void shrinkMapVectorsToFit(RuleSet::AtomRuleMap& map) { RuleSet::AtomRuleMap::iterator end = map.end(); for (RuleSet::AtomRuleMap::iterator it = map.begin(); it != end; ++it) it->value->shrinkToFit(); } void RuleSet::shrinkToFit() { shrinkMapVectorsToFit(m_idRules); shrinkMapVectorsToFit(m_classRules); shrinkMapVectorsToFit(m_tagRules); shrinkMapVectorsToFit(m_shadowPseudoElementRules); m_linkPseudoClassRules.shrinkToFit(); m_focusPseudoClassRules.shrinkToFit(); m_universalRules.shrinkToFit(); m_pageRules.shrinkToFit(); } } // namespace WebCore