Implement hixie's first-line proposal: only inherit properties that inherit by default from ::first-line. Inherit the reset properties from its style parent (which is the node that we'd inherit from if the ::first-line were not there). Bug 395623, r+sr=dbaron, a=beltzner
authorbzbarsky@mit.edu
Wed, 07 Nov 2007 09:13:00 -0800
changeset 7656 db9be2d12403c8eaa9bedeb09afe39e67c69632b
parent 7655 4b973556562c578b83e6e5ac34f068bbfcda6e1b
child 7657 c11b2985f18b574dbcaebbbf6d9aaa76b9173093
push idunknown
push userunknown
push dateunknown
reviewersbeltzner
bugs395623
milestone1.9a9pre
Implement hixie's first-line proposal: only inherit properties that inherit by default from ::first-line. Inherit the reset properties from its style parent (which is the node that we'd inherit from if the ::first-line were not there). Bug 395623, r+sr=dbaron, a=beltzner
layout/base/nsFrameManager.cpp
layout/reftests/first-line/basic-1.html
layout/reftests/first-line/basic-ref.html
layout/reftests/first-line/out-of-flow-1-ref.html
layout/reftests/first-line/out-of-flow-1a.html
layout/reftests/first-line/out-of-flow-1b.html
layout/reftests/first-line/out-of-flow-1c.html
layout/reftests/first-line/out-of-flow-1d.html
layout/reftests/first-line/reftest.list
layout/reftests/first-line/stress-1.html
layout/reftests/first-line/stress-10.html
layout/reftests/first-line/stress-11-ref.xhtml
layout/reftests/first-line/stress-11.xhtml
layout/reftests/first-line/stress-2-ref.html
layout/reftests/first-line/stress-2.html
layout/reftests/first-line/stress-3.html
layout/reftests/first-line/stress-4.html
layout/reftests/first-line/stress-5.html
layout/reftests/first-line/stress-6.html
layout/reftests/first-line/stress-7.html
layout/reftests/first-line/stress-8-ref.html
layout/reftests/first-line/stress-8.html
layout/reftests/first-line/stress-9-ref.html
layout/reftests/first-line/stress-9.html
layout/reftests/reftest.list
layout/style/nsRuleNode.cpp
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -848,17 +848,17 @@ VerifyStyleTree(nsPresContext* aPresCont
   nsIAtom* childList = nsnull;
   nsIFrame* child;
 
   do {
     child = aFrame->GetFirstChild(childList);
     while (child) {
       if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)
           || (child->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
-        // only do frames that are in flow
+        // only do frames that don't have placeholders
         if (nsGkAtoms::placeholderFrame == child->GetType()) { 
           // placeholder: first recurse and verify the out of flow frame,
           // then verify the placeholder's context
           nsIFrame* outOfFlowFrame =
             nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
 
           // recurse to out of flow frame, letting the parent context get resolved
           VerifyStyleTree(aPresContext, outOfFlowFrame, nsnull);
@@ -900,16 +900,25 @@ nsFrameManager::DebugVerifyStyleTree(nsI
   }
 }
 
 #endif // DEBUG
 
 nsresult
 nsFrameManager::ReParentStyleContext(nsIFrame* aFrame)
 {
+  if (nsGkAtoms::placeholderFrame == aFrame->GetType()) {
+    // Also reparent the out-of-flow
+    nsIFrame* outOfFlow =
+      nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
+    NS_ASSERTION(outOfFlow, "no out-of-flow frame");
+
+    ReParentStyleContext(outOfFlow);
+  }
+
   // DO NOT verify the style tree before reparenting.  The frame
   // tree has already been changed, so this check would just fail.
   nsStyleContext* oldContext = aFrame->GetStyleContext();
   // XXXbz can oldContext really ever be null?
   if (oldContext) {
     nsPresContext *presContext = GetPresContext();
     nsRefPtr<nsStyleContext> newContext;
     nsIFrame* providerFrame = nsnull;
@@ -933,46 +942,51 @@ nsFrameManager::ReParentStyleContext(nsI
     // Currently the IB anonymous block's style context takes the first part's
     // style context as parent, which is wrong since first-line style should
     // not apply to the anonymous block.
 
     newContext = mStyleSet->ReParentStyleContext(presContext, oldContext,
                                                  newParentContext);
     if (newContext) {
       if (newContext != oldContext) {
+        // Make sure to call CalcStyleDifference so that the new context ends
+        // up resolving all the structs the old context resolved.
+        nsChangeHint styleChange = oldContext->CalcStyleDifference(newContext);
+        // The style change is always 0 because we have the same rulenode and
+        // CalcStyleDifference optimizes us away.  That's OK, though:
+        // reparenting should never trigger a frame reconstruct, and whenever
+        // it's happening we already plan to reflow and repaint the frames.
+        NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame),
+                     "Our frame tree is likely to be bogus!");
+        
         PRInt32 listIndex = 0;
         nsIAtom* childList = nsnull;
         nsIFrame* child;
           
         aFrame->SetStyleContext(newContext);
 
         do {
           child = aFrame->GetFirstChild(childList);
           while (child) {
-            // only do frames that are in flow
-            if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)
-                 || (child->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+            // only do frames that don't have placeholders
+            if ((!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW) ||
+                 (child->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER)) &&
+                child != providerChild) {
+#ifdef DEBUG
               if (nsGkAtoms::placeholderFrame == child->GetType()) {
-                // get out of flow frame and recurse there
                 nsIFrame* outOfFlowFrame =
                   nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
                 NS_ASSERTION(outOfFlowFrame, "no out-of-flow frame");
 
                 NS_ASSERTION(outOfFlowFrame != providerChild,
                              "Out of flow provider?");
-
-                ReParentStyleContext(outOfFlowFrame);
+              }
+#endif
 
-                // reparent placeholder too
-                ReParentStyleContext(child);
-              }
-              else if (child != providerChild) {
-                // regular frame, not reparented yet
-                ReParentStyleContext(child);
-              }
+              ReParentStyleContext(child);
             }
 
             child = child->GetNextSibling();
           }
 
           childList = aFrame->GetAdditionalChildListName(listIndex++);
         } while (childList);
 
@@ -996,16 +1010,31 @@ nsFrameManager::ReParentStyleContext(nsI
           nsStyleContext* oldExtraContext =
             aFrame->GetAdditionalStyleContext(++contextIndex);
           if (oldExtraContext) {
             nsRefPtr<nsStyleContext> newExtraContext;
             newExtraContext = mStyleSet->ReParentStyleContext(presContext,
                                                               oldExtraContext,
                                                               newContext);
             if (newExtraContext) {
+              if (newExtraContext != oldExtraContext) {
+                // Make sure to call CalcStyleDifference so that the new
+                // context ends up resolving all the structs the old context
+                // resolved.
+                styleChange =
+                  oldExtraContext->CalcStyleDifference(newExtraContext);
+                // The style change is always 0 because we have the same
+                // rulenode and CalcStyleDifference optimizes us away.  That's
+                // OK, though: reparenting should never trigger a frame
+                // reconstruct, and whenever it's happening we already plan to
+                // reflow and repaint the frames.
+                NS_ASSERTION(!(styleChange & nsChangeHint_ReconstructFrame),
+                             "Our frame tree is likely to be bogus!");
+              }
+              
               aFrame->SetAdditionalStyleContext(contextIndex, newExtraContext);
             }
           }
           else {
             break;
           }
         }
 #ifdef DEBUG
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/basic-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      body { color: red }
+      body::first-line { color: green; }
+    </style>
+  </head>
+  <body>
+    This should be green
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/basic-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      body { color: green }
+    </style>
+  </head>
+  <body>
+    This should be green
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/out-of-flow-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      div { color: green }
+    </style>
+  </head>
+  <body>
+    <div><span style="float: left">This should be green</span></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/out-of-flow-1a.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      div { color: green }
+      div::first-line { color: red }
+    </style>
+  </head>
+  <body>
+    <div><span style="float: left">This should be green</span></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/out-of-flow-1b.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      div { color: red }
+      div::first-line { color: green }
+    </style>
+  </head>
+  <body>
+    <div><span><span style="float: left">This should be green</span></span></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/out-of-flow-1c.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      div { color: green }
+      div::first-line { color: red }
+    </style>
+  </head>
+  <body onload="document.getElementById('test').className = ''">
+    <div>
+      <span class="some value" style="float: left">
+        This should be green
+      </span>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/out-of-flow-1d.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      div { color: green }
+      div::first-line { color: red }
+    </style>
+    <script>
+      function runTest() {
+        var s = document.createElement("span");
+        s.setAttribute("style", "float: left");
+        s.appendChild(document.createTextNode("This should be green"));
+        var i = document.getElementById("i");
+        i.parentNode.insertBefore(s, i);
+      }
+    </script>
+  </head>
+  <body onload="runTest()">
+    <div><span id="i"></span></div>
+  </body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/reftest.list
@@ -0,0 +1,21 @@
+# basic functionality
+== basic-1.html basic-ref.html
+
+# handling of out-of-flows when ::first-line happens
+== out-of-flow-1a.html out-of-flow-1-ref.html
+== out-of-flow-1b.html out-of-flow-1-ref.html
+== out-of-flow-1c.html out-of-flow-1-ref.html
+fails == out-of-flow-1d.html out-of-flow-1-ref.html # bug 396645
+
+# stress-tests
+== stress-1.html about:blank # assertion test
+== stress-2.html stress-2-ref.html # assertion + rendering test
+== stress-3.html about:blank # assertion test
+== stress-4.html about:blank # assertion/crash test
+== stress-5.html about:blank # assertion/crash test
+== stress-6.html about:blank # assertion/crash test
+== stress-7.html about:blank # assertion/crash test
+== stress-8.html stress-8-ref.html # assertion/crash test
+== stress-9.html stress-9-ref.html # assertion/crash test
+== stress-10.html about:blank # crash test
+== stress-11.xhtml stress-11-ref.xhtml # crash test
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-1.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+
+<style>
+  .fl:first-line { }
+  .inh { position: inherit; }
+  .abs { position: absolute; }
+  body { visibility: hidden; }
+</style>
+ 
+<script>
+
+function boom()
+{
+  x = document.getElementById("x");
+  y = document.getElementById("y");
+
+  x.setAttribute('class', "fl abs");
+  y.setAttribute('class', "inh");
+  setTimeout(boom2, 5);
+}
+
+function boom2()
+{
+  y.setAttribute('class', "abs");
+  document.body.offsetWidth;
+  document.documentElement.className = "";
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 5);">
+<div id="x">
+  <p id="y">foo</p>
+</div>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-10.html
@@ -0,0 +1,5 @@
+<style>
+*::first-line { }
+*::before { content:"before text";}
+</style>
+<object style="position: fixed;-moz-column-count: 100;"><ol style="float: right;">
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-11-ref.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<style>
+*::before { content:"--"; }
+</style>
+
+<caption></caption>
+<span></span>
+This should not crash Mozilla
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-11.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<style>
+*::first-line { }
+*::before { content:"--"; }
+</style>
+<script>
+function doe() {
+document.getElementsByTagName('caption')[0].removeAttribute('style');
+document.documentElement.offsetHeight;
+document.getElementsByTagName('span')[0].removeAttribute('style');
+}
+window.onload=doe;
+</script>
+
+<caption style="float: left;"></caption>
+<span style="float: right;"></span>
+This should not crash Mozilla
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-2-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<div>
+  <b><span>Foo</span></b>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+
+<style>
+#fl:first-line { }
+</style>
+
+<script>
+function boom()
+{
+  document.getElementById("s").style.overflow = "auto";
+  document.body.offsetWidth;
+  document.documentElement.className = "";  
+}
+</script>
+
+</head>
+<body onload="setTimeout(boom, 300);">
+
+<div id="fl">
+  <b><span id="s">Foo</span></b>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-3.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait" id="fl" style="position: absolute">
+<head>
+
+<style>
+
+.flcounter:first-line { content: counter(egg); }
+
+#fl:first-line { }
+
+</style>
+
+<script>
+
+function boom()
+{
+  document.documentElement.appendChild(document.createTextNode("\n"));
+  document.getElementById("div").style.counterReset = "";
+  setTimeout(boom2, 20);
+}
+
+function boom2()
+{
+  document.body.removeAttribute("class");
+  document.body.offsetWidth;
+  document.documentElement.className = "";  
+}
+
+</script>
+</head>
+
+<body style="position: inherit; float: left;" class="flcounter" onload="boom();">
+<div id="div" style="counter-reset: chicken;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-4.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<style>
+#a:first-child::first-line { }
+body { visibility: hidden; }
+</style>
+<script>
+  function runTest() {
+    document.getElementById("test").removeAttribute('style');
+    document.body.offsetWidth;
+    document.documentElement.className = "";  
+  }
+</script>
+</head><body>
+
+<div style="position:absolute;">
+  <span id="a" style="position:fixed;">
+    <span>
+      <span style="display:table;position:absolute;">
+      </span>
+    </span>
+    Loading this should not crash Mozilla
+  </span>
+</div>
+
+
+</body></html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-5.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+#b::first-letter { }
+#c::first-line { }
+</style>
+</head>
+<body style="visibility: hidden">
+This page should not crash Mozilla
+<div id="c">
+  <table>
+    <div id="b" style="display:table-header-group;">
+      <q>
+        text
+        <div style="position:fixed;">
+          <q>y</q>
+        </div>
+      </q>  
+    </div>
+    <span style="display: table;"></span>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-6.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html class="reftest-wait"><head>
+<style>
+#b td::first-line { font-size:110%;}
+nobr::first-line { font-size:110%;}
+
+#b td::after { content:"anonymous text"; }
+nobr::after{ content:"anonymous text"; }
+
+#b::before { content:"before text";}
+#b td::before { content:"before text";}
+
+body { visibility: hidden; }
+</style>
+</head>
+<body>
+<table style="display: table-row;"></table><nobr style="display: list-item; -moz-column-count: 2;">
+<table id="b" style="display: inline;"></table>
+</nobr>
+<br>
+This page should not crash Mozilla
+<script>
+function doe(){
+   var td = document.createElement('td');;
+   td.setAttribute('height', '50%');
+   var tr = document.createElement('tr');;
+   tr.setAttribute('height', '50%');
+   tr.appendChild(td);
+   document.getElementsByTagName('table')[1].appendChild(tr);
+   document.body.offsetHeight;
+
+   var td = document.createElement('td');;
+   td.setAttribute('height', '50%');
+   document.getElementsByTagName('tr')[0].appendChild(td); 
+   document.body.offsetHeight;
+
+   var td = document.createElement('td');;
+   td.setAttribute('height', '50%');
+   document.getElementsByTagName('tr')[0].appendChild(td);
+
+   document.body.offsetWidth;
+   document.documentElement.className = "";  
+} 
+setTimeout(doe, 60);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-7.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html><head>
+<style>
+body > span::first-line { }
+span::before { content:"before text"; border:3px solid black;}
+</style>
+</head>
+<body>
+
+<span style=" float: left; -moz-column-count: 2;">
+  <span style="float: right;">
+    <span style=" float: right;-moz-column-count: 2;"></span>
+  </span>
+</span>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-8-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div>
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b
+
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-8.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head><style>
+ div:first-line {}
+</style></head>
+<body>
+<div>
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a
+b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b a b
+a b a b a b a b a b
+
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-9-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html><head>
+<style>
+*::after { content:"anonymous text"; }
+*::before { content:"before text"; }
+</style>
+</head>
+<body>
+
+<ol style="overflow: hidden;  float: right; -moz-column-count: 3;">
+<span style="overflow: auto;  float: left;"></span>
+</ol>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/first-line/stress-9.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html><head>
+<style>
+*::first-line { }
+*::after { content:"anonymous text"; }
+*::before { content:"before text"; }
+</style>
+</head>
+<body>
+
+<ol style="overflow: hidden;  float: right; -moz-column-count: 3;">
+<span style="overflow: auto;  float: left;"></span>
+</ol>
+</body>
+</html>
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -34,16 +34,19 @@ include xul-document-load/reftest.list
 include box-properties/reftest.list
 
 # counters/
 include counters/reftest.list
 
 # first-letter/
 include first-letter/reftest.list
 
+# first-line/
+include first-line/reftest.list
+
 # svg/
 include svg/reftest.list
 
 # text-indent/
 include text-indent/reftest.list
 
 # text-transform/
 include text-transform/reftest.list
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -1792,16 +1792,21 @@ nsRuleNode::AdjustLogicalBoxProp(nsStyle
  * @param rdtype_ The nsCSS* struct type used to compute this struct's data.
  * @param rdata_ Variable (declared here) holding the nsCSS* used here.
  */
 #define COMPUTE_START_RESET(type_, ctorargs_, data_, parentdata_, rdtype_, rdata_) \
   NS_ASSERTION(aRuleDetail != eRuleFullInherited,                             \
                "should not have bothered calling Compute*Data");              \
                                                                               \
   nsStyleContext* parentContext = aContext->GetParent();                      \
+  if (parentContext &&                                                        \
+      parentContext->GetPseudoType() == nsCSSPseudoElements::firstLine) {     \
+    /* Reset structs don't inherit from first-line */                         \
+    parentContext = parentContext->GetParent();                               \
+  }                                                                           \
                                                                               \
   const nsRuleData##rdtype_& rdata_ =                                         \
     static_cast<const nsRuleData##rdtype_&>(aData);                           \
   nsStyle##type_* data_;                                                      \
   if (aStartStruct)                                                           \
     /* We only need to compute the delta between this computed data and */    \
     /* our computed data. */                                                  \
     data_ = new (mPresContext)                                                \