Title: [104060] trunk/Source/WebCore
Revision
104060
Author
[email protected]
Date
2012-01-04 12:03:35 -0800 (Wed, 04 Jan 2012)

Log Message

Analyze stylesheet scope to minimize style recalcs
https://bugs.webkit.org/show_bug.cgi?id=75508
        
Reviewed by Dave Hyatt.

It is a relatively common pattern to use inline stylesheets in document body where all rules are scoped using descendant selector

<style>
#foo {...}
#foo div {...}
#foo .bar {...}
</style>

When this pattern is used it is also common that the rules only apply to elements that come after the style element. 

When the set of active stylesheets changes we invalidate and recompute the entire document style. This is very expensive. We can 
detect the case above and avoid the style recalc.
        
On engadget.com, this patch cuts the time spent in style recalcs to roughly half. There are further savings from reduced 
relayouts. In total the engine CPU time used over the page load is reduced by ~10%.

* css/CSSStyleSelector.cpp:
(WebCore::CSSStyleSelector::CSSStyleSelector):
(WebCore::CSSStyleSelector::collectFeatures):
        
    Refactor feature collection from constructor to a separate function.
        
(WebCore::CSSStyleSelector::appendAuthorStylesheets):
        
    New function for non-destructively updating the style selector.
        
(WebCore::CSSStyleSelector::Features::clear):
        
    Clear the features for another collection.
            
(WebCore::CSSStyleSelector::determineStylesheetSelectorScopes):
        
    Find if all rules on a stylesheetare scoped to some set of ids and classes.
        
(WebCore::CSSStyleSelector::styleForDocument):
        
    Add optional font selector argument. We updated the correct base style font on style selector construction but that is no longer sufficient
    as font selector may be updated without reconstructing the style selector.
        
(WebCore::RuleSet::addRulesFromSheet):
        
    Invalidate the matched declaration cache in case of new font-face rules.
        
* css/CSSStyleSelector.h:
* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::determineSelectorScopes):
        
    Find if all rules on a selector list are scoped to some set of ids and classes.
        
* css/SelectorChecker.h:
* dom/Document.cpp:
(WebCore::Document::Document):
(WebCore::Document::recalcStyle):
        
    Pass the font selector, if exists, to styleForDocument so we always have the base font up to date.
        
(WebCore::Document::combineCSSFeatureFlags):
(WebCore::Document::resetCSSFeatureFlags):
(WebCore::Document::createStyleSelector):

    Refactor css feature flag resetting to functions.
        
(WebCore::Document::removePendingSheet):
        
    Use new PendingStylesheetCompleted flag when new stylesheets arrive.

(WebCore::Document::styleSelectorChanged):

    Skip style recalc if it is not needed.

(WebCore::Document::collectActiveStylesheets):
        
    Refactor collecting stylesheets to a separate function.
        
(WebCore::Document::testAddedStylesheetRequiresStyleRecalc):
        
    Determine the scopes and use hasElementWithId/getElementsByClassName to figure out if any scoped elements currently exist in the tree.
        
(WebCore::Document::analyzeStylesheetChange):
        
    Figure out if we can update the style selector incrementally and if we can skip the style recalc.
        
(WebCore::Document::updateActiveStylesheets):
        
    Renamed from recalcStyleSelector.
    Invoke the new analysis functions.
        
* dom/Document.h:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (104059 => 104060)


--- trunk/Source/WebCore/ChangeLog	2012-01-04 20:02:54 UTC (rev 104059)
+++ trunk/Source/WebCore/ChangeLog	2012-01-04 20:03:35 UTC (rev 104060)
@@ -1,3 +1,99 @@
+2012-01-03  Antti Koivisto  <[email protected]>
+
+        Reviewed by Dave Hyatt.
+
+        Analyze stylesheet scope to minimize style recalcs
+        https://bugs.webkit.org/show_bug.cgi?id=75508
+        
+        It is a relatively common pattern to use inline stylesheets in document body where all rules are scoped using descendant selector
+
+        <style>
+        #foo {...}
+        #foo div {...}
+        #foo .bar {...}
+        </style>
+
+        When this pattern is used it is also common that the rules only apply to elements that come after the style element. 
+
+        When the set of active stylesheets changes we invalidate and recompute the entire document style. This is very expensive. We can 
+        detect the case above and avoid the style recalc.
+        
+        On engadget.com, this patch cuts the time spent in style recalcs to roughly half. There are further savings from reduced 
+        relayouts. In total the engine CPU time used over the page load is reduced by ~10%.
+
+        * css/CSSStyleSelector.cpp:
+        (WebCore::CSSStyleSelector::CSSStyleSelector):
+        (WebCore::CSSStyleSelector::collectFeatures):
+        
+            Refactor feature collection from constructor to a separate function.
+        
+        (WebCore::CSSStyleSelector::appendAuthorStylesheets):
+        
+            New function for non-destructively updating the style selector.
+        
+        (WebCore::CSSStyleSelector::Features::clear):
+        
+            Clear the features for another collection.
+            
+        (WebCore::CSSStyleSelector::determineStylesheetSelectorScopes):
+        
+            Find if all rules on a stylesheetare scoped to some set of ids and classes.
+        
+        (WebCore::CSSStyleSelector::styleForDocument):
+        
+            Add optional font selector argument. We updated the correct base style font on style selector construction but that is no longer sufficient
+            as font selector may be updated without reconstructing the style selector.
+        
+        (WebCore::RuleSet::addRulesFromSheet):
+        
+            Invalidate the matched declaration cache in case of new font-face rules.
+        
+        * css/CSSStyleSelector.h:
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::determineSelectorScopes):
+        
+            Find if all rules on a selector list are scoped to some set of ids and classes.
+        
+        * css/SelectorChecker.h:
+        * dom/Document.cpp:
+        (WebCore::Document::Document):
+        (WebCore::Document::recalcStyle):
+        
+            Pass the font selector, if exists, to styleForDocument so we always have the base font up to date.
+        
+        (WebCore::Document::combineCSSFeatureFlags):
+        (WebCore::Document::resetCSSFeatureFlags):
+        (WebCore::Document::createStyleSelector):
+
+            Refactor css feature flag resetting to functions.
+        
+        (WebCore::Document::removePendingSheet):
+        
+            Use new PendingStylesheetCompleted flag when new stylesheets arrive.
+
+        (WebCore::Document::styleSelectorChanged):
+
+            Skip style recalc if it is not needed.
+
+        (WebCore::Document::collectActiveStylesheets):
+        
+            Refactor collecting stylesheets to a separate function.
+        
+        (WebCore::Document::testAddedStylesheetRequiresStyleRecalc):
+        
+            Determine the scopes and use hasElementWithId/getElementsByClassName to figure out if any scoped elements currently exist in the tree.
+        
+        (WebCore::Document::analyzeStylesheetChange):
+        
+            Figure out if we can update the style selector incrementally and if we can skip the style recalc.
+        
+        (WebCore::Document::updateActiveStylesheets):
+        
+            Renamed from recalcStyleSelector.
+            Invoke the new analysis functions.
+        
+        * dom/Document.h:
+
 2012-01-04  Igor Oliveira  <[email protected]>
 
         Share fractional time code between AnimationBase and KeyframeAnimation

Modified: trunk/Source/WebCore/css/CSSStyleSelector.cpp (104059 => 104060)


--- trunk/Source/WebCore/css/CSSStyleSelector.cpp	2012-01-04 20:02:54 UTC (rev 104059)
+++ trunk/Source/WebCore/css/CSSStyleSelector.cpp	2012-01-04 20:03:35 UTC (rev 104060)
@@ -418,6 +418,16 @@
         if (sheet->isCSSStyleSheet() && !sheet->disabled())
             m_authorStyle->addRulesFromSheet(static_cast<CSSStyleSheet*>(sheet), *m_medium, this);
     }
+    m_authorStyle->shrinkToFit();
+
+    collectFeatures();
+    
+    if (document->renderer() && document->renderer()->style())
+        document->renderer()->style()->font().update(fontSelector());
+}
+
+void CSSStyleSelector::collectFeatures()
+{
     // Collect all ids and rules using sibling selectors (:first-child and similar)
     // in the current set of stylesheets. Style sharing code uses this information to reject
     // sharing candidates.
@@ -429,15 +439,29 @@
     m_authorStyle->collectFeatures(m_features);
     if (m_userStyle)
         m_userStyle->collectFeatures(m_features);
-
-    m_authorStyle->shrinkToFit();
     if (m_features.siblingRules)
         m_features.siblingRules->shrinkToFit();
     if (m_features.uncommonAttributeRules)
         m_features.uncommonAttributeRules->shrinkToFit();
+}
 
-    if (document->renderer() && document->renderer()->style())
-        document->renderer()->style()->font().update(fontSelector());
+void CSSStyleSelector::appendAuthorStylesheets(unsigned firstNew, const Vector<RefPtr<StyleSheet> >& stylesheets)
+{
+    // This handles sheets added to the end of the stylesheet list only. In other cases the style resolver
+    // needs to be reconstructed. To handle insertions too the rule order numbers would need to be updated.
+    unsigned size = stylesheets.size();
+    for (unsigned i = firstNew; i < size; ++i) {
+        if (!stylesheets[i]->isCSSStyleSheet() || stylesheets[i]->disabled())
+            continue;
+        m_authorStyle->addRulesFromSheet(static_cast<CSSStyleSheet*>(stylesheets[i].get()), *m_medium, this);
+    }
+    m_authorStyle->shrinkToFit();
+    // FIXME: This really only needs to collect the features from the newly added sheets.
+    m_features.clear();
+    collectFeatures();
+    
+    if (document()->renderer() && document()->renderer()->style())
+        document()->renderer()->style()->font().update(fontSelector());
 }
 
 void CSSStyleSelector::addRegionRule(PassRefPtr<WebKitCSSRegionRule> regionStyleRule)
@@ -469,6 +493,17 @@
 {
 }
 
+void CSSStyleSelector::Features::clear()
+{
+    idsInRules.clear();
+    attrsInRules.clear();
+    siblingRules.clear();
+    uncommonAttributeRules.clear();
+    usesFirstLineRules = false;
+    usesBeforeAfterRules = false;
+    usesLinkRules = false;
+}
+
 static CSSStyleSheet* parseUASheet(const String& str)
 {
     CSSStyleSheet* sheet = CSSStyleSheet::create().leakRef(); // leak the sheet on purpose
@@ -1148,7 +1183,7 @@
     }
 }
 
-PassRefPtr<RenderStyle> CSSStyleSelector::styleForDocument(Document* document)
+PassRefPtr<RenderStyle> CSSStyleSelector::styleForDocument(Document* document, CSSFontSelector* fontSelector)
 {
     Frame* frame = document->frame();
 
@@ -1204,7 +1239,7 @@
     }
 
     documentStyle->setFontDescription(fontDescription);
-    documentStyle->font().update(0);
+    documentStyle->font().update(fontSelector);
 
     return documentStyle.release();
 }
@@ -1799,6 +1834,33 @@
         return false;
     return true;
 }
+    
+bool CSSStyleSelector::determineStylesheetSelectorScopes(CSSStyleSheet* stylesheet, HashSet<AtomicStringImpl*>& idScopes, HashSet<AtomicStringImpl*>& classScopes)
+{
+    ASSERT(!stylesheet->isLoading());
+    
+    size_t size = stylesheet->length();
+    for (size_t i = 0; i < size; i++) {
+        CSSRule* rule = stylesheet->item(i);
+        if (rule->isStyleRule()) {
+            CSSStyleRule* styleRule = static_cast<CSSStyleRule*>(rule);
+            if (!SelectorChecker::determineSelectorScopes(styleRule->selectorList(), idScopes, classScopes))
+                return false;
+            continue;
+        } 
+        if (rule->isImportRule()) {
+            CSSImportRule* importRule = static_cast<CSSImportRule*>(rule);
+            if (importRule->styleSheet()) {
+                if (!determineStylesheetSelectorScopes(importRule->styleSheet(), idScopes, classScopes))
+                    return false;
+            }
+            continue;
+        }
+        // FIXME: Media rules and maybe some others could be allowed.
+        return false;
+    }
+    return true;
+}
 
 // -----------------------------------------------------------------
 
@@ -1968,6 +2030,7 @@
                         // Add this font face to our set.
                         const CSSFontFaceRule* fontFaceRule = static_cast<CSSFontFaceRule*>(childItem);
                         styleSelector->fontSelector()->addFontFaceRule(fontFaceRule);
+                        styleSelector->invalidateMatchedDeclarationCache();
                     } else if (childItem->isKeyframesRule() && styleSelector) {
                         // Add this keyframe rule to our set.
                         styleSelector->addKeyframeStyle(static_cast<WebKitCSSKeyframesRule*>(childItem));
@@ -1978,6 +2041,7 @@
             // Add this font face to our set.
             const CSSFontFaceRule* fontFaceRule = static_cast<CSSFontFaceRule*>(rule);
             styleSelector->fontSelector()->addFontFaceRule(fontFaceRule);
+            styleSelector->invalidateMatchedDeclarationCache();
         } else if (rule->isKeyframesRule())
             styleSelector->addKeyframeStyle(static_cast<WebKitCSSKeyframesRule*>(rule));
         else if (rule->isRegionRule() && styleSelector)

Modified: trunk/Source/WebCore/css/CSSStyleSelector.h (104059 => 104060)


--- trunk/Source/WebCore/css/CSSStyleSelector.h	2012-01-04 20:02:54 UTC (rev 104059)
+++ trunk/Source/WebCore/css/CSSStyleSelector.h	2012-01-04 20:03:35 UTC (rev 104060)
@@ -112,7 +112,7 @@
 
     PassRefPtr<RenderStyle> styleForPage(int pageIndex);
 
-    static PassRefPtr<RenderStyle> styleForDocument(Document*);
+    static PassRefPtr<RenderStyle> styleForDocument(Document*, CSSFontSelector* = 0);
 
     RenderStyle* style() const { return m_style.get(); }
     RenderStyle* parentStyle() const { return m_parentStyle; }
@@ -126,10 +126,16 @@
     void setEffectiveZoom(float f) { m_fontDirty |= style()->setEffectiveZoom(f); }
     void setTextSizeAdjust(bool b) { m_fontDirty |= style()->setTextSizeAdjust(b); }
     bool hasParentNode() const { return m_parentNode; }
+    
+    void appendAuthorStylesheets(unsigned firstNew, const Vector<RefPtr<StyleSheet> >&);
+    
+    // Find the ids or classes the selectors on a stylesheet are scoped to. The selectors only apply to elements in subtrees where the root element matches the scope.
+    static bool determineStylesheetSelectorScopes(CSSStyleSheet*, HashSet<AtomicStringImpl*>& idScopes, HashSet<AtomicStringImpl*>& classScopes);
 
 private:
     void initForStyleResolve(Element*, RenderStyle* parentStyle = 0, PseudoId = NOPSEUDO);
     void initElement(Element*);
+    void collectFeatures();
     RenderStyle* locateSharedStyle();
     bool matchesRuleSet(RuleSet*);
     Node* locateCousinList(Element* parent, unsigned& visitedNodeCount) const;
@@ -218,6 +224,7 @@
     struct Features {
         Features();
         ~Features();
+        void clear();
         HashSet<AtomicStringImpl*> idsInRules;
         HashSet<AtomicStringImpl*> attrsInRules;
         OwnPtr<RuleSet> siblingRules;

Modified: trunk/Source/WebCore/css/SelectorChecker.cpp (104059 => 104060)


--- trunk/Source/WebCore/css/SelectorChecker.cpp	2012-01-04 20:02:54 UTC (rev 104059)
+++ trunk/Source/WebCore/css/SelectorChecker.cpp	2012-01-04 20:03:35 UTC (rev 104060)
@@ -1398,4 +1398,28 @@
     return element->document()->frame() && element->document()->frame()->selection()->isFocusedAndActive();
 }
 
+bool SelectorChecker::determineSelectorScopes(const CSSSelectorList& selectorList, HashSet<AtomicStringImpl*>& idScopes, HashSet<AtomicStringImpl*>& classScopes)
+{
+    for (CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector)) {
+        CSSSelector* rightmostSelector = selector;
+        CSSSelector::Relation relation = CSSSelector::Descendant;
+        for (;rightmostSelector->tagHistory(); rightmostSelector = rightmostSelector->tagHistory()) {
+            if (rightmostSelector->relation() != CSSSelector::SubSelector)
+                relation = rightmostSelector->relation();
+        }
+        if (relation != CSSSelector::Descendant && relation != CSSSelector::Child)
+            return false;
+        if (rightmostSelector->m_match == CSSSelector::Id) {
+            idScopes.add(rightmostSelector->value().impl());
+            continue;
+        }
+        if (rightmostSelector->m_match == CSSSelector::Class) {
+            classScopes.add(rightmostSelector->value().impl());
+            continue;
+        }
+        return false;
+    }
+    return true;
 }
+
+}

Modified: trunk/Source/WebCore/css/SelectorChecker.h (104059 => 104060)


--- trunk/Source/WebCore/css/SelectorChecker.h	2012-01-04 20:02:54 UTC (rev 104059)
+++ trunk/Source/WebCore/css/SelectorChecker.h	2012-01-04 20:03:35 UTC (rev 104060)
@@ -90,6 +90,9 @@
     enum LinkMatchMask { MatchLink = 1, MatchVisited = 2, MatchAll = MatchLink | MatchVisited };
     static unsigned determineLinkMatchType(const CSSSelector*);
 
+    // Find the ids or classes selectors are scoped to. The selectors only apply to elements in subtrees where the root element matches the scope.
+    static bool determineSelectorScopes(const CSSSelectorList&, HashSet<AtomicStringImpl*>& idScopes, HashSet<AtomicStringImpl*>& classScopes);
+
 private:
     bool checkOneSelector(CSSSelector*, Element*, PseudoId& dynamicPseudo, bool isSubSelector, VisitedMatchType, RenderStyle*, RenderStyle* elementParentStyle) const;
     bool checkScrollbarPseudoClass(CSSSelector*, PseudoId& dynamicPseudo) const;

Modified: trunk/Source/WebCore/dom/Document.cpp (104059 => 104060)


--- trunk/Source/WebCore/dom/Document.cpp	2012-01-04 20:02:54 UTC (rev 104059)
+++ trunk/Source/WebCore/dom/Document.cpp	2012-01-04 20:03:35 UTC (rev 104060)
@@ -382,6 +382,7 @@
     , m_mutationObserverTypes(0)
 #endif
     , m_styleSheets(StyleSheetList::create(this))
+    , m_hadActiveLoadingStylesheet(false)
     , m_readyState(Complete)
     , m_styleRecalcTimer(this, &Document::styleRecalcTimerFired)
     , m_pendingStyleRecalcShouldForce(false)
@@ -1528,7 +1529,7 @@
         return; // Guard against re-entrancy. -dwh
     
     if (m_hasDirtyStyleSelector)
-        recalcStyleSelector();
+        updateActiveStylesheets(RecalcStyleImmediately);
 
     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willRecalculateStyle(this);
 
@@ -1553,7 +1554,7 @@
         // style selector may set this again during recalc
         m_hasNodesWithPlaceholderStyle = false;
         
-        RefPtr<RenderStyle> documentStyle = CSSStyleSelector::styleForDocument(this);
+        RefPtr<RenderStyle> documentStyle = CSSStyleSelector::styleForDocument(this, m_styleSelector ? m_styleSelector->fontSelector() : 0);
         StyleChange ch = diff(documentStyle.get(), renderer()->style());
         if (ch != NoChange)
             renderer()->setStyle(documentStyle.release());
@@ -1584,12 +1585,8 @@
     m_inStyleRecalc = false;
     
     // Pseudo element removal and similar may only work with these flags still set. Reset them after the style recalc.
-    if (m_styleSelector) {
-        m_usesSiblingRules = m_styleSelector->usesSiblingRules();
-        m_usesFirstLineRules = m_styleSelector->usesFirstLineRules();
-        m_usesBeforeAfterRules = m_styleSelector->usesBeforeAfterRules();
-        m_usesLinkRules = m_styleSelector->usesLinkRules();
-    }
+    if (m_styleSelector)
+        resetCSSFeatureFlags();
 
     if (frameView) {
         frameView->resumeScheduledEvents();
@@ -1778,6 +1775,23 @@
     setSecurityOrigin(SecurityOrigin::createUnique());
 }
 
+void Document::combineCSSFeatureFlags()
+{
+    // Delay resetting the flags until after next style recalc since unapplying the style may not work without these set (this is true at least with before/after).
+    m_usesSiblingRules = m_usesSiblingRules || m_styleSelector->usesSiblingRules();
+    m_usesFirstLineRules = m_usesFirstLineRules || m_styleSelector->usesFirstLineRules();
+    m_usesBeforeAfterRules = m_usesBeforeAfterRules || m_styleSelector->usesBeforeAfterRules();
+    m_usesLinkRules = m_usesLinkRules || m_styleSelector->usesLinkRules();
+}
+
+void Document::resetCSSFeatureFlags()
+{
+    m_usesSiblingRules = m_styleSelector->usesSiblingRules();
+    m_usesFirstLineRules = m_styleSelector->usesFirstLineRules();
+    m_usesBeforeAfterRules = m_styleSelector->usesBeforeAfterRules();
+    m_usesLinkRules = m_styleSelector->usesLinkRules();
+}
+
 void Document::createStyleSelector()
 {
     bool matchAuthorAndUserStyles = true;
@@ -1785,11 +1799,7 @@
         matchAuthorAndUserStyles = docSettings->authorAndUserStylesEnabled();
     m_styleSelector = adoptPtr(new CSSStyleSelector(this, m_styleSheets.get(), m_mappedElementSheet.get(), pageUserSheet(), pageGroupUserSheets(), m_userSheets.get(),
                                                     !inQuirksMode(), matchAuthorAndUserStyles));
-    // Delay resetting the flags until after next style recalc since unapplying the style may not work without these set (this is true at least with before/after).
-    m_usesSiblingRules = m_usesSiblingRules || m_styleSelector->usesSiblingRules();
-    m_usesFirstLineRules = m_usesFirstLineRules || m_styleSelector->usesFirstLineRules();
-    m_usesBeforeAfterRules = m_usesBeforeAfterRules || m_styleSelector->usesBeforeAfterRules();
-    m_usesLinkRules = m_usesLinkRules || m_styleSelector->usesLinkRules();
+    combineCSSFeatureFlags();
 }
 
 void Document::attach()
@@ -2950,7 +2960,7 @@
     if (m_pendingStylesheets)
         return;
 
-    styleSelectorChanged(RecalcStyleImmediately);
+    styleSelectorChanged(RecalcStyleIfNeeded);
 
     if (ScriptableDocumentParser* parser = scriptableDocumentParser())
         parser->executeScriptsWaitingForStylesheets();
@@ -2971,8 +2981,10 @@
         printf("Beginning update of style selector at time %d.\n", elapsedTime());
 #endif
 
-    recalcStyleSelector();
-    
+    bool stylesheetChangeRequiresStyleRecalc = updateActiveStylesheets(updateFlag);
+    if (!stylesheetChangeRequiresStyleRecalc)
+        return;
+
     if (updateFlag == DeferRecalcStyle) {
         scheduleForcedStyleRecalc();
         return;
@@ -3044,22 +3056,9 @@
 {
     m_styleSheetCandidateNodes.remove(node);
 }
-
-void Document::recalcStyleSelector()
+    
+void Document::collectActiveStylesheets(Vector<RefPtr<StyleSheet> >& sheets)
 {
-    if (m_inStyleRecalc) {
-        // SVG <use> element may manage to invalidate style selector in the middle of a style recalc.
-        // https://bugs.webkit.org/show_bug.cgi?id=54344
-        // FIXME: This should be fixed in SVG and this code replaced with ASSERT(!m_inStyleRecalc).
-        m_hasDirtyStyleSelector = true;
-        scheduleForcedStyleRecalc();
-        return;
-    }
-    if (!renderer() || !attached())
-        return;
-
-    StyleSheetVector sheets;
-
     bool matchAuthorAndUserStyles = true;
     if (Settings* settings = this->settings())
         matchAuthorAndUserStyles = settings->authorAndUserStylesEnabled();
@@ -3070,9 +3069,7 @@
         end = begin;
     for (StyleSheetCandidateListHashSet::iterator it = begin; it != end; ++it) {
         Node* n = *it;
-
         StyleSheet* sheet = 0;
-
         if (n->nodeType() == PROCESSING_INSTRUCTION_NODE) {
             // Processing instruction (XML documents only).
             // We don't support linking to embedded CSS stylesheets, see <https://bugs.webkit.org/show_bug.cgi?id=49281> for discussion.
@@ -3089,9 +3086,9 @@
 #endif
         } else if ((n->isHTMLElement() && (n->hasTagName(linkTag) || n->hasTagName(styleTag)))
 #if ENABLE(SVG)
-            ||  (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
+                   ||  (n->isSVGElement() && n->hasTagName(SVGNames::styleTag))
 #endif
-        ) {
+                   ) {
             Element* e = static_cast<Element*>(n);
             AtomicString title = e->getAttribute(titleAttr);
             bool enabledViaScript = false;
@@ -3115,7 +3112,6 @@
                 if (!linkElement->sheet())
                     title = nullAtom;
             }
-
             // Get the current preferred styleset.  This is the
             // set of sheets that will be enabled.
 #if ENABLE(SVG)
@@ -3128,7 +3124,6 @@
             else
                 // <STYLE> element
                 sheet = static_cast<HTMLStyleElement*>(n)->sheet();
-
             // Check to see if this sheet belongs to a styleset
             // (thus making it PREFERRED or ALTERNATE rather than
             // PERSISTENT).
@@ -3143,21 +3138,121 @@
                     if (e->hasLocalName(styleTag) || !rel.contains("alternate"))
                         m_preferredStylesheetSet = m_selectedStylesheetSet = title;
                 }
-
                 if (title != m_preferredStylesheetSet)
                     sheet = 0;
             }
         }
-
         if (sheet)
             sheets.append(sheet);
     }
+}
 
-    m_styleSheets->swap(sheets);
+bool Document::testAddedStylesheetRequiresStyleRecalc(CSSStyleSheet* stylesheet)
+{
+    if (stylesheet->disabled())
+        return false;
+    // See if all rules on the sheet are scoped to some specific ids or classes.
+    // Then test if we actually have any of those in the tree at the moment.
+    // FIXME: Even we if we find some, we could just invalidate those subtrees instead of invalidating the entire style.
+    HashSet<AtomicStringImpl*> idScopes; 
+    HashSet<AtomicStringImpl*> classScopes;
+    if (!CSSStyleSelector::determineStylesheetSelectorScopes(stylesheet, idScopes, classScopes))
+        return true;
+    // Testing for classes is not particularly fast so bail out if there are more than a few.
+    static const int maximumClassScopesToTest = 4;
+    if (classScopes.size() > maximumClassScopesToTest)
+        return true;
+    HashSet<AtomicStringImpl*>::iterator end = idScopes.end();
+    for (HashSet<AtomicStringImpl*>::iterator it = idScopes.begin(); it != end; ++it) {
+        if (hasElementWithId(*it))
+            return true;
+    }
+    end = classScopes.end();
+    for (HashSet<AtomicStringImpl*>::iterator it = classScopes.begin(); it != end; ++it) {
+        // FIXME: getElementsByClassName is not optimal for this. We should handle all classes in a single pass.
+        if (getElementsByClassName(*it)->length())
+            return true;
+    }
+    return false;
+}
+    
+void Document::analyzeStylesheetChange(StyleSelectorUpdateFlag updateFlag, const Vector<RefPtr<StyleSheet> >& newStylesheets, bool& requiresStyleSelectorReset, bool& requiresStyleRecalc)
+{
+    requiresStyleSelectorReset = true;
+    requiresStyleRecalc = true;
+    
+    // Stylesheets of <style> elements that @import stylesheets are active but loading. We need to trigger a full recalc when such loads are done.
+    bool hasActiveLoadingStylesheet = false;
+    unsigned newStylesheetCount = newStylesheets.size();
+    for (unsigned i = 0; i < newStylesheetCount; ++i) {
+        if (newStylesheets[i]->isLoading())
+            hasActiveLoadingStylesheet = true;
+    }
+    if (m_hadActiveLoadingStylesheet && !hasActiveLoadingStylesheet) {
+        m_hadActiveLoadingStylesheet = false;
+        return;
+    }
+    m_hadActiveLoadingStylesheet = hasActiveLoadingStylesheet;
 
-    m_styleSelector.clear();
+    if (updateFlag != RecalcStyleIfNeeded)
+        return;
+    if (!m_styleSelector)
+        return;
+
+    // See if we are just adding stylesheets.
+    unsigned oldStylesheetCount = m_styleSheets->length();
+    if (newStylesheetCount < oldStylesheetCount)
+        return;
+    for (unsigned i = 0; i < oldStylesheetCount; ++i) {
+        if (m_styleSheets->item(i) != newStylesheets[i])
+            return;
+    }
+    requiresStyleSelectorReset = false;
+
+    // If we are already parsing the body and so may have significant amount of elements, put some effort into trying to avoid style recalcs.
+    if (!body() || m_hasNodesWithPlaceholderStyle)
+        return;
+    for (unsigned i = oldStylesheetCount; i < newStylesheetCount; ++i) {
+        if (!newStylesheets[i]->isCSSStyleSheet())
+            return;
+        if (testAddedStylesheetRequiresStyleRecalc(static_cast<CSSStyleSheet*>(newStylesheets[i].get())))
+            return;
+    }
+    requiresStyleRecalc = false;
+}
+
+bool Document::updateActiveStylesheets(StyleSelectorUpdateFlag updateFlag)
+{
+    if (m_inStyleRecalc) {
+        // SVG <use> element may manage to invalidate style selector in the middle of a style recalc.
+        // https://bugs.webkit.org/show_bug.cgi?id=54344
+        // FIXME: This should be fixed in SVG and this code replaced with ASSERT(!m_inStyleRecalc).
+        m_hasDirtyStyleSelector = true;
+        scheduleForcedStyleRecalc();
+        return false;
+    }
+    if (!renderer() || !attached())
+        return false;
+
+    StyleSheetVector newStylesheets;
+    collectActiveStylesheets(newStylesheets);
+
+    bool requiresStyleSelectorReset;
+    bool requiresStyleRecalc;
+    analyzeStylesheetChange(updateFlag, newStylesheets, requiresStyleSelectorReset, requiresStyleRecalc);
+
+    if (requiresStyleSelectorReset)
+        m_styleSelector.clear();
+    else {
+        m_styleSelector->appendAuthorStylesheets(m_styleSheets->length(), newStylesheets);
+        resetCSSFeatureFlags();
+    }
+    m_styleSheets->swap(newStylesheets);
+
     m_didCalculateStyleSelector = true;
     m_hasDirtyStyleSelector = false;
+    
+    return requiresStyleRecalc;
 }
 
 void Document::setHoverNode(PassRefPtr<Node> newHoverNode)

Modified: trunk/Source/WebCore/dom/Document.h (104059 => 104060)


--- trunk/Source/WebCore/dom/Document.h	2012-01-04 20:02:54 UTC (rev 104059)
+++ trunk/Source/WebCore/dom/Document.h	2012-01-04 20:03:35 UTC (rev 104060)
@@ -211,7 +211,7 @@
     PageshowEventPersisted = 1
 };
 
-enum StyleSelectorUpdateFlag { RecalcStyleImmediately, DeferRecalcStyle };
+enum StyleSelectorUpdateFlag { RecalcStyleImmediately, DeferRecalcStyle, RecalcStyleIfNeeded };
 
 class Document : public TreeScope, public ScriptExecutionContext {
 public:
@@ -456,7 +456,7 @@
     }
 
     /**
-     * Updates the pending sheet count and then calls updateStyleSelector.
+     * Updates the pending sheet count and then calls updateActiveStylesheets.
      */
     void removePendingSheet();
 
@@ -491,7 +491,6 @@
      * found and is used to calculate the derived styles for all rendering objects.
      */
     void styleSelectorChanged(StyleSelectorUpdateFlag);
-    void recalcStyleSelector();
 
     bool usesSiblingRules() const { return m_usesSiblingRules || m_usesSiblingRulesOverride; }
     void setUsesSiblingRules(bool b) { m_usesSiblingRulesOverride = b; }
@@ -1161,6 +1160,13 @@
     void buildAccessKeyMap(TreeScope* root);
 
     void createStyleSelector();
+    void combineCSSFeatureFlags();
+    void resetCSSFeatureFlags();
+    
+    bool updateActiveStylesheets(StyleSelectorUpdateFlag);
+    void collectActiveStylesheets(Vector<RefPtr<StyleSheet> >&);
+    bool testAddedStylesheetRequiresStyleRecalc(CSSStyleSheet*);
+    void analyzeStylesheetChange(StyleSelectorUpdateFlag, const Vector<RefPtr<StyleSheet> >& newStylesheets, bool& requiresStyleSelectorReset, bool& requiresStyleRecalc);
 
     void deleteCustomFonts();
 
@@ -1264,6 +1270,7 @@
 #endif
 
     RefPtr<StyleSheetList> m_styleSheets; // All of the stylesheets that are currently in effect for our media type and stylesheet set.
+    bool m_hadActiveLoadingStylesheet;
     
     typedef ListHashSet<Node*, 32> StyleSheetCandidateListHashSet;
     StyleSheetCandidateListHashSet m_styleSheetCandidateNodes; // All of the nodes that could potentially provide stylesheets to the document (<link>, <style>, <?xml-stylesheet>)
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to