Title: [152453] trunk
Revision
152453
Author
[email protected]
Date
2013-07-08 08:53:54 -0700 (Mon, 08 Jul 2013)

Log Message

REGRESSION(r125294): A style rule with more than 8192 selectors can cause style corruption.
<http://webkit.org/b/118369>
<rdar://problem/14291428>

Reviewed by Antti Koivisto.

Source/WebCore:

Add a semi-hard cap of 8192 selectors per CSS rule. Rules with longer selector lists will be split into
multiple rules (with 8192 selectors per rule.) The style properties are shared behind the scenes
so it's all pretty efficient.

This does generate a different CSSOM for gigantic selector lists, I've added a test to document this.
Note that setting CSSStyleRule.selectorText with over 8192 selectors will now fail, the splitting into
multiple rules only happens when parsing full style sheets.

Test: fast/css/rule-selector-overflow.html

* css/CSSSelectorList.cpp:
(WebCore::CSSSelectorList::selectorCount):

    Add a helper to count the number of selectors in a list (not the number of raw selector components.)

* css/RuleSet.h:
* css/CSSStyleRule.cpp:
(WebCore::CSSStyleRule::setSelectorText):

    Add RuleData::maximumSelectorCount constant and cap CSSStyleRule.selectorText to that many selectors.

* css/CSSSelectorList.h:
(WebCore::CSSSelectorList::adoptSelectorArray):
* css/StyleRule.cpp:
(WebCore::StyleRule::create):
(WebCore::StyleRule::splitIntoMultipleRulesWithMaximumSelectorCount):
* css/StyleRule.h:
(WebCore::StyleRule::parserAdoptSelectorArray):
* css/StyleSheetContents.cpp:
(WebCore::StyleSheetContents::parserAppendRule):

    The meat of this change. Split rules with >8K selectors into multiple rules with 8K selectors in each.

* css/StyleRule.h:
(WebCore::toStyleRule):

    Added a toStyleRule() helper for good measure.

LayoutTests:

Added a test to document the new cap of 8192 selectors per CSS rule.

* fast/css/rule-selector-overflow-expected.txt: Added.
* fast/css/rule-selector-overflow.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (152452 => 152453)


--- trunk/LayoutTests/ChangeLog	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/LayoutTests/ChangeLog	2013-07-08 15:53:54 UTC (rev 152453)
@@ -1,3 +1,16 @@
+2013-07-08  Andreas Kling  <[email protected]>
+
+        REGRESSION(r125294): A style rule with more than 8192 selectors can cause style corruption.
+        <http://webkit.org/b/118369>
+        <rdar://problem/14291428>
+
+        Reviewed by Antti Koivisto.
+
+        Added a test to document the new cap of 8192 selectors per CSS rule.
+
+        * fast/css/rule-selector-overflow-expected.txt: Added.
+        * fast/css/rule-selector-overflow.html: Added.
+
 2013-07-08  Simon Pena  <[email protected]>
 
         [GTK] Unreviewed gardening. Update TestExpectations

Added: trunk/LayoutTests/fast/css/rule-selector-overflow-expected.txt (0 => 152453)


--- trunk/LayoutTests/fast/css/rule-selector-overflow-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/css/rule-selector-overflow-expected.txt	2013-07-08 15:53:54 UTC (rev 152453)
@@ -0,0 +1,27 @@
+This test tests and documents the behavior of CSS style rules with a massive number of selectors. Rules with >8192 selectors get split into multiple rules at the parsing stage. Setting a rule's selectorText via CSSOM will do nothing if there are more than 8192 selectors.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS rule().selectorText = selectorListWithLength(1); rule().selectorText is selectorListWithLength(1)
+PASS rule().selectorText = selectorListWithLength(8192); rule().selectorText is selectorListWithLength(8192)
+PASS rule().selectorText = '.reset'; rule().selectorText is '.reset'
+PASS rule().selectorText = selectorListWithLength(8193); rule().selectorText is '.reset'
+PASS rule().selectorText = '.reset'; rule().selectorText is '.reset'
+PASS rule().selectorText = selectorListWithLength(8193); sheet().rules.length is 1
+PASS rule().selectorText = selectorListWithLength(8192); rule().selectorText is selectorListWithLength(8192)
+PASS rule().selectorText = selectorListWithLength(8192); sheet().rules.length is 1
+PASS rule().selectorText = '.reset'; rule().selectorText is '.reset'
+PASS rule().selectorText = selectorListWithLength(8193); rule().selectorText is '.reset'
+PASS rule().selectorText = selectorListWithLength(8193); sheet().rules.length is 1
+PASS styleElement.innerText = styleSheetWithSelectorLength(1); rule().selectorText is selectorListWithLength(1)
+PASS styleElement.innerText = styleSheetWithSelectorLength(8192); rule().selectorText is selectorListWithLength(8192)
+PASS styleElement.innerText = styleSheetWithSelectorLength(8192); sheet().rules.length is 1
+PASS styleElement.innerText = styleSheetWithSelectorLength(8193); rule().selectorText is selectorListWithLength(8192)
+PASS styleElement.innerText = styleSheetWithSelectorLength(8193); sheet().rules.length is 2
+PASS styleElement.innerText = styleSheetWithSelectorLength(16384); sheet().rules.length is 2
+PASS styleElement.innerText = styleSheetWithSelectorLength(16385); sheet().rules.length is 3
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/css/rule-selector-overflow.html (0 => 152453)


--- trunk/LayoutTests/fast/css/rule-selector-overflow.html	                        (rev 0)
+++ trunk/LayoutTests/fast/css/rule-selector-overflow.html	2013-07-08 15:53:54 UTC (rev 152453)
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src=""
+<style id="stylebro" type="text/css">
+.peb {
+    color: red;
+}
+</style>
+</head>
+<body>
+<script>
+
+styleElement = document.getElementById("stylebro");
+
+function selectorListWithLength(length)
+{
+    var a = new Array();
+    for (i = 0; i < length; ++i)
+        a.push(".x");
+    return a.join(", ");
+}
+
+function styleSheetWithSelectorLength(length)
+{
+    return selectorListWithLength(length) + " { color: red; }";
+}
+
+function sheet()
+{
+    return styleElement.sheet;
+}
+
+function rule()
+{
+    return sheet().cssRules[0];
+}
+
+description("This test tests and documents the behavior of CSS style rules with a massive number of selectors. Rules with >8192 selectors get split into multiple rules at the parsing stage. Setting a rule's selectorText via CSSOM will do nothing if there are more than 8192 selectors.");
+
+shouldBe("rule().selectorText = selectorListWithLength(1); rule().selectorText", "selectorListWithLength(1)");
+shouldBe("rule().selectorText = selectorListWithLength(8192); rule().selectorText", "selectorListWithLength(8192)");
+shouldBe("rule().selectorText = '.reset'; rule().selectorText", "'.reset'");
+shouldBe("rule().selectorText = selectorListWithLength(8193); rule().selectorText", "'.reset'");
+shouldBe("rule().selectorText = '.reset'; rule().selectorText", "'.reset'");
+shouldBe("rule().selectorText = selectorListWithLength(8193); sheet().rules.length", "1");
+shouldBe("rule().selectorText = selectorListWithLength(8192); rule().selectorText", "selectorListWithLength(8192)");
+shouldBe("rule().selectorText = selectorListWithLength(8192); sheet().rules.length", "1");
+shouldBe("rule().selectorText = '.reset'; rule().selectorText", "'.reset'");
+shouldBe("rule().selectorText = selectorListWithLength(8193); rule().selectorText", "'.reset'");
+shouldBe("rule().selectorText = selectorListWithLength(8193); sheet().rules.length", "1");
+
+shouldBe("styleElement.innerText = styleSheetWithSelectorLength(1); rule().selectorText", "selectorListWithLength(1)");
+shouldBe("styleElement.innerText = styleSheetWithSelectorLength(8192); rule().selectorText", "selectorListWithLength(8192)");
+shouldBe("styleElement.innerText = styleSheetWithSelectorLength(8192); sheet().rules.length", "1");
+shouldBe("styleElement.innerText = styleSheetWithSelectorLength(8193); rule().selectorText", "selectorListWithLength(8192)");
+shouldBe("styleElement.innerText = styleSheetWithSelectorLength(8193); sheet().rules.length", "2");
+shouldBe("styleElement.innerText = styleSheetWithSelectorLength(16384); sheet().rules.length", "2");
+shouldBe("styleElement.innerText = styleSheetWithSelectorLength(16385); sheet().rules.length", "3");
+
+</script>
+<script src=""
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (152452 => 152453)


--- trunk/Source/WebCore/ChangeLog	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/ChangeLog	2013-07-08 15:53:54 UTC (rev 152453)
@@ -1,3 +1,49 @@
+2013-07-08  Andreas Kling  <[email protected]>
+
+        REGRESSION(r125294): A style rule with more than 8192 selectors can cause style corruption.
+        <http://webkit.org/b/118369>
+        <rdar://problem/14291428>
+
+        Reviewed by Antti Koivisto.
+
+        Add a semi-hard cap of 8192 selectors per CSS rule. Rules with longer selector lists will be split into
+        multiple rules (with 8192 selectors per rule.) The style properties are shared behind the scenes
+        so it's all pretty efficient.
+
+        This does generate a different CSSOM for gigantic selector lists, I've added a test to document this.
+        Note that setting CSSStyleRule.selectorText with over 8192 selectors will now fail, the splitting into
+        multiple rules only happens when parsing full style sheets.
+
+        Test: fast/css/rule-selector-overflow.html
+
+        * css/CSSSelectorList.cpp:
+        (WebCore::CSSSelectorList::selectorCount):
+
+            Add a helper to count the number of selectors in a list (not the number of raw selector components.)
+
+        * css/RuleSet.h:
+        * css/CSSStyleRule.cpp:
+        (WebCore::CSSStyleRule::setSelectorText):
+
+            Add RuleData::maximumSelectorCount constant and cap CSSStyleRule.selectorText to that many selectors.
+
+        * css/CSSSelectorList.h:
+        (WebCore::CSSSelectorList::adoptSelectorArray):
+        * css/StyleRule.cpp:
+        (WebCore::StyleRule::create):
+        (WebCore::StyleRule::splitIntoMultipleRulesWithMaximumSelectorCount):
+        * css/StyleRule.h:
+        (WebCore::StyleRule::parserAdoptSelectorArray):
+        * css/StyleSheetContents.cpp:
+        (WebCore::StyleSheetContents::parserAppendRule):
+
+            The meat of this change. Split rules with >8K selectors into multiple rules with 8K selectors in each.
+
+        * css/StyleRule.h:
+        (WebCore::toStyleRule):
+
+            Added a toStyleRule() helper for good measure.
+
 2013-07-08  Christophe Dumez  <[email protected]>
 
         Introduce DECLARE_FORWARDING_ATTRIBUTE_EVENT_LISTENER() macro

Modified: trunk/Source/WebCore/css/CSSSelectorList.cpp (152452 => 152453)


--- trunk/Source/WebCore/css/CSSSelectorList.cpp	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/css/CSSSelectorList.cpp	2013-07-08 15:53:54 UTC (rev 152453)
@@ -85,6 +85,14 @@
     selectorVector.clear();
 }
 
+unsigned CSSSelectorList::selectorCount() const
+{
+    unsigned count = 0;
+    for (const CSSSelector* s = first(); s; s = next(s))
+        ++count;
+    return count;
+}
+
 unsigned CSSSelectorList::length() const
 {
     if (!m_selectorArray)

Modified: trunk/Source/WebCore/css/CSSSelectorList.h (152452 => 152453)


--- trunk/Source/WebCore/css/CSSSelectorList.h	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/css/CSSSelectorList.h	2013-07-08 15:53:54 UTC (rev 152453)
@@ -42,6 +42,7 @@
 
     void adopt(CSSSelectorList& list);
     void adoptSelectorVector(Vector<OwnPtr<CSSParserSelector> >& selectorVector);
+    void adoptSelectorArray(CSSSelector* selectors) { ASSERT(!m_selectorArray); m_selectorArray = selectors; }
 
     bool isValid() const { return !!m_selectorArray; }
     const CSSSelector* first() const { return m_selectorArray; }
@@ -63,6 +64,8 @@
 
     String selectorsText() const;
 
+    unsigned selectorCount() const;
+
 private:
     unsigned length() const;
     void deleteSelectors();

Modified: trunk/Source/WebCore/css/CSSStyleRule.cpp (152452 => 152453)


--- trunk/Source/WebCore/css/CSSStyleRule.cpp	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/css/CSSStyleRule.cpp	2013-07-08 15:53:54 UTC (rev 152453)
@@ -27,6 +27,7 @@
 #include "CSSStyleSheet.h"
 #include "Document.h"
 #include "PropertySetCSSStyleDeclaration.h"
+#include "RuleSet.h"
 #include "StylePropertySet.h"
 #include "StyleRule.h"
 #include <wtf/text/StringBuilder.h>
@@ -98,6 +99,10 @@
     if (!selectorList.isValid())
         return;
 
+    // NOTE: The selector list has to fit into RuleData. <http://webkit.org/b/118369>
+    if (selectorList.selectorCount() > RuleData::maximumSelectorCount)
+        return;
+
     CSSStyleSheet::RuleMutationScope mutationScope(this);
 
     m_styleRule->wrapperAdoptSelectorList(selectorList);

Modified: trunk/Source/WebCore/css/RuleSet.h (152452 => 152453)


--- trunk/Source/WebCore/css/RuleSet.h	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/css/RuleSet.h	2013-07-08 15:53:54 UTC (rev 152453)
@@ -55,6 +55,8 @@
 
 class RuleData {
 public:
+    static const unsigned maximumSelectorCount = 8192;
+
     RuleData(StyleRule*, unsigned selectorIndex, unsigned position, AddRuleFlags);
 
     unsigned position() const { return m_position; }

Modified: trunk/Source/WebCore/css/StyleRule.cpp (152452 => 152453)


--- trunk/Source/WebCore/css/StyleRule.cpp	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/css/StyleRule.cpp	2013-07-08 15:53:54 UTC (rev 152453)
@@ -260,6 +260,44 @@
     m_properties = properties;
 }
 
+PassRefPtr<StyleRule> StyleRule::create(int sourceLine, const Vector<const CSSSelector*>& selectors, PassRefPtr<StylePropertySet> properties)
+{
+    CSSSelector* selectorListArray = reinterpret_cast<CSSSelector*>(fastMalloc(sizeof(CSSSelector) * selectors.size()));
+    for (unsigned i = 0; i < selectors.size(); ++i)
+        new (NotNull, &selectorListArray[i]) CSSSelector(*selectors.at(i));
+    selectorListArray[selectors.size() - 1].setLastInSelectorList();
+    RefPtr<StyleRule> rule = StyleRule::create(sourceLine);
+    rule->parserAdoptSelectorArray(selectorListArray);
+    rule->setProperties(properties);
+    return rule.release();
+}
+
+Vector<RefPtr<StyleRule> > StyleRule::splitIntoMultipleRulesWithMaximumSelectorCount(unsigned maximumSelectorCount) const
+{
+    ASSERT(selectorList().selectorCount() > maximumSelectorCount);
+
+    Vector<RefPtr<StyleRule> > rules;
+    Vector<const CSSSelector*> selectorsToCopy;
+
+    unsigned selectorCount = 0;
+
+    for (const CSSSelector* selector = selectorList().first(); selector; selector = CSSSelectorList::next(selector)) {
+        for (const CSSSelector* component = selector; component; component = component->tagHistory())
+            selectorsToCopy.append(component);
+
+        if (++selectorCount == maximumSelectorCount) {
+            rules.append(create(sourceLine(), selectorsToCopy, m_properties));
+            selectorsToCopy.clear();
+            selectorCount = 0;
+        }
+    }
+
+    if (selectorCount)
+        rules.append(create(sourceLine(), selectorsToCopy, m_properties));
+
+    return rules;
+}
+
 StyleRulePage::StyleRulePage()
     : StyleRuleBase(Page)
 {

Modified: trunk/Source/WebCore/css/StyleRule.h (152452 => 152453)


--- trunk/Source/WebCore/css/StyleRule.h	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/css/StyleRule.h	2013-07-08 15:53:54 UTC (rev 152453)
@@ -127,20 +127,31 @@
     
     void parserAdoptSelectorVector(Vector<OwnPtr<CSSParserSelector> >& selectors) { m_selectorList.adoptSelectorVector(selectors); }
     void wrapperAdoptSelectorList(CSSSelectorList& selectors) { m_selectorList.adopt(selectors); }
+    void parserAdoptSelectorArray(CSSSelector* selectors) { m_selectorList.adoptSelectorArray(selectors); }
     void setProperties(PassRefPtr<StylePropertySet>);
 
     PassRefPtr<StyleRule> copy() const { return adoptRef(new StyleRule(*this)); }
 
+    Vector<RefPtr<StyleRule> > splitIntoMultipleRulesWithMaximumSelectorCount(unsigned maxSelectorCount) const;
+
     static unsigned averageSizeInBytes();
 
 private:
     StyleRule(int sourceLine);
     StyleRule(const StyleRule&);
 
+    static PassRefPtr<StyleRule> create(int sourceLine, const Vector<const CSSSelector*>&, PassRefPtr<StylePropertySet>);
+
     RefPtr<StylePropertySet> m_properties;
     CSSSelectorList m_selectorList;
 };
 
+inline const StyleRule* toStyleRule(const StyleRuleBase* rule)
+{
+    ASSERT_WITH_SECURITY_IMPLICATION(!rule || rule->isStyleRule());
+    return static_cast<const StyleRule*>(rule);
+}
+
 class StyleRuleFontFace : public StyleRuleBase {
 public:
     static PassRefPtr<StyleRuleFontFace> create() { return adoptRef(new StyleRuleFontFace); }

Modified: trunk/Source/WebCore/css/StyleSheetContents.cpp (152452 => 152453)


--- trunk/Source/WebCore/css/StyleSheetContents.cpp	2013-07-08 15:37:17 UTC (rev 152452)
+++ trunk/Source/WebCore/css/StyleSheetContents.cpp	2013-07-08 15:53:54 UTC (rev 152453)
@@ -1,6 +1,6 @@
 /*
  * (C) 1999-2003 Lars Knoll ([email protected])
- * Copyright (C) 2004, 2006, 2007, 2012 Apple Inc. All rights reserved.
+ * Copyright (C) 2004, 2006, 2007, 2012, 2013 Apple 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
@@ -28,6 +28,7 @@
 #include "Document.h"
 #include "MediaList.h"
 #include "Node.h"
+#include "RuleSet.h"
 #include "SecurityOrigin.h"
 #include "StylePropertySet.h"
 #include "StyleRule.h"
@@ -140,6 +141,14 @@
         reportMediaQueryWarningIfNeeded(singleOwnerDocument(), static_cast<StyleRuleMedia*>(rule.get())->mediaQueries());
 #endif
 
+    // NOTE: The selector list has to fit into RuleData. <http://webkit.org/b/118369>
+    // If we're adding a rule with a huge number of selectors, split it up into multiple rules
+    if (rule->isStyleRule() && toStyleRule(rule.get())->selectorList().selectorCount() > RuleData::maximumSelectorCount) {
+        Vector<RefPtr<StyleRule> > rules = toStyleRule(rule.get())->splitIntoMultipleRulesWithMaximumSelectorCount(RuleData::maximumSelectorCount);
+        m_childRules.appendVector(rules);
+        return;
+    }
+
     m_childRules.append(rule);
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to