Share stylesheet data structures between documents
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 May 2012 09:56:49 +0000 (09:56 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 7 May 2012 09:56:49 +0000 (09:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=85598

Source/WebCore:

Reviewed by Darin Adler.

We currently make a copy of the data structures when restoring a cached stylesheet. This patch lets us share
the data until someone uses a mutating CSSOM API to modify the sheet.

The patch implements copy-on-write for the internal style sheet data structures. If any mutating CSSOM API is
invoked, we check if the mutation is safe (there is only one client, the sheet is not cached). If not then the
internal structures are copied and any existing CSSOM objects are re-attached to the new style tree. The copied
tree is mutated while the other clients stay attached to the original tree.

Sharing can save significant amount of memory on sites with large stylesheets. For example if you have
multiple articles open on wsj.com this saves ~2.6MB per tab.

Test: http/tests/css/shared-stylesheet-mutation.html
      http/tests/css/shared-stylesheet-mutation-preconstruct.html

* css/CSSFontFaceRule.cpp:
(WebCore::CSSFontFaceRule::reattach):
(WebCore):
* css/CSSFontFaceRule.h:
(CSSFontFaceRule):
* css/CSSMediaRule.cpp:
(WebCore::CSSMediaRule::insertRule):
(WebCore::CSSMediaRule::deleteRule):
(WebCore::CSSMediaRule::reattach):
(WebCore):
* css/CSSMediaRule.h:
(CSSMediaRule):
* css/CSSPageRule.cpp:
(WebCore::CSSPageRule::setSelectorText):
(WebCore::CSSPageRule::reattach):
(WebCore):
* css/CSSPageRule.h:
(CSSPageRule):
* css/CSSRule.cpp:
(WebCore::CSSRule::reattach):

    After the internal stylerule tree has been copied, the existing wrappers are re-attached using recursive reattach() function.

* css/CSSRule.h:
(WebCore):
(CSSRule):
* css/CSSStyleRule.cpp:
(WebCore::CSSStyleRule::setSelectorText):
(WebCore::CSSStyleRule::reattach):
(WebCore):
* css/CSSStyleRule.h:
(CSSStyleRule):
* css/CSSStyleSheet.cpp:
(WebCore::StyleSheetInternal::StyleSheetInternal):
(WebCore::StyleSheetInternal::isCacheable):
(WebCore::StyleSheetInternal::ruleAt):

    Add ruleAt(), use it for both wrapper creation and reattaching.  Remove createChildRuleCSSOMWrapper .

(WebCore):
(WebCore::StyleSheetInternal::wrapperInsertRule):
(WebCore::StyleSheetInternal::wrapperDeleteRule):

    Invalidation moves to the calling wrapper.

(WebCore::StyleSheetInternal::addedToMemoryCache):
(WebCore::StyleSheetInternal::removedFromMemoryCache):
(WebCore::CSSStyleSheet::willMutateRules):

    This is called whenever StyleSheetInternal is going to be mutated. It will do copy-on-write if needed.

    Usually invoked by CSSStyleSheet::RuleMutation RAII type.

(WebCore::CSSStyleSheet::didMutateRules):

    This is called after the mutation is complete and will trigger the style recalc in the document.

(WebCore::CSSStyleSheet::didMutate):

    This is called directly after mutations that don't change StyleSheetInternal so don't require copy-on-write.

(WebCore::CSSStyleSheet::reattachChildRuleCSSOMWrappers):
(WebCore::CSSStyleSheet::setDisabled):
(WebCore::CSSStyleSheet::insertRule):
(WebCore::CSSStyleSheet::deleteRule):
* css/CSSStyleSheet.h:
(StyleSheetInternal):
(WebCore::StyleSheetInternal::hasOneClient):
(WebCore::StyleSheetInternal::isMutable):
(WebCore::StyleSheetInternal::setMutable):

    Track mutability. Mutation is allowed only after willMutate call.

(WebCore::StyleSheetInternal::isInMemoryCache):

    Track if the object is in memory cache.

(WebCore::CSSStyleSheet::clearOwnerRule):
(CSSStyleSheet):
* css/MediaList.cpp:
(WebCore::MediaList::setMediaText):
(WebCore::MediaList::deleteMedium):
(WebCore::MediaList::appendMedium):
(WebCore::MediaList::didMutate):
(WebCore):
(WebCore::MediaList::reattach):
* css/MediaList.h:
(MediaList):
* css/PropertySetCSSStyleDeclaration.cpp:
(WebCore::PropertySetCSSStyleDeclaration::setCssText):
(WebCore::PropertySetCSSStyleDeclaration::setProperty):
(WebCore::PropertySetCSSStyleDeclaration::removeProperty):
(WebCore::PropertySetCSSStyleDeclaration::setPropertyInternal):
(WebCore):
(WebCore::StyleRuleCSSStyleDeclaration::willMutate):
(WebCore::StyleRuleCSSStyleDeclaration::didMutate):
(WebCore::StyleRuleCSSStyleDeclaration::reattach):
(WebCore::InlineCSSStyleDeclaration::didMutate):
* css/PropertySetCSSStyleDeclaration.h:
(WebCore::PropertySetCSSStyleDeclaration::willMutate):
(WebCore::PropertySetCSSStyleDeclaration::didMutate):
(StyleRuleCSSStyleDeclaration):
* css/WebKitCSSKeyframesRule.cpp:
(WebCore::WebKitCSSKeyframesRule::setName):
(WebCore::WebKitCSSKeyframesRule::insertRule):
(WebCore::WebKitCSSKeyframesRule::deleteRule):
(WebCore::WebKitCSSKeyframesRule::reattach):
(WebCore):
* css/WebKitCSSKeyframesRule.h:
(WebKitCSSKeyframesRule):
* css/WebKitCSSRegionRule.cpp:
(WebCore::WebKitCSSRegionRule::reattach):
* css/WebKitCSSRegionRule.h:
(WebKitCSSRegionRule):
* inspector/InspectorStyleSheet.cpp:
(WebCore::InspectorStyleSheet::reparseStyleSheet):
* loader/cache/CachedCSSStyleSheet.cpp:
(WebCore::CachedCSSStyleSheet::~CachedCSSStyleSheet):
(WebCore::CachedCSSStyleSheet::destroyDecodedData):
(WebCore::CachedCSSStyleSheet::restoreParsedStyleSheet):

    Don't copy when restoring. It is no longer necessary.
    Set the cache bit on the stylesheet.

(WebCore::CachedCSSStyleSheet::saveParsedStyleSheet):

LayoutTests:

Reviewed by Darin Adler.

Test that mutations of a shared stylesheet work as expected.

This is an http test due to cross-document security restrictions with file urls
(they can be overriden in DRT but I'd like this to work in browser too).

* http/tests/css/resources/shared.css: Added.
* http/tests/css/resources/shared-stylesheet-mutation.js: Added.
* http/tests/css/shared-stylesheet-mutation-expected.txt: Added.
* http/tests/css/shared-stylesheet-mutation-preconstruct-expected.txt: Added.
* http/tests/css/shared-stylesheet-mutation-preconstruct.html: Added.
* http/tests/css/shared-stylesheet-mutation.html: Added.

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

35 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/css/resources/shared-stylesheet-mutation.js [new file with mode: 0644]
LayoutTests/http/tests/css/resources/shared.css [new file with mode: 0644]
LayoutTests/http/tests/css/shared-stylesheet-mutation-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/css/shared-stylesheet-mutation-preconstruct-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/css/shared-stylesheet-mutation-preconstruct.html [new file with mode: 0644]
LayoutTests/http/tests/css/shared-stylesheet-mutation.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/objc/DOMCSS.mm
Source/WebCore/css/CSSFontFaceRule.cpp
Source/WebCore/css/CSSFontFaceRule.h
Source/WebCore/css/CSSImportRule.cpp
Source/WebCore/css/CSSMediaRule.cpp
Source/WebCore/css/CSSMediaRule.h
Source/WebCore/css/CSSPageRule.cpp
Source/WebCore/css/CSSPageRule.h
Source/WebCore/css/CSSRule.cpp
Source/WebCore/css/CSSRule.h
Source/WebCore/css/CSSStyleRule.cpp
Source/WebCore/css/CSSStyleRule.h
Source/WebCore/css/CSSStyleSheet.cpp
Source/WebCore/css/CSSStyleSheet.h
Source/WebCore/css/MediaList.cpp
Source/WebCore/css/MediaList.h
Source/WebCore/css/PropertySetCSSStyleDeclaration.cpp
Source/WebCore/css/PropertySetCSSStyleDeclaration.h
Source/WebCore/css/StylePropertySet.cpp
Source/WebCore/css/StyleResolver.cpp
Source/WebCore/css/StyleResolver.h
Source/WebCore/css/WebKitCSSKeyframesRule.cpp
Source/WebCore/css/WebKitCSSKeyframesRule.h
Source/WebCore/css/WebKitCSSRegionRule.cpp
Source/WebCore/css/WebKitCSSRegionRule.h
Source/WebCore/inspector/InspectorStyleSheet.cpp
Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp

index 791fc31..3719b86 100644 (file)
@@ -1,3 +1,22 @@
+2012-05-07  Antti Koivisto  <antti@apple.com>
+
+        Share stylesheet data structures between documents
+        https://bugs.webkit.org/show_bug.cgi?id=85598
+
+        Reviewed by Darin Adler.
+        
+        Test that mutations of a shared stylesheet work as expected. 
+        
+        This is an http test due to cross-document security restrictions with file urls
+        (they can be overriden in DRT but I'd like this to work in browser too).
+
+        * http/tests/css/resources/shared.css: Added.
+        * http/tests/css/resources/shared-stylesheet-mutation.js: Added.
+        * http/tests/css/shared-stylesheet-mutation-expected.txt: Added.
+        * http/tests/css/shared-stylesheet-mutation-preconstruct-expected.txt: Added.
+        * http/tests/css/shared-stylesheet-mutation-preconstruct.html: Added.
+        * http/tests/css/shared-stylesheet-mutation.html: Added.
+
 2012-05-07  Andrey Kosyakov  <caseq@chromium.org>
 
         Unreviewed gardening, fast/text/international/thai-{baht-space,line-breaks}.html now fail in release, too.
diff --git a/LayoutTests/http/tests/css/resources/shared-stylesheet-mutation.js b/LayoutTests/http/tests/css/resources/shared-stylesheet-mutation.js
new file mode 100644 (file)
index 0000000..e72b177
--- /dev/null
@@ -0,0 +1,140 @@
+var testCount = 17;
+var testDocuments = [];
+var testSheets = [];
+var expectedResults = [];
+
+function buildTestFrame(content)
+{
+    var iframe = document.createElement("iframe");
+    iframe.setAttribute("width", "100");
+    iframe.setAttribute("height", "50");
+    document.body.appendChild(iframe);
+    var iframeDocument = iframe.contentDocument;
+    iframeDocument.body.innerHTML = content;
+    return iframeDocument;
+}
+
+function buildTestFrames(content)
+{
+    for (var i = 0; i < testCount; ++i)
+        testDocuments.push(buildTestFrame(content));
+}
+
+function printRules(ruleList)
+{
+    for (var i = 0; i < ruleList.length; ++i)
+        debug(ruleList[i].cssText);
+}
+
+function printTestResults(index)
+{
+    testElement = testDocuments[index].getElementById('testdiv');
+    
+    debug("Test " + index);
+    debug("--------------------------------------");
+    shouldBe("getComputedStyle(testElement, null).getPropertyValue('background-color')", expectedResults[index]);
+    debug("");
+    printRules(testSheets[index].cssRules);
+    debug("");
+}
+
+function finishedTests()
+{
+    for (var i = 0; i < testSheets.length; ++i)
+        printTestResults(i);
+    
+    finishJSTest();
+}
+
+function ensureCSSOM(ruleList)
+{
+    // Touch all CSSOM objects to force wrapper creation.
+    for (var i = 0; i < ruleList.length; ++i) {
+        ruleList[i].style;
+        ruleList[i].media;
+        ruleList[i].selectorText;
+        ruleList[i].name;
+        ruleList[i].keyText;
+        ruleList[i].cssText;
+        if (ruleList[i].cssRules)
+            ensureCSSOM(ruleList[i].cssRules);
+    }
+}
+
+function mutationTest(index, testString, expectColor)
+{
+    var sheet = testSheets[index];
+    eval(testString);
+    expectedResults[index] = expectColor == "red" ? "'rgb(255, 0, 0)'" : "'rgb(0, 128, 0)'";
+}
+
+function executeTests(createCSSOMObjectBeforeTest)
+{
+    if (createCSSOMObjectBeforeTest) {
+        for (var i = 0; i < testSheets.length; ++i)
+            ensureCSSOM(testSheets[i].cssRules);
+    }
+
+    mutationTest(0, '', 'red');
+
+    mutationTest(1, 'sheet.insertRule("#testdiv { background-color: green; }", 3)', 'green');
+    mutationTest(2, 'sheet.deleteRule(2)');
+    mutationTest(3, 'sheet.cssRules[2].insertRule("#testdiv { background-color: green; }", 1)', 'green');
+    mutationTest(4, 'sheet.cssRules[2].deleteRule(0)', 'green');
+    mutationTest(5, 'sheet.cssRules[2].cssRules[0].style.setProperty("background-color", "green", "")', 'green');
+    mutationTest(6, 'sheet.cssRules[2].cssRules[0].style.removeProperty("background-color")', 'green');
+    mutationTest(7, 'sheet.cssRules[2].cssRules[0].style.cssText = "background-color: green"', 'green');
+    mutationTest(8, 'sheet.cssRules[2].cssRules[0].selectorText = "#dontmatch"', 'green');
+    mutationTest(9, 'sheet.cssRules[2].media.mediaText = "print"', 'green');
+    
+    var testString = '\
+        sheet.cssRules[2].media.appendMedium("print");\
+        sheet.cssRules[2].media.deleteMedium("all");\
+    ';
+    mutationTest(10, testString, 'green');
+    
+    var testString = '\
+        sheet.deleteRule(3);\
+        sheet.insertRule("#testdiv { background-color: green; }", 1);\
+        sheet.deleteRule(2);\
+        sheet.deleteRule(0);\
+        sheet.deleteRule(3);\
+        sheet.deleteRule(1);\
+        sheet.deleteRule(1);\
+    ';
+    mutationTest(11, testString, 'green');
+
+    var importRule = '@import "data:text/css;charset=utf-8,%23testdiv%7Bbackground-color%3Agreen%20!important%7D";';
+    mutationTest(12, "sheet.insertRule('"+importRule+"', 1)", 'green');
+
+    mutationTest(13, 'sheet.cssRules[3].selectorText = "foo"', 'red');
+    mutationTest(14, 'sheet.cssRules[4].insertRule("40% { left: 40px; }")', 'red');
+    mutationTest(15, 'sheet.cssRules[4].deleteRule("100%")', 'red');
+    mutationTest(16, 'sheet.cssRules[5].style.setProperty("font-family", "Bar", "")', 'red');
+
+    setTimeout(finishedTests, 50);
+}
+
+function runTestsAfterLoadComplete(createCSSOMObjectBeforeTest)
+{
+    var complete = true;
+    for (var i = 0; i < testDocuments.length; ++i) {
+        var sheet = testDocuments[i].styleSheets.length == 1 ? testDocuments[i].styleSheets[0] : null;
+        if (sheet)
+            testSheets[i] = sheet;
+        else
+            complete = false;
+    }
+    if (!complete) {
+        setTimeout(runTestsAfterLoadComplete, 10);
+        return;
+    }
+    executeTests(createCSSOMObjectBeforeTest);
+}
+
+function runTests(createCSSOMObjectBeforeTest)
+{
+    buildTestFrames("<link rel=stylesheet href=resources/shared.css><div id=testdiv>Test</div>");
+
+    runTestsAfterLoadComplete(createCSSOMObjectBeforeTest);
+}
diff --git a/LayoutTests/http/tests/css/resources/shared.css b/LayoutTests/http/tests/css/resources/shared.css
new file mode 100644 (file)
index 0000000..cf67f73
--- /dev/null
@@ -0,0 +1,15 @@
+@charset 'utf-8';
+#testdiv { background-color: green; }
+@media all {
+    #testdiv { background-color: red; }
+}
+@page :right {
+  margin-left: 3cm;
+}
+@-webkit-keyframes bounce {
+    from { left: 0px; }
+    to { left: 200px; }
+}
+@font-face {
+  font-family: Foo;
+}
diff --git a/LayoutTests/http/tests/css/shared-stylesheet-mutation-expected.txt b/LayoutTests/http/tests/css/shared-stylesheet-mutation-expected.txt
new file mode 100644 (file)
index 0000000..ed15c05
--- /dev/null
@@ -0,0 +1,270 @@
+The test loads the same stylesheet to different frames and then mutates them. The mutations should not affect other frames.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Test 0
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 1
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+#testdiv { background-color: green; }
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 2
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 3
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+  #testdiv { background-color: green; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 4
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 5
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: green; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 6
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 7
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: green; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 8
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #dontmatch { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 9
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media print { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 10
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media print { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 11
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+#testdiv { background-color: green; }
+
+Test 12
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+@import url("data:text/css;charset=utf-8,%23testdiv%7Bbackground-color%3Agreen%20!important%7D") ;
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 13
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page foo { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 14
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+  40% { left: 40px; }
+}
+@font-face { font-family: Foo; }
+
+Test 15
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+}
+@font-face { font-family: Foo; }
+
+Test 16
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Bar; }
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/css/shared-stylesheet-mutation-preconstruct-expected.txt b/LayoutTests/http/tests/css/shared-stylesheet-mutation-preconstruct-expected.txt
new file mode 100644 (file)
index 0000000..ed15c05
--- /dev/null
@@ -0,0 +1,270 @@
+The test loads the same stylesheet to different frames and then mutates them. The mutations should not affect other frames.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Test 0
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 1
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+#testdiv { background-color: green; }
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 2
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 3
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+  #testdiv { background-color: green; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 4
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 5
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: green; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 6
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 7
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: green; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 8
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #dontmatch { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 9
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media print { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 10
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media print { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 11
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+#testdiv { background-color: green; }
+
+Test 12
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(0, 128, 0)'
+
+@charset "utf-8";
+@import url("data:text/css;charset=utf-8,%23testdiv%7Bbackground-color%3Agreen%20!important%7D") ;
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 13
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page foo { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Foo; }
+
+Test 14
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+  40% { left: 40px; }
+}
+@font-face { font-family: Foo; }
+
+Test 15
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+}
+@font-face { font-family: Foo; }
+
+Test 16
+--------------------------------------
+PASS getComputedStyle(testElement, null).getPropertyValue('background-color') is 'rgb(255, 0, 0)'
+
+@charset "utf-8";
+#testdiv { background-color: green; }
+@media all { 
+  #testdiv { background-color: red; }
+}
+@page :right { margin-left: 3cm; }
+@-webkit-keyframes bounce { 
+  0% { left: 0px; }
+  100% { left: 200px; }
+}
+@font-face { font-family: Bar; }
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/css/shared-stylesheet-mutation-preconstruct.html b/LayoutTests/http/tests/css/shared-stylesheet-mutation-preconstruct.html
new file mode 100644 (file)
index 0000000..484f020
--- /dev/null
@@ -0,0 +1,11 @@
+<script>jsTestIsAsync = true;</script>
+<script src="/js-test-resources/js-test-pre.js"></script>
+<script src="resources/shared-stylesheet-mutation.js"></script>
+<body>
+<script>
+description("The test loads the same stylesheet to different frames and then mutates them. The mutations should not affect other frames.");
+
+var createCSSOMObjectBeforeTest = true;
+runTests(createCSSOMObjectBeforeTest);
+</script>
+<script src="/js-test-resources/js-test-post.js"></script>
diff --git a/LayoutTests/http/tests/css/shared-stylesheet-mutation.html b/LayoutTests/http/tests/css/shared-stylesheet-mutation.html
new file mode 100644 (file)
index 0000000..b31c9fe
--- /dev/null
@@ -0,0 +1,11 @@
+<script>jsTestIsAsync = true;</script>
+<script src="/js-test-resources/js-test-pre.js"></script>
+<script src="resources/shared-stylesheet-mutation.js"></script>
+<body>
+<script>
+description("The test loads the same stylesheet to different frames and then mutates them. The mutations should not affect other frames.");
+
+var createCSSOMObjectBeforeTest = false;
+runTests(createCSSOMObjectBeforeTest);
+</script>
+<script src="/js-test-resources/js-test-post.js"></script>
index b2f1989..4269c86 100644 (file)
@@ -1,3 +1,151 @@
+
+2012-05-07  Antti Koivisto  <antti@apple.com>
+
+        Share stylesheet data structures between documents
+        https://bugs.webkit.org/show_bug.cgi?id=85598
+
+        Reviewed by Darin Adler.
+
+        We currently make a copy of the data structures when restoring a cached stylesheet. This patch lets us share
+        the data until someone uses a mutating CSSOM API to modify the sheet.
+        
+        The patch implements copy-on-write for the internal style sheet data structures. If any mutating CSSOM API is
+        invoked, we check if the mutation is safe (there is only one client, the sheet is not cached). If not then the
+        internal structures are copied and any existing CSSOM objects are re-attached to the new style tree. The copied
+        tree is mutated while the other clients stay attached to the original tree.
+        
+        Sharing can save significant amount of memory on sites with large stylesheets. For example if you have
+        multiple articles open on wsj.com this saves ~2.6MB per tab.
+        
+        Test: http/tests/css/shared-stylesheet-mutation.html
+              http/tests/css/shared-stylesheet-mutation-preconstruct.html
+
+        * css/CSSFontFaceRule.cpp:
+        (WebCore::CSSFontFaceRule::reattach):
+        (WebCore):
+        * css/CSSFontFaceRule.h:
+        (CSSFontFaceRule):
+        * css/CSSMediaRule.cpp:
+        (WebCore::CSSMediaRule::insertRule):
+        (WebCore::CSSMediaRule::deleteRule):
+        (WebCore::CSSMediaRule::reattach):
+        (WebCore):
+        * css/CSSMediaRule.h:
+        (CSSMediaRule):
+        * css/CSSPageRule.cpp:
+        (WebCore::CSSPageRule::setSelectorText):
+        (WebCore::CSSPageRule::reattach):
+        (WebCore):
+        * css/CSSPageRule.h:
+        (CSSPageRule):
+        * css/CSSRule.cpp:
+        (WebCore::CSSRule::reattach):
+        
+            After the internal stylerule tree has been copied, the existing wrappers are re-attached using recursive reattach() function.
+
+        * css/CSSRule.h:
+        (WebCore):
+        (CSSRule):
+        * css/CSSStyleRule.cpp:
+        (WebCore::CSSStyleRule::setSelectorText):
+        (WebCore::CSSStyleRule::reattach):
+        (WebCore):
+        * css/CSSStyleRule.h:
+        (CSSStyleRule):
+        * css/CSSStyleSheet.cpp:
+        (WebCore::StyleSheetInternal::StyleSheetInternal):
+        (WebCore::StyleSheetInternal::isCacheable):
+        (WebCore::StyleSheetInternal::ruleAt):
+        
+            Add ruleAt(), use it for both wrapper creation and reattaching.  Remove createChildRuleCSSOMWrapper .
+
+        (WebCore):
+        (WebCore::StyleSheetInternal::wrapperInsertRule):
+        (WebCore::StyleSheetInternal::wrapperDeleteRule):
+        
+            Invalidation moves to the calling wrapper.
+
+        (WebCore::StyleSheetInternal::addedToMemoryCache):
+        (WebCore::StyleSheetInternal::removedFromMemoryCache):
+        (WebCore::CSSStyleSheet::willMutateRules):
+        
+            This is called whenever StyleSheetInternal is going to be mutated. It will do copy-on-write if needed.
+            
+            Usually invoked by CSSStyleSheet::RuleMutation RAII type.
+
+        (WebCore::CSSStyleSheet::didMutateRules):
+        
+            This is called after the mutation is complete and will trigger the style recalc in the document.
+
+        (WebCore::CSSStyleSheet::didMutate):
+        
+            This is called directly after mutations that don't change StyleSheetInternal so don't require copy-on-write.
+
+        (WebCore::CSSStyleSheet::reattachChildRuleCSSOMWrappers):
+        (WebCore::CSSStyleSheet::setDisabled):
+        (WebCore::CSSStyleSheet::insertRule):
+        (WebCore::CSSStyleSheet::deleteRule):
+        * css/CSSStyleSheet.h:
+        (StyleSheetInternal):
+        (WebCore::StyleSheetInternal::hasOneClient):
+        (WebCore::StyleSheetInternal::isMutable):
+        (WebCore::StyleSheetInternal::setMutable):
+        
+            Track mutability. Mutation is allowed only after willMutate call.
+
+        (WebCore::StyleSheetInternal::isInMemoryCache):
+        
+            Track if the object is in memory cache.
+
+        (WebCore::CSSStyleSheet::clearOwnerRule):
+        (CSSStyleSheet):
+        * css/MediaList.cpp:
+        (WebCore::MediaList::setMediaText):
+        (WebCore::MediaList::deleteMedium):
+        (WebCore::MediaList::appendMedium):
+        (WebCore::MediaList::didMutate):
+        (WebCore):
+        (WebCore::MediaList::reattach):
+        * css/MediaList.h:
+        (MediaList):
+        * css/PropertySetCSSStyleDeclaration.cpp:
+        (WebCore::PropertySetCSSStyleDeclaration::setCssText):
+        (WebCore::PropertySetCSSStyleDeclaration::setProperty):
+        (WebCore::PropertySetCSSStyleDeclaration::removeProperty):
+        (WebCore::PropertySetCSSStyleDeclaration::setPropertyInternal):
+        (WebCore):
+        (WebCore::StyleRuleCSSStyleDeclaration::willMutate):
+        (WebCore::StyleRuleCSSStyleDeclaration::didMutate):
+        (WebCore::StyleRuleCSSStyleDeclaration::reattach):
+        (WebCore::InlineCSSStyleDeclaration::didMutate):
+        * css/PropertySetCSSStyleDeclaration.h:
+        (WebCore::PropertySetCSSStyleDeclaration::willMutate):
+        (WebCore::PropertySetCSSStyleDeclaration::didMutate):
+        (StyleRuleCSSStyleDeclaration):
+        * css/WebKitCSSKeyframesRule.cpp:
+        (WebCore::WebKitCSSKeyframesRule::setName):
+        (WebCore::WebKitCSSKeyframesRule::insertRule):
+        (WebCore::WebKitCSSKeyframesRule::deleteRule):
+        (WebCore::WebKitCSSKeyframesRule::reattach):
+        (WebCore):
+        * css/WebKitCSSKeyframesRule.h:
+        (WebKitCSSKeyframesRule):
+        * css/WebKitCSSRegionRule.cpp:
+        (WebCore::WebKitCSSRegionRule::reattach):
+        * css/WebKitCSSRegionRule.h:
+        (WebKitCSSRegionRule):
+        * inspector/InspectorStyleSheet.cpp:
+        (WebCore::InspectorStyleSheet::reparseStyleSheet):
+        * loader/cache/CachedCSSStyleSheet.cpp:
+        (WebCore::CachedCSSStyleSheet::~CachedCSSStyleSheet):
+        (WebCore::CachedCSSStyleSheet::destroyDecodedData):
+        (WebCore::CachedCSSStyleSheet::restoreParsedStyleSheet):
+        
+            Don't copy when restoring. It is no longer necessary.
+            Set the cache bit on the stylesheet.
+
+        (WebCore::CachedCSSStyleSheet::saveParsedStyleSheet):
+
 2012-05-06  Kinuko Yasuda  <kinuko@chromium.org>
 
         Cleanup: Change boolean synchronous flag argument into enum in FileSystem API code
index 660c2a0..7af5645 100644 (file)
@@ -28,6 +28,7 @@
 #import "config.h"
 
 #import "CSSRule.h"
+#import "CSSStyleSheet.h"
 #import "CSSValue.h"
 #import "DOMCSSCharsetRule.h"
 #import "DOMCSSFontFaceRule.h"
index 7b611bb..8bb94db 100644 (file)
@@ -55,4 +55,12 @@ String CSSFontFaceRule::cssText() const
     return result;
 }
 
+void CSSFontFaceRule::reattach(StyleRuleFontFace* rule)
+{
+    ASSERT(rule);
+    m_fontFaceRule = rule;
+    if (m_propertiesCSSOMWrapper)
+        m_propertiesCSSOMWrapper->reattach(m_fontFaceRule->properties());
+}
+
 } // namespace WebCore
index 222fc31..6414c87 100644 (file)
@@ -43,6 +43,8 @@ public:
 
     String cssText() const;
 
+    void reattach(StyleRuleFontFace*);
+
 private:
     CSSFontFaceRule(StyleRuleFontFace*, CSSStyleSheet* parent);
 
index f9d1467..d150fb9 100644 (file)
@@ -22,6 +22,7 @@
 #include "config.h"
 #include "CSSImportRule.h"
 
+#include "CSSStyleSheet.h"
 #include "CachedCSSStyleSheet.h"
 #include "CachedResourceLoader.h"
 #include "Document.h"
index a6ee351..e099042 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "CSSParser.h"
 #include "CSSRuleList.h"
+#include "CSSStyleSheet.h"
 #include "ExceptionCode.h"
 #include "StyleRule.h"
 #include <wtf/text/StringBuilder.h>
@@ -80,13 +81,11 @@ unsigned CSSMediaRule::insertRule(const String& ruleString, unsigned index, Exce
         ec = HIERARCHY_REQUEST_ERR;
         return 0;
     }
+    CSSStyleSheet::RuleMutationScope mutationScope(this);
+
     m_mediaRule->wrapperInsertRule(index, newRule);
 
     m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
-
-    if (CSSStyleSheet* styleSheet = parentStyleSheet())
-        styleSheet->styleSheetChanged();
-
     return index;
 }
 
@@ -100,14 +99,14 @@ void CSSMediaRule::deleteRule(unsigned index, ExceptionCode& ec)
         ec = INDEX_SIZE_ERR;
         return;
     }
+
+    CSSStyleSheet::RuleMutationScope mutationScope(this);
+
     m_mediaRule->wrapperRemoveRule(index);
 
     if (m_childRuleCSSOMWrappers[index])
         m_childRuleCSSOMWrappers[index]->setParentRule(0);
     m_childRuleCSSOMWrappers.remove(index);
-
-    if (CSSStyleSheet* styleSheet = parentStyleSheet())
-        styleSheet->styleSheetChanged();
 }
 
 String CSSMediaRule::cssText() const
@@ -163,4 +162,16 @@ CSSRuleList* CSSMediaRule::cssRules() const
     return m_ruleListCSSOMWrapper.get();
 }
 
+void CSSMediaRule::reattach(StyleRuleMedia* rule)
+{
+    ASSERT(rule);
+    m_mediaRule = rule;
+    if (m_mediaCSSOMWrapper)
+        m_mediaCSSOMWrapper->reattach(m_mediaRule->mediaQueries());
+    for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
+        if (m_childRuleCSSOMWrappers[i])
+            m_childRuleCSSOMWrappers[i]->reattach(m_mediaRule->childRules()[i].get());
+    }
+}
+
 } // namespace WebCore
index 1aa5442..fda23ca 100644 (file)
@@ -50,6 +50,8 @@ public:
     unsigned length() const;
     CSSRule* item(unsigned index) const;
 
+    void reattach(StyleRuleMedia*);
+
 private:
     CSSMediaRule(StyleRuleMedia*, CSSStyleSheet*);
     
index 88a0899..70bbab7 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "CSSParser.h"
 #include "CSSSelector.h"
+#include "CSSStyleSheet.h"
 #include "Document.h"
 #include "PropertySetCSSStyleDeclaration.h"
 #include "StylePropertySet.h"
@@ -65,24 +66,16 @@ String CSSPageRule::selectorText() const
 
 void CSSPageRule::setSelectorText(const String& selectorText)
 {
-    Document* doc = 0;
-    if (CSSStyleSheet* styleSheet = parentStyleSheet())
-        doc = styleSheet->ownerDocument();
-    if (!doc)
-        return;
-    
     CSSParser parser(parserContext());
     CSSSelectorList selectorList;
     parser.parseSelector(selectorText, selectorList);
     if (!selectorList.first())
         return;
-    
+
+    CSSStyleSheet::RuleMutationScope mutationScope(this);
+
     String oldSelectorText = this->selectorText();
     m_pageRule->wrapperAdoptSelectorList(selectorList);
-    
-    if (this->selectorText() == oldSelectorText)
-        return;
-    doc->styleResolverChanged(DeferRecalcStyle);
 }
 
 String CSSPageRule::cssText() const
@@ -94,4 +87,12 @@ String CSSPageRule::cssText() const
     return result;
 }
 
+void CSSPageRule::reattach(StyleRulePage* rule)
+{
+    ASSERT(rule);
+    m_pageRule = rule;
+    if (m_propertiesCSSOMWrapper)
+        m_propertiesCSSOMWrapper->reattach(m_pageRule->properties());
+}
+
 } // namespace WebCore
index 69a578d..163900c 100644 (file)
@@ -45,7 +45,9 @@ public:
     void setSelectorText(const String&);
 
     String cssText() const;
-    
+
+    void reattach(StyleRulePage*);
+
 private:
     CSSPageRule(StyleRulePage*, CSSStyleSheet*);
     
index 13ad50a..09643ea 100644 (file)
 #include "CSSMediaRule.h"
 #include "CSSPageRule.h"
 #include "CSSStyleRule.h"
+#include "CSSStyleSheet.h"
 #include "CSSUnknownRule.h"
 #include "WebKitCSSKeyframeRule.h"
 #include "WebKitCSSKeyframesRule.h"
 #include "WebKitCSSRegionRule.h"
 #include "NotImplemented.h"
+#include "StyleRule.h"
 
 namespace WebCore {
 
@@ -113,4 +115,48 @@ void CSSRule::destroy()
     ASSERT_NOT_REACHED();
 }
 
+void CSSRule::reattach(StyleRuleBase* rule)
+{
+    switch (type()) {
+    case UNKNOWN_RULE:
+        return;
+    case STYLE_RULE:
+        static_cast<CSSStyleRule*>(this)->reattach(static_cast<StyleRule*>(rule));
+        return;
+    case PAGE_RULE:
+        static_cast<CSSPageRule*>(this)->reattach(static_cast<StyleRulePage*>(rule));
+        return;
+    case CHARSET_RULE:
+        ASSERT(!rule);
+        return;
+    case IMPORT_RULE:
+        // FIXME: Implement when enabling caching for stylesheets with import rules.
+        ASSERT_NOT_REACHED();
+        return;
+    case MEDIA_RULE:
+        static_cast<CSSMediaRule*>(this)->reattach(static_cast<StyleRuleMedia*>(rule));
+        return;
+    case FONT_FACE_RULE:
+        static_cast<CSSFontFaceRule*>(this)->reattach(static_cast<StyleRuleFontFace*>(rule));
+        return;
+    case WEBKIT_KEYFRAMES_RULE:
+        static_cast<WebKitCSSKeyframesRule*>(this)->reattach(static_cast<StyleRuleKeyframes*>(rule));
+        return;
+    case WEBKIT_KEYFRAME_RULE:
+        // No need to reattach, the underlying data is shareable on mutation.
+        ASSERT_NOT_REACHED();
+        return;
+    case WEBKIT_REGION_RULE:
+        static_cast<WebKitCSSRegionRule*>(this)->reattach(static_cast<StyleRuleRegion*>(rule));
+        return;
+    }
+    ASSERT_NOT_REACHED();
+}
+
+const CSSParserContext& CSSRule::parserContext() const
+{
+    CSSStyleSheet* styleSheet = parentStyleSheet();
+    return styleSheet ? styleSheet->internal()->parserContext() : strictCSSParserContext();
+}
+
 } // namespace WebCore
index 5a50645..5ec2f7a 100644 (file)
 #ifndef CSSRule_h
 #define CSSRule_h
 
-#include "CSSStyleSheet.h"
 #include "KURLHash.h"
 #include <wtf/ListHashSet.h>
+#include <wtf/RefCounted.h>
 
 namespace WebCore {
 
+class CSSStyleSheet;
+class StyleRuleBase;
+struct CSSParserContext;
 typedef int ExceptionCode;
 
 class CSSRule : public RefCounted<CSSRule> {
@@ -93,6 +96,8 @@ public:
     String cssText() const;
     void setCssText(const String&, ExceptionCode&);
 
+    void reattach(StyleRuleBase*);
+
 protected:
     CSSRule(CSSStyleSheet* parent, Type type)
         : m_hasCachedSelectorText(false)
@@ -110,11 +115,7 @@ protected:
     bool hasCachedSelectorText() const { return m_hasCachedSelectorText; }
     void setHasCachedSelectorText(bool hasCachedSelectorText) const { m_hasCachedSelectorText = hasCachedSelectorText; }
 
-    const CSSParserContext& parserContext() const 
-    {
-        CSSStyleSheet* styleSheet = parentStyleSheet();
-        return styleSheet ? styleSheet->internal()->parserContext() : strictCSSParserContext();
-    }
+    const CSSParserContext& parserContext() const;
 
 private:
     mutable unsigned m_hasCachedSelectorText : 1;
index 7357fcf..5a78026 100644 (file)
@@ -91,18 +91,14 @@ String CSSStyleRule::selectorText() const
 
 void CSSStyleRule::setSelectorText(const String& selectorText)
 {
-    Document* doc = 0;
-    if (CSSStyleSheet* styleSheet = parentStyleSheet())
-        doc = styleSheet->ownerDocument();
-    if (!doc)
-        return;
-
     CSSParser p(parserContext());
     CSSSelectorList selectorList;
     p.parseSelector(selectorText, selectorList);
     if (!selectorList.first())
         return;
 
+    CSSStyleSheet::RuleMutationScope mutationScope(this);
+
     String oldSelectorText = this->selectorText();
     m_styleRule->wrapperAdoptSelectorList(selectorList);
 
@@ -110,8 +106,6 @@ void CSSStyleRule::setSelectorText(const String& selectorText)
         ASSERT(selectorTextCache().contains(this));
         selectorTextCache().set(this, generateSelectorText());
     }
-
-    doc->styleResolverChanged(DeferRecalcStyle);
 }
 
 String CSSStyleRule::cssText() const
@@ -125,4 +119,11 @@ String CSSStyleRule::cssText() const
     return result;
 }
 
+void CSSStyleRule::reattach(StyleRule* rule)
+{
+    m_styleRule = rule;
+    if (m_propertiesCSSOMWrapper)
+        m_propertiesCSSOMWrapper->reattach(m_styleRule->properties());
+}
+
 } // namespace WebCore
index ae03b58..d76a802 100644 (file)
@@ -48,6 +48,8 @@ public:
     // FIXME: Not CSSOM. Remove.
     StyleRule* styleRule() const { return m_styleRule.get(); }
 
+    void reattach(StyleRule*);
+
 private:
     CSSStyleRule(StyleRule*, CSSStyleSheet*);
 
index 3852008..329fc8d 100644 (file)
@@ -99,7 +99,8 @@ StyleSheetInternal::StyleSheetInternal(StyleRuleImport* ownerRule, const String&
     , m_hasSyntacticallyValidCSSHeader(true)
     , m_didLoadErrorOccur(false)
     , m_usesRemUnits(false)
-    , m_hasMutated(false)
+    , m_isMutable(false)
+    , m_isInMemoryCache(false)
     , m_parserContext(context)
 {
 }
@@ -118,7 +119,8 @@ StyleSheetInternal::StyleSheetInternal(const StyleSheetInternal& o)
     , m_hasSyntacticallyValidCSSHeader(o.m_hasSyntacticallyValidCSSHeader)
     , m_didLoadErrorOccur(false)
     , m_usesRemUnits(o.m_usesRemUnits)
-    , m_hasMutated(false)
+    , m_isMutable(false)
+    , m_isInMemoryCache(false)
     , m_parserContext(o.m_parserContext)
 {
     ASSERT(o.isCacheable());
@@ -140,13 +142,16 @@ bool StyleSheetInternal::isCacheable() const
     // FIXME: Support copying import rules.
     if (!m_importRules.isEmpty())
         return false;
+    // FIXME: Support cached stylesheets in import rules.
+    if (m_ownerRule)
+        return false;
     // This would require dealing with multiple clients for load callbacks.
     if (!m_loadCompleted)
         return false;
     if (m_didLoadErrorOccur)
         return false;
     // It is not the original sheet anymore.
-    if (m_hasMutated)
+    if (m_isMutable)
         return false;
     // If the header is valid we are not going to need to check the SecurityOrigin.
     // FIXME: Valid mime type avoids the check too.
@@ -169,21 +174,21 @@ void StyleSheetInternal::parserAppendRule(PassRefPtr<StyleRuleBase> rule)
     m_childRules.append(rule);
 }
 
-PassRefPtr<CSSRule> StyleSheetInternal::createChildRuleCSSOMWrapper(unsigned index, CSSStyleSheet* parentWrapper)
+StyleRuleBase* StyleSheetInternal::ruleAt(unsigned index) const
 {
     ASSERT(index < ruleCount());
     
     unsigned childVectorIndex = index;
     if (hasCharsetRule()) {
         if (index == 0)
-            return CSSCharsetRule::create(parentWrapper, m_encodingFromCharsetRule);
+            return 0;
         --childVectorIndex;
     }
     if (childVectorIndex < m_importRules.size())
-        return m_importRules[childVectorIndex]->createCSSOMWrapper(parentWrapper);
-    
+        return m_importRules[childVectorIndex].get();
+
     childVectorIndex -= m_importRules.size();
-    return m_childRules[childVectorIndex]->createCSSOMWrapper(parentWrapper);
+    return m_childRules[childVectorIndex].get();
 }
 
 unsigned StyleSheetInternal::ruleCount() const
@@ -220,6 +225,7 @@ void StyleSheetInternal::parserSetEncodingFromCharsetRule(const String& encoding
 
 bool StyleSheetInternal::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsigned index)
 {
+    ASSERT(m_isMutable);
     ASSERT(index <= ruleCount());
     // Parser::parseRule doesn't currently allow @charset so we don't need to deal with it.
     ASSERT(!rule->isCharsetRule());
@@ -242,7 +248,6 @@ bool StyleSheetInternal::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsig
         m_importRules[childVectorIndex]->setParentStyleSheet(this);
         m_importRules[childVectorIndex]->requestStyleSheet();
         // FIXME: Stylesheet doesn't actually change meaningfully before the imported sheets are loaded.
-        styleSheetChanged();
         return true;
     }
     // Inserting @import rule after a non-import rule is not allowed.
@@ -251,20 +256,18 @@ bool StyleSheetInternal::wrapperInsertRule(PassRefPtr<StyleRuleBase> rule, unsig
     childVectorIndex -= m_importRules.size();
  
     m_childRules.insert(childVectorIndex, rule);
-    
-    styleSheetChanged();
     return true;
 }
 
 void StyleSheetInternal::wrapperDeleteRule(unsigned index)
 {
+    ASSERT(m_isMutable);
     ASSERT(index < ruleCount());
 
     unsigned childVectorIndex = index;
     if (hasCharsetRule()) {
         if (childVectorIndex == 0) {
             clearCharsetRule();
-            styleSheetChanged();
             return;
         }
         --childVectorIndex;
@@ -272,13 +275,11 @@ void StyleSheetInternal::wrapperDeleteRule(unsigned index)
     if (childVectorIndex < m_importRules.size()) {
         m_importRules[childVectorIndex]->clearParentStyleSheet();
         m_importRules.remove(childVectorIndex);
-        styleSheetChanged();
         return;
     }
     childVectorIndex -= m_importRules.size();
 
     m_childRules.remove(childVectorIndex);
-    styleSheetChanged();
 }
 
 void StyleSheetInternal::parserAddNamespace(const AtomicString& prefix, const AtomicString& uri)
@@ -415,16 +416,6 @@ Document* StyleSheetInternal::singleOwnerDocument() const
     return ownerNode ? ownerNode->document() : 0;
 }
 
-void StyleSheetInternal::styleSheetChanged()
-{
-    m_hasMutated = true;
-
-    Document* ownerDocument = singleOwnerDocument();
-    if (!ownerDocument)
-        return;
-    ownerDocument->styleResolverChanged(DeferRecalcStyle);
-}
-
 KURL StyleSheetInternal::completeURL(const String& url) const
 {
     return CSSParser::completeURL(m_parserContext, url);
@@ -473,6 +464,20 @@ void StyleSheetInternal::unregisterClient(CSSStyleSheet* sheet)
     m_clients.remove(position);
 }
 
+void StyleSheetInternal::addedToMemoryCache()
+{
+    ASSERT(!m_isInMemoryCache);
+    ASSERT(isCacheable());
+    m_isInMemoryCache = true;
+}
+
+void StyleSheetInternal::removedFromMemoryCache()
+{
+    ASSERT(m_isInMemoryCache);
+    ASSERT(isCacheable());
+    m_isInMemoryCache = false;
+}
+
 PassRefPtr<CSSStyleSheet> CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const String& encoding)
 {
     CSSParserContext parserContext(ownerNode->document(), baseURL, encoding);
@@ -514,14 +519,59 @@ CSSStyleSheet::~CSSStyleSheet()
     m_internal->unregisterClient(this);
 }
 
+void CSSStyleSheet::willMutateRules()
+{
+    // If we are the only client it is safe to mutate.
+    if (m_internal->hasOneClient() && !m_internal->isInMemoryCache()) {
+        m_internal->setMutable();
+        return;
+    }
+    // Only cacheable stylesheets should have multiple clients.
+    ASSERT(m_internal->isCacheable());
+
+    // Copy-on-write.
+    m_internal->unregisterClient(this);
+    m_internal = m_internal->copy();
+    m_internal->registerClient(this);
+
+    m_internal->setMutable();
+
+    // Any existing CSSOM wrappers need to be connected to the copied child rules.
+    reattachChildRuleCSSOMWrappers();
+}
+
+void CSSStyleSheet::didMutateRules()
+{
+    ASSERT(m_internal->isMutable());
+    ASSERT(m_internal->hasOneClient());
+
+    didMutate();
+}
+
+void CSSStyleSheet::didMutate()
+{
+    Document* owner = ownerDocument();
+    if (!owner)
+        return;
+    owner->styleResolverChanged(DeferRecalcStyle);
+}
+
+void CSSStyleSheet::reattachChildRuleCSSOMWrappers()
+{
+    for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
+        if (!m_childRuleCSSOMWrappers[i])
+            continue;
+        m_childRuleCSSOMWrappers[i]->reattach(m_internal->ruleAt(i));
+    }
+}
+
 void CSSStyleSheet::setDisabled(bool disabled)
 { 
     if (disabled == m_isDisabled)
         return;
     m_isDisabled = disabled;
-    Document* owner = ownerDocument();
-    if (owner)
-        owner->styleResolverChanged(DeferRecalcStyle);
+
+    didMutate();
 }
 
 void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries)
@@ -545,8 +595,13 @@ CSSRule* CSSStyleSheet::item(unsigned index)
     ASSERT(m_childRuleCSSOMWrappers.size() == ruleCount);
     
     RefPtr<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index];
-    if (!cssRule)
-        cssRule = m_internal->createChildRuleCSSOMWrapper(index, this);
+    if (!cssRule) {
+        if (index == 0 && m_internal->hasCharsetRule()) {
+            ASSERT(!m_internal->ruleAt(0));
+            cssRule = CSSCharsetRule::create(this, m_internal->encodingFromCharsetRule());
+        } else
+            cssRule = m_internal->ruleAt(index)->createCSSOMWrapper(this);
+    }
     return cssRule.get();
 }
 
@@ -584,6 +639,8 @@ unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, Exc
         ec = SYNTAX_ERR;
         return 0;
     }
+    RuleMutationScope mutationScope(this);
+
     bool success = m_internal->wrapperInsertRule(rule, index);
     if (!success) {
         ec = HIERARCHY_REQUEST_ERR;
@@ -591,6 +648,7 @@ unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, Exc
     }        
     if (!m_childRuleCSSOMWrappers.isEmpty())
         m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
+
     return index;
 }
 
@@ -603,6 +661,8 @@ void CSSStyleSheet::deleteRule(unsigned index, ExceptionCode& ec)
         ec = INDEX_SIZE_ERR;
         return;
     }
+    RuleMutationScope mutationScope(this);
+
     m_internal->wrapperDeleteRule(index);
 
     if (!m_childRuleCSSOMWrappers.isEmpty()) {
index 9bd3057..425f675 100644 (file)
 #define CSSStyleSheet_h
 
 #include "CSSParserMode.h"
+#include "CSSRule.h"
 #include "StyleSheet.h"
 #include <wtf/HashMap.h>
+#include <wtf/Noncopyable.h>
 #include <wtf/text/AtomicStringHash.h>
 
 namespace WebCore {
@@ -65,8 +67,6 @@ public:
 
     const AtomicString& determineNamespace(const AtomicString& prefix);
 
-    void styleSheetChanged();
-
     void parseAuthorStyleSheet(const CachedCSSStyleSheet*, const SecurityOrigin*);
     bool parseString(const String&);
     bool parseStringAtLine(const String&, int startLineNumber);
@@ -101,6 +101,8 @@ public:
 
     void clearRules();
 
+    bool hasCharsetRule() const { return !m_encodingFromCharsetRule.isNull(); }
+    String encodingFromCharsetRule() const { return m_encodingFromCharsetRule; }
     // Rules other than @charset and @import.
     const Vector<RefPtr<StyleRuleBase> >& childRules() const { return m_childRules; }
     const Vector<RefPtr<StyleRuleImport> >& importRules() const { return m_importRules; }
@@ -120,7 +122,8 @@ public:
     const KURL& baseURL() const { return m_parserContext.baseURL; }
 
     unsigned ruleCount() const;
-    
+    StyleRuleBase* ruleAt(unsigned index) const;
+
     bool usesRemUnits() const { return m_usesRemUnits; }
 
     unsigned estimatedSizeInBytes() const;
@@ -128,19 +131,24 @@ public:
     bool wrapperInsertRule(PassRefPtr<StyleRuleBase>, unsigned index);
     void wrapperDeleteRule(unsigned index);
 
-    PassRefPtr<CSSRule> createChildRuleCSSOMWrapper(unsigned index, CSSStyleSheet* parentWrapper);
-
     PassRefPtr<StyleSheetInternal> copy() const { return adoptRef(new StyleSheetInternal(*this)); }
 
     void registerClient(CSSStyleSheet*);
     void unregisterClient(CSSStyleSheet*);
+    bool hasOneClient() { return m_clients.size() == 1; }
+
+    bool isMutable() const { return m_isMutable; }
+    void setMutable() { m_isMutable = true; }
+
+    bool isInMemoryCache() const { return m_isInMemoryCache; }
+    void addedToMemoryCache();
+    void removedFromMemoryCache();
 
 private:
     StyleSheetInternal(StyleRuleImport* ownerRule, const String& originalURL, const KURL& baseURL, const CSSParserContext&);
     StyleSheetInternal(const StyleSheetInternal&);
 
     void clearCharsetRule();
-    bool hasCharsetRule() const { return !m_encodingFromCharsetRule.isNull(); }
 
     StyleRuleImport* m_ownerRule;
 
@@ -158,7 +166,8 @@ private:
     bool m_hasSyntacticallyValidCSSHeader : 1;
     bool m_didLoadErrorOccur : 1;
     bool m_usesRemUnits : 1;
-    bool m_hasMutated : 1;
+    bool m_isMutable : 1;
+    bool m_isInMemoryCache : 1;
     
     CSSParserContext m_parserContext;
 
@@ -207,13 +216,28 @@ public:
     virtual bool isLoading() const OVERRIDE { return m_internal->isLoading(); }
     
     void clearOwnerRule() { m_ownerRule = 0; }
-    void styleSheetChanged() { m_internal->styleSheetChanged(); }
     Document* ownerDocument() const;
     MediaQuerySet* mediaQueries() const { return m_mediaQueries.get(); }
     void setMediaQueries(PassRefPtr<MediaQuerySet>);
     void setTitle(const String& title) { m_title = title; }
+
+    class RuleMutationScope {
+        WTF_MAKE_NONCOPYABLE(RuleMutationScope);
+    public:
+        RuleMutationScope(CSSStyleSheet*);
+        RuleMutationScope(CSSRule*);
+        ~RuleMutationScope();
+
+    private:
+        CSSStyleSheet* m_styleSheet;
+    };
+
+    void willMutateRules();
+    void didMutateRules();
+    void didMutate();
     
     void clearChildRuleCSSOMWrappers();
+    void reattachChildRuleCSSOMWrappers();
 
     StyleSheetInternal* internal() const { return m_internal.get(); }
 
@@ -237,6 +261,26 @@ private:
     mutable OwnPtr<CSSRuleList> m_ruleListCSSOMWrapper;
 };
 
+inline CSSStyleSheet::RuleMutationScope::RuleMutationScope(CSSStyleSheet* sheet)
+    : m_styleSheet(sheet)
+{
+    if (m_styleSheet)
+        m_styleSheet->willMutateRules();
+}
+
+inline CSSStyleSheet::RuleMutationScope::RuleMutationScope(CSSRule* rule)
+    : m_styleSheet(rule ? rule->parentStyleSheet() : 0)
+{
+    if (m_styleSheet)
+        m_styleSheet->willMutateRules();
+}
+
+inline CSSStyleSheet::RuleMutationScope::~RuleMutationScope()
+{
+    if (m_styleSheet)
+        m_styleSheet->didMutateRules();
+}
+
 } // namespace
 
 #endif
index 57c6eaa..5fc793e 100644 (file)
@@ -230,12 +230,15 @@ MediaList::~MediaList()
 
 void MediaList::setMediaText(const String& value, ExceptionCode& ec)
 {
+    CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule);
+
     bool success = m_mediaQueries->parse(value);
     if (!success) {
         ec = SYNTAX_ERR;
         return;
     }
-    notifyChanged();
+    if (m_parentStyleSheet)
+        m_parentStyleSheet->didMutate();
 }
 
 String MediaList::item(unsigned index) const
@@ -248,31 +251,35 @@ String MediaList::item(unsigned index) const
 
 void MediaList::deleteMedium(const String& medium, ExceptionCode& ec)
 {
+    CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule);
+
     bool success = m_mediaQueries->remove(medium);
     if (!success) {
         ec = NOT_FOUND_ERR;
         return;
     }
-    notifyChanged();
+    if (m_parentStyleSheet)
+        m_parentStyleSheet->didMutate();
 }
 
 void MediaList::appendMedium(const String& medium, ExceptionCode& ec)
 {
+    CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule);
+
     bool success = m_mediaQueries->add(medium);
     if (!success) {
         // FIXME: Should this really be INVALID_CHARACTER_ERR?
         ec = INVALID_CHARACTER_ERR;
         return;
     }
-    notifyChanged();
+    if (m_parentStyleSheet)
+        m_parentStyleSheet->didMutate();
 }
 
-void MediaList::notifyChanged()
+void MediaList::reattach(MediaQuerySet* mediaQueries)
 {
-    CSSStyleSheet* parentStyleSheet = m_parentRule ? m_parentRule->parentStyleSheet() : m_parentStyleSheet;
-    if (!parentStyleSheet)
-        return;
-    parentStyleSheet->styleSheetChanged();
+    ASSERT(mediaQueries);
+    m_mediaQueries = mediaQueries;
 }
 
 }
index 88bdca2..32d0ec3 100644 (file)
@@ -104,13 +104,13 @@ public:
     void clearParentRule() { ASSERT(m_parentRule); m_parentRule = 0; }
     const MediaQuerySet* queries() const { return m_mediaQueries.get(); }
 
+    void reattach(MediaQuerySet*);
+
 private:
     MediaList();
     MediaList(MediaQuerySet*, CSSStyleSheet* parentSheet);
     MediaList(MediaQuerySet*, CSSRule* parentRule);
 
-    void notifyChanged();
-
     RefPtr<MediaQuerySet> m_mediaQueries;
     CSSStyleSheet* m_parentStyleSheet;
     CSSRule* m_parentRule;
index b3e6f1d..33f21a1 100644 (file)
@@ -152,11 +152,14 @@ void PropertySetCSSStyleDeclaration::setCssText(const String& text, ExceptionCod
 #if ENABLE(MUTATION_OBSERVERS)
     StyleAttributeMutationScope mutationScope(this);
 #endif
+    willMutate();
+
     ec = 0;
     // FIXME: Detect syntax errors and set ec.
     m_propertySet->parseDeclaration(text, contextStyleSheet());
 
-    didMutate();
+    didMutate(PropertyChanged);
+
 #if ENABLE(MUTATION_OBSERVERS)
     mutationScope.enqueueMutationRecord();    
 #endif
@@ -213,13 +216,19 @@ void PropertySetCSSStyleDeclaration::setProperty(const String& propertyName, con
     CSSPropertyID propertyID = cssPropertyID(propertyName);
     if (!propertyID)
         return;
+
     bool important = priority.find("important", 0, false) != notFound;
+
+    willMutate();
+
     ec = 0;
     bool changed = m_propertySet->setProperty(propertyID, value, important, contextStyleSheet());
+
+    didMutate(changed ? PropertyChanged : NoChanges);
+
     if (changed) {
         // CSS DOM requires raising SYNTAX_ERR of parsing failed, but this is too dangerous for compatibility,
         // see <http://bugs.webkit.org/show_bug.cgi?id=7296>.
-        didMutate();
 #if ENABLE(MUTATION_OBSERVERS)
         mutationScope.enqueueMutationRecord();
 #endif
@@ -234,11 +243,16 @@ String PropertySetCSSStyleDeclaration::removeProperty(const String& propertyName
     CSSPropertyID propertyID = cssPropertyID(propertyName);
     if (!propertyID)
         return String();
+
+    willMutate();
+
     ec = 0;
     String result;
-    bool changes = m_propertySet->removeProperty(propertyID, &result);
-    if (changes) {
-        didMutate();
+    bool changed = m_propertySet->removeProperty(propertyID, &result);
+
+    didMutate(changed ? PropertyChanged : NoChanges);
+
+    if (changed) {
 #if ENABLE(MUTATION_OBSERVERS)
         mutationScope.enqueueMutationRecord();
 #endif
@@ -261,22 +275,20 @@ void PropertySetCSSStyleDeclaration::setPropertyInternal(CSSPropertyID propertyI
 #if ENABLE(MUTATION_OBSERVERS)
     StyleAttributeMutationScope mutationScope(this);
 #endif
+    willMutate();
+
     ec = 0;
     bool changed = m_propertySet->setProperty(propertyID, value, important, contextStyleSheet());
+
+    didMutate(changed ? PropertyChanged : NoChanges);
+
     if (changed) {
-        didMutate();
 #if ENABLE(MUTATION_OBSERVERS)
         mutationScope.enqueueMutationRecord();
 #endif
     }
 }
 
-void PropertySetCSSStyleDeclaration::didMutate()
-{
-    m_cssomCSSValueClones.clear();
-    setNeedsStyleRecalc();
-}
-
 CSSValue* PropertySetCSSStyleDeclaration::cloneAndCacheForCSSOM(CSSValue* internalValue)
 {
     if (!internalValue)
@@ -339,12 +351,20 @@ void StyleRuleCSSStyleDeclaration::deref()
         delete this;
 }
 
-void StyleRuleCSSStyleDeclaration::setNeedsStyleRecalc()
+void StyleRuleCSSStyleDeclaration::willMutate()
 {
-    if (CSSStyleSheet* styleSheet = parentStyleSheet()) {
-        if (Document* document = styleSheet->ownerDocument())
-            document->styleResolverChanged(DeferRecalcStyle);
-    }
+    if (m_parentRule && m_parentRule->parentStyleSheet())
+        m_parentRule->parentStyleSheet()->willMutateRules();
+}
+
+void StyleRuleCSSStyleDeclaration::didMutate(MutationType type)
+{
+    if (type == PropertyChanged)
+        m_cssomCSSValueClones.clear();
+
+    // Style sheet mutation needs to be signaled even if the change failed. willMutateRules/didMutateRules must pair.
+    if (m_parentRule && m_parentRule->parentStyleSheet())
+        m_parentRule->parentStyleSheet()->didMutateRules();
 }
 
 CSSStyleSheet* StyleRuleCSSStyleDeclaration::parentStyleSheet() const
@@ -352,14 +372,26 @@ CSSStyleSheet* StyleRuleCSSStyleDeclaration::parentStyleSheet() const
     return m_parentRule ? m_parentRule->parentStyleSheet() : 0;
 }
 
-void InlineCSSStyleDeclaration::setNeedsStyleRecalc()
+void StyleRuleCSSStyleDeclaration::reattach(StylePropertySet* propertySet)
+{
+    ASSERT(propertySet);
+    m_propertySet->deref();
+    m_propertySet = propertySet;
+    m_propertySet->ref();
+}
+
+void InlineCSSStyleDeclaration::didMutate(MutationType type)
 {
+    if (type == NoChanges)
+        return;
+
+    m_cssomCSSValueClones.clear();
+
     if (!m_parentElement)
         return;
     m_parentElement->setNeedsStyleRecalc(InlineStyleChange);
     m_parentElement->invalidateStyleAttribute();
     StyleAttributeMutationScope(this).didInvalidateStyleAttr();
-    return;
 }
 
 CSSStyleSheet* InlineCSSStyleDeclaration::parentStyleSheet() const
index 2b8f412..bf9694c 100644 (file)
@@ -68,12 +68,14 @@ private:
     virtual bool cssPropertyMatches(const CSSProperty*) const OVERRIDE;
     virtual PassRefPtr<StylePropertySet> copy() const OVERRIDE;
     virtual PassRefPtr<StylePropertySet> makeMutable() OVERRIDE;
-    virtual void setNeedsStyleRecalc() { }
-    
-    void didMutate();
+
     CSSValue* cloneAndCacheForCSSOM(CSSValue*);
     
 protected:
+    enum MutationType { NoChanges, PropertyChanged };
+    virtual void willMutate() { }
+    virtual void didMutate(MutationType) { }
+
     StylePropertySet* m_propertySet;
     OwnPtr<HashMap<CSSValue*, RefPtr<CSSValue> > > m_cssomCSSValueClones;
 };
@@ -91,6 +93,8 @@ public:
     virtual void ref() OVERRIDE;
     virtual void deref() OVERRIDE;
 
+    void reattach(StylePropertySet*);
+
 private:
     StyleRuleCSSStyleDeclaration(StylePropertySet*, CSSRule*);
     virtual ~StyleRuleCSSStyleDeclaration();
@@ -98,8 +102,10 @@ private:
     virtual CSSStyleSheet* parentStyleSheet() const OVERRIDE;
 
     virtual CSSRule* parentRule() const OVERRIDE { return m_parentRule;  }
-    virtual void setNeedsStyleRecalc() OVERRIDE;
-    
+
+    virtual void willMutate() OVERRIDE;
+    virtual void didMutate(MutationType) OVERRIDE;
+
     unsigned m_refCount;
     CSSRule* m_parentRule;
 };
@@ -117,7 +123,8 @@ private:
     virtual CSSStyleSheet* parentStyleSheet() const OVERRIDE;
     virtual StyledElement* parentElement() const OVERRIDE { return m_parentElement; }
     virtual void clearParentElement() OVERRIDE { m_parentElement = 0; }
-    virtual void setNeedsStyleRecalc() OVERRIDE;
+
+    virtual void didMutate(MutationType) OVERRIDE;
     
     StyledElement* m_parentElement;
 };
index a5c8806..22b89a6 100644 (file)
@@ -23,6 +23,7 @@
 #include "StylePropertySet.h"
 
 #include "CSSParser.h"
+#include "CSSStyleSheet.h"
 #include "CSSValueKeywords.h"
 #include "CSSValueList.h"
 #include "CSSValuePool.h"
index 2d99334..70c854c 100644 (file)
@@ -467,14 +467,14 @@ void StyleResolver::collectFeatures()
 }
 
 #if ENABLE(STYLE_SCOPED)
-const ContainerNode* StyleResolver::determineScope(const StyleSheetInternal* sheet)
+const ContainerNode* StyleResolver::determineScope(const CSSStyleSheet* sheet)
 {
     ASSERT(sheet);
 
     if (!RuntimeEnabledFeatures::styleScopedEnabled())
         return 0;
 
-    Node* ownerNode = sheet->singleOwnerNode();
+    Node* ownerNode = sheet->ownerNode();
     if (!ownerNode || !ownerNode->isHTMLElement() || !ownerNode->hasTagName(HTMLNames::styleTag))
         return 0;
 
@@ -513,7 +513,7 @@ void StyleResolver::appendAuthorStylesheets(unsigned firstNew, const Vector<RefP
             continue;
         StyleSheetInternal* sheet = cssSheet->internal();
 #if ENABLE(STYLE_SCOPED)
-        const ContainerNode* scope = determineScope(sheet);
+        const ContainerNode* scope = determineScope(cssSheet);
         if (scope) {
             ScopedRuleSetMap::AddResult addResult = m_scopedAuthorStyles.add(scope, nullptr);
             if (addResult.isNewEntry)
index 3916a73..5f30bf8 100644 (file)
@@ -505,7 +505,7 @@ private:
 #endif
 
 #if ENABLE(STYLE_SCOPED)
-    static const ContainerNode* determineScope(const StyleSheetInternal*);
+    static const ContainerNode* determineScope(const CSSStyleSheet*);
 
     typedef HashMap<const ContainerNode*, OwnPtr<RuleSet> > ScopedRuleSetMap;
 
index cbf97d5..dcd7903 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "CSSParser.h"
 #include "CSSRuleList.h"
+#include "CSSStyleSheet.h"
 #include "StylePropertySet.h"
 #include "StyleSheet.h"
 #include "WebKitCSSKeyframeRule.h"
@@ -104,12 +105,9 @@ WebKitCSSKeyframesRule::~WebKitCSSKeyframesRule()
 
 void WebKitCSSKeyframesRule::setName(const String& name)
 {
-    m_keyframesRule->setName(name);
+    CSSStyleSheet::RuleMutationScope mutationScope(this);
 
-    // Since the name is used in the keyframe map list in StyleResolver, we need
-    // to recompute the style sheet to get the updated name.
-    if (CSSStyleSheet* styleSheet = parentStyleSheet())
-        styleSheet->styleSheetChanged();
+    m_keyframesRule->setName(name);
 }
 
 void WebKitCSSKeyframesRule::insertRule(const String& ruleText)
@@ -122,6 +120,8 @@ void WebKitCSSKeyframesRule::insertRule(const String& ruleText)
     if (!keyframe)
         return;
 
+    CSSStyleSheet::RuleMutationScope mutationScope(this);
+
     m_keyframesRule->wrapperAppendKeyframe(keyframe);
 
     m_childRuleCSSOMWrappers.grow(length());
@@ -135,6 +135,8 @@ void WebKitCSSKeyframesRule::deleteRule(const String& s)
     if (i < 0)
         return;
 
+    CSSStyleSheet::RuleMutationScope mutationScope(this);
+
     m_keyframesRule->wrapperRemoveKeyframe(i);
 
     if (m_childRuleCSSOMWrappers[i])
@@ -190,4 +192,10 @@ CSSRuleList* WebKitCSSKeyframesRule::cssRules()
     return m_ruleListCSSOMWrapper.get();
 }
 
+void WebKitCSSKeyframesRule::reattach(StyleRuleKeyframes* rule)
+{
+    ASSERT(rule);
+    m_keyframesRule = rule;
+}
+
 } // namespace WebCore
index 81abf87..2f9365c 100644 (file)
@@ -87,6 +87,8 @@ public:
     unsigned length() const;
     WebKitCSSKeyframeRule* item(unsigned index) const;
 
+    void reattach(StyleRuleKeyframes*);
+
 private:
     WebKitCSSKeyframesRule(StyleRuleKeyframes*, CSSStyleSheet* parent);
 
index df1c9b0..677a9f8 100644 (file)
@@ -96,5 +96,14 @@ CSSRuleList* WebKitCSSRegionRule::cssRules() const
     return m_ruleListCSSOMWrapper.get();
 }
 
+void WebKitCSSRegionRule::reattach(StyleRuleRegion* rule)
+{
+    ASSERT(rule);
+    m_regionRule = rule;
+    for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
+        if (m_childRuleCSSOMWrappers[i])
+            m_childRuleCSSOMWrappers[i]->reattach(m_regionRule->childRules()[i].get());
+    }
+}
 
 } // namespace WebCore
index ff57623..1987ef2 100644 (file)
@@ -53,6 +53,8 @@ public:
     unsigned length() const;
     CSSRule* item(unsigned index) const;
 
+    void reattach(StyleRuleRegion*);
+
 private:
     WebKitCSSRegionRule(StyleRuleRegion*, CSSStyleSheet* parent);
 
index 32a5c22..0565ff4 100644 (file)
@@ -718,10 +718,10 @@ String InspectorStyleSheet::finalURL() const
 
 void InspectorStyleSheet::reparseStyleSheet(const String& text)
 {
+    CSSStyleSheet::RuleMutationScope mutationScope(m_pageStyleSheet.get());
     m_pageStyleSheet->internal()->clearRules();
     m_pageStyleSheet->internal()->parseString(text);
     m_pageStyleSheet->clearChildRuleCSSOMWrappers();
-    m_pageStyleSheet->styleSheetChanged();
     m_inspectorStyles.clear();
     fireStyleSheetChanged();
 }
index 41fa617..ea3d04a 100644 (file)
@@ -50,6 +50,8 @@ CachedCSSStyleSheet::CachedCSSStyleSheet(const ResourceRequest& resourceRequest,
 
 CachedCSSStyleSheet::~CachedCSSStyleSheet()
 {
+    if (m_parsedStyleSheetCache)
+        m_parsedStyleSheetCache->removedFromMemoryCache();
 }
 
 void CachedCSSStyleSheet::didAddClient(CachedResourceClient* c)
@@ -153,7 +155,12 @@ bool CachedCSSStyleSheet::canUseSheet(bool enforceMIMEType, bool* hasValidMIMETy
 
 void CachedCSSStyleSheet::destroyDecodedData()
 {
+    if (!m_parsedStyleSheetCache)
+        return;
+
+    m_parsedStyleSheetCache->removedFromMemoryCache();
     m_parsedStyleSheetCache.clear();
+
     setDecodedSize(0);
 }
 
@@ -161,25 +168,26 @@ PassRefPtr<StyleSheetInternal> CachedCSSStyleSheet::restoreParsedStyleSheet(cons
 {
     if (!m_parsedStyleSheetCache)
         return 0;
-    // Cached parsed stylesheet has mutated, kick it out.
-    if (!m_parsedStyleSheetCache->isCacheable()) {
-        m_parsedStyleSheetCache.clear();
-        setDecodedSize(0);
-        return 0;
-    }
+    ASSERT(m_parsedStyleSheetCache->isCacheable());
+    ASSERT(m_parsedStyleSheetCache->isInMemoryCache());
+
     // Contexts must be identical so we know we would get the same exact result if we parsed again.
     if (m_parsedStyleSheetCache->parserContext() != context)
         return 0;
 
     didAccessDecodedData(currentTime());
-    // FIXME: Implement copy-on-write to avoid copying when not necessary.
-    return m_parsedStyleSheetCache->copy();
+
+    return m_parsedStyleSheetCache;
 }
 
 void CachedCSSStyleSheet::saveParsedStyleSheet(PassRefPtr<StyleSheetInternal> sheet)
 {
     ASSERT(sheet && sheet->isCacheable());
+
+    if (m_parsedStyleSheetCache)
+        m_parsedStyleSheetCache->removedFromMemoryCache();
     m_parsedStyleSheetCache = sheet;
+    m_parsedStyleSheetCache->addedToMemoryCache();
 
     setDecodedSize(m_parsedStyleSheetCache->estimatedSizeInBytes());
 }