Home | History | Annotate | Download | only in invalidation
      1 /*
      2  * Copyright (C) 2012 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "core/css/invalidation/StyleSheetInvalidationAnalysis.h"
     28 
     29 #include "core/css/CSSSelectorList.h"
     30 #include "core/css/StyleRuleImport.h"
     31 #include "core/css/StyleSheetContents.h"
     32 #include "core/dom/ContainerNode.h"
     33 #include "core/dom/Document.h"
     34 #include "core/dom/ElementTraversal.h"
     35 #include "core/dom/shadow/ShadowRoot.h"
     36 #include "core/html/HTMLStyleElement.h"
     37 
     38 namespace WebCore {
     39 
     40 StyleSheetInvalidationAnalysis::StyleSheetInvalidationAnalysis(const WillBeHeapVector<RawPtrWillBeMember<StyleSheetContents> >& sheets)
     41     : m_dirtiesAllStyle(false)
     42 {
     43     for (unsigned i = 0; i < sheets.size() && !m_dirtiesAllStyle; ++i)
     44         analyzeStyleSheet(sheets[i]);
     45 }
     46 
     47 static bool determineSelectorScopes(const CSSSelectorList& selectorList, HashSet<StringImpl*>& idScopes, HashSet<StringImpl*>& classScopes)
     48 {
     49     for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(*selector)) {
     50         const CSSSelector* scopeSelector = 0;
     51         // This picks the widest scope, not the narrowest, to minimize the number of found scopes.
     52         for (const CSSSelector* current = selector; current; current = current->tagHistory()) {
     53             // Prefer ids over classes.
     54             if (current->match() == CSSSelector::Id)
     55                 scopeSelector = current;
     56             else if (current->match() == CSSSelector::Class && (!scopeSelector || scopeSelector->match() != CSSSelector::Id))
     57                 scopeSelector = current;
     58             CSSSelector::Relation relation = current->relation();
     59             // FIXME: it would be better to use setNeedsStyleRecalc for all shadow hosts matching
     60             // scopeSelector. Currently requests full style recalc.
     61             if (relation == CSSSelector::ShadowDeep || relation == CSSSelector::ShadowPseudo)
     62                 return false;
     63             if (relation != CSSSelector::Descendant && relation != CSSSelector::Child && relation != CSSSelector::SubSelector)
     64                 break;
     65         }
     66         if (!scopeSelector)
     67             return false;
     68         ASSERT(scopeSelector->match() == CSSSelector::Class || scopeSelector->match() == CSSSelector::Id);
     69         if (scopeSelector->match() == CSSSelector::Id)
     70             idScopes.add(scopeSelector->value().impl());
     71         else
     72             classScopes.add(scopeSelector->value().impl());
     73     }
     74     return true;
     75 }
     76 
     77 static bool hasDistributedRule(StyleSheetContents* styleSheetContents)
     78 {
     79     const WillBeHeapVector<RefPtrWillBeMember<StyleRuleBase> >& rules = styleSheetContents->childRules();
     80     for (unsigned i = 0; i < rules.size(); i++) {
     81         const StyleRuleBase* rule = rules[i].get();
     82         if (!rule->isStyleRule())
     83             continue;
     84 
     85         const StyleRule* styleRule = toStyleRule(rule);
     86         const CSSSelectorList& selectorList = styleRule->selectorList();
     87         for (size_t selectorIndex = 0; selectorIndex != kNotFound; selectorIndex = selectorList.indexOfNextSelectorAfter(selectorIndex)) {
     88             if (selectorList.hasShadowDistributedAt(selectorIndex))
     89                 return true;
     90         }
     91     }
     92     return false;
     93 }
     94 
     95 static Node* determineScopingNodeForStyleInShadow(HTMLStyleElement* ownerElement, StyleSheetContents* styleSheetContents)
     96 {
     97     ASSERT(ownerElement && ownerElement->isInShadowTree());
     98 
     99     if (hasDistributedRule(styleSheetContents)) {
    100         ContainerNode* scope = ownerElement;
    101         do {
    102             scope = scope->containingShadowRoot()->shadowHost();
    103         } while (scope->isInShadowTree());
    104         return scope;
    105     }
    106 
    107     return ownerElement->containingShadowRoot()->shadowHost();
    108 }
    109 
    110 static bool ruleAdditionMightRequireDocumentStyleRecalc(StyleRuleBase* rule)
    111 {
    112     // This funciton is conservative. We only return false when we know that
    113     // the added @rule can't require style recalcs.
    114     switch (rule->type()) {
    115     case StyleRule::Import: // Whatever we import should do its own analysis, we don't need to invalidate the document here!
    116     case StyleRule::Keyframes: // Keyframes never cause style invalidations and are handled during sheet insertion.
    117     case StyleRule::Page: // Page rules apply only during printing, we force a full-recalc before printing.
    118         return false;
    119 
    120     case StyleRule::Media: // If the media rule doesn't apply, we could avoid recalc.
    121     case StyleRule::FontFace: // If the fonts aren't in use, we could avoid recalc.
    122     case StyleRule::Supports: // If we evaluated the supports-clause we could avoid recalc.
    123     case StyleRule::Viewport: // If the viewport doesn't match, we could avoid recalcing.
    124     // FIXME: Unclear if any of the rest need to cause style recalc:
    125     case StyleRule::Filter:
    126         return true;
    127 
    128     // These should all be impossible to reach:
    129     case StyleRule::Unknown:
    130     case StyleRule::Charset:
    131     case StyleRule::Keyframe:
    132     case StyleRule::Style:
    133         break;
    134     }
    135     ASSERT_NOT_REACHED();
    136     return true;
    137 }
    138 
    139 void StyleSheetInvalidationAnalysis::analyzeStyleSheet(StyleSheetContents* styleSheetContents)
    140 {
    141     ASSERT(!styleSheetContents->isLoading());
    142 
    143     // See if all rules on the sheet are scoped to some specific ids or classes.
    144     // Then test if we actually have any of those in the tree at the moment.
    145     const WillBeHeapVector<RefPtrWillBeMember<StyleRuleImport> >& importRules = styleSheetContents->importRules();
    146     for (unsigned i = 0; i < importRules.size(); ++i) {
    147         if (!importRules[i]->styleSheet())
    148             continue;
    149         analyzeStyleSheet(importRules[i]->styleSheet());
    150         if (m_dirtiesAllStyle)
    151             return;
    152     }
    153     if (styleSheetContents->hasSingleOwnerNode()) {
    154         Node* ownerNode = styleSheetContents->singleOwnerNode();
    155         if (isHTMLStyleElement(ownerNode) && toHTMLStyleElement(*ownerNode).isInShadowTree()) {
    156             m_scopingNodes.append(determineScopingNodeForStyleInShadow(toHTMLStyleElement(ownerNode), styleSheetContents));
    157             return;
    158         }
    159     }
    160 
    161     const WillBeHeapVector<RefPtrWillBeMember<StyleRuleBase> >& rules = styleSheetContents->childRules();
    162     for (unsigned i = 0; i < rules.size(); i++) {
    163         StyleRuleBase* rule = rules[i].get();
    164         if (!rule->isStyleRule()) {
    165             if (ruleAdditionMightRequireDocumentStyleRecalc(rule)) {
    166                 m_dirtiesAllStyle = true;
    167                 return;
    168             }
    169             continue;
    170         }
    171         StyleRule* styleRule = toStyleRule(rule);
    172         if (!determineSelectorScopes(styleRule->selectorList(), m_idScopes, m_classScopes)) {
    173             m_dirtiesAllStyle = true;
    174             return;
    175         }
    176     }
    177 }
    178 
    179 static bool elementMatchesSelectorScopes(const Element* element, const HashSet<StringImpl*>& idScopes, const HashSet<StringImpl*>& classScopes)
    180 {
    181     if (!idScopes.isEmpty() && element->hasID() && idScopes.contains(element->idForStyleResolution().impl()))
    182         return true;
    183     if (classScopes.isEmpty() || !element->hasClass())
    184         return false;
    185     const SpaceSplitString& classNames = element->classNames();
    186     for (unsigned i = 0; i < classNames.size(); ++i) {
    187         if (classScopes.contains(classNames[i].impl()))
    188             return true;
    189     }
    190     return false;
    191 }
    192 
    193 void StyleSheetInvalidationAnalysis::invalidateStyle(Document& document)
    194 {
    195     ASSERT(!m_dirtiesAllStyle);
    196 
    197     if (!m_scopingNodes.isEmpty()) {
    198         for (unsigned i = 0; i < m_scopingNodes.size(); ++i)
    199             m_scopingNodes.at(i)->setNeedsStyleRecalc(SubtreeStyleChange);
    200     }
    201 
    202     if (m_idScopes.isEmpty() && m_classScopes.isEmpty())
    203         return;
    204     Element* element = ElementTraversal::firstWithin(document);
    205     while (element) {
    206         if (elementMatchesSelectorScopes(element, m_idScopes, m_classScopes)) {
    207             element->setNeedsStyleRecalc(SubtreeStyleChange);
    208             // The whole subtree is now invalidated, we can skip to the next sibling.
    209             element = ElementTraversal::nextSkippingChildren(*element);
    210             continue;
    211         }
    212         element = ElementTraversal::next(*element);
    213     }
    214 }
    215 
    216 }
    217