Check selectors exactly when invalidating style
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Feb 2014 01:58:40 +0000 (01:58 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Feb 2014 01:58:40 +0000 (01:58 +0000)
https://bugs.webkit.org/show_bug.cgi?id=128321

Reviewed by Andreas Kling.

Selectors are now really fast to match with the JIT. Take advantage of this by invalidating
the document style exactly when a new stylesheet arrives (instead of using heuristics).

This reduces need for large style recalculations in some common cases.

* css/ElementRuleCollector.cpp:
(WebCore::ElementRuleCollector::clearMatchedRules):
(WebCore::ElementRuleCollector::collectMatchingRulesForList):
* css/ElementRuleCollector.h:
(WebCore::ElementRuleCollector::hasMatchedRules):
* css/RuleSet.h:
(WebCore::RuleSet::hasShadowPseudoElementRules):
* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::matchRecursively):
* css/SelectorChecker.h:

    Add new mode where all pseudo elements match so we can invalidate their element.

* css/StyleInvalidationAnalysis.cpp:
(WebCore::shouldDirtyAllStyle):
(WebCore::StyleInvalidationAnalysis::StyleInvalidationAnalysis):
(WebCore::invalidateStyleRecursively):
(WebCore::StyleInvalidationAnalysis::invalidateStyle):

    Switch to real selector checker.

* css/StyleInvalidationAnalysis.h:
* css/StyleResolver.cpp:
(WebCore::StyleResolver::MatchedProperties::~MatchedProperties):
* css/StyleResolver.h:
(WebCore::StyleResolver::mediaQueryEvaluator):
* dom/DocumentStyleSheetCollection.cpp:
(WebCore::DocumentStyleSheetCollection::analyzeStyleSheetChange):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@163592 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/css/ElementRuleCollector.cpp
Source/WebCore/css/ElementRuleCollector.h
Source/WebCore/css/RuleSet.h
Source/WebCore/css/SelectorChecker.cpp
Source/WebCore/css/SelectorChecker.h
Source/WebCore/css/StyleInvalidationAnalysis.cpp
Source/WebCore/css/StyleInvalidationAnalysis.h
Source/WebCore/css/StyleResolver.cpp
Source/WebCore/css/StyleResolver.h
Source/WebCore/dom/DocumentStyleSheetCollection.cpp

index f230516..a86b309 100644 (file)
@@ -1,3 +1,44 @@
+2014-02-06  Antti Koivisto  <antti@apple.com>
+
+        Check selectors exactly when invalidating style
+        https://bugs.webkit.org/show_bug.cgi?id=128321
+
+        Reviewed by Andreas Kling.
+        
+        Selectors are now really fast to match with the JIT. Take advantage of this by invalidating
+        the document style exactly when a new stylesheet arrives (instead of using heuristics).
+        
+        This reduces need for large style recalculations in some common cases.
+
+        * css/ElementRuleCollector.cpp:
+        (WebCore::ElementRuleCollector::clearMatchedRules):
+        (WebCore::ElementRuleCollector::collectMatchingRulesForList):
+        * css/ElementRuleCollector.h:
+        (WebCore::ElementRuleCollector::hasMatchedRules):
+        * css/RuleSet.h:
+        (WebCore::RuleSet::hasShadowPseudoElementRules):
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::matchRecursively):
+        * css/SelectorChecker.h:
+        
+            Add new mode where all pseudo elements match so we can invalidate their element.
+
+        * css/StyleInvalidationAnalysis.cpp:
+        (WebCore::shouldDirtyAllStyle):
+        (WebCore::StyleInvalidationAnalysis::StyleInvalidationAnalysis):
+        (WebCore::invalidateStyleRecursively):
+        (WebCore::StyleInvalidationAnalysis::invalidateStyle):
+        
+            Switch to real selector checker.
+
+        * css/StyleInvalidationAnalysis.h:
+        * css/StyleResolver.cpp:
+        (WebCore::StyleResolver::MatchedProperties::~MatchedProperties):
+        * css/StyleResolver.h:
+        (WebCore::StyleResolver::mediaQueryEvaluator):
+        * dom/DocumentStyleSheetCollection.cpp:
+        (WebCore::DocumentStyleSheetCollection::analyzeStyleSheetChange):
+
 2014-02-06  Gavin Barraclough  <barraclough@apple.com>
 
         Remove ChildProcess::m_activeTasks
index f0f1d68..e2b505d 100644 (file)
@@ -94,7 +94,7 @@ inline void ElementRuleCollector::addMatchedRule(const RuleData* rule)
     m_matchedRules->append(rule);
 }
 
-inline void ElementRuleCollector::clearMatchedRules()
+void ElementRuleCollector::clearMatchedRules()
 {
     if (!m_matchedRules)
         return;
@@ -359,7 +359,7 @@ void ElementRuleCollector::collectMatchingRulesForList(const Vector<RuleData>* r
         PseudoId dynamicPseudo = NOPSEUDO;
         if (ruleMatches(ruleData, dynamicPseudo)) {
             // For SharingRules testing, any match is good enough, we don't care what is matched.
-            if (m_mode == SelectorChecker::SharingRules) {
+            if (m_mode == SelectorChecker::SharingRules || m_mode == SelectorChecker::StyleInvalidation) {
                 addMatchedRule(&ruleData);
                 break;
             }
index a163b3d..2c4a83e 100644 (file)
@@ -70,6 +70,9 @@ public:
     StyleResolver::MatchResult& matchedResult();
     const Vector<RefPtr<StyleRuleBase>>& matchedRuleList() const;
 
+    bool hasMatchedRules() const { return m_matchedRules && !m_matchedRules->isEmpty(); }
+    void clearMatchedRules();
+
 private:
     void addElementStyleProperties(const StyleProperties*, bool isCacheable = true);
 
@@ -84,7 +87,6 @@ private:
     void sortAndTransferMatchedRules();
 
     void addMatchedRule(const RuleData*);
-    void clearMatchedRules();
 
     Element& m_element;
     RenderStyle* m_style;
index 59fb197..fd82612 100644 (file)
@@ -166,6 +166,8 @@ public:
 
     unsigned ruleCount() const { return m_ruleCount; }
 
+    bool hasShadowPseudoElementRules() const { return !m_shadowPseudoElementRules.isEmpty(); }
+
 private:
     void addChildRules(const Vector<RefPtr<StyleRuleBase>>&, const MediaQueryEvaluator& medium, StyleResolver*, bool hasDocumentSecurityOrigin, AddRuleFlags);
     bool findBestRuleSetAndAdd(const CSSSelector*, RuleData&);
index 449e4e4..0f61235 100644 (file)
@@ -2,7 +2,7 @@
  * 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, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Apple Inc. All rights reserved.
  * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
  * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org>
  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
@@ -162,13 +162,14 @@ SelectorChecker::Match SelectorChecker::matchRecursively(const SelectorCheckingC
 
                 if (context.selector->pseudoType() == CSSSelector::PseudoWebKitCustomElement && root->type() != ShadowRoot::UserAgentShadowRoot)
                     return SelectorFailsLocally;
-            } else
+            } else if (m_mode != StyleInvalidation)
                 return SelectorFailsLocally;
         } else {
             if ((!context.elementStyle && m_mode == ResolvingStyle) || m_mode == QueryingRules)
                 return SelectorFailsLocally;
 
-            PseudoId pseudoId = CSSSelector::pseudoId(context.selector->pseudoType());
+            // When invalidating style all pseudo elements need to match.
+            PseudoId pseudoId = m_mode == StyleInvalidation ? NOPSEUDO : CSSSelector::pseudoId(context.selector->pseudoType());
             if (pseudoId == FIRST_LETTER)
                 context.element->document().styleSheetCollection().setUsesFirstLetterRules(true);
             if (pseudoId != NOPSEUDO)
index ac68862..09aa690 100644 (file)
@@ -47,7 +47,7 @@ class SelectorChecker {
 
 public:
     enum VisitedMatchType { VisitedMatchDisabled, VisitedMatchEnabled };
-    enum Mode { ResolvingStyle = 0, CollectingRules, QueryingRules, SharingRules };
+    enum Mode { ResolvingStyle = 0, CollectingRules, QueryingRules, SharingRules, StyleInvalidation };
 
     SelectorChecker(Document&, Mode);
 
index 48383ed..4e9a04d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2012, 2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "CSSSelectorList.h"
 #include "Document.h"
 #include "ElementIterator.h"
+#include "ElementRuleCollector.h"
+#include "SelectorFilter.h"
 #include "StyleRuleImport.h"
 #include "StyleSheetContents.h"
-#include "StyledElement.h"
 
 namespace WebCore {
 
-StyleInvalidationAnalysis::StyleInvalidationAnalysis(const Vector<StyleSheetContents*>& sheets)
-    : m_dirtiesAllStyle(false)
+static bool shouldDirtyAllStyle(const Vector<RefPtr<StyleRuleBase>> rules)
 {
-    for (unsigned i = 0; i < sheets.size() && !m_dirtiesAllStyle; ++i)
-        analyzeStyleSheet(sheets[i]);
-}
-
-static bool determineSelectorScopes(const CSSSelectorList& selectorList, HashSet<AtomicStringImpl*>& idScopes, HashSet<AtomicStringImpl*>& classScopes)
-{
-    for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector)) {
-        const CSSSelector* scopeSelector = 0;
-        // This picks the widest scope, not the narrowest, to minimize the number of found scopes.
-        for (const CSSSelector* current = selector; current; current = current->tagHistory()) {
-            // Prefer ids over classes.
-            if (current->m_match == CSSSelector::Id)
-                scopeSelector = current;
-            else if (current->m_match == CSSSelector::Class && (!scopeSelector || scopeSelector->m_match != CSSSelector::Id))
-                scopeSelector = current;
-            CSSSelector::Relation relation = current->relation();
-            if (relation != CSSSelector::Descendant && relation != CSSSelector::Child && relation != CSSSelector::SubSelector)
-                break;
+    for (auto& rule : rules) {
+        if (rule->isMediaRule()) {
+            if (shouldDirtyAllStyle(static_cast<StyleRuleMedia&>(*rule).childRules()))
+                return true;
+            continue;
         }
-        if (!scopeSelector)
-            return false;
-        ASSERT(scopeSelector->m_match == CSSSelector::Class || scopeSelector->m_match == CSSSelector::Id);
-        if (scopeSelector->m_match == CSSSelector::Id)
-            idScopes.add(scopeSelector->value().impl());
-        else
-            classScopes.add(scopeSelector->value().impl());
+        // FIXME: At least font faces don't need full recalc in all cases.
+        if (!rule->isStyleRule())
+            return true;
     }
-    return true;
+    return false;
 }
 
-void StyleInvalidationAnalysis::analyzeStyleSheet(StyleSheetContents* styleSheetContents)
+static bool shouldDirtyAllStyle(const StyleSheetContents& sheet)
 {
-    ASSERT(!styleSheetContents->isLoading());
-
-    // 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.
-    const Vector<RefPtr<StyleRuleImport>>& importRules = styleSheetContents->importRules();
-    for (unsigned i = 0; i < importRules.size(); ++i) {
-        if (!importRules[i]->styleSheet())
+    for (auto& import : sheet.importRules()) {
+        if (!import->styleSheet())
             continue;
-        analyzeStyleSheet(importRules[i]->styleSheet());
-        if (m_dirtiesAllStyle)
-            return;
-    }
-    const Vector<RefPtr<StyleRuleBase>>& rules = styleSheetContents->childRules();
-    for (unsigned i = 0; i < rules.size(); i++) {
-        StyleRuleBase* rule = rules[i].get();
-        if (!rule->isStyleRule()) {
-            // FIXME: Media rules and maybe some others could be allowed.
-            m_dirtiesAllStyle = true;
-            return;
-        }
-        StyleRule* styleRule = static_cast<StyleRule*>(rule);
-        if (!determineSelectorScopes(styleRule->selectorList(), m_idScopes, m_classScopes)) {
-            m_dirtiesAllStyle = true;
-            return;
-        }
+        if (shouldDirtyAllStyle(*import->styleSheet()))
+            return true;
     }
+    if (shouldDirtyAllStyle(sheet.childRules()))
+        return true;
+    return false;
 }
 
-static bool elementMatchesSelectorScopes(const Element& element, const HashSet<AtomicStringImpl*>& idScopes, const HashSet<AtomicStringImpl*>& classScopes)
+static bool shouldDirtyAllStyle(const Vector<StyleSheetContents*>& sheets)
 {
-    if (!idScopes.isEmpty() && element.hasID() && idScopes.contains(element.idForStyleResolution().impl()))
-        return true;
-    if (classScopes.isEmpty() || !element.hasClass())
-        return false;
-    const SpaceSplitString& classNames = element.classNames();
-    for (unsigned i = 0; i < classNames.size(); ++i) {
-        if (classScopes.contains(classNames[i].impl()))
+    for (auto& sheet : sheets) {
+        if (shouldDirtyAllStyle(*sheet))
             return true;
     }
     return false;
 }
 
+StyleInvalidationAnalysis::StyleInvalidationAnalysis(const Vector<StyleSheetContents*>& sheets, const MediaQueryEvaluator& mediaQueryEvaluator)
+    : m_dirtiesAllStyle(shouldDirtyAllStyle(sheets))
+{
+    if (m_dirtiesAllStyle)
+        return;
+
+    m_ruleSets.resetAuthorStyle();
+    for (auto& sheet : sheets)
+        m_ruleSets.authorStyle()->addRulesFromSheet(sheet, mediaQueryEvaluator);
+
+    // FIXME: We don't descent into shadow trees or otherwise handle shadow pseudo elements.
+    if (m_ruleSets.authorStyle()->hasShadowPseudoElementRules())
+        m_dirtiesAllStyle = true;
+}
+
+static void invalidateStyleRecursively(Element& element, SelectorFilter& filter, const DocumentRuleSets& ruleSets)
+{
+    if (element.styleChangeType() > InlineStyleChange)
+        return;
+    if (element.styleChangeType() == NoStyleChange) {
+        ElementRuleCollector ruleCollector(element, nullptr, ruleSets, filter);
+        ruleCollector.setMode(SelectorChecker::StyleInvalidation);
+        ruleCollector.matchAuthorRules(false);
+
+        if (ruleCollector.hasMatchedRules())
+            element.setNeedsStyleRecalc(InlineStyleChange);
+    }
+
+    auto children = childrenOfType<Element>(element);
+    if (!children.first())
+        return;
+    filter.pushParent(&element);
+    for (auto& child : children)
+        invalidateStyleRecursively(child, filter, ruleSets);
+    filter.popParent();
+}
+
 void StyleInvalidationAnalysis::invalidateStyle(Document& document)
 {
     ASSERT(!m_dirtiesAllStyle);
-    if (m_idScopes.isEmpty() && m_classScopes.isEmpty())
+    if (!m_ruleSets.authorStyle())
         return;
 
-    auto it = descendantsOfType<Element>(document).begin();
-    auto end = descendantsOfType<Element>(document).end();
-    while (it != end) {
-        if (elementMatchesSelectorScopes(*it, m_idScopes, m_classScopes)) {
-            it->setNeedsStyleRecalc();
-            // The whole subtree is now invalidated, we can skip to the next sibling.
-            it.traverseNextSkippingChildren();
-            continue;
-        }
-        ++it;
-    }
+    Element* documentElement = document.documentElement();
+    if (!documentElement)
+        return;
+
+    SelectorFilter filter;
+    filter.setupParentStack(documentElement);
+    invalidateStyleRecursively(*documentElement, filter, m_ruleSets);
 }
 
 }
index cb242d2..1f81516 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2012, 2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -26,6 +26,7 @@
 #ifndef StyleInvalidationAnalysis_h
 #define StyleInvalidationAnalysis_h
 
+#include "DocumentRuleSets.h"
 #include <wtf/HashSet.h>
 #include <wtf/text/AtomicStringImpl.h>
 
@@ -36,18 +37,14 @@ class StyleSheetContents;
 
 class StyleInvalidationAnalysis {
 public:
-    StyleInvalidationAnalysis(const Vector<StyleSheetContents*>&);
+    StyleInvalidationAnalysis(const Vector<StyleSheetContents*>&, const MediaQueryEvaluator&);
 
     bool dirtiesAllStyle() const { return m_dirtiesAllStyle; }
     void invalidateStyle(Document&);
 
 private:
-
-    void analyzeStyleSheet(StyleSheetContents*);
-
     bool m_dirtiesAllStyle;
-    HashSet<AtomicStringImpl*> m_idScopes;
-    HashSet<AtomicStringImpl*> m_classScopes;
+    DocumentRuleSets m_ruleSets;
 };
 
 }
index a303bf6..0cafd18 100644 (file)
@@ -3657,7 +3657,7 @@ inline StyleResolver::MatchedProperties::MatchedProperties()
 {
 }
 
-inline StyleResolver::MatchedProperties::~MatchedProperties()
+StyleResolver::MatchedProperties::~MatchedProperties()
 {
 }
 
index 7d29703..103dd5c 100644 (file)
@@ -168,6 +168,8 @@ public:
     const DocumentRuleSets& ruleSets() const { return m_ruleSets; }
     SelectorFilter& selectorFilter() { return m_selectorFilter; }
 
+    const MediaQueryEvaluator& mediaQueryEvaluator() const { return *m_medium; }
+
 private:
     void initElement(Element*);
     RenderStyle* locateSharedStyle();
index de60a63..fa236ab 100644 (file)
@@ -366,6 +366,7 @@ void DocumentStyleSheetCollection::analyzeStyleSheetChange(UpdateFlag updateFlag
         return;
     if (!m_document.styleResolverIfExists())
         return;
+    StyleResolver& styleResolver = *m_document.styleResolverIfExists();
 
     // Find out which stylesheets are new.
     unsigned oldStylesheetCount = m_activeAuthorStyleSheets.size();
@@ -396,7 +397,7 @@ void DocumentStyleSheetCollection::analyzeStyleSheetChange(UpdateFlag updateFlag
     // 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 (!m_document.body() || m_document.hasNodesWithPlaceholderStyle())
         return;
-    StyleInvalidationAnalysis invalidationAnalysis(addedSheets);
+    StyleInvalidationAnalysis invalidationAnalysis(addedSheets, styleResolver.mediaQueryEvaluator());
     if (invalidationAnalysis.dirtiesAllStyle())
         return;
     invalidationAnalysis.invalidateStyle(m_document);