Support for CSS widows and orphans
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 10 Dec 2012 21:22:44 +0000 (21:22 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 10 Dec 2012 21:22:44 +0000 (21:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=9410

Reviewed by Darin Adler.

Source/WebCore:

The CSS 'widow' and 'orphan' properties were supported by the parser and
style system, but were not actually doing anything in layout. Now they do!

In order to not break existing content, I unfortunately had to go against
the specification and change our default value from 2 to auto. A value of
auto means that widows and orphans will not cause any page/column breaks.

Supporting orphans was a small change. During line layout, if we realise
we need to move to a new page in a situation that would create an orphan,
move the entire block to the new page. There was already code to do this.

Support for widows was trickier. When we finish laying out a block, count
the number of hanging lines. If we've created a widow, and there are enough
available lines on the previous page, then steal some and remember the line
to break at. Trigger a relayout. Then notice the line that was marked for
breaking and avoid the widow. This means some blocks go through layout
twice, but as long as the widow value is small it shouldn't be a huge penalty.

Tests: fast/multicol/widows-and-orphans.html (new test)
       printing/page-break-orphans.html
       printing/page-break-orphans-and-widows.html
       printing/page-break-widows.html

* css/CSSComputedStyleDeclaration.cpp:
(WebCore::CSSComputedStyleDeclaration::getPropertyCSSValue): Return CSSValueAuto for widows and orphans if necessary.
* css/CSSParser.cpp:
(WebCore::CSSParser::parseValue): Support "auto" in widow and orphan property parsing.
* css/StyleBuilder.cpp:
(WebCore::StyleBuilder::StyleBuilder): Change property handler to something that supports auto.
* page/animation/CSSPropertyAnimation.cpp:
(WebCore::CSSPropertyAnimation::ensurePropertyMap): Add widows and orphans to the animation map.
* rendering/RenderBlock.cpp:
(WebCore::RenderBlock::layoutBlockChild): Detect if we've caused a widow-fixing break, and request relayout evaluation.
(WebCore::RenderBlock::markForPaginationRelayoutIfNeeded): Test for a widow-fixing line.
(WebCore::RenderBlock::setBreakAtLineToAvoidWidow): New function to remember where we should break on next layout.
(WebCore::RenderBlock::adjustLinePositionForPagination): Move the entire block to a new page if this would create an orphan.
* rendering/RenderBlock.h:
(WebCore::RenderBlock::shouldBreakAtLineToAvoidWidow): New function to indicate if we are remembering a widow.
(WebCore::RenderBlock::clearShouldBreakAtLineToAvoidWidow): Reset the line breaks.
(WebCore::RenderBlock::lineBreakToAvoidWidow):
(WebCore::RenderBlock::RenderBlockRareData::RenderBlockRareData): Add a flag for indicating if we are remembering a line, and the actual line itself.
* rendering/RenderBlockLineLayout.cpp:
(WebCore::RenderBlock::layoutRunsAndFloatsInRange): The code to detect and fix widows. Explained above.
* rendering/style/RenderStyle.h: New methods for indicating if widows and orphans are non-default values.
* rendering/style/StyleRareInheritedData.cpp: Ditto.
* rendering/style/StyleRareInheritedData.h: Ditto.

LayoutTests:

A new, slightly more comprehensive, test for widows and orphans. We also
now pass the existing widows and orphans tests from printing. And
since the default value of the properties changed, update the
computed style test as well.

* fast/css/getComputedStyle/computed-style-expected.txt: New auto value.
* fast/css/getComputedStyle/computed-style-without-renderer-expected.txt: New auto value.
* fast/multicol/widows-and-orphans-expected.txt: Added.
* fast/multicol/widows-and-orphans.html: Added.
* printing/page-break-orphans-and-widows-expected.txt:
* printing/page-break-orphans-expected.txt:
* printing/page-break-widows-expected.txt:
* svg/css/getComputedStyle-basic-expected.txt: New auto value.

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

20 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/css/getComputedStyle/computed-style-expected.txt
LayoutTests/fast/css/getComputedStyle/computed-style-without-renderer-expected.txt
LayoutTests/fast/multicol/widows-and-orphans-expected.txt [new file with mode: 0644]
LayoutTests/fast/multicol/widows-and-orphans.html [new file with mode: 0644]
LayoutTests/printing/page-break-orphans-and-widows-expected.txt
LayoutTests/printing/page-break-orphans-expected.txt
LayoutTests/printing/page-break-widows-expected.txt
LayoutTests/svg/css/getComputedStyle-basic-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/css/CSSComputedStyleDeclaration.cpp
Source/WebCore/css/CSSParser.cpp
Source/WebCore/css/StyleBuilder.cpp
Source/WebCore/page/animation/CSSPropertyAnimation.cpp
Source/WebCore/rendering/RenderBlock.cpp
Source/WebCore/rendering/RenderBlock.h
Source/WebCore/rendering/RenderBlockLineLayout.cpp
Source/WebCore/rendering/style/RenderStyle.h
Source/WebCore/rendering/style/StyleRareInheritedData.cpp
Source/WebCore/rendering/style/StyleRareInheritedData.h

index 4530eec..3349951 100644 (file)
@@ -1,3 +1,24 @@
+2012-12-10  Dean Jackson  <dino@apple.com>
+
+        Support for CSS widows and orphans
+        https://bugs.webkit.org/show_bug.cgi?id=9410
+
+        Reviewed by Darin Adler.
+
+        A new, slightly more comprehensive, test for widows and orphans. We also
+        now pass the existing widows and orphans tests from printing. And
+        since the default value of the properties changed, update the
+        computed style test as well.
+
+        * fast/css/getComputedStyle/computed-style-expected.txt: New auto value.
+        * fast/css/getComputedStyle/computed-style-without-renderer-expected.txt: New auto value.
+        * fast/multicol/widows-and-orphans-expected.txt: Added.
+        * fast/multicol/widows-and-orphans.html: Added.
+        * printing/page-break-orphans-and-widows-expected.txt:
+        * printing/page-break-orphans-expected.txt:
+        * printing/page-break-widows-expected.txt:
+        * svg/css/getComputedStyle-basic-expected.txt: New auto value.
+
 2012-12-10  Christophe Dumez  <christophe.dumez@intel.com>
 
         [EFL] Massive rebaseline after r137146
index b5f1ada..98862b7 100644 (file)
@@ -63,7 +63,7 @@ max-width: none;
 min-height: 0px;
 min-width: 0px;
 opacity: 1;
-orphans: 2;
+orphans: auto;
 outline-color: rgb(0, 0, 0);
 outline-style: none;
 outline-width: 0px;
@@ -95,7 +95,7 @@ unicode-bidi: normal;
 vertical-align: baseline;
 visibility: visible;
 white-space: normal;
-widows: 2;
+widows: auto;
 width: 784px;
 word-break: normal;
 word-spacing: 0px;
index ddeffc0..f0c8287 100644 (file)
@@ -62,7 +62,7 @@ max-width: none
 min-height: 0px
 min-width: 0px
 opacity: 1
-orphans: 2
+orphans: auto
 outline-color: rgb(0, 0, 0)
 outline-style: none
 outline-width: 0px
@@ -94,7 +94,7 @@ unicode-bidi: normal
 vertical-align: baseline
 visibility: visible
 white-space: normal
-widows: 2
+widows: auto
 width: 50%
 word-break: normal
 word-spacing: 0px
diff --git a/LayoutTests/fast/multicol/widows-and-orphans-expected.txt b/LayoutTests/fast/multicol/widows-and-orphans-expected.txt
new file mode 100644 (file)
index 0000000..2b6da5f
--- /dev/null
@@ -0,0 +1,43 @@
+Testing widows and orphans. Any green lines should be at the bottom of pages/columns, and any red lines should be at the top of pages/columns.
+
+test1 - Normal breaking
+
+test2 - Basic Orphan
+
+test3 - Basic Widow
+
+test4 - Orphans causing Widows
+
+test5 - Widows blocked by Orphan rule
+
+test6 - Ridiculous values
+
+PASS: test1 Block 1 Line 1 is correct.
+
+PASS: test1 Block 2 Line 5 is correct.
+
+PASS: test1 Block 4 Line 2 is correct.
+
+PASS: test2 Block 1 Line 1 is correct.
+
+PASS: test2 Block 2 Line 1 is correct.
+
+PASS: test3 Block 1 Line 1 is correct.
+
+PASS: test3 Block 2 Line 5 is correct.
+
+PASS: test4 Block 1 Line 1 is correct.
+
+PASS: test4 Block 2 Line 1 is correct.
+
+PASS: test4 Block 3 Line 3 is correct.
+
+PASS: test5 Block 1 Line 1 is correct.
+
+PASS: test5 Block 2 Line 3 is correct.
+
+PASS: test6 Block 1 Line 1 is correct.
+
+PASS: test6 Block 2 Line 1 is correct.
+
+PASS: test6 Block 3 Line 1 is correct.
diff --git a/LayoutTests/fast/multicol/widows-and-orphans.html b/LayoutTests/fast/multicol/widows-and-orphans.html
new file mode 100644 (file)
index 0000000..8feccdc
--- /dev/null
@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Testing Widows and Orphans</title>
+<style>
+body.hide-containers .container {
+    display: none;
+}
+
+.container {
+    width: 600px;
+    height: 200px;
+    -webkit-columns: 3;
+    line-height: 20px; /* 10 lines per page */
+    font-size: 16px;
+    margin: 0 0 20px 0;
+    padding: 0;
+    overflow: hidden;
+}
+
+.block {
+    margin: 0 0 15px 0;
+    padding: 0;
+}
+
+.top {
+    color: red;
+}
+
+.bottom {
+    color: green;
+}
+</style>
+<script>
+var results;
+
+function createTestContainer(id, description, blocks)
+{
+    var label = document.createElement("h3");
+    label.textContent = id + " - " + description;
+    document.body.appendChild(label);
+    var element = document.createElement("div");
+    element.className = "container";
+    element.id = id;
+
+    for (var i = 1; i <= blocks.length; i++) {
+        var block = document.createElement("div");
+        block.className = "block";
+        var numLines = blocks[i-1];
+        for (var j = 1; j <= numLines; j++) {
+            var line = document.createElement("span");
+            line.id = id + "-block-" + i + "-line-" + j;
+            line.textContent = "Block " + i + " Line " + j;
+            block.appendChild(line);
+            block.appendChild(document.createElement("br"));
+        }
+        element.appendChild(block);
+    }
+    document.body.appendChild(element);
+    return element;
+}
+
+function markTopLine(containerId, blockNumber, lineNumber)
+{
+    var element = document.getElementById(containerId + "-block-" + blockNumber + "-line-" + lineNumber);
+    element.className = "top";
+}
+
+function markBottomLine(containerId, blockNumber, lineNumber)
+{
+    var element = document.getElementById(containerId + "-block-" + blockNumber + "-line-" + lineNumber);
+    element.className = "bottom";
+}
+
+function logPass(msg) {
+    log("PASS: " + msg);
+}
+
+function logFail(msg) {
+    log("FAIL: " + msg);
+}
+
+function log(msg) {
+    if (!results)
+        results = document.createElement("div");
+
+    var output = document.createElement("p");
+    output.textContent = msg;
+    results.appendChild(output);
+}
+
+function testIsFirstInColumn(containerId, blockNumber, lineNumber)
+{
+    // Get the upper bounds of the container and line.
+    var topOfContainer = document.getElementById(containerId).getBoundingClientRect().top;
+    var topOfLine = document.getElementById(containerId + "-block-" + blockNumber + "-line-" + lineNumber).getBoundingClientRect().top;
+
+    if (Math.abs(topOfContainer - topOfLine) < 5) // Give 5 pixels to account for subpixel layout.
+        logPass(containerId + " Block " + blockNumber + " Line " + lineNumber + " is correct.");
+    else
+        logFail(containerId + " Block " + blockNumber + " Line " + lineNumber + " wasn't at the top of the column.");
+}
+
+function runTest()
+{
+    var container;
+
+    createTestContainer("test1", "Normal breaking", [5, 6, 5, 5]);
+
+    markTopLine("test1", 1, 1);
+    markBottomLine("test1", 2, 4);
+    markTopLine("test1", 2, 5);
+    markBottomLine("test1", 4, 1);
+    markTopLine("test1", 4, 2);
+
+    testIsFirstInColumn("test1", 1, 1);
+    testIsFirstInColumn("test1", 2, 5);
+    testIsFirstInColumn("test1", 4, 2);
+
+    container = createTestContainer("test2", "Basic Orphan", [8, 6]);
+    container.style.orphans = 2;
+
+    markTopLine("test2", 1, 1);
+    markBottomLine("test2", 1, 8); // Orphan break happens here.
+    markTopLine("test2", 2, 1);
+
+    testIsFirstInColumn("test2", 1, 1);
+    testIsFirstInColumn("test2", 2, 1);
+
+    container = createTestContainer("test3", "Basic Widow", [4, 6, 3]);
+    container.style.widows = 2;
+
+    markTopLine("test3", 1, 1);
+    markBottomLine("test3", 2, 4); // Widow break happens here.
+    markTopLine("test3", 2, 5);
+
+    testIsFirstInColumn("test3", 1, 1);
+    testIsFirstInColumn("test3", 2, 5);
+
+    container = createTestContainer("test4", "Orphans causing Widows", [8, 6, 4, 4]);
+    container.style.orphans = 2;
+    container.style.widows = 2;
+
+    markTopLine("test4", 1, 1);
+    markBottomLine("test4", 1, 8); // Orphan break happens here.
+    markTopLine("test4", 2, 1);
+    markBottomLine("test4", 3, 2); // And that creates a widow forcing a break here.
+    markTopLine("test4", 3, 3);
+
+    testIsFirstInColumn("test4", 1, 1);
+    testIsFirstInColumn("test4", 2, 1);
+    testIsFirstInColumn("test4", 3, 3);
+
+    container = createTestContainer("test5", "Widows blocked by Orphan rule", [7, 3, 4]);
+    container.style.orphans = 2;
+    container.style.widows = 2;
+
+    markTopLine("test5", 1, 1);
+    markBottomLine("test5", 2, 2); // This line should not move - protected by orphaning.
+    markTopLine("test5", 2, 3); // This line won't be un-widowed - blocked by orphaning.
+
+    testIsFirstInColumn("test5", 1, 1);
+    testIsFirstInColumn("test5", 2, 3);
+
+    container = createTestContainer("test6", "Ridiculous values", [7, 7, 7, 7]);
+    container.style.orphans = 100;
+    container.style.widows = 100;
+
+    markTopLine("test6", 1, 1);
+    markBottomLine("test6", 1, 7); // Orphan break happens here.
+    markTopLine("test6", 2, 1); // Adopted.
+    markBottomLine("test6", 2, 7); // Orphan break.
+    markTopLine("test6", 3, 1); // Adopted.
+
+    testIsFirstInColumn("test6", 1, 1);
+    testIsFirstInColumn("test6", 2, 1);
+    testIsFirstInColumn("test6", 3, 1);
+
+    if (results)
+        document.body.appendChild(results);
+
+    if (window.testRunner) {
+        // Hide all the containers and leave just the test results for text output.
+        document.body.className = "hide-containers";
+        testRunner.notifyDone();
+    }
+}
+
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+window.addEventListener("load", runTest, false);
+</script>
+</head>
+<body>
+    <p>
+        Testing widows and orphans. Any green lines
+        should be at the bottom of pages/columns, and any red lines
+        should be at the top of pages/columns.
+    </p>
+</body>
+</html>
index feeea09..e8dc74b 100644 (file)
@@ -8,7 +8,7 @@ PASS: page number of "page1-1" is 1
 PASS: page number of "page1-2" is 1
 PASS: page number of "page1-3" is 1
 PASS: page number of "page2" is 2
-FAIL: expected page number of "page3" is 3. Was 2
+PASS: page number of "page3" is 3
 PASS: page number of "page4" is 4
 FAIL: expected page number of "page5" is 5. Was 4
 PASS: page number of "page6-1" is 6
index 2127aaa..3fc1523 100644 (file)
@@ -7,9 +7,10 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 PASS: page number of "page1-1" is 1
 PASS: page number of "page1-2" is 1
 PASS: page number of "page3" is 3
-FAIL: expected page number of "page4" is 4. Was 3
+PASS: page number of "page4" is 4
 PASS: page number of "page5-1" is 5
 PASS: page number of "page5-2" is 5
+All tests passed
 
 PASS successfullyParsed is true
 
index f40e460..ada914a 100644 (file)
@@ -11,7 +11,8 @@ PASS: page number of "page2-2" is 2
 PASS: page number of "page3-1" is 3
 PASS: page number of "page3-2" is 3
 PASS: page number of "page4" is 4
-FAIL: expected page number of "page5" is 5. Was 4
+PASS: page number of "page5" is 5
+All tests passed
 
 PASS successfullyParsed is true
 
index ab29de7..9f36db2 100644 (file)
@@ -124,7 +124,7 @@ rect: style.getPropertyValue(min-width) : 0px
 rect: style.getPropertyCSSValue(min-width) : [object CSSPrimitiveValue]
 rect: style.getPropertyValue(opacity) : 1
 rect: style.getPropertyCSSValue(opacity) : [object CSSPrimitiveValue]
-rect: style.getPropertyValue(orphans) : 2
+rect: style.getPropertyValue(orphans) : auto
 rect: style.getPropertyCSSValue(orphans) : [object CSSPrimitiveValue]
 rect: style.getPropertyValue(outline-color) : rgb(0, 0, 0)
 rect: style.getPropertyCSSValue(outline-color) : [object CSSPrimitiveValue]
@@ -188,7 +188,7 @@ rect: style.getPropertyValue(visibility) : visible
 rect: style.getPropertyCSSValue(visibility) : [object CSSPrimitiveValue]
 rect: style.getPropertyValue(white-space) : normal
 rect: style.getPropertyCSSValue(white-space) : [object CSSPrimitiveValue]
-rect: style.getPropertyValue(widows) : 2
+rect: style.getPropertyValue(widows) : auto
 rect: style.getPropertyCSSValue(widows) : [object CSSPrimitiveValue]
 rect: style.getPropertyValue(width) : auto
 rect: style.getPropertyCSSValue(width) : [object CSSPrimitiveValue]
@@ -624,7 +624,7 @@ g: style.getPropertyValue(min-width) : 0px
 g: style.getPropertyCSSValue(min-width) : [object CSSPrimitiveValue]
 g: style.getPropertyValue(opacity) : 1
 g: style.getPropertyCSSValue(opacity) : [object CSSPrimitiveValue]
-g: style.getPropertyValue(orphans) : 2
+g: style.getPropertyValue(orphans) : auto
 g: style.getPropertyCSSValue(orphans) : [object CSSPrimitiveValue]
 g: style.getPropertyValue(outline-color) : rgb(0, 0, 0)
 g: style.getPropertyCSSValue(outline-color) : [object CSSPrimitiveValue]
@@ -688,7 +688,7 @@ g: style.getPropertyValue(visibility) : visible
 g: style.getPropertyCSSValue(visibility) : [object CSSPrimitiveValue]
 g: style.getPropertyValue(white-space) : normal
 g: style.getPropertyCSSValue(white-space) : [object CSSPrimitiveValue]
-g: style.getPropertyValue(widows) : 2
+g: style.getPropertyValue(widows) : auto
 g: style.getPropertyCSSValue(widows) : [object CSSPrimitiveValue]
 g: style.getPropertyValue(width) : auto
 g: style.getPropertyCSSValue(width) : [object CSSPrimitiveValue]
index ccb795e..b7dc00e 100644 (file)
@@ -1,3 +1,57 @@
+2012-12-10  Dean Jackson  <dino@apple.com>
+
+        Support for CSS widows and orphans
+        https://bugs.webkit.org/show_bug.cgi?id=9410
+
+        Reviewed by Darin Adler.
+
+        The CSS 'widow' and 'orphan' properties were supported by the parser and
+        style system, but were not actually doing anything in layout. Now they do!
+
+        In order to not break existing content, I unfortunately had to go against
+        the specification and change our default value from 2 to auto. A value of
+        auto means that widows and orphans will not cause any page/column breaks.
+
+        Supporting orphans was a small change. During line layout, if we realise
+        we need to move to a new page in a situation that would create an orphan,
+        move the entire block to the new page. There was already code to do this.
+
+        Support for widows was trickier. When we finish laying out a block, count
+        the number of hanging lines. If we've created a widow, and there are enough
+        available lines on the previous page, then steal some and remember the line
+        to break at. Trigger a relayout. Then notice the line that was marked for
+        breaking and avoid the widow. This means some blocks go through layout
+        twice, but as long as the widow value is small it shouldn't be a huge penalty.
+
+        Tests: fast/multicol/widows-and-orphans.html (new test)
+               printing/page-break-orphans.html
+               printing/page-break-orphans-and-widows.html
+               printing/page-break-widows.html
+
+        * css/CSSComputedStyleDeclaration.cpp:
+        (WebCore::CSSComputedStyleDeclaration::getPropertyCSSValue): Return CSSValueAuto for widows and orphans if necessary.
+        * css/CSSParser.cpp:
+        (WebCore::CSSParser::parseValue): Support "auto" in widow and orphan property parsing.
+        * css/StyleBuilder.cpp:
+        (WebCore::StyleBuilder::StyleBuilder): Change property handler to something that supports auto.
+        * page/animation/CSSPropertyAnimation.cpp:
+        (WebCore::CSSPropertyAnimation::ensurePropertyMap): Add widows and orphans to the animation map.
+        * rendering/RenderBlock.cpp:
+        (WebCore::RenderBlock::layoutBlockChild): Detect if we've caused a widow-fixing break, and request relayout evaluation.
+        (WebCore::RenderBlock::markForPaginationRelayoutIfNeeded): Test for a widow-fixing line.
+        (WebCore::RenderBlock::setBreakAtLineToAvoidWidow): New function to remember where we should break on next layout.
+        (WebCore::RenderBlock::adjustLinePositionForPagination): Move the entire block to a new page if this would create an orphan.
+        * rendering/RenderBlock.h:
+        (WebCore::RenderBlock::shouldBreakAtLineToAvoidWidow): New function to indicate if we are remembering a widow.
+        (WebCore::RenderBlock::clearShouldBreakAtLineToAvoidWidow): Reset the line breaks.
+        (WebCore::RenderBlock::lineBreakToAvoidWidow):
+        (WebCore::RenderBlock::RenderBlockRareData::RenderBlockRareData): Add a flag for indicating if we are remembering a line, and the actual line itself.
+        * rendering/RenderBlockLineLayout.cpp:
+        (WebCore::RenderBlock::layoutRunsAndFloatsInRange): The code to detect and fix widows. Explained above.
+        * rendering/style/RenderStyle.h: New methods for indicating if widows and orphans are non-default values.
+        * rendering/style/StyleRareInheritedData.cpp: Ditto.
+        * rendering/style/StyleRareInheritedData.h: Ditto.
+
 2012-12-10  Zan Dobersek  <zandobersek@gmail.com>
 
         REGRESSION (r118735): svg/dom/complex-svgView-specification.html, svg/dom/SVGViewSpec.html, svg/dom/viewspec-parser.html failing on GTK Linux 64-bit Release
index daa2b0f..c104945 100644 (file)
@@ -2000,6 +2000,8 @@ PassRefPtr<CSSValue> CSSComputedStyleDeclaration::getPropertyCSSValue(CSSPropert
         case CSSPropertyOpacity:
             return cssValuePool().createValue(style->opacity(), CSSPrimitiveValue::CSS_NUMBER);
         case CSSPropertyOrphans:
+            if (style->hasAutoOrphans())
+                return cssValuePool().createIdentifierValue(CSSValueAuto);
             return cssValuePool().createValue(style->orphans(), CSSPrimitiveValue::CSS_NUMBER);
         case CSSPropertyOutlineColor:
             return m_allowVisitedStyle ? cssValuePool().createColorValue(style->visitedDependentColor(CSSPropertyOutlineColor).rgb()) : currentColorOrValidColor(style.get(), style->outlineColor());
@@ -2148,6 +2150,8 @@ PassRefPtr<CSSValue> CSSComputedStyleDeclaration::getPropertyCSSValue(CSSPropert
         case CSSPropertyWhiteSpace:
             return cssValuePool().createValue(style->whiteSpace());
         case CSSPropertyWidows:
+            if (style->hasAutoWidows())
+                return cssValuePool().createIdentifierValue(CSSValueAuto);
             return cssValuePool().createValue(style->widows(), CSSPrimitiveValue::CSS_NUMBER);
         case CSSPropertyWidth:
             if (renderer) {
index 6494f17..b28a739 100644 (file)
@@ -2153,10 +2153,12 @@ bool CSSParser::parseValue(CSSPropertyID propId, bool important)
             break;
         }
         /* nobreak */
-    case CSSPropertyOrphans:              // <integer> | inherit
-    case CSSPropertyWidows:               // <integer> | inherit
-        // ### not supported later on
-        validPrimitive = (!id && validUnit(value, FInteger, CSSQuirksMode));
+    case CSSPropertyOrphans: // <integer> | inherit | auto (We've added support for auto for backwards compatibility)
+    case CSSPropertyWidows: // <integer> | inherit | auto (Ditto)
+        if (id == CSSValueAuto)
+            validPrimitive = true;
+        else
+            validPrimitive = (!id && validUnit(value, FInteger, CSSQuirksMode));
         break;
 
     case CSSPropertyLineHeight:
index c4dd74e..30380cf 100644 (file)
@@ -1895,7 +1895,7 @@ StyleBuilder::StyleBuilder()
     setPropertyHandler(CSSPropertyMinHeight, ApplyPropertyLength<&RenderStyle::minHeight, &RenderStyle::setMinHeight, &RenderStyle::initialMinSize, AutoEnabled, LegacyIntrinsicEnabled, IntrinsicDisabled>::createHandler());
     setPropertyHandler(CSSPropertyMinWidth, ApplyPropertyLength<&RenderStyle::minWidth, &RenderStyle::setMinWidth, &RenderStyle::initialMinSize, AutoEnabled, LegacyIntrinsicEnabled, IntrinsicEnabled>::createHandler());
     setPropertyHandler(CSSPropertyOpacity, ApplyPropertyDefault<float, &RenderStyle::opacity, float, &RenderStyle::setOpacity, float, &RenderStyle::initialOpacity>::createHandler());
-    setPropertyHandler(CSSPropertyOrphans, ApplyPropertyDefault<short, &RenderStyle::orphans, short, &RenderStyle::setOrphans, short, &RenderStyle::initialOrphans>::createHandler());
+    setPropertyHandler(CSSPropertyOrphans, ApplyPropertyAuto<short, &RenderStyle::orphans, &RenderStyle::setOrphans, &RenderStyle::hasAutoOrphans, &RenderStyle::setHasAutoOrphans>::createHandler());
     setPropertyHandler(CSSPropertyOutline, ApplyPropertyExpanding<SuppressValue, CSSPropertyOutlineWidth, CSSPropertyOutlineColor, CSSPropertyOutlineStyle>::createHandler());
     setPropertyHandler(CSSPropertyOutlineColor, ApplyPropertyColor<NoInheritFromParent, &RenderStyle::outlineColor, &RenderStyle::setOutlineColor, &RenderStyle::setVisitedLinkOutlineColor, &RenderStyle::color>::createHandler());
     setPropertyHandler(CSSPropertyOutlineOffset, ApplyPropertyComputeLength<int, &RenderStyle::outlineOffset, &RenderStyle::setOutlineOffset, &RenderStyle::initialOutlineOffset>::createHandler());
@@ -2081,7 +2081,7 @@ StyleBuilder::StyleBuilder()
     setPropertyHandler(CSSPropertyWebkitShapeOutside, ApplyPropertyExclusionShape<&RenderStyle::shapeOutside, &RenderStyle::setShapeOutside, &RenderStyle::initialShapeOutside>::createHandler());
 #endif
     setPropertyHandler(CSSPropertyWhiteSpace, ApplyPropertyDefault<EWhiteSpace, &RenderStyle::whiteSpace, EWhiteSpace, &RenderStyle::setWhiteSpace, EWhiteSpace, &RenderStyle::initialWhiteSpace>::createHandler());
-    setPropertyHandler(CSSPropertyWidows, ApplyPropertyDefault<short, &RenderStyle::widows, short, &RenderStyle::setWidows, short, &RenderStyle::initialWidows>::createHandler());
+    setPropertyHandler(CSSPropertyWidows, ApplyPropertyAuto<short, &RenderStyle::widows, &RenderStyle::setWidows, &RenderStyle::hasAutoWidows, &RenderStyle::setHasAutoWidows>::createHandler());
     setPropertyHandler(CSSPropertyWidth, ApplyPropertyLength<&RenderStyle::width, &RenderStyle::setWidth, &RenderStyle::initialSize, AutoEnabled, LegacyIntrinsicEnabled, IntrinsicEnabled, NoneDisabled, UndefinedDisabled>::createHandler());
     setPropertyHandler(CSSPropertyWordBreak, ApplyPropertyDefault<EWordBreak, &RenderStyle::wordBreak, EWordBreak, &RenderStyle::setWordBreak, EWordBreak, &RenderStyle::initialWordBreak>::createHandler());
     setPropertyHandler(CSSPropertyWordSpacing, ApplyPropertyComputeLength<int, &RenderStyle::wordSpacing, &RenderStyle::setWordSpacing, &RenderStyle::initialLetterWordSpacing, NormalEnabled, ThicknessDisabled, SVGZoomEnabled>::createHandler());
index 2504da4..019f543 100644 (file)
@@ -1130,6 +1130,8 @@ void CSSPropertyAnimation::ensurePropertyMap()
     gPropertyWrappers->append(new PropertyWrapper<short>(CSSPropertyWebkitBorderHorizontalSpacing, &RenderStyle::horizontalBorderSpacing, &RenderStyle::setHorizontalBorderSpacing));
     gPropertyWrappers->append(new PropertyWrapper<short>(CSSPropertyWebkitBorderVerticalSpacing, &RenderStyle::verticalBorderSpacing, &RenderStyle::setVerticalBorderSpacing));
     gPropertyWrappers->append(new PropertyWrapper<int>(CSSPropertyZIndex, &RenderStyle::zIndex, &RenderStyle::setZIndex));
+    gPropertyWrappers->append(new PropertyWrapper<short>(CSSPropertyOrphans, &RenderStyle::orphans, &RenderStyle::setOrphans));
+    gPropertyWrappers->append(new PropertyWrapper<short>(CSSPropertyWidows, &RenderStyle::widows, &RenderStyle::setWidows));
     gPropertyWrappers->append(new PropertyWrapper<Length>(CSSPropertyLineHeight, &RenderStyle::specifiedLineHeight, &RenderStyle::setLineHeight));
     gPropertyWrappers->append(new PropertyWrapper<int>(CSSPropertyOutlineOffset, &RenderStyle::outlineOffset, &RenderStyle::setOutlineOffset));
     gPropertyWrappers->append(new PropertyWrapper<unsigned short>(CSSPropertyOutlineWidth, &RenderStyle::outlineWidth, &RenderStyle::setOutlineWidth));
index 0773892..7a444f9 100644 (file)
@@ -2517,7 +2517,7 @@ void RenderBlock::layoutBlockChild(RenderBox* child, MarginInfo& marginInfo, Lay
     // Now we have a final top position.  See if it really does end up being different from our estimate.
     // clearFloatsIfNeeded can also mark the child as needing a layout even though we didn't move. This happens
     // when collapseMargins dynamically adds overhanging floats because of a child with negative margins.
-    if (logicalTopAfterClear != logicalTopEstimate || child->needsLayout()) {
+    if (logicalTopAfterClear != logicalTopEstimate || child->needsLayout() || (paginated && childRenderBlock && childRenderBlock->shouldBreakAtLineToAvoidWidow())) {
         if (child->shrinkToAvoidFloats()) {
             // The child's width depends on the line width.
             // When the child shifts to clear an item, its width can
@@ -2724,7 +2724,7 @@ void RenderBlock::markForPaginationRelayoutIfNeeded()
     if (needsLayout())
         return;
 
-    if (view()->layoutState()->pageLogicalHeightChanged() || (view()->layoutState()->pageLogicalHeight() && view()->layoutState()->pageLogicalOffset(this, logicalTop()) != pageLogicalOffset()))
+    if (view()->layoutState()->pageLogicalHeightChanged() || (view()->layoutState()->pageLogicalHeight() && view()->layoutState()->pageLogicalOffset(this, logicalTop()) != pageLogicalOffset()) || shouldBreakAtLineToAvoidWidow())
         setChildNeedsLayout(true, MarkOnlyThis);
 }
 
@@ -6776,6 +6776,23 @@ void RenderBlock::setPageLogicalOffset(LayoutUnit logicalOffset)
     m_rareData->m_pageLogicalOffset = logicalOffset;
 }
 
+void RenderBlock::setBreakAtLineToAvoidWidow(RootInlineBox* lineToBreak)
+{
+    ASSERT(lineToBreak);
+    if (!m_rareData)
+        m_rareData = adoptPtr(new RenderBlockRareData(this));
+    m_rareData->m_shouldBreakAtLineToAvoidWidow = true;
+    m_rareData->m_lineBreakToAvoidWidow = lineToBreak;
+}
+
+void RenderBlock::clearShouldBreakAtLineToAvoidWidow() const
+{
+    if (!m_rareData)
+        return;
+    m_rareData->m_shouldBreakAtLineToAvoidWidow = false;
+    m_rareData->m_lineBreakToAvoidWidow = 0;
+}
+
 void RenderBlock::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
 {
     // For blocks inside inlines, we go ahead and include margins so that we run right up to the
@@ -7159,7 +7176,10 @@ void RenderBlock::adjustLinePositionForPagination(RootInlineBox* lineBox, Layout
         || !hasNextPage(logicalOffset))
         return;
     LayoutUnit remainingLogicalHeight = pageRemainingLogicalHeightForOffset(logicalOffset, ExcludePageBoundary);
-    if (remainingLogicalHeight < lineHeight) {
+
+    if (remainingLogicalHeight < lineHeight || (shouldBreakAtLineToAvoidWidow() && lineBreakToAvoidWidow() == lineBox)) {
+        if (shouldBreakAtLineToAvoidWidow() && lineBreakToAvoidWidow() == lineBox)
+            clearShouldBreakAtLineToAvoidWidow();
         // If we have a non-uniform page height, then we have to shift further possibly.
         if (!hasUniformPageLogicalHeight && !pushToNextPageWithMinimumLogicalHeight(remainingLogicalHeight, logicalOffset, lineHeight))
             return;
@@ -7169,7 +7189,8 @@ void RenderBlock::adjustLinePositionForPagination(RootInlineBox* lineBox, Layout
         }
         LayoutUnit totalLogicalHeight = lineHeight + max<LayoutUnit>(0, logicalOffset);
         LayoutUnit pageLogicalHeightAtNewOffset = hasUniformPageLogicalHeight ? pageLogicalHeight : pageLogicalHeightForOffset(logicalOffset + remainingLogicalHeight);
-        if (lineBox == firstRootBox() && totalLogicalHeight < pageLogicalHeightAtNewOffset && !isOutOfFlowPositioned() && !isTableCell())
+        if (((lineBox == firstRootBox() && totalLogicalHeight < pageLogicalHeightAtNewOffset) || (!style()->hasAutoOrphans() && style()->orphans() >= lineCount()))
+            && !isOutOfFlowPositioned() && !isTableCell())
             setPaginationStrut(remainingLogicalHeight + max<LayoutUnit>(0, logicalOffset));
         else {
             delta += remainingLogicalHeight;
index 77265d6..ad14a01 100644 (file)
@@ -306,7 +306,12 @@ public:
 
     LayoutUnit paginationStrut() const { return m_rareData ? m_rareData->m_paginationStrut : LayoutUnit(); }
     void setPaginationStrut(LayoutUnit);
-    
+
+    bool shouldBreakAtLineToAvoidWidow() const { return m_rareData && m_rareData->m_shouldBreakAtLineToAvoidWidow; }
+    void clearShouldBreakAtLineToAvoidWidow() const;
+    RootInlineBox* lineBreakToAvoidWidow() const { return m_rareData ? m_rareData->m_lineBreakToAvoidWidow : 0; }
+    void setBreakAtLineToAvoidWidow(RootInlineBox*);
+
     // The page logical offset is the object's offset from the top of the page in the page progression
     // direction (so an x-offset in vertical text and a y-offset for horizontal text).
     LayoutUnit pageLogicalOffset() const { return m_rareData ? m_rareData->m_pageLogicalOffset : LayoutUnit(); }
@@ -1149,6 +1154,8 @@ protected:
             , m_paginationStrut(0)
             , m_pageLogicalOffset(0)
             , m_lineGridBox(0)
+            , m_shouldBreakAtLineToAvoidWidow(false)
+            , m_lineBreakToAvoidWidow(0)
         { 
         }
 
@@ -1175,6 +1182,9 @@ protected:
         LayoutUnit m_pageLogicalOffset;
         
         RootInlineBox* m_lineGridBox;
+
+        bool m_shouldBreakAtLineToAvoidWidow;
+        RootInlineBox* m_lineBreakToAvoidWidow;
      };
 
     OwnPtr<RenderBlockRareData> m_rareData;
index 4576012..f63fbdd 100644 (file)
@@ -1642,6 +1642,63 @@ void RenderBlock::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, Inlin
         lineMidpointState.reset();
         resolver.setPosition(end, numberOfIsolateAncestors(end));
     }
+
+    if (paginated && !style()->hasAutoWidows()) {
+        // Check the line boxes to make sure we didn't create unacceptable widows.
+        // However, we'll prioritize orphans - so nothing we do here should create
+        // a new orphan.
+
+        RootInlineBox* lineBox = lastRootBox();
+
+        // Count from the end of the block backwards, to see how many hanging
+        // lines we have.
+        RootInlineBox* firstLineInBlock = firstRootBox();
+        int numLinesHanging = 1;
+        while (lineBox && lineBox != firstLineInBlock && !lineBox->isFirstAfterPageBreak()) {
+            ++numLinesHanging;
+            lineBox = lineBox->prevRootBox();
+        }
+
+        // If there were no breaks in the block, we didn't create any widows.
+        if (!lineBox->isFirstAfterPageBreak() || lineBox == firstLineInBlock)
+            return;
+
+        if (numLinesHanging < style()->widows()) {
+            // We have detected a widow. Now we need to work out how many
+            // lines there are on the previous page, and how many we need
+            // to steal.
+            int numLinesNeeded = style()->widows() - numLinesHanging;
+            RootInlineBox* currentFirstLineOfNewPage = lineBox;
+
+            // Count the number of lines in the previous page.
+            lineBox = lineBox->prevRootBox();
+            int numLinesInPreviousPage = 1;
+            while (lineBox && lineBox != firstLineInBlock && !lineBox->isFirstAfterPageBreak()) {
+                ++numLinesInPreviousPage;
+                lineBox = lineBox->prevRootBox();
+            }
+
+            // If there was an explicit value for orphans, respect that. If not, we still
+            // shouldn't create a situation where we make an orphan bigger than the initial value.
+            // This means that setting widows implies we also care about orphans, but given
+            // the specification says the initial orphan value is non-zero, this is ok. The
+            // author is always free to set orphans explicitly as well.
+            int orphans = style()->hasAutoOrphans() ? style()->initialOrphans() : style()->orphans();
+            int numLinesAvailable = numLinesInPreviousPage - orphans;
+            if (numLinesAvailable <= 0)
+                return;
+
+            int numLinesToTake = min(numLinesAvailable, numLinesNeeded);
+            // Wind back from our first widowed line.
+            lineBox = currentFirstLineOfNewPage;
+            for (int i = 0; i < numLinesToTake; ++i)
+                lineBox = lineBox->prevRootBox();
+
+            // We now want to break at this line. Remember for next layout and trigger relayout.
+            setBreakAtLineToAvoidWidow(lineBox);
+            markLinesDirtyInBlockRange(lastRootBox()->lineBottomWithLeading(), lineBox->lineBottomWithLeading(), lineBox);
+        }
+    }
 }
 
 void RenderBlock::linkToEndLineIfNeeded(LineLayoutState& layoutState)
index c0f8f1c..6e9730e 100644 (file)
@@ -706,6 +706,8 @@ public:
 
     short widows() const { return rareInheritedData->widows; }
     short orphans() const { return rareInheritedData->orphans; }
+    bool hasAutoWidows() const { return rareInheritedData->m_hasAutoWidows; }
+    bool hasAutoOrphans() const { return rareInheritedData->m_hasAutoOrphans; }
     EPageBreak pageBreakInside() const { return static_cast<EPageBreak>(noninherited_flags._page_break_inside); }
     EPageBreak pageBreakBefore() const { return static_cast<EPageBreak>(noninherited_flags._page_break_before); }
     EPageBreak pageBreakAfter() const { return static_cast<EPageBreak>(noninherited_flags._page_break_after); }
@@ -1198,8 +1200,12 @@ public:
     int zIndex() const { return m_box->zIndex(); }
     void setZIndex(int v) { SET_VAR(m_box, m_hasAutoZIndex, false); SET_VAR(m_box, m_zIndex, v) }
 
-    void setWidows(short w) { SET_VAR(rareInheritedData, widows, w); }
-    void setOrphans(short o) { SET_VAR(rareInheritedData, orphans, o); }
+    void setHasAutoWidows() { SET_VAR(rareInheritedData, m_hasAutoWidows, true); SET_VAR(rareInheritedData, widows, initialWidows()) }
+    void setWidows(short w) { SET_VAR(rareInheritedData, m_hasAutoWidows, false); SET_VAR(rareInheritedData, widows, w); }
+
+    void setHasAutoOrphans() { SET_VAR(rareInheritedData, m_hasAutoOrphans, true); SET_VAR(rareInheritedData, orphans, initialOrphans()) }
+    void setOrphans(short o) { SET_VAR(rareInheritedData, m_hasAutoOrphans, false); SET_VAR(rareInheritedData, orphans, o); }
+
     // For valid values of page-break-inside see http://www.w3.org/TR/CSS21/page.html#page-break-props
     void setPageBreakInside(EPageBreak b) { ASSERT(b == PBAUTO || b == PBAVOID); noninherited_flags._page_break_inside = b; }
     void setPageBreakBefore(EPageBreak b) { noninherited_flags._page_break_before = b; }
index ea10aeb..1d11673 100644 (file)
@@ -70,6 +70,8 @@ StyleRareInheritedData::StyleRareInheritedData()
     , m_effectiveZoom(RenderStyle::initialZoom())
     , widows(RenderStyle::initialWidows())
     , orphans(RenderStyle::initialOrphans())
+    , m_hasAutoWidows(true)
+    , m_hasAutoOrphans(true)
     , textSecurity(RenderStyle::initialTextSecurity())
     , userModify(READ_ONLY)
     , wordBreak(RenderStyle::initialWordBreak())
@@ -137,6 +139,8 @@ StyleRareInheritedData::StyleRareInheritedData(const StyleRareInheritedData& o)
     , m_effectiveZoom(o.m_effectiveZoom)
     , widows(o.widows)
     , orphans(o.orphans)
+    , m_hasAutoWidows(o.m_hasAutoWidows)
+    , m_hasAutoOrphans(o.m_hasAutoOrphans)
     , textSecurity(o.textSecurity)
     , userModify(o.userModify)
     , wordBreak(o.wordBreak)
@@ -222,6 +226,8 @@ bool StyleRareInheritedData::operator==(const StyleRareInheritedData& o) const
         && m_effectiveZoom == o.m_effectiveZoom
         && widows == o.widows
         && orphans == o.orphans
+        && m_hasAutoWidows == o.m_hasAutoWidows
+        && m_hasAutoOrphans == o.m_hasAutoOrphans
         && textSecurity == o.textSecurity
         && userModify == o.userModify
         && wordBreak == o.wordBreak
index 1df0352..54c9fd2 100644 (file)
@@ -82,6 +82,8 @@ public:
     // Paged media properties.
     short widows;
     short orphans;
+    unsigned m_hasAutoWidows : 1;
+    unsigned m_hasAutoOrphans : 1;
     
     unsigned textSecurity : 2; // ETextSecurity
     unsigned userModify : 2; // EUserModify (editing)