From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- layout/tables/BasicTableLayoutStrategy.cpp | 1060 +++ layout/tables/BasicTableLayoutStrategy.h | 81 + layout/tables/FixedTableLayoutStrategy.cpp | 422 ++ layout/tables/FixedTableLayoutStrategy.h | 40 + layout/tables/SpanningCellSorter.cpp | 160 + layout/tables/SpanningCellSorter.h | 91 + layout/tables/TableArea.h | 41 + layout/tables/celldata.h | 459 ++ layout/tables/crashtests/1027611-1.html | 24 + layout/tables/crashtests/1031934.html | 18 + layout/tables/crashtests/110523-1.html | 45 + layout/tables/crashtests/1183896.html | 25 + layout/tables/crashtests/1223232.html | 6 + layout/tables/crashtests/1223282.html | 11 + layout/tables/crashtests/1243623-1.html | 27 + layout/tables/crashtests/138725-1.html | 32 + layout/tables/crashtests/159359-1.html | 13 + layout/tables/crashtests/187779-1.html | 19 + layout/tables/crashtests/189751-1.html | 3 + layout/tables/crashtests/197015-1.html | 10 + layout/tables/crashtests/220536-1.html | 93 + layout/tables/crashtests/223458-1.html | 25 + layout/tables/crashtests/237421-1.html | 16 + layout/tables/crashtests/237421-2.html | 47 + layout/tables/crashtests/238909-1.html | 8 + layout/tables/crashtests/239294-1.html | 38 + layout/tables/crashtests/240854-1.html | 40 + layout/tables/crashtests/266015-1.html | 18 + layout/tables/crashtests/267418.html | 122 + layout/tables/crashtests/275625.html | 8 + layout/tables/crashtests/277062-1.html | 4 + layout/tables/crashtests/278385-1.html | 25 + layout/tables/crashtests/282175-1.html | 26 + layout/tables/crashtests/284844-1.html | 13 + layout/tables/crashtests/284852.html | 130 + layout/tables/crashtests/28933-1.html | 10 + layout/tables/crashtests/29157-1.html | 28 + layout/tables/crashtests/300912.html | 19 + layout/tables/crashtests/308752-1-inner.html | 43 + layout/tables/crashtests/308752-1.html | 9 + layout/tables/crashtests/308752-2-inner.html | 35 + layout/tables/crashtests/308752-2.html | 9 + layout/tables/crashtests/316636-1.html | 19 + layout/tables/crashtests/317876.html | 16 + layout/tables/crashtests/322779-1.xul | 8 + layout/tables/crashtests/323489-1.html | 22 + layout/tables/crashtests/323604-1.html | 10 + layout/tables/crashtests/323604-2.xhtml | 43 + layout/tables/crashtests/32447-1.html | 13 + layout/tables/crashtests/331344-1.html | 11 + layout/tables/crashtests/331446-1.xhtml | 42 + layout/tables/crashtests/331690-1.html | 38 + layout/tables/crashtests/339130-1.html | 37 + layout/tables/crashtests/339246-1.html | 32 + layout/tables/crashtests/339315-1.html | 38 + layout/tables/crashtests/341227-1.xhtml | 30 + layout/tables/crashtests/343087-1.html | 40 + layout/tables/crashtests/343588-1.xhtml | 35 + layout/tables/crashtests/344000-1.html | 45 + layout/tables/crashtests/347367.html | 78 + layout/tables/crashtests/347506-1.xhtml | 23 + layout/tables/crashtests/347506-2.xhtml | 14 + layout/tables/crashtests/347725-1.xhtml | 39 + layout/tables/crashtests/348977-1.xhtml | 7 + layout/tables/crashtests/350524-1.xhtml | 33 + layout/tables/crashtests/351326-1.xhtml | 14 + layout/tables/crashtests/351327-1.xhtml | 21 + layout/tables/crashtests/351328-1.xhtml | 26 + layout/tables/crashtests/351628-1.xhtml | 14 + layout/tables/crashtests/358679-1.xhtml | 31 + layout/tables/crashtests/358871-1.xhtml | 19 + layout/tables/crashtests/362275.html | 14 + layout/tables/crashtests/364512-1.html | 20 + layout/tables/crashtests/366556-1.xhtml | 50 + layout/tables/crashtests/367673-1.xhtml | 38 + layout/tables/crashtests/367749.html | 14 + layout/tables/crashtests/367755.xhtml | 24 + layout/tables/crashtests/368013.html | 13 + layout/tables/crashtests/368166-1.xhtml | 21 + layout/tables/crashtests/370360-1.html | 34 + layout/tables/crashtests/370710.xhtml | 39 + layout/tables/crashtests/370713-1.html | 13 + layout/tables/crashtests/370876-1.html | 41 + layout/tables/crashtests/370897-1.html | 45 + layout/tables/crashtests/371290.html | 33 + layout/tables/crashtests/373400-1.html | 34 + layout/tables/crashtests/373400-2.html | 2109 ++++++ layout/tables/crashtests/373400-3.html | 64 + layout/tables/crashtests/373611-1.html | 22 + layout/tables/crashtests/373946-1.html | 6 + layout/tables/crashtests/374356-1.html | 28 + layout/tables/crashtests/374819-1.html | 16 + layout/tables/crashtests/374819-2.html | 16 + layout/tables/crashtests/375058-1.xhtml | 10 + layout/tables/crashtests/378240-1.html | 12 + layout/tables/crashtests/379687-1.html | 14 + layout/tables/crashtests/380200-1.xhtml | 24 + layout/tables/crashtests/385132-1.xhtml | 21 + layout/tables/crashtests/385132-2.html | 17 + layout/tables/crashtests/387051-1.html | 15 + layout/tables/crashtests/388700-1.html | 34 + layout/tables/crashtests/391898-1.html | 19 + layout/tables/crashtests/391901-1.html | 16 + layout/tables/crashtests/392132-1.xhtml | 9 + layout/tables/crashtests/397448-1.html | 7 + layout/tables/crashtests/398157-1.xhtml | 5 + layout/tables/crashtests/399209-1.xhtml | 15 + layout/tables/crashtests/403249-1.html | 20 + layout/tables/crashtests/403579-1.html | 12 + layout/tables/crashtests/404301-1.xhtml | 21 + layout/tables/crashtests/408753-1.xhtml | 1 + layout/tables/crashtests/410426-1.html | 16 + layout/tables/crashtests/410428-1.xhtml | 9 + layout/tables/crashtests/411582.xhtml | 6 + layout/tables/crashtests/413091.xhtml | 7 + layout/tables/crashtests/413180-1.html | 17 + layout/tables/crashtests/416845-1.xhtml | 7 + layout/tables/crashtests/416845-2.xhtml | 15 + layout/tables/crashtests/416845-3.html | 38 + layout/tables/crashtests/420242-1.xhtml | 4 + layout/tables/crashtests/420654-1.xhtml | 27 + layout/tables/crashtests/423514-1.xhtml | 35 + layout/tables/crashtests/430374.html | 31 + layout/tables/crashtests/444431-1.html | 26 + layout/tables/crashtests/444702-1.html | 5 + layout/tables/crashtests/448988-1.xhtml | 32 + layout/tables/crashtests/450311-1.html | 23 + layout/tables/crashtests/451170.html | 21 + layout/tables/crashtests/451355-1.html | 5 + layout/tables/crashtests/456041.html | 19 + layout/tables/crashtests/457115.html | 7 + layout/tables/crashtests/460637-1.xhtml | 41 + layout/tables/crashtests/460637-2.xhtml | 24 + layout/tables/crashtests/460637-3.xhtml | 26 + layout/tables/crashtests/462849.xhtml | 20 + layout/tables/crashtests/467141-1.html | 8 + layout/tables/crashtests/488388-1.html | 21 + layout/tables/crashtests/501870-1.html | 1 + layout/tables/crashtests/509562-1.xhtml | 18 + layout/tables/crashtests/512749-1.html | 1 + layout/tables/crashtests/513732-1.html | 7 + layout/tables/crashtests/533380-1.xhtml | 1 + layout/tables/crashtests/534716-1.html | 18 + layout/tables/crashtests/55789-1.html | 13 + layout/tables/crashtests/563009-1.html | 42 + layout/tables/crashtests/563009-2.html | 40 + layout/tables/crashtests/563009-3.html | 34 + layout/tables/crashtests/573354-1.xhtml | 14 + layout/tables/crashtests/576890-1.html | 8 + layout/tables/crashtests/576890-2.html | 8 + layout/tables/crashtests/576890-3.html | 8 + layout/tables/crashtests/580481-1.xhtml | 23 + layout/tables/crashtests/595758-1.xhtml | 13 + layout/tables/crashtests/595758-2.xhtml | 12 + layout/tables/crashtests/678447-1.html | 10 + layout/tables/crashtests/691824-1.xhtml | 279 + layout/tables/crashtests/695430-1.html | 23 + layout/tables/crashtests/696640-1.html | 47 + layout/tables/crashtests/696640-2.html | 486 ++ layout/tables/crashtests/705996-1.html | 6 + layout/tables/crashtests/705996-2.html | 6 + layout/tables/crashtests/707622-1.html | 6 + layout/tables/crashtests/710098-1.html | 7 + layout/tables/crashtests/711864-1.html | 15 + layout/tables/crashtests/759249-1.html | 6 + layout/tables/crashtests/759249-2.html | 10 + layout/tables/crashtests/78623-1.html | 17 + layout/tables/crashtests/814713.html | 96 + layout/tables/crashtests/crashtests.list | 159 + layout/tables/moz.build | 46 + layout/tables/nsCellMap.cpp | 2716 +++++++ layout/tables/nsCellMap.h | 668 ++ layout/tables/nsITableCellLayout.h | 35 + layout/tables/nsITableLayoutStrategy.h | 59 + layout/tables/nsTableCellFrame.cpp | 1238 ++++ layout/tables/nsTableCellFrame.h | 357 + layout/tables/nsTableColFrame.cpp | 213 + layout/tables/nsTableColFrame.h | 343 + layout/tables/nsTableColGroupFrame.cpp | 524 ++ layout/tables/nsTableColGroupFrame.h | 256 + layout/tables/nsTableFrame.cpp | 7536 ++++++++++++++++++++ layout/tables/nsTableFrame.h | 997 +++ layout/tables/nsTablePainter.cpp | 696 ++ layout/tables/nsTablePainter.h | 268 + layout/tables/nsTableRowFrame.cpp | 1517 ++++ layout/tables/nsTableRowFrame.h | 437 ++ layout/tables/nsTableRowGroupFrame.cpp | 2019 ++++++ layout/tables/nsTableRowGroupFrame.h | 450 ++ layout/tables/nsTableWrapperFrame.cpp | 1101 +++ layout/tables/nsTableWrapperFrame.h | 305 + layout/tables/reftests/1031934-ref.html | 27 + layout/tables/reftests/1031934.html | 54 + layout/tables/reftests/1220621-1-ref.html | 17 + layout/tables/reftests/1220621-1a.html | 32 + layout/tables/reftests/1220621-1b.html | 31 + layout/tables/reftests/1220621-1c.html | 30 + layout/tables/reftests/1220621-1d.html | 34 + layout/tables/reftests/1220621-1e.html | 34 + layout/tables/reftests/1220621-1f.html | 32 + layout/tables/reftests/1220621-2-ref.html | 21 + layout/tables/reftests/1220621-2a.html | 29 + layout/tables/reftests/1220621-2b.html | 32 + layout/tables/reftests/reftest-stylo.list | 10 + layout/tables/reftests/reftest.list | 9 + layout/tables/test/mochitest.ini | 4 + layout/tables/test/test_bug337124.html | 32 + .../test/test_bug541668_table_event_delivery.html | 49 + 207 files changed, 31299 insertions(+) create mode 100644 layout/tables/BasicTableLayoutStrategy.cpp create mode 100644 layout/tables/BasicTableLayoutStrategy.h create mode 100644 layout/tables/FixedTableLayoutStrategy.cpp create mode 100644 layout/tables/FixedTableLayoutStrategy.h create mode 100644 layout/tables/SpanningCellSorter.cpp create mode 100644 layout/tables/SpanningCellSorter.h create mode 100644 layout/tables/TableArea.h create mode 100644 layout/tables/celldata.h create mode 100644 layout/tables/crashtests/1027611-1.html create mode 100644 layout/tables/crashtests/1031934.html create mode 100644 layout/tables/crashtests/110523-1.html create mode 100644 layout/tables/crashtests/1183896.html create mode 100644 layout/tables/crashtests/1223232.html create mode 100644 layout/tables/crashtests/1223282.html create mode 100644 layout/tables/crashtests/1243623-1.html create mode 100644 layout/tables/crashtests/138725-1.html create mode 100644 layout/tables/crashtests/159359-1.html create mode 100644 layout/tables/crashtests/187779-1.html create mode 100644 layout/tables/crashtests/189751-1.html create mode 100644 layout/tables/crashtests/197015-1.html create mode 100644 layout/tables/crashtests/220536-1.html create mode 100644 layout/tables/crashtests/223458-1.html create mode 100644 layout/tables/crashtests/237421-1.html create mode 100644 layout/tables/crashtests/237421-2.html create mode 100644 layout/tables/crashtests/238909-1.html create mode 100644 layout/tables/crashtests/239294-1.html create mode 100644 layout/tables/crashtests/240854-1.html create mode 100644 layout/tables/crashtests/266015-1.html create mode 100644 layout/tables/crashtests/267418.html create mode 100644 layout/tables/crashtests/275625.html create mode 100644 layout/tables/crashtests/277062-1.html create mode 100644 layout/tables/crashtests/278385-1.html create mode 100644 layout/tables/crashtests/282175-1.html create mode 100644 layout/tables/crashtests/284844-1.html create mode 100644 layout/tables/crashtests/284852.html create mode 100644 layout/tables/crashtests/28933-1.html create mode 100644 layout/tables/crashtests/29157-1.html create mode 100644 layout/tables/crashtests/300912.html create mode 100644 layout/tables/crashtests/308752-1-inner.html create mode 100644 layout/tables/crashtests/308752-1.html create mode 100644 layout/tables/crashtests/308752-2-inner.html create mode 100644 layout/tables/crashtests/308752-2.html create mode 100644 layout/tables/crashtests/316636-1.html create mode 100644 layout/tables/crashtests/317876.html create mode 100644 layout/tables/crashtests/322779-1.xul create mode 100644 layout/tables/crashtests/323489-1.html create mode 100644 layout/tables/crashtests/323604-1.html create mode 100644 layout/tables/crashtests/323604-2.xhtml create mode 100644 layout/tables/crashtests/32447-1.html create mode 100644 layout/tables/crashtests/331344-1.html create mode 100644 layout/tables/crashtests/331446-1.xhtml create mode 100644 layout/tables/crashtests/331690-1.html create mode 100644 layout/tables/crashtests/339130-1.html create mode 100644 layout/tables/crashtests/339246-1.html create mode 100644 layout/tables/crashtests/339315-1.html create mode 100644 layout/tables/crashtests/341227-1.xhtml create mode 100644 layout/tables/crashtests/343087-1.html create mode 100644 layout/tables/crashtests/343588-1.xhtml create mode 100644 layout/tables/crashtests/344000-1.html create mode 100644 layout/tables/crashtests/347367.html create mode 100644 layout/tables/crashtests/347506-1.xhtml create mode 100644 layout/tables/crashtests/347506-2.xhtml create mode 100644 layout/tables/crashtests/347725-1.xhtml create mode 100644 layout/tables/crashtests/348977-1.xhtml create mode 100644 layout/tables/crashtests/350524-1.xhtml create mode 100644 layout/tables/crashtests/351326-1.xhtml create mode 100644 layout/tables/crashtests/351327-1.xhtml create mode 100644 layout/tables/crashtests/351328-1.xhtml create mode 100644 layout/tables/crashtests/351628-1.xhtml create mode 100644 layout/tables/crashtests/358679-1.xhtml create mode 100644 layout/tables/crashtests/358871-1.xhtml create mode 100644 layout/tables/crashtests/362275.html create mode 100644 layout/tables/crashtests/364512-1.html create mode 100644 layout/tables/crashtests/366556-1.xhtml create mode 100644 layout/tables/crashtests/367673-1.xhtml create mode 100644 layout/tables/crashtests/367749.html create mode 100644 layout/tables/crashtests/367755.xhtml create mode 100644 layout/tables/crashtests/368013.html create mode 100644 layout/tables/crashtests/368166-1.xhtml create mode 100644 layout/tables/crashtests/370360-1.html create mode 100644 layout/tables/crashtests/370710.xhtml create mode 100644 layout/tables/crashtests/370713-1.html create mode 100644 layout/tables/crashtests/370876-1.html create mode 100644 layout/tables/crashtests/370897-1.html create mode 100644 layout/tables/crashtests/371290.html create mode 100644 layout/tables/crashtests/373400-1.html create mode 100644 layout/tables/crashtests/373400-2.html create mode 100644 layout/tables/crashtests/373400-3.html create mode 100644 layout/tables/crashtests/373611-1.html create mode 100644 layout/tables/crashtests/373946-1.html create mode 100644 layout/tables/crashtests/374356-1.html create mode 100644 layout/tables/crashtests/374819-1.html create mode 100644 layout/tables/crashtests/374819-2.html create mode 100644 layout/tables/crashtests/375058-1.xhtml create mode 100644 layout/tables/crashtests/378240-1.html create mode 100644 layout/tables/crashtests/379687-1.html create mode 100644 layout/tables/crashtests/380200-1.xhtml create mode 100644 layout/tables/crashtests/385132-1.xhtml create mode 100644 layout/tables/crashtests/385132-2.html create mode 100644 layout/tables/crashtests/387051-1.html create mode 100644 layout/tables/crashtests/388700-1.html create mode 100644 layout/tables/crashtests/391898-1.html create mode 100644 layout/tables/crashtests/391901-1.html create mode 100644 layout/tables/crashtests/392132-1.xhtml create mode 100644 layout/tables/crashtests/397448-1.html create mode 100644 layout/tables/crashtests/398157-1.xhtml create mode 100644 layout/tables/crashtests/399209-1.xhtml create mode 100644 layout/tables/crashtests/403249-1.html create mode 100644 layout/tables/crashtests/403579-1.html create mode 100644 layout/tables/crashtests/404301-1.xhtml create mode 100644 layout/tables/crashtests/408753-1.xhtml create mode 100644 layout/tables/crashtests/410426-1.html create mode 100644 layout/tables/crashtests/410428-1.xhtml create mode 100644 layout/tables/crashtests/411582.xhtml create mode 100644 layout/tables/crashtests/413091.xhtml create mode 100644 layout/tables/crashtests/413180-1.html create mode 100644 layout/tables/crashtests/416845-1.xhtml create mode 100644 layout/tables/crashtests/416845-2.xhtml create mode 100644 layout/tables/crashtests/416845-3.html create mode 100644 layout/tables/crashtests/420242-1.xhtml create mode 100644 layout/tables/crashtests/420654-1.xhtml create mode 100644 layout/tables/crashtests/423514-1.xhtml create mode 100644 layout/tables/crashtests/430374.html create mode 100644 layout/tables/crashtests/444431-1.html create mode 100644 layout/tables/crashtests/444702-1.html create mode 100644 layout/tables/crashtests/448988-1.xhtml create mode 100644 layout/tables/crashtests/450311-1.html create mode 100644 layout/tables/crashtests/451170.html create mode 100644 layout/tables/crashtests/451355-1.html create mode 100644 layout/tables/crashtests/456041.html create mode 100644 layout/tables/crashtests/457115.html create mode 100644 layout/tables/crashtests/460637-1.xhtml create mode 100644 layout/tables/crashtests/460637-2.xhtml create mode 100644 layout/tables/crashtests/460637-3.xhtml create mode 100644 layout/tables/crashtests/462849.xhtml create mode 100644 layout/tables/crashtests/467141-1.html create mode 100644 layout/tables/crashtests/488388-1.html create mode 100644 layout/tables/crashtests/501870-1.html create mode 100644 layout/tables/crashtests/509562-1.xhtml create mode 100644 layout/tables/crashtests/512749-1.html create mode 100644 layout/tables/crashtests/513732-1.html create mode 100644 layout/tables/crashtests/533380-1.xhtml create mode 100644 layout/tables/crashtests/534716-1.html create mode 100644 layout/tables/crashtests/55789-1.html create mode 100644 layout/tables/crashtests/563009-1.html create mode 100644 layout/tables/crashtests/563009-2.html create mode 100644 layout/tables/crashtests/563009-3.html create mode 100644 layout/tables/crashtests/573354-1.xhtml create mode 100644 layout/tables/crashtests/576890-1.html create mode 100644 layout/tables/crashtests/576890-2.html create mode 100644 layout/tables/crashtests/576890-3.html create mode 100644 layout/tables/crashtests/580481-1.xhtml create mode 100644 layout/tables/crashtests/595758-1.xhtml create mode 100644 layout/tables/crashtests/595758-2.xhtml create mode 100644 layout/tables/crashtests/678447-1.html create mode 100644 layout/tables/crashtests/691824-1.xhtml create mode 100644 layout/tables/crashtests/695430-1.html create mode 100644 layout/tables/crashtests/696640-1.html create mode 100644 layout/tables/crashtests/696640-2.html create mode 100644 layout/tables/crashtests/705996-1.html create mode 100644 layout/tables/crashtests/705996-2.html create mode 100644 layout/tables/crashtests/707622-1.html create mode 100644 layout/tables/crashtests/710098-1.html create mode 100644 layout/tables/crashtests/711864-1.html create mode 100644 layout/tables/crashtests/759249-1.html create mode 100644 layout/tables/crashtests/759249-2.html create mode 100644 layout/tables/crashtests/78623-1.html create mode 100644 layout/tables/crashtests/814713.html create mode 100644 layout/tables/crashtests/crashtests.list create mode 100644 layout/tables/moz.build create mode 100644 layout/tables/nsCellMap.cpp create mode 100644 layout/tables/nsCellMap.h create mode 100644 layout/tables/nsITableCellLayout.h create mode 100644 layout/tables/nsITableLayoutStrategy.h create mode 100644 layout/tables/nsTableCellFrame.cpp create mode 100644 layout/tables/nsTableCellFrame.h create mode 100644 layout/tables/nsTableColFrame.cpp create mode 100644 layout/tables/nsTableColFrame.h create mode 100644 layout/tables/nsTableColGroupFrame.cpp create mode 100644 layout/tables/nsTableColGroupFrame.h create mode 100644 layout/tables/nsTableFrame.cpp create mode 100644 layout/tables/nsTableFrame.h create mode 100644 layout/tables/nsTablePainter.cpp create mode 100644 layout/tables/nsTablePainter.h create mode 100644 layout/tables/nsTableRowFrame.cpp create mode 100644 layout/tables/nsTableRowFrame.h create mode 100644 layout/tables/nsTableRowGroupFrame.cpp create mode 100644 layout/tables/nsTableRowGroupFrame.h create mode 100644 layout/tables/nsTableWrapperFrame.cpp create mode 100644 layout/tables/nsTableWrapperFrame.h create mode 100644 layout/tables/reftests/1031934-ref.html create mode 100644 layout/tables/reftests/1031934.html create mode 100644 layout/tables/reftests/1220621-1-ref.html create mode 100644 layout/tables/reftests/1220621-1a.html create mode 100644 layout/tables/reftests/1220621-1b.html create mode 100644 layout/tables/reftests/1220621-1c.html create mode 100644 layout/tables/reftests/1220621-1d.html create mode 100644 layout/tables/reftests/1220621-1e.html create mode 100644 layout/tables/reftests/1220621-1f.html create mode 100644 layout/tables/reftests/1220621-2-ref.html create mode 100644 layout/tables/reftests/1220621-2a.html create mode 100644 layout/tables/reftests/1220621-2b.html create mode 100644 layout/tables/reftests/reftest-stylo.list create mode 100644 layout/tables/reftests/reftest.list create mode 100644 layout/tables/test/mochitest.ini create mode 100644 layout/tables/test/test_bug337124.html create mode 100644 layout/tables/test/test_bug541668_table_event_delivery.html (limited to 'layout/tables') diff --git a/layout/tables/BasicTableLayoutStrategy.cpp b/layout/tables/BasicTableLayoutStrategy.cpp new file mode 100644 index 000000000..65835e68e --- /dev/null +++ b/layout/tables/BasicTableLayoutStrategy.cpp @@ -0,0 +1,1060 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// vim:cindent:ts=4:et:sw=4: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Web-compatible algorithms that determine column and table widths, + * used for CSS2's 'table-layout: auto'. + */ + +#include "BasicTableLayoutStrategy.h" + +#include + +#include "nsTableFrame.h" +#include "nsTableColFrame.h" +#include "nsTableCellFrame.h" +#include "nsLayoutUtils.h" +#include "nsGkAtoms.h" +#include "SpanningCellSorter.h" +#include "nsIContent.h" + +using namespace mozilla; +using namespace mozilla::layout; + +namespace css = mozilla::css; + +#undef DEBUG_TABLE_STRATEGY + +BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame) + : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto) + , mTableFrame(aTableFrame) +{ + MarkIntrinsicISizesDirty(); +} + +/* virtual */ +BasicTableLayoutStrategy::~BasicTableLayoutStrategy() +{ +} + +/* virtual */ nscoord +BasicTableLayoutStrategy::GetMinISize(nsRenderingContext* aRenderingContext) +{ + DISPLAY_MIN_WIDTH(mTableFrame, mMinISize); + if (mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) { + ComputeIntrinsicISizes(aRenderingContext); + } + return mMinISize; +} + +/* virtual */ nscoord +BasicTableLayoutStrategy::GetPrefISize(nsRenderingContext* aRenderingContext, + bool aComputingSize) +{ + DISPLAY_PREF_WIDTH(mTableFrame, mPrefISize); + NS_ASSERTION((mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) == + (mPrefISizePctExpand == NS_INTRINSIC_WIDTH_UNKNOWN), + "dirtyness out of sync"); + if (mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) { + ComputeIntrinsicISizes(aRenderingContext); + } + return aComputingSize ? mPrefISizePctExpand : mPrefISize; +} + +struct CellISizeInfo { + CellISizeInfo(nscoord aMinCoord, nscoord aPrefCoord, + float aPrefPercent, bool aHasSpecifiedISize) + : hasSpecifiedISize(aHasSpecifiedISize) + , minCoord(aMinCoord) + , prefCoord(aPrefCoord) + , prefPercent(aPrefPercent) + { + } + + bool hasSpecifiedISize; + nscoord minCoord; + nscoord prefCoord; + float prefPercent; +}; + +// Used for both column and cell calculations. The parts needed only +// for cells are skipped when aIsCell is false. +static CellISizeInfo +GetISizeInfo(nsRenderingContext *aRenderingContext, + nsIFrame *aFrame, WritingMode aWM, bool aIsCell) +{ + nscoord minCoord, prefCoord; + const nsStylePosition *stylePos = aFrame->StylePosition(); + bool isQuirks = aFrame->PresContext()->CompatibilityMode() == + eCompatibility_NavQuirks; + nscoord boxSizingToBorderEdge = 0; + if (aIsCell) { + // If aFrame is a container for font size inflation, then shrink + // wrapping inside of it should not apply font size inflation. + AutoMaybeDisableFontInflation an(aFrame); + + minCoord = aFrame->GetMinISize(aRenderingContext); + prefCoord = aFrame->GetPrefISize(aRenderingContext); + // Until almost the end of this function, minCoord and prefCoord + // represent the box-sizing based isize values (which mean they + // should include inline padding and border width when + // box-sizing is set to border-box). + // Note that this function returns border-box isize, we add the + // outer edges near the end of this function. + + // XXX Should we ignore percentage padding? + nsIFrame::IntrinsicISizeOffsetData offsets = + aFrame->IntrinsicISizeOffsets(); + + // In quirks mode, table cell isize should be content-box, + // but bsize should be border box. + // Because of this historic anomaly, we do not use quirk.css. + // (We can't specify one value of box-sizing for isize and another + // for bsize). + // For this reason, we also do not use box-sizing for just one of + // them, as this may be confusing. + if (isQuirks || stylePos->mBoxSizing == StyleBoxSizing::Content) { + boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder; + } + else { + // StyleBoxSizing::Border and standards-mode + minCoord += offsets.hPadding + offsets.hBorder; + prefCoord += offsets.hPadding + offsets.hBorder; + } + } else { + minCoord = 0; + prefCoord = 0; + } + float prefPercent = 0.0f; + bool hasSpecifiedISize = false; + + const nsStyleCoord& iSize = stylePos->ISize(aWM); + nsStyleUnit unit = iSize.GetUnit(); + // NOTE: We're ignoring calc() units with percentages here, for lack of a + // sensible idea for what to do with them. This means calc() with + // percentages is basically handled like 'auto' for table cells and + // columns. + if (iSize.ConvertsToLength()) { + hasSpecifiedISize = true; + // Note: since ComputeISizeValue was designed to return content-box + // isize, it will (in some cases) subtract the box-sizing edges. + // We prevent this unwanted behavior by calling it with + // aContentEdgeToBoxSizing and aBoxSizingToMarginEdge set to 0. + nscoord c = aFrame->ComputeISizeValue(aRenderingContext, 0, 0, 0, iSize); + // Quirk: A cell with "nowrap" set and a coord value for the + // isize which is bigger than the intrinsic minimum isize uses + // that coord value as the minimum isize. + // This is kept up-to-date with dynamic changes to nowrap by code in + // nsTableCellFrame::AttributeChanged + if (aIsCell && c > minCoord && isQuirks && + aFrame->GetContent()->HasAttr(kNameSpaceID_None, + nsGkAtoms::nowrap)) { + minCoord = c; + } + prefCoord = std::max(c, minCoord); + } else if (unit == eStyleUnit_Percent) { + prefPercent = iSize.GetPercentValue(); + } else if (unit == eStyleUnit_Enumerated && aIsCell) { + switch (iSize.GetIntValue()) { + case NS_STYLE_WIDTH_MAX_CONTENT: + // 'inline-size' only affects pref isize, not min + // isize, so don't change anything + break; + case NS_STYLE_WIDTH_MIN_CONTENT: + prefCoord = minCoord; + break; + case NS_STYLE_WIDTH_FIT_CONTENT: + case NS_STYLE_WIDTH_AVAILABLE: + // act just like 'inline-size: auto' + break; + default: + NS_NOTREACHED("unexpected enumerated value"); + } + } + + nsStyleCoord maxISize(stylePos->MaxISize(aWM)); + if (maxISize.GetUnit() == eStyleUnit_Enumerated) { + if (!aIsCell || maxISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) { + maxISize.SetNoneValue(); + } else if (maxISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) { + // for 'max-inline-size', '-moz-fit-content' is like + // '-moz-max-content' + maxISize.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT, + eStyleUnit_Enumerated); + } + } + unit = maxISize.GetUnit(); + // XXX To really implement 'max-inline-size' well, we'd need to store + // it separately on the columns. + if (maxISize.ConvertsToLength() || unit == eStyleUnit_Enumerated) { + nscoord c = aFrame->ComputeISizeValue(aRenderingContext, + 0, 0, 0, maxISize); + minCoord = std::min(c, minCoord); + prefCoord = std::min(c, prefCoord); + } else if (unit == eStyleUnit_Percent) { + float p = stylePos->MaxISize(aWM).GetPercentValue(); + if (p < prefPercent) { + prefPercent = p; + } + } + // treat calc() with percentages on max-inline-size just like 'none'. + + nsStyleCoord minISize(stylePos->MinISize(aWM)); + if (minISize.GetUnit() == eStyleUnit_Enumerated) { + if (!aIsCell || minISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) { + minISize.SetCoordValue(0); + } else if (minISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) { + // for 'min-inline-size', '-moz-fit-content' is like + // '-moz-min-content' + minISize.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT, + eStyleUnit_Enumerated); + } + } + unit = minISize.GetUnit(); + if (minISize.ConvertsToLength() || unit == eStyleUnit_Enumerated) { + nscoord c = aFrame->ComputeISizeValue(aRenderingContext, + 0, 0, 0, minISize); + minCoord = std::max(c, minCoord); + prefCoord = std::max(c, prefCoord); + } else if (unit == eStyleUnit_Percent) { + float p = stylePos->MinISize(aWM).GetPercentValue(); + if (p > prefPercent) { + prefPercent = p; + } + } + // treat calc() with percentages on min-inline-size just like '0'. + + // XXX Should col frame have border/padding considered? + if (aIsCell) { + minCoord += boxSizingToBorderEdge; + prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge); + } + + return CellISizeInfo(minCoord, prefCoord, prefPercent, hasSpecifiedISize); +} + +static inline CellISizeInfo +GetCellISizeInfo(nsRenderingContext *aRenderingContext, + nsTableCellFrame *aCellFrame, WritingMode aWM) +{ + return GetISizeInfo(aRenderingContext, aCellFrame, aWM, true); +} + +static inline CellISizeInfo +GetColISizeInfo(nsRenderingContext *aRenderingContext, + nsIFrame *aFrame, WritingMode aWM) +{ + return GetISizeInfo(aRenderingContext, aFrame, aWM, false); +} + + +/** + * The algorithm in this function, in addition to meeting the + * requirements of Web-compatibility, is also invariant under reordering + * of the rows within a table (something that most, but not all, other + * browsers are). + */ +void +BasicTableLayoutStrategy::ComputeColumnIntrinsicISizes(nsRenderingContext* aRenderingContext) +{ + nsTableFrame *tableFrame = mTableFrame; + nsTableCellMap *cellMap = tableFrame->GetCellMap(); + WritingMode wm = tableFrame->GetWritingMode(); + + mozilla::AutoStackArena arena; + SpanningCellSorter spanningCells; + + // Loop over the columns to consider the columns and cells *without* + // a colspan. + int32_t col, col_end; + for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { + nsTableColFrame *colFrame = tableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + colFrame->ResetIntrinsics(); + colFrame->ResetSpanIntrinsics(); + + // Consider the isizes on the column. + CellISizeInfo colInfo = GetColISizeInfo(aRenderingContext, + colFrame, wm); + colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, + colInfo.hasSpecifiedISize); + colFrame->AddPrefPercent(colInfo.prefPercent); + + // Consider the isizes on the column-group. Note that we follow + // what the HTML spec says here, and make the isize apply to + // each column in the group, not the group as a whole. + + // If column has isize, column-group doesn't override isize. + if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 && + colInfo.prefPercent == 0.0f) { + NS_ASSERTION(colFrame->GetParent()->GetType() == + nsGkAtoms::tableColGroupFrame, + "expected a column-group"); + colInfo = GetColISizeInfo(aRenderingContext, + colFrame->GetParent(), wm); + colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord, + colInfo.hasSpecifiedISize); + colFrame->AddPrefPercent(colInfo.prefPercent); + } + + // Consider the contents of and the isizes on the cells without + // colspans. + nsCellMapColumnIterator columnIter(cellMap, col); + int32_t row, colSpan; + nsTableCellFrame* cellFrame; + while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) { + if (colSpan > 1) { + spanningCells.AddCell(colSpan, row, col); + continue; + } + + CellISizeInfo info = GetCellISizeInfo(aRenderingContext, + cellFrame, wm); + + colFrame->AddCoords(info.minCoord, info.prefCoord, + info.hasSpecifiedISize); + colFrame->AddPrefPercent(info.prefPercent); + } +#ifdef DEBUG_dbaron_off + printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n", + mTableFrame, col, colFrame->GetMinCoord(), + colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(), + colFrame->GetPrefPercent()); +#endif + } +#ifdef DEBUG_TABLE_STRATEGY + printf("ComputeColumnIntrinsicISizes single\n"); + mTableFrame->Dump(false, true, false); +#endif + + // Consider the cells with a colspan that we saved in the loop above + // into the spanning cell sorter. We consider these cells by seeing + // if they require adding to the isizes resulting only from cells + // with a smaller colspan, and therefore we must process them sorted + // in increasing order by colspan. For each colspan group, we + // accumulate new values to accumulate in the column frame's Span* + // members. + // + // Considering things only relative to the isizes resulting from + // cells with smaller colspans (rather than incrementally including + // the results from spanning cells, or doing spanning and + // non-spanning cells in a single pass) means that layout remains + // row-order-invariant and (except for percentage isizes that add to + // more than 100%) column-order invariant. + // + // Starting with smaller colspans makes it more likely that we + // satisfy all the constraints given and don't distribute space to + // columns where we don't need it. + SpanningCellSorter::Item *item; + int32_t colSpan; + while ((item = spanningCells.GetNext(&colSpan))) { + NS_ASSERTION(colSpan > 1, + "cell should not have been put in spanning cell sorter"); + do { + int32_t row = item->row; + col = item->col; + CellData *cellData = cellMap->GetDataAt(row, col); + NS_ASSERTION(cellData && cellData->IsOrig(), + "bogus result from spanning cell sorter"); + + nsTableCellFrame *cellFrame = cellData->GetCellFrame(); + NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter"); + + CellISizeInfo info = + GetCellISizeInfo(aRenderingContext, cellFrame, wm); + + if (info.prefPercent > 0.0f) { + DistributePctISizeToColumns(info.prefPercent, + col, colSpan); + } + DistributeISizeToColumns(info.minCoord, col, colSpan, + BTLS_MIN_ISIZE, info.hasSpecifiedISize); + DistributeISizeToColumns(info.prefCoord, col, colSpan, + BTLS_PREF_ISIZE, info.hasSpecifiedISize); + } while ((item = item->next)); + + // Combine the results of the span analysis into the main results, + // for each increment of colspan. + + for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { + nsTableColFrame *colFrame = tableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + + colFrame->AccumulateSpanIntrinsics(); + colFrame->ResetSpanIntrinsics(); + +#ifdef DEBUG_dbaron_off + printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n", + mTableFrame, col, colSpan, colFrame->GetMinCoord(), + colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(), + colFrame->GetPrefPercent()); +#endif + } + } + + // Prevent percentages from adding to more than 100% by (to be + // compatible with other browsers) treating any percentages that would + // increase the total percentage to more than 100% as the number that + // would increase it to only 100% (which is 0% if we've already hit + // 100%). This means layout depends on the order of columns. + float pct_used = 0.0f; + for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) { + nsTableColFrame *colFrame = tableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + + colFrame->AdjustPrefPercent(&pct_used); + } + +#ifdef DEBUG_TABLE_STRATEGY + printf("ComputeColumnIntrinsicISizes spanning\n"); + mTableFrame->Dump(false, true, false); +#endif +} + +void +BasicTableLayoutStrategy::ComputeIntrinsicISizes(nsRenderingContext* aRenderingContext) +{ + ComputeColumnIntrinsicISizes(aRenderingContext); + + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); + nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0; + float pct_total = 0.0f; // always from 0.0f - 1.0f + int32_t colCount = cellMap->GetColCount(); + // add a total of (colcount + 1) lots of cellSpacingX for columns where a + // cell originates + nscoord add = mTableFrame->GetColSpacing(colCount); + + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (mTableFrame->ColumnHasCellSpacingBefore(col)) { + add += mTableFrame->GetColSpacing(col - 1); + } + min += colFrame->GetMinCoord(); + pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord()); + + // Percentages are of the table, so we have to reverse them for + // intrinsic isizes. + float p = colFrame->GetPrefPercent(); + if (p > 0.0f) { + nscoord colPref = colFrame->GetPrefCoord(); + nscoord new_small_pct_expand = + (colPref == nscoord_MAX ? + nscoord_MAX : nscoord(float(colPref) / p)); + if (new_small_pct_expand > max_small_pct_pref) { + max_small_pct_pref = new_small_pct_expand; + } + pct_total += p; + } else { + nonpct_pref_total = NSCoordSaturatingAdd(nonpct_pref_total, + colFrame->GetPrefCoord()); + } + } + + nscoord pref_pct_expand = pref; + + // Account for small percentages expanding the preferred isize of + // *other* columns. + if (max_small_pct_pref > pref_pct_expand) { + pref_pct_expand = max_small_pct_pref; + } + + // Account for large percentages expanding the preferred isize of + // themselves. There's no need to iterate over the columns multiple + // times, since when there is such a need, the small percentage + // effect is bigger anyway. (I think!) + NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f, + "column percentage inline-sizes not adjusted down to 100%"); + if (pct_total == 1.0f) { + if (nonpct_pref_total > 0) { + pref_pct_expand = nscoord_MAX; + // XXX Or should I use some smaller value? (Test this using + // nested tables!) + } + } else { + nscoord large_pct_pref = + (nonpct_pref_total == nscoord_MAX ? + nscoord_MAX : + nscoord(float(nonpct_pref_total) / (1.0f - pct_total))); + if (large_pct_pref > pref_pct_expand) + pref_pct_expand = large_pct_pref; + } + + // border-spacing isn't part of the basis for percentages + if (colCount > 0) { + min += add; + pref = NSCoordSaturatingAdd(pref, add); + pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add); + } + + mMinISize = min; + mPrefISize = pref; + mPrefISizePctExpand = pref_pct_expand; +} + +/* virtual */ void +BasicTableLayoutStrategy::MarkIntrinsicISizesDirty() +{ + mMinISize = NS_INTRINSIC_WIDTH_UNKNOWN; + mPrefISize = NS_INTRINSIC_WIDTH_UNKNOWN; + mPrefISizePctExpand = NS_INTRINSIC_WIDTH_UNKNOWN; + mLastCalcISize = nscoord_MIN; +} + +/* virtual */ void +BasicTableLayoutStrategy::ComputeColumnISizes(const ReflowInput& aReflowInput) +{ + nscoord iSize = aReflowInput.ComputedISize(); + + if (mLastCalcISize == iSize) { + return; + } + mLastCalcISize = iSize; + + NS_ASSERTION((mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) == + (mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN), + "dirtyness out of sync"); + NS_ASSERTION((mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) == + (mPrefISizePctExpand == NS_INTRINSIC_WIDTH_UNKNOWN), + "dirtyness out of sync"); + // XXX Is this needed? + if (mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) { + ComputeIntrinsicISizes(aReflowInput.mRenderingContext); + } + + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); + int32_t colCount = cellMap->GetColCount(); + if (colCount <= 0) + return; // nothing to do + + DistributeISizeToColumns(iSize, 0, colCount, BTLS_FINAL_ISIZE, false); + +#ifdef DEBUG_TABLE_STRATEGY + printf("ComputeColumnISizes final\n"); + mTableFrame->Dump(false, true, false); +#endif +} + +void +BasicTableLayoutStrategy::DistributePctISizeToColumns(float aSpanPrefPct, + int32_t aFirstCol, + int32_t aColCount) +{ + // First loop to determine: + int32_t nonPctColCount = 0; // number of spanned columns without % isize + nscoord nonPctTotalPrefISize = 0; // total pref isize of those columns + // and to reduce aSpanPrefPct by columns that already have % isize + + int32_t scol, scol_end; + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); + for (scol = aFirstCol, scol_end = aFirstCol + aColCount; + scol < scol_end; ++scol) { + nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol); + if (!scolFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + float scolPct = scolFrame->GetPrefPercent(); + if (scolPct == 0.0f) { + nonPctTotalPrefISize += scolFrame->GetPrefCoord(); + if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { + ++nonPctColCount; + } + } else { + aSpanPrefPct -= scolPct; + } + } + + if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) { + // There's no %-isize on the colspan left over to distribute, + // or there are no columns to which we could distribute %-isize + return; + } + + // Second loop, to distribute what remains of aSpanPrefPct + // between the non-percent-isize spanned columns + const bool spanHasNonPctPref = nonPctTotalPrefISize > 0; // Loop invariant + for (scol = aFirstCol, scol_end = aFirstCol + aColCount; + scol < scol_end; ++scol) { + nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol); + if (!scolFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + + if (scolFrame->GetPrefPercent() == 0.0f) { + NS_ASSERTION((!spanHasNonPctPref || + nonPctTotalPrefISize != 0) && + nonPctColCount != 0, + "should not be zero if we haven't allocated " + "all pref percent"); + + float allocatedPct; // % isize to be given to this column + if (spanHasNonPctPref) { + // Group so we're multiplying by 1.0f when we need + // to use up aSpanPrefPct. + allocatedPct = aSpanPrefPct * + (float(scolFrame->GetPrefCoord()) / + float(nonPctTotalPrefISize)); + } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { + // distribute equally when all pref isizes are 0 + allocatedPct = aSpanPrefPct / float(nonPctColCount); + } else { + allocatedPct = 0.0f; + } + // Allocate the percent + scolFrame->AddSpanPrefPercent(allocatedPct); + + // To avoid accumulating rounding error from division, + // subtract this column's values from the totals. + aSpanPrefPct -= allocatedPct; + nonPctTotalPrefISize -= scolFrame->GetPrefCoord(); + if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) { + --nonPctColCount; + } + + if (!aSpanPrefPct) { + // No more span-percent-isize to distribute --> we're done. + NS_ASSERTION(spanHasNonPctPref ? + nonPctTotalPrefISize == 0 : + nonPctColCount == 0, + "No more pct inline-size to distribute, " + "but there are still cols that need some."); + return; + } + } + } +} + +void +BasicTableLayoutStrategy::DistributeISizeToColumns(nscoord aISize, + int32_t aFirstCol, + int32_t aColCount, + BtlsISizeType aISizeType, + bool aSpanHasSpecifiedISize) +{ + NS_ASSERTION(aISizeType != BTLS_FINAL_ISIZE || + (aFirstCol == 0 && + aColCount == mTableFrame->GetCellMap()->GetColCount()), + "Computing final column isizes, but didn't get full column range"); + + nscoord subtract = 0; + // aISize initially includes border-spacing for the boundaries in between + // each of the columns. We start at aFirstCol + 1 because the first + // in-between boundary would be at the left edge of column aFirstCol + 1 + for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) { + if (mTableFrame->ColumnHasCellSpacingBefore(col)) { + // border-spacing isn't part of the basis for percentages. + subtract += mTableFrame->GetColSpacing(col - 1); + } + } + if (aISizeType == BTLS_FINAL_ISIZE) { + // If we're computing final col-isize, then aISize initially includes + // border spacing on the table's far istart + far iend edge, too. Need + // to subtract those out, too. + subtract += (mTableFrame->GetColSpacing(-1) + + mTableFrame->GetColSpacing(aColCount)); + } + aISize = NSCoordSaturatingSubtract(aISize, subtract, nscoord_MAX); + + /* + * The goal of this function is to distribute |aISize| between the + * columns by making an appropriate AddSpanCoords or SetFinalISize + * call for each column. (We call AddSpanCoords if we're + * distributing a column-spanning cell's minimum or preferred isize + * to its spanned columns. We call SetFinalISize if we're + * distributing a table's final isize to its columns.) + * + * The idea is to either assign one of the following sets of isizes + * or a weighted average of two adjacent sets of isizes. It is not + * possible to assign values smaller than the smallest set of + * isizes. However, see below for handling the case of assigning + * values larger than the largest set of isizes. From smallest to + * largest, these are: + * + * 1. [guess_min] Assign all columns their min isize. + * + * 2. [guess_min_pct] Assign all columns with percentage isizes + * their percentage isize, and all other columns their min isize. + * + * 3. [guess_min_spec] Assign all columns with percentage isizes + * their percentage isize, all columns with specified coordinate + * isizes their pref isize (since it doesn't matter whether it's the + * largest contributor to the pref isize that was the specified + * contributor), and all other columns their min isize. + * + * 4. [guess_pref] Assign all columns with percentage isizes their + * specified isize, and all other columns their pref isize. + * + * If |aISize| is *larger* than what we would assign in (4), then we + * expand the columns: + * + * a. if any columns without a specified coordinate isize or + * percent isize have nonzero pref isize, in proportion to pref + * isize [total_flex_pref] + * + * b. otherwise, if any columns without a specified coordinate + * isize or percent isize, but with cells originating in them, + * have zero pref isize, equally between these + * [numNonSpecZeroISizeCols] + * + * c. otherwise, if any columns without percent isize have nonzero + * pref isize, in proportion to pref isize [total_fixed_pref] + * + * d. otherwise, if any columns have nonzero percentage isizes, in + * proportion to the percentage isizes [total_pct] + * + * e. otherwise, equally. + */ + + // Loop #1 over the columns, to figure out the four values above so + // we know which case we're dealing with. + + nscoord guess_min = 0, + guess_min_pct = 0, + guess_min_spec = 0, + guess_pref = 0, + total_flex_pref = 0, + total_fixed_pref = 0; + float total_pct = 0.0f; // 0.0f to 1.0f + int32_t numInfiniteISizeCols = 0; + int32_t numNonSpecZeroISizeCols = 0; + + int32_t col; + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); + for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord min_iSize = colFrame->GetMinCoord(); + guess_min += min_iSize; + if (colFrame->GetPrefPercent() != 0.0f) { + float pct = colFrame->GetPrefPercent(); + total_pct += pct; + nscoord val = nscoord(float(aISize) * pct); + if (val < min_iSize) { + val = min_iSize; + } + guess_min_pct += val; + guess_pref = NSCoordSaturatingAdd(guess_pref, val); + } else { + nscoord pref_iSize = colFrame->GetPrefCoord(); + if (pref_iSize == nscoord_MAX) { + ++numInfiniteISizeCols; + } + guess_pref = NSCoordSaturatingAdd(guess_pref, pref_iSize); + guess_min_pct += min_iSize; + if (colFrame->GetHasSpecifiedCoord()) { + // we'll add on the rest of guess_min_spec outside the + // loop + nscoord delta = NSCoordSaturatingSubtract(pref_iSize, + min_iSize, 0); + guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta); + total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref, + pref_iSize); + } else if (pref_iSize == 0) { + if (cellMap->GetNumCellsOriginatingInCol(col) > 0) { + ++numNonSpecZeroISizeCols; + } + } else { + total_flex_pref = NSCoordSaturatingAdd(total_flex_pref, + pref_iSize); + } + } + } + guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct); + + // Determine what we're flexing: + enum Loop2Type { + FLEX_PCT_SMALL, // between (1) and (2) above + FLEX_FIXED_SMALL, // between (2) and (3) above + FLEX_FLEX_SMALL, // between (3) and (4) above + FLEX_FLEX_LARGE, // greater than (4) above, case (a) + FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b) + FLEX_FIXED_LARGE, // greater than (4) above, case (c) + FLEX_PCT_LARGE, // greater than (4) above, case (d) + FLEX_ALL_LARGE // greater than (4) above, case (e) + }; + + Loop2Type l2t; + // These are constants (over columns) for each case's math. We use + // a pair of nscoords rather than a float so that we can subtract + // each column's allocation so we avoid accumulating rounding error. + nscoord space; // the amount of extra isize to allocate + union { + nscoord c; + float f; + } basis; // the sum of the statistic over columns to divide it + if (aISize < guess_pref) { + if (aISizeType != BTLS_FINAL_ISIZE && aISize <= guess_min) { + // Return early -- we don't have any extra space to distribute. + return; + } + NS_ASSERTION(!(aISizeType == BTLS_FINAL_ISIZE && aISize < guess_min), + "Table inline-size is less than the " + "sum of its columns' min inline-sizes"); + if (aISize < guess_min_pct) { + l2t = FLEX_PCT_SMALL; + space = aISize - guess_min; + basis.c = guess_min_pct - guess_min; + } else if (aISize < guess_min_spec) { + l2t = FLEX_FIXED_SMALL; + space = aISize - guess_min_pct; + basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct, + nscoord_MAX); + } else { + l2t = FLEX_FLEX_SMALL; + space = aISize - guess_min_spec; + basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec, + nscoord_MAX); + } + } else { + space = NSCoordSaturatingSubtract(aISize, guess_pref, nscoord_MAX); + if (total_flex_pref > 0) { + l2t = FLEX_FLEX_LARGE; + basis.c = total_flex_pref; + } else if (numNonSpecZeroISizeCols > 0) { + l2t = FLEX_FLEX_LARGE_ZERO; + basis.c = numNonSpecZeroISizeCols; + } else if (total_fixed_pref > 0) { + l2t = FLEX_FIXED_LARGE; + basis.c = total_fixed_pref; + } else if (total_pct > 0.0f) { + l2t = FLEX_PCT_LARGE; + basis.f = total_pct; + } else { + l2t = FLEX_ALL_LARGE; + basis.c = aColCount; + } + } + +#ifdef DEBUG_dbaron_off + printf("ComputeColumnISizes: %d columns in isize %d,\n" + " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n" + " l2t=%d, space=%d, basis.c=%d\n", + aColCount, aISize, + guess_min, guess_min_pct, guess_min_spec, guess_pref, + total_flex_pref, total_fixed_pref, total_pct, + l2t, space, basis.c); +#endif + + for (col = aFirstCol; col < aFirstCol + aColCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord col_iSize; + + float pct = colFrame->GetPrefPercent(); + if (pct != 0.0f) { + col_iSize = nscoord(float(aISize) * pct); + nscoord col_min = colFrame->GetMinCoord(); + if (col_iSize < col_min) { + col_iSize = col_min; + } + } else { + col_iSize = colFrame->GetPrefCoord(); + } + + nscoord col_iSize_before_adjust = col_iSize; + + switch (l2t) { + case FLEX_PCT_SMALL: + col_iSize = col_iSize_before_adjust = colFrame->GetMinCoord(); + if (pct != 0.0f) { + nscoord pct_minus_min = + nscoord(float(aISize) * pct) - col_iSize; + if (pct_minus_min > 0) { + float c = float(space) / float(basis.c); + basis.c -= pct_minus_min; + col_iSize += NSToCoordRound(float(pct_minus_min) * c); + } + } + break; + case FLEX_FIXED_SMALL: + if (pct == 0.0f) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + if (colFrame->GetHasSpecifiedCoord()) { + nscoord col_min = colFrame->GetMinCoord(); + nscoord pref_minus_min = col_iSize - col_min; + col_iSize = col_iSize_before_adjust = col_min; + if (pref_minus_min != 0) { + float c = float(space) / float(basis.c); + basis.c -= pref_minus_min; + col_iSize += NSToCoordRound( + float(pref_minus_min) * c); + } + } else + col_iSize = col_iSize_before_adjust = + colFrame->GetMinCoord(); + } + break; + case FLEX_FLEX_SMALL: + if (pct == 0.0f && + !colFrame->GetHasSpecifiedCoord()) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + nscoord col_min = colFrame->GetMinCoord(); + nscoord pref_minus_min = + NSCoordSaturatingSubtract(col_iSize, col_min, 0); + col_iSize = col_iSize_before_adjust = col_min; + if (pref_minus_min != 0) { + float c = float(space) / float(basis.c); + // If we have infinite-isize cols, then the standard + // adjustment to col_iSize using 'c' won't work, + // because basis.c and pref_minus_min are both + // nscoord_MAX and will cancel each other out in the + // col_iSize adjustment (making us assign all the + // space to the first inf-isize col). To correct for + // this, we'll also divide by numInfiniteISizeCols to + // spread the space equally among the inf-isize cols. + if (numInfiniteISizeCols) { + if (colFrame->GetPrefCoord() == nscoord_MAX) { + c = c / float(numInfiniteISizeCols); + --numInfiniteISizeCols; + } else { + c = 0.0f; + } + } + basis.c = NSCoordSaturatingSubtract(basis.c, + pref_minus_min, + nscoord_MAX); + col_iSize += NSToCoordRound( + float(pref_minus_min) * c); + } + } + break; + case FLEX_FLEX_LARGE: + if (pct == 0.0f && + !colFrame->GetHasSpecifiedCoord()) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + if (col_iSize != 0) { + if (space == nscoord_MAX) { + basis.c -= col_iSize; + col_iSize = nscoord_MAX; + } else { + float c = float(space) / float(basis.c); + basis.c -= col_iSize; + col_iSize += NSToCoordRound(float(col_iSize) * c); + } + } + } + break; + case FLEX_FLEX_LARGE_ZERO: + if (pct == 0.0f && + !colFrame->GetHasSpecifiedCoord() && + cellMap->GetNumCellsOriginatingInCol(col) > 0) { + + NS_ASSERTION(col_iSize == 0 && + colFrame->GetPrefCoord() == 0, + "Since we're in FLEX_FLEX_LARGE_ZERO case, " + "all auto-inline-size cols should have zero " + "pref inline-size."); + float c = float(space) / float(basis.c); + col_iSize += NSToCoordRound(c); + --basis.c; + } + break; + case FLEX_FIXED_LARGE: + if (pct == 0.0f) { + NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(), + "wrong inline-size assigned"); + NS_ASSERTION(colFrame->GetHasSpecifiedCoord() || + colFrame->GetPrefCoord() == 0, + "wrong case"); + if (col_iSize != 0) { + float c = float(space) / float(basis.c); + basis.c -= col_iSize; + col_iSize += NSToCoordRound(float(col_iSize) * c); + } + } + break; + case FLEX_PCT_LARGE: + NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0, + "wrong case"); + if (pct != 0.0f) { + float c = float(space) / basis.f; + col_iSize += NSToCoordRound(pct * c); + basis.f -= pct; + } + break; + case FLEX_ALL_LARGE: + { + float c = float(space) / float(basis.c); + col_iSize += NSToCoordRound(c); + --basis.c; + } + break; + } + + // Only subtract from space if it's a real number. + if (space != nscoord_MAX) { + NS_ASSERTION(col_iSize != nscoord_MAX, + "How is col_iSize nscoord_MAX if space isn't?"); + NS_ASSERTION(col_iSize_before_adjust != nscoord_MAX, + "How is col_iSize_before_adjust nscoord_MAX if space isn't?"); + space -= col_iSize - col_iSize_before_adjust; + } + + NS_ASSERTION(col_iSize >= colFrame->GetMinCoord(), + "assigned inline-size smaller than min"); + + // Apply the new isize + switch (aISizeType) { + case BTLS_MIN_ISIZE: + { + // Note: AddSpanCoords requires both a min and pref isize. + // For the pref isize, we'll just pass in our computed + // min isize, because the real pref isize will be at least + // as big + colFrame->AddSpanCoords(col_iSize, col_iSize, + aSpanHasSpecifiedISize); + } + break; + case BTLS_PREF_ISIZE: + { + // Note: AddSpanCoords requires both a min and pref isize. + // For the min isize, we'll just pass in 0, because + // the real min isize will be at least 0 + colFrame->AddSpanCoords(0, col_iSize, + aSpanHasSpecifiedISize); + } + break; + case BTLS_FINAL_ISIZE: + { + nscoord old_final = colFrame->GetFinalISize(); + colFrame->SetFinalISize(col_iSize); + + if (old_final != col_iSize) { + mTableFrame->DidResizeColumns(); + } + } + break; + } + } + NS_ASSERTION((space == 0 || space == nscoord_MAX) && + ((l2t == FLEX_PCT_LARGE) + ? (-0.001f < basis.f && basis.f < 0.001f) + : (basis.c == 0 || basis.c == nscoord_MAX)), + "didn't subtract all that we added"); +} diff --git a/layout/tables/BasicTableLayoutStrategy.h b/layout/tables/BasicTableLayoutStrategy.h new file mode 100644 index 000000000..082da8fb3 --- /dev/null +++ b/layout/tables/BasicTableLayoutStrategy.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// vim:cindent:ts=4:et:sw=4: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Web-compatible algorithms that determine column and table isizes, + * used for CSS2's 'table-layout: auto'. + */ + +#ifndef BasicTableLayoutStrategy_h_ +#define BasicTableLayoutStrategy_h_ + +#include "mozilla/Attributes.h" +#include "nsITableLayoutStrategy.h" + +class nsTableFrame; + +class BasicTableLayoutStrategy : public nsITableLayoutStrategy +{ +public: + explicit BasicTableLayoutStrategy(nsTableFrame *aTableFrame); + virtual ~BasicTableLayoutStrategy(); + + // nsITableLayoutStrategy implementation + virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext, + bool aComputingSize) override; + virtual void MarkIntrinsicISizesDirty() override; + virtual void ComputeColumnISizes(const ReflowInput& aReflowInput) override; + +private: + // NOTE: Using prefix "BTLS" to avoid overlapping names with + // the values of nsLayoutUtils::IntrinsicISizeType + enum BtlsISizeType { BTLS_MIN_ISIZE, + BTLS_PREF_ISIZE, + BTLS_FINAL_ISIZE }; + + // Compute intrinsic isize member variables on the columns. + void ComputeColumnIntrinsicISizes(nsRenderingContext* aRenderingContext); + + // Distribute a colspanning cell's percent isize (if any) to its columns. + void DistributePctISizeToColumns(float aSpanPrefPct, + int32_t aFirstCol, + int32_t aColCount); + + // Distribute an isize of some BltsISizeType type to a set of columns. + // aISize: The amount of isize to be distributed + // aFirstCol: The index (in the table) of the first column to be + // considered for receiving isize + // aColCount: The number of consecutive columns (starting with aFirstCol) + // to be considered for receiving isize + // aISizeType: The type of isize being distributed. (BTLS_MIN_ISIZE and + // BTLS_PREF_ISIZE are intended to be used for dividing up + // colspan's min & pref isize. BTLS_FINAL_ISIZE is intended + // to be used for distributing the table's final isize across + // all its columns) + // aSpanHasSpecifiedISize: Should be true iff: + // - We're distributing a colspanning cell's + // pref or min isize to its columns + // - The colspanning cell has a specified isize. + void DistributeISizeToColumns(nscoord aISize, + int32_t aFirstCol, + int32_t aColCount, + BtlsISizeType aISizeType, + bool aSpanHasSpecifiedISize); + + + // Compute the min and pref isizes of the table from the isize + // variables on the columns. + void ComputeIntrinsicISizes(nsRenderingContext* aRenderingContext); + + nsTableFrame *mTableFrame; + nscoord mMinISize; + nscoord mPrefISize; + nscoord mPrefISizePctExpand; + nscoord mLastCalcISize; +}; + +#endif /* !defined(BasicTableLayoutStrategy_h_) */ diff --git a/layout/tables/FixedTableLayoutStrategy.cpp b/layout/tables/FixedTableLayoutStrategy.cpp new file mode 100644 index 000000000..427765795 --- /dev/null +++ b/layout/tables/FixedTableLayoutStrategy.cpp @@ -0,0 +1,422 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=2:et:sw=2: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Algorithms that determine column and table inline sizes used for + * CSS2's 'table-layout: fixed'. + */ + +#include "FixedTableLayoutStrategy.h" +#include "nsTableFrame.h" +#include "nsTableColFrame.h" +#include "nsTableCellFrame.h" +#include + +FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame) + : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed) + , mTableFrame(aTableFrame) +{ + MarkIntrinsicISizesDirty(); +} + +/* virtual */ +FixedTableLayoutStrategy::~FixedTableLayoutStrategy() +{ +} + +/* virtual */ nscoord +FixedTableLayoutStrategy::GetMinISize(nsRenderingContext* aRenderingContext) +{ + DISPLAY_MIN_WIDTH(mTableFrame, mMinISize); + if (mMinISize != NS_INTRINSIC_WIDTH_UNKNOWN) { + return mMinISize; + } + + // It's theoretically possible to do something much better here that + // depends only on the columns and the first row (where we look at + // intrinsic inline sizes inside the first row and then reverse the + // algorithm to find the narrowest inline size that would hold all of + // those intrinsic inline sizes), but it wouldn't be compatible with + // other browsers, or with the use of GetMinISize by + // nsTableFrame::ComputeSize to determine the inline size of a fixed + // layout table, since CSS2.1 says: + // The width of the table is then the greater of the value of the + // 'width' property for the table element and the sum of the column + // widths (plus cell spacing or borders). + + // XXX Should we really ignore 'min-width' and 'max-width'? + // XXX Should we really ignore widths on column groups? + + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); + int32_t colCount = cellMap->GetColCount(); + + nscoord result = 0; + + if (colCount > 0) { + result += mTableFrame->GetColSpacing(-1, colCount); + } + + WritingMode wm = mTableFrame->GetWritingMode(); + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord spacing = mTableFrame->GetColSpacing(col); + const nsStyleCoord *styleISize = &colFrame->StylePosition()->ISize(wm); + if (styleISize->ConvertsToLength()) { + result += colFrame->ComputeISizeValue(aRenderingContext, + 0, 0, 0, *styleISize); + } else if (styleISize->GetUnit() == eStyleUnit_Percent) { + // do nothing + } else { + NS_ASSERTION(styleISize->GetUnit() == eStyleUnit_Auto || + styleISize->GetUnit() == eStyleUnit_Enumerated || + (styleISize->IsCalcUnit() && styleISize->CalcHasPercent()), + "bad inline size"); + + // The 'table-layout: fixed' algorithm considers only cells in the + // first row. + bool originates; + int32_t colSpan; + nsTableCellFrame *cellFrame = cellMap->GetCellInfoAt(0, col, &originates, + &colSpan); + if (cellFrame) { + styleISize = &cellFrame->StylePosition()->ISize(wm); + if (styleISize->ConvertsToLength() || + (styleISize->GetUnit() == eStyleUnit_Enumerated && + (styleISize->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || + styleISize->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) { + nscoord cellISize = + nsLayoutUtils::IntrinsicForContainer(aRenderingContext, cellFrame, + nsLayoutUtils::MIN_ISIZE); + if (colSpan > 1) { + // If a column-spanning cell is in the first row, split up + // the space evenly. (XXX This isn't quite right if some of + // the columns it's in have specified inline sizes. Should + // we care?) + cellISize = ((cellISize + spacing) / colSpan) - spacing; + } + result += cellISize; + } else if (styleISize->GetUnit() == eStyleUnit_Percent) { + if (colSpan > 1) { + // XXX Can this force columns to negative inline sizes? + result -= spacing * (colSpan - 1); + } + } + // else, for 'auto', '-moz-available', '-moz-fit-content', + // and 'calc()' with percentages, do nothing + } + } + } + + return (mMinISize = result); +} + +/* virtual */ nscoord +FixedTableLayoutStrategy::GetPrefISize(nsRenderingContext* aRenderingContext, + bool aComputingSize) +{ + // It's theoretically possible to do something much better here that + // depends only on the columns and the first row (where we look at + // intrinsic inline sizes inside the first row and then reverse the + // algorithm to find the narrowest inline size that would hold all of + // those intrinsic inline sizes), but it wouldn't be compatible with + // other browsers. + nscoord result = nscoord_MAX; + DISPLAY_PREF_WIDTH(mTableFrame, result); + return result; +} + +/* virtual */ void +FixedTableLayoutStrategy::MarkIntrinsicISizesDirty() +{ + mMinISize = NS_INTRINSIC_WIDTH_UNKNOWN; + mLastCalcISize = nscoord_MIN; +} + +static inline nscoord +AllocateUnassigned(nscoord aUnassignedSpace, float aShare) +{ + if (aShare == 1.0f) { + // This happens when the numbers we're dividing to get aShare are + // equal. We want to return unassignedSpace exactly, even if it + // can't be precisely round-tripped through float. + return aUnassignedSpace; + } + return NSToCoordRound(float(aUnassignedSpace) * aShare); +} + +/* virtual */ void +FixedTableLayoutStrategy::ComputeColumnISizes(const ReflowInput& aReflowInput) +{ + nscoord tableISize = aReflowInput.ComputedISize(); + + if (mLastCalcISize == tableISize) { + return; + } + mLastCalcISize = tableISize; + + nsTableCellMap *cellMap = mTableFrame->GetCellMap(); + int32_t colCount = cellMap->GetColCount(); + + if (colCount == 0) { + // No Columns - nothing to compute + return; + } + + // border-spacing isn't part of the basis for percentages. + tableISize -= mTableFrame->GetColSpacing(-1, colCount); + + // store the old column inline sizes. We might call SetFinalISize + // multiple times on the columns, due to this we can't compare at the + // last call that the inline size has changed with respect to the last + // call to ComputeColumnISizes. In order to overcome this we store the + // old values in this array. A single call to SetFinalISize would make + // it possible to call GetFinalISize before and to compare when + // setting the final inline size. + nsTArray oldColISizes; + + // XXX This ignores the 'min-width' and 'max-width' properties + // throughout. Then again, that's what the CSS spec says to do. + + // XXX Should we really ignore widths on column groups? + + uint32_t unassignedCount = 0; + nscoord unassignedSpace = tableISize; + const nscoord unassignedMarker = nscoord_MIN; + + // We use the PrefPercent on the columns to store the percentages + // used to compute column inline sizes in case we need to shrink or + // expand the columns. + float pctTotal = 0.0f; + + // Accumulate the total specified (non-percent) on the columns for + // distributing excess inline size to the columns. + nscoord specTotal = 0; + + WritingMode wm = mTableFrame->GetWritingMode(); + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + oldColISizes.AppendElement(0); + NS_ERROR("column frames out of sync with cell map"); + continue; + } + oldColISizes.AppendElement(colFrame->GetFinalISize()); + colFrame->ResetPrefPercent(); + const nsStyleCoord *styleISize = &colFrame->StylePosition()->ISize(wm); + nscoord colISize; + if (styleISize->ConvertsToLength()) { + colISize = colFrame->ComputeISizeValue(aReflowInput.mRenderingContext, + 0, 0, 0, *styleISize); + specTotal += colISize; + } else if (styleISize->GetUnit() == eStyleUnit_Percent) { + float pct = styleISize->GetPercentValue(); + colISize = NSToCoordFloor(pct * float(tableISize)); + colFrame->AddPrefPercent(pct); + pctTotal += pct; + } else { + NS_ASSERTION(styleISize->GetUnit() == eStyleUnit_Auto || + styleISize->GetUnit() == eStyleUnit_Enumerated || + (styleISize->IsCalcUnit() && styleISize->CalcHasPercent()), + "bad inline size"); + + // The 'table-layout: fixed' algorithm considers only cells in the + // first row. + bool originates; + int32_t colSpan; + nsTableCellFrame *cellFrame = cellMap->GetCellInfoAt(0, col, &originates, + &colSpan); + if (cellFrame) { + const nsStylePosition* cellStylePos = cellFrame->StylePosition(); + styleISize = &cellStylePos->ISize(wm); + if (styleISize->ConvertsToLength() || + (styleISize->GetUnit() == eStyleUnit_Enumerated && + (styleISize->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || + styleISize->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) { + // XXX This should use real percentage padding + // Note that the difference between MIN_ISIZE and PREF_ISIZE + // shouldn't matter for any of these values of styleISize; use + // MIN_ISIZE for symmetry with GetMinISize above, just in case + // there is a difference. + colISize = + nsLayoutUtils::IntrinsicForContainer(aReflowInput.mRenderingContext, + cellFrame, + nsLayoutUtils::MIN_ISIZE); + } else if (styleISize->GetUnit() == eStyleUnit_Percent) { + // XXX This should use real percentage padding + float pct = styleISize->GetPercentValue(); + colISize = NSToCoordFloor(pct * float(tableISize)); + + if (cellStylePos->mBoxSizing == StyleBoxSizing::Content) { + nsIFrame::IntrinsicISizeOffsetData offsets = + cellFrame->IntrinsicISizeOffsets(); + colISize += offsets.hPadding + offsets.hBorder; + } + + pct /= float(colSpan); + colFrame->AddPrefPercent(pct); + pctTotal += pct; + } else { + // 'auto', '-moz-available', '-moz-fit-content', and 'calc()' + // with percentages + colISize = unassignedMarker; + } + if (colISize != unassignedMarker) { + if (colSpan > 1) { + // If a column-spanning cell is in the first row, split up + // the space evenly. (XXX This isn't quite right if some of + // the columns it's in have specified iSizes. Should we + // care?) + nscoord spacing = mTableFrame->GetColSpacing(col); + colISize = ((colISize + spacing) / colSpan) - spacing; + if (colISize < 0) { + colISize = 0; + } + } + if (styleISize->GetUnit() != eStyleUnit_Percent) { + specTotal += colISize; + } + } + } else { + colISize = unassignedMarker; + } + } + + colFrame->SetFinalISize(colISize); + + if (colISize == unassignedMarker) { + ++unassignedCount; + } else { + unassignedSpace -= colISize; + } + } + + if (unassignedSpace < 0) { + if (pctTotal > 0) { + // If the columns took up too much space, reduce those that had + // percentage inline sizes. The spec doesn't say to do this, but + // we've always done it in the past, and so does WinIE6. + nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableISize)); + nscoord reduce = std::min(pctUsed, -unassignedSpace); + float reduceRatio = float(reduce) / pctTotal; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + nscoord colISize = colFrame->GetFinalISize(); + colISize -= NSToCoordFloor(colFrame->GetPrefPercent() * reduceRatio); + if (colISize < 0) { + colISize = 0; + } + colFrame->SetFinalISize(colISize); + } + } + unassignedSpace = 0; + } + + if (unassignedCount > 0) { + // The spec says to distribute the remaining space evenly among + // the columns. + nscoord toAssign = unassignedSpace / unassignedCount; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (colFrame->GetFinalISize() == unassignedMarker) { + colFrame->SetFinalISize(toAssign); + } + } + } else if (unassignedSpace > 0) { + // The spec doesn't say how to distribute the unassigned space. + if (specTotal > 0) { + // Distribute proportionally to non-percentage columns. + nscoord specUndist = specTotal; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (colFrame->GetPrefPercent() == 0.0f) { + NS_ASSERTION(colFrame->GetFinalISize() <= specUndist, + "inline sizes don't add up"); + nscoord toAdd = AllocateUnassigned(unassignedSpace, + float(colFrame->GetFinalISize()) / + float(specUndist)); + specUndist -= colFrame->GetFinalISize(); + colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd); + unassignedSpace -= toAdd; + if (specUndist <= 0) { + NS_ASSERTION(specUndist == 0, "math should be exact"); + break; + } + } + } + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); + } else if (pctTotal > 0) { + // Distribute proportionally to percentage columns. + float pctUndist = pctTotal; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (pctUndist < colFrame->GetPrefPercent()) { + // This can happen with floating-point math. + NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist < 0.0001, + "inline sizes don't add up"); + pctUndist = colFrame->GetPrefPercent(); + } + nscoord toAdd = AllocateUnassigned(unassignedSpace, + colFrame->GetPrefPercent() / + pctUndist); + colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd); + unassignedSpace -= toAdd; + pctUndist -= colFrame->GetPrefPercent(); + if (pctUndist <= 0.0f) { + break; + } + } + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); + } else { + // Distribute equally to the zero-iSize columns. + int32_t colsRemaining = colCount; + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + NS_ASSERTION(colFrame->GetFinalISize() == 0, "yikes"); + nscoord toAdd = AllocateUnassigned(unassignedSpace, + 1.0f / float(colsRemaining)); + colFrame->SetFinalISize(toAdd); + unassignedSpace -= toAdd; + --colsRemaining; + } + NS_ASSERTION(unassignedSpace == 0, "failed to redistribute"); + } + } + for (int32_t col = 0; col < colCount; ++col) { + nsTableColFrame *colFrame = mTableFrame->GetColFrame(col); + if (!colFrame) { + NS_ERROR("column frames out of sync with cell map"); + continue; + } + if (oldColISizes.ElementAt(col) != colFrame->GetFinalISize()) { + mTableFrame->DidResizeColumns(); + break; + } + } +} diff --git a/layout/tables/FixedTableLayoutStrategy.h b/layout/tables/FixedTableLayoutStrategy.h new file mode 100644 index 000000000..6d3d20c4e --- /dev/null +++ b/layout/tables/FixedTableLayoutStrategy.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=2:et:sw=2: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Algorithms that determine column and table isizes used for CSS2's + * 'table-layout: fixed'. + */ + +#ifndef FixedTableLayoutStrategy_h_ +#define FixedTableLayoutStrategy_h_ + +#include "mozilla/Attributes.h" +#include "nsITableLayoutStrategy.h" + +class nsTableFrame; + +class FixedTableLayoutStrategy : public nsITableLayoutStrategy +{ +public: + explicit FixedTableLayoutStrategy(nsTableFrame *aTableFrame); + virtual ~FixedTableLayoutStrategy(); + + // nsITableLayoutStrategy implementation + virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext, + bool aComputingSize) override; + virtual void MarkIntrinsicISizesDirty() override; + virtual void ComputeColumnISizes(const ReflowInput& aReflowInput) + override; + +private: + nsTableFrame *mTableFrame; + nscoord mMinISize; + nscoord mLastCalcISize; +}; + +#endif /* !defined(FixedTableLayoutStrategy_h_) */ diff --git a/layout/tables/SpanningCellSorter.cpp b/layout/tables/SpanningCellSorter.cpp new file mode 100644 index 000000000..c67d784bb --- /dev/null +++ b/layout/tables/SpanningCellSorter.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// vim:cindent:ts=4:et:sw=4: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Code to sort cells by their colspan, used by BasicTableLayoutStrategy. + */ + +#include "SpanningCellSorter.h" +#include "nsQuickSort.h" +#include "nsIPresShell.h" + +//#define DEBUG_SPANNING_CELL_SORTER + +SpanningCellSorter::SpanningCellSorter() + : mState(ADDING) + , mHashTable(&HashTableOps, sizeof(HashTableEntry)) + , mSortedHashTable(nullptr) +{ + memset(mArray, 0, sizeof(mArray)); +} + +SpanningCellSorter::~SpanningCellSorter() +{ + delete [] mSortedHashTable; +} + +/* static */ const PLDHashTableOps +SpanningCellSorter::HashTableOps = { + HashTableHashKey, + HashTableMatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + +/* static */ PLDHashNumber +SpanningCellSorter::HashTableHashKey(const void *key) +{ + return NS_PTR_TO_INT32(key); +} + +/* static */ bool +SpanningCellSorter::HashTableMatchEntry(const PLDHashEntryHdr *hdr, + const void *key) +{ + const HashTableEntry *entry = static_cast(hdr); + return NS_PTR_TO_INT32(key) == entry->mColSpan; +} + +bool +SpanningCellSorter::AddCell(int32_t aColSpan, int32_t aRow, int32_t aCol) +{ + NS_ASSERTION(mState == ADDING, "cannot call AddCell after GetNext"); + NS_ASSERTION(aColSpan >= ARRAY_BASE, "cannot add cells with colspan<2"); + + Item *i = (Item*) mozilla::AutoStackArena::Allocate(sizeof(Item)); + NS_ENSURE_TRUE(i != nullptr, false); + + i->row = aRow; + i->col = aCol; + + if (UseArrayForSpan(aColSpan)) { + int32_t index = SpanToIndex(aColSpan); + i->next = mArray[index]; + mArray[index] = i; + } else { + auto entry = static_cast + (mHashTable.Add(NS_INT32_TO_PTR(aColSpan), fallible)); + NS_ENSURE_TRUE(entry, false); + + NS_ASSERTION(entry->mColSpan == 0 || entry->mColSpan == aColSpan, + "wrong entry"); + NS_ASSERTION((entry->mColSpan == 0) == (entry->mItems == nullptr), + "entry should be either new or properly initialized"); + entry->mColSpan = aColSpan; + + i->next = entry->mItems; + entry->mItems = i; + } + + return true; +} + +/* static */ int +SpanningCellSorter::SortArray(const void *a, const void *b, void *closure) +{ + int32_t spanA = (*static_cast(a))->mColSpan; + int32_t spanB = (*static_cast(b))->mColSpan; + + if (spanA < spanB) + return -1; + if (spanA == spanB) + return 0; + return 1; +} + +SpanningCellSorter::Item* +SpanningCellSorter::GetNext(int32_t *aColSpan) +{ + NS_ASSERTION(mState != DONE, "done enumerating, stop calling"); + + switch (mState) { + case ADDING: + /* prepare to enumerate the array */ + mState = ENUMERATING_ARRAY; + mEnumerationIndex = 0; + MOZ_FALLTHROUGH; + case ENUMERATING_ARRAY: + while (mEnumerationIndex < ARRAY_SIZE && !mArray[mEnumerationIndex]) + ++mEnumerationIndex; + if (mEnumerationIndex < ARRAY_SIZE) { + Item *result = mArray[mEnumerationIndex]; + *aColSpan = IndexToSpan(mEnumerationIndex); + NS_ASSERTION(result, "logic error"); +#ifdef DEBUG_SPANNING_CELL_SORTER + printf("SpanningCellSorter[%p]:" + " returning list for colspan=%d from array\n", + static_cast(this), *aColSpan); +#endif + ++mEnumerationIndex; + return result; + } + /* prepare to enumerate the hash */ + mState = ENUMERATING_HASH; + mEnumerationIndex = 0; + if (mHashTable.EntryCount() > 0) { + HashTableEntry **sh = + new HashTableEntry*[mHashTable.EntryCount()]; + int32_t j = 0; + for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) { + sh[j++] = static_cast(iter.Get()); + } + NS_QuickSort(sh, mHashTable.EntryCount(), sizeof(sh[0]), + SortArray, nullptr); + mSortedHashTable = sh; + } + MOZ_FALLTHROUGH; + case ENUMERATING_HASH: + if (mEnumerationIndex < mHashTable.EntryCount()) { + Item *result = mSortedHashTable[mEnumerationIndex]->mItems; + *aColSpan = mSortedHashTable[mEnumerationIndex]->mColSpan; + NS_ASSERTION(result, "holes in hash table"); +#ifdef DEBUG_SPANNING_CELL_SORTER + printf("SpanningCellSorter[%p]:" + " returning list for colspan=%d from hash\n", + static_cast(this), *aColSpan); +#endif + ++mEnumerationIndex; + return result; + } + mState = DONE; + MOZ_FALLTHROUGH; + case DONE: + ; + } + return nullptr; +} diff --git a/layout/tables/SpanningCellSorter.h b/layout/tables/SpanningCellSorter.h new file mode 100644 index 000000000..30139c0e3 --- /dev/null +++ b/layout/tables/SpanningCellSorter.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// vim:cindent:ts=4:et:sw=4: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SpanningCellSorter_h +#define SpanningCellSorter_h + +/* + * Code to sort cells by their colspan, used by BasicTableLayoutStrategy. + */ + +#include "PLDHashTable.h" +#include "nsDebug.h" +#include "StackArena.h" + +/** + * The SpanningCellSorter is responsible for accumulating lists of cells + * with colspans so that those cells can later be enumerated, sorted + * from lowest number of columns spanned to highest. It does not use a + * stable sort (in fact, it currently reverses). + */ +class MOZ_STACK_CLASS SpanningCellSorter { +public: + SpanningCellSorter(); + ~SpanningCellSorter(); + + struct Item { + int32_t row, col; + Item *next; + }; + + /** + * Add a cell to the sorter. Returns false on out of memory. + * aColSpan is the number of columns spanned, and aRow/aCol are the + * position of the cell in the table (for GetCellInfoAt). + */ + bool AddCell(int32_t aColSpan, int32_t aRow, int32_t aCol); + + /** + * Get the next *list* of cells. Each list contains all the cells + * for a colspan value, and the lists are given in order from lowest + * to highest colspan. The colspan value is filled in to *aColSpan. + */ + Item* GetNext(int32_t *aColSpan); +private: + + enum State { ADDING, ENUMERATING_ARRAY, ENUMERATING_HASH, DONE }; + State mState; + + // store small colspans in an array for fast sorting and + // enumeration, and large colspans in a hash table + + enum { ARRAY_BASE = 2 }; + enum { ARRAY_SIZE = 8 }; + Item *mArray[ARRAY_SIZE]; + int32_t SpanToIndex(int32_t aSpan) { return aSpan - ARRAY_BASE; } + int32_t IndexToSpan(int32_t aIndex) { return aIndex + ARRAY_BASE; } + bool UseArrayForSpan(int32_t aSpan) { + NS_ASSERTION(SpanToIndex(aSpan) >= 0, "cell without colspan"); + return SpanToIndex(aSpan) < ARRAY_SIZE; + } + + PLDHashTable mHashTable; + struct HashTableEntry : public PLDHashEntryHdr { + int32_t mColSpan; + Item *mItems; + }; + + static const PLDHashTableOps HashTableOps; + + static PLDHashNumber HashTableHashKey(const void *key); + static bool + HashTableMatchEntry(const PLDHashEntryHdr *hdr, const void *key); + + static int SortArray(const void *a, const void *b, void *closure); + + /* state used only during enumeration */ + uint32_t mEnumerationIndex; // into mArray or mSortedHashTable + HashTableEntry **mSortedHashTable; + + /* + * operator new is forbidden since we use the pres shell's stack + * memory, which much be pushed and popped at points matching a + * push/pop on the C++ stack. + */ + void* operator new(size_t sz) CPP_THROW_NEW { return nullptr; } +}; + +#endif diff --git a/layout/tables/TableArea.h b/layout/tables/TableArea.h new file mode 100644 index 000000000..a94eea6e6 --- /dev/null +++ b/layout/tables/TableArea.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_TableArea_h_ +#define mozilla_TableArea_h_ + +#include "nsRect.h" + +namespace mozilla { + +struct TableArea +{ + TableArea() : mRect() { } + TableArea(int32_t aStartCol, int32_t aStartRow, + int32_t aColCount, int32_t aRowCount) + : mRect(aStartCol, aStartRow, aColCount, aRowCount) { } + + int32_t& StartCol() { return mRect.x; } + int32_t& StartRow() { return mRect.y; } + int32_t& ColCount() { return mRect.width; } + int32_t& RowCount() { return mRect.height; } + + int32_t StartCol() const { return mRect.x; } + int32_t StartRow() const { return mRect.y; } + int32_t ColCount() const { return mRect.width; } + int32_t RowCount() const { return mRect.height; } + int32_t EndCol() const { return mRect.XMost(); } + int32_t EndRow() const { return mRect.YMost(); } + + void UnionArea(const TableArea& aArea1, const TableArea& aArea2) + { mRect.UnionRect(aArea1.mRect, aArea2.mRect); } + +private: + nsIntRect mRect; +}; + +} // namespace mozilla + +#endif // mozilla_TableArea_h_ diff --git a/layout/tables/celldata.h b/layout/tables/celldata.h new file mode 100644 index 000000000..b744b5175 --- /dev/null +++ b/layout/tables/celldata.h @@ -0,0 +1,459 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef CellData_h__ +#define CellData_h__ + +#include "nsISupports.h" +#include "nsCoord.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/WritingModes.h" +#include + +class nsTableCellFrame; +class nsCellMap; +class BCCellData; + + +#define MAX_ROWSPAN 65534 // the cellmap can not handle more. +#define MAX_COLSPAN 1000 // limit as IE and opera do. If this ever changes, + // change COL_SPAN_OFFSET/COL_SPAN_SHIFT accordingly. + +/** + * Data stored by nsCellMap to rationalize rowspan and colspan cells. + */ +class CellData +{ +public: + /** Initialize the mOrigCell pointer + * @param aOrigCell the table cell frame which will be stored in mOrigCell. + */ + void Init(nsTableCellFrame* aCellFrame); + + /** does a cell originate from here + * @return is true if a cell corresponds to this cellmap entry + */ + bool IsOrig() const; + + /** is the celldata valid + * @return is true if no cell originates and the cell is not spanned by + * a row- or colspan. mBits are 0 in this case and mOrigCell is + * nullptr + */ + bool IsDead() const; + + /** is the entry spanned by row- or a colspan + * @return is true if the entry is spanned by a row- or colspan + */ + bool IsSpan() const; + + /** is the entry spanned by rowspan + * @return is true if the entry is spanned by a rowspan + */ + bool IsRowSpan() const; + + /** is the entry spanned by a zero rowspan + * zero rowspans span all cells starting from the originating cell down to + * the end of the rowgroup or a cell originating in the same column + * @return is true if the entry is spanned by a zero rowspan + */ + bool IsZeroRowSpan() const; + + /** mark the current entry as spanned by a zero rowspan + * @param aIsZero if true mark the entry as covered by a zero rowspan + */ + void SetZeroRowSpan(bool aIsZero); + + /** get the distance from the current entry to the corresponding origin of the rowspan + * @return containing the distance in the column to the originating cell + */ + uint32_t GetRowSpanOffset() const; + + /** set the distance from the current entry to the corresponding origin of the rowspan + * @param the distance in the column to the originating cell + */ + void SetRowSpanOffset(uint32_t aSpan); + + /** is the entry spanned by colspan + * @return is true if the entry is spanned by a colspan + */ + bool IsColSpan() const; + + /** get the distance from the current entry to the corresponding origin of the colspan + * @return containing the distance in the row to the originating cell + */ + uint32_t GetColSpanOffset() const; + + /** set the distance from the current entry to the corresponding origin of the colspan + * @param the distance in the column to the originating cell + */ + void SetColSpanOffset(uint32_t aSpan); + + /** is the entry spanned by a row- and a colspan + * @return is true if the entry is spanned by a row- and a colspan + */ + bool IsOverlap() const; + + /** mark the current entry as spanned by a row- and a colspan + * @param aOverlap if true mark the entry as covered by a row- and a colspan + */ + void SetOverlap(bool aOverlap); + + /** get the table cell frame for this entry + * @return a pointer to the cellframe, this will be nullptr when the entry + * is only a spanned entry + */ + nsTableCellFrame* GetCellFrame() const; + +private: + friend class nsCellMap; + friend class BCCellData; + + /** constructor. + * @param aOrigCell the table cell frame which will be stored in mOrigCell. + */ + explicit CellData(nsTableCellFrame* aOrigCell); // implemented in nsCellMap.cpp + + /** destructor */ + ~CellData(); // implemented in nsCellMap.cpp + +protected: + + // this union relies on the assumption that an object (not primitive type) does + // not start on an odd bit boundary. If mSpan is 0 then mOrigCell is in effect + // and the data does not represent a span. If mSpan is 1, then mBits is in + // effect and the data represents a span. + // mBits must match the size of mOrigCell on both 32- and 64-bit platforms. + union { + nsTableCellFrame* mOrigCell; + uintptr_t mBits; + }; +}; + +// Border Collapsing Cell Data +enum BCBorderOwner +{ + eTableOwner = 0, + eColGroupOwner = 1, + eAjaColGroupOwner = 2, // col group to the left + eColOwner = 3, + eAjaColOwner = 4, // col to the left + eRowGroupOwner = 5, + eAjaRowGroupOwner = 6, // row group above + eRowOwner = 7, + eAjaRowOwner = 8, // row above + eCellOwner = 9, + eAjaCellOwner = 10 // cell to the top or to the left +}; + +typedef uint16_t BCPixelSize; + +// These are the max sizes that are stored. If they are exceeded, then the max is stored and +// the actual value is computed when needed. +#define MAX_BORDER_WIDTH nscoord((1u << (sizeof(BCPixelSize) * 8)) - 1) + +// The half of border on inline/block-axis start side +static inline BCPixelSize +BC_BORDER_START_HALF(BCPixelSize px) { return px - px / 2; } +// The half of border on inline/block-axis end side +static inline BCPixelSize +BC_BORDER_END_HALF(BCPixelSize px) { return px / 2; } + +static inline nscoord +BC_BORDER_START_HALF_COORD(int32_t p2t, BCPixelSize px) + { return BC_BORDER_START_HALF(px) * p2t; } +static inline nscoord +BC_BORDER_END_HALF_COORD(int32_t p2t, BCPixelSize px) + { return BC_BORDER_END_HALF(px) * p2t; } + +// BCData stores the bstart and istart border info and the corner connecting the two. +class BCData +{ +public: + BCData(); + + ~BCData(); + + nscoord GetIStartEdge(BCBorderOwner& aOwner, + bool& aStart) const; + + void SetIStartEdge(BCBorderOwner aOwner, + nscoord aSize, + bool aStart); + + nscoord GetBStartEdge(BCBorderOwner& aOwner, + bool& aStart) const; + + void SetBStartEdge(BCBorderOwner aOwner, + nscoord aSize, + bool aStart); + + BCPixelSize GetCorner(mozilla::LogicalSide& aCornerOwner, + bool& aBevel) const; + + void SetCorner(BCPixelSize aSubSize, + mozilla::LogicalSide aOwner, + bool aBevel); + + bool IsIStartStart() const; + + void SetIStartStart(bool aValue); + + bool IsBStartStart() const; + + void SetBStartStart(bool aValue); + + +protected: + BCPixelSize mIStartSize; // size in pixels of iStart border + BCPixelSize mBStartSize; // size in pixels of bStart border + BCPixelSize mCornerSubSize; // size of the largest border not in the + // dominant plane (for example, if corner is + // owned by the segment to its bStart or bEnd, + // then the size is the max of the border + // sizes of the segments to its iStart or iEnd. + unsigned mIStartOwner: 4; // owner of iStart border + unsigned mBStartOwner: 4; // owner of bStart border + unsigned mIStartStart: 1; // set if this is the start of a block-dir border segment + unsigned mBStartStart: 1; // set if this is the start of an inline-dir border segment + unsigned mCornerSide: 2; // LogicalSide of the owner of the bStart-iStart corner relative to the corner + unsigned mCornerBevel: 1; // is the corner beveled (only two segments, perpendicular, not dashed or dotted). +}; + +// BCCellData entries replace CellData entries in the cell map if the border collapsing model is in +// effect. BCData for a row and col entry contains the left and top borders of cell at that row and +// col and the corner connecting the two. The right borders of the cells in the last col and the bottom +// borders of the last row are stored in separate BCData entries in the cell map. +class BCCellData : public CellData +{ +public: + explicit BCCellData(nsTableCellFrame* aOrigCell); + ~BCCellData(); + + BCData mData; +}; + + +// The layout of a celldata is as follows. The top 10 bits are the colspan +// offset (which is enough to represent our allowed values 1-1000 for colspan). +// Then there are two bits of flags. +// XXXmats Then one unused bit that we should decide how to use in bug 862624. +// Then 16 bits of rowspan offset (which +// lets us represent numbers up to 65535. Then another 3 bits of flags. + +// num bits to shift right to get right aligned col span +#define COL_SPAN_SHIFT 22 +// num bits to shift right to get right aligned row span +#define ROW_SPAN_SHIFT 3 + +// the col offset to the data containing the original cell. +#define COL_SPAN_OFFSET (0x3FF << COL_SPAN_SHIFT) +// the row offset to the data containing the original cell +#define ROW_SPAN_OFFSET (0xFFFF << ROW_SPAN_SHIFT) + +// And the flags +#define SPAN 0x00000001 // there a row or col span +#define ROW_SPAN 0x00000002 // there is a row span +#define ROW_SPAN_0 0x00000004 // the row span is 0 +#define COL_SPAN (1 << (COL_SPAN_SHIFT - 2)) // there is a col span +#define OVERLAP (1 << (COL_SPAN_SHIFT - 1)) // there is a row span and + // col span but not by + // same cell + +inline nsTableCellFrame* CellData::GetCellFrame() const +{ + if (SPAN != (SPAN & mBits)) { + return mOrigCell; + } + return nullptr; +} + +inline void CellData::Init(nsTableCellFrame* aCellFrame) +{ + mOrigCell = aCellFrame; +} + +inline bool CellData::IsOrig() const +{ + return ((nullptr != mOrigCell) && (SPAN != (SPAN & mBits))); +} + +inline bool CellData::IsDead() const +{ + return (0 == mBits); +} + +inline bool CellData::IsSpan() const +{ + return (SPAN == (SPAN & mBits)); +} + +inline bool CellData::IsRowSpan() const +{ + return (SPAN == (SPAN & mBits)) && + (ROW_SPAN == (ROW_SPAN & mBits)); +} + +inline bool CellData::IsZeroRowSpan() const +{ + return (SPAN == (SPAN & mBits)) && + (ROW_SPAN == (ROW_SPAN & mBits)) && + (ROW_SPAN_0 == (ROW_SPAN_0 & mBits)); +} + +inline void CellData::SetZeroRowSpan(bool aIsZeroSpan) +{ + if (SPAN == (SPAN & mBits)) { + if (aIsZeroSpan) { + mBits |= ROW_SPAN_0; + } + else { + mBits &= ~ROW_SPAN_0; + } + } +} + +inline uint32_t CellData::GetRowSpanOffset() const +{ + if ((SPAN == (SPAN & mBits)) && ((ROW_SPAN == (ROW_SPAN & mBits)))) { + return (uint32_t)((mBits & ROW_SPAN_OFFSET) >> ROW_SPAN_SHIFT); + } + return 0; +} + +inline void CellData::SetRowSpanOffset(uint32_t aSpan) +{ + mBits &= ~ROW_SPAN_OFFSET; + mBits |= (aSpan << ROW_SPAN_SHIFT); + mBits |= SPAN; + mBits |= ROW_SPAN; +} + +inline bool CellData::IsColSpan() const +{ + return (SPAN == (SPAN & mBits)) && + (COL_SPAN == (COL_SPAN & mBits)); +} + +inline uint32_t CellData::GetColSpanOffset() const +{ + if ((SPAN == (SPAN & mBits)) && ((COL_SPAN == (COL_SPAN & mBits)))) { + return (uint32_t)((mBits & COL_SPAN_OFFSET) >> COL_SPAN_SHIFT); + } + return 0; +} + +inline void CellData::SetColSpanOffset(uint32_t aSpan) +{ + mBits &= ~COL_SPAN_OFFSET; + mBits |= (aSpan << COL_SPAN_SHIFT); + + mBits |= SPAN; + mBits |= COL_SPAN; +} + +inline bool CellData::IsOverlap() const +{ + return (SPAN == (SPAN & mBits)) && (OVERLAP == (OVERLAP & mBits)); +} + +inline void CellData::SetOverlap(bool aOverlap) +{ + if (SPAN == (SPAN & mBits)) { + if (aOverlap) { + mBits |= OVERLAP; + } + else { + mBits &= ~OVERLAP; + } + } +} + +inline BCData::BCData() +{ + mIStartOwner = mBStartOwner = eCellOwner; + mIStartStart = mBStartStart = 1; + mIStartSize = mCornerSubSize = mBStartSize = 0; + mCornerSide = mozilla::eLogicalSideBStart; + mCornerBevel = false; +} + +inline BCData::~BCData() +{ +} + +inline nscoord BCData::GetIStartEdge(BCBorderOwner& aOwner, + bool& aStart) const +{ + aOwner = (BCBorderOwner)mIStartOwner; + aStart = (bool)mIStartStart; + + return (nscoord)mIStartSize; +} + +inline void BCData::SetIStartEdge(BCBorderOwner aOwner, + nscoord aSize, + bool aStart) +{ + mIStartOwner = aOwner; + mIStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize; + mIStartStart = aStart; +} + +inline nscoord BCData::GetBStartEdge(BCBorderOwner& aOwner, + bool& aStart) const +{ + aOwner = (BCBorderOwner)mBStartOwner; + aStart = (bool)mBStartStart; + + return (nscoord)mBStartSize; +} + +inline void BCData::SetBStartEdge(BCBorderOwner aOwner, + nscoord aSize, + bool aStart) +{ + mBStartOwner = aOwner; + mBStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize; + mBStartStart = aStart; +} + +inline BCPixelSize BCData::GetCorner(mozilla::LogicalSide& aOwnerSide, + bool& aBevel) const +{ + aOwnerSide = mozilla::LogicalSide(mCornerSide); + aBevel = (bool)mCornerBevel; + return mCornerSubSize; +} + +inline void BCData::SetCorner(BCPixelSize aSubSize, + mozilla::LogicalSide aOwnerSide, + bool aBevel) +{ + mCornerSubSize = aSubSize; + mCornerSide = aOwnerSide; + mCornerBevel = aBevel; +} + +inline bool BCData::IsIStartStart() const +{ + return (bool)mIStartStart; +} + +inline void BCData::SetIStartStart(bool aValue) +{ + mIStartStart = aValue; +} + +inline bool BCData::IsBStartStart() const +{ + return (bool)mBStartStart; +} + +inline void BCData::SetBStartStart(bool aValue) +{ + mBStartStart = aValue; +} + +#endif diff --git a/layout/tables/crashtests/1027611-1.html b/layout/tables/crashtests/1027611-1.html new file mode 100644 index 000000000..6083a6335 --- /dev/null +++ b/layout/tables/crashtests/1027611-1.html @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + +
+ + diff --git a/layout/tables/crashtests/1031934.html b/layout/tables/crashtests/1031934.html new file mode 100644 index 000000000..b718df845 --- /dev/null +++ b/layout/tables/crashtests/1031934.html @@ -0,0 +1,18 @@ + + + + + + + + +
+ + + + + +
+ + + diff --git a/layout/tables/crashtests/110523-1.html b/layout/tables/crashtests/110523-1.html new file mode 100644 index 000000000..144494960 --- /dev/null +++ b/layout/tables/crashtests/110523-1.html @@ -0,0 +1,45 @@ + + + + +
+ + + + + + + + + + + + +
Without the <tbody> tags Mozilla doesn't crash +
I disappear +
Without this row Mozilla doesn't crash +
+
+ + + diff --git a/layout/tables/crashtests/1183896.html b/layout/tables/crashtests/1183896.html new file mode 100644 index 000000000..3995ba09f --- /dev/null +++ b/layout/tables/crashtests/1183896.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/1223232.html b/layout/tables/crashtests/1223232.html new file mode 100644 index 000000000..6df062a0b --- /dev/null +++ b/layout/tables/crashtests/1223232.html @@ -0,0 +1,6 @@ + + + +
+ + diff --git a/layout/tables/crashtests/1223282.html b/layout/tables/crashtests/1223282.html new file mode 100644 index 000000000..132fdd74d --- /dev/null +++ b/layout/tables/crashtests/1223282.html @@ -0,0 +1,11 @@ + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/1243623-1.html b/layout/tables/crashtests/1243623-1.html new file mode 100644 index 000000000..9fd97b368 --- /dev/null +++ b/layout/tables/crashtests/1243623-1.html @@ -0,0 +1,27 @@ + + + + +
+
+

grilling it up +

+ + + diff --git a/layout/tables/crashtests/138725-1.html b/layout/tables/crashtests/138725-1.html new file mode 100644 index 000000000..8fe5721cc --- /dev/null +++ b/layout/tables/crashtests/138725-1.html @@ -0,0 +1,32 @@ + + + + + + +
+ + + + + + + + + +
+ + + + +
+
+ +
+
+
+
+
+
+ + diff --git a/layout/tables/crashtests/159359-1.html b/layout/tables/crashtests/159359-1.html new file mode 100644 index 000000000..9d52d990b --- /dev/null +++ b/layout/tables/crashtests/159359-1.html @@ -0,0 +1,13 @@ + + +Crash test + + + + + + + +
+ + \ No newline at end of file diff --git a/layout/tables/crashtests/187779-1.html b/layout/tables/crashtests/187779-1.html new file mode 100644 index 000000000..37ef83475 --- /dev/null +++ b/layout/tables/crashtests/187779-1.html @@ -0,0 +1,19 @@ + + +bug + + + + + + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/189751-1.html b/layout/tables/crashtests/189751-1.html new file mode 100644 index 000000000..270f4b22d --- /dev/null +++ b/layout/tables/crashtests/189751-1.html @@ -0,0 +1,3 @@ +
+
+
\ No newline at end of file diff --git a/layout/tables/crashtests/197015-1.html b/layout/tables/crashtests/197015-1.html new file mode 100644 index 000000000..e913acb39 --- /dev/null +++ b/layout/tables/crashtests/197015-1.html @@ -0,0 +1,10 @@ + +bug 197015 + + + + +foo +
+ + diff --git a/layout/tables/crashtests/220536-1.html b/layout/tables/crashtests/220536-1.html new file mode 100644 index 000000000..e15548cc7 --- /dev/null +++ b/layout/tables/crashtests/220536-1.html @@ -0,0 +1,93 @@ + + + +Essai + + + + + + + + + + + + + + + + + + + + + + + + + + +
aaaabbbbccccdddd
eeeeffff
11112222333344445555
+ + + diff --git a/layout/tables/crashtests/223458-1.html b/layout/tables/crashtests/223458-1.html new file mode 100644 index 000000000..ecf4a7360 --- /dev/null +++ b/layout/tables/crashtests/223458-1.html @@ -0,0 +1,25 @@ + + + + bug 223458 + + + + + + + + + + + + + + + + + + +
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
mod_ssl
+ + diff --git a/layout/tables/crashtests/237421-1.html b/layout/tables/crashtests/237421-1.html new file mode 100644 index 000000000..77cb1316c --- /dev/null +++ b/layout/tables/crashtests/237421-1.html @@ -0,0 +1,16 @@ + + + + bug 237421: test1 + + + + + +
+ + + diff --git a/layout/tables/crashtests/237421-2.html b/layout/tables/crashtests/237421-2.html new file mode 100644 index 000000000..a80081946 --- /dev/null +++ b/layout/tables/crashtests/237421-2.html @@ -0,0 +1,47 @@ + + + + bug 237421: test2a + + + + + + + + + + + +
ab
c
+ + + diff --git a/layout/tables/crashtests/238909-1.html b/layout/tables/crashtests/238909-1.html new file mode 100644 index 000000000..3921e1c6b --- /dev/null +++ b/layout/tables/crashtests/238909-1.html @@ -0,0 +1,8 @@ + + + bug 238909 + + +
+ + diff --git a/layout/tables/crashtests/239294-1.html b/layout/tables/crashtests/239294-1.html new file mode 100644 index 000000000..7e6f076a5 --- /dev/null +++ b/layout/tables/crashtests/239294-1.html @@ -0,0 +1,38 @@ + + + + + + Watch Mozilla Go Boom + + + + + + + + + + + + + + + + + + + + + + + + +
YearMonth
2001 SepOctNovDec
2002JanFebMar 
+ + \ No newline at end of file diff --git a/layout/tables/crashtests/240854-1.html b/layout/tables/crashtests/240854-1.html new file mode 100644 index 000000000..ac98567a3 --- /dev/null +++ b/layout/tables/crashtests/240854-1.html @@ -0,0 +1,40 @@ + + + + Table dom Column Handling crash + + + + +The 2 tables should look the same + + + + + + + +
100200auto
+
+ + + + + + +
100200auto
+ + + + + + + + + diff --git a/layout/tables/crashtests/266015-1.html b/layout/tables/crashtests/266015-1.html new file mode 100644 index 000000000..cdfd9f949 --- /dev/null +++ b/layout/tables/crashtests/266015-1.html @@ -0,0 +1,18 @@ + + + + +col crash 1 + + + +
+
+
+
this text should be visible
+ + +
diff --git a/layout/tables/crashtests/267418.html b/layout/tables/crashtests/267418.html new file mode 100644 index 000000000..d8f804120 --- /dev/null +++ b/layout/tables/crashtests/267418.html @@ -0,0 +1,122 @@ + + + + Testcase, bug 174470 + + + + + + + +
+ +

The following two tables should be mirrors of each other, except that +(1) the digits should still be normal left-to-right digits and (2) the +color changes for each of the 6 colors should, in both, be lightest on top +clockwise to darkest on the left.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
122333444455555666666
+ + + diff --git a/layout/tables/crashtests/275625.html b/layout/tables/crashtests/275625.html new file mode 100644 index 000000000..369c5aa51 --- /dev/null +++ b/layout/tables/crashtests/275625.html @@ -0,0 +1,8 @@ +Testcase bug 275625 - Crash with a:hover, a:hover+br{display:table-row;} + + + +Hovering over the link should not crash Mozilla
+ \ No newline at end of file diff --git a/layout/tables/crashtests/277062-1.html b/layout/tables/crashtests/277062-1.html new file mode 100644 index 000000000..59ca0ace5 --- /dev/null +++ b/layout/tables/crashtests/277062-1.html @@ -0,0 +1,4 @@ + + + + diff --git a/layout/tables/crashtests/278385-1.html b/layout/tables/crashtests/278385-1.html new file mode 100644 index 000000000..c82a3a70c --- /dev/null +++ b/layout/tables/crashtests/278385-1.html @@ -0,0 +1,25 @@ + + + + + Firefox Test + + +
+ + + Firefox Test + + + + + + +
+ +

TWO

+
+

ONE

+
+ + diff --git a/layout/tables/crashtests/282175-1.html b/layout/tables/crashtests/282175-1.html new file mode 100644 index 000000000..27d4f86e8 --- /dev/null +++ b/layout/tables/crashtests/282175-1.html @@ -0,0 +1,26 @@ + + + + bug 282175 + + + + + + + + + +
0.2 miles
+ + diff --git a/layout/tables/crashtests/284844-1.html b/layout/tables/crashtests/284844-1.html new file mode 100644 index 000000000..ad1d72f64 --- /dev/null +++ b/layout/tables/crashtests/284844-1.html @@ -0,0 +1,13 @@ + + + +Testcase for bug 284844 + + + + + +
+ + + diff --git a/layout/tables/crashtests/284852.html b/layout/tables/crashtests/284852.html new file mode 100644 index 000000000..5b6a2e5c9 --- /dev/null +++ b/layout/tables/crashtests/284852.html @@ -0,0 +1,130 @@ + + + + Testcase for bug 284852 + + + + + + + + + + + + + + + + + +
g1row1
g1row2
g1row3
g2row1
g2row2
g2row3
g2row4
+ + + + +
+Clicking the button above five times or more gives:
+
+###!!! ASSERTION: invalid BC damage area: 'PR_FALSE', file nsTableFrame.cpp, line 4567
+
+ + + + + + + + + + + + + +
g1row1
g1row2
g1row3
g2row1
g2row2
g2row3
g2row4
+ + + +
+Clicking the button gives:
+
+###!!! ASSERTION: invalid BC damage area: 'PR_FALSE', file nsTableFrame.cpp, line 4567
+
+ + + + + + + + + + + + + +
g1row1
g1row2
g1row3
g2row1
g2row2
g2row3
+ + + + +
+Clicking the button the first time gives:
+
+###!!! ASSERTION: invalid BC damage area: 'PR_FALSE', file nsTableFrame.cpp, line 4567
+
+ + + + + diff --git a/layout/tables/crashtests/28933-1.html b/layout/tables/crashtests/28933-1.html new file mode 100644 index 000000000..26ffb0223 --- /dev/null +++ b/layout/tables/crashtests/28933-1.html @@ -0,0 +1,10 @@ +test + + + + + + + + +
diff --git a/layout/tables/crashtests/29157-1.html b/layout/tables/crashtests/29157-1.html new file mode 100644 index 000000000..9302d3403 --- /dev/null +++ b/layout/tables/crashtests/29157-1.html @@ -0,0 +1,28 @@ + + + +test + + + + + + +
cell datacell data
cell datacell data
cell datacell data
+ + + + + + + + + + diff --git a/layout/tables/crashtests/300912.html b/layout/tables/crashtests/300912.html new file mode 100644 index 000000000..e3e2b31c5 --- /dev/null +++ b/layout/tables/crashtests/300912.html @@ -0,0 +1,19 @@ + +Testcase for assertion + + + + + + +
+ + + + +
+
+
+ + + \ No newline at end of file diff --git a/layout/tables/crashtests/308752-1-inner.html b/layout/tables/crashtests/308752-1-inner.html new file mode 100644 index 000000000..4d2155f16 --- /dev/null +++ b/layout/tables/crashtests/308752-1-inner.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
P 6 O
5 P
5 U
6 S
S 9
+Mozilla should not crash when clicking in the document + + diff --git a/layout/tables/crashtests/308752-1.html b/layout/tables/crashtests/308752-1.html new file mode 100644 index 000000000..a2e92ae31 --- /dev/null +++ b/layout/tables/crashtests/308752-1.html @@ -0,0 +1,9 @@ + + + + + + + diff --git a/layout/tables/crashtests/308752-2-inner.html b/layout/tables/crashtests/308752-2-inner.html new file mode 100644 index 000000000..ebb8df711 --- /dev/null +++ b/layout/tables/crashtests/308752-2-inner.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
r11
r21
+Mozilla should not crash when clicking in the document + + + diff --git a/layout/tables/crashtests/308752-2.html b/layout/tables/crashtests/308752-2.html new file mode 100644 index 000000000..fdfca1658 --- /dev/null +++ b/layout/tables/crashtests/308752-2.html @@ -0,0 +1,9 @@ + + + + + + + diff --git a/layout/tables/crashtests/316636-1.html b/layout/tables/crashtests/316636-1.html new file mode 100644 index 000000000..1ccc0e1b8 --- /dev/null +++ b/layout/tables/crashtests/316636-1.html @@ -0,0 +1,19 @@ + + + + + +(Type a title for your page here) + + + + + + + + + +
foobarzap
boom
+ + + diff --git a/layout/tables/crashtests/317876.html b/layout/tables/crashtests/317876.html new file mode 100644 index 000000000..6c4ae9890 --- /dev/null +++ b/layout/tables/crashtests/317876.html @@ -0,0 +1,16 @@ + + +Testcase bug 317876 - Crash with evil testcase on hovering after reload with display:table-row, display:inherit + + +
+
+ + + + Hovering over this should not crash Mozilla + +
+
+ + diff --git a/layout/tables/crashtests/322779-1.xul b/layout/tables/crashtests/322779-1.xul new file mode 100644 index 000000000..8af18adcc --- /dev/null +++ b/layout/tables/crashtests/322779-1.xul @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/layout/tables/crashtests/323489-1.html b/layout/tables/crashtests/323489-1.html new file mode 100644 index 000000000..f09221599 --- /dev/null +++ b/layout/tables/crashtests/323489-1.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + +
Cell
+ + + + \ No newline at end of file diff --git a/layout/tables/crashtests/323604-1.html b/layout/tables/crashtests/323604-1.html new file mode 100644 index 000000000..6605b5b22 --- /dev/null +++ b/layout/tables/crashtests/323604-1.html @@ -0,0 +1,10 @@ + + +
12
34
+ diff --git a/layout/tables/crashtests/323604-2.xhtml b/layout/tables/crashtests/323604-2.xhtml new file mode 100644 index 000000000..b51c91709 --- /dev/null +++ b/layout/tables/crashtests/323604-2.xhtml @@ -0,0 +1,43 @@ + + + + + + + + + + + +

Tables

+ + + + + + + + + + + + + +
TD
TD
+ + + + diff --git a/layout/tables/crashtests/32447-1.html b/layout/tables/crashtests/32447-1.html new file mode 100644 index 000000000..e33a3007b --- /dev/null +++ b/layout/tables/crashtests/32447-1.html @@ -0,0 +1,13 @@ + + + + + + + +
Test +
+ + + + diff --git a/layout/tables/crashtests/331344-1.html b/layout/tables/crashtests/331344-1.html new file mode 100644 index 000000000..0a4b9048a --- /dev/null +++ b/layout/tables/crashtests/331344-1.html @@ -0,0 +1,11 @@ + + + +
+ + +
A
B
+
+ + + \ No newline at end of file diff --git a/layout/tables/crashtests/331446-1.xhtml b/layout/tables/crashtests/331446-1.xhtml new file mode 100644 index 000000000..6aab87c8e --- /dev/null +++ b/layout/tables/crashtests/331446-1.xhtml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + +
Table
+ +
A
+ +
B
+ +
C
+ + + diff --git a/layout/tables/crashtests/331690-1.html b/layout/tables/crashtests/331690-1.html new file mode 100644 index 000000000..d0e84e3dc --- /dev/null +++ b/layout/tables/crashtests/331690-1.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +f + + + diff --git a/layout/tables/crashtests/339130-1.html b/layout/tables/crashtests/339130-1.html new file mode 100644 index 000000000..61cb6ad34 --- /dev/null +++ b/layout/tables/crashtests/339130-1.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + +
td
td
+ + + diff --git a/layout/tables/crashtests/339246-1.html b/layout/tables/crashtests/339246-1.html new file mode 100644 index 000000000..5366e263a --- /dev/null +++ b/layout/tables/crashtests/339246-1.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + +
td
tdtd
td
+ + + diff --git a/layout/tables/crashtests/339315-1.html b/layout/tables/crashtests/339315-1.html new file mode 100644 index 000000000..54e20dbf6 --- /dev/null +++ b/layout/tables/crashtests/339315-1.html @@ -0,0 +1,38 @@ + + + + + + + + + +
x
+ + + diff --git a/layout/tables/crashtests/341227-1.xhtml b/layout/tables/crashtests/341227-1.xhtml new file mode 100644 index 000000000..bca62eefa --- /dev/null +++ b/layout/tables/crashtests/341227-1.xhtml @@ -0,0 +1,30 @@ + + + + + + + + + +
+ + + + diff --git a/layout/tables/crashtests/343087-1.html b/layout/tables/crashtests/343087-1.html new file mode 100644 index 000000000..139e7898e --- /dev/null +++ b/layout/tables/crashtests/343087-1.html @@ -0,0 +1,40 @@ + + + + + +testcase + + + + + + +
x
+ + + + + + diff --git a/layout/tables/crashtests/343588-1.xhtml b/layout/tables/crashtests/343588-1.xhtml new file mode 100644 index 000000000..66854e554 --- /dev/null +++ b/layout/tables/crashtests/343588-1.xhtml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + Y + + + + + + +
AB
CD
+ + + diff --git a/layout/tables/crashtests/344000-1.html b/layout/tables/crashtests/344000-1.html new file mode 100644 index 000000000..0dbfac857 --- /dev/null +++ b/layout/tables/crashtests/344000-1.html @@ -0,0 +1,45 @@ + + + dom cellmap crash + + + + + + + + + +
x
+ + + + + + + diff --git a/layout/tables/crashtests/347367.html b/layout/tables/crashtests/347367.html new file mode 100644 index 000000000..4fe252108 --- /dev/null +++ b/layout/tables/crashtests/347367.html @@ -0,0 +1,78 @@ + + +Testcase bug 347367 - crash when print preview is opened on a certain file styled with meda=print [@ BasicTableLayoutStrategy::CalcPctAdjTableWidth] + + +
+ + +
+ + + + + + + + + + +

Customer Information

Company NameTenSen.net
Address208
CityCarthage
StateTX
Zipcode75633
Contact NameDakota
Phone Number9032353248
Email Addressdakota@tensen.net
Customer Information Additional NotesThis is a rather long "additional notes" field, just a test to see what will happen on the display page.
+ +
+ + + + + + + + + + +

Customer Information

Company NameTenSen.net
Address208
CityCarthage
StateTX
Zipcode75633
Contact NameDakota
Phone Number9032353248
Email Addressdakota@tensen.net
Customer Information Additional NotesThis is a rather long "additional notes" field, just a test to see what will happen on the display page.
+ +
+ + + + + + + + + + +

Customer Information

Company NameTenSen.net
Address208
CityCarthage
StateTX
Zipcode75633
Contact NameDakota
Phone Number9032353248
Email Addressdakota@tensen.net
Customer Information Additional NotesThis is a rather long "additional notes" field, just a test to see what will happen on the display page.
+ +
+ + + + + + + + + + +

Customer Information

Company NameTenSen.net
Address208
CityCarthage
StateTX
Zipcode75633
Contact NameDakota
Phone Number9032353248
Email Addressdakota@tensen.net
Customer Information Additional NotesThis is a rather long "additional notes" field, just a test to see what will happen on the display page.
+ + +
+ diff --git a/layout/tables/crashtests/347506-1.xhtml b/layout/tables/crashtests/347506-1.xhtml new file mode 100644 index 000000000..4119389a5 --- /dev/null +++ b/layout/tables/crashtests/347506-1.xhtml @@ -0,0 +1,23 @@ + + + + +
+ + + + x + y + + + z + w + + + + +
+ + + + \ No newline at end of file diff --git a/layout/tables/crashtests/347506-2.xhtml b/layout/tables/crashtests/347506-2.xhtml new file mode 100644 index 000000000..a6b49febb --- /dev/null +++ b/layout/tables/crashtests/347506-2.xhtml @@ -0,0 +1,14 @@ + + + + + + + + + + + +
xy
zw
+ + \ No newline at end of file diff --git a/layout/tables/crashtests/347725-1.xhtml b/layout/tables/crashtests/347725-1.xhtml new file mode 100644 index 000000000..f0f00ccd5 --- /dev/null +++ b/layout/tables/crashtests/347725-1.xhtml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + +
A
B
C
D
+ + + diff --git a/layout/tables/crashtests/348977-1.xhtml b/layout/tables/crashtests/348977-1.xhtml new file mode 100644 index 000000000..c71757eb9 --- /dev/null +++ b/layout/tables/crashtests/348977-1.xhtml @@ -0,0 +1,7 @@ + + +
+ +
+ + diff --git a/layout/tables/crashtests/350524-1.xhtml b/layout/tables/crashtests/350524-1.xhtml new file mode 100644 index 000000000..4df84ebbe --- /dev/null +++ b/layout/tables/crashtests/350524-1.xhtml @@ -0,0 +1,33 @@ + + + + + + + + + + + +

Tables

+ + + + + + + + + + + + + + + + +
td
td
tdtd
+ + + + diff --git a/layout/tables/crashtests/351326-1.xhtml b/layout/tables/crashtests/351326-1.xhtml new file mode 100644 index 000000000..7fc72c276 --- /dev/null +++ b/layout/tables/crashtests/351326-1.xhtml @@ -0,0 +1,14 @@ + + + + + + + + + + +
y
+ + + diff --git a/layout/tables/crashtests/351327-1.xhtml b/layout/tables/crashtests/351327-1.xhtml new file mode 100644 index 000000000..a9e381a20 --- /dev/null +++ b/layout/tables/crashtests/351327-1.xhtml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + +
td
+ + + + diff --git a/layout/tables/crashtests/351328-1.xhtml b/layout/tables/crashtests/351328-1.xhtml new file mode 100644 index 000000000..a7c7e10ca --- /dev/null +++ b/layout/tables/crashtests/351328-1.xhtml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + +
TDTD
+ + + diff --git a/layout/tables/crashtests/351628-1.xhtml b/layout/tables/crashtests/351628-1.xhtml new file mode 100644 index 000000000..9bbcef752 --- /dev/null +++ b/layout/tables/crashtests/351628-1.xhtml @@ -0,0 +1,14 @@ + + + + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/358679-1.xhtml b/layout/tables/crashtests/358679-1.xhtml new file mode 100644 index 000000000..ef3e40b4b --- /dev/null +++ b/layout/tables/crashtests/358679-1.xhtml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + +
AB
1
+ + + + diff --git a/layout/tables/crashtests/358871-1.xhtml b/layout/tables/crashtests/358871-1.xhtml new file mode 100644 index 000000000..1964032e7 --- /dev/null +++ b/layout/tables/crashtests/358871-1.xhtml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + +
AB
CD
+ + + + diff --git a/layout/tables/crashtests/362275.html b/layout/tables/crashtests/362275.html new file mode 100644 index 000000000..45edb6311 --- /dev/null +++ b/layout/tables/crashtests/362275.html @@ -0,0 +1,14 @@ + +Testcase bug 362275 - Hang with testcase on print preview, using -moz-column-count and table related stuff + + +This page should not hang Mozilla on print preview +
+ + + + + + +
+ diff --git a/layout/tables/crashtests/364512-1.html b/layout/tables/crashtests/364512-1.html new file mode 100644 index 000000000..3b8417e31 --- /dev/null +++ b/layout/tables/crashtests/364512-1.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + +
+ Foo +
+

Bar

+
+ + + diff --git a/layout/tables/crashtests/366556-1.xhtml b/layout/tables/crashtests/366556-1.xhtml new file mode 100644 index 000000000..16a5a7745 --- /dev/null +++ b/layout/tables/crashtests/366556-1.xhtml @@ -0,0 +1,50 @@ + + + + + + + + +

This page should not trigger assertions.

+ + + + + + + + +
Foo bar baz zap + + + + + + +
+ + + + + + + +
This text wrapsFoo
+
YYY
+
+
+ + + + \ No newline at end of file diff --git a/layout/tables/crashtests/367673-1.xhtml b/layout/tables/crashtests/367673-1.xhtml new file mode 100644 index 000000000..e7fb6493e --- /dev/null +++ b/layout/tables/crashtests/367673-1.xhtml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + +
+ + + + +
Foo
+
Bar + + + + +
Baz
+
+ + + + diff --git a/layout/tables/crashtests/367749.html b/layout/tables/crashtests/367749.html new file mode 100644 index 000000000..7fbb23fae --- /dev/null +++ b/layout/tables/crashtests/367749.html @@ -0,0 +1,14 @@ + +Testcase bug 362275 - Hang with testcase on print preview, using -moz-column-count and table related stuff + + +This page should not hang Mozilla on print preview +
+ + + + + + +
+ diff --git a/layout/tables/crashtests/367755.xhtml b/layout/tables/crashtests/367755.xhtml new file mode 100644 index 000000000..c59e5bf38 --- /dev/null +++ b/layout/tables/crashtests/367755.xhtml @@ -0,0 +1,24 @@ + + + + + + + + + + + + +
Foo
+ + + diff --git a/layout/tables/crashtests/368013.html b/layout/tables/crashtests/368013.html new file mode 100644 index 000000000..ea6760d4a --- /dev/null +++ b/layout/tables/crashtests/368013.html @@ -0,0 +1,13 @@ + + +Bug 368013 + + + + 1 + + + + +
1
+ diff --git a/layout/tables/crashtests/368166-1.xhtml b/layout/tables/crashtests/368166-1.xhtml new file mode 100644 index 000000000..7739b20d2 --- /dev/null +++ b/layout/tables/crashtests/368166-1.xhtml @@ -0,0 +1,21 @@ + + + + + + + + +
+ + + + diff --git a/layout/tables/crashtests/370360-1.html b/layout/tables/crashtests/370360-1.html new file mode 100644 index 000000000..ffcad72ab --- /dev/null +++ b/layout/tables/crashtests/370360-1.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
testa variable-height box within the area defined by the left margin +and adjacent to the bottom of the top-left-corner.
testa variable-height box within the area defined by the left margin +and adjacent to the bottom of the top-left-corner.
testa variable-height box within the area defined by the left margin +and adjacent to the bottom of the top-left-corner.
testa variable-height box within the area defined by the left margin +and adjacent to the bottom of the top-left-corner.
testa variable-height box within the area defined by the left margin +and adjacent to the bottom of the top-left-corner.
testa variable-height box within the area defined by the left margin +and adjacent to the bottom of the top-left-corner.
\ No newline at end of file diff --git a/layout/tables/crashtests/370710.xhtml b/layout/tables/crashtests/370710.xhtml new file mode 100644 index 000000000..1e4faad51 --- /dev/null +++ b/layout/tables/crashtests/370710.xhtml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + +
A
B
C
+ + + diff --git a/layout/tables/crashtests/370713-1.html b/layout/tables/crashtests/370713-1.html new file mode 100644 index 000000000..f2b8cdca7 --- /dev/null +++ b/layout/tables/crashtests/370713-1.html @@ -0,0 +1,13 @@ + + +Testcase #2 for bug 370713 + + +
+ diff --git a/layout/tables/crashtests/370876-1.html b/layout/tables/crashtests/370876-1.html new file mode 100644 index 000000000..d6ca97e18 --- /dev/null +++ b/layout/tables/crashtests/370876-1.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + +
X
+ + + + + + + + +
A
B
+ + + + diff --git a/layout/tables/crashtests/370897-1.html b/layout/tables/crashtests/370897-1.html new file mode 100644 index 000000000..8a3fa5cab --- /dev/null +++ b/layout/tables/crashtests/370897-1.html @@ -0,0 +1,45 @@ + + + + + + + + + +
+
+
+ + + diff --git a/layout/tables/crashtests/371290.html b/layout/tables/crashtests/371290.html new file mode 100644 index 000000000..0c19fdaa5 --- /dev/null +++ b/layout/tables/crashtests/371290.html @@ -0,0 +1,33 @@ + + +BC crash + + + + + + + + + + + + + + +
+
+ + diff --git a/layout/tables/crashtests/373400-1.html b/layout/tables/crashtests/373400-1.html new file mode 100644 index 000000000..bce7e5e59 --- /dev/null +++ b/layout/tables/crashtests/373400-1.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + +
L!
L-RLk:715KB
jonasLZ:13.34MBmZ:2.447MB
jonas
LTp:673msTp2:531.5125msTdhtml:1263msTxul:562msTs:1906ms
L
L TUnit223/0 reftest435/03953/0/455 chrome25/0/0
L-
LTp:748msTp2:541.15msTdhtml:1294msTxul:774msTs:2302ms
LZ:13.34MBmZ:2.447MB
surkov.alexanderL-
+ + + diff --git a/layout/tables/crashtests/373400-2.html b/layout/tables/crashtests/373400-2.html new file mode 100644 index 000000000..5e224d832 --- /dev/null +++ b/layout/tables/crashtests/373400-2.html @@ -0,0 +1,2109 @@ +tinderbox: Firefox + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Build TimeGuiltyLinux argo-vm Dep NightlyLinux bl-bldlnx01 Dep argo-vm test perfLinux bl-bldlnx01 Dep fx-linux-tbox test perfLinux fx-linux-tbox DepLinux fxdbug-linux-tbox DepLinux qm-rhel02 dep unit testMacOSX Darwin 8.8.4 bm-xserve08 Dep Universal NightlyMacOSX Darwin 8.8.4 qm-xserve01 dep unit testWINNT 5.1 bl-bldxp01 Dep fx-win32-tbox perf testWINNT 5.1 qm-winxp01 dep unit testWINNT 5.2 fx-win32-tbox Dep Nightly
Click time to
see changes
since then
Click name to see what they did
+2007/04/14 06:57:02 + + + + + + +L/ + + + + + +L/ + + + + + + +L/ + + + + + + + + +L/ + +
+06:57:00 + + + +L +
Tp:842ms
Tp2:629.6625ms
Tdhtml:1393ms
Txul:814ms
Ts:2414ms
+
+06:56:02 +
+06:55:03
+06:55:00 + + + +L +
RLk:4.71KB
Lk:1.92MB
MH:22.3MB
A:690K
+
+06:54:01 + + + +L + +
+06:44:01 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+06:43:00
+06:41:02 + + + +L + + +
+06:40:00 + + + +L +
RLk:4.71KB
Lk:1.95MB
MH:21.6MB
A:694K
+
+06:37:03 +
+06:32:02 + + + +L +
Tp:744ms
Tp2:547.425ms
Tdhtml:1321ms
Txul:756ms
Ts:2331ms
+
+06:28:02 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+06:27:00
+06:26:00 + + + +L + +
+06:24:02 +
+06:15:00 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:21.8MB
A:694K
+
+06:11:02 +
+06:10:00
+06:09:00 + + + + +L + +
+06:07:01 +
+06:06:01
+06:05:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+ + + +L- +
RLk:4.71KB
+
+06:00:02 + + + +L + D
Tp:153ms
Tdhtml:526ms
Txul:197ms
Ts:963ms
+
+2007/04/14 05:58:01 + + + +L +
Tp:829ms
Tp2:643.4375ms
Tdhtml:1390ms
Txul:827ms
Ts:2436ms
+
+05:57:00
+05:52:00 + + + +L + +
+05:51:00 + +
+05:49:01 +
+05:46:02 + + + +L +
RLk:4.71KB
Lk:2.00MB
MH:22.1MB
A:689K
+
+05:45:00
+05:42:02 + + + +L + +
+05:39:02 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+05:37:00
+05:34:03 +
+05:34:02 + + + +L- + +
+05:33:00
+05:27:00 + + + +L + +
+05:18:01 +
+05:17:01 + + + +L + D
Z:13.01MB
mZ:2.382MB
+
+05:16:00
+05:14:02 + + + +L +
Tp:863ms
Tp2:645.725ms
Tdhtml:1382ms
Txul:849ms
Ts:2436ms
+
+ + + +L + + +
+05:09:01 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:22.0MB
A:695K
+
+05:00:03 + + + +L +
Tp:752ms
Tp2:546.2875ms
Tdhtml:1313ms
Txul:775ms
Ts:2339ms
+
+05:00:00
+2007/04/14 04:59:00 + + + + +L + D +
+04:56:02 +
+04:44:00 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:21.9MB
A:697K
+
+04:41:05 +
+04:41:03 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:21.9MB
A:689K
+
+04:39:00
+04:32:02 + + + +L +
Tp:152ms
Tdhtml:527ms
Txul:192ms
Ts:958ms
+
+04:27:00 + + + +L +
Tp:856ms
Tp2:624.35ms
Tdhtml:1401ms
Txul:807ms
Ts:2417ms
+
+04:24:03 +
+04:18:00 + + + +L- + +
+04:12:03 +
+04:11:05
+04:10:00
+04:08:02 + + + +L + +
+04:07:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+04:04:02 +
+2007/04/14 03:58:01 + + + +L- +
RLk:4.71KB
+
+03:58:00
+03:57:00 + + + + +L + +
+03:49:02 + + + + +L +
Tp:736ms
Tp2:548.4ms
Tdhtml:1290ms
Txul:743ms
Ts:2345ms
+
+03:49:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+03:48:01 +
+03:47:02
+03:47:00 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:21.8MB
A:692K
+
+03:42:01 + + + +L + +
+03:40:00
+03:37:01 + + + +L +
Tp:153ms
Tdhtml:524ms
Txul:191ms
Ts:965ms
+
+03:36:00
+03:35:01 + + + + + +L + +
+03:32:00 + + + +L +
Tp:835ms
Tp2:624.8125ms
Tdhtml:1396ms
Txul:825ms
Ts:2447ms
+
+03:30:02 +
+03:28:01 + + + +L- +
RLk:4.71KB
+
+03:25:02 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+03:24:00
+03:15:03 + + + +L + +
+03:11:00 + + + +L +
Tp:736ms
Tp2:535.6125ms
Tdhtml:1322ms
Txul:765ms
Ts:2349ms
+
+03:09:05 + +
+03:06:02 + + + +L- +
RLk:4.71KB
+
+03:05:00
+03:00:01 + + + +L + +
+2007/04/14 02:54:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+02:53:02 +
+02:52:00
+02:51:01 + + + + +L + +
+02:50:00 + + + +L- +
RLk:4.71KB
+
+02:42:03 +
+02:41:00
+02:40:02 + + + +L +
Tp:151ms
Tdhtml:526ms
Txul:189ms
Ts:961ms
+
+02:39:01
+02:39:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+02:38:02 + + + +L + +
+02:36:00 + + + +L +
Tp:846ms
Tp2:616.3125ms
Tdhtml:1389ms
Txul:834ms
Ts:2420ms
+
+02:34:01 +
+02:25:00 + + + +L- + +
+02:22:01 + +
+02:21:00
+02:18:01 + + + +L + +
+02:17:00 + + + +L +
Tp:733ms
Tp2:545.85ms
Tdhtml:1317ms
Txul:740ms
Ts:2332ms
+
+02:15:07 +
+02:15:03 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:21.9MB
A:698K
+
+02:04:04 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:21.9MB
A:698K
+
+02:03:00
+02:00:00 + + + +L + +
+2007/04/14 01:59:01 + +
+01:58:04
+01:50:02 + + + +L- +
RLk:4.71KB
+
+01:50:01
+01:49:01 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+01:49:00 + + + +L +
Tp:622ms
Tp2:487.45ms
Tdhtml:1262ms
Txul:579ms
Ts:1922ms
+
+01:43:03 + + + +L +
Tp:844ms
Tp2:620.6625ms
Tdhtml:1386ms
Txul:842ms
Ts:2441ms
+
+ + + +L + +
+01:42:00
+01:41:00 +
+01:39:02 + + + +L +
Tp:153ms
Tdhtml:524ms
Txul:199ms
Ts:960ms
+
+01:38:03 + + + +L +
RLk:4.71KB
Lk:1.96MB
MH:21.9MB
A:698K
+
+01:37:03
+01:37:02
+01:37:00
+01:36:00 + + + +L + +
+01:30:04 + + + + +L +
Tp:749ms
Tp2:544.7375ms
Tdhtml:1306ms
Txul:762ms
Ts:2314ms
+
+01:30:00
+01:25:08 + + + +L + +
+01:22:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+01:20:00 + + + +L +
RLk:4.71KB
Lk:1.96MB
MH:22.0MB
A:693K
+
+01:19:08 + + + +L +
RLk:4.71KB
Lk:1.96MB
MH:22.0MB
A:693K
+
+01:06:00
+01:05:02 +
+01:03:01
+01:02:00
+01:00:02 + + + +L +
Tp:831ms
Tp2:635.375ms
Tdhtml:1377ms
Txul:828ms
Ts:2446ms
+
+ + + +L + + +
+2007/04/14 00:58:02 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:21.9MB
A:695K
+
+00:56:01
+00:49:02 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+00:49:00
+00:47:01 + + + +L + +
+00:46:00
+00:44:02 + + + + +L + +
+00:44:01
+00:42:00 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:21.9MB
A:697K
+
+00:39:02 + + + +L +
Tp:152ms
Tdhtml:523ms
Txul:191ms
Ts:961ms
+
+00:39:00
+00:36:02 + + + + +L +
Tp:713ms
Tp2:536.2375ms
Tdhtml:1328ms
Txul:761ms
Ts:2291ms
+
+00:36:00
+00:27:02 + + + +L +
Tp:845ms
Tp2:632.075ms
Tdhtml:1399ms
Txul:800ms
Ts:2435ms
+
+ + + +L + +
+00:27:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+00:25:01 +
+00:24:02 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:21.9MB
A:693K
+
+00:19:03
+00:19:01
+00:19:00
+00:11:00 + + + +L + +
+00:08:00 + + + + +L +
Tp:741ms
Tp2:537.8375ms
Tdhtml:1307ms
Txul:750ms
Ts:2349ms
+
+00:06:02 +
+00:05:02 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:22.0MB
A:697K
+
+00:05:00
+00:02:02 + + + +L +
Tp:608ms
Tp2:486.0625ms
Tdhtml:1272ms
Txul:563ms
Ts:1922ms
+
+ + + +L + +
+00:02:00
+2007/04/13 23:59:03 + + + +L + +
+23:53:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+23:51:02 +
+23:49:02 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:22.0MB
A:696K
+
+23:49:00
+23:45:01 + + + +L +
Tp:860ms
Tp2:647.2625ms
Tdhtml:1403ms
Txul:833ms
Ts:2436ms
+
+ + + +L + +
+23:43:00
+23:40:01 + + + + + +L +
Tp:151ms
Tdhtml:524ms
Txul:199ms
Ts:952ms
+
+23:39:02
+23:36:01 + + + +L +
Tp:732ms
Tp2:543.3125ms
Tdhtml:1319ms
Txul:752ms
Ts:2325ms
+
+23:35:00
+23:34:00 + + + +L + +
+23:32:01 +
+23:21:02 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+ + + +L- + +
+23:20:01
+23:20:00
+23:19:01 + + + +L +
Tp:623ms
Tp2:493.825ms
Tdhtml:1245ms
Txul:579ms
Ts:1906ms
+
+ + + +L + +
+23:18:00
+23:14:00 + + + +L +
Tp:841ms
Tp2:617.1ms
Tdhtml:1387ms
Txul:847ms
Ts:2440ms
+
+ + + +L + +
+23:12:00 +
+23:10:01 +
+23:02:07 + + + +L +
RLk:4.71KB
Lk:1.97MB
MH:21.6MB
A:692K
+
+23:02:01 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+23:01:00
+23:00:01 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+2007/04/13 22:57:01
+22:56:00
+22:54:01 +
+22:49:01 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:21.9MB
A:693K
+
+22:48:00
+22:44:02 + + + + +L +
Tp:753ms
Tp2:558.5ms
Tdhtml:1329ms
Txul:749ms
Ts:2341ms
+
+ + + +L + +
+22:42:00
+22:41:00 + + + +L +
Tp:157ms
Tdhtml:527ms
Txul:193ms
Ts:964ms
+
+22:40:02 +
+22:39:02
+22:38:00 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:22.0MB
A:698K
+
+22:37:02 + + + +L +
Tp:627ms
Tp2:489.875ms
Tdhtml:1241ms
Txul:563ms
Ts:1922ms
+
+ + + +L + +
+22:36:02 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+22:35:00
+22:25:00 + + + +L +
Tp:837ms
Tp2:624.425ms
Tdhtml:1390ms
Txul:826ms
Ts:2417ms
+
+ + + +L + +
+22:22:02 +
+22:20:01 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:21.9MB
A:698K
+
+22:20:00
+22:19:00 + + + + +L +
Tp:719ms
Tp2:536.85ms
Tdhtml:1288ms
Txul:757ms
Ts:2337ms
+
+22:09:01 + + + +L + +
+22:06:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+22:04:01 +
+22:02:02 + + + +L +
RLk:4.71KB
Lk:1.99MB
MH:18.6MB
A:698K
+
+22:01:02
+22:01:00
+2007/04/13 21:56:02 + + + +L + +
+21:55:00
+21:51:01 + + + + +L +
Tp:639ms
Tp2:490.125ms
Tdhtml:1236ms
Txul:562ms
Ts:1906ms
+
+ + + +L + +
+21:51:00
+21:49:01 + + + +L/ + +
+21:48:00
+21:45:01 + + + +L +
Tp:837ms
Tp2:623.4125ms
Tdhtml:1384ms
Txul:832ms
Ts:2431ms
+
+ + + +L + +
+21:44:01
+21:43:00
+21:42:02 + + + +L +
Tp:153ms
Tdhtml:530ms
Txul:197ms
Ts:958ms
+
+21:41:02 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+21:36:02
+21:36:00
+21:34:00 + + + +L + +
+21:27:00 + + + +L! + +
+21:21:02 + + + + +L +
Tp:745ms
Tp2:543.6375ms
Tdhtml:1328ms
Txul:756ms
Ts:2320ms
+
+21:20:02
+21:20:00
+21:19:00 +
+21:18:02 + + + +L +
Tp:838ms
Tp2:632.6875ms
Tdhtml:1404ms
Txul:844ms
Ts:2434ms
+
+ + + +L + +
+21:18:01
+21:17:01 + + + +L- + +
+21:17:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+21:03:01 + + + +L +
Tp:614ms
Tp2:498.65ms
Tdhtml:1262ms
Txul:578ms
Ts:1937ms
+
+ + + +L + +
+21:02:00
+21:01:02 + + + +L + +
+21:01:01
+2007/04/13 20:59:00
+20:57:01 + + + + +L +
Tp:740ms
Tp2:546.875ms
Tdhtml:1291ms
Txul:746ms
Ts:2342ms
+
+
+20:49:01 + + + +L +
RLk:4.71KB
Lk:1.92MB
MH:22.0MB
A:697K
+
+20:48:02
+20:48:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+20:45:00 + + + +L + +
+20:44:03 +
+20:43:01
+20:43:00 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:21.9MB
A:694K
+
+20:42:02 + + + +L +
Tp:151ms
Tdhtml:530ms
Txul:186ms
Ts:954ms
+
+20:40:01
+20:40:00
+20:36:02 + + + +L +
Tp:623ms
Tp2:489.075ms
Tdhtml:1253ms
Txul:578ms
Ts:1907ms
+
+ + + +L + +
+20:35:00
+20:29:00 + + + +L +
Tp:855ms
Tp2:612.7125ms
Tdhtml:1405ms
Txul:838ms
Ts:2406ms
+
+ + + +L + +
+20:28:00 +
+20:25:04 +
+20:22:02 + + + +L- +
RLk:4.71KB
+
+20:20:03
+20:19:02
+20:19:01 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+20:18:00
+20:07:00 + + + +L + +
+20:05:02 +
+20:03:00 + + + +L- + +
+20:02:01 + + + + +L +
Tp:747ms
Tp2:527.975ms
Tdhtml:1299ms
Txul:765ms
Ts:2318ms
+
+20:01:03
+20:01:00
+2007/04/13 19:50:02 + + + +L +
Tp:844ms
Tp2:620.475ms
Tdhtml:1394ms
Txul:839ms
Ts:2437ms
+
+ + + +L + + + + + +L +
Tp:629ms
Tp2:490.575ms
Tdhtml:1245ms
Txul:563ms
Ts:1922ms
+
+ + + +L + +
+19:50:00 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+19:48:03 +
+19:48:02
+19:47:00 + + + +L- +
RLk:4.71KB
+
+19:45:02 + + + +L + +
+19:43:00
+19:42:01 + + + +L +
Tp:153ms
Tdhtml:525ms
Txul:197ms
Ts:964ms
+
+19:36:02
+19:35:02
+19:34:00
+19:30:00 + + + + +L +
Tp:749ms
Tp2:528.725ms
Tdhtml:1318ms
Txul:764ms
Ts:2344ms
+
+ + + +L + +
+19:28:01 +
+19:24:02 + + + +L +
RLk:4.71KB
Lk:1.98MB
MH:21.9MB
A:687K
+
+19:23:01 + + + +L +
Z:13.01MB
mZ:2.382MB
+
+19:18:02
+19:17:01
+19:17:00
+19:16:00 + + + +L + +
+19:11:00 + + + +L +
Tp:843ms
Tp2:653.95ms
Tdhtml:1394ms
Txul:823ms
Ts:2444ms
+
+ + + +L + +
+19:09:01 +
+19:06:00 + + + +L +
RLk:4.71KB
Lk:2.00MB
MH:22.0MB
A:689K
+
+19:02:02 +
+2007/04/13 18:59:01
+18:59:00
+ diff --git a/layout/tables/crashtests/373400-3.html b/layout/tables/crashtests/373400-3.html new file mode 100644 index 000000000..ad32a18d0 --- /dev/null +++ b/layout/tables/crashtests/373400-3.html @@ -0,0 +1,64 @@ + + + + + + + + +
+

Location:

+ +

+We are located at 333 West San Carlos Street, Suite 1650, San Jose. +Our building has a parking garage, and we will validate your parking. +After you park, as you are walking out of the parking garage, you will +see a bank of elevators. These elevators only go up and down in the +garage – they don’t connect to the office building. You +should walk past those elevators, walk out of the garage, walk across a +courtyard, and into the doors for the main building. There is another +bank of elevators in that building. Take these elevators to the 16th +Floor. +

+ +

+From Highway 280 heading northbound to San Jose: Take the Guadalupe +Parkway exit, also called Highway 87. This exit splits into a +northbound and a southbound direction. Take the northbound direction. +Once on the Guadalupe Parkway take the first exit, which is the Santa +Clara Street exit. Bear right on Santa Clara Street as you come off +that exit. The first light you come to is Almaden Blvd. Turn right on +Almaden. Go down 3 lights to West San Carlos Street. Turn right on +West San Carlos. The next light you come to is a small street called +Woz Way. Turn right on Woz. The parking garage for our building is on +your right. Turn right into the garage. Then follow the directions +above from the garage to our office. +

+ +

+From Highway 101 heading southbound to San Jose: Take the Guadalupe +Parkway exit, also called Highway 87. Stay on the Guadalupe as it +turns into a surface street and you cross over Hedding and Coleman. +Once it turns into an expressway again, the second exit is the Park +Avenue exit. Take this exit. Turn left on Park Avenue. After you +turn left you will come under the freeway and immediately come to a +traffic light at a small street called Woz Way. Turn right on Woz +Way. The parking garage for our building is on your left. Turn left +into the garage. Then follow the directions above from the garage to +our office. +

+ +

+From Highway 101 heading northbound to San Jose: Turn on to Highway +280 headed north. Then follow directions above for Highway 280 heading +northbound to San Jose. +

+ +

+From Highway 880: Take Highway 880 to Highway 280 South, and then +follow directions above from Highway 280 heading southbound to San +Jose. +

+
+ + diff --git a/layout/tables/crashtests/373611-1.html b/layout/tables/crashtests/373611-1.html new file mode 100644 index 000000000..147901763 --- /dev/null +++ b/layout/tables/crashtests/373611-1.html @@ -0,0 +1,22 @@ + + + + + + + +
+
a
+
b
+
c c c
+
+ + + diff --git a/layout/tables/crashtests/373946-1.html b/layout/tables/crashtests/373946-1.html new file mode 100644 index 000000000..d332d722c --- /dev/null +++ b/layout/tables/crashtests/373946-1.html @@ -0,0 +1,6 @@ + +ASSERTION: no common ancestor at all??? with iframe in display: table-caption + + + + \ No newline at end of file diff --git a/layout/tables/crashtests/374356-1.html b/layout/tables/crashtests/374356-1.html new file mode 100644 index 000000000..d58c9ba4d --- /dev/null +++ b/layout/tables/crashtests/374356-1.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + +
+

A

+

B

+
+ + + diff --git a/layout/tables/crashtests/374819-1.html b/layout/tables/crashtests/374819-1.html new file mode 100644 index 000000000..bd65fd2bf --- /dev/null +++ b/layout/tables/crashtests/374819-1.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + +
A BC
D
+ + + diff --git a/layout/tables/crashtests/374819-2.html b/layout/tables/crashtests/374819-2.html new file mode 100644 index 000000000..d26cdfe07 --- /dev/null +++ b/layout/tables/crashtests/374819-2.html @@ -0,0 +1,16 @@ + + + + + + + + +
hello + + + + + +
+
diff --git a/layout/tables/crashtests/375058-1.xhtml b/layout/tables/crashtests/375058-1.xhtml new file mode 100644 index 000000000..0f0c92cd5 --- /dev/null +++ b/layout/tables/crashtests/375058-1.xhtml @@ -0,0 +1,10 @@ + + + + + +
TabIndented
+ + diff --git a/layout/tables/crashtests/378240-1.html b/layout/tables/crashtests/378240-1.html new file mode 100644 index 000000000..303028080 --- /dev/null +++ b/layout/tables/crashtests/378240-1.html @@ -0,0 +1,12 @@ + + + + + + + + +
td
caption
+ + + \ No newline at end of file diff --git a/layout/tables/crashtests/379687-1.html b/layout/tables/crashtests/379687-1.html new file mode 100644 index 000000000..e16cc22a6 --- /dev/null +++ b/layout/tables/crashtests/379687-1.html @@ -0,0 +1,14 @@ + + + + + +Foo + + diff --git a/layout/tables/crashtests/380200-1.xhtml b/layout/tables/crashtests/380200-1.xhtml new file mode 100644 index 000000000..d9cda291a --- /dev/null +++ b/layout/tables/crashtests/380200-1.xhtml @@ -0,0 +1,24 @@ + + + + + + + + +
1
+ + + + + + + + + + +
x
abc
+ + + + diff --git a/layout/tables/crashtests/385132-1.xhtml b/layout/tables/crashtests/385132-1.xhtml new file mode 100644 index 000000000..53753acab --- /dev/null +++ b/layout/tables/crashtests/385132-1.xhtml @@ -0,0 +1,21 @@ + + + + + + + + +
x
+ + + +
c
+ +
ab
+ + + + diff --git a/layout/tables/crashtests/385132-2.html b/layout/tables/crashtests/385132-2.html new file mode 100644 index 000000000..593681e08 --- /dev/null +++ b/layout/tables/crashtests/385132-2.html @@ -0,0 +1,17 @@ + + + + + + + + + + + +
a
b
+ + + diff --git a/layout/tables/crashtests/387051-1.html b/layout/tables/crashtests/387051-1.html new file mode 100644 index 000000000..b2beec86e --- /dev/null +++ b/layout/tables/crashtests/387051-1.html @@ -0,0 +1,15 @@ + + + + + +
+
+ a + b + c + d +
+
+ + diff --git a/layout/tables/crashtests/388700-1.html b/layout/tables/crashtests/388700-1.html new file mode 100644 index 000000000..f75336e15 --- /dev/null +++ b/layout/tables/crashtests/388700-1.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + +
+ + + + +
+
+ + + diff --git a/layout/tables/crashtests/391898-1.html b/layout/tables/crashtests/391898-1.html new file mode 100644 index 000000000..ff241691b --- /dev/null +++ b/layout/tables/crashtests/391898-1.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/layout/tables/crashtests/391901-1.html b/layout/tables/crashtests/391901-1.html new file mode 100644 index 000000000..fb13855df --- /dev/null +++ b/layout/tables/crashtests/391901-1.html @@ -0,0 +1,16 @@ + + + + + + +
+
+ש תות בעית בלעברמון - ד +
+
+ + + \ No newline at end of file diff --git a/layout/tables/crashtests/392132-1.xhtml b/layout/tables/crashtests/392132-1.xhtml new file mode 100644 index 000000000..b1b259255 --- /dev/null +++ b/layout/tables/crashtests/392132-1.xhtml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/layout/tables/crashtests/397448-1.html b/layout/tables/crashtests/397448-1.html new file mode 100644 index 000000000..fddf70295 --- /dev/null +++ b/layout/tables/crashtests/397448-1.html @@ -0,0 +1,7 @@ + + + + +
+ + diff --git a/layout/tables/crashtests/398157-1.xhtml b/layout/tables/crashtests/398157-1.xhtml new file mode 100644 index 000000000..45f763857 --- /dev/null +++ b/layout/tables/crashtests/398157-1.xhtml @@ -0,0 +1,5 @@ + + + + + diff --git a/layout/tables/crashtests/399209-1.xhtml b/layout/tables/crashtests/399209-1.xhtml new file mode 100644 index 000000000..932fca8b5 --- /dev/null +++ b/layout/tables/crashtests/399209-1.xhtml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/layout/tables/crashtests/403249-1.html b/layout/tables/crashtests/403249-1.html new file mode 100644 index 000000000..5f956d2ee --- /dev/null +++ b/layout/tables/crashtests/403249-1.html @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/layout/tables/crashtests/403579-1.html b/layout/tables/crashtests/403579-1.html new file mode 100644 index 000000000..6e2233282 --- /dev/null +++ b/layout/tables/crashtests/403579-1.html @@ -0,0 +1,12 @@ + + + + + + + + + +
x
y
+ + diff --git a/layout/tables/crashtests/404301-1.xhtml b/layout/tables/crashtests/404301-1.xhtml new file mode 100644 index 000000000..56b573308 --- /dev/null +++ b/layout/tables/crashtests/404301-1.xhtml @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/layout/tables/crashtests/408753-1.xhtml b/layout/tables/crashtests/408753-1.xhtml new file mode 100644 index 000000000..760d7e532 --- /dev/null +++ b/layout/tables/crashtests/408753-1.xhtml @@ -0,0 +1 @@ +foo
diff --git a/layout/tables/crashtests/410426-1.html b/layout/tables/crashtests/410426-1.html new file mode 100644 index 000000000..c9d2f33da --- /dev/null +++ b/layout/tables/crashtests/410426-1.html @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/layout/tables/crashtests/410428-1.xhtml b/layout/tables/crashtests/410428-1.xhtml new file mode 100644 index 000000000..596422d38 --- /dev/null +++ b/layout/tables/crashtests/410428-1.xhtml @@ -0,0 +1,9 @@ + + + + + +
x
+ + + diff --git a/layout/tables/crashtests/411582.xhtml b/layout/tables/crashtests/411582.xhtml new file mode 100644 index 000000000..35c6d8771 --- /dev/null +++ b/layout/tables/crashtests/411582.xhtml @@ -0,0 +1,6 @@ +
diff --git a/layout/tables/crashtests/413091.xhtml b/layout/tables/crashtests/413091.xhtml new file mode 100644 index 000000000..d9f6732fb --- /dev/null +++ b/layout/tables/crashtests/413091.xhtml @@ -0,0 +1,7 @@ + + + + +
+ + diff --git a/layout/tables/crashtests/413180-1.html b/layout/tables/crashtests/413180-1.html new file mode 100644 index 000000000..81807cc96 --- /dev/null +++ b/layout/tables/crashtests/413180-1.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/416845-1.xhtml b/layout/tables/crashtests/416845-1.xhtml new file mode 100644 index 000000000..d67b611a5 --- /dev/null +++ b/layout/tables/crashtests/416845-1.xhtml @@ -0,0 +1,7 @@ + + + + +x
+ + diff --git a/layout/tables/crashtests/416845-2.xhtml b/layout/tables/crashtests/416845-2.xhtml new file mode 100644 index 000000000..7cf09a75b --- /dev/null +++ b/layout/tables/crashtests/416845-2.xhtml @@ -0,0 +1,15 @@ + + + + + + +12
+ + diff --git a/layout/tables/crashtests/416845-3.html b/layout/tables/crashtests/416845-3.html new file mode 100644 index 000000000..6e0ac2c88 --- /dev/null +++ b/layout/tables/crashtests/416845-3.html @@ -0,0 +1,38 @@ + + + + + + + + + + + diff --git a/layout/tables/crashtests/420242-1.xhtml b/layout/tables/crashtests/420242-1.xhtml new file mode 100644 index 000000000..286bb4f7a --- /dev/null +++ b/layout/tables/crashtests/420242-1.xhtml @@ -0,0 +1,4 @@ + + +
+ diff --git a/layout/tables/crashtests/420654-1.xhtml b/layout/tables/crashtests/420654-1.xhtml new file mode 100644 index 000000000..962c44d10 --- /dev/null +++ b/layout/tables/crashtests/420654-1.xhtml @@ -0,0 +1,27 @@ + + + + + + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/423514-1.xhtml b/layout/tables/crashtests/423514-1.xhtml new file mode 100644 index 000000000..b6e3876de --- /dev/null +++ b/layout/tables/crashtests/423514-1.xhtml @@ -0,0 +1,35 @@ + + + + + + + +
+ +
+ + diff --git a/layout/tables/crashtests/430374.html b/layout/tables/crashtests/430374.html new file mode 100644 index 000000000..d5ec8101a --- /dev/null +++ b/layout/tables/crashtests/430374.html @@ -0,0 +1,31 @@ + + + + + + + diff --git a/layout/tables/crashtests/444431-1.html b/layout/tables/crashtests/444431-1.html new file mode 100644 index 000000000..821d390b5 --- /dev/null +++ b/layout/tables/crashtests/444431-1.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/layout/tables/crashtests/444702-1.html b/layout/tables/crashtests/444702-1.html new file mode 100644 index 000000000..2b30ea9c1 --- /dev/null +++ b/layout/tables/crashtests/444702-1.html @@ -0,0 +1,5 @@ + + +
x
+ + diff --git a/layout/tables/crashtests/448988-1.xhtml b/layout/tables/crashtests/448988-1.xhtml new file mode 100644 index 000000000..6811813c4 --- /dev/null +++ b/layout/tables/crashtests/448988-1.xhtml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/layout/tables/crashtests/450311-1.html b/layout/tables/crashtests/450311-1.html new file mode 100644 index 000000000..283b3774a --- /dev/null +++ b/layout/tables/crashtests/450311-1.html @@ -0,0 +1,23 @@ + + + + + + + + + + + +
1 2 34 5 6
+ + + diff --git a/layout/tables/crashtests/451170.html b/layout/tables/crashtests/451170.html new file mode 100644 index 000000000..bf5d14dca --- /dev/null +++ b/layout/tables/crashtests/451170.html @@ -0,0 +1,21 @@ + + + + + + + + +
+ +
+ + diff --git a/layout/tables/crashtests/451355-1.html b/layout/tables/crashtests/451355-1.html new file mode 100644 index 000000000..41ad1fdf8 --- /dev/null +++ b/layout/tables/crashtests/451355-1.html @@ -0,0 +1,5 @@ + + + + +diff --git a/layout/tables/crashtests/456041.html b/layout/tables/crashtests/456041.html new file mode 100644 index 000000000..fd818476e --- /dev/null +++ b/layout/tables/crashtests/456041.html @@ -0,0 +1,19 @@ + + + Bug 456041 - Crash [@ nsCellMapColumnIterator::GetNextFrame] with contenteditable, generated content on table and double tbody + + + + + + + +
+ + + +
+ + diff --git a/layout/tables/crashtests/457115.html b/layout/tables/crashtests/457115.html new file mode 100644 index 000000000..67923dc6d --- /dev/null +++ b/layout/tables/crashtests/457115.html @@ -0,0 +1,7 @@ + + + + + +
+ + + + + diff --git a/layout/tables/crashtests/460637-2.xhtml b/layout/tables/crashtests/460637-2.xhtml new file mode 100644 index 000000000..2bdaee93d --- /dev/null +++ b/layout/tables/crashtests/460637-2.xhtml @@ -0,0 +1,24 @@ + + + +
+ diff --git a/layout/tables/crashtests/460637-3.xhtml b/layout/tables/crashtests/460637-3.xhtml new file mode 100644 index 000000000..2d73a1e71 --- /dev/null +++ b/layout/tables/crashtests/460637-3.xhtml @@ -0,0 +1,26 @@ + + + + +
+ diff --git a/layout/tables/crashtests/462849.xhtml b/layout/tables/crashtests/462849.xhtml new file mode 100644 index 000000000..84df33eee --- /dev/null +++ b/layout/tables/crashtests/462849.xhtml @@ -0,0 +1,20 @@ + + + +++ +
+ + + diff --git a/layout/tables/crashtests/467141-1.html b/layout/tables/crashtests/467141-1.html new file mode 100644 index 000000000..5a6d3df5a --- /dev/null +++ b/layout/tables/crashtests/467141-1.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/layout/tables/crashtests/488388-1.html b/layout/tables/crashtests/488388-1.html new file mode 100644 index 000000000..e54deb924 --- /dev/null +++ b/layout/tables/crashtests/488388-1.html @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/layout/tables/crashtests/501870-1.html b/layout/tables/crashtests/501870-1.html new file mode 100644 index 000000000..5c386c2ea --- /dev/null +++ b/layout/tables/crashtests/501870-1.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/layout/tables/crashtests/509562-1.xhtml b/layout/tables/crashtests/509562-1.xhtml new file mode 100644 index 000000000..ae38c4f7c --- /dev/null +++ b/layout/tables/crashtests/509562-1.xhtml @@ -0,0 +1,18 @@ + + + + + + +XY
+ + + diff --git a/layout/tables/crashtests/512749-1.html b/layout/tables/crashtests/512749-1.html new file mode 100644 index 000000000..12829799e --- /dev/null +++ b/layout/tables/crashtests/512749-1.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/layout/tables/crashtests/513732-1.html b/layout/tables/crashtests/513732-1.html new file mode 100644 index 000000000..7ca35ddce --- /dev/null +++ b/layout/tables/crashtests/513732-1.html @@ -0,0 +1,7 @@ + + + + +
+ + diff --git a/layout/tables/crashtests/533380-1.xhtml b/layout/tables/crashtests/533380-1.xhtml new file mode 100644 index 000000000..4bec0ae68 --- /dev/null +++ b/layout/tables/crashtests/533380-1.xhtml @@ -0,0 +1 @@ +ab
diff --git a/layout/tables/crashtests/534716-1.html b/layout/tables/crashtests/534716-1.html new file mode 100644 index 000000000..15a547753 --- /dev/null +++ b/layout/tables/crashtests/534716-1.html @@ -0,0 +1,18 @@ + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/55789-1.html b/layout/tables/crashtests/55789-1.html new file mode 100644 index 000000000..b80109bf5 --- /dev/null +++ b/layout/tables/crashtests/55789-1.html @@ -0,0 +1,13 @@ + + + + +25016 + ++ + \ No newline at end of file diff --git a/layout/tables/crashtests/563009-1.html b/layout/tables/crashtests/563009-1.html new file mode 100644 index 000000000..c76f3e050 --- /dev/null +++ b/layout/tables/crashtests/563009-1.html @@ -0,0 +1,42 @@ + + + + + + + +
+ +
+ + +
+
+ 4659 +
+
+
+ + + + + + + + + + +
+
+
+
+ + +
+ +
diff --git a/layout/tables/crashtests/563009-2.html b/layout/tables/crashtests/563009-2.html new file mode 100644 index 000000000..daa0c8fa5 --- /dev/null +++ b/layout/tables/crashtests/563009-2.html @@ -0,0 +1,40 @@ + + + + + + + +
+ +
+ + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
diff --git a/layout/tables/crashtests/563009-3.html b/layout/tables/crashtests/563009-3.html new file mode 100644 index 000000000..8db6203ae --- /dev/null +++ b/layout/tables/crashtests/563009-3.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
diff --git a/layout/tables/crashtests/573354-1.xhtml b/layout/tables/crashtests/573354-1.xhtml new file mode 100644 index 000000000..6f8681a17 --- /dev/null +++ b/layout/tables/crashtests/573354-1.xhtml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/layout/tables/crashtests/576890-1.html b/layout/tables/crashtests/576890-1.html new file mode 100644 index 000000000..aa97e6815 --- /dev/null +++ b/layout/tables/crashtests/576890-1.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/layout/tables/crashtests/576890-2.html b/layout/tables/crashtests/576890-2.html new file mode 100644 index 000000000..5ffea0d2f --- /dev/null +++ b/layout/tables/crashtests/576890-2.html @@ -0,0 +1,8 @@ + + + + +footer +header + + diff --git a/layout/tables/crashtests/576890-3.html b/layout/tables/crashtests/576890-3.html new file mode 100644 index 000000000..b5717a96d --- /dev/null +++ b/layout/tables/crashtests/576890-3.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/layout/tables/crashtests/580481-1.xhtml b/layout/tables/crashtests/580481-1.xhtml new file mode 100644 index 000000000..32ba0dd54 --- /dev/null +++ b/layout/tables/crashtests/580481-1.xhtml @@ -0,0 +1,23 @@ + + + + + + +
+ + + diff --git a/layout/tables/crashtests/595758-1.xhtml b/layout/tables/crashtests/595758-1.xhtml new file mode 100644 index 000000000..767a43650 --- /dev/null +++ b/layout/tables/crashtests/595758-1.xhtml @@ -0,0 +1,13 @@ + + + +
  • + +
  • + + + + diff --git a/layout/tables/crashtests/595758-2.xhtml b/layout/tables/crashtests/595758-2.xhtml new file mode 100644 index 000000000..644266ef8 --- /dev/null +++ b/layout/tables/crashtests/595758-2.xhtml @@ -0,0 +1,12 @@ + + + + + + + + +
    + +
    + diff --git a/layout/tables/crashtests/678447-1.html b/layout/tables/crashtests/678447-1.html new file mode 100644 index 000000000..654b47dc4 --- /dev/null +++ b/layout/tables/crashtests/678447-1.html @@ -0,0 +1,10 @@ + + + + + + + +
    rowgroup1
    rowgroup2
    + + diff --git a/layout/tables/crashtests/691824-1.xhtml b/layout/tables/crashtests/691824-1.xhtml new file mode 100644 index 000000000..873707eea --- /dev/null +++ b/layout/tables/crashtests/691824-1.xhtml @@ -0,0 +1,279 @@ + + + + aaa + + + +
    +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + diff --git a/layout/tables/crashtests/695430-1.html b/layout/tables/crashtests/695430-1.html new file mode 100644 index 000000000..696fa731e --- /dev/null +++ b/layout/tables/crashtests/695430-1.html @@ -0,0 +1,23 @@ + + + + + +
    + + + + + + + + + + + +
    + diff --git a/layout/tables/crashtests/696640-1.html b/layout/tables/crashtests/696640-1.html new file mode 100644 index 000000000..c0f85ec80 --- /dev/null +++ b/layout/tables/crashtests/696640-1.html @@ -0,0 +1,47 @@ + + + + + crash at A4 90% generated content + repeatable tfoot + + + + + + +
    + +
    +
    + + + + + + + + + + + +
    +
    +
    +
    +
    + + diff --git a/layout/tables/crashtests/696640-2.html b/layout/tables/crashtests/696640-2.html new file mode 100644 index 000000000..52d64c7ad --- /dev/null +++ b/layout/tables/crashtests/696640-2.html @@ -0,0 +1,486 @@ + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –
    i – – vsvs. –

    Total Points

    0
    +
    +
    + + diff --git a/layout/tables/crashtests/705996-1.html b/layout/tables/crashtests/705996-1.html new file mode 100644 index 000000000..13d64dc84 --- /dev/null +++ b/layout/tables/crashtests/705996-1.html @@ -0,0 +1,6 @@ + + + +
    + + diff --git a/layout/tables/crashtests/705996-2.html b/layout/tables/crashtests/705996-2.html new file mode 100644 index 000000000..149661253 --- /dev/null +++ b/layout/tables/crashtests/705996-2.html @@ -0,0 +1,6 @@ + + + +
    + + diff --git a/layout/tables/crashtests/707622-1.html b/layout/tables/crashtests/707622-1.html new file mode 100644 index 000000000..db885f494 --- /dev/null +++ b/layout/tables/crashtests/707622-1.html @@ -0,0 +1,6 @@ + + + +
    + + diff --git a/layout/tables/crashtests/710098-1.html b/layout/tables/crashtests/710098-1.html new file mode 100644 index 000000000..4886f8388 --- /dev/null +++ b/layout/tables/crashtests/710098-1.html @@ -0,0 +1,7 @@ + + + + +
    + + diff --git a/layout/tables/crashtests/711864-1.html b/layout/tables/crashtests/711864-1.html new file mode 100644 index 000000000..554a069ce --- /dev/null +++ b/layout/tables/crashtests/711864-1.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + +
    + + diff --git a/layout/tables/crashtests/759249-1.html b/layout/tables/crashtests/759249-1.html new file mode 100644 index 000000000..e96b38b94 --- /dev/null +++ b/layout/tables/crashtests/759249-1.html @@ -0,0 +1,6 @@ + +>>>>>>\ No newline at end of file diff --git a/layout/tables/crashtests/759249-2.html b/layout/tables/crashtests/759249-2.html new file mode 100644 index 000000000..57c575eb0 --- /dev/null +++ b/layout/tables/crashtests/759249-2.html @@ -0,0 +1,10 @@ + + + +
    + + diff --git a/layout/tables/crashtests/78623-1.html b/layout/tables/crashtests/78623-1.html new file mode 100644 index 000000000..11ea838b5 --- /dev/null +++ b/layout/tables/crashtests/78623-1.html @@ -0,0 +1,17 @@ + + + +
    + + diff --git a/layout/tables/crashtests/814713.html b/layout/tables/crashtests/814713.html new file mode 100644 index 000000000..6dd903b80 --- /dev/null +++ b/layout/tables/crashtests/814713.html @@ -0,0 +1,96 @@ + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + diff --git a/layout/tables/crashtests/crashtests.list b/layout/tables/crashtests/crashtests.list new file mode 100644 index 000000000..f110053f1 --- /dev/null +++ b/layout/tables/crashtests/crashtests.list @@ -0,0 +1,159 @@ +load 28933-1.html +load 29157-1.html +load 32447-1.html +load 55789-1.html +load 78623-1.html +load 110523-1.html +load 138725-1.html +load 159359-1.html +load 187779-1.html +load 189751-1.html +load 197015-1.html +load 220536-1.html +load 223458-1.html +load 237421-1.html +load 237421-2.html +load 238909-1.html +load 239294-1.html +load 240854-1.html +load 266015-1.html +load 267418.html +load 275625.html +load 277062-1.html +load 278385-1.html +load 282175-1.html +load 284844-1.html +load 284844-1.html +load 284852.html +load 300912.html +load 308752-1.html +load 308752-2.html +load 316636-1.html +load 317876.html +load 322779-1.xul +load 323489-1.html +load 323604-1.html +load 323604-2.xhtml +load 331344-1.html +load 331446-1.xhtml +load 331690-1.html +load 339130-1.html +load 339246-1.html +load 339315-1.html +load 341227-1.xhtml +load 343087-1.html +load 343588-1.xhtml +load 344000-1.html +load 347367.html +load 347506-1.xhtml +load 347506-2.xhtml +load 347725-1.xhtml +load 348977-1.xhtml +load 350524-1.xhtml +load 351326-1.xhtml +load 351327-1.xhtml +load 351328-1.xhtml +load 351628-1.xhtml +load 358679-1.xhtml +load 358871-1.xhtml +load 362275.html +load 364512-1.html +load 366556-1.xhtml +load 367673-1.xhtml +load 367749.html +load 367755.xhtml +load 368013.html +load 368166-1.xhtml +load 370360-1.html +load 370710.xhtml +load 370713-1.html +load 370876-1.html +load 370897-1.html +load 371290.html +load 373400-1.html +load 373400-2.html +load 373400-3.html +load 373611-1.html +load 373946-1.html +load 374356-1.html +load 374819-1.html +load 374819-2.html +load 375058-1.xhtml +load 378240-1.html +load 379687-1.html +load 380200-1.xhtml +load 385132-1.xhtml +load 385132-2.html +load 387051-1.html +load 388700-1.html +load 391898-1.html +load 391901-1.html +load 392132-1.xhtml +load 397448-1.html +load 398157-1.xhtml +load 399209-1.xhtml +load 403249-1.html +load 403579-1.html +load 404301-1.xhtml +load 408753-1.xhtml +load 410426-1.html +load 410428-1.xhtml +load 411582.xhtml +load 413091.xhtml +load 413180-1.html +load 416845-1.xhtml +load 416845-2.xhtml +load 416845-3.html +load 420242-1.xhtml +asserts(8) load 420654-1.xhtml # bug 458238, bug 436123, bug 457397 +load 423514-1.xhtml +load 430374.html +load 444431-1.html +load 444702-1.html +load 448988-1.xhtml +load 450311-1.html +load 451170.html +load 451355-1.html +load 456041.html +load 457115.html +load 460637-1.xhtml +load 460637-2.xhtml +load 460637-3.xhtml +load 462849.xhtml +load 467141-1.html +load 488388-1.html +load 501870-1.html +load 509562-1.xhtml +load 512749-1.html +load 513732-1.html +load 533380-1.xhtml +load 534716-1.html +load 563009-1.html +load 563009-2.html +load 563009-3.html +load 573354-1.xhtml +load 576890-1.html +load 576890-2.html +load 576890-3.html +load 580481-1.xhtml +asserts(1) load 595758-1.xhtml # Bug 714667 +load 595758-2.xhtml +load 678447-1.html +load 691824-1.xhtml +load 695430-1.html +load 696640-1.html +load 696640-2.html +load 705996-1.html +load 705996-2.html +load 707622-1.html +load 710098-1.html +load 711864-1.html +asserts-if(gtkWidget&&browserIsRemote,5) load 759249-1.html # Bug 1195474 +load 759249-2.html +load 814713.html +load 1027611-1.html +load 1031934.html +load 1183896.html +load 1223282.html +load 1223232.html +load 1243623-1.html diff --git a/layout/tables/moz.build b/layout/tables/moz.build new file mode 100644 index 000000000..b77776320 --- /dev/null +++ b/layout/tables/moz.build @@ -0,0 +1,46 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'Layout: Tables') + +MOCHITEST_MANIFESTS += ['test/mochitest.ini'] + +EXPORTS += [ + 'nsITableCellLayout.h', +] + +UNIFIED_SOURCES += [ + 'BasicTableLayoutStrategy.cpp', + 'FixedTableLayoutStrategy.cpp', + 'nsCellMap.cpp', + 'nsTableCellFrame.cpp', + 'nsTableColFrame.cpp', + 'nsTableColGroupFrame.cpp', + 'nsTableFrame.cpp', + 'nsTablePainter.cpp', + 'nsTableRowFrame.cpp', + 'nsTableRowGroupFrame.cpp', + 'nsTableWrapperFrame.cpp', + 'SpanningCellSorter.cpp', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../../intl/unicharutil/util', + '../base', + '../generic', + '../style', + '../xul', + '/dom/base', + '/dom/html', +] + +DEFINES['DEBUG_TABLE_STRATEGY_off'] = True + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/layout/tables/nsCellMap.cpp b/layout/tables/nsCellMap.cpp new file mode 100644 index 000000000..bdd12cf70 --- /dev/null +++ b/layout/tables/nsCellMap.cpp @@ -0,0 +1,2716 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTArray.h" +#include "nsCellMap.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsTableRowFrame.h" +#include "nsTableRowGroupFrame.h" +#include + +using namespace mozilla; + +static void +SetDamageArea(int32_t aStartCol, + int32_t aStartRow, + int32_t aColCount, + int32_t aRowCount, + TableArea& aDamageArea) +{ + NS_ASSERTION(aStartCol >= 0, "negative col index"); + NS_ASSERTION(aStartRow >= 0, "negative row index"); + NS_ASSERTION(aColCount >= 0, "negative col count"); + NS_ASSERTION(aRowCount >= 0, "negative row count"); + aDamageArea.StartCol() = aStartCol; + aDamageArea.StartRow() = aStartRow; + aDamageArea.ColCount() = aColCount; + aDamageArea.RowCount() = aRowCount; +} + +// Empty static array used for SafeElementAt() calls on mRows. +static nsCellMap::CellDataArray * sEmptyRow; + +// CellData + +CellData::CellData(nsTableCellFrame* aOrigCell) +{ + MOZ_COUNT_CTOR(CellData); + static_assert(sizeof(mOrigCell) == sizeof(mBits), + "mOrigCell and mBits must be the same size"); + mOrigCell = aOrigCell; +} + +CellData::~CellData() +{ + MOZ_COUNT_DTOR(CellData); +} + +BCCellData::BCCellData(nsTableCellFrame* aOrigCell) +:CellData(aOrigCell) +{ + MOZ_COUNT_CTOR(BCCellData); +} + +BCCellData::~BCCellData() +{ + MOZ_COUNT_DTOR(BCCellData); +} + +// nsTableCellMap + +nsTableCellMap::nsTableCellMap(nsTableFrame& aTableFrame, + bool aBorderCollapse) +:mTableFrame(aTableFrame), mFirstMap(nullptr), mBCInfo(nullptr) +{ + MOZ_COUNT_CTOR(nsTableCellMap); + + nsTableFrame::RowGroupArray orderedRowGroups; + aTableFrame.OrderRowGroups(orderedRowGroups); + + nsTableRowGroupFrame* prior = nullptr; + for (uint32_t rgX = 0; rgX < orderedRowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgX]; + InsertGroupCellMap(rgFrame, prior); + prior = rgFrame; + } + if (aBorderCollapse) { + mBCInfo = new BCInfo(); + } +} + +nsTableCellMap::~nsTableCellMap() +{ + MOZ_COUNT_DTOR(nsTableCellMap); + + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + nsCellMap* next = cellMap->GetNextSibling(); + delete cellMap; + cellMap = next; + } + + if (mBCInfo) { + DeleteIEndBEndBorders(); + delete mBCInfo; + } +} + +// Get the bcData holding the border segments of the iEnd edge of the table +BCData* +nsTableCellMap::GetIEndMostBorder(int32_t aRowIndex) +{ + if (!mBCInfo) ABORT1(nullptr); + + int32_t numRows = mBCInfo->mIEndBorders.Length(); + if (aRowIndex < numRows) { + return &mBCInfo->mIEndBorders.ElementAt(aRowIndex); + } + + mBCInfo->mIEndBorders.SetLength(aRowIndex+1); + return &mBCInfo->mIEndBorders.ElementAt(aRowIndex); +} + +// Get the bcData holding the border segments of the bEnd edge of the table +BCData* +nsTableCellMap::GetBEndMostBorder(int32_t aColIndex) +{ + if (!mBCInfo) ABORT1(nullptr); + + int32_t numCols = mBCInfo->mBEndBorders.Length(); + if (aColIndex < numCols) { + return &mBCInfo->mBEndBorders.ElementAt(aColIndex); + } + + mBCInfo->mBEndBorders.SetLength(aColIndex+1); + return &mBCInfo->mBEndBorders.ElementAt(aColIndex); +} + +// delete the borders corresponding to the iEnd and bEnd edges of the table +void +nsTableCellMap::DeleteIEndBEndBorders() +{ + if (mBCInfo) { + mBCInfo->mBEndBorders.Clear(); + mBCInfo->mIEndBorders.Clear(); + } +} + +void +nsTableCellMap::InsertGroupCellMap(nsCellMap* aPrevMap, + nsCellMap& aNewMap) +{ + nsCellMap* next; + if (aPrevMap) { + next = aPrevMap->GetNextSibling(); + aPrevMap->SetNextSibling(&aNewMap); + } + else { + next = mFirstMap; + mFirstMap = &aNewMap; + } + aNewMap.SetNextSibling(next); +} + +void nsTableCellMap::InsertGroupCellMap(nsTableRowGroupFrame* aNewGroup, + nsTableRowGroupFrame*& aPrevGroup) +{ + nsCellMap* newMap = new nsCellMap(aNewGroup, mBCInfo != nullptr); + nsCellMap* prevMap = nullptr; + nsCellMap* lastMap = mFirstMap; + if (aPrevGroup) { + nsCellMap* map = mFirstMap; + while (map) { + lastMap = map; + if (map->GetRowGroup() == aPrevGroup) { + prevMap = map; + break; + } + map = map->GetNextSibling(); + } + } + if (!prevMap) { + if (aPrevGroup) { + prevMap = lastMap; + aPrevGroup = (prevMap) ? prevMap->GetRowGroup() : nullptr; + } + else { + aPrevGroup = nullptr; + } + } + InsertGroupCellMap(prevMap, *newMap); +} + +void nsTableCellMap::RemoveGroupCellMap(nsTableRowGroupFrame* aGroup) +{ + nsCellMap* map = mFirstMap; + nsCellMap* prior = nullptr; + while (map) { + if (map->GetRowGroup() == aGroup) { + nsCellMap* next = map->GetNextSibling(); + if (mFirstMap == map) { + mFirstMap = next; + } + else { + prior->SetNextSibling(next); + } + delete map; + break; + } + prior = map; + map = map->GetNextSibling(); + } +} + +static nsCellMap* +FindMapFor(const nsTableRowGroupFrame* aRowGroup, + nsCellMap* aStart, + const nsCellMap* aEnd) +{ + for (nsCellMap* map = aStart; map != aEnd; map = map->GetNextSibling()) { + if (aRowGroup == map->GetRowGroup()) { + return map; + } + } + + return nullptr; +} + +nsCellMap* +nsTableCellMap::GetMapFor(const nsTableRowGroupFrame* aRowGroup, + nsCellMap* aStartHint) const +{ + NS_PRECONDITION(aRowGroup, "Must have a rowgroup"); + NS_ASSERTION(!aRowGroup->GetPrevInFlow(), "GetMapFor called with continuation"); + if (aStartHint) { + nsCellMap* map = FindMapFor(aRowGroup, aStartHint, nullptr); + if (map) { + return map; + } + } + + nsCellMap* map = FindMapFor(aRowGroup, mFirstMap, aStartHint); + if (map) { + return map; + } + + // if aRowGroup is a repeated header or footer find the header or footer it was repeated from + if (aRowGroup->IsRepeatable()) { + nsTableFrame* fifTable = static_cast(mTableFrame.FirstInFlow()); + + const nsStyleDisplay* display = aRowGroup->StyleDisplay(); + nsTableRowGroupFrame* rgOrig = + (StyleDisplay::TableHeaderGroup == display->mDisplay) ? + fifTable->GetTHead() : fifTable->GetTFoot(); + // find the row group cell map using the original header/footer + if (rgOrig && rgOrig != aRowGroup) { + return GetMapFor(rgOrig, aStartHint); + } + } + + return nullptr; +} + +void +nsTableCellMap::Synchronize(nsTableFrame* aTableFrame) +{ + nsTableFrame::RowGroupArray orderedRowGroups; + AutoTArray maps; + + aTableFrame->OrderRowGroups(orderedRowGroups); + if (!orderedRowGroups.Length()) { + return; + } + + // XXXbz this fails if orderedRowGroups is missing some row groups + // (due to OOM when appending to the array, e.g. -- we leak maps in + // that case). + + // Scope |map| outside the loop so we can use it as a hint. + nsCellMap* map = nullptr; + for (uint32_t rgX = 0; rgX < orderedRowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgX]; + map = GetMapFor(static_cast(rgFrame->FirstInFlow()), + map); + if (map) { + if (!maps.AppendElement(map)) { + delete map; + map = nullptr; + NS_WARNING("Could not AppendElement"); + break; + } + } + } + if (maps.IsEmpty()) { + MOZ_ASSERT(!mFirstMap); + return; + } + + int32_t mapIndex = maps.Length() - 1; // Might end up -1 + nsCellMap* nextMap = maps.ElementAt(mapIndex); + nextMap->SetNextSibling(nullptr); + for (mapIndex-- ; mapIndex >= 0; mapIndex--) { + nsCellMap* map = maps.ElementAt(mapIndex); + map->SetNextSibling(nextMap); + nextMap = map; + } + mFirstMap = nextMap; +} + +bool +nsTableCellMap::HasMoreThanOneCell(int32_t aRowIndex) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* map = mFirstMap; + while (map) { + if (map->GetRowCount() > rowIndex) { + return map->HasMoreThanOneCell(rowIndex); + } + rowIndex -= map->GetRowCount(); + map = map->GetNextSibling(); + } + return false; +} + +int32_t +nsTableCellMap::GetNumCellsOriginatingInRow(int32_t aRowIndex) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* map = mFirstMap; + while (map) { + if (map->GetRowCount() > rowIndex) { + return map->GetNumCellsOriginatingInRow(rowIndex); + } + rowIndex -= map->GetRowCount(); + map = map->GetNextSibling(); + } + return 0; +} +int32_t +nsTableCellMap::GetEffectiveRowSpan(int32_t aRowIndex, + int32_t aColIndex) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* map = mFirstMap; + while (map) { + if (map->GetRowCount() > rowIndex) { + return map->GetRowSpan(rowIndex, aColIndex, true); + } + rowIndex -= map->GetRowCount(); + map = map->GetNextSibling(); + } + NS_NOTREACHED("Bogus row index?"); + return 0; +} + +int32_t +nsTableCellMap::GetEffectiveColSpan(int32_t aRowIndex, + int32_t aColIndex) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* map = mFirstMap; + while (map) { + if (map->GetRowCount() > rowIndex) { + return map->GetEffectiveColSpan(*this, rowIndex, aColIndex); + } + rowIndex -= map->GetRowCount(); + map = map->GetNextSibling(); + } + NS_NOTREACHED("Bogus row index?"); + return 0; +} + +nsTableCellFrame* +nsTableCellMap::GetCellFrame(int32_t aRowIndex, + int32_t aColIndex, + CellData& aData, + bool aUseRowIfOverlap) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* map = mFirstMap; + while (map) { + if (map->GetRowCount() > rowIndex) { + return map->GetCellFrame(rowIndex, aColIndex, aData, aUseRowIfOverlap); + } + rowIndex -= map->GetRowCount(); + map = map->GetNextSibling(); + } + return nullptr; +} + +nsColInfo* +nsTableCellMap::GetColInfoAt(int32_t aColIndex) +{ + int32_t numColsToAdd = aColIndex + 1 - mCols.Length(); + if (numColsToAdd > 0) { + AddColsAtEnd(numColsToAdd); // XXX this could fail to add cols in theory + } + return &mCols.ElementAt(aColIndex); +} + +int32_t +nsTableCellMap::GetRowCount() const +{ + int32_t numRows = 0; + nsCellMap* map = mFirstMap; + while (map) { + numRows += map->GetRowCount(); + map = map->GetNextSibling(); + } + return numRows; +} + +CellData* +nsTableCellMap::GetDataAt(int32_t aRowIndex, + int32_t aColIndex) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* map = mFirstMap; + while (map) { + if (map->GetRowCount() > rowIndex) { + return map->GetDataAt(rowIndex, aColIndex); + } + rowIndex -= map->GetRowCount(); + map = map->GetNextSibling(); + } + return nullptr; +} + +void +nsTableCellMap::AddColsAtEnd(uint32_t aNumCols) +{ + if (!mCols.AppendElements(aNumCols)) { + NS_WARNING("Could not AppendElement"); + } + if (mBCInfo) { + if (!mBCInfo->mBEndBorders.AppendElements(aNumCols)) { + NS_WARNING("Could not AppendElement"); + } + } +} + +void +nsTableCellMap::RemoveColsAtEnd() +{ + // Remove the cols at the end which don't have originating cells or cells spanning + // into them. Only do this if the col was created as eColAnonymousCell + int32_t numCols = GetColCount(); + int32_t lastGoodColIndex = mTableFrame.GetIndexOfLastRealCol(); + for (int32_t colX = numCols - 1; (colX >= 0) && (colX > lastGoodColIndex); colX--) { + nsColInfo& colInfo = mCols.ElementAt(colX); + if ((colInfo.mNumCellsOrig <= 0) && (colInfo.mNumCellsSpan <= 0)) { + mCols.RemoveElementAt(colX); + + if (mBCInfo) { + int32_t count = mBCInfo->mBEndBorders.Length(); + if (colX < count) { + mBCInfo->mBEndBorders.RemoveElementAt(colX); + } + } + } + else break; // only remove until we encounter the 1st valid one + } +} + +void +nsTableCellMap::ClearCols() +{ + mCols.Clear(); + if (mBCInfo) + mBCInfo->mBEndBorders.Clear(); +} +void +nsTableCellMap::InsertRows(nsTableRowGroupFrame* aParent, + nsTArray& aRows, + int32_t aFirstRowIndex, + bool aConsiderSpans, + TableArea& aDamageArea) +{ + int32_t numNewRows = aRows.Length(); + if ((numNewRows <= 0) || (aFirstRowIndex < 0)) ABORT0(); + + int32_t rowIndex = aFirstRowIndex; + int32_t rgStartRowIndex = 0; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + nsTableRowGroupFrame* rg = cellMap->GetRowGroup(); + if (rg == aParent) { + cellMap->InsertRows(*this, aRows, rowIndex, aConsiderSpans, + rgStartRowIndex, aDamageArea); +#ifdef DEBUG_TABLE_CELLMAP + Dump("after InsertRows"); +#endif + if (mBCInfo) { + int32_t count = mBCInfo->mIEndBorders.Length(); + if (aFirstRowIndex < count) { + for (int32_t rowX = aFirstRowIndex; rowX < aFirstRowIndex + numNewRows; rowX++) { + mBCInfo->mIEndBorders.InsertElementAt(rowX); + } + } + else { + GetIEndMostBorder(aFirstRowIndex); // this will create missing entries + for (int32_t rowX = aFirstRowIndex + 1; rowX < aFirstRowIndex + numNewRows; rowX++) { + mBCInfo->mIEndBorders.AppendElement(); + } + } + } + return; + } + int32_t rowCount = cellMap->GetRowCount(); + rgStartRowIndex += rowCount; + rowIndex -= rowCount; + cellMap = cellMap->GetNextSibling(); + } + + NS_ERROR("Attempt to insert row into wrong map."); +} + +void +nsTableCellMap::RemoveRows(int32_t aFirstRowIndex, + int32_t aNumRowsToRemove, + bool aConsiderSpans, + TableArea& aDamageArea) +{ + int32_t rowIndex = aFirstRowIndex; + int32_t rgStartRowIndex = 0; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + int32_t rowCount = cellMap->GetRowCount(); + if (rowCount > rowIndex) { + cellMap->RemoveRows(*this, rowIndex, aNumRowsToRemove, aConsiderSpans, + rgStartRowIndex, aDamageArea); + if (mBCInfo) { + for (int32_t rowX = aFirstRowIndex + aNumRowsToRemove - 1; rowX >= aFirstRowIndex; rowX--) { + if (uint32_t(rowX) < mBCInfo->mIEndBorders.Length()) { + mBCInfo->mIEndBorders.RemoveElementAt(rowX); + } + } + } + break; + } + rgStartRowIndex += rowCount; + rowIndex -= rowCount; + cellMap = cellMap->GetNextSibling(); + } +#ifdef DEBUG_TABLE_CELLMAP + Dump("after RemoveRows"); +#endif +} + + + +CellData* +nsTableCellMap::AppendCell(nsTableCellFrame& aCellFrame, + int32_t aRowIndex, + bool aRebuildIfNecessary, + TableArea& aDamageArea) +{ + MOZ_ASSERT(&aCellFrame == aCellFrame.FirstInFlow(), + "invalid call on continuing frame"); + nsIFrame* rgFrame = aCellFrame.GetParent(); // get the row + if (!rgFrame) return 0; + rgFrame = rgFrame->GetParent(); // get the row group + if (!rgFrame) return 0; + + CellData* result = nullptr; + int32_t rowIndex = aRowIndex; + int32_t rgStartRowIndex = 0; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + if (cellMap->GetRowGroup() == rgFrame) { + result = cellMap->AppendCell(*this, &aCellFrame, rowIndex, + aRebuildIfNecessary, rgStartRowIndex, + aDamageArea); + break; + } + int32_t rowCount = cellMap->GetRowCount(); + rgStartRowIndex += rowCount; + rowIndex -= rowCount; + cellMap = cellMap->GetNextSibling(); + } +#ifdef DEBUG_TABLE_CELLMAP + Dump("after AppendCell"); +#endif + return result; +} + + +void +nsTableCellMap::InsertCells(nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndexBefore, + TableArea& aDamageArea) +{ + int32_t rowIndex = aRowIndex; + int32_t rgStartRowIndex = 0; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + int32_t rowCount = cellMap->GetRowCount(); + if (rowCount > rowIndex) { + cellMap->InsertCells(*this, aCellFrames, rowIndex, aColIndexBefore, + rgStartRowIndex, aDamageArea); + break; + } + rgStartRowIndex += rowCount; + rowIndex -= rowCount; + cellMap = cellMap->GetNextSibling(); + } +#ifdef DEBUG_TABLE_CELLMAP + Dump("after InsertCells"); +#endif +} + + +void +nsTableCellMap::RemoveCell(nsTableCellFrame* aCellFrame, + int32_t aRowIndex, + TableArea& aDamageArea) +{ + if (!aCellFrame) ABORT0(); + MOZ_ASSERT(aCellFrame == aCellFrame->FirstInFlow(), + "invalid call on continuing frame"); + int32_t rowIndex = aRowIndex; + int32_t rgStartRowIndex = 0; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + int32_t rowCount = cellMap->GetRowCount(); + if (rowCount > rowIndex) { + cellMap->RemoveCell(*this, aCellFrame, rowIndex, rgStartRowIndex, + aDamageArea); +#ifdef DEBUG_TABLE_CELLMAP + Dump("after RemoveCell"); +#endif + return; + } + rgStartRowIndex += rowCount; + rowIndex -= rowCount; + cellMap = cellMap->GetNextSibling(); + } + // if we reach this point - the cell did not get removed, the caller of this routine + // will delete the cell and the cellmap will probably hold a reference to + // the deleted cell which will cause a subsequent crash when this cell is + // referenced later + NS_ERROR("nsTableCellMap::RemoveCell - could not remove cell"); +} + +void +nsTableCellMap::RebuildConsideringCells(nsCellMap* aCellMap, + nsTArray* aCellFrames, + int32_t aRowIndex, + int32_t aColIndex, + bool aInsert, + TableArea& aDamageArea) +{ + int32_t numOrigCols = GetColCount(); + ClearCols(); + nsCellMap* cellMap = mFirstMap; + int32_t rowCount = 0; + while (cellMap) { + if (cellMap == aCellMap) { + cellMap->RebuildConsideringCells(*this, numOrigCols, aCellFrames, + aRowIndex, aColIndex, aInsert); + } + else { + cellMap->RebuildConsideringCells(*this, numOrigCols, nullptr, -1, 0, + false); + } + rowCount += cellMap->GetRowCount(); + cellMap = cellMap->GetNextSibling(); + } + SetDamageArea(0, 0, GetColCount(), rowCount, aDamageArea); +} + +void +nsTableCellMap::RebuildConsideringRows(nsCellMap* aCellMap, + int32_t aStartRowIndex, + nsTArray* aRowsToInsert, + int32_t aNumRowsToRemove, + TableArea& aDamageArea) +{ + NS_PRECONDITION(!aRowsToInsert || aNumRowsToRemove == 0, + "Can't handle both removing and inserting rows at once"); + + int32_t numOrigCols = GetColCount(); + ClearCols(); + nsCellMap* cellMap = mFirstMap; + int32_t rowCount = 0; + while (cellMap) { + if (cellMap == aCellMap) { + cellMap->RebuildConsideringRows(*this, aStartRowIndex, aRowsToInsert, + aNumRowsToRemove); + } + else { + cellMap->RebuildConsideringCells(*this, numOrigCols, nullptr, -1, 0, + false); + } + rowCount += cellMap->GetRowCount(); + cellMap = cellMap->GetNextSibling(); + } + SetDamageArea(0, 0, GetColCount(), rowCount, aDamageArea); +} + +int32_t +nsTableCellMap::GetNumCellsOriginatingInCol(int32_t aColIndex) const +{ + int32_t colCount = mCols.Length(); + if ((aColIndex >= 0) && (aColIndex < colCount)) { + return mCols.ElementAt(aColIndex).mNumCellsOrig; + } + else { + NS_ERROR("nsCellMap::GetNumCellsOriginatingInCol - bad col index"); + return 0; + } +} + +#ifdef DEBUG +void +nsTableCellMap::Dump(char* aString) const +{ + if (aString) + printf("%s \n", aString); + printf("***** START TABLE CELL MAP DUMP ***** %p\n", (void*)this); + // output col info + int32_t colCount = mCols.Length(); + printf ("cols array orig/span-> %p", (void*)this); + for (int32_t colX = 0; colX < colCount; colX++) { + const nsColInfo& colInfo = mCols.ElementAt(colX); + printf ("%d=%d/%d ", colX, colInfo.mNumCellsOrig, colInfo.mNumCellsSpan); + } + printf(" cols in cache %d\n", int(mTableFrame.GetColCache().Length())); + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + cellMap->Dump(nullptr != mBCInfo); + cellMap = cellMap->GetNextSibling(); + } + if (nullptr != mBCInfo) { + printf("***** block-end borders *****\n"); + nscoord size; + BCBorderOwner owner; + LogicalSide side; + bool segStart; + bool bevel; + int32_t colIndex; + int32_t numCols = mBCInfo->mBEndBorders.Length(); + for (int32_t i = 0; i <= 2; i++) { + + printf("\n "); + for (colIndex = 0; colIndex < numCols; colIndex++) { + BCData& cd = mBCInfo->mBEndBorders.ElementAt(colIndex); + if (0 == i) { + size = cd.GetBStartEdge(owner, segStart); + printf("t=%d%X%d ", int32_t(size), owner, segStart); + } + else if (1 == i) { + size = cd.GetIStartEdge(owner, segStart); + printf("l=%d%X%d ", int32_t(size), owner, segStart); + } + else { + size = cd.GetCorner(side, bevel); + printf("c=%d%X%d ", int32_t(size), side, bevel); + } + } + BCData& cd = mBCInfo->mBEndIEndCorner; + if (0 == i) { + size = cd.GetBStartEdge(owner, segStart); + printf("t=%d%X%d ", int32_t(size), owner, segStart); + } + else if (1 == i) { + size = cd.GetIStartEdge(owner, segStart); + printf("l=%d%X%d ", int32_t(size), owner, segStart); + } + else { + size = cd.GetCorner(side, bevel); + printf("c=%d%X%d ", int32_t(size), side, bevel); + } + } + printf("\n"); + } + printf("***** END TABLE CELL MAP DUMP *****\n"); +} +#endif + +nsTableCellFrame* +nsTableCellMap::GetCellInfoAt(int32_t aRowIndex, + int32_t aColIndex, + bool* aOriginates, + int32_t* aColSpan) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + if (cellMap->GetRowCount() > rowIndex) { + return cellMap->GetCellInfoAt(*this, rowIndex, aColIndex, aOriginates, aColSpan); + } + rowIndex -= cellMap->GetRowCount(); + cellMap = cellMap->GetNextSibling(); + } + return nullptr; +} + +int32_t +nsTableCellMap::GetIndexByRowAndColumn(int32_t aRow, int32_t aColumn) const +{ + int32_t index = 0; + + int32_t colCount = mCols.Length(); + int32_t rowIndex = aRow; + + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + int32_t rowCount = cellMap->GetRowCount(); + if (rowIndex >= rowCount) { + // If the rowCount is less than the rowIndex, this means that the index is + // not within the current map. If so, get the index of the last cell in + // the last row. + rowIndex -= rowCount; + + int32_t cellMapIdx = cellMap->GetHighestIndex(colCount); + if (cellMapIdx != -1) + index += cellMapIdx + 1; + + } else { + // Index is in valid range for this cellmap, so get the index of rowIndex + // and aColumn. + int32_t cellMapIdx = cellMap->GetIndexByRowAndColumn(colCount, rowIndex, + aColumn); + if (cellMapIdx == -1) + return -1; // no cell at the given row and column. + + index += cellMapIdx; + return index; // no need to look through further maps here + } + + cellMap = cellMap->GetNextSibling(); + } + + return -1; +} + +void +nsTableCellMap::GetRowAndColumnByIndex(int32_t aIndex, + int32_t *aRow, int32_t *aColumn) const +{ + *aRow = -1; + *aColumn = -1; + + int32_t colCount = mCols.Length(); + + int32_t previousRows = 0; + int32_t index = aIndex; + + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + int32_t rowCount = cellMap->GetRowCount(); + // Determine the highest possible index in this map to see + // if wanted index is in here. + int32_t cellMapIdx = cellMap->GetHighestIndex(colCount); + if (cellMapIdx == -1) { + // The index is not within this map, increase the total row index + // accordingly. + previousRows += rowCount; + } else { + if (index > cellMapIdx) { + // The index is not within this map, so decrease it by the cellMapIdx + // determined index and increase the total row index accordingly. + index -= cellMapIdx + 1; + previousRows += rowCount; + } else { + cellMap->GetRowAndColumnByIndex(colCount, index, aRow, aColumn); + // If there were previous indexes, take them into account. + *aRow += previousRows; + return; // no need to look any further. + } + } + + cellMap = cellMap->GetNextSibling(); + } +} + +bool nsTableCellMap::RowIsSpannedInto(int32_t aRowIndex, + int32_t aNumEffCols) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + if (cellMap->GetRowCount() > rowIndex) { + return cellMap->RowIsSpannedInto(rowIndex, aNumEffCols); + } + rowIndex -= cellMap->GetRowCount(); + cellMap = cellMap->GetNextSibling(); + } + return false; +} + +bool nsTableCellMap::RowHasSpanningCells(int32_t aRowIndex, + int32_t aNumEffCols) const +{ + int32_t rowIndex = aRowIndex; + nsCellMap* cellMap = mFirstMap; + while (cellMap) { + if (cellMap->GetRowCount() > rowIndex) { + return cellMap->RowHasSpanningCells(rowIndex, aNumEffCols); + } + rowIndex -= cellMap->GetRowCount(); + cellMap = cellMap->GetNextSibling(); + } + return false; +} + +void +nsTableCellMap::ResetBStartStart(LogicalSide aSide, + nsCellMap& aCellMap, + uint32_t aRowIndex, + uint32_t aColIndex, + bool aIsBEndIEnd) +{ + if (!mBCInfo || aIsBEndIEnd) ABORT0(); + + BCCellData* cellData; + BCData* bcData = nullptr; + + switch(aSide) { + case eLogicalSideBEnd: + aRowIndex++; + MOZ_FALLTHROUGH; + case eLogicalSideBStart: + cellData = (BCCellData*)aCellMap.GetDataAt(aRowIndex, aColIndex); + if (cellData) { + bcData = &cellData->mData; + } + else { + NS_ASSERTION(aSide == eLogicalSideBEnd, "program error"); + // try the next row group + nsCellMap* cellMap = aCellMap.GetNextSibling(); + if (cellMap) { + cellData = (BCCellData*)cellMap->GetDataAt(0, aColIndex); + if (cellData) { + bcData = &cellData->mData; + } + else { + bcData = GetBEndMostBorder(aColIndex); + } + } + } + break; + case eLogicalSideIEnd: + aColIndex++; + MOZ_FALLTHROUGH; + case eLogicalSideIStart: + cellData = (BCCellData*)aCellMap.GetDataAt(aRowIndex, aColIndex); + if (cellData) { + bcData = &cellData->mData; + } + else { + NS_ASSERTION(aSide == eLogicalSideIEnd, "program error"); + bcData = GetIEndMostBorder(aRowIndex); + } + break; + } + if (bcData) { + bcData->SetBStartStart(false); + } +} + +// store the aSide border segment at coord = (aRowIndex, aColIndex). For bStart/iStart, store +// the info at coord. For bEnd/iStart store it at the adjacent location so that it is +// bStart/iStart at that location. If the new location is at the iEnd or bEnd edge of the +// table, then store it one of the special arrays (iEnd-most borders, bEnd-most borders). +void +nsTableCellMap::SetBCBorderEdge(LogicalSide aSide, + nsCellMap& aCellMap, + uint32_t aCellMapStart, + uint32_t aRowIndex, + uint32_t aColIndex, + uint32_t aLength, + BCBorderOwner aOwner, + nscoord aSize, + bool aChanged) +{ + if (!mBCInfo) ABORT0(); + + BCCellData* cellData; + int32_t lastIndex, xIndex, yIndex; + int32_t xPos = aColIndex; + int32_t yPos = aRowIndex; + int32_t rgYPos = aRowIndex - aCellMapStart; + bool changed; + + switch(aSide) { + case eLogicalSideBEnd: + rgYPos++; + yPos++; + MOZ_FALLTHROUGH; + case eLogicalSideBStart: + lastIndex = xPos + aLength - 1; + for (xIndex = xPos; xIndex <= lastIndex; xIndex++) { + changed = aChanged && (xIndex == xPos); + BCData* bcData = nullptr; + cellData = (BCCellData*)aCellMap.GetDataAt(rgYPos, xIndex); + if (!cellData) { + int32_t numRgRows = aCellMap.GetRowCount(); + if (yPos < numRgRows) { // add a dead cell data + TableArea damageArea; + cellData = (BCCellData*)aCellMap.AppendCell(*this, nullptr, rgYPos, + false, 0, damageArea); + if (!cellData) ABORT0(); + } + else { + NS_ASSERTION(aSide == eLogicalSideBEnd, "program error"); + // try the next non empty row group + nsCellMap* cellMap = aCellMap.GetNextSibling(); + while (cellMap && (0 == cellMap->GetRowCount())) { + cellMap = cellMap->GetNextSibling(); + } + if (cellMap) { + cellData = (BCCellData*)cellMap->GetDataAt(0, xIndex); + if (!cellData) { // add a dead cell + TableArea damageArea; + cellData = (BCCellData*)cellMap->AppendCell(*this, nullptr, 0, + false, 0, + damageArea); + } + } + else { // must be at the end of the table + bcData = GetBEndMostBorder(xIndex); + } + } + } + if (!bcData && cellData) { + bcData = &cellData->mData; + } + if (bcData) { + bcData->SetBStartEdge(aOwner, aSize, changed); + } + else NS_ERROR("Cellmap: BStart edge not found"); + } + break; + case eLogicalSideIEnd: + xPos++; + MOZ_FALLTHROUGH; + case eLogicalSideIStart: + // since bStart, bEnd borders were set, there should already be a cellData entry + lastIndex = rgYPos + aLength - 1; + for (yIndex = rgYPos; yIndex <= lastIndex; yIndex++) { + changed = aChanged && (yIndex == rgYPos); + cellData = (BCCellData*)aCellMap.GetDataAt(yIndex, xPos); + if (cellData) { + cellData->mData.SetIStartEdge(aOwner, aSize, changed); + } + else { + NS_ASSERTION(aSide == eLogicalSideIEnd, "program error"); + BCData* bcData = GetIEndMostBorder(yIndex + aCellMapStart); + if (bcData) { + bcData->SetIStartEdge(aOwner, aSize, changed); + } + else NS_ERROR("Cellmap: IStart edge not found"); + } + } + break; + } +} + +// store corner info (aOwner, aSubSize, aBevel). For aCorner = eBStartIStart, store the info at +// (aRowIndex, aColIndex). For eBStartIEnd, store it in the entry to the iEnd-wards where +// it would be BStartIStart. For eBEndIEnd, store it in the entry to the bEnd-wards. etc. +void +nsTableCellMap::SetBCBorderCorner(Corner aCorner, + nsCellMap& aCellMap, + uint32_t aCellMapStart, + uint32_t aRowIndex, + uint32_t aColIndex, + LogicalSide aOwner, + nscoord aSubSize, + bool aBevel, + bool aIsBEndIEnd) +{ + if (!mBCInfo) ABORT0(); + + if (aIsBEndIEnd) { + mBCInfo->mBEndIEndCorner.SetCorner(aSubSize, aOwner, aBevel); + return; + } + + int32_t xPos = aColIndex; + int32_t yPos = aRowIndex; + int32_t rgYPos = aRowIndex - aCellMapStart; + + if (eBStartIEnd == aCorner) { + xPos++; + } + else if (eBEndIEnd == aCorner) { + xPos++; + rgYPos++; + yPos++; + } + else if (eBEndIStart == aCorner) { + rgYPos++; + yPos++; + } + + BCCellData* cellData = nullptr; + BCData* bcData = nullptr; + if (GetColCount() <= xPos) { + NS_ASSERTION(xPos == GetColCount(), "program error"); + // at the iEnd edge of the table as we checked the corner before + NS_ASSERTION(!aIsBEndIEnd, "should be handled before"); + bcData = GetIEndMostBorder(yPos); + } + else { + cellData = (BCCellData*)aCellMap.GetDataAt(rgYPos, xPos); + if (!cellData) { + int32_t numRgRows = aCellMap.GetRowCount(); + if (yPos < numRgRows) { // add a dead cell data + TableArea damageArea; + cellData = (BCCellData*)aCellMap.AppendCell(*this, nullptr, rgYPos, + false, 0, damageArea); + } + else { + // try the next non empty row group + nsCellMap* cellMap = aCellMap.GetNextSibling(); + while (cellMap && (0 == cellMap->GetRowCount())) { + cellMap = cellMap->GetNextSibling(); + } + if (cellMap) { + cellData = (BCCellData*)cellMap->GetDataAt(0, xPos); + if (!cellData) { // add a dead cell + TableArea damageArea; + cellData = (BCCellData*)cellMap->AppendCell(*this, nullptr, 0, + false, 0, damageArea); + } + } + else { // must be at the bEnd of the table + bcData = GetBEndMostBorder(xPos); + } + } + } + } + if (!bcData && cellData) { + bcData = &cellData->mData; + } + if (bcData) { + bcData->SetCorner(aSubSize, aOwner, aBevel); + } + else NS_ERROR("program error: Corner not found"); +} + +nsCellMap::nsCellMap(nsTableRowGroupFrame* aRowGroup, bool aIsBC) + : mRows(8), mContentRowCount(0), mRowGroupFrame(aRowGroup), + mNextSibling(nullptr), mIsBC(aIsBC), + mPresContext(aRowGroup->PresContext()) +{ + MOZ_COUNT_CTOR(nsCellMap); + NS_ASSERTION(mPresContext, "Must have prescontext"); +} + +nsCellMap::~nsCellMap() +{ + MOZ_COUNT_DTOR(nsCellMap); + + uint32_t mapRowCount = mRows.Length(); + for (uint32_t rowX = 0; rowX < mapRowCount; rowX++) { + CellDataArray &row = mRows[rowX]; + uint32_t colCount = row.Length(); + for (uint32_t colX = 0; colX < colCount; colX++) { + DestroyCellData(row[colX]); + } + } +} + +/* static */ +void +nsCellMap::Init() +{ + MOZ_ASSERT(!sEmptyRow, "How did that happen?"); + sEmptyRow = new nsCellMap::CellDataArray(); +} + +/* static */ +void +nsCellMap::Shutdown() +{ + delete sEmptyRow; + sEmptyRow = nullptr; +} + +nsTableCellFrame* +nsCellMap::GetCellFrame(int32_t aRowIndexIn, + int32_t aColIndexIn, + CellData& aData, + bool aUseRowIfOverlap) const +{ + int32_t rowIndex = aRowIndexIn - aData.GetRowSpanOffset(); + int32_t colIndex = aColIndexIn - aData.GetColSpanOffset(); + if (aData.IsOverlap()) { + if (aUseRowIfOverlap) { + colIndex = aColIndexIn; + } + else { + rowIndex = aRowIndexIn; + } + } + + CellData* data = + mRows.SafeElementAt(rowIndex, *sEmptyRow).SafeElementAt(colIndex); + if (data) { + return data->GetCellFrame(); + } + return nullptr; +} + +int32_t +nsCellMap::GetHighestIndex(int32_t aColCount) +{ + int32_t index = -1; + int32_t rowCount = mRows.Length(); + for (int32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + const CellDataArray& row = mRows[rowIdx]; + + for (int32_t colIdx = 0; colIdx < aColCount; colIdx++) { + CellData* data = row.SafeElementAt(colIdx); + // No data means row doesn't have more cells. + if (!data) + break; + + if (data->IsOrig()) + index++; + } + } + + return index; +} + +int32_t +nsCellMap::GetIndexByRowAndColumn(int32_t aColCount, + int32_t aRow, int32_t aColumn) const +{ + if (uint32_t(aRow) >= mRows.Length()) + return -1; + + int32_t index = -1; + int32_t lastColsIdx = aColCount - 1; + + // Find row index of the cell where row span is started. + const CellDataArray& row = mRows[aRow]; + CellData* data = row.SafeElementAt(aColumn); + int32_t origRow = data ? aRow - data->GetRowSpanOffset() : aRow; + + // Calculate cell index. + for (int32_t rowIdx = 0; rowIdx <= origRow; rowIdx++) { + const CellDataArray& row = mRows[rowIdx]; + int32_t colCount = (rowIdx == origRow) ? aColumn : lastColsIdx; + + for (int32_t colIdx = 0; colIdx <= colCount; colIdx++) { + data = row.SafeElementAt(colIdx); + // No data means row doesn't have more cells. + if (!data) + break; + + if (data->IsOrig()) + index++; + } + } + + // Given row and column don't point to the cell. + if (!data) + return -1; + + return index; +} + +void +nsCellMap::GetRowAndColumnByIndex(int32_t aColCount, int32_t aIndex, + int32_t *aRow, int32_t *aColumn) const +{ + *aRow = -1; + *aColumn = -1; + + int32_t index = aIndex; + int32_t rowCount = mRows.Length(); + + for (int32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + const CellDataArray& row = mRows[rowIdx]; + + for (int32_t colIdx = 0; colIdx < aColCount; colIdx++) { + CellData* data = row.SafeElementAt(colIdx); + + // The row doesn't have more cells. + if (!data) + break; + + if (data->IsOrig()) + index--; + + if (index < 0) { + *aRow = rowIdx; + *aColumn = colIdx; + return; + } + } + } +} + +bool nsCellMap::Grow(nsTableCellMap& aMap, + int32_t aNumRows, + int32_t aRowIndex) +{ + NS_ASSERTION(aNumRows >= 1, "Why are we calling this?"); + + // Get the number of cols we want to use for preallocating the row arrays. + int32_t numCols = aMap.GetColCount(); + if (numCols == 0) { + numCols = 4; + } + uint32_t startRowIndex = (aRowIndex >= 0) ? aRowIndex : mRows.Length(); + NS_ASSERTION(startRowIndex <= mRows.Length(), "Missing grow call inbetween"); + + return mRows.InsertElementsAt(startRowIndex, aNumRows, numCols) != nullptr; +} + +void nsCellMap::GrowRow(CellDataArray& aRow, + int32_t aNumCols) + +{ + // Have to have the cast to get the template to do the right thing. + aRow.InsertElementsAt(aRow.Length(), aNumCols, (CellData*)nullptr); +} + +void +nsCellMap::InsertRows(nsTableCellMap& aMap, + nsTArray& aRows, + int32_t aFirstRowIndex, + bool aConsiderSpans, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + int32_t numCols = aMap.GetColCount(); + NS_ASSERTION(aFirstRowIndex >= 0, "nsCellMap::InsertRows called with negative rowIndex"); + if (uint32_t(aFirstRowIndex) > mRows.Length()) { + // create (aFirstRowIndex - mRows.Length()) empty rows up to aFirstRowIndex + int32_t numEmptyRows = aFirstRowIndex - mRows.Length(); + if (!Grow(aMap, numEmptyRows)) { + return; + } + } + + if (!aConsiderSpans) { + // update mContentRowCount, since non-empty rows will be added + mContentRowCount = std::max(aFirstRowIndex, mContentRowCount); + ExpandWithRows(aMap, aRows, aFirstRowIndex, aRgFirstRowIndex, aDamageArea); + return; + } + + // if any cells span into or out of the row being inserted, then rebuild + bool spansCauseRebuild = CellsSpanInOrOut(aFirstRowIndex, + aFirstRowIndex, 0, numCols - 1); + + // update mContentRowCount, since non-empty rows will be added + mContentRowCount = std::max(aFirstRowIndex, mContentRowCount); + + // if any of the new cells span out of the new rows being added, then rebuild + // XXX it would be better to only rebuild the portion of the map that follows the new rows + if (!spansCauseRebuild && (uint32_t(aFirstRowIndex) < mRows.Length())) { + spansCauseRebuild = CellsSpanOut(aRows); + } + if (spansCauseRebuild) { + aMap.RebuildConsideringRows(this, aFirstRowIndex, &aRows, 0, aDamageArea); + } + else { + ExpandWithRows(aMap, aRows, aFirstRowIndex, aRgFirstRowIndex, aDamageArea); + } +} + +void +nsCellMap::RemoveRows(nsTableCellMap& aMap, + int32_t aFirstRowIndex, + int32_t aNumRowsToRemove, + bool aConsiderSpans, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + int32_t numRows = mRows.Length(); + int32_t numCols = aMap.GetColCount(); + + if (aFirstRowIndex >= numRows) { + // reduce the content based row count based on the function arguments + // as they are known to be real rows even if the cell map did not create + // rows for them before. + mContentRowCount -= aNumRowsToRemove; + return; + } + if (!aConsiderSpans) { + ShrinkWithoutRows(aMap, aFirstRowIndex, aNumRowsToRemove, aRgFirstRowIndex, + aDamageArea); + return; + } + int32_t endRowIndex = aFirstRowIndex + aNumRowsToRemove - 1; + if (endRowIndex >= numRows) { + NS_ERROR("nsCellMap::RemoveRows tried to remove too many rows"); + endRowIndex = numRows - 1; + } + bool spansCauseRebuild = CellsSpanInOrOut(aFirstRowIndex, endRowIndex, + 0, numCols - 1); + if (spansCauseRebuild) { + aMap.RebuildConsideringRows(this, aFirstRowIndex, nullptr, aNumRowsToRemove, + aDamageArea); + } + else { + ShrinkWithoutRows(aMap, aFirstRowIndex, aNumRowsToRemove, aRgFirstRowIndex, + aDamageArea); + } +} + + + + +CellData* +nsCellMap::AppendCell(nsTableCellMap& aMap, + nsTableCellFrame* aCellFrame, + int32_t aRowIndex, + bool aRebuildIfNecessary, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea, + int32_t* aColToBeginSearch) +{ + NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch"); + int32_t origNumMapRows = mRows.Length(); + int32_t origNumCols = aMap.GetColCount(); + bool zeroRowSpan = false; + int32_t rowSpan = (aCellFrame) ? GetRowSpanForNewCell(aCellFrame, aRowIndex, + zeroRowSpan) : 1; + // add new rows if necessary + int32_t endRowIndex = aRowIndex + rowSpan - 1; + if (endRowIndex >= origNumMapRows) { + // XXXbz handle allocation failures? + Grow(aMap, 1 + endRowIndex - origNumMapRows); + } + + // get the first null or dead CellData in the desired row. It will equal origNumCols if there are none + CellData* origData = nullptr; + int32_t startColIndex = 0; + if (aColToBeginSearch) + startColIndex = *aColToBeginSearch; + for (; startColIndex < origNumCols; startColIndex++) { + CellData* data = GetDataAt(aRowIndex, startColIndex); + if (!data) + break; + // The border collapse code relies on having multiple dead cell data entries + // in a row. + if (data->IsDead() && aCellFrame) { + origData = data; + break; + } + } + // We found the place to append the cell, when the next cell is appended + // the next search does not need to duplicate the search but can start + // just at the next cell. + if (aColToBeginSearch) + *aColToBeginSearch = startColIndex + 1; + + int32_t colSpan = aCellFrame ? aCellFrame->GetColSpan() : 1; + + // if the new cell could potentially span into other rows and collide with + // originating cells there, we will play it safe and just rebuild the map + if (aRebuildIfNecessary && (aRowIndex < mContentRowCount - 1) && (rowSpan > 1)) { + AutoTArray newCellArray; + newCellArray.AppendElement(aCellFrame); + aMap.RebuildConsideringCells(this, &newCellArray, aRowIndex, startColIndex, true, aDamageArea); + return origData; + } + mContentRowCount = std::max(mContentRowCount, aRowIndex + 1); + + // add new cols to the table map if necessary + int32_t endColIndex = startColIndex + colSpan - 1; + if (endColIndex >= origNumCols) { + NS_ASSERTION(aCellFrame, "dead cells should not require new columns"); + aMap.AddColsAtEnd(1 + endColIndex - origNumCols); + } + + // Setup CellData for this cell + if (origData) { + NS_ASSERTION(origData->IsDead(), "replacing a non dead cell is a memory leak"); + if (aCellFrame) { // do nothing to replace a dead cell with a dead cell + origData->Init(aCellFrame); + // we are replacing a dead cell, increase the number of cells + // originating at this column + nsColInfo* colInfo = aMap.GetColInfoAt(startColIndex); + NS_ASSERTION(colInfo, "access to a non existing column"); + if (colInfo) { + colInfo->mNumCellsOrig++; + } + } + } + else { + origData = AllocCellData(aCellFrame); + if (!origData) ABORT1(origData); + SetDataAt(aMap, *origData, aRowIndex, startColIndex); + } + + if (aRebuildIfNecessary) { + //the caller depends on the damageArea + // The special case for zeroRowSpan is to adjust for the '2' in + // GetRowSpanForNewCell. + uint32_t height = zeroRowSpan ? endRowIndex - aRowIndex : + 1 + endRowIndex - aRowIndex; + SetDamageArea(startColIndex, aRgFirstRowIndex + aRowIndex, + 1 + endColIndex - startColIndex, height, aDamageArea); + } + + if (!aCellFrame) { + return origData; + } + + // initialize the cell frame + aCellFrame->SetColIndex(startColIndex); + + // Create CellData objects for the rows that this cell spans. Set + // their mOrigCell to nullptr and their mSpanData to point to data. + for (int32_t rowX = aRowIndex; rowX <= endRowIndex; rowX++) { + // The row at rowX will need to have at least endColIndex columns + mRows[rowX].SetCapacity(endColIndex); + for (int32_t colX = startColIndex; colX <= endColIndex; colX++) { + if ((rowX != aRowIndex) || (colX != startColIndex)) { // skip orig cell data done above + CellData* cellData = GetDataAt(rowX, colX); + if (cellData) { + if (cellData->IsOrig()) { + NS_ERROR("cannot overlap originating cell"); + continue; + } + if (rowX > aRowIndex) { // row spanning into cell + if (cellData->IsRowSpan()) { + // do nothing, this can be caused by rowspan which is overlapped + // by a another cell with a rowspan and a colspan + } + else { + cellData->SetRowSpanOffset(rowX - aRowIndex); + if (zeroRowSpan) { + cellData->SetZeroRowSpan(true); + } + } + } + if (colX > startColIndex) { // col spanning into cell + if (!cellData->IsColSpan()) { + if (cellData->IsRowSpan()) { + cellData->SetOverlap(true); + } + cellData->SetColSpanOffset(colX - startColIndex); + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + colInfo->mNumCellsSpan++; + } + } + } + else { + cellData = AllocCellData(nullptr); + if (!cellData) return origData; + if (rowX > aRowIndex) { + cellData->SetRowSpanOffset(rowX - aRowIndex); + if (zeroRowSpan) { + cellData->SetZeroRowSpan(true); + } + } + if (colX > startColIndex) { + cellData->SetColSpanOffset(colX - startColIndex); + } + SetDataAt(aMap, *cellData, rowX, colX); + } + } + } + } +#ifdef DEBUG_TABLE_CELLMAP + printf("appended cell=%p row=%d \n", aCellFrame, aRowIndex); + aMap.Dump(); +#endif + return origData; +} + +bool nsCellMap::CellsSpanOut(nsTArray& aRows) const +{ + int32_t numNewRows = aRows.Length(); + for (int32_t rowX = 0; rowX < numNewRows; rowX++) { + nsIFrame* rowFrame = (nsIFrame *) aRows.ElementAt(rowX); + for (nsIFrame* childFrame : rowFrame->PrincipalChildList()) { + nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); + if (cellFrame) { + bool zeroSpan; + int32_t rowSpan = GetRowSpanForNewCell(cellFrame, rowX, zeroSpan); + if (zeroSpan || rowX + rowSpan > numNewRows) { + return true; + } + } + } + } + return false; +} + +// return true if any cells have rows spans into or out of the region +// defined by the row and col indices or any cells have colspans into the region +bool nsCellMap::CellsSpanInOrOut(int32_t aStartRowIndex, + int32_t aEndRowIndex, + int32_t aStartColIndex, + int32_t aEndColIndex) const +{ + /* + * this routine will watch the cells adjacent to the region or at the edge + * they are marked with *. The routine will verify whether they span in or + * are spanned out. + * + * startCol endCol + * r1c1 r1c2 r1c3 r1c4 r1c5 r1rc6 r1c7 + * startrow r2c1 r2c2 *r2c3 *r2c4 *r2c5 *r2rc6 r2c7 + * endrow r3c1 r3c2 *r3c3 r3c4 r3c5 *r3rc6 r3c7 + * r4c1 r4c2 *r4c3 *r4c4 *r4c5 r4rc6 r4c7 + * r5c1 r5c2 r5c3 r5c4 r5c5 r5rc6 r5c7 + */ + + int32_t numRows = mRows.Length(); // use the cellmap rows to determine the + // current cellmap extent. + for (int32_t colX = aStartColIndex; colX <= aEndColIndex; colX++) { + CellData* cellData; + if (aStartRowIndex > 0) { + cellData = GetDataAt(aStartRowIndex, colX); + if (cellData && (cellData->IsRowSpan())) { + return true; // there is a row span into the region + } + if ((aStartRowIndex >= mContentRowCount) && (mContentRowCount > 0)) { + cellData = GetDataAt(mContentRowCount - 1, colX); + if (cellData && cellData->IsZeroRowSpan()) { + return true; // When we expand the zerospan it'll span into our row + } + } + } + if (aEndRowIndex < numRows - 1) { // is there anything below aEndRowIndex + cellData = GetDataAt(aEndRowIndex + 1, colX); + if ((cellData) && (cellData->IsRowSpan())) { + return true; // there is a row span out of the region + } + } + else { + cellData = GetDataAt(aEndRowIndex, colX); + if ((cellData) && (cellData->IsRowSpan()) && (mContentRowCount < numRows)) { + return true; // this cell might be the cause of a dead row + } + } + } + if (aStartColIndex > 0) { + for (int32_t rowX = aStartRowIndex; rowX <= aEndRowIndex; rowX++) { + CellData* cellData = GetDataAt(rowX, aStartColIndex); + if (cellData && (cellData->IsColSpan())) { + return true; // there is a col span into the region + } + cellData = GetDataAt(rowX, aEndColIndex + 1); + if (cellData && (cellData->IsColSpan())) { + return true; // there is a col span out of the region + } + } + } + return false; +} + +void nsCellMap::InsertCells(nsTableCellMap& aMap, + nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndexBefore, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + if (aCellFrames.Length() == 0) return; + NS_ASSERTION(aColIndexBefore >= -1, "index out of range"); + int32_t numCols = aMap.GetColCount(); + if (aColIndexBefore >= numCols) { + NS_ERROR("Inserting instead of appending cells indicates a serious cellmap error"); + aColIndexBefore = numCols - 1; + } + + // get the starting col index of the 1st new cells + int32_t startColIndex; + for (startColIndex = aColIndexBefore + 1; startColIndex < numCols; startColIndex++) { + CellData* data = GetDataAt(aRowIndex, startColIndex); + if (!data || data->IsOrig() || data->IsDead()) { + // // Not a span. Stop. + break; + } + } + + // record whether inserted cells are going to cause complications due + // to existing row spans, col spans or table sizing. + bool spansCauseRebuild = false; + + // check that all cells have the same row span + int32_t numNewCells = aCellFrames.Length(); + bool zeroRowSpan = false; + int32_t rowSpan = 0; + for (int32_t cellX = 0; cellX < numNewCells; cellX++) { + nsTableCellFrame* cell = aCellFrames.ElementAt(cellX); + int32_t rowSpan2 = GetRowSpanForNewCell(cell, aRowIndex, zeroRowSpan); + if (rowSpan == 0) { + rowSpan = rowSpan2; + } + else if (rowSpan != rowSpan2) { + spansCauseRebuild = true; + break; + } + } + + // check if the new cells will cause the table to add more rows + if (!spansCauseRebuild) { + if (mRows.Length() < uint32_t(aRowIndex + rowSpan)) { + spansCauseRebuild = true; + } + } + + if (!spansCauseRebuild) { + spansCauseRebuild = CellsSpanInOrOut(aRowIndex, aRowIndex + rowSpan - 1, + startColIndex, numCols - 1); + } + if (spansCauseRebuild) { + aMap.RebuildConsideringCells(this, &aCellFrames, aRowIndex, startColIndex, + true, aDamageArea); + } + else { + ExpandWithCells(aMap, aCellFrames, aRowIndex, startColIndex, rowSpan, + zeroRowSpan, aRgFirstRowIndex, aDamageArea); + } +} + +void +nsCellMap::ExpandWithRows(nsTableCellMap& aMap, + nsTArray& aRowFrames, + int32_t aStartRowIndexIn, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + int32_t startRowIndex = (aStartRowIndexIn >= 0) ? aStartRowIndexIn : 0; + NS_ASSERTION(uint32_t(startRowIndex) <= mRows.Length(), "caller should have grown cellmap before"); + + int32_t numNewRows = aRowFrames.Length(); + mContentRowCount += numNewRows; + + int32_t endRowIndex = startRowIndex + numNewRows - 1; + + // shift the rows after startRowIndex down and insert empty rows that will + // be filled via the AppendCell call below + if (!Grow(aMap, numNewRows, startRowIndex)) { + return; + } + + + int32_t newRowIndex = 0; + for (int32_t rowX = startRowIndex; rowX <= endRowIndex; rowX++) { + nsTableRowFrame* rFrame = aRowFrames.ElementAt(newRowIndex); + // append cells + int32_t colIndex = 0; + for (nsIFrame* cFrame : rFrame->PrincipalChildList()) { + nsTableCellFrame *cellFrame = do_QueryFrame(cFrame); + if (cellFrame) { + AppendCell(aMap, cellFrame, rowX, false, aRgFirstRowIndex, aDamageArea, + &colIndex); + } + } + newRowIndex++; + } + // mark all following rows damaged, they might contain a previously set + // damage area which we can not shift. + int32_t firstDamagedRow = aRgFirstRowIndex + startRowIndex; + SetDamageArea(0, firstDamagedRow, aMap.GetColCount(), + aMap.GetRowCount() - firstDamagedRow, aDamageArea); +} + +void nsCellMap::ExpandWithCells(nsTableCellMap& aMap, + nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndex, + int32_t aRowSpan, // same for all cells + bool aRowSpanIsZero, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch"); + int32_t endRowIndex = aRowIndex + aRowSpan - 1; + int32_t startColIndex = aColIndex; + int32_t endColIndex = aColIndex; + int32_t numCells = aCellFrames.Length(); + int32_t totalColSpan = 0; + + // add cellData entries for the space taken up by the new cells + for (int32_t cellX = 0; cellX < numCells; cellX++) { + nsTableCellFrame* cellFrame = aCellFrames.ElementAt(cellX); + CellData* origData = AllocCellData(cellFrame); // the originating cell + if (!origData) return; + + // set the starting and ending col index for the new cell + int32_t colSpan = cellFrame->GetColSpan(); + totalColSpan += colSpan; + if (cellX == 0) { + endColIndex = aColIndex + colSpan - 1; + } + else { + startColIndex = endColIndex + 1; + endColIndex = startColIndex + colSpan - 1; + } + + // add the originating cell data and any cell data corresponding to row/col spans + for (int32_t rowX = aRowIndex; rowX <= endRowIndex; rowX++) { + CellDataArray& row = mRows[rowX]; + // Pre-allocate all the cells we'll need in this array, setting + // them to null. + // Have to have the cast to get the template to do the right thing. + int32_t insertionIndex = row.Length(); + if (insertionIndex > startColIndex) { + insertionIndex = startColIndex; + } + if (!row.InsertElementsAt(insertionIndex, endColIndex - insertionIndex + 1, + (CellData*)nullptr) && + rowX == aRowIndex) { + // Failed to insert the slots, and this is the very first row. That + // means that we need to clean up |origData| before returning, since + // the cellmap doesn't own it yet. + DestroyCellData(origData); + return; + } + + for (int32_t colX = startColIndex; colX <= endColIndex; colX++) { + CellData* data = origData; + if ((rowX != aRowIndex) || (colX != startColIndex)) { + data = AllocCellData(nullptr); + if (!data) return; + if (rowX > aRowIndex) { + data->SetRowSpanOffset(rowX - aRowIndex); + if (aRowSpanIsZero) { + data->SetZeroRowSpan(true); + } + } + if (colX > startColIndex) { + data->SetColSpanOffset(colX - startColIndex); + } + } + SetDataAt(aMap, *data, rowX, colX); + } + } + cellFrame->SetColIndex(startColIndex); + } + int32_t damageHeight = std::min(GetRowGroup()->GetRowCount() - aRowIndex, + aRowSpan); + SetDamageArea(aColIndex, aRgFirstRowIndex + aRowIndex, + 1 + endColIndex - aColIndex, damageHeight, aDamageArea); + + int32_t rowX; + + // update the row and col info due to shifting + for (rowX = aRowIndex; rowX <= endRowIndex; rowX++) { + CellDataArray& row = mRows[rowX]; + uint32_t numCols = row.Length(); + uint32_t colX; + for (colX = aColIndex + totalColSpan; colX < numCols; colX++) { + CellData* data = row[colX]; + if (data) { + // increase the origin and span counts beyond the spanned cols + if (data->IsOrig()) { + // a cell that gets moved needs adjustment as well as it new orignating col + data->GetCellFrame()->SetColIndex(colX); + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + colInfo->mNumCellsOrig++; + } + if (data->IsColSpan()) { + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + colInfo->mNumCellsSpan++; + } + + // decrease the origin and span counts within the spanned cols + int32_t colX2 = colX - totalColSpan; + nsColInfo* colInfo2 = aMap.GetColInfoAt(colX2); + if (data->IsOrig()) { + // the old originating col of a moved cell needs adjustment + colInfo2->mNumCellsOrig--; + } + if (data->IsColSpan()) { + colInfo2->mNumCellsSpan--; + } + } + } + } +} + +void nsCellMap::ShrinkWithoutRows(nsTableCellMap& aMap, + int32_t aStartRowIndex, + int32_t aNumRowsToRemove, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch"); + int32_t endRowIndex = aStartRowIndex + aNumRowsToRemove - 1; + uint32_t colCount = aMap.GetColCount(); + for (int32_t rowX = endRowIndex; rowX >= aStartRowIndex; --rowX) { + CellDataArray& row = mRows[rowX]; + uint32_t colX; + for (colX = 0; colX < colCount; colX++) { + CellData* data = row.SafeElementAt(colX); + if (data) { + // Adjust the column counts. + if (data->IsOrig()) { + // Decrement the column count. + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + colInfo->mNumCellsOrig--; + } + // colspan=0 is only counted as a spanned cell in the 1st col it spans + else if (data->IsColSpan()) { + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + colInfo->mNumCellsSpan--; + } + } + } + + uint32_t rowLength = row.Length(); + // Delete our row information. + for (colX = 0; colX < rowLength; colX++) { + DestroyCellData(row[colX]); + } + + mRows.RemoveElementAt(rowX); + + // Decrement our row and next available index counts. + mContentRowCount--; + } + aMap.RemoveColsAtEnd(); + // mark all following rows damaged, they might contain a previously set + // damage area which we can not shift. + int32_t firstDamagedRow = aRgFirstRowIndex + aStartRowIndex; + SetDamageArea(0, firstDamagedRow, aMap.GetColCount(), + aMap.GetRowCount() - firstDamagedRow, aDamageArea); +} + +int32_t nsCellMap::GetEffectiveColSpan(const nsTableCellMap& aMap, + int32_t aRowIndex, + int32_t aColIndex) const +{ + int32_t numColsInTable = aMap.GetColCount(); + int32_t colSpan = 1; + if (uint32_t(aRowIndex) >= mRows.Length()) { + return colSpan; + } + + const CellDataArray& row = mRows[aRowIndex]; + int32_t colX; + CellData* data; + int32_t maxCols = numColsInTable; + bool hitOverlap = false; // XXX this is not ever being set to true + for (colX = aColIndex + 1; colX < maxCols; colX++) { + data = row.SafeElementAt(colX); + if (data) { + // for an overlapping situation get the colspan from the originating cell and + // use that as the max number of cols to iterate. Since this is rare, only + // pay the price of looking up the cell's colspan here. + if (!hitOverlap && data->IsOverlap()) { + CellData* origData = row.SafeElementAt(aColIndex); + if (origData && origData->IsOrig()) { + nsTableCellFrame* cellFrame = origData->GetCellFrame(); + if (cellFrame) { + // possible change the number of colums to iterate + maxCols = std::min(aColIndex + cellFrame->GetColSpan(), maxCols); + if (colX >= maxCols) + break; + } + } + } + if (data->IsColSpan()) { + colSpan++; + } + else { + break; + } + } + else break; + } + return colSpan; +} + +int32_t +nsCellMap::GetRowSpanForNewCell(nsTableCellFrame* aCellFrameToAdd, + int32_t aRowIndex, + bool& aIsZeroRowSpan) const +{ + aIsZeroRowSpan = false; + int32_t rowSpan = aCellFrameToAdd->GetRowSpan(); + if (0 == rowSpan) { + // Use a min value of 2 for a zero rowspan to make computations easier + // elsewhere. Zero rowspans are only content dependent! + rowSpan = std::max(2, mContentRowCount - aRowIndex); + aIsZeroRowSpan = true; + } + return rowSpan; +} + +bool nsCellMap::HasMoreThanOneCell(int32_t aRowIndex) const +{ + const CellDataArray& row = mRows.SafeElementAt(aRowIndex, *sEmptyRow); + uint32_t maxColIndex = row.Length(); + uint32_t count = 0; + uint32_t colIndex; + for (colIndex = 0; colIndex < maxColIndex; colIndex++) { + CellData* cellData = row[colIndex]; + if (cellData && (cellData->GetCellFrame() || cellData->IsRowSpan())) + count++; + if (count > 1) + return true; + } + return false; +} + +int32_t +nsCellMap::GetNumCellsOriginatingInRow(int32_t aRowIndex) const +{ + const CellDataArray& row = mRows.SafeElementAt(aRowIndex, *sEmptyRow); + uint32_t count = 0; + uint32_t maxColIndex = row.Length(); + uint32_t colIndex; + for (colIndex = 0; colIndex < maxColIndex; colIndex++) { + CellData* cellData = row[colIndex]; + if (cellData && cellData->IsOrig()) + count++; + } + return count; +} + +int32_t nsCellMap::GetRowSpan(int32_t aRowIndex, + int32_t aColIndex, + bool aGetEffective) const +{ + int32_t rowSpan = 1; + int32_t rowCount = (aGetEffective) ? mContentRowCount : mRows.Length(); + int32_t rowX; + for (rowX = aRowIndex + 1; rowX < rowCount; rowX++) { + CellData* data = GetDataAt(rowX, aColIndex); + if (data) { + if (data->IsRowSpan()) { + rowSpan++; + } + else { + break; + } + } + else break; + } + return rowSpan; +} + +void nsCellMap::ShrinkWithoutCell(nsTableCellMap& aMap, + nsTableCellFrame& aCellFrame, + int32_t aRowIndex, + int32_t aColIndex, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch"); + uint32_t colX, rowX; + + // get the rowspan and colspan from the cell map since the content may have changed + uint32_t numCols = aMap.GetColCount(); + int32_t rowSpan = GetRowSpan(aRowIndex, aColIndex, true); + uint32_t colSpan = GetEffectiveColSpan(aMap, aRowIndex, aColIndex); + uint32_t endRowIndex = aRowIndex + rowSpan - 1; + uint32_t endColIndex = aColIndex + colSpan - 1; + + // adjust the col counts due to the deleted cell before removing it + for (colX = aColIndex; colX <= endColIndex; colX++) { + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + if (colX == uint32_t(aColIndex)) { + colInfo->mNumCellsOrig--; + } + else { + colInfo->mNumCellsSpan--; + } + } + + // remove the deleted cell and cellData entries for it + for (rowX = aRowIndex; rowX <= endRowIndex; rowX++) { + CellDataArray& row = mRows[rowX]; + + // endIndexForRow points at the first slot we don't want to clean up. This + // makes the aColIndex == 0 case work right with our unsigned int colX. + NS_ASSERTION(endColIndex + 1 <= row.Length(), "span beyond the row size!"); + uint32_t endIndexForRow = std::min(endColIndex + 1, uint32_t(row.Length())); + + // Since endIndexForRow <= row.Length(), enough to compare aColIndex to it. + if (uint32_t(aColIndex) < endIndexForRow) { + for (colX = endIndexForRow; colX > uint32_t(aColIndex); colX--) { + DestroyCellData(row[colX-1]); + } + row.RemoveElementsAt(aColIndex, endIndexForRow - aColIndex); + } + } + + numCols = aMap.GetColCount(); + + // update the row and col info due to shifting + for (rowX = aRowIndex; rowX <= endRowIndex; rowX++) { + CellDataArray& row = mRows[rowX]; + for (colX = aColIndex; colX < numCols - colSpan; colX++) { + CellData* data = row.SafeElementAt(colX); + if (data) { + if (data->IsOrig()) { + // a cell that gets moved to the left needs adjustment in its new location + data->GetCellFrame()->SetColIndex(colX); + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + colInfo->mNumCellsOrig++; + // a cell that gets moved to the left needs adjustment in its old location + colInfo = aMap.GetColInfoAt(colX + colSpan); + if (colInfo) { + colInfo->mNumCellsOrig--; + } + } + + else if (data->IsColSpan()) { + // a cell that gets moved to the left needs adjustment + // in its new location + nsColInfo* colInfo = aMap.GetColInfoAt(colX); + colInfo->mNumCellsSpan++; + // a cell that gets moved to the left needs adjustment + // in its old location + colInfo = aMap.GetColInfoAt(colX + colSpan); + if (colInfo) { + colInfo->mNumCellsSpan--; + } + } + } + } + } + aMap.RemoveColsAtEnd(); + SetDamageArea(aColIndex, aRgFirstRowIndex + aRowIndex, + std::max(0, aMap.GetColCount() - aColIndex - 1), + 1 + endRowIndex - aRowIndex, aDamageArea); +} + +void +nsCellMap::RebuildConsideringRows(nsTableCellMap& aMap, + int32_t aStartRowIndex, + nsTArray* aRowsToInsert, + int32_t aNumRowsToRemove) +{ + NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch"); + // copy the old cell map into a new array + uint32_t numOrigRows = mRows.Length(); + nsTArray origRows; + mRows.SwapElements(origRows); + + int32_t rowNumberChange; + if (aRowsToInsert) { + rowNumberChange = aRowsToInsert->Length(); + } else { + rowNumberChange = -aNumRowsToRemove; + } + + // adjust mContentRowCount based on the function arguments as they are known to + // be real rows. + mContentRowCount += rowNumberChange; + NS_ASSERTION(mContentRowCount >= 0, "previous mContentRowCount was wrong"); + // mRows is empty now. Grow it to the size we expect it to have. + if (mContentRowCount) { + if (!Grow(aMap, mContentRowCount)) { + // Bail, I guess... Not sure what else we can do here. + return; + } + } + + // aStartRowIndex might be after all existing rows so we should limit the + // copy to the amount of exisiting rows + uint32_t copyEndRowIndex = std::min(numOrigRows, uint32_t(aStartRowIndex)); + + // rowX keeps track of where we are in mRows while setting up the + // new cellmap. + uint32_t rowX = 0; + TableArea damageArea; + // put back the rows before the affected ones just as before. Note that we + // can't just copy the old rows in bit-for-bit, because they might be + // spanning out into the rows we're adding/removing. + for ( ; rowX < copyEndRowIndex; rowX++) { + const CellDataArray& row = origRows[rowX]; + uint32_t numCols = row.Length(); + for (uint32_t colX = 0; colX < numCols; colX++) { + // put in the original cell from the cell map + const CellData* data = row.ElementAt(colX); + if (data && data->IsOrig()) { + AppendCell(aMap, data->GetCellFrame(), rowX, false, 0, damageArea); + } + } + } + + // Now handle the new rows being inserted, if any. + uint32_t copyStartRowIndex; + rowX = aStartRowIndex; + if (aRowsToInsert) { + // add in the new cells and create rows if necessary + int32_t numNewRows = aRowsToInsert->Length(); + for (int32_t newRowX = 0; newRowX < numNewRows; newRowX++) { + nsTableRowFrame* rFrame = aRowsToInsert->ElementAt(newRowX); + for (nsIFrame* cFrame : rFrame->PrincipalChildList()) { + nsTableCellFrame *cellFrame = do_QueryFrame(cFrame); + if (cellFrame) { + AppendCell(aMap, cellFrame, rowX, false, 0, damageArea); + } + } + rowX++; + } + copyStartRowIndex = aStartRowIndex; + } + else { + copyStartRowIndex = aStartRowIndex + aNumRowsToRemove; + } + + // put back the rows after the affected ones just as before. Again, we can't + // just copy the old bits because that would not handle the new rows spanning + // out or our earlier old rows spanning through the damaged area. + for (uint32_t copyRowX = copyStartRowIndex; copyRowX < numOrigRows; + copyRowX++) { + const CellDataArray& row = origRows[copyRowX]; + uint32_t numCols = row.Length(); + for (uint32_t colX = 0; colX < numCols; colX++) { + // put in the original cell from the cell map + CellData* data = row.ElementAt(colX); + if (data && data->IsOrig()) { + AppendCell(aMap, data->GetCellFrame(), rowX, false, 0, damageArea); + } + } + rowX++; + } + + // delete the old cell map. Now rowX no longer has anything to do with mRows + for (rowX = 0; rowX < numOrigRows; rowX++) { + CellDataArray& row = origRows[rowX]; + uint32_t len = row.Length(); + for (uint32_t colX = 0; colX < len; colX++) { + DestroyCellData(row[colX]); + } + } +} + +void +nsCellMap::RebuildConsideringCells(nsTableCellMap& aMap, + int32_t aNumOrigCols, + nsTArray* aCellFrames, + int32_t aRowIndex, + int32_t aColIndex, + bool aInsert) +{ + NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch"); + // copy the old cell map into a new array + int32_t numOrigRows = mRows.Length(); + nsTArray origRows; + mRows.SwapElements(origRows); + + int32_t numNewCells = (aCellFrames) ? aCellFrames->Length() : 0; + + // the new cells might extend the previous column number + NS_ASSERTION(aNumOrigCols >= aColIndex, "Appending cells far beyond cellmap data?!"); + int32_t numCols = aInsert ? std::max(aNumOrigCols, aColIndex + 1) : aNumOrigCols; + + // build the new cell map. Hard to say what, if anything, we can preallocate + // here... Should come back to that sometime, perhaps. + int32_t rowX; + TableArea damageArea; + for (rowX = 0; rowX < numOrigRows; rowX++) { + const CellDataArray& row = origRows[rowX]; + for (int32_t colX = 0; colX < numCols; colX++) { + if ((rowX == aRowIndex) && (colX == aColIndex)) { + if (aInsert) { // put in the new cells + for (int32_t cellX = 0; cellX < numNewCells; cellX++) { + nsTableCellFrame* cell = aCellFrames->ElementAt(cellX); + if (cell) { + AppendCell(aMap, cell, rowX, false, 0, damageArea); + } + } + } + else { + continue; // do not put the deleted cell back + } + } + // put in the original cell from the cell map + CellData* data = row.SafeElementAt(colX); + if (data && data->IsOrig()) { + AppendCell(aMap, data->GetCellFrame(), rowX, false, 0, damageArea); + } + } + } + if (aInsert && numOrigRows <= aRowIndex) { // append the new cells below the last original row + NS_ASSERTION (numOrigRows == aRowIndex, "Appending cells far beyond the last row"); + for (int32_t cellX = 0; cellX < numNewCells; cellX++) { + nsTableCellFrame* cell = aCellFrames->ElementAt(cellX); + if (cell) { + AppendCell(aMap, cell, aRowIndex, false, 0, damageArea); + } + } + } + + // delete the old cell map + for (rowX = 0; rowX < numOrigRows; rowX++) { + CellDataArray& row = origRows[rowX]; + uint32_t len = row.Length(); + for (uint32_t colX = 0; colX < len; colX++) { + DestroyCellData(row.SafeElementAt(colX)); + } + } + // expand the cellmap to cover empty content rows + if (mRows.Length() < uint32_t(mContentRowCount)) { + Grow(aMap, mContentRowCount - mRows.Length()); + } + +} + +void nsCellMap::RemoveCell(nsTableCellMap& aMap, + nsTableCellFrame* aCellFrame, + int32_t aRowIndex, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea) +{ + uint32_t numRows = mRows.Length(); + if (uint32_t(aRowIndex) >= numRows) { + NS_ERROR("bad arg in nsCellMap::RemoveCell"); + return; + } + int32_t numCols = aMap.GetColCount(); + + // Now aRowIndex is guaranteed OK. + + // get the starting col index of the cell to remove + int32_t startColIndex; + for (startColIndex = 0; startColIndex < numCols; startColIndex++) { + CellData* data = mRows[aRowIndex].SafeElementAt(startColIndex); + if (data && (data->IsOrig()) && (aCellFrame == data->GetCellFrame())) { + break; // we found the col index + } + } + + int32_t rowSpan = GetRowSpan(aRowIndex, startColIndex, false); + // record whether removing the cells is going to cause complications due + // to existing row spans, col spans or table sizing. + bool spansCauseRebuild = CellsSpanInOrOut(aRowIndex, + aRowIndex + rowSpan - 1, + startColIndex, numCols - 1); + // XXX if the cell has a col span to the end of the map, and the end has no originating + // cells, we need to assume that this the only such cell, and rebuild so that there are + // no extraneous cols at the end. The same is true for removing rows. + if (!aCellFrame->GetRowSpan() || !aCellFrame->GetColSpan()) + spansCauseRebuild = true; + + if (spansCauseRebuild) { + aMap.RebuildConsideringCells(this, nullptr, aRowIndex, startColIndex, false, + aDamageArea); + } + else { + ShrinkWithoutCell(aMap, *aCellFrame, aRowIndex, startColIndex, + aRgFirstRowIndex, aDamageArea); + } +} + +#ifdef DEBUG +void nsCellMap::Dump(bool aIsBorderCollapse) const +{ + printf("\n ***** START GROUP CELL MAP DUMP ***** %p\n", (void*)this); + nsTableRowGroupFrame* rg = GetRowGroup(); + const nsStyleDisplay* display = rg->StyleDisplay(); + switch (display->mDisplay) { + case StyleDisplay::TableHeaderGroup: + printf(" thead "); + break; + case StyleDisplay::TableFooterGroup: + printf(" tfoot "); + break; + case StyleDisplay::TableRowGroup: + printf(" tbody "); + break; + default: + printf("HUH? wrong display type on rowgroup"); + } + uint32_t mapRowCount = mRows.Length(); + printf("mapRowCount=%u tableRowCount=%d\n", mapRowCount, mContentRowCount); + + + uint32_t rowIndex, colIndex; + for (rowIndex = 0; rowIndex < mapRowCount; rowIndex++) { + const CellDataArray& row = mRows[rowIndex]; + printf(" row %d : ", rowIndex); + uint32_t colCount = row.Length(); + for (colIndex = 0; colIndex < colCount; colIndex++) { + CellData* cd = row[colIndex]; + if (cd) { + if (cd->IsOrig()) { + printf("C%d,%d ", rowIndex, colIndex); + } else { + if (cd->IsRowSpan()) { + printf("R "); + } + if (cd->IsColSpan()) { + printf("C "); + } + if (!(cd->IsRowSpan() && cd->IsColSpan())) { + printf(" "); + } + printf(" "); + } + } else { + printf("---- "); + } + } + if (aIsBorderCollapse) { + nscoord size; + BCBorderOwner owner; + LogicalSide side; + bool segStart; + bool bevel; + for (int32_t i = 0; i <= 2; i++) { + printf("\n "); + for (colIndex = 0; colIndex < colCount; colIndex++) { + BCCellData* cd = (BCCellData *)row[colIndex]; + if (cd) { + if (0 == i) { + size = cd->mData.GetBStartEdge(owner, segStart); + printf("t=%d%d%d ", int32_t(size), owner, segStart); + } + else if (1 == i) { + size = cd->mData.GetIStartEdge(owner, segStart); + printf("l=%d%d%d ", int32_t(size), owner, segStart); + } + else { + size = cd->mData.GetCorner(side, bevel); + printf("c=%d%d%d ", int32_t(size), side, bevel); + } + } + } + } + } + printf("\n"); + } + + // output info mapping Ci,j to cell address + uint32_t cellCount = 0; + for (uint32_t rIndex = 0; rIndex < mapRowCount; rIndex++) { + const CellDataArray& row = mRows[rIndex]; + uint32_t colCount = row.Length(); + printf(" "); + for (colIndex = 0; colIndex < colCount; colIndex++) { + CellData* cd = row[colIndex]; + if (cd) { + if (cd->IsOrig()) { + nsTableCellFrame* cellFrame = cd->GetCellFrame(); + int32_t cellFrameColIndex; + cellFrame->GetColIndex(cellFrameColIndex); + printf("C%d,%d=%p(%d) ", rIndex, colIndex, (void*)cellFrame, + cellFrameColIndex); + cellCount++; + } + } + } + printf("\n"); + } + + printf(" ***** END GROUP CELL MAP DUMP *****\n"); +} +#endif + +CellData* +nsCellMap::GetDataAt(int32_t aMapRowIndex, + int32_t aColIndex) const +{ + return + mRows.SafeElementAt(aMapRowIndex, *sEmptyRow).SafeElementAt(aColIndex); +} + +// only called if the cell at aMapRowIndex, aColIndex is null or dead +// (the latter from ExpandZeroColSpans (XXXmats which has now been removed - +// are there other ways cells may be dead?)). +void nsCellMap::SetDataAt(nsTableCellMap& aMap, + CellData& aNewCell, + int32_t aMapRowIndex, + int32_t aColIndex) +{ + NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch"); + if (uint32_t(aMapRowIndex) >= mRows.Length()) { + NS_ERROR("SetDataAt called with row index > num rows"); + return; + } + + CellDataArray& row = mRows[aMapRowIndex]; + + // the table map may need cols added + int32_t numColsToAdd = aColIndex + 1 - aMap.GetColCount(); + if (numColsToAdd > 0) { + aMap.AddColsAtEnd(numColsToAdd); + } + // the row may need cols added + numColsToAdd = aColIndex + 1 - row.Length(); + if (numColsToAdd > 0) { + // XXXbz need to handle allocation failures. + GrowRow(row, numColsToAdd); + } + + DestroyCellData(row[aColIndex]); + + row.ReplaceElementsAt(aColIndex, 1, &aNewCell); + // update the originating cell counts if cell originates in this row, col + nsColInfo* colInfo = aMap.GetColInfoAt(aColIndex); + if (colInfo) { + if (aNewCell.IsOrig()) { + colInfo->mNumCellsOrig++; + } + else if (aNewCell.IsColSpan()) { + colInfo->mNumCellsSpan++; + } + } + else NS_ERROR("SetDataAt called with col index > table map num cols"); +} + +nsTableCellFrame* +nsCellMap::GetCellInfoAt(const nsTableCellMap& aMap, + int32_t aRowX, + int32_t aColX, + bool* aOriginates, + int32_t* aColSpan) const +{ + if (aOriginates) { + *aOriginates = false; + } + CellData* data = GetDataAt(aRowX, aColX); + nsTableCellFrame* cellFrame = nullptr; + if (data) { + if (data->IsOrig()) { + cellFrame = data->GetCellFrame(); + if (aOriginates) + *aOriginates = true; + } + else { + cellFrame = GetCellFrame(aRowX, aColX, *data, true); + } + if (cellFrame && aColSpan) { + int32_t initialColIndex; + cellFrame->GetColIndex(initialColIndex); + *aColSpan = GetEffectiveColSpan(aMap, aRowX, initialColIndex); + } + } + return cellFrame; +} + + +bool nsCellMap::RowIsSpannedInto(int32_t aRowIndex, + int32_t aNumEffCols) const +{ + if ((0 > aRowIndex) || (aRowIndex >= mContentRowCount)) { + return false; + } + for (int32_t colIndex = 0; colIndex < aNumEffCols; colIndex++) { + CellData* cd = GetDataAt(aRowIndex, colIndex); + if (cd) { // there's really a cell at (aRowIndex, colIndex) + if (cd->IsSpan()) { // the cell at (aRowIndex, colIndex) is the result of a span + if (cd->IsRowSpan() && GetCellFrame(aRowIndex, colIndex, *cd, true)) { // XXX why the last check + return true; + } + } + } + } + return false; +} + +bool nsCellMap::RowHasSpanningCells(int32_t aRowIndex, + int32_t aNumEffCols) const +{ + if ((0 > aRowIndex) || (aRowIndex >= mContentRowCount)) { + return false; + } + if (aRowIndex != mContentRowCount - 1) { + // aRowIndex is not the last row, so we check the next row after aRowIndex for spanners + for (int32_t colIndex = 0; colIndex < aNumEffCols; colIndex++) { + CellData* cd = GetDataAt(aRowIndex, colIndex); + if (cd && (cd->IsOrig())) { // cell originates + CellData* cd2 = GetDataAt(aRowIndex + 1, colIndex); + if (cd2 && cd2->IsRowSpan()) { // cd2 is spanned by a row + if (cd->GetCellFrame() == GetCellFrame(aRowIndex + 1, colIndex, *cd2, true)) { + return true; + } + } + } + } + } + return false; +} + +void nsCellMap::DestroyCellData(CellData* aData) +{ + if (!aData) { + return; + } + + if (mIsBC) { + BCCellData* bcData = static_cast(aData); + bcData->~BCCellData(); + mPresContext->FreeToShell(sizeof(BCCellData), bcData); + } else { + aData->~CellData(); + mPresContext->FreeToShell(sizeof(CellData), aData); + } +} + +CellData* nsCellMap::AllocCellData(nsTableCellFrame* aOrigCell) +{ + if (mIsBC) { + BCCellData* data = (BCCellData*) + mPresContext->AllocateFromShell(sizeof(BCCellData)); + if (data) { + new (data) BCCellData(aOrigCell); + } + return data; + } + + CellData* data = (CellData*) + mPresContext->AllocateFromShell(sizeof(CellData)); + if (data) { + new (data) CellData(aOrigCell); + } + return data; +} + +void +nsCellMapColumnIterator::AdvanceRowGroup() +{ + do { + mCurMapStart += mCurMapContentRowCount; + mCurMap = mCurMap->GetNextSibling(); + if (!mCurMap) { + // Set mCurMapContentRowCount and mCurMapRelevantRowCount to 0 in case + // mCurMap has no next sibling. This can happen if we just handled the + // last originating cell. Future calls will end up with mFoundCells == + // mOrigCells, but for this one mFoundCells was definitely not big enough + // if we got here. + mCurMapContentRowCount = 0; + mCurMapRelevantRowCount = 0; + break; + } + + mCurMapContentRowCount = mCurMap->GetRowCount(); + uint32_t rowArrayLength = mCurMap->mRows.Length(); + mCurMapRelevantRowCount = std::min(mCurMapContentRowCount, rowArrayLength); + } while (0 == mCurMapRelevantRowCount); + + NS_ASSERTION(mCurMapRelevantRowCount != 0 || !mCurMap, + "How did that happen?"); + + // Set mCurMapRow to 0, since cells can't span across table row groups. + mCurMapRow = 0; +} + +void +nsCellMapColumnIterator::IncrementRow(int32_t aIncrement) +{ + NS_PRECONDITION(aIncrement >= 0, "Bogus increment"); + NS_PRECONDITION(mCurMap, "Bogus mOrigCells?"); + if (aIncrement == 0) { + AdvanceRowGroup(); + } + else { + mCurMapRow += aIncrement; + if (mCurMapRow >= mCurMapRelevantRowCount) { + AdvanceRowGroup(); + } + } +} + +nsTableCellFrame* +nsCellMapColumnIterator::GetNextFrame(int32_t* aRow, int32_t* aColSpan) +{ + // Fast-path for the case when we don't have anything left in the column and + // we know it. + if (mFoundCells == mOrigCells) { + *aRow = 0; + *aColSpan = 1; + return nullptr; + } + + while (1) { + NS_ASSERTION(mCurMapRow < mCurMapRelevantRowCount, "Bogus mOrigCells?"); + // Safe to just get the row (which is faster than calling GetDataAt(), but + // there may not be that many cells in it, so have to use SafeElementAt for + // the mCol. + const nsCellMap::CellDataArray& row = mCurMap->mRows[mCurMapRow]; + CellData* cellData = row.SafeElementAt(mCol); + if (!cellData || cellData->IsDead()) { + // Could hit this if there are fewer cells in this row than others, for + // example. + IncrementRow(1); + continue; + } + + if (cellData->IsColSpan()) { + // Look up the originating data for this cell, advance by its relative rowspan. + int32_t rowspanOffset = cellData->GetRowSpanOffset(); + nsTableCellFrame* cellFrame = mCurMap->GetCellFrame(mCurMapRow, mCol, *cellData, false); + NS_ASSERTION(cellFrame,"Must have usable originating data here"); + int32_t rowSpan = cellFrame->GetRowSpan(); + if (rowSpan == 0) { + AdvanceRowGroup(); + } + else { + IncrementRow(rowSpan - rowspanOffset); + } + continue; + } + + NS_ASSERTION(cellData->IsOrig(), + "Must have originating cellData by this point. " + "See comment on mCurMapRow in header."); + + nsTableCellFrame* cellFrame = cellData->GetCellFrame(); + NS_ASSERTION(cellFrame, "Orig data without cellframe?"); + + *aRow = mCurMapStart + mCurMapRow; + *aColSpan = mCurMap->GetEffectiveColSpan(*mMap, mCurMapRow, mCol); + + IncrementRow(cellFrame->GetRowSpan()); + + ++mFoundCells; + + MOZ_ASSERT(cellData == mMap->GetDataAt(*aRow, mCol), + "Giving caller bogus row?"); + + return cellFrame; + } + + NS_NOTREACHED("Can't get here"); + return nullptr; +} diff --git a/layout/tables/nsCellMap.h b/layout/tables/nsCellMap.h new file mode 100644 index 000000000..991343aa7 --- /dev/null +++ b/layout/tables/nsCellMap.h @@ -0,0 +1,668 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsCellMap_h__ +#define nsCellMap_h__ + +#include "nscore.h" +#include "celldata.h" +#include "nsTArray.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsAlgorithm.h" +#include "nsRect.h" +#include +#include "TableArea.h" + +#undef DEBUG_TABLE_CELLMAP + +class nsTableCellFrame; +class nsTableRowFrame; +class nsTableRowGroupFrame; +class nsTableFrame; +class nsCellMap; +class nsPresContext; +class nsCellMapColumnIterator; + +struct nsColInfo +{ + int32_t mNumCellsOrig; // number of cells originating in the col + int32_t mNumCellsSpan; // number of cells spanning into the col via colspans (not rowspans) + + nsColInfo(); + nsColInfo(int32_t aNumCellsOrig, + int32_t aNumCellsSpan); +}; + +enum Corner +{ + eBStartIStart = 0, + eBStartIEnd = 1, + eBEndIEnd = 2, + eBEndIStart = 3 +}; + +struct BCInfo +{ + nsTArray mIEndBorders; + nsTArray mBEndBorders; + BCData mBEndIEndCorner; +}; + +class nsTableCellMap +{ + typedef mozilla::TableArea TableArea; + +public: + nsTableCellMap(nsTableFrame& aTableFrame, + bool aBorderCollapse); + + /** destructor + * NOT VIRTUAL BECAUSE THIS CLASS SHOULD **NEVER** BE SUBCLASSED + */ + ~nsTableCellMap(); + + void RemoveGroupCellMap(nsTableRowGroupFrame* aRowGroup); + + void InsertGroupCellMap(nsTableRowGroupFrame* aNewRowGroup, + nsTableRowGroupFrame*& aPrevRowGroup); + + /** + * Get the nsCellMap for the given row group. If aStartHint is non-null, + * will start looking with that cellmap and only fall back to starting at the + * beginning of the list if that doesn't find us the right nsCellMap. + * Otherwise, just start at the beginning. + * + * aRowGroup must not be null. + */ + nsCellMap* GetMapFor(const nsTableRowGroupFrame* aRowGroup, + nsCellMap* aStartHint) const; + + /** synchronize the cellmaps with the rowgroups again **/ + void Synchronize(nsTableFrame* aTableFrame); + + nsTableCellFrame* GetCellFrame(int32_t aRowIndex, + int32_t aColIndex, + CellData& aData, + bool aUseRowIfOverlap) const; + + /** return the CellData for the cell at (aRowIndex, aColIndex) */ + CellData* GetDataAt(int32_t aRowIndex, + int32_t aColIndex) const; + + // this function creates a col if needed + nsColInfo* GetColInfoAt(int32_t aColIndex); + + /** append the cellFrame at the end of the row at aRowIndex and return the col index + */ + CellData* AppendCell(nsTableCellFrame& aCellFrame, + int32_t aRowIndex, + bool aRebuildIfNecessary, + TableArea& aDamageArea); + + void InsertCells(nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndexBefore, + TableArea& aDamageArea); + + void RemoveCell(nsTableCellFrame* aCellFrame, + int32_t aRowIndex, + TableArea& aDamageArea); + /** Remove the previously gathered column information */ + void ClearCols(); + void InsertRows(nsTableRowGroupFrame* aRowGroup, + nsTArray& aRows, + int32_t aFirstRowIndex, + bool aConsiderSpans, + TableArea& aDamageArea); + + void RemoveRows(int32_t aFirstRowIndex, + int32_t aNumRowsToRemove, + bool aConsiderSpans, + TableArea& aDamageArea); + + int32_t GetNumCellsOriginatingInRow(int32_t aRowIndex) const; + int32_t GetNumCellsOriginatingInCol(int32_t aColIndex) const; + + /** indicate whether the row has more than one cell that either originates + * or is spanned from the rows above + */ + bool HasMoreThanOneCell(int32_t aRowIndex) const; + + int32_t GetEffectiveRowSpan(int32_t aRowIndex, + int32_t aColIndex) const; + int32_t GetEffectiveColSpan(int32_t aRowIndex, + int32_t aColIndex) const; + + /** return the total number of columns in the table represented by this CellMap */ + int32_t GetColCount() const; + + /** return the actual number of rows in the table represented by this CellMap */ + int32_t GetRowCount() const; + + nsTableCellFrame* GetCellInfoAt(int32_t aRowX, + int32_t aColX, + bool* aOriginates = nullptr, + int32_t* aColSpan = nullptr) const; + + /** + * Returns the index at the given row and column coordinates. + * + * @see nsITableLayout::GetIndexByRowAndColumn() + * + * @param aRow [in] the row coordinate + * @param aColumn [in] the column coordinate + * @returns the index for the cell + */ + int32_t GetIndexByRowAndColumn(int32_t aRow, int32_t aColumn) const; + + /** + * Retrieves the row and column coordinates for the given index. + * + * @see nsITableLayout::GetRowAndColumnByIndex() + * + * @param aIndex [in] the index for which coordinates are to be retrieved + * @param aRow [out] the row coordinate to be returned + * @param aColumn [out] the column coordinate to be returned + */ + void GetRowAndColumnByIndex(int32_t aIndex, + int32_t *aRow, int32_t *aColumn) const; + + void AddColsAtEnd(uint32_t aNumCols); + void RemoveColsAtEnd(); + + bool RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols) const; + bool RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) const; + void RebuildConsideringCells(nsCellMap* aCellMap, + nsTArray* aCellFrames, + int32_t aRowIndex, + int32_t aColIndex, + bool aInsert, + TableArea& aDamageArea); + +protected: + /** + * Rebuild due to rows being inserted or deleted with cells spanning + * into or out of the rows. This function can only handle insertion + * or deletion but NOT both. So either aRowsToInsert must be null + * or aNumRowsToRemove must be 0. + * + * // XXXbz are both allowed to happen? That'd be a no-op... + */ + void RebuildConsideringRows(nsCellMap* aCellMap, + int32_t aStartRowIndex, + nsTArray* aRowsToInsert, + int32_t aNumRowsToRemove, + TableArea& aDamageArea); + +public: + void ResetBStartStart(mozilla::LogicalSide aSide, + nsCellMap& aCellMap, + uint32_t aYPos, + uint32_t aXPos, + bool aIsBEndIEnd = false); + + void SetBCBorderEdge(mozilla::LogicalSide aEdge, + nsCellMap& aCellMap, + uint32_t aCellMapStart, + uint32_t aYPos, + uint32_t aXPos, + uint32_t aLength, + BCBorderOwner aOwner, + nscoord aSize, + bool aChanged); + + void SetBCBorderCorner(::Corner aCorner, + nsCellMap& aCellMap, + uint32_t aCellMapStart, + uint32_t aYPos, + uint32_t aXPos, + mozilla::LogicalSide aOwner, + nscoord aSubSize, + bool aBevel, + bool aIsBottomRight = false); + + /** dump a representation of the cell map to stdout for debugging */ +#ifdef DEBUG + void Dump(char* aString = nullptr) const; +#endif + +protected: + BCData* GetIEndMostBorder(int32_t aRowIndex); + BCData* GetBEndMostBorder(int32_t aColIndex); + + friend class nsCellMap; + friend class BCMapCellIterator; + friend class BCPaintBorderIterator; + friend class nsCellMapColumnIterator; + +/** Insert a row group cellmap after aPrevMap, if aPrefMap is null insert it + * at the beginning, the ordering of the cellmap corresponds to the ordering of + * rowgroups once OrderRowGroups has been called + */ + void InsertGroupCellMap(nsCellMap* aPrevMap, + nsCellMap& aNewMap); + void DeleteIEndBEndBorders(); + + nsTableFrame& mTableFrame; + AutoTArray mCols; + nsCellMap* mFirstMap; + // border collapsing info + BCInfo* mBCInfo; +}; + +/** nsCellMap is a support class for nsTablePart. + * It maintains an Rows x Columns grid onto which the cells of the table are mapped. + * This makes processing of rowspan and colspan attributes much easier. + * Each cell is represented by a CellData object. + * + * @see CellData + * @see nsTableFrame::AddCellToMap + * @see nsTableFrame::GrowCellMap + * @see nsTableFrame::BuildCellIntoMap + * + * mRows is an array of rows. Each row is an array of cells. a cell + * can be null. + */ +class nsCellMap +{ + typedef mozilla::TableArea TableArea; + +public: + /** constructor + * @param aRowGroupFrame the row group frame this is a cellmap for + * @param aIsBC whether the table is doing border-collapse + */ + nsCellMap(nsTableRowGroupFrame* aRowGroupFrame, bool aIsBC); + + /** destructor + * NOT VIRTUAL BECAUSE THIS CLASS SHOULD **NEVER** BE SUBCLASSED + */ + ~nsCellMap(); + + static void Init(); + static void Shutdown(); + + nsCellMap* GetNextSibling() const; + void SetNextSibling(nsCellMap* aSibling); + + nsTableRowGroupFrame* GetRowGroup() const; + + nsTableCellFrame* GetCellFrame(int32_t aRowIndex, + int32_t aColIndex, + CellData& aData, + bool aUseRowSpanIfOverlap) const; + + /** + * Returns highest cell index within the cell map. + * + * @param aColCount [in] the number of columns in the table + */ + int32_t GetHighestIndex(int32_t aColCount); + + /** + * Returns the index of the given row and column coordinates. + * + * @see nsITableLayout::GetIndexByRowAndColumn() + * + * @param aColCount [in] the number of columns in the table + * @param aRow [in] the row coordinate + * @param aColumn [in] the column coordinate + */ + int32_t GetIndexByRowAndColumn(int32_t aColCount, + int32_t aRow, int32_t aColumn) const; + + /** + * Get the row and column coordinates at the given index. + * + * @see nsITableLayout::GetRowAndColumnByIndex() + * + * @param aColCount [in] the number of columns in the table + * @param aIndex [in] the index for which coordinates are to be retrieved + * @param aRow [out] the row coordinate to be returned + * @param aColumn [out] the column coordinate to be returned + */ + void GetRowAndColumnByIndex(int32_t aColCount, int32_t aIndex, + int32_t *aRow, int32_t *aColumn) const; + + /** append the cellFrame at an empty or dead cell or finally at the end of + * the row at aRowIndex and return a pointer to the celldata entry in the + * cellmap + * + * @param aMap - reference to the table cell map + * @param aCellFrame - a pointer to the cellframe which will be appended + * to the row + * @param aRowIndex - to this row the celldata entry will be added + * @param aRebuildIfNecessay - if a cell spans into a row below it might be + * necesserary to rebuild the cellmap as this rowspan + * might overlap another cell. + * @param aDamageArea - area in cellmap coordinates which have been updated. + * @param aColToBeginSearch - if not null contains the column number where + * the search for a empty or dead cell in the + * row should start + * @return - a pointer to the celldata entry inserted into + * the cellmap + */ + CellData* AppendCell(nsTableCellMap& aMap, + nsTableCellFrame* aCellFrame, + int32_t aRowIndex, + bool aRebuildIfNecessary, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea, + int32_t* aBeginSearchAtCol = nullptr); + + void InsertCells(nsTableCellMap& aMap, + nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndexBefore, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + void RemoveCell(nsTableCellMap& aMap, + nsTableCellFrame* aCellFrame, + int32_t aRowIndex, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + void InsertRows(nsTableCellMap& aMap, + nsTArray& aRows, + int32_t aFirstRowIndex, + bool aConsiderSpans, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + void RemoveRows(nsTableCellMap& aMap, + int32_t aFirstRowIndex, + int32_t aNumRowsToRemove, + bool aConsiderSpans, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + int32_t GetNumCellsOriginatingInRow(int32_t aRowIndex) const; + int32_t GetNumCellsOriginatingInCol(int32_t aColIndex) const; + + /** return the number of rows in the table represented by this CellMap */ + int32_t GetRowCount(bool aConsiderDeadRowSpanRows = false) const; + + nsTableCellFrame* GetCellInfoAt(const nsTableCellMap& aMap, + int32_t aRowX, + int32_t aColX, + bool* aOriginates = nullptr, + int32_t* aColSpan = nullptr) const; + + bool RowIsSpannedInto(int32_t aRowIndex, + int32_t aNumEffCols) const; + + bool RowHasSpanningCells(int32_t aRowIndex, + int32_t aNumEffCols) const; + + /** indicate whether the row has more than one cell that either originates + * or is spanned from the rows above + */ + bool HasMoreThanOneCell(int32_t aRowIndex) const; + + /* Get the rowspan for a cell starting at aRowIndex and aColIndex. + * If aGetEffective is true the size will not exceed the last content based + * row. Cells can have a specified rowspan that extends below the last + * content based row. This is legitimate considering incr. reflow where the + * content rows will arive later. + */ + int32_t GetRowSpan(int32_t aRowIndex, + int32_t aColIndex, + bool aGetEffective) const; + + int32_t GetEffectiveColSpan(const nsTableCellMap& aMap, + int32_t aRowIndex, + int32_t aColIndex) const; + + typedef nsTArray CellDataArray; + + /** dump a representation of the cell map to stdout for debugging */ +#ifdef DEBUG + void Dump(bool aIsBorderCollapse) const; +#endif + +protected: + friend class nsTableCellMap; + friend class BCMapCellIterator; + friend class BCPaintBorderIterator; + friend class nsTableFrame; + friend class nsCellMapColumnIterator; + + /** + * Increase the number of rows in this cellmap by aNumRows. Put the + * new rows at aRowIndex. If aRowIndex is -1, put them at the end. + */ + bool Grow(nsTableCellMap& aMap, + int32_t aNumRows, + int32_t aRowIndex = -1); + + void GrowRow(CellDataArray& aRow, + int32_t aNumCols); + + /** assign aCellData to the cell at (aRow,aColumn) */ + void SetDataAt(nsTableCellMap& aMap, + CellData& aCellData, + int32_t aMapRowIndex, + int32_t aColIndex); + + CellData* GetDataAt(int32_t aMapRowIndex, + int32_t aColIndex) const; + + int32_t GetNumCellsIn(int32_t aColIndex) const; + + void ExpandWithRows(nsTableCellMap& aMap, + nsTArray& aRowFrames, + int32_t aStartRowIndex, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + void ExpandWithCells(nsTableCellMap& aMap, + nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndex, + int32_t aRowSpan, + bool aRowSpanIsZero, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + void ShrinkWithoutRows(nsTableCellMap& aMap, + int32_t aFirstRowIndex, + int32_t aNumRowsToRemove, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + void ShrinkWithoutCell(nsTableCellMap& aMap, + nsTableCellFrame& aCellFrame, + int32_t aRowIndex, + int32_t aColIndex, + int32_t aRgFirstRowIndex, + TableArea& aDamageArea); + + /** + * Rebuild due to rows being inserted or deleted with cells spanning + * into or out of the rows. This function can only handle insertion + * or deletion but NOT both. So either aRowsToInsert must be null + * or aNumRowsToRemove must be 0. + * + * // XXXbz are both allowed to happen? That'd be a no-op... + */ + void RebuildConsideringRows(nsTableCellMap& aMap, + int32_t aStartRowIndex, + nsTArray* aRowsToInsert, + int32_t aNumRowsToRemove); + + void RebuildConsideringCells(nsTableCellMap& aMap, + int32_t aNumOrigCols, + nsTArray* aCellFrames, + int32_t aRowIndex, + int32_t aColIndex, + bool aInsert); + + bool CellsSpanOut(nsTArray& aNewRows) const; + + /** If a cell spans out of the area defined by aStartRowIndex, aEndRowIndex + * and aStartColIndex, aEndColIndex the cellmap changes are more severe so + * the corresponding routines needs to be called. This is also necessary if + * cells outside spans into this region. + * @aStartRowIndex - y start index + * @aEndRowIndex - y end index + * @param aStartColIndex - x start index + * @param aEndColIndex - x end index + * @return - true if a cell span crosses the border of the + region + */ + bool CellsSpanInOrOut(int32_t aStartRowIndex, + int32_t aEndRowIndex, + int32_t aStartColIndex, + int32_t aEndColIndex) const; + + bool CreateEmptyRow(int32_t aRowIndex, + int32_t aNumCols); + + int32_t GetRowSpanForNewCell(nsTableCellFrame* aCellFrameToAdd, + int32_t aRowIndex, + bool& aIsZeroRowSpan) const; + + // Destroy a CellData struct. This will handle the case of aData + // actually being a BCCellData properly. + void DestroyCellData(CellData* aData); + // Allocate a CellData struct. This will handle needing to create a + // BCCellData properly. + // @param aOrigCell the originating cell to pass to the celldata constructor + CellData* AllocCellData(nsTableCellFrame* aOrigCell); + + /** an array containing, for each row, the CellDatas for the cells + * in that row. It can be larger than mContentRowCount due to row spans + * extending beyond the table */ + // XXXbz once we have auto TArrays, we should probably use them here. + nsTArray mRows; + + /** the number of rows in the table (content) which is not indentical to the + * number of rows in the cell map due to row spans extending beyond the end + * of thetable (dead rows) or empty tr tags + */ + int32_t mContentRowCount; + + // the row group that corresponds to this map + nsTableRowGroupFrame* mRowGroupFrame; + + // the next row group cell map + nsCellMap* mNextSibling; + + // Whether this is a BC cellmap or not + bool mIsBC; + + // Prescontext to deallocate and allocate celldata + RefPtr mPresContext; +}; + +/** + * A class for iterating the cells in a given column. Must be given a + * non-null nsTableCellMap and a column number valid for that cellmap. + */ +class nsCellMapColumnIterator +{ +public: + nsCellMapColumnIterator(const nsTableCellMap* aMap, int32_t aCol) : + mMap(aMap), mCurMap(aMap->mFirstMap), mCurMapStart(0), + mCurMapRow(0), mCol(aCol), mFoundCells(0) + { + NS_PRECONDITION(aMap, "Must have map"); + NS_PRECONDITION(mCol < aMap->GetColCount(), "Invalid column"); + mOrigCells = aMap->GetNumCellsOriginatingInCol(mCol); + if (mCurMap) { + mCurMapContentRowCount = mCurMap->GetRowCount(); + uint32_t rowArrayLength = mCurMap->mRows.Length(); + mCurMapRelevantRowCount = std::min(mCurMapContentRowCount, rowArrayLength); + if (mCurMapRelevantRowCount == 0 && mOrigCells > 0) { + // This row group is useless; advance! + AdvanceRowGroup(); + } + } +#ifdef DEBUG + else { + NS_ASSERTION(mOrigCells == 0, "Why no rowgroups?"); + } +#endif + } + + nsTableCellFrame* GetNextFrame(int32_t* aRow, int32_t* aColSpan); + +private: + void AdvanceRowGroup(); + + // Advance the row; aIncrement is considered to be a cell's rowspan, + // so if 0 is passed in we'll advance to the next rowgroup. + void IncrementRow(int32_t aIncrement); + + const nsTableCellMap* mMap; + const nsCellMap* mCurMap; + + // mCurMapStart is the row in the entire nsTableCellMap where + // mCurMap starts. This is used to compute row indices to pass to + // nsTableCellMap::GetDataAt, so must be a _content_ row index. + uint32_t mCurMapStart; + + // In steady-state mCurMapRow is the row in our current nsCellMap + // that we'll use the next time GetNextFrame() is called. Due to + // the way we skip over rowspans, the entry in mCurMapRow and mCol + // is either null, dead, originating, or a colspan. In particular, + // it cannot be a rowspan or overlap entry. + uint32_t mCurMapRow; + const int32_t mCol; + uint32_t mOrigCells; + uint32_t mFoundCells; + + // The number of content rows in mCurMap. This may be bigger than the number + // of "relevant" rows, or it might be smaller. + uint32_t mCurMapContentRowCount; + + // The number of "relevant" rows in mCurMap. That is, the number of rows + // which might have an originating cell in them. Once mCurMapRow reaches + // mCurMapRelevantRowCount, we should move to the next map. + uint32_t mCurMapRelevantRowCount; +}; + + +/* ----- inline methods ----- */ +inline int32_t nsTableCellMap::GetColCount() const +{ + return mCols.Length(); +} + +inline nsCellMap* nsCellMap::GetNextSibling() const +{ + return mNextSibling; +} + +inline void nsCellMap::SetNextSibling(nsCellMap* aSibling) +{ + mNextSibling = aSibling; +} + +inline nsTableRowGroupFrame* nsCellMap::GetRowGroup() const +{ + return mRowGroupFrame; +} + +inline int32_t nsCellMap::GetRowCount(bool aConsiderDeadRowSpanRows) const +{ + int32_t rowCount = (aConsiderDeadRowSpanRows) ? mRows.Length() : mContentRowCount; + return rowCount; +} + +// nsColInfo + +inline nsColInfo::nsColInfo() + :mNumCellsOrig(0), mNumCellsSpan(0) +{} + +inline nsColInfo::nsColInfo(int32_t aNumCellsOrig, + int32_t aNumCellsSpan) + :mNumCellsOrig(aNumCellsOrig), mNumCellsSpan(aNumCellsSpan) +{} + + +#endif diff --git a/layout/tables/nsITableCellLayout.h b/layout/tables/nsITableCellLayout.h new file mode 100644 index 000000000..e761d76be --- /dev/null +++ b/layout/tables/nsITableCellLayout.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsITableCellLayout_h__ +#define nsITableCellLayout_h__ + +#include "nsQueryFrame.h" + +/** + * nsITableCellLayout + * interface for layout objects that act like table cells. + * + * @author sclark + */ +class nsITableCellLayout +{ +public: + + NS_DECL_QUERYFRAME_TARGET(nsITableCellLayout) + + /** return the mapped cell's row and column indexes (starting at 0 for each) */ + NS_IMETHOD GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex)=0; + + /** return the mapped cell's row index (starting at 0 for the first row) */ + virtual nsresult GetRowIndex(int32_t &aRowIndex) const = 0; + + /** return the mapped cell's column index (starting at 0 for the first column) */ + virtual nsresult GetColIndex(int32_t &aColIndex) const = 0; +}; + +#endif + + + diff --git a/layout/tables/nsITableLayoutStrategy.h b/layout/tables/nsITableLayoutStrategy.h new file mode 100644 index 000000000..d7d694096 --- /dev/null +++ b/layout/tables/nsITableLayoutStrategy.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +// vim:cindent:ts=4:et:sw=4: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * interface for the set of algorithms that determine column and table + * isizes + */ + +#ifndef nsITableLayoutStrategy_h_ +#define nsITableLayoutStrategy_h_ + +#include "nscore.h" +#include "nsCoord.h" + +class nsRenderingContext; +namespace mozilla { +struct ReflowInput; +} // namespace mozilla + +class nsITableLayoutStrategy +{ +public: + using ReflowInput = mozilla::ReflowInput; + + virtual ~nsITableLayoutStrategy() {} + + /** Implement nsIFrame::GetMinISize for the table */ + virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) = 0; + + /** Implement nsIFrame::GetPrefISize for the table */ + virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext, + bool aComputingSize) = 0; + + /** Implement nsIFrame::MarkIntrinsicISizesDirty for the table */ + virtual void MarkIntrinsicISizesDirty() = 0; + + /** + * Compute final column isizes based on the intrinsic isize data and + * the available isize. + */ + virtual void ComputeColumnISizes(const ReflowInput& aReflowInput) = 0; + + /** + * Return the type of table layout strategy, without the cost of + * a virtual function call + */ + enum Type { Auto, Fixed }; + Type GetType() const { return mType; } + +protected: + explicit nsITableLayoutStrategy(Type aType) : mType(aType) {} +private: + Type mType; +}; + +#endif /* !defined(nsITableLayoutStrategy_h_) */ diff --git a/layout/tables/nsTableCellFrame.cpp b/layout/tables/nsTableCellFrame.cpp new file mode 100644 index 000000000..316a96613 --- /dev/null +++ b/layout/tables/nsTableCellFrame.cpp @@ -0,0 +1,1238 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTableCellFrame.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "nsTableFrame.h" +#include "nsTableColFrame.h" +#include "nsTableRowFrame.h" +#include "nsTableRowGroupFrame.h" +#include "nsTablePainter.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsRenderingContext.h" +#include "nsCSSRendering.h" +#include "nsIContent.h" +#include "nsGenericHTMLElement.h" +#include "nsAttrValueInlines.h" +#include "nsHTMLParts.h" +#include "nsGkAtoms.h" +#include "nsIPresShell.h" +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsIDOMNode.h" +#include "nsNameSpaceManager.h" +#include "nsDisplayList.h" +#include "nsLayoutUtils.h" +#include "nsTextFrame.h" +#include "FrameLayerBuilder.h" +#include + +//TABLECELL SELECTION +#include "nsFrameSelection.h" +#include "mozilla/LookAndFeel.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +nsTableCellFrame::nsTableCellFrame(nsStyleContext* aContext, + nsTableFrame* aTableFrame) + : nsContainerFrame(aContext) + , mDesiredSize(aTableFrame->GetWritingMode()) +{ + mColIndex = 0; + mPriorAvailISize = 0; + + SetContentEmpty(false); + SetHasPctOverBSize(false); +} + +nsTableCellFrame::~nsTableCellFrame() +{ +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableCellFrame) + +nsTableCellFrame* +nsTableCellFrame::GetNextCell() const +{ + nsIFrame* childFrame = GetNextSibling(); + while (childFrame) { + nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); + if (cellFrame) { + return cellFrame; + } + childFrame = childFrame->GetNextSibling(); + } + return nullptr; +} + +void +nsTableCellFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + // Let the base class do its initialization + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + + if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) { + AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); + } + + if (aPrevInFlow) { + // Set the column index + nsTableCellFrame* cellFrame = (nsTableCellFrame*)aPrevInFlow; + int32_t colIndex; + cellFrame->GetColIndex(colIndex); + SetColIndex(colIndex); + } +} + +void +nsTableCellFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { + nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot); + } + + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +// nsIPercentBSizeObserver methods + +void +nsTableCellFrame::NotifyPercentBSize(const ReflowInput& aReflowInput) +{ + // ReflowInput ensures the mCBReflowInput of blocks inside a + // cell is the cell frame, not the inner-cell block, and that the + // containing block of an inner table is the containing block of its + // table wrapper. + // XXXldb Given the now-stricter |NeedsToObserve|, many if not all of + // these tests are probably unnecessary. + + // Maybe the cell reflow state; we sure if we're inside the |if|. + const ReflowInput *cellRI = aReflowInput.mCBReflowInput; + + if (cellRI && cellRI->mFrame == this && + (cellRI->ComputedBSize() == NS_UNCONSTRAINEDSIZE || + cellRI->ComputedBSize() == 0)) { // XXXldb Why 0? + // This is a percentage bsize on a frame whose percentage bsizes + // are based on the bsize of the cell, since its containing block + // is the inner cell frame. + + // We'll only honor the percent bsize if sibling-cells/ancestors + // have specified/pct bsize. (Also, siblings only count for this if + // both this cell and the sibling cell span exactly 1 row.) + + if (nsTableFrame::AncestorsHaveStyleBSize(*cellRI) || + (GetTableFrame()->GetEffectiveRowSpan(*this) == 1 && + cellRI->mParentReflowInput->mFrame-> + HasAnyStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE))) { + + for (const ReflowInput *rs = aReflowInput.mParentReflowInput; + rs != cellRI; + rs = rs->mParentReflowInput) { + rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + } + + nsTableFrame::RequestSpecialBSizeReflow(*cellRI); + } + } +} + +// The cell needs to observe its block and things inside its block but nothing below that +bool +nsTableCellFrame::NeedsToObserve(const ReflowInput& aReflowInput) +{ + const ReflowInput *rs = aReflowInput.mParentReflowInput; + if (!rs) + return false; + if (rs->mFrame == this) { + // We always observe the child block. It will never send any + // notifications, but we need this so that the observer gets + // propagated to its kids. + return true; + } + rs = rs->mParentReflowInput; + if (!rs) { + return false; + } + + // We always need to let the percent bsize observer be propagated + // from a table wrapper frame to an inner table frame. + nsIAtom *fType = aReflowInput.mFrame->GetType(); + if (fType == nsGkAtoms::tableFrame) { + return true; + } + + // We need the observer to be propagated to all children of the cell + // (i.e., children of the child block) in quirks mode, but only to + // tables in standards mode. + // XXX This may not be true in the case of orthogonal flows within + // the cell (bug 1174711 comment 8); we may need to observe isizes + // instead of bsizes for orthogonal children. + return rs->mFrame == this && + (PresContext()->CompatibilityMode() == eCompatibility_NavQuirks || + fType == nsGkAtoms::tableWrapperFrame); +} + +nsresult +nsTableCellFrame::GetRowIndex(int32_t &aRowIndex) const +{ + nsresult result; + nsTableRowFrame* row = static_cast(GetParent()); + if (row) { + aRowIndex = row->GetRowIndex(); + result = NS_OK; + } + else { + aRowIndex = 0; + result = NS_ERROR_NOT_INITIALIZED; + } + return result; +} + +nsresult +nsTableCellFrame::GetColIndex(int32_t &aColIndex) const +{ + if (GetPrevInFlow()) { + return static_cast(FirstInFlow())->GetColIndex(aColIndex); + } + else { + aColIndex = mColIndex; + return NS_OK; + } +} + +nsresult +nsTableCellFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + // We need to recalculate in this case because of the nowrap quirk in + // BasicTableLayoutStrategy + if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::nowrap && + PresContext()->CompatibilityMode() == eCompatibility_NavQuirks) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); + } + // let the table frame decide what to do + GetTableFrame()->AttributeChangedFor(this, mContent, aAttribute); + return NS_OK; +} + +/* virtual */ void +nsTableCellFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsContainerFrame::DidSetStyleContext(aOldStyleContext); + + if (!aOldStyleContext) //avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) { + int32_t colIndex, rowIndex; + GetColIndex(colIndex); + GetRowIndex(rowIndex); + // row span needs to be clamped as we do not create rows in the cellmap + // which do not have cells originating in them + TableArea damageArea(colIndex, rowIndex, GetColSpan(), + std::min(GetRowSpan(), tableFrame->GetRowCount() - rowIndex)); + tableFrame->AddBCDamageArea(damageArea); + } +} + +#ifdef DEBUG +void +nsTableCellFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + MOZ_CRASH("unsupported operation"); +} + +void +nsTableCellFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + MOZ_CRASH("unsupported operation"); +} + +void +nsTableCellFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + MOZ_CRASH("unsupported operation"); +} +#endif + +void nsTableCellFrame::SetColIndex(int32_t aColIndex) +{ + mColIndex = aColIndex; +} + +/* virtual */ nsMargin +nsTableCellFrame::GetUsedMargin() const +{ + return nsMargin(0,0,0,0); +} + +//ASSURE DIFFERENT COLORS for selection +inline nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB) +{ + if (colorA == colorB) + { + nscolor res; + res = NS_RGB(NS_GET_R(colorA) ^ 0xff, + NS_GET_G(colorA) ^ 0xff, + NS_GET_B(colorA) ^ 0xff); + return res; + } + return colorA; +} + +void +nsTableCellFrame::DecorateForSelection(DrawTarget* aDrawTarget, nsPoint aPt) +{ + NS_ASSERTION(IsSelected(), "Should only be called for selected cells"); + int16_t displaySelection; + nsPresContext* presContext = PresContext(); + displaySelection = DisplaySelection(presContext); + if (displaySelection) { + RefPtr frameSelection = + presContext->PresShell()->FrameSelection(); + + if (frameSelection->GetTableCellSelection()) { + nscolor bordercolor; + if (displaySelection == nsISelectionController::SELECTION_DISABLED) { + bordercolor = NS_RGB(176,176,176);// disabled color + } + else { + bordercolor = + LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground); + } + nscoord threePx = nsPresContext::CSSPixelsToAppUnits(3); + if ((mRect.width > threePx) && (mRect.height > threePx)) + { + //compare bordercolor to ((nsStyleColor *)myColor)->mBackgroundColor) + bordercolor = EnsureDifferentColors(bordercolor, + StyleBackground()->mBackgroundColor); + + int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + Point devPixelOffset = NSPointToPoint(aPt, appUnitsPerDevPixel); + + AutoRestoreTransform autoRestoreTransform(aDrawTarget); + aDrawTarget->SetTransform( + aDrawTarget->GetTransform().PreTranslate(devPixelOffset)); + + ColorPattern color(ToDeviceColor(bordercolor)); + + nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); + + StrokeLineWithSnapping(nsPoint(onePixel, 0), nsPoint(mRect.width, 0), + appUnitsPerDevPixel, *aDrawTarget, color); + StrokeLineWithSnapping(nsPoint(0, onePixel), nsPoint(0, mRect.height), + appUnitsPerDevPixel, *aDrawTarget, color); + StrokeLineWithSnapping(nsPoint(onePixel, mRect.height), + nsPoint(mRect.width, mRect.height), + appUnitsPerDevPixel, *aDrawTarget, color); + StrokeLineWithSnapping(nsPoint(mRect.width, onePixel), + nsPoint(mRect.width, mRect.height), + appUnitsPerDevPixel, *aDrawTarget, color); + //middle + nsRect r(onePixel, onePixel, + mRect.width - onePixel, mRect.height - onePixel); + Rect devPixelRect = + NSRectToSnappedRect(r, appUnitsPerDevPixel, *aDrawTarget); + aDrawTarget->StrokeRect(devPixelRect, color); + //shading + StrokeLineWithSnapping(nsPoint(2*onePixel, mRect.height-2*onePixel), + nsPoint(mRect.width-onePixel, mRect.height- (2*onePixel)), + appUnitsPerDevPixel, *aDrawTarget, color); + StrokeLineWithSnapping(nsPoint(mRect.width - (2*onePixel), 2*onePixel), + nsPoint(mRect.width - (2*onePixel), mRect.height-onePixel), + appUnitsPerDevPixel, *aDrawTarget, color); + } + } + } +} + +DrawResult +nsTableCellFrame::PaintBackground(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + uint32_t aFlags) +{ + nsRect rect(aPt, GetSize()); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*PresContext(), + aRenderingContext, + aDirtyRect, rect, + this, aFlags); + return nsCSSRendering::PaintBackground(params); +} + +// Called by nsTablePainter +DrawResult +nsTableCellFrame::PaintCellBackground(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt, + uint32_t aFlags) +{ + if (!StyleVisibility()->IsVisible()) { + return DrawResult::SUCCESS; + } + + return PaintBackground(aRenderingContext, aDirtyRect, aPt, aFlags); +} + +nsresult +nsTableCellFrame::ProcessBorders(nsTableFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) +{ + const nsStyleBorder* borderStyle = StyleBorder(); + if (aFrame->IsBorderCollapse() || !borderStyle->HasBorder()) + return NS_OK; + + if (!GetContentEmpty() || + StyleTableBorder()->mEmptyCells == NS_STYLE_TABLE_EMPTY_CELLS_SHOW) { + aLists.BorderBackground()->AppendNewToTop(new (aBuilder) + nsDisplayBorder(aBuilder, this)); + } + + return NS_OK; +} + +class nsDisplayTableCellBackground : public nsDisplayTableItem { +public: + nsDisplayTableCellBackground(nsDisplayListBuilder* aBuilder, + nsTableCellFrame* aFrame) : + nsDisplayTableItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableCellBackground); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTableCellBackground() { + MOZ_COUNT_DTOR(nsDisplayTableCellBackground); + } +#endif + + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray *aOutFrames) override { + aOutFrames->AppendElement(mFrame); + } + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) override; + NS_DISPLAY_DECL_NAME("TableCellBackground", TYPE_TABLE_CELL_BACKGROUND) +}; + +void nsDisplayTableCellBackground::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + DrawResult result = static_cast(mFrame)-> + PaintBackground(*aCtx, mVisibleRect, ToReferenceFrame(), + aBuilder->GetBackgroundPaintFlags()); + + nsDisplayTableItemGeometry::UpdateDrawResult(this, result); +} + +nsRect +nsDisplayTableCellBackground::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) +{ + // revert from nsDisplayTableItem's implementation ... cell backgrounds + // don't overflow the cell + return nsDisplayItem::GetBounds(aBuilder, aSnap); +} + +void nsTableCellFrame::InvalidateFrame(uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrame(aDisplayItemKey); + GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey); +} + +void nsTableCellFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey); + // If we have filters applied that would affects our bounds, then + // we get an inactive layer created and this is computed + // within FrameLayerBuilder + GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey); +} + +static void +PaintTableCellSelection(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const nsRect& aRect, nsPoint aPt) +{ + static_cast(aFrame)->DecorateForSelection(aDrawTarget, + aPt); +} + +void +nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame"); + if (IsVisibleInSelection(aBuilder)) { + nsTableFrame* tableFrame = GetTableFrame(); + int32_t emptyCellStyle = GetContentEmpty() && !tableFrame->IsBorderCollapse() ? + StyleTableBorder()->mEmptyCells + : NS_STYLE_TABLE_EMPTY_CELLS_SHOW; + // take account of 'empty-cells' + if (StyleVisibility()->IsVisible() && + (NS_STYLE_TABLE_EMPTY_CELLS_HIDE != emptyCellStyle)) { + // display outset box-shadows if we need to. + bool hasBoxShadow = !!StyleEffects()->mBoxShadow; + if (hasBoxShadow) { + aLists.BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, this)); + } + + // display background if we need to. + if (aBuilder->IsForEventDelivery() || + !StyleBackground()->IsTransparent() || StyleDisplay()->mAppearance) { + if (!tableFrame->IsBorderCollapse()) { + nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, + this, + GetRectRelativeToSelf(), + aLists.BorderBackground()); + } else if (aBuilder->IsAtRootOfPseudoStackingContext() || + aBuilder->IsForEventDelivery()) { + // The cell background was not painted by the nsTablePainter, + // so we need to do it. We have special background processing here + // so we need to duplicate some code from nsFrame::DisplayBorderBackgroundOutline + nsDisplayTableItem* item = + new (aBuilder) nsDisplayTableCellBackground(aBuilder, this); + aLists.BorderBackground()->AppendNewToTop(item); + item->UpdateForFrameBackground(this); + } else { + // The nsTablePainter will paint our background. Make sure it + // knows if we're background-attachment:fixed. + nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem(); + if (currentItem) { + currentItem->UpdateForFrameBackground(this); + } + } + } + + // display inset box-shadows if we need to. + if (hasBoxShadow) { + aLists.BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayBoxShadowInner(aBuilder, this)); + } + + // display borders if we need to + ProcessBorders(tableFrame, aBuilder, aLists); + + // and display the selection border if we need to + if (IsSelected()) { + aLists.BorderBackground()->AppendNewToTop(new (aBuilder) + nsDisplayGeneric(aBuilder, this, ::PaintTableCellSelection, + "TableCellSelection", + nsDisplayItem::TYPE_TABLE_CELL_SELECTION)); + } + } + + // the 'empty-cells' property has no effect on 'outline' + DisplayOutline(aBuilder, aLists); + } + + // Push a null 'current table item' so that descendant tables can't + // accidentally mess with our table + nsAutoPushCurrentTableItem pushTableItem; + pushTableItem.Push(aBuilder, nullptr); + + nsIFrame* kid = mFrames.FirstChild(); + NS_ASSERTION(kid && !kid->GetNextSibling(), "Table cells should have just one child"); + // The child's background will go in our BorderBackground() list. + // This isn't a problem since it won't have a real background except for + // event handling. We do not call BuildDisplayListForNonBlockChildren + // because that/ would put the child's background in the Content() list + // which isn't right (e.g., would end up on top of our child floats for + // event handling). + BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); +} + +nsIFrame::LogicalSides +nsTableCellFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const +{ + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return LogicalSides(); + } + + LogicalSides skip; + if (nullptr != GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (nullptr != GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +/* virtual */ nsMargin +nsTableCellFrame::GetBorderOverflow() +{ + return nsMargin(0, 0, 0, 0); +} + +// Align the cell's child frame within the cell + +void nsTableCellFrame::BlockDirAlignChild(WritingMode aWM, nscoord aMaxAscent) +{ + /* It's the 'border-collapse' on the table that matters */ + LogicalMargin borderPadding = GetLogicalUsedBorderAndPadding(aWM); + + nscoord bStartInset = borderPadding.BStart(aWM); + nscoord bEndInset = borderPadding.BEnd(aWM); + + uint8_t verticalAlignFlags = GetVerticalAlign(); + + nscoord bSize = BSize(aWM); + nsIFrame* firstKid = mFrames.FirstChild(); + nsSize containerSize = mRect.Size(); + NS_ASSERTION(firstKid, "Frame construction error, a table cell always has " + "an inner cell frame"); + LogicalRect kidRect = firstKid->GetLogicalRect(aWM, containerSize); + nscoord childBSize = kidRect.BSize(aWM); + + // Vertically align the child + nscoord kidBStart = 0; + switch (verticalAlignFlags) + { + case NS_STYLE_VERTICAL_ALIGN_BASELINE: + // Align the baselines of the child frame with the baselines of + // other children in the same row which have 'vertical-align: baseline' + kidBStart = bStartInset + aMaxAscent - GetCellBaseline(); + break; + + case NS_STYLE_VERTICAL_ALIGN_TOP: + // Align the top of the child frame with the top of the content area, + kidBStart = bStartInset; + break; + + case NS_STYLE_VERTICAL_ALIGN_BOTTOM: + // Align the bottom of the child frame with the bottom of the content area, + kidBStart = bSize - childBSize - bEndInset; + break; + + default: + case NS_STYLE_VERTICAL_ALIGN_MIDDLE: + // Align the middle of the child frame with the middle of the content area, + kidBStart = (bSize - childBSize - bEndInset + bStartInset) / 2; + } + // If the content is larger than the cell bsize, align from bStartInset + // (cell's content-box bstart edge). + kidBStart = std::max(bStartInset, kidBStart); + + if (kidBStart != kidRect.BStart(aWM)) { + // Invalidate at the old position first + firstKid->InvalidateFrameSubtree(); + } + + firstKid->SetPosition(aWM, LogicalPoint(aWM, kidRect.IStart(aWM), + kidBStart), containerSize); + ReflowOutput desiredSize(aWM); + desiredSize.SetSize(aWM, GetLogicalSize(aWM)); + + nsRect overflow(nsPoint(0,0), GetSize()); + overflow.Inflate(GetBorderOverflow()); + desiredSize.mOverflowAreas.SetAllTo(overflow); + ConsiderChildOverflow(desiredSize.mOverflowAreas, firstKid); + FinishAndStoreOverflow(&desiredSize); + if (kidBStart != kidRect.BStart(aWM)) { + // Make sure any child views are correctly positioned. We know the inner table + // cell won't have a view + nsContainerFrame::PositionChildViews(firstKid); + + // Invalidate new overflow rect + firstKid->InvalidateFrameSubtree(); + } + if (HasView()) { + nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this, + GetView(), + desiredSize.VisualOverflow(), 0); + } +} + +bool +nsTableCellFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) +{ + nsRect bounds(nsPoint(0,0), GetSize()); + bounds.Inflate(GetBorderOverflow()); + + aOverflowAreas.UnionAllWith(bounds); + return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); +} + +// Per CSS 2.1, we map 'sub', 'super', 'text-top', 'text-bottom', +// length, percentage, and calc() values to 'baseline'. +uint8_t +nsTableCellFrame::GetVerticalAlign() const +{ + const nsStyleCoord& verticalAlign = StyleDisplay()->mVerticalAlign; + if (verticalAlign.GetUnit() == eStyleUnit_Enumerated) { + uint8_t value = verticalAlign.GetIntValue(); + if (value == NS_STYLE_VERTICAL_ALIGN_TOP || + value == NS_STYLE_VERTICAL_ALIGN_MIDDLE || + value == NS_STYLE_VERTICAL_ALIGN_BOTTOM) { + return value; + } + } + return NS_STYLE_VERTICAL_ALIGN_BASELINE; +} + +bool +nsTableCellFrame::CellHasVisibleContent(nscoord height, + nsTableFrame* tableFrame, + nsIFrame* kidFrame) +{ + // see http://www.w3.org/TR/CSS21/tables.html#empty-cells + if (height > 0) + return true; + if (tableFrame->IsBorderCollapse()) + return true; + for (nsIFrame* innerFrame : kidFrame->PrincipalChildList()) { + nsIAtom* frameType = innerFrame->GetType(); + if (nsGkAtoms::textFrame == frameType) { + nsTextFrame* textFrame = static_cast(innerFrame); + if (textFrame->HasNoncollapsedCharacters()) + return true; + } + else if (nsGkAtoms::placeholderFrame != frameType) { + return true; + } + else { + nsIFrame *floatFrame = nsLayoutUtils::GetFloatFromPlaceholder(innerFrame); + if (floatFrame) + return true; + } + } + return false; +} + +nscoord +nsTableCellFrame::GetCellBaseline() const +{ + // Ignore the position of the inner frame relative to the cell frame + // since we want the position as though the inner were top-aligned. + nsIFrame *inner = mFrames.FirstChild(); + nscoord borderPadding = GetUsedBorderAndPadding().top; + nscoord result; + if (nsLayoutUtils::GetFirstLineBaseline(GetWritingMode(), inner, &result)) + return result + borderPadding; + return inner->GetContentRectRelativeToSelf().YMost() + + borderPadding; +} + +int32_t nsTableCellFrame::GetRowSpan() +{ + int32_t rowSpan=1; + nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent); + + // Don't look at the content's rowspan if we're a pseudo cell + if (hc && !StyleContext()->GetPseudo()) { + const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::rowspan); + // Note that we don't need to check the tag name, because only table cells + // and table headers parse the "rowspan" attribute into an integer. + if (attr && attr->Type() == nsAttrValue::eInteger) { + rowSpan = attr->GetIntegerValue(); + } + } + return rowSpan; +} + +int32_t nsTableCellFrame::GetColSpan() +{ + int32_t colSpan=1; + nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent); + + // Don't look at the content's colspan if we're a pseudo cell + if (hc && !StyleContext()->GetPseudo()) { + const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::colspan); + // Note that we don't need to check the tag name, because only table cells + // and table headers parse the "colspan" attribute into an integer. + if (attr && attr->Type() == nsAttrValue::eInteger) { + colSpan = attr->GetIntegerValue(); + } + } + return colSpan; +} + +/* virtual */ nscoord +nsTableCellFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nscoord result = 0; + DISPLAY_MIN_WIDTH(this, result); + + nsIFrame *inner = mFrames.FirstChild(); + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner, + nsLayoutUtils::MIN_ISIZE); + return result; +} + +/* virtual */ nscoord +nsTableCellFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nscoord result = 0; + DISPLAY_PREF_WIDTH(this, result); + + nsIFrame *inner = mFrames.FirstChild(); + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner, + nsLayoutUtils::PREF_ISIZE); + return result; +} + +/* virtual */ nsIFrame::IntrinsicISizeOffsetData +nsTableCellFrame::IntrinsicISizeOffsets() +{ + IntrinsicISizeOffsetData result = nsContainerFrame::IntrinsicISizeOffsets(); + + result.hMargin = 0; + result.hPctMargin = 0; + + WritingMode wm = GetWritingMode(); + result.hBorder = GetBorderWidth(wm).IStartEnd(wm); + + return result; +} + +#ifdef DEBUG +#define PROBABLY_TOO_LARGE 1000000 +static +void DebugCheckChildSize(nsIFrame* aChild, + ReflowOutput& aMet) +{ + WritingMode wm = aMet.GetWritingMode(); + if ((aMet.ISize(wm) < 0) || (aMet.ISize(wm) > PROBABLY_TOO_LARGE)) { + printf("WARNING: cell content %p has large inline size %d \n", + static_cast(aChild), int32_t(aMet.ISize(wm))); + } +} +#endif + +// the computed bsize for the cell, which descendants use for percent bsize calculations +// it is the bsize (minus border, padding) of the cell's first in flow during its final +// reflow without an unconstrained bsize. +static nscoord +CalcUnpaginatedBSize(nsTableCellFrame& aCellFrame, + nsTableFrame& aTableFrame, + nscoord aBlockDirBorderPadding) +{ + const nsTableCellFrame* firstCellInFlow = + static_cast(aCellFrame.FirstInFlow()); + nsTableFrame* firstTableInFlow = + static_cast(aTableFrame.FirstInFlow()); + nsTableRowFrame* row = + static_cast(firstCellInFlow->GetParent()); + nsTableRowGroupFrame* firstRGInFlow = + static_cast(row->GetParent()); + + int32_t rowIndex; + firstCellInFlow->GetRowIndex(rowIndex); + int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(*firstCellInFlow); + + nscoord computedBSize = firstTableInFlow->GetRowSpacing(rowIndex, + rowIndex + rowSpan - 1); + computedBSize -= aBlockDirBorderPadding; + int32_t rowX; + for (row = firstRGInFlow->GetFirstRow(), rowX = 0; row; row = row->GetNextRow(), rowX++) { + if (rowX > rowIndex + rowSpan - 1) { + break; + } + else if (rowX >= rowIndex) { + computedBSize += row->GetUnpaginatedBSize(); + } + } + return computedBSize; +} + +void +nsTableCellFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableCellFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + + if (aReflowInput.mFlags.mSpecialBSizeReflow) { + FirstInFlow()->AddStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW); + } + + // see if a special bsize reflow needs to occur due to having a pct height + nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput); + + aStatus = NS_FRAME_COMPLETE; + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalSize availSize(wm, aReflowInput.AvailableISize(), + aReflowInput.AvailableBSize()); + + LogicalMargin borderPadding = aReflowInput.ComputedLogicalPadding(); + LogicalMargin border = GetBorderWidth(wm); + borderPadding += border; + + // reduce available space by insets, if we're in a constrained situation + availSize.ISize(wm) -= borderPadding.IStartEnd(wm); + if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) { + availSize.BSize(wm) -= borderPadding.BStartEnd(wm); + } + + // Try to reflow the child into the available space. It might not + // fit or might need continuing. + if (availSize.BSize(wm) < 0) { + availSize.BSize(wm) = 1; + } + + ReflowOutput kidSize(wm, aDesiredSize.mFlags); + kidSize.ClearSize(); + SetPriorAvailISize(aReflowInput.AvailableISize()); + nsIFrame* firstKid = mFrames.FirstChild(); + NS_ASSERTION(firstKid, "Frame construction error, a table cell always has an inner cell frame"); + nsTableFrame* tableFrame = GetTableFrame(); + + if (aReflowInput.mFlags.mSpecialBSizeReflow) { + const_cast(aReflowInput). + SetComputedBSize(BSize(wm) - borderPadding.BStartEnd(wm)); + DISPLAY_REFLOW_CHANGE(); + } + else if (aPresContext->IsPaginated()) { + nscoord computedUnpaginatedBSize = + CalcUnpaginatedBSize((nsTableCellFrame&)*this, + *tableFrame, borderPadding.BStartEnd(wm)); + if (computedUnpaginatedBSize > 0) { + const_cast(aReflowInput).SetComputedBSize(computedUnpaginatedBSize); + DISPLAY_REFLOW_CHANGE(); + } + } + else { + SetHasPctOverBSize(false); + } + + WritingMode kidWM = firstKid->GetWritingMode(); + ReflowInput kidReflowInput(aPresContext, aReflowInput, firstKid, + availSize.ConvertTo(kidWM, wm)); + + // Don't be a percent height observer if we're in the middle of + // special-bsize reflow, in case we get an accidental NotifyPercentBSize() + // call (which we shouldn't honor during special-bsize reflow) + if (!aReflowInput.mFlags.mSpecialBSizeReflow) { + // mPercentBSizeObserver is for children of cells in quirks mode, + // but only those than are tables in standards mode. NeedsToObserve + // will determine how far this is propagated to descendants. + kidReflowInput.mPercentBSizeObserver = this; + } + // Don't propagate special bsize reflow state to our kids + kidReflowInput.mFlags.mSpecialBSizeReflow = false; + + if (aReflowInput.mFlags.mSpecialBSizeReflow || + FirstInFlow()->HasAnyStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) { + // We need to force the kid to have mBResize set if we've had a + // special reflow in the past, since the non-special reflow needs to + // resize back to what it was without the special bsize reflow. + kidReflowInput.SetBResize(true); + } + + nsSize containerSize = + aReflowInput.ComputedSizeAsContainerIfConstrained(); + + LogicalPoint kidOrigin(wm, borderPadding.IStart(wm), + borderPadding.BStart(wm)); + nsRect origRect = firstKid->GetRect(); + nsRect origVisualOverflow = firstKid->GetVisualOverflowRect(); + bool firstReflow = firstKid->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + ReflowChild(firstKid, aPresContext, kidSize, kidReflowInput, + wm, kidOrigin, containerSize, 0, aStatus); + if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) { + // Don't pass OVERFLOW_INCOMPLETE through tables until they can actually handle it + //XXX should paginate overflow as overflow, but not in this patch (bug 379349) + NS_FRAME_SET_INCOMPLETE(aStatus); + printf("Set table cell incomplete %p\n", static_cast(this)); + } + + // XXXbz is this invalidate actually needed, really? + if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { + InvalidateFrameSubtree(); + } + +#ifdef DEBUG + DebugCheckChildSize(firstKid, kidSize); +#endif + + // 0 dimensioned cells need to be treated specially in Standard/NavQuirks mode + // see testcase "emptyCells.html" + nsIFrame* prevInFlow = GetPrevInFlow(); + bool isEmpty; + if (prevInFlow) { + isEmpty = static_cast(prevInFlow)->GetContentEmpty(); + } else { + isEmpty = !CellHasVisibleContent(kidSize.Height(), tableFrame, firstKid); + } + SetContentEmpty(isEmpty); + + // Place the child + FinishReflowChild(firstKid, aPresContext, kidSize, &kidReflowInput, + wm, kidOrigin, containerSize, 0); + + nsTableFrame::InvalidateTableFrame(firstKid, origRect, origVisualOverflow, + firstReflow); + + // first, compute the bsize which can be set w/o being restricted by + // available bsize + LogicalSize cellSize(wm); + cellSize.BSize(wm) = kidSize.BSize(wm); + + if (NS_UNCONSTRAINEDSIZE != cellSize.BSize(wm)) { + cellSize.BSize(wm) += borderPadding.BStartEnd(wm); + } + + // next determine the cell's isize + cellSize.ISize(wm) = kidSize.ISize(wm); // at this point, we've factored in the cell's style attributes + + // factor in border and padding + if (NS_UNCONSTRAINEDSIZE != cellSize.ISize(wm)) { + cellSize.ISize(wm) += borderPadding.IStartEnd(wm); + } + + // set the cell's desired size and max element size + aDesiredSize.SetSize(wm, cellSize); + + // the overflow area will be computed when BlockDirAlignChild() gets called + + if (aReflowInput.mFlags.mSpecialBSizeReflow) { + if (aDesiredSize.BSize(wm) > BSize(wm)) { + // set a bit indicating that the pct bsize contents exceeded + // the height that they could honor in the pass 2 reflow + SetHasPctOverBSize(true); + } + if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) { + aDesiredSize.BSize(wm) = BSize(wm); + } + } + + // If our parent is in initial reflow, it'll handle invalidating our + // entire overflow rect. + if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && + nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { + InvalidateFrame(); + } + + // remember the desired size for this reflow + SetDesiredSize(aDesiredSize); + + // Any absolutely-positioned children will get reflowed in + // nsFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +/* ----- global methods ----- */ + +NS_QUERYFRAME_HEAD(nsTableCellFrame) + NS_QUERYFRAME_ENTRY(nsTableCellFrame) + NS_QUERYFRAME_ENTRY(nsITableCellLayout) + NS_QUERYFRAME_ENTRY(nsIPercentBSizeObserver) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +#ifdef ACCESSIBILITY +a11y::AccType +nsTableCellFrame::AccessibleType() +{ + return a11y::eHTMLTableCellType; +} +#endif + +/* This is primarily for editor access via nsITableLayout */ +NS_IMETHODIMP +nsTableCellFrame::GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex) +{ + nsresult res = GetRowIndex(aRowIndex); + if (NS_FAILED(res)) + { + aColIndex = 0; + return res; + } + aColIndex = mColIndex; + return NS_OK; +} + +nsTableCellFrame* +NS_NewTableCellFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext, + nsTableFrame* aTableFrame) +{ + if (aTableFrame->IsBorderCollapse()) + return new (aPresShell) nsBCTableCellFrame(aContext, aTableFrame); + else + return new (aPresShell) nsTableCellFrame(aContext, aTableFrame); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsBCTableCellFrame) + +LogicalMargin +nsTableCellFrame::GetBorderWidth(WritingMode aWM) const +{ + return LogicalMargin(aWM, StyleBorder()->GetComputedBorder()); +} + +nsIAtom* +nsTableCellFrame::GetType() const +{ + return nsGkAtoms::tableCellFrame; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableCellFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("TableCell"), aResult); +} +#endif + +// nsBCTableCellFrame + +nsBCTableCellFrame::nsBCTableCellFrame(nsStyleContext* aContext, + nsTableFrame* aTableFrame) + : nsTableCellFrame(aContext, aTableFrame) +{ + mBStartBorder = mIEndBorder = mBEndBorder = mIStartBorder = 0; +} + +nsBCTableCellFrame::~nsBCTableCellFrame() +{ +} + +nsIAtom* +nsBCTableCellFrame::GetType() const +{ + return nsGkAtoms::bcTableCellFrame; +} + +/* virtual */ nsMargin +nsBCTableCellFrame::GetUsedBorder() const +{ + WritingMode wm = GetWritingMode(); + return GetBorderWidth(wm).GetPhysicalMargin(wm); +} + +/* virtual */ bool +nsBCTableCellFrame::GetBorderRadii(const nsSize& aFrameSize, + const nsSize& aBorderArea, + Sides aSkipSides, + nscoord aRadii[8]) const +{ + NS_FOR_CSS_HALF_CORNERS(corner) { + aRadii[corner] = 0; + } + return false; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsBCTableCellFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("BCTableCell"), aResult); +} +#endif + +LogicalMargin +nsBCTableCellFrame::GetBorderWidth(WritingMode aWM) const +{ + int32_t pixelsToTwips = nsPresContext::AppUnitsPerCSSPixel(); + return LogicalMargin(aWM, + BC_BORDER_END_HALF_COORD(pixelsToTwips, mBStartBorder), + BC_BORDER_START_HALF_COORD(pixelsToTwips, mIEndBorder), + BC_BORDER_START_HALF_COORD(pixelsToTwips, mBEndBorder), + BC_BORDER_END_HALF_COORD(pixelsToTwips, mIStartBorder)); +} + +BCPixelSize +nsBCTableCellFrame::GetBorderWidth(LogicalSide aSide) const +{ + switch(aSide) { + case eLogicalSideBStart: + return BC_BORDER_END_HALF(mBStartBorder); + case eLogicalSideIEnd: + return BC_BORDER_START_HALF(mIEndBorder); + case eLogicalSideBEnd: + return BC_BORDER_START_HALF(mBEndBorder); + default: + return BC_BORDER_END_HALF(mIStartBorder); + } +} + +void +nsBCTableCellFrame::SetBorderWidth(LogicalSide aSide, BCPixelSize aValue) +{ + switch(aSide) { + case eLogicalSideBStart: + mBStartBorder = aValue; + break; + case eLogicalSideIEnd: + mIEndBorder = aValue; + break; + case eLogicalSideBEnd: + mBEndBorder = aValue; + break; + default: + mIStartBorder = aValue; + } +} + +/* virtual */ nsMargin +nsBCTableCellFrame::GetBorderOverflow() +{ + WritingMode wm = GetWritingMode(); + int32_t p2t = nsPresContext::AppUnitsPerCSSPixel(); + LogicalMargin halfBorder(wm, + BC_BORDER_START_HALF_COORD(p2t, mBStartBorder), + BC_BORDER_END_HALF_COORD(p2t, mIEndBorder), + BC_BORDER_END_HALF_COORD(p2t, mBEndBorder), + BC_BORDER_START_HALF_COORD(p2t, mIStartBorder)); + return halfBorder.GetPhysicalMargin(wm); +} + + +DrawResult +nsBCTableCellFrame::PaintBackground(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + uint32_t aFlags) +{ + // make border-width reflect the half of the border-collapse + // assigned border that's inside the cell + WritingMode wm = GetWritingMode(); + nsMargin borderWidth = GetBorderWidth(wm).GetPhysicalMargin(wm); + + nsStyleBorder myBorder(*StyleBorder()); + + NS_FOR_CSS_SIDES(side) { + myBorder.SetBorderWidth(side, borderWidth.Side(side)); + } + + // bypassing nsCSSRendering::PaintBackground is safe because this kind + // of frame cannot be used for the root element + nsRect rect(aPt, GetSize()); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*PresContext(), + aRenderingContext, aDirtyRect, + rect, this, + aFlags); + return nsCSSRendering::PaintBackgroundWithSC(params, StyleContext(), myBorder); +} diff --git a/layout/tables/nsTableCellFrame.h b/layout/tables/nsTableCellFrame.h new file mode 100644 index 000000000..6717e1b70 --- /dev/null +++ b/layout/tables/nsTableCellFrame.h @@ -0,0 +1,357 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTableCellFrame_h__ +#define nsTableCellFrame_h__ + +#include "mozilla/Attributes.h" +#include "celldata.h" +#include "imgIContainer.h" +#include "nsITableCellLayout.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsStyleContext.h" +#include "nsIPercentBSizeObserver.h" +#include "nsGkAtoms.h" +#include "nsLayoutUtils.h" +#include "nsTArray.h" +#include "nsTableRowFrame.h" +#include "mozilla/WritingModes.h" + +/** + * nsTableCellFrame + * data structure to maintain information about a single table cell's frame + * + * NOTE: frames are not ref counted. We expose addref and release here + * so we can change that decsion in the future. Users of nsITableCellLayout + * should refcount correctly as if this object is being ref counted, though + * no actual support is under the hood. + * + * @author sclark + */ +class nsTableCellFrame : public nsContainerFrame, + public nsITableCellLayout, + public nsIPercentBSizeObserver +{ + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::image::DrawResult DrawResult; + +protected: + typedef mozilla::WritingMode WritingMode; + typedef mozilla::LogicalSide LogicalSide; + typedef mozilla::LogicalMargin LogicalMargin; + +public: + NS_DECL_QUERYFRAME_TARGET(nsTableCellFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + // default constructor supplied by the compiler + + nsTableCellFrame(nsStyleContext* aContext, nsTableFrame* aTableFrame); + ~nsTableCellFrame(); + + nsTableRowFrame* GetTableRowFrame() const + { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableRowFrame); + return static_cast(parent); + } + + nsTableFrame* GetTableFrame() const + { + return GetTableRowFrame()->GetTableFrame(); + } + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + +#ifdef ACCESSIBILITY + virtual mozilla::a11y::AccType AccessibleType() override; +#endif + + virtual nsresult AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) override; + + /** @see nsIFrame::DidSetStyleContext */ + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + +#ifdef DEBUG + // Our anonymous block frame is the content insertion frame so these + // methods should never be called: + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; +#endif + + virtual nsContainerFrame* GetContentInsertionFrame() override { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + virtual nsMargin GetUsedMargin() const override; + + virtual void NotifyPercentBSize(const ReflowInput& aReflowInput) override; + + virtual bool NeedsToObserve(const ReflowInput& aReflowInput) override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + DrawResult PaintCellBackground(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt, + uint32_t aFlags); + + + virtual nsresult ProcessBorders(nsTableFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists); + + virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; + virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override; + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + /** + * Get the "type" of the frame + * + * @see nsLayoutAtoms::tableCellFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + // Although the spec doesn't say that writing-mode is not applied to + // table-cells, we still override this method here because we want to + // make effective writing mode of table structure frames consistent + // within a table. The content inside table cells is reflowed by an + // anonymous block, hence their writing mode is not affected. + virtual mozilla::WritingMode GetWritingMode() const override + { return GetTableFrame()->GetWritingMode(); } + + void BlockDirAlignChild(mozilla::WritingMode aWM, nscoord aMaxAscent); + + /* + * Get the value of vertical-align adjusted for CSS 2's rules for a + * table cell, which means the result is always + * NS_STYLE_VERTICAL_ALIGN_{TOP,MIDDLE,BOTTOM,BASELINE}. + */ + virtual uint8_t GetVerticalAlign() const; + + bool HasVerticalAlignBaseline() const { + return GetVerticalAlign() == NS_STYLE_VERTICAL_ALIGN_BASELINE; + } + + bool CellHasVisibleContent(nscoord aBSize, + nsTableFrame* tableFrame, + nsIFrame* kidFrame); + + /** + * Get the first-line baseline of the cell relative to its block-start border + * edge, as if the cell were vertically aligned to the top of the row. + */ + nscoord GetCellBaseline() const; + + /** + * return the cell's specified row span. this is what was specified in the + * content model or in the style info, and is always >= 1. + * to get the effective row span (the actual value that applies), use GetEffectiveRowSpan() + * @see nsTableFrame::GetEffectiveRowSpan() + */ + virtual int32_t GetRowSpan(); + + // there is no set row index because row index depends on the cell's parent row only + + /*---------------- nsITableCellLayout methods ------------------------*/ + + /** + * return the cell's starting row index (starting at 0 for the first row). + * for continued cell frames the row index is that of the cell's first-in-flow + * and the column index (starting at 0 for the first column + */ + NS_IMETHOD GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex) override; + + /** return the mapped cell's row index (starting at 0 for the first row) */ + virtual nsresult GetRowIndex(int32_t &aRowIndex) const override; + + /** + * return the cell's specified col span. this is what was specified in the + * content model or in the style info, and is always >= 1. + * to get the effective col span (the actual value that applies), use GetEffectiveColSpan() + * @see nsTableFrame::GetEffectiveColSpan() + */ + virtual int32_t GetColSpan(); + + /** return the cell's column index (starting at 0 for the first column) */ + virtual nsresult GetColIndex(int32_t &aColIndex) const override; + void SetColIndex(int32_t aColIndex); + + /** return the available isize given to this frame during its last reflow */ + inline nscoord GetPriorAvailISize(); + + /** set the available isize given to this frame during its last reflow */ + inline void SetPriorAvailISize(nscoord aPriorAvailISize); + + /** return the desired size returned by this frame during its last reflow */ + inline mozilla::LogicalSize GetDesiredSize(); + + /** set the desired size returned by this frame during its last reflow */ + inline void SetDesiredSize(const ReflowOutput & aDesiredSize); + + bool GetContentEmpty(); + void SetContentEmpty(bool aContentEmpty); + + bool HasPctOverBSize(); + void SetHasPctOverBSize(bool aValue); + + nsTableCellFrame* GetNextCell() const; + + virtual LogicalMargin GetBorderWidth(WritingMode aWM) const; + + virtual DrawResult PaintBackground(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + uint32_t aFlags); + + void DecorateForSelection(DrawTarget* aDrawTarget, nsPoint aPt); + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart)); + } + + virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + +protected: + virtual LogicalSides + GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override; + + /** + * GetBorderOverflow says how far the cell's own borders extend + * outside its own bounds. In the separated borders model this should + * just be zero (as it is for most frames), but in the collapsed + * borders model (for which nsBCTableCellFrame overrides this virtual + * method), it considers the extents of the collapsed border. + */ + virtual nsMargin GetBorderOverflow(); + + friend class nsTableRowFrame; + + uint32_t mColIndex; // the starting column for this cell + + nscoord mPriorAvailISize; // the avail isize during the last reflow + mozilla::LogicalSize mDesiredSize; // the last desired inline and block size +}; + +inline nscoord nsTableCellFrame::GetPriorAvailISize() +{ return mPriorAvailISize; } + +inline void nsTableCellFrame::SetPriorAvailISize(nscoord aPriorAvailISize) +{ mPriorAvailISize = aPriorAvailISize; } + +inline mozilla::LogicalSize nsTableCellFrame::GetDesiredSize() +{ return mDesiredSize; } + +inline void nsTableCellFrame::SetDesiredSize(const ReflowOutput & aDesiredSize) +{ + mozilla::WritingMode wm = aDesiredSize.GetWritingMode(); + mDesiredSize = aDesiredSize.Size(wm).ConvertTo(GetWritingMode(), wm); +} + +inline bool nsTableCellFrame::GetContentEmpty() +{ + return HasAnyStateBits(NS_TABLE_CELL_CONTENT_EMPTY); +} + +inline void nsTableCellFrame::SetContentEmpty(bool aContentEmpty) +{ + if (aContentEmpty) { + AddStateBits(NS_TABLE_CELL_CONTENT_EMPTY); + } else { + RemoveStateBits(NS_TABLE_CELL_CONTENT_EMPTY); + } +} + +inline bool nsTableCellFrame::HasPctOverBSize() +{ + return HasAnyStateBits(NS_TABLE_CELL_HAS_PCT_OVER_BSIZE); +} + +inline void nsTableCellFrame::SetHasPctOverBSize(bool aValue) +{ + if (aValue) { + AddStateBits(NS_TABLE_CELL_HAS_PCT_OVER_BSIZE); + } else { + RemoveStateBits(NS_TABLE_CELL_HAS_PCT_OVER_BSIZE); + } +} + +// nsBCTableCellFrame +class nsBCTableCellFrame final : public nsTableCellFrame +{ + typedef mozilla::image::DrawResult DrawResult; +public: + NS_DECL_FRAMEARENA_HELPERS + + nsBCTableCellFrame(nsStyleContext* aContext, nsTableFrame* aTableFrame); + + ~nsBCTableCellFrame(); + + virtual nsIAtom* GetType() const override; + + virtual nsMargin GetUsedBorder() const override; + virtual bool GetBorderRadii(const nsSize& aFrameSize, + const nsSize& aBorderArea, + Sides aSkipSides, + nscoord aRadii[8]) const override; + + // Get the *inner half of the border only*, in twips. + virtual LogicalMargin GetBorderWidth(WritingMode aWM) const override; + + // Get the *inner half of the border only*, in pixels. + BCPixelSize GetBorderWidth(LogicalSide aSide) const; + + // Set the full (both halves) width of the border + void SetBorderWidth(LogicalSide aSide, BCPixelSize aPixelValue); + + virtual nsMargin GetBorderOverflow() override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + virtual DrawResult PaintBackground(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + uint32_t aFlags) override; + +private: + + // These are the entire width of the border (the cell edge contains only + // the inner half, per the macros in nsTablePainter.h). + BCPixelSize mBStartBorder; + BCPixelSize mIEndBorder; + BCPixelSize mBEndBorder; + BCPixelSize mIStartBorder; +}; + +#endif diff --git a/layout/tables/nsTableColFrame.cpp b/layout/tables/nsTableColFrame.cpp new file mode 100644 index 000000000..8f449c3d9 --- /dev/null +++ b/layout/tables/nsTableColFrame.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsTableColFrame.h" +#include "nsTableFrame.h" +#include "nsContainerFrame.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsGkAtoms.h" +#include "nsCSSRendering.h" +#include "nsIContent.h" + +using namespace mozilla; + +#define COL_TYPE_BITS (NS_FRAME_STATE_BIT(28) | \ + NS_FRAME_STATE_BIT(29) | \ + NS_FRAME_STATE_BIT(30) | \ + NS_FRAME_STATE_BIT(31)) +#define COL_TYPE_OFFSET 28 + +using namespace mozilla; + +nsTableColFrame::nsTableColFrame(nsStyleContext* aContext) : + nsSplittableFrame(aContext) +{ + SetColType(eColContent); + ResetIntrinsics(); + ResetSpanIntrinsics(); + ResetFinalISize(); +} + +nsTableColFrame::~nsTableColFrame() +{ +} + +nsTableColType +nsTableColFrame::GetColType() const +{ + return (nsTableColType)((mState & COL_TYPE_BITS) >> COL_TYPE_OFFSET); +} + +void +nsTableColFrame::SetColType(nsTableColType aType) +{ + NS_ASSERTION(aType != eColAnonymousCol || + (GetPrevContinuation() && + GetPrevContinuation()->GetNextContinuation() == this && + GetPrevContinuation()->GetNextSibling() == this), + "spanned content cols must be continuations"); + uint32_t type = aType - eColContent; + RemoveStateBits(COL_TYPE_BITS); + AddStateBits(nsFrameState(type << COL_TYPE_OFFSET)); +} + +/* virtual */ void +nsTableColFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsSplittableFrame::DidSetStyleContext(aOldStyleContext); + + if (!aOldStyleContext) //avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) { + TableArea damageArea(GetColIndex(), 0, 1, tableFrame->GetRowCount()); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void nsTableColFrame::SetContinuousBCBorderWidth(LogicalSide aForSide, + BCPixelSize aPixelValue) +{ + switch (aForSide) { + case eLogicalSideBStart: + mBStartContBorderWidth = aPixelValue; + return; + case eLogicalSideIEnd: + mIEndContBorderWidth = aPixelValue; + return; + case eLogicalSideBEnd: + mBEndContBorderWidth = aPixelValue; + return; + default: + NS_ERROR("invalid side arg"); + } +} + +void +nsTableColFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableColFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + aDesiredSize.ClearSize(); + const nsStyleVisibility* colVis = StyleVisibility(); + bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible); + if (collapseCol) { + GetTableFrame()->SetNeedToCollapse(true); + } + aStatus = NS_FRAME_COMPLETE; + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +int32_t nsTableColFrame::GetSpan() +{ + return StyleTable()->mSpan; +} + +#ifdef DEBUG +void nsTableColFrame::Dump(int32_t aIndent) +{ + char* indent = new char[aIndent + 1]; + if (!indent) return; + for (int32_t i = 0; i < aIndent + 1; i++) { + indent[i] = ' '; + } + indent[aIndent] = 0; + + printf("%s**START COL DUMP**\n%s colIndex=%d coltype=", + indent, indent, mColIndex); + nsTableColType colType = GetColType(); + switch (colType) { + case eColContent: + printf(" content "); + break; + case eColAnonymousCol: + printf(" anonymous-column "); + break; + case eColAnonymousColGroup: + printf(" anonymous-colgroup "); + break; + case eColAnonymousCell: + printf(" anonymous-cell "); + break; + } + printf("\nm:%d c:%d(%c) p:%f sm:%d sc:%d sp:%f f:%d", + int32_t(mMinCoord), int32_t(mPrefCoord), + mHasSpecifiedCoord ? 's' : 'u', mPrefPercent, + int32_t(mSpanMinCoord), int32_t(mSpanPrefCoord), + mSpanPrefPercent, + int32_t(GetFinalISize())); + printf("\n%s**END COL DUMP** ", indent); + delete [] indent; +} +#endif +/* ----- global methods ----- */ + +nsTableColFrame* +NS_NewTableColFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableColFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableColFrame) + +nsTableColFrame* +nsTableColFrame::GetNextCol() const +{ + nsIFrame* childFrame = GetNextSibling(); + while (childFrame) { + if (nsGkAtoms::tableColFrame == childFrame->GetType()) { + return (nsTableColFrame*)childFrame; + } + childFrame = childFrame->GetNextSibling(); + } + return nullptr; +} + +nsIAtom* +nsTableColFrame::GetType() const +{ + return nsGkAtoms::tableColFrame; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableColFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("TableCol"), aResult); +} +#endif + +nsSplittableType +nsTableColFrame::GetSplittableType() const +{ + return NS_FRAME_NOT_SPLITTABLE; +} + +void +nsTableColFrame::InvalidateFrame(uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrame(aDisplayItemKey); + GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey); +} + +void +nsTableColFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey); + + // If we have filters applied that would affects our bounds, then + // we get an inactive layer created and this is computed + // within FrameLayerBuilder + GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey); +} + diff --git a/layout/tables/nsTableColFrame.h b/layout/tables/nsTableColFrame.h new file mode 100644 index 000000000..e95fe76b1 --- /dev/null +++ b/layout/tables/nsTableColFrame.h @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTableColFrame_h__ +#define nsTableColFrame_h__ + +#include "mozilla/Attributes.h" +#include "celldata.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsTArray.h" +#include "nsTableColGroupFrame.h" +#include "mozilla/WritingModes.h" + +class nsTableColFrame : public nsSplittableFrame { +public: + NS_DECL_FRAMEARENA_HELPERS + + enum {eWIDTH_SOURCE_NONE =0, // no cell has contributed to the width style + eWIDTH_SOURCE_CELL =1, // a cell specified a width + eWIDTH_SOURCE_CELL_WITH_SPAN=2 // a cell implicitly specified a width via colspan + }; + + nsTableColType GetColType() const; + void SetColType(nsTableColType aType); + + /** instantiate a new instance of nsTableRowFrame. + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableColFrame* NS_NewTableColFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + nsTableColGroupFrame* GetTableColGroupFrame() const + { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableColGroupFrame); + return static_cast(parent); + } + + nsTableFrame* GetTableFrame() const + { + return GetTableColGroupFrame()->GetTableFrame(); + } + + /** @see nsIFrame::DidSetStyleContext */ + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + int32_t GetColIndex() const; + + void SetColIndex (int32_t aColIndex); + + nsTableColFrame* GetNextCol() const; + + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + /** + * Table columns never paint anything, nor receive events. + */ + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::tableColFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + virtual nsSplittableType GetSplittableType() const override; + + virtual mozilla::WritingMode GetWritingMode() const override + { return GetTableFrame()->GetWritingMode(); } + + /** return the number of the columns the col represents. always >= 1 */ + int32_t GetSpan(); + + /** convenience method, calls into cellmap */ + int32_t Count() const; + + nscoord GetIStartBorderWidth() const { return mIStartBorderWidth; } + nscoord GetIEndBorderWidth() const { return mIEndBorderWidth; } + void SetIStartBorderWidth(BCPixelSize aWidth) { mIStartBorderWidth = aWidth; } + void SetIEndBorderWidth(BCPixelSize aWidth) { mIEndBorderWidth = aWidth; } + + /** + * Gets inner border widths before collapsing with cell borders + * Caller must get istart border from previous column or from table + * GetContinuousBCBorderWidth will not overwrite aBorder.IStart + * see nsTablePainter about continuous borders + * + * @return outer iend border width (istart inner for next column) + */ + nscoord GetContinuousBCBorderWidth(mozilla::WritingMode aWM, + mozilla::LogicalMargin& aBorder); + /** + * Set full border widths before collapsing with cell borders + * @param aForSide - side to set; only valid for bstart, iend, and bend + */ + void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide, + BCPixelSize aPixelValue); +#ifdef DEBUG + void Dump(int32_t aIndent); +#endif + + /** + * Restore the default values of the intrinsic widths, so that we can + * re-accumulate intrinsic widths from the cells in the column. + */ + void ResetIntrinsics() { + mMinCoord = 0; + mPrefCoord = 0; + mPrefPercent = 0.0f; + mHasSpecifiedCoord = false; + } + + /** + * Restore the default value of the preferred percentage width (the + * only intrinsic width used by FixedTableLayoutStrategy. + */ + void ResetPrefPercent() { + mPrefPercent = 0.0f; + } + + /** + * Restore the default values of the temporary buffer for + * spanning-cell intrinsic widths (as we process spanning cells). + */ + void ResetSpanIntrinsics() { + mSpanMinCoord = 0; + mSpanPrefCoord = 0; + mSpanPrefPercent = 0.0f; + } + + /** + * Add the widths for a cell or column element, or the contribution of + * the widths from a column-spanning cell: + * @param aMinCoord The minimum intrinsic width + * @param aPrefCoord The preferred intrinsic width or, if there is a + * specified non-percentage width, max(specified width, minimum intrinsic + * width). + * @param aHasSpecifiedCoord Whether there is a specified + * non-percentage width. + * + * Note that the implementation of this functions is a bit tricky + * since mPrefCoord means different things depending on + * whether mHasSpecifiedCoord is true (and likewise for aPrefCoord and + * aHasSpecifiedCoord). If mHasSpecifiedCoord is false, then + * all widths added had aHasSpecifiedCoord false and mPrefCoord is the + * largest of the pref widths. But if mHasSpecifiedCoord is true, + * then mPrefCoord is the largest of (1) the pref widths for cells + * with aHasSpecifiedCoord true and (2) the min widths for cells with + * aHasSpecifiedCoord false. + */ + void AddCoords(nscoord aMinCoord, nscoord aPrefCoord, + bool aHasSpecifiedCoord) { + NS_ASSERTION(aMinCoord <= aPrefCoord, "intrinsic widths out of order"); + + if (aHasSpecifiedCoord && !mHasSpecifiedCoord) { + mPrefCoord = mMinCoord; + mHasSpecifiedCoord = true; + } + if (!aHasSpecifiedCoord && mHasSpecifiedCoord) { + aPrefCoord = aMinCoord; // NOTE: modifying argument + } + + if (aMinCoord > mMinCoord) + mMinCoord = aMinCoord; + if (aPrefCoord > mPrefCoord) + mPrefCoord = aPrefCoord; + + NS_ASSERTION(mMinCoord <= mPrefCoord, "min larger than pref"); + } + + /** + * Add a percentage width specified on a cell or column element or the + * contribution to this column of a percentage width specified on a + * column-spanning cell. + */ + void AddPrefPercent(float aPrefPercent) { + if (aPrefPercent > mPrefPercent) + mPrefPercent = aPrefPercent; + } + + /** + * Get the largest minimum intrinsic width for this column. + */ + nscoord GetMinCoord() const { return mMinCoord; } + /** + * Get the largest preferred width for this column, or, if there were + * any specified non-percentage widths (see GetHasSpecifiedCoord), the + * largest minimum intrinsic width or specified width. + */ + nscoord GetPrefCoord() const { return mPrefCoord; } + /** + * Get whether there were any specified widths contributing to this + * column. + */ + bool GetHasSpecifiedCoord() const { return mHasSpecifiedCoord; } + + /** + * Get the largest specified percentage width contributing to this + * column (returns 0 if there were none). + */ + float GetPrefPercent() const { return mPrefPercent; } + + /** + * Like AddCoords, but into a temporary buffer used for groups of + * column-spanning cells. + */ + void AddSpanCoords(nscoord aSpanMinCoord, nscoord aSpanPrefCoord, + bool aSpanHasSpecifiedCoord) { + NS_ASSERTION(aSpanMinCoord <= aSpanPrefCoord, + "intrinsic widths out of order"); + + if (!aSpanHasSpecifiedCoord && mHasSpecifiedCoord) { + aSpanPrefCoord = aSpanMinCoord; // NOTE: modifying argument + } + + if (aSpanMinCoord > mSpanMinCoord) + mSpanMinCoord = aSpanMinCoord; + if (aSpanPrefCoord > mSpanPrefCoord) + mSpanPrefCoord = aSpanPrefCoord; + + NS_ASSERTION(mSpanMinCoord <= mSpanPrefCoord, "min larger than pref"); + } + + /* + * Accumulate percentage widths on column spanning cells into + * temporary variables. + */ + void AddSpanPrefPercent(float aSpanPrefPercent) { + if (aSpanPrefPercent > mSpanPrefPercent) + mSpanPrefPercent = aSpanPrefPercent; + } + + /* + * Accumulate the temporary variables for column spanning cells into + * the primary variables. + */ + void AccumulateSpanIntrinsics() { + AddCoords(mSpanMinCoord, mSpanPrefCoord, mHasSpecifiedCoord); + AddPrefPercent(mSpanPrefPercent); + } + + // Used to adjust a column's pref percent so that the table's total + // never exceeeds 100% (by only allowing percentages to be used, + // starting at the first column, until they reach 100%). + void AdjustPrefPercent(float *aTableTotalPercent) { + float allowed = 1.0f - *aTableTotalPercent; + if (mPrefPercent > allowed) + mPrefPercent = allowed; + *aTableTotalPercent += mPrefPercent; + } + + // The final width of the column. + void ResetFinalISize() { + mFinalISize = nscoord_MIN; // so we detect that it changed + } + void SetFinalISize(nscoord aFinalISize) { + mFinalISize = aFinalISize; + } + nscoord GetFinalISize() { + return mFinalISize; + } + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsSplittableFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart)); + } + + virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + +protected: + + explicit nsTableColFrame(nsStyleContext* aContext); + ~nsTableColFrame(); + + nscoord mMinCoord; + nscoord mPrefCoord; + nscoord mSpanMinCoord; // XXX... + nscoord mSpanPrefCoord; // XXX... + float mPrefPercent; + float mSpanPrefPercent; // XXX... + // ...XXX the four members marked above could be allocated as part of + // a separate array allocated only during + // BasicTableLayoutStrategy::ComputeColumnIntrinsicISizes (and only + // when colspans were present). + nscoord mFinalISize; + + // the index of the column with respect to the whole table (starting at 0) + // it should never be smaller then the start column index of the parent + // colgroup + uint32_t mColIndex; + + // border width in pixels of the inner half of the border only + BCPixelSize mIStartBorderWidth; + BCPixelSize mIEndBorderWidth; + BCPixelSize mBStartContBorderWidth; + BCPixelSize mIEndContBorderWidth; + BCPixelSize mBEndContBorderWidth; + + bool mHasSpecifiedCoord; +}; + +inline int32_t nsTableColFrame::GetColIndex() const +{ + return mColIndex; +} + +inline void nsTableColFrame::SetColIndex (int32_t aColIndex) +{ + mColIndex = aColIndex; +} + +inline nscoord +nsTableColFrame::GetContinuousBCBorderWidth(mozilla::WritingMode aWM, + mozilla::LogicalMargin& aBorder) +{ + int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel(); + aBorder.BStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips, + mBStartContBorderWidth); + aBorder.IEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips, + mIEndContBorderWidth); + aBorder.BEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips, + mBEndContBorderWidth); + return BC_BORDER_END_HALF_COORD(aPixelsToTwips, mIEndContBorderWidth); +} + +#endif + diff --git a/layout/tables/nsTableColGroupFrame.cpp b/layout/tables/nsTableColGroupFrame.cpp new file mode 100644 index 000000000..ff8879a0b --- /dev/null +++ b/layout/tables/nsTableColGroupFrame.cpp @@ -0,0 +1,524 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsTableColGroupFrame.h" +#include "nsTableColFrame.h" +#include "nsTableFrame.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsHTMLParts.h" +#include "nsGkAtoms.h" +#include "nsCOMPtr.h" +#include "nsCSSRendering.h" +#include "nsIPresShell.h" + +using namespace mozilla; + +#define COL_GROUP_TYPE_BITS (NS_FRAME_STATE_BIT(30) | \ + NS_FRAME_STATE_BIT(31)) +#define COL_GROUP_TYPE_OFFSET 30 + +nsTableColGroupType +nsTableColGroupFrame::GetColType() const +{ + return (nsTableColGroupType)((mState & COL_GROUP_TYPE_BITS) >> COL_GROUP_TYPE_OFFSET); +} + +void nsTableColGroupFrame::SetColType(nsTableColGroupType aType) +{ + NS_ASSERTION(GetColType() == eColGroupContent, + "should only call nsTableColGroupFrame::SetColType with aType " + "!= eColGroupContent once"); + uint32_t type = aType - eColGroupContent; + RemoveStateBits(COL_GROUP_TYPE_BITS); + AddStateBits(nsFrameState(type << COL_GROUP_TYPE_OFFSET)); +} + +void nsTableColGroupFrame::ResetColIndices(nsIFrame* aFirstColGroup, + int32_t aFirstColIndex, + nsIFrame* aStartColFrame) +{ + nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)aFirstColGroup; + int32_t colIndex = aFirstColIndex; + while (colGroupFrame) { + if (nsGkAtoms::tableColGroupFrame == colGroupFrame->GetType()) { + // reset the starting col index for the first cg only if we should reset + // the whole colgroup (aStartColFrame defaults to nullptr) or if + // aFirstColIndex is smaller than the existing starting col index + if ((colIndex != aFirstColIndex) || + (colIndex < colGroupFrame->GetStartColumnIndex()) || + !aStartColFrame) { + colGroupFrame->SetStartColumnIndex(colIndex); + } + nsIFrame* colFrame = aStartColFrame; + if (!colFrame || (colIndex != aFirstColIndex)) { + colFrame = colGroupFrame->PrincipalChildList().FirstChild(); + } + while (colFrame) { + if (nsGkAtoms::tableColFrame == colFrame->GetType()) { + ((nsTableColFrame*)colFrame)->SetColIndex(colIndex); + colIndex++; + } + colFrame = colFrame->GetNextSibling(); + } + } + colGroupFrame = static_cast + (colGroupFrame->GetNextSibling()); + } +} + + +nsresult +nsTableColGroupFrame::AddColsToTable(int32_t aFirstColIndex, + bool aResetSubsequentColIndices, + const nsFrameList::Slice& aCols) +{ + nsTableFrame* tableFrame = GetTableFrame(); + + tableFrame->InvalidateFrameSubtree(); + + // set the col indices of the col frames and and add col info to the table + int32_t colIndex = aFirstColIndex; + nsFrameList::Enumerator e(aCols); + for (; !e.AtEnd(); e.Next()) { + ((nsTableColFrame*)e.get())->SetColIndex(colIndex); + mColCount++; + tableFrame->InsertCol((nsTableColFrame &)*e.get(), colIndex); + colIndex++; + } + + for (nsFrameList::Enumerator eTail = e.GetUnlimitedEnumerator(); + !eTail.AtEnd(); + eTail.Next()) { + ((nsTableColFrame*)eTail.get())->SetColIndex(colIndex); + colIndex++; + } + + // We have already set the colindex for all the colframes in this + // colgroup that come after the first inserted colframe, but there could + // be other colgroups following this one and their colframes need + // correct colindices too. + if (aResetSubsequentColIndices && GetNextSibling()) { + ResetColIndices(GetNextSibling(), colIndex); + } + + return NS_OK; +} + + +nsTableColGroupFrame* +nsTableColGroupFrame::GetLastRealColGroup(nsTableFrame* aTableFrame) +{ + nsFrameList colGroups = aTableFrame->GetColGroups(); + + nsIFrame* nextToLastColGroup = nullptr; + nsFrameList::FrameLinkEnumerator link(colGroups); + for ( ; !link.AtEnd(); link.Next()) { + nextToLastColGroup = link.PrevFrame(); + } + + if (!link.PrevFrame()) { + return nullptr; // there are no col group frames + } + + nsTableColGroupType lastColGroupType = + static_cast(link.PrevFrame())->GetColType(); + if (eColGroupAnonymousCell == lastColGroupType) { + return static_cast(nextToLastColGroup); + } + + return static_cast(link.PrevFrame()); +} + +// don't set mColCount here, it is done in AddColsToTable +void +nsTableColGroupFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + MOZ_ASSERT(mFrames.IsEmpty(), + "unexpected second call to SetInitialChildList"); + MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list"); + if (aChildList.IsEmpty()) { + GetTableFrame()->AppendAnonymousColFrames(this, GetSpan(), + eColAnonymousColGroup, false); + return; + } + + mFrames.AppendFrames(this, aChildList); +} + +/* virtual */ void +nsTableColGroupFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsContainerFrame::DidSetStyleContext(aOldStyleContext); + + if (!aOldStyleContext) //avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) { + int32_t colCount = GetColCount(); + if (!colCount) + return; // this is a degenerated colgroup + TableArea damageArea(GetFirstColumn()->GetColIndex(), 0, colCount, + tableFrame->GetRowCount()); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void +nsTableColGroupFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + + nsTableColFrame* col = GetFirstColumn(); + nsTableColFrame* nextCol; + while (col && col->GetColType() == eColAnonymousColGroup) { + // this colgroup spans one or more columns but now that there is a + // real column below, spanned anonymous columns should be removed, + // since the HTML spec says to ignore the span of a colgroup if it + // has content columns in it. + nextCol = col->GetNextCol(); + RemoveFrame(kPrincipalList, col); + col = nextCol; + } + + const nsFrameList::Slice& newFrames = + mFrames.AppendFrames(this, aFrameList); + InsertColsReflow(GetStartColumnIndex() + mColCount, newFrames); +} + +void +nsTableColGroupFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + + nsTableColFrame* col = GetFirstColumn(); + nsTableColFrame* nextCol; + while (col && col->GetColType() == eColAnonymousColGroup) { + // this colgroup spans one or more columns but now that there is a + // real column below, spanned anonymous columns should be removed, + // since the HTML spec says to ignore the span of a colgroup if it + // has content columns in it. + nextCol = col->GetNextCol(); + if (col == aPrevFrame) { + // This can happen when we're being appended to + NS_ASSERTION(!nextCol || nextCol->GetColType() != eColAnonymousColGroup, + "Inserting in the middle of our anonymous cols?"); + // We'll want to insert at the beginning + aPrevFrame = nullptr; + } + RemoveFrame(kPrincipalList, col); + col = nextCol; + } + + NS_ASSERTION(!aPrevFrame || aPrevFrame == aPrevFrame->LastContinuation(), + "Prev frame should be last in continuation chain"); + NS_ASSERTION(!aPrevFrame || !GetNextColumn(aPrevFrame) || + GetNextColumn(aPrevFrame)->GetColType() != eColAnonymousCol, + "Shouldn't be inserting before a spanned colframe"); + + const nsFrameList::Slice& newFrames = + mFrames.InsertFrames(this, aPrevFrame, aFrameList); + nsIFrame* prevFrame = nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, + nsGkAtoms::tableColFrame); + + int32_t colIndex = (prevFrame) ? ((nsTableColFrame*)prevFrame)->GetColIndex() + 1 : GetStartColumnIndex(); + InsertColsReflow(colIndex, newFrames); +} + +void +nsTableColGroupFrame::InsertColsReflow(int32_t aColIndex, + const nsFrameList::Slice& aCols) +{ + AddColsToTable(aColIndex, true, aCols); + + PresContext()->PresShell()->FrameNeedsReflow(this, + nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsTableColGroupFrame::RemoveChild(nsTableColFrame& aChild, + bool aResetSubsequentColIndices) +{ + int32_t colIndex = 0; + nsIFrame* nextChild = nullptr; + if (aResetSubsequentColIndices) { + colIndex = aChild.GetColIndex(); + nextChild = aChild.GetNextSibling(); + } + mFrames.DestroyFrame(&aChild); + mColCount--; + if (aResetSubsequentColIndices) { + if (nextChild) { // reset inside this and all following colgroups + ResetColIndices(this, colIndex, nextChild); + } + else { + nsIFrame* nextGroup = GetNextSibling(); + if (nextGroup) // reset next and all following colgroups + ResetColIndices(nextGroup, colIndex); + } + } + + PresContext()->PresShell()->FrameNeedsReflow(this, + nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsTableColGroupFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + + if (!aOldFrame) { + return; + } + bool contentRemoval = false; + + if (nsGkAtoms::tableColFrame == aOldFrame->GetType()) { + nsTableColFrame* colFrame = (nsTableColFrame*)aOldFrame; + if (colFrame->GetColType() == eColContent) { + contentRemoval = true; + // Remove any anonymous column frames this produced via a colspan + nsTableColFrame* col = colFrame->GetNextCol(); + nsTableColFrame* nextCol; + while (col && col->GetColType() == eColAnonymousCol) { +#ifdef DEBUG + nsIFrame* providerFrame; + nsStyleContext* psc = colFrame->GetParentStyleContext(&providerFrame); + if (colFrame->StyleContext()->GetParent() == psc) { + NS_ASSERTION(col->StyleContext() == colFrame->StyleContext() && + col->GetContent() == colFrame->GetContent(), + "How did that happen??"); + } + // else colFrame is being removed because of a frame + // reconstruct on it, and its style context is still the old + // one, so we can't assert anything about how it compares to + // col's style context. +#endif + nextCol = col->GetNextCol(); + RemoveFrame(kPrincipalList, col); + col = nextCol; + } + } + + int32_t colIndex = colFrame->GetColIndex(); + // The RemoveChild call handles calling FrameNeedsReflow on us. + RemoveChild(*colFrame, true); + + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->RemoveCol(this, colIndex, true, true); + if (mFrames.IsEmpty() && contentRemoval && + GetColType() == eColGroupContent) { + tableFrame->AppendAnonymousColFrames(this, GetSpan(), + eColAnonymousColGroup, true); + } + } + else { + mFrames.DestroyFrame(aOldFrame); + } +} + +nsIFrame::LogicalSides +nsTableColGroupFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const +{ + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return LogicalSides(); + } + + LogicalSides skip; + if (nullptr != GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (nullptr != GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +void +nsTableColGroupFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableColGroupFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + NS_ASSERTION(nullptr!=mContent, "bad state -- null content for frame"); + + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); + if (collapseGroup) { + GetTableFrame()->SetNeedToCollapse(true); + } + // for every content child that (is a column thingy and does not already have a frame) + // create a frame and adjust it's style + + for (nsIFrame *kidFrame = mFrames.FirstChild(); kidFrame; + kidFrame = kidFrame->GetNextSibling()) { + // Give the child frame a chance to reflow, even though we know it'll have 0 size + ReflowOutput kidSize(aReflowInput); + ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame, + LogicalSize(kidFrame->GetWritingMode())); + + nsReflowStatus status; + ReflowChild(kidFrame, aPresContext, kidSize, kidReflowInput, 0, 0, 0, status); + FinishReflowChild(kidFrame, aPresContext, kidSize, nullptr, 0, 0, 0); + } + + aDesiredSize.ClearSize(); + aStatus = NS_FRAME_COMPLETE; + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +nsTableColFrame * nsTableColGroupFrame::GetFirstColumn() +{ + return GetNextColumn(nullptr); +} + +nsTableColFrame * nsTableColGroupFrame::GetNextColumn(nsIFrame *aChildFrame) +{ + nsTableColFrame *result = nullptr; + nsIFrame *childFrame = aChildFrame; + if (!childFrame) { + childFrame = mFrames.FirstChild(); + } + else { + childFrame = childFrame->GetNextSibling(); + } + while (childFrame) + { + if (mozilla::StyleDisplay::TableColumn == + childFrame->StyleDisplay()->mDisplay) + { + result = (nsTableColFrame *)childFrame; + break; + } + childFrame = childFrame->GetNextSibling(); + } + return result; +} + +int32_t nsTableColGroupFrame::GetSpan() +{ + return StyleTable()->mSpan; +} + +void nsTableColGroupFrame::SetContinuousBCBorderWidth(LogicalSide aForSide, + BCPixelSize aPixelValue) +{ + switch (aForSide) { + case eLogicalSideBStart: + mBStartContBorderWidth = aPixelValue; + return; + case eLogicalSideBEnd: + mBEndContBorderWidth = aPixelValue; + return; + default: + NS_ERROR("invalid side arg"); + } +} + +void nsTableColGroupFrame::GetContinuousBCBorderWidth(WritingMode aWM, + LogicalMargin& aBorder) +{ + int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel(); + nsTableColFrame* col = GetTableFrame()-> + GetColFrame(mStartColIndex + mColCount - 1); + col->GetContinuousBCBorderWidth(aWM, aBorder); + aBorder.BStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips, + mBStartContBorderWidth); + aBorder.BEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips, + mBEndContBorderWidth); +} + +/* ----- global methods ----- */ + +nsTableColGroupFrame* +NS_NewTableColGroupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableColGroupFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableColGroupFrame) + +nsIAtom* +nsTableColGroupFrame::GetType() const +{ + return nsGkAtoms::tableColGroupFrame; +} + +void +nsTableColGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrame(aDisplayItemKey); + GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey); +} + +void +nsTableColGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey); + // If we have filters applied that would affects our bounds, then + // we get an inactive layer created and this is computed + // within FrameLayerBuilder + GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableColGroupFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("TableColGroup"), aResult); +} + +void nsTableColGroupFrame::Dump(int32_t aIndent) +{ + char* indent = new char[aIndent + 1]; + if (!indent) return; + for (int32_t i = 0; i < aIndent + 1; i++) { + indent[i] = ' '; + } + indent[aIndent] = 0; + + printf("%s**START COLGROUP DUMP**\n%s startcolIndex=%d colcount=%d span=%d coltype=", + indent, indent, GetStartColumnIndex(), GetColCount(), GetSpan()); + nsTableColGroupType colType = GetColType(); + switch (colType) { + case eColGroupContent: + printf(" content "); + break; + case eColGroupAnonymousCol: + printf(" anonymous-column "); + break; + case eColGroupAnonymousCell: + printf(" anonymous-cell "); + break; + } + // verify the colindices + int32_t j = GetStartColumnIndex(); + nsTableColFrame* col = GetFirstColumn(); + while (col) { + NS_ASSERTION(j == col->GetColIndex(), "wrong colindex on col frame"); + col = col->GetNextCol(); + j++; + } + NS_ASSERTION((j - GetStartColumnIndex()) == GetColCount(), + "number of cols out of sync"); + printf("\n%s**END COLGROUP DUMP** ", indent); + delete [] indent; +} +#endif + diff --git a/layout/tables/nsTableColGroupFrame.h b/layout/tables/nsTableColGroupFrame.h new file mode 100644 index 000000000..2a25fdc44 --- /dev/null +++ b/layout/tables/nsTableColGroupFrame.h @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTableColGroupFrame_h__ +#define nsTableColGroupFrame_h__ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsTableFrame.h" +#include "mozilla/WritingModes.h" + +class nsTableColFrame; + +/** + * nsTableColGroupFrame + * data structure to maintain information about a single table cell's frame + * + * @author sclark + */ +class nsTableColGroupFrame final : public nsContainerFrame +{ +public: + NS_DECL_FRAMEARENA_HELPERS + + // default constructor supplied by the compiler + + /** instantiate a new instance of nsTableRowFrame. + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableColGroupFrame* NS_NewTableColGroupFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + nsTableFrame* GetTableFrame() const + { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableFrame); + MOZ_ASSERT(!parent->GetPrevInFlow(), + "Col group should always be in a first-in-flow table frame"); + return static_cast(parent); + } + + /** + * ColGroups never paint anything, nor receive events. + */ + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override {} + + /** A colgroup can be caused by three things: + * 1) An element with table-column-group display + * 2) An element with a table-column display without a + * table-column-group parent + * 3) Cells that are not in a column (and hence get an anonymous + * column and colgroup). + * @return colgroup type + */ + nsTableColGroupType GetColType() const; + + /** Set the colgroup type based on the creation cause + * @param aType - the reason why this colgroup is needed + */ + void SetColType(nsTableColGroupType aType); + + /** Real in this context are colgroups that come from an element + * with table-column-group display or wrap around columns that + * come from an element with table-column display. Colgroups + * that are the result of wrapping cells in an anonymous + * column and colgroup are not considered real here. + * @param aTableFrame - the table parent of the colgroups + * @return the last real colgroup + */ + static nsTableColGroupFrame* GetLastRealColGroup(nsTableFrame* aTableFrame); + + /** @see nsIFrame::DidSetStyleContext */ + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + /** remove the column aChild from the column group, if requested renumber + * the subsequent columns in this column group and all following column + * groups. see also ResetColIndices for this + * @param aChild - the column frame that needs to be removed + * @param aResetSubsequentColIndices - if true the columns that follow + * after aChild will be reenumerated + */ + void RemoveChild(nsTableColFrame& aChild, + bool aResetSubsequentColIndices); + + /** reflow of a column group is a trivial matter of reflowing + * the col group's children (columns), and setting this frame + * to 0-size. Since tables are row-centric, column group frames + * don't play directly in the rendering game. They do however + * maintain important state that effects table and cell layout. + */ + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::tableColGroupFrame + */ + virtual nsIAtom* GetType() const override; + + virtual mozilla::WritingMode GetWritingMode() const override + { return GetTableFrame()->GetWritingMode(); } + + /** Add column frames to the table storages: colframe cache and cellmap + * this doesn't change the mFrames of the colgroup frame. + * @param aFirstColIndex - the index at which aFirstFrame should be inserted + * into the colframe cache. + * @param aResetSubsequentColIndices - the indices of the col frames + * after the insertion might need + * an update + * @param aCols - an iterator that can be used to iterate over the col + * frames to be added. Once this is done, the frames on the + * sbling chain of its .get() at that point will still need + * their col indices updated. + * @result - if there is no table frame or the table frame is not + * the first in flow it will return an error + */ + nsresult AddColsToTable(int32_t aFirstColIndex, + bool aResetSubsequentColIndices, + const nsFrameList::Slice& aCols); + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; + void Dump(int32_t aIndent); +#endif + + /** returns the number of columns represented by this group. + * if there are col children, count them (taking into account the span of each) + * else, check my own span attribute. + */ + virtual int32_t GetColCount() const; + + /** first column on the child list */ + nsTableColFrame * GetFirstColumn(); + /** next sibling to aChildFrame that is a column frame, first column frame + * in the column group if aChildFrame is null + */ + nsTableColFrame * GetNextColumn(nsIFrame *aChildFrame); + + /** @return - the position of the first column in this colgroup in the table + * colframe cache. + */ + int32_t GetStartColumnIndex(); + + /** set the position of the first column in this colgroup in the table + * colframe cache. + */ + void SetStartColumnIndex(int32_t aIndex); + + /** helper method to get the span attribute for this colgroup */ + int32_t GetSpan(); + + /** provide access to the mFrames list + */ + nsFrameList& GetWritableChildList(); + + /** set the column index for all frames starting at aStartColFrame, it + * will also reset the column indices in all subsequent colgroups + * @param aFirstColGroup - start the reset operation inside this colgroup + * @param aFirstColIndex - first column that is reset should get this index + * @param aStartColFrame - if specified the reset starts with this column + * inside the colgroup; if not specified, the reset + * starts with the first column + */ + static void ResetColIndices(nsIFrame* aFirstColGroup, + int32_t aFirstColIndex, + nsIFrame* aStartColFrame = nullptr); + + /** + * Gets inner border widths before collapsing with cell borders + * Caller must get istart border from previous column + * GetContinuousBCBorderWidth will not overwrite aBorder.IStart + * see nsTablePainter about continuous borders + */ + void GetContinuousBCBorderWidth(mozilla::WritingMode aWM, + mozilla::LogicalMargin& aBorder); + /** + * Set full border widths before collapsing with cell borders + * @param aForSide - side to set; only accepts bstart and bend + */ + void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide, + BCPixelSize aPixelValue); + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart)); + } + + virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + +protected: + explicit nsTableColGroupFrame(nsStyleContext* aContext); + + void InsertColsReflow(int32_t aColIndex, + const nsFrameList::Slice& aCols); + + virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override; + + // data members + int32_t mColCount; + // the starting column index this col group represents. Must be >= 0. + int32_t mStartColIndex; + + // border width in pixels + BCPixelSize mBStartContBorderWidth; + BCPixelSize mBEndContBorderWidth; +}; + +inline nsTableColGroupFrame::nsTableColGroupFrame(nsStyleContext *aContext) +: nsContainerFrame(aContext), mColCount(0), mStartColIndex(0) +{ + SetColType(eColGroupContent); +} + +inline int32_t nsTableColGroupFrame::GetStartColumnIndex() +{ + return mStartColIndex; +} + +inline void nsTableColGroupFrame::SetStartColumnIndex (int32_t aIndex) +{ + mStartColIndex = aIndex; +} + +inline int32_t nsTableColGroupFrame::GetColCount() const +{ + return mColCount; +} + +inline nsFrameList& nsTableColGroupFrame::GetWritableChildList() +{ + return mFrames; +} + +#endif + diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp new file mode 100644 index 000000000..5030804ed --- /dev/null +++ b/layout/tables/nsTableFrame.cpp @@ -0,0 +1,7536 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/WritingModes.h" + +#include "nsCOMPtr.h" +#include "nsTableFrame.h" +#include "nsRenderingContext.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsIContent.h" +#include "nsCellMap.h" +#include "nsTableCellFrame.h" +#include "nsHTMLParts.h" +#include "nsTableColFrame.h" +#include "nsTableColGroupFrame.h" +#include "nsTableRowFrame.h" +#include "nsTableRowGroupFrame.h" +#include "nsTableWrapperFrame.h" +#include "nsTablePainter.h" + +#include "BasicTableLayoutStrategy.h" +#include "FixedTableLayoutStrategy.h" + +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsCSSRendering.h" +#include "nsGkAtoms.h" +#include "nsCSSAnonBoxes.h" +#include "nsIPresShell.h" +#include "nsIDOMElement.h" +#include "nsIDOMHTMLElement.h" +#include "nsIScriptError.h" +#include "nsFrameManager.h" +#include "nsError.h" +#include "nsCSSFrameConstructor.h" +#include "mozilla/StyleSetHandle.h" +#include "mozilla/StyleSetHandleInlines.h" +#include "nsDisplayList.h" +#include "nsIScrollableFrame.h" +#include "nsCSSProps.h" +#include "RestyleTracker.h" +#include + +using namespace mozilla; +using namespace mozilla::image; +using namespace mozilla::layout; + +/******************************************************************************** + ** TableReflowInput ** + ********************************************************************************/ + +namespace mozilla { + +struct TableReflowInput { + + // the real reflow state + const ReflowInput& reflowInput; + + // The table's available size (in reflowInput's writing mode) + LogicalSize availSize; + + // Stationary inline-offset + nscoord iCoord; + + // Running block-offset + nscoord bCoord; + + TableReflowInput(const ReflowInput& aReflowInput, + const LogicalSize& aAvailSize) + : reflowInput(aReflowInput) + , availSize(aAvailSize) + { + MOZ_ASSERT(reflowInput.mFrame->GetType() == nsGkAtoms::tableFrame, + "TableReflowInput should only be created for nsTableFrame"); + nsTableFrame* table = + static_cast(reflowInput.mFrame->FirstInFlow()); + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalMargin borderPadding = table->GetChildAreaOffset(wm, &reflowInput); + + iCoord = borderPadding.IStart(wm) + table->GetColSpacing(-1); + bCoord = borderPadding.BStart(wm); //cellspacing added during reflow + + // XXX do we actually need to check for unconstrained inline-size here? + if (NS_UNCONSTRAINEDSIZE != availSize.ISize(wm)) { + int32_t colCount = table->GetColCount(); + availSize.ISize(wm) -= borderPadding.IStartEnd(wm) + + table->GetColSpacing(-1) + + table->GetColSpacing(colCount); + availSize.ISize(wm) = std::max(0, availSize.ISize(wm)); + } + + if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) { + availSize.BSize(wm) -= borderPadding.BStartEnd(wm) + + table->GetRowSpacing(-1) + + table->GetRowSpacing(table->GetRowCount()); + availSize.BSize(wm) = std::max(0, availSize.BSize(wm)); + } + } +}; + +} // namespace mozilla + +/******************************************************************************** + ** nsTableFrame ** + ********************************************************************************/ + +struct BCPropertyData +{ + BCPropertyData() : mBStartBorderWidth(0), mIEndBorderWidth(0), + mBEndBorderWidth(0), mIStartBorderWidth(0), + mIStartCellBorderWidth(0), mIEndCellBorderWidth(0) {} + TableArea mDamageArea; + BCPixelSize mBStartBorderWidth; + BCPixelSize mIEndBorderWidth; + BCPixelSize mBEndBorderWidth; + BCPixelSize mIStartBorderWidth; + BCPixelSize mIStartCellBorderWidth; + BCPixelSize mIEndCellBorderWidth; +}; + +nsStyleContext* +nsTableFrame::GetParentStyleContext(nsIFrame** aProviderFrame) const +{ + // Since our parent, the table wrapper frame, returned this frame, we + // must return whatever our parent would normally have returned. + + NS_PRECONDITION(GetParent(), "table constructed without table wrapper"); + if (!mContent->GetParent() && !StyleContext()->GetPseudo()) { + // We're the root. We have no style context parent. + *aProviderFrame = nullptr; + return nullptr; + } + + return GetParent()->DoGetParentStyleContext(aProviderFrame); +} + + +nsIAtom* +nsTableFrame::GetType() const +{ + return nsGkAtoms::tableFrame; +} + + +nsTableFrame::nsTableFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext), + mCellMap(nullptr), + mTableLayoutStrategy(nullptr) +{ + memset(&mBits, 0, sizeof(mBits)); +} + +void +nsTableFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_PRECONDITION(!mCellMap, "Init called twice"); + NS_PRECONDITION(!mTableLayoutStrategy, "Init called twice"); + NS_PRECONDITION(!aPrevInFlow || + aPrevInFlow->GetType() == nsGkAtoms::tableFrame, + "prev-in-flow must be of same type"); + + // Let the base class do its processing + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + + // see if border collapse is on, if so set it + const nsStyleTableBorder* tableStyle = StyleTableBorder(); + bool borderCollapse = (NS_STYLE_BORDER_COLLAPSE == tableStyle->mBorderCollapse); + SetBorderCollapse(borderCollapse); + + if (!aPrevInFlow) { + // If we're the first-in-flow, we manage the cell map & layout strategy that + // get used by our continuation chain: + mCellMap = new nsTableCellMap(*this, borderCollapse); + if (IsAutoLayout()) { + mTableLayoutStrategy = new BasicTableLayoutStrategy(this); + } else { + mTableLayoutStrategy = new FixedTableLayoutStrategy(this); + } + } else { + // Set my isize, because all frames in a table flow are the same isize and + // code in nsTableWrapperFrame depends on this being set. + WritingMode wm = GetWritingMode(); + SetSize(LogicalSize(wm, aPrevInFlow->ISize(wm), BSize(wm))); + } +} + +nsTableFrame::~nsTableFrame() +{ + delete mCellMap; + delete mTableLayoutStrategy; +} + +void +nsTableFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + mColGroups.DestroyFramesFrom(aDestructRoot); + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +// Make sure any views are positioned properly +void +nsTableFrame::RePositionViews(nsIFrame* aFrame) +{ + nsContainerFrame::PositionFrameView(aFrame); + nsContainerFrame::PositionChildViews(aFrame); +} + +static bool +IsRepeatedFrame(nsIFrame* kidFrame) +{ + return (kidFrame->GetType() == nsGkAtoms::tableRowFrame || + kidFrame->GetType() == nsGkAtoms::tableRowGroupFrame) && + kidFrame->HasAnyStateBits(NS_REPEATED_ROW_OR_ROWGROUP); +} + +bool +nsTableFrame::PageBreakAfter(nsIFrame* aSourceFrame, + nsIFrame* aNextFrame) +{ + const nsStyleDisplay* display = aSourceFrame->StyleDisplay(); + nsTableRowGroupFrame* prevRg = do_QueryFrame(aSourceFrame); + // don't allow a page break after a repeated element ... + if ((display->mBreakAfter || (prevRg && prevRg->HasInternalBreakAfter())) && + !IsRepeatedFrame(aSourceFrame)) { + return !(aNextFrame && IsRepeatedFrame(aNextFrame)); // or before + } + + if (aNextFrame) { + display = aNextFrame->StyleDisplay(); + // don't allow a page break before a repeated element ... + nsTableRowGroupFrame* nextRg = do_QueryFrame(aNextFrame); + if ((display->mBreakBefore || + (nextRg && nextRg->HasInternalBreakBefore())) && + !IsRepeatedFrame(aNextFrame)) { + return !IsRepeatedFrame(aSourceFrame); // or after + } + } + return false; +} + +/* static */ void +nsTableFrame::RegisterPositionedTablePart(nsIFrame* aFrame) +{ + // Supporting relative positioning for table parts other than table cells has + // the potential to break sites that apply 'position: relative' to those + // parts, expecting nothing to happen. We warn at the console to make tracking + // down the issue easy. + if (!IS_TABLE_CELL(aFrame->GetType())) { + nsIContent* content = aFrame->GetContent(); + nsPresContext* presContext = aFrame->PresContext(); + if (content && !presContext->HasWarnedAboutPositionedTableParts()) { + presContext->SetHasWarnedAboutPositionedTableParts(); + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Layout: Tables"), + content->OwnerDoc(), + nsContentUtils::eLAYOUT_PROPERTIES, + "TablePartRelPosWarning"); + } + } + + nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(aFrame); + MOZ_ASSERT(tableFrame, "Should have a table frame here"); + tableFrame = static_cast(tableFrame->FirstContinuation()); + + // Retrieve the positioned parts array for this table. + FrameProperties props = tableFrame->Properties(); + FrameTArray* positionedParts = props.Get(PositionedTablePartArray()); + + // Lazily create the array if it doesn't exist yet. + if (!positionedParts) { + positionedParts = new FrameTArray; + props.Set(PositionedTablePartArray(), positionedParts); + } + + // Add this frame to the list. + positionedParts->AppendElement(aFrame); +} + +/* static */ void +nsTableFrame::UnregisterPositionedTablePart(nsIFrame* aFrame, + nsIFrame* aDestructRoot) +{ + // Retrieve the table frame, and check if we hit aDestructRoot on the way. + bool didPassThrough; + nsTableFrame* tableFrame = GetTableFramePassingThrough(aDestructRoot, aFrame, + &didPassThrough); + if (!didPassThrough && !tableFrame->GetPrevContinuation()) { + // The table frame will be destroyed, and it's the first im flow (and thus + // owning the PositionedTablePartArray), so we don't need to do + // anything. + return; + } + tableFrame = static_cast(tableFrame->FirstContinuation()); + + // Retrieve the positioned parts array for this table. + FrameProperties props = tableFrame->Properties(); + FrameTArray* positionedParts = props.Get(PositionedTablePartArray()); + + // Remove the frame. + MOZ_ASSERT(positionedParts && positionedParts->Contains(aFrame), + "Asked to unregister a positioned table part that wasn't registered"); + if (positionedParts) { + positionedParts->RemoveElement(aFrame); + } +} + +// XXX this needs to be cleaned up so that the frame constructor breaks out col group +// frames into a separate child list, bug 343048. +void +nsTableFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + if (aListID != kPrincipalList) { + nsContainerFrame::SetInitialChildList(aListID, aChildList); + return; + } + + MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(), + "unexpected second call to SetInitialChildList"); + + // XXXbz the below code is an icky cesspit that's only needed in its current + // form for two reasons: + // 1) Both rowgroups and column groups come in on the principal child list. + while (aChildList.NotEmpty()) { + nsIFrame* childFrame = aChildList.FirstChild(); + aChildList.RemoveFirstChild(); + const nsStyleDisplay* childDisplay = childFrame->StyleDisplay(); + + if (mozilla::StyleDisplay::TableColumnGroup == childDisplay->mDisplay) { + NS_ASSERTION(nsGkAtoms::tableColGroupFrame == childFrame->GetType(), + "This is not a colgroup"); + mColGroups.AppendFrame(nullptr, childFrame); + } + else { // row groups and unknown frames go on the main list for now + mFrames.AppendFrame(nullptr, childFrame); + } + } + + // If we have a prev-in-flow, then we're a table that has been split and + // so don't treat this like an append + if (!GetPrevInFlow()) { + // process col groups first so that real cols get constructed before + // anonymous ones due to cells in rows. + InsertColGroups(0, mColGroups); + InsertRowGroups(mFrames); + // calc collapsing borders + if (IsBorderCollapse()) { + SetFullBCDamageArea(); + } + } +} + +void +nsTableFrame::AttributeChangedFor(nsIFrame* aFrame, + nsIContent* aContent, + nsIAtom* aAttribute) +{ + nsTableCellFrame *cellFrame = do_QueryFrame(aFrame); + if (cellFrame) { + if ((nsGkAtoms::rowspan == aAttribute) || + (nsGkAtoms::colspan == aAttribute)) { + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + // for now just remove the cell from the map and reinsert it + int32_t rowIndex, colIndex; + cellFrame->GetRowIndex(rowIndex); + cellFrame->GetColIndex(colIndex); + RemoveCell(cellFrame, rowIndex); + AutoTArray cells; + cells.AppendElement(cellFrame); + InsertCells(cells, rowIndex, colIndex - 1); + + // XXX Should this use eStyleChange? It currently doesn't need + // to, but it might given more optimization. + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); + } + } + } +} + + +/* ****** CellMap methods ******* */ + +/* return the effective col count */ +int32_t +nsTableFrame::GetEffectiveColCount() const +{ + int32_t colCount = GetColCount(); + if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto) { + nsTableCellMap* cellMap = GetCellMap(); + if (!cellMap) { + return 0; + } + // don't count cols at the end that don't have originating cells + for (int32_t colIdx = colCount - 1; colIdx >= 0; colIdx--) { + if (cellMap->GetNumCellsOriginatingInCol(colIdx) > 0) { + break; + } + colCount--; + } + } + return colCount; +} + +int32_t +nsTableFrame::GetIndexOfLastRealCol() +{ + int32_t numCols = mColFrames.Length(); + if (numCols > 0) { + for (int32_t colIdx = numCols - 1; colIdx >= 0; colIdx--) { + nsTableColFrame* colFrame = GetColFrame(colIdx); + if (colFrame) { + if (eColAnonymousCell != colFrame->GetColType()) { + return colIdx; + } + } + } + } + return -1; +} + +nsTableColFrame* +nsTableFrame::GetColFrame(int32_t aColIndex) const +{ + NS_ASSERTION(!GetPrevInFlow(), "GetColFrame called on next in flow"); + int32_t numCols = mColFrames.Length(); + if ((aColIndex >= 0) && (aColIndex < numCols)) { + return mColFrames.ElementAt(aColIndex); + } + else { + NS_ERROR("invalid col index"); + return nullptr; + } +} + +int32_t +nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex, + const nsTableCellFrame& aCell) const +{ + nsTableCellMap* cellMap = GetCellMap(); + NS_PRECONDITION (nullptr != cellMap, "bad call, cellMap not yet allocated."); + + int32_t colIndex; + aCell.GetColIndex(colIndex); + return cellMap->GetEffectiveRowSpan(aRowIndex, colIndex); +} + +int32_t +nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell, + nsCellMap* aCellMap) +{ + nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1); + + int32_t colIndex, rowIndex; + aCell.GetColIndex(colIndex); + aCell.GetRowIndex(rowIndex); + + if (aCellMap) + return aCellMap->GetRowSpan(rowIndex, colIndex, true); + else + return tableCellMap->GetEffectiveRowSpan(rowIndex, colIndex); +} + +int32_t +nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell, + nsCellMap* aCellMap) const +{ + nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1); + + int32_t colIndex, rowIndex; + aCell.GetColIndex(colIndex); + aCell.GetRowIndex(rowIndex); + + if (aCellMap) + return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex); + else + return tableCellMap->GetEffectiveColSpan(rowIndex, colIndex); +} + +bool +nsTableFrame::HasMoreThanOneCell(int32_t aRowIndex) const +{ + nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1); + return tableCellMap->HasMoreThanOneCell(aRowIndex); +} + +void +nsTableFrame::AdjustRowIndices(int32_t aRowIndex, + int32_t aAdjustment) +{ + // Iterate over the row groups and adjust the row indices of all rows + // whose index is >= aRowIndex. + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + rowGroups[rgIdx]->AdjustRowIndices(aRowIndex, aAdjustment); + } +} + + +void +nsTableFrame::ResetRowIndices(const nsFrameList::Slice& aRowGroupsToExclude) +{ + // Iterate over the row groups and adjust the row indices of all rows + // omit the rowgroups that will be inserted later + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + int32_t rowIndex = 0; + nsTHashtable > excludeRowGroups; + nsFrameList::Enumerator excludeRowGroupsEnumerator(aRowGroupsToExclude); + while (!excludeRowGroupsEnumerator.AtEnd()) { + excludeRowGroups.PutEntry(static_cast(excludeRowGroupsEnumerator.get())); + excludeRowGroupsEnumerator.Next(); + } + + for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + if (!excludeRowGroups.GetEntry(rgFrame)) { + const nsFrameList& rowFrames = rgFrame->PrincipalChildList(); + for (nsFrameList::Enumerator rows(rowFrames); !rows.AtEnd(); rows.Next()) { + if (mozilla::StyleDisplay::TableRow == rows.get()->StyleDisplay()->mDisplay) { + ((nsTableRowFrame *)rows.get())->SetRowIndex(rowIndex); + rowIndex++; + } + } + } + } +} +void +nsTableFrame::InsertColGroups(int32_t aStartColIndex, + const nsFrameList::Slice& aColGroups) +{ + int32_t colIndex = aStartColIndex; + nsFrameList::Enumerator colGroups(aColGroups); + for (; !colGroups.AtEnd(); colGroups.Next()) { + MOZ_ASSERT(colGroups.get()->GetType() == nsGkAtoms::tableColGroupFrame); + nsTableColGroupFrame* cgFrame = + static_cast(colGroups.get()); + cgFrame->SetStartColumnIndex(colIndex); + // XXXbz this sucks. AddColsToTable will actually remove colgroups from + // the list we're traversing! Need to fix things here. :( I guess this is + // why the old code used pointer-to-last-frame as opposed to + // pointer-to-frame-after-last.... + + // How about dealing with this by storing a const reference to the + // mNextSibling of the framelist's last frame, instead of storing a pointer + // to the first-after-next frame? Will involve making nsFrameList friend + // of nsIFrame, but it's time for that anyway. + cgFrame->AddColsToTable(colIndex, false, + colGroups.get()->PrincipalChildList()); + int32_t numCols = cgFrame->GetColCount(); + colIndex += numCols; + } + + nsFrameList::Enumerator remainingColgroups = colGroups.GetUnlimitedEnumerator(); + if (!remainingColgroups.AtEnd()) { + nsTableColGroupFrame::ResetColIndices( + static_cast(remainingColgroups.get()), colIndex); + } +} + +void +nsTableFrame::InsertCol(nsTableColFrame& aColFrame, + int32_t aColIndex) +{ + mColFrames.InsertElementAt(aColIndex, &aColFrame); + nsTableColType insertedColType = aColFrame.GetColType(); + int32_t numCacheCols = mColFrames.Length(); + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + int32_t numMapCols = cellMap->GetColCount(); + if (numCacheCols > numMapCols) { + bool removedFromCache = false; + if (eColAnonymousCell != insertedColType) { + nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1); + if (lastCol) { + nsTableColType lastColType = lastCol->GetColType(); + if (eColAnonymousCell == lastColType) { + // remove the col from the cache + mColFrames.RemoveElementAt(numCacheCols - 1); + // remove the col from the eColGroupAnonymousCell col group + nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame *)mColGroups.LastChild(); + if (lastColGroup) { + lastColGroup->RemoveChild(*lastCol, false); + + // remove the col group if it is empty + if (lastColGroup->GetColCount() <= 0) { + mColGroups.DestroyFrame((nsIFrame*)lastColGroup); + } + } + removedFromCache = true; + } + } + } + if (!removedFromCache) { + cellMap->AddColsAtEnd(1); + } + } + } + // for now, just bail and recalc all of the collapsing borders + if (IsBorderCollapse()) { + TableArea damageArea(aColIndex, 0, 1, GetRowCount()); + AddBCDamageArea(damageArea); + } +} + +void +nsTableFrame::RemoveCol(nsTableColGroupFrame* aColGroupFrame, + int32_t aColIndex, + bool aRemoveFromCache, + bool aRemoveFromCellMap) +{ + if (aRemoveFromCache) { + mColFrames.RemoveElementAt(aColIndex); + } + if (aRemoveFromCellMap) { + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + // If we have some anonymous cols at the end already, we just + // add a new anonymous col. + if (!mColFrames.IsEmpty() && + mColFrames.LastElement() && // XXXbz is this ever null? + mColFrames.LastElement()->GetColType() == eColAnonymousCell) { + AppendAnonymousColFrames(1); + } else { + // All of our colframes correspond to actual tags. It's possible + // that we still have at least as many tags as we have logical + // columns from cells, but we might have one less. Handle the latter + // case as follows: First ask the cellmap to drop its last col if it + // doesn't have any actual cells in it. Then call + // MatchCellMapToColCache to append an anonymous column if it's needed; + // this needs to be after RemoveColsAtEnd, since it will determine the + // need for a new column frame based on the width of the cell map. + cellMap->RemoveColsAtEnd(); + MatchCellMapToColCache(cellMap); + } + } + } + // for now, just bail and recalc all of the collapsing borders + if (IsBorderCollapse()) { + TableArea damageArea(0, 0, GetColCount(), GetRowCount()); + AddBCDamageArea(damageArea); + } +} + +/** Get the cell map for this table frame. It is not always mCellMap. + * Only the first-in-flow has a legit cell map. + */ +nsTableCellMap* +nsTableFrame::GetCellMap() const +{ + return static_cast(FirstInFlow())->mCellMap; +} + +// XXX this needs to be moved to nsCSSFrameConstructor +nsTableColGroupFrame* +nsTableFrame::CreateAnonymousColGroupFrame(nsTableColGroupType aColGroupType) +{ + nsIContent* colGroupContent = GetContent(); + nsPresContext* presContext = PresContext(); + nsIPresShell *shell = presContext->PresShell(); + + RefPtr colGroupStyle; + colGroupStyle = shell->StyleSet()-> + ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableColGroup, mStyleContext); + // Create a col group frame + nsIFrame* newFrame = NS_NewTableColGroupFrame(shell, colGroupStyle); + ((nsTableColGroupFrame *)newFrame)->SetColType(aColGroupType); + newFrame->Init(colGroupContent, this, nullptr); + return (nsTableColGroupFrame *)newFrame; +} + +void +nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) +{ + // get the last col group frame + nsTableColGroupFrame* colGroupFrame = + static_cast(mColGroups.LastChild()); + + if (!colGroupFrame || + (colGroupFrame->GetColType() != eColGroupAnonymousCell)) { + int32_t colIndex = (colGroupFrame) ? + colGroupFrame->GetStartColumnIndex() + + colGroupFrame->GetColCount() : 0; + colGroupFrame = CreateAnonymousColGroupFrame(eColGroupAnonymousCell); + if (!colGroupFrame) { + return; + } + // add the new frame to the child list + mColGroups.AppendFrame(this, colGroupFrame); + colGroupFrame->SetStartColumnIndex(colIndex); + } + AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell, + true); + +} + +// XXX this needs to be moved to nsCSSFrameConstructor +// Right now it only creates the col frames at the end +void +nsTableFrame::AppendAnonymousColFrames(nsTableColGroupFrame* aColGroupFrame, + int32_t aNumColsToAdd, + nsTableColType aColType, + bool aAddToTable) +{ + NS_PRECONDITION(aColGroupFrame, "null frame"); + NS_PRECONDITION(aColType != eColAnonymousCol, "Shouldn't happen"); + + nsIPresShell *shell = PresContext()->PresShell(); + + // Get the last col frame + nsFrameList newColFrames; + + int32_t startIndex = mColFrames.Length(); + int32_t lastIndex = startIndex + aNumColsToAdd - 1; + + for (int32_t childX = startIndex; childX <= lastIndex; childX++) { + nsIContent* iContent; + RefPtr styleContext; + nsStyleContext* parentStyleContext; + + // all anonymous cols that we create here use a pseudo style context of the + // col group + iContent = aColGroupFrame->GetContent(); + parentStyleContext = aColGroupFrame->StyleContext(); + styleContext = shell->StyleSet()-> + ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableCol, parentStyleContext); + // ASSERTION to check for bug 54454 sneaking back in... + NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames"); + + // create the new col frame + nsIFrame* colFrame = NS_NewTableColFrame(shell, styleContext); + ((nsTableColFrame *) colFrame)->SetColType(aColType); + colFrame->Init(iContent, aColGroupFrame, nullptr); + + newColFrames.AppendFrame(nullptr, colFrame); + } + nsFrameList& cols = aColGroupFrame->GetWritableChildList(); + nsIFrame* oldLastCol = cols.LastChild(); + const nsFrameList::Slice& newCols = + cols.InsertFrames(nullptr, oldLastCol, newColFrames); + if (aAddToTable) { + // get the starting col index in the cache + int32_t startColIndex; + if (oldLastCol) { + startColIndex = + static_cast(oldLastCol)->GetColIndex() + 1; + } else { + startColIndex = aColGroupFrame->GetStartColumnIndex(); + } + + aColGroupFrame->AddColsToTable(startColIndex, true, newCols); + } +} + +void +nsTableFrame::MatchCellMapToColCache(nsTableCellMap* aCellMap) +{ + int32_t numColsInMap = GetColCount(); + int32_t numColsInCache = mColFrames.Length(); + int32_t numColsToAdd = numColsInMap - numColsInCache; + if (numColsToAdd > 0) { + // this sets the child list, updates the col cache and cell map + AppendAnonymousColFrames(numColsToAdd); + } + if (numColsToAdd < 0) { + int32_t numColsNotRemoved = DestroyAnonymousColFrames(-numColsToAdd); + // if the cell map has fewer cols than the cache, correct it + if (numColsNotRemoved > 0) { + aCellMap->AddColsAtEnd(numColsNotRemoved); + } + } +} + +void +nsTableFrame::DidResizeColumns() +{ + NS_PRECONDITION(!GetPrevInFlow(), + "should only be called on first-in-flow"); + if (mBits.mResizedColumns) + return; // already marked + + for (nsTableFrame *f = this; f; + f = static_cast(f->GetNextInFlow())) + f->mBits.mResizedColumns = true; +} + +void +nsTableFrame::AppendCell(nsTableCellFrame& aCellFrame, + int32_t aRowIndex) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + TableArea damageArea(0, 0, 0, 0); + cellMap->AppendCell(aCellFrame, aRowIndex, true, damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } +} + +void +nsTableFrame::InsertCells(nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndexBefore) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + TableArea damageArea(0, 0, 0, 0); + cellMap->InsertCells(aCellFrames, aRowIndex, aColIndexBefore, damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } +} + +// this removes the frames from the col group and table, but not the cell map +int32_t +nsTableFrame::DestroyAnonymousColFrames(int32_t aNumFrames) +{ + // only remove cols that are of type eTypeAnonymous cell (they are at the end) + int32_t endIndex = mColFrames.Length() - 1; + int32_t startIndex = (endIndex - aNumFrames) + 1; + int32_t numColsRemoved = 0; + for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) { + nsTableColFrame* colFrame = GetColFrame(colIdx); + if (colFrame && (eColAnonymousCell == colFrame->GetColType())) { + nsTableColGroupFrame* cgFrame = + static_cast(colFrame->GetParent()); + // remove the frame from the colgroup + cgFrame->RemoveChild(*colFrame, false); + // remove the frame from the cache, but not the cell map + RemoveCol(nullptr, colIdx, true, false); + numColsRemoved++; + } + else { + break; + } + } + return (aNumFrames - numColsRemoved); +} + +void +nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame, + int32_t aRowIndex) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + TableArea damageArea(0, 0, 0, 0); + cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } +} + +int32_t +nsTableFrame::GetStartRowIndex(nsTableRowGroupFrame* aRowGroupFrame) +{ + RowGroupArray orderedRowGroups; + OrderRowGroups(orderedRowGroups); + + int32_t rowIndex = 0; + for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; + if (rgFrame == aRowGroupFrame) { + break; + } + int32_t numRows = rgFrame->GetRowCount(); + rowIndex += numRows; + } + return rowIndex; +} + +// this cannot extend beyond a single row group +void +nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame, + int32_t aRowIndex, + nsTArray& aRowFrames) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + int32_t absRowIndex = GetStartRowIndex(aRowGroupFrame) + aRowIndex; + InsertRows(aRowGroupFrame, aRowFrames, absRowIndex, true); + } +} + +// this cannot extend beyond a single row group +int32_t +nsTableFrame::InsertRows(nsTableRowGroupFrame* aRowGroupFrame, + nsTArray& aRowFrames, + int32_t aRowIndex, + bool aConsiderSpans) +{ +#ifdef DEBUG_TABLE_CELLMAP + printf("=== insertRowsBefore firstRow=%d \n", aRowIndex); + Dump(true, false, true); +#endif + + int32_t numColsToAdd = 0; + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + TableArea damageArea(0, 0, 0, 0); + int32_t origNumRows = cellMap->GetRowCount(); + int32_t numNewRows = aRowFrames.Length(); + cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans, damageArea); + MatchCellMapToColCache(cellMap); + if (aRowIndex < origNumRows) { + AdjustRowIndices(aRowIndex, numNewRows); + } + // assign the correct row indices to the new rows. If they were adjusted above + // it may not have been done correctly because each row is constructed with index 0 + for (int32_t rowB = 0; rowB < numNewRows; rowB++) { + nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowB); + rowFrame->SetRowIndex(aRowIndex + rowB); + } + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } +#ifdef DEBUG_TABLE_CELLMAP + printf("=== insertRowsAfter \n"); + Dump(true, false, true); +#endif + + return numColsToAdd; +} + +// this cannot extend beyond a single row group +void +nsTableFrame::RemoveRows(nsTableRowFrame& aFirstRowFrame, + int32_t aNumRowsToRemove, + bool aConsiderSpans) +{ +#ifdef TBD_OPTIMIZATION + // decide if we need to rebalance. we have to do this here because the row group + // cannot do it when it gets the dirty reflow corresponding to the frame being destroyed + bool stopTelling = false; + for (nsIFrame* kidFrame = aFirstFrame.FirstChild(); (kidFrame && !stopAsking); + kidFrame = kidFrame->GetNextSibling()) { + nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); + if (cellFrame) { + stopTelling = tableFrame->CellChangedWidth(*cellFrame, cellFrame->GetPass1MaxElementWidth(), + cellFrame->GetMaximumWidth(), true); + } + } + // XXX need to consider what happens if there are cells that have rowspans + // into the deleted row. Need to consider moving rows if a rebalance doesn't happen +#endif + + int32_t firstRowIndex = aFirstRowFrame.GetRowIndex(); +#ifdef DEBUG_TABLE_CELLMAP + printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex, aNumRowsToRemove); + Dump(true, false, true); +#endif + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + TableArea damageArea(0, 0, 0, 0); + cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans, damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } + AdjustRowIndices(firstRowIndex, -aNumRowsToRemove); +#ifdef DEBUG_TABLE_CELLMAP + printf("=== removeRowsAfter\n"); + Dump(true, true, true); +#endif +} + +// collect the rows ancestors of aFrame +int32_t +nsTableFrame::CollectRows(nsIFrame* aFrame, + nsTArray& aCollection) +{ + NS_PRECONDITION(aFrame, "null frame"); + int32_t numRows = 0; + for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { + aCollection.AppendElement(static_cast(childFrame)); + numRows++; + } + return numRows; +} + +void +nsTableFrame::InsertRowGroups(const nsFrameList::Slice& aRowGroups) +{ +#ifdef DEBUG_TABLE_CELLMAP + printf("=== insertRowGroupsBefore\n"); + Dump(true, false, true); +#endif + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + RowGroupArray orderedRowGroups; + OrderRowGroups(orderedRowGroups); + + AutoTArray rows; + // Loop over the rowgroups and check if some of them are new, if they are + // insert cellmaps in the order that is predefined by OrderRowGroups, + // XXXbz this code is O(N*M) where N is number of new rowgroups + // and M is number of rowgroups we have! + uint32_t rgIndex; + for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd(); + rowgroups.Next()) { + if (orderedRowGroups[rgIndex] == rowgroups.get()) { + nsTableRowGroupFrame* priorRG = + (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; + // create and add the cell map for the row group + cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG); + + break; + } + } + } + cellMap->Synchronize(this); + ResetRowIndices(aRowGroups); + + //now that the cellmaps are reordered too insert the rows + for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd(); + rowgroups.Next()) { + if (orderedRowGroups[rgIndex] == rowgroups.get()) { + nsTableRowGroupFrame* priorRG = + (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; + // collect the new row frames in an array and add them to the table + int32_t numRows = CollectRows(rowgroups.get(), rows); + if (numRows > 0) { + int32_t rowIndex = 0; + if (priorRG) { + int32_t priorNumRows = priorRG->GetRowCount(); + rowIndex = priorRG->GetStartRowIndex() + priorNumRows; + } + InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true); + rows.Clear(); + } + break; + } + } + } + + } +#ifdef DEBUG_TABLE_CELLMAP + printf("=== insertRowGroupsAfter\n"); + Dump(true, true, true); +#endif +} + + +///////////////////////////////////////////////////////////////////////////// +// Child frame enumeration + +const nsFrameList& +nsTableFrame::GetChildList(ChildListID aListID) const +{ + if (aListID == kColGroupList) { + return mColGroups; + } + return nsContainerFrame::GetChildList(aListID); +} + +void +nsTableFrame::GetChildLists(nsTArray* aLists) const +{ + nsContainerFrame::GetChildLists(aLists); + mColGroups.AppendIfNonempty(aLists, kColGroupList); +} + +nsRect +nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { + *aSnap = false; + return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +void +nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame) +{ + nsStyleContext *bgSC; + if (!nsCSSRendering::FindBackground(aFrame, &bgSC)) + return; + if (!bgSC->StyleBackground()->HasFixedBackground(aFrame)) + return; + + mPartHasFixedBackground = true; +} + +nsDisplayItemGeometry* +nsDisplayTableItem::AllocateGeometry(nsDisplayListBuilder* aBuilder) +{ + return new nsDisplayTableItemGeometry(this, aBuilder, + mFrame->GetOffsetTo(mFrame->PresContext()->PresShell()->GetRootFrame())); +} + +void +nsDisplayTableItem::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) +{ + auto geometry = + static_cast(aGeometry); + + bool invalidateForAttachmentFixed = false; + if (mDrawsBackground && mPartHasFixedBackground) { + nsPoint frameOffsetToViewport = mFrame->GetOffsetTo( + mFrame->PresContext()->PresShell()->GetRootFrame()); + invalidateForAttachmentFixed = + frameOffsetToViewport != geometry->mFrameOffsetToViewport; + } + + if (invalidateForAttachmentFixed || + (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages())) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); +} + +class nsDisplayTableBorderBackground : public nsDisplayTableItem { +public: + nsDisplayTableBorderBackground(nsDisplayListBuilder* aBuilder, + nsTableFrame* aFrame, + bool aDrawsBackground) : + nsDisplayTableItem(aBuilder, aFrame, aDrawsBackground) { + MOZ_COUNT_CTOR(nsDisplayTableBorderBackground); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTableBorderBackground() { + MOZ_COUNT_DTOR(nsDisplayTableBorderBackground); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + NS_DISPLAY_DECL_NAME("TableBorderBackground", TYPE_TABLE_BORDER_BACKGROUND) +}; + +#ifdef DEBUG +static bool +IsFrameAllowedInTable(nsIAtom* aType) +{ + return IS_TABLE_CELL(aType) || + nsGkAtoms::tableRowFrame == aType || + nsGkAtoms::tableRowGroupFrame == aType || + nsGkAtoms::scrollFrame == aType || + nsGkAtoms::tableFrame == aType || + nsGkAtoms::tableColFrame == aType || + nsGkAtoms::tableColGroupFrame == aType; +} +#endif + +void +nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + DrawResult result = static_cast(mFrame)-> + PaintTableBorderBackground(aBuilder, *aCtx, mVisibleRect, + ToReferenceFrame()); + + nsDisplayTableItemGeometry::UpdateDrawResult(this, result); +} + +static int32_t +GetTablePartRank(nsDisplayItem* aItem) +{ + nsIAtom* type = aItem->Frame()->GetType(); + if (type == nsGkAtoms::tableFrame) + return 0; + if (type == nsGkAtoms::tableRowGroupFrame) + return 1; + if (type == nsGkAtoms::tableRowFrame) + return 2; + return 3; +} + +static bool CompareByTablePartRank(nsDisplayItem* aItem1, nsDisplayItem* aItem2, + void* aClosure) +{ + return GetTablePartRank(aItem1) <= GetTablePartRank(aItem2); +} + +/* static */ void +nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, + const nsRect& aDirtyRect, const nsDisplayListSet& aLists) +{ + // This is similar to what nsContainerFrame::BuildDisplayListForNonBlockChildren + // does, except that we allow the children's background and borders to go + // in our BorderBackground list. This doesn't really affect background + // painting --- the children won't actually draw their own backgrounds + // because the nsTableFrame already drew them, unless a child has its own + // stacking context, in which case the child won't use its passed-in + // BorderBackground list anyway. It does affect cell borders though; this + // lets us get cell borders into the nsTableFrame's BorderBackground list. + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + } +} + +/* static */ void +nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder, + nsFrame* aFrame, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists, + nsDisplayTableItem* aDisplayItem, + DisplayGenericTablePartTraversal aTraversal) +{ + nsDisplayList eventsBorderBackground; + // If we need to sort the event backgrounds, then we'll put descendants' + // display items into their own set of lists. + bool sortEventBackgrounds = aDisplayItem && aBuilder->IsForEventDelivery(); + nsDisplayListCollection separatedCollection; + const nsDisplayListSet* lists = sortEventBackgrounds ? &separatedCollection : &aLists; + + nsAutoPushCurrentTableItem pushTableItem; + if (aDisplayItem) { + pushTableItem.Push(aBuilder, aDisplayItem); + } + + if (aFrame->IsVisibleForPainting(aBuilder)) { + nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem(); + // currentItem may be null, when none of the table parts have a + // background or border + if (currentItem) { + currentItem->UpdateForFrameBackground(aFrame); + } + + // Paint the outset box-shadows for the table frames + bool hasBoxShadow = aFrame->StyleEffects()->mBoxShadow != nullptr; + if (hasBoxShadow) { + lists->BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, aFrame)); + } + + // Create dedicated background display items per-frame when we're + // handling events. + // XXX how to handle collapsed borders? + if (aBuilder->IsForEventDelivery()) { + nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, + aFrame->GetRectRelativeToSelf(), + lists->BorderBackground()); + } + + // Paint the inset box-shadows for the table frames + if (hasBoxShadow) { + lists->BorderBackground()->AppendNewToTop( + new (aBuilder) nsDisplayBoxShadowInner(aBuilder, aFrame)); + } + } + + aTraversal(aBuilder, aFrame, aDirtyRect, *lists); + + if (sortEventBackgrounds) { + // Ensure that the table frame event background goes before the + // table rowgroups event backgrounds, before the table row event backgrounds, + // before everything else (cells and their blocks) + separatedCollection.BorderBackground()->Sort(CompareByTablePartRank, nullptr); + separatedCollection.MoveTo(aLists); + } + + aFrame->DisplayOutline(aBuilder, aLists); +} + +static bool +AnyTablePartHasBorderOrBackground(nsIFrame* aStart, nsIFrame* aEnd) +{ + for (nsIFrame* f = aStart; f != aEnd; f = f->GetNextSibling()) { + NS_ASSERTION(IsFrameAllowedInTable(f->GetType()), "unexpected frame type"); + + if (FrameHasBorderOrBackground(f)) + return true; + + nsTableCellFrame *cellFrame = do_QueryFrame(f); + if (cellFrame) + continue; + + if (AnyTablePartHasBorderOrBackground(f->PrincipalChildList().FirstChild(), nullptr)) + return true; + } + + return false; +} + +static void +UpdateItemForColGroupBackgrounds(nsDisplayTableItem* item, + const nsFrameList& aFrames) { + for (nsFrameList::Enumerator e(aFrames); !e.AtEnd(); e.Next()) { + nsTableColGroupFrame* cg = static_cast(e.get()); + item->UpdateForFrameBackground(cg); + for (nsTableColFrame* colFrame = cg->GetFirstColumn(); colFrame; + colFrame = colFrame->GetNextCol()) { + item->UpdateForFrameBackground(colFrame); + } + } +} + +// table paint code is concerned primarily with borders and bg color +// SEC: TODO: adjust the rect for captions +void +nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255,128,255)); + + nsDisplayTableItem* item = nullptr; + if (IsVisibleInSelection(aBuilder)) { + nsMargin deflate = GetDeflationForBackground(PresContext()); + if (StyleVisibility()->IsVisible()) { + // If 'deflate' is (0,0,0,0) then we can paint the table background + // in its own display item, so do that to take advantage of + // opacity and visibility optimizations + if (deflate == nsMargin(0, 0, 0, 0)) { + DisplayBackgroundUnconditional(aBuilder, aLists, false); + } + } + + // This background is created if any of the table parts are visible, + // or if we're doing event handling (since DisplayGenericTablePart + // needs the item for the |sortEventBackgrounds|-dependent code). + // Specific visibility decisions are delegated to the table background + // painter, which handles borders and backgrounds for the table. + if (aBuilder->IsForEventDelivery() || + AnyTablePartHasBorderOrBackground(this, GetNextSibling()) || + AnyTablePartHasBorderOrBackground(mColGroups.FirstChild(), nullptr)) { + item = new (aBuilder) nsDisplayTableBorderBackground(aBuilder, this, + deflate != nsMargin(0, 0, 0, 0)); + aLists.BorderBackground()->AppendNewToTop(item); + } + } + DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item); + if (item) { + UpdateItemForColGroupBackgrounds(item, mColGroups); + } +} + +nsMargin +nsTableFrame::GetDeflationForBackground(nsPresContext* aPresContext) const +{ + if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode() || + !IsBorderCollapse()) + return nsMargin(0,0,0,0); + + WritingMode wm = GetWritingMode(); + return GetOuterBCBorder(wm).GetPhysicalMargin(wm); +} + +// XXX We don't put the borders and backgrounds in tree order like we should. +// That requires some major surgery which we aren't going to do right now. +DrawResult +nsTableFrame::PaintTableBorderBackground(nsDisplayListBuilder* aBuilder, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt) +{ + nsPresContext* presContext = PresContext(); + + uint32_t bgFlags = aBuilder->GetBackgroundPaintFlags(); + PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages() + ? PaintBorderFlags::SYNC_DECODE_IMAGES + : PaintBorderFlags(); + + TableBackgroundPainter painter(this, TableBackgroundPainter::eOrigin_Table, + presContext, aRenderingContext, + aDirtyRect, aPt, bgFlags); + nsMargin deflate = GetDeflationForBackground(presContext); + // If 'deflate' is (0,0,0,0) then we'll paint the table background + // in a separate display item, so don't do it here. + DrawResult result = + painter.PaintTable(this, deflate, deflate != nsMargin(0, 0, 0, 0)); + + if (StyleVisibility()->IsVisible()) { + if (!IsBorderCollapse()) { + Sides skipSides = GetSkipSides(); + nsRect rect(aPt, mRect.Size()); + + result &= + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, + borderFlags, skipSides); + } else { + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(aPt, + PresContext()->AppUnitsPerDevPixel()); + + // XXX we should probably get rid of this translation at some stage + // But that would mean modifying PaintBCBorders, ugh + AutoRestoreTransform autoRestoreTransform(drawTarget); + drawTarget->SetTransform( + drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); + + PaintBCBorders(*drawTarget, aDirtyRect - aPt); + } + } + + return result; +} + +nsIFrame::LogicalSides +nsTableFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const +{ + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return LogicalSides(); + } + + LogicalSides skip; + // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto + // account for pagination + if (nullptr != GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (nullptr != GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +void +nsTableFrame::SetColumnDimensions(nscoord aBSize, WritingMode aWM, + const LogicalMargin& aBorderPadding, + const nsSize& aContainerSize) +{ + const nscoord colBSize = aBSize - (aBorderPadding.BStartEnd(aWM) + + GetRowSpacing(-1) + GetRowSpacing(GetRowCount())); + int32_t colIdx = 0; + LogicalPoint colGroupOrigin(aWM, + aBorderPadding.IStart(aWM) + GetColSpacing(-1), + aBorderPadding.BStart(aWM) + GetRowSpacing(-1)); + nsTableFrame* fif = static_cast(FirstInFlow()); + for (nsIFrame* colGroupFrame : mColGroups) { + MOZ_ASSERT(colGroupFrame->GetType() == nsGkAtoms::tableColGroupFrame); + // first we need to figure out the size of the colgroup + int32_t groupFirstCol = colIdx; + nscoord colGroupISize = 0; + nscoord cellSpacingI = 0; + const nsFrameList& columnList = colGroupFrame->PrincipalChildList(); + for (nsIFrame* colFrame : columnList) { + if (mozilla::StyleDisplay::TableColumn == + colFrame->StyleDisplay()->mDisplay) { + NS_ASSERTION(colIdx < GetColCount(), "invalid number of columns"); + cellSpacingI = GetColSpacing(colIdx); + colGroupISize += fif->GetColumnISizeFromFirstInFlow(colIdx) + + cellSpacingI; + ++colIdx; + } + } + if (colGroupISize) { + colGroupISize -= cellSpacingI; + } + + LogicalRect colGroupRect(aWM, colGroupOrigin.I(aWM), colGroupOrigin.B(aWM), + colGroupISize, colBSize); + colGroupFrame->SetRect(aWM, colGroupRect, aContainerSize); + nsSize colGroupSize = colGroupFrame->GetSize(); + + // then we can place the columns correctly within the group + colIdx = groupFirstCol; + LogicalPoint colOrigin(aWM); + for (nsIFrame* colFrame : columnList) { + if (mozilla::StyleDisplay::TableColumn == + colFrame->StyleDisplay()->mDisplay) { + nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx); + LogicalRect colRect(aWM, colOrigin.I(aWM), colOrigin.B(aWM), + colISize, colBSize); + colFrame->SetRect(aWM, colRect, colGroupSize); + cellSpacingI = GetColSpacing(colIdx); + colOrigin.I(aWM) += colISize + cellSpacingI; + ++colIdx; + } + } + + colGroupOrigin.I(aWM) += colGroupISize + cellSpacingI; + } +} + +// SEC: TODO need to worry about continuing frames prev/next in flow for splitting across pages. + +// XXX this could be made more general to handle row modifications that change the +// table bsize, but first we need to scrutinize every Invalidate +void +nsTableFrame::ProcessRowInserted(nscoord aNewBSize) +{ + SetRowInserted(false); // reset the bit that got us here + nsTableFrame::RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + // find the row group containing the inserted row + for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + NS_ASSERTION(rgFrame, "Must have rgFrame here"); + // find the row that was inserted first + for (nsIFrame* childFrame : rgFrame->PrincipalChildList()) { + nsTableRowFrame *rowFrame = do_QueryFrame(childFrame); + if (rowFrame) { + if (rowFrame->IsFirstInserted()) { + rowFrame->SetFirstInserted(false); + // damage the table from the 1st row inserted to the end of the table + nsIFrame::InvalidateFrame(); + // XXXbz didn't we do this up front? Why do we need to do it again? + SetRowInserted(false); + return; // found it, so leave + } + } + } + } +} + +/* virtual */ void +nsTableFrame::MarkIntrinsicISizesDirty() +{ + nsITableLayoutStrategy* tls = LayoutStrategy(); + if (MOZ_UNLIKELY(!tls)) { + // This is a FrameNeedsReflow() from nsBlockFrame::RemoveFrame() + // walking up the ancestor chain in a table next-in-flow. In this case + // our original first-in-flow (which owns the TableLayoutStrategy) has + // already been destroyed and unhooked from the flow chain and thusly + // LayoutStrategy() returns null. All the frames in the flow will be + // destroyed so no need to mark anything dirty here. See bug 595758. + return; + } + tls->MarkIntrinsicISizesDirty(); + + // XXXldb Call SetBCDamageArea? + + nsContainerFrame::MarkIntrinsicISizesDirty(); +} + +/* virtual */ nscoord +nsTableFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + if (NeedToCalcBCBorders()) + CalcBCBorders(); + + ReflowColGroups(aRenderingContext); + + return LayoutStrategy()->GetMinISize(aRenderingContext); +} + +/* virtual */ nscoord +nsTableFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + if (NeedToCalcBCBorders()) + CalcBCBorders(); + + ReflowColGroups(aRenderingContext); + + return LayoutStrategy()->GetPrefISize(aRenderingContext, false); +} + +/* virtual */ nsIFrame::IntrinsicISizeOffsetData +nsTableFrame::IntrinsicISizeOffsets() +{ + IntrinsicISizeOffsetData result = nsContainerFrame::IntrinsicISizeOffsets(); + + result.hMargin = 0; + result.hPctMargin = 0; + + if (IsBorderCollapse()) { + result.hPadding = 0; + result.hPctPadding = 0; + + WritingMode wm = GetWritingMode(); + LogicalMargin outerBC = GetIncludedOuterBCBorder(wm); + result.hBorder = outerBC.IStartEnd(wm); + } + + return result; +} + +/* virtual */ +LogicalSize +nsTableFrame::ComputeSize(nsRenderingContext* aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + LogicalSize result = + nsContainerFrame::ComputeSize(aRenderingContext, aWM, + aCBSize, aAvailableISize, + aMargin, aBorder, aPadding, aFlags); + + // XXX The code below doesn't make sense if the caller's writing mode + // is orthogonal to this frame's. Not sure yet what should happen then; + // for now, just bail out. + if (aWM.IsVertical() != GetWritingMode().IsVertical()) { + return result; + } + + // If we're a container for font size inflation, then shrink + // wrapping inside of us should not apply font size inflation. + AutoMaybeDisableFontInflation an(this); + + // Tables never shrink below their min inline-size. + nscoord minISize = GetMinISize(aRenderingContext); + if (minISize > result.ISize(aWM)) { + result.ISize(aWM) = minISize; + } + + return result; +} + +nscoord +nsTableFrame::TableShrinkISizeToFit(nsRenderingContext *aRenderingContext, + nscoord aISizeInCB) +{ + // If we're a container for font size inflation, then shrink + // wrapping inside of us should not apply font size inflation. + AutoMaybeDisableFontInflation an(this); + + nscoord result; + nscoord minISize = GetMinISize(aRenderingContext); + if (minISize > aISizeInCB) { + result = minISize; + } else { + // Tables shrink inline-size to fit with a slightly different algorithm + // from the one they use for their intrinsic isize (the difference + // relates to handling of percentage isizes on columns). So this + // function differs from nsFrame::ShrinkWidthToFit by only the + // following line. + // Since we've already called GetMinISize, we don't need to do any + // of the other stuff GetPrefISize does. + nscoord prefISize = + LayoutStrategy()->GetPrefISize(aRenderingContext, true); + if (prefISize > aISizeInCB) { + result = aISizeInCB; + } else { + result = prefISize; + } + } + return result; +} + +/* virtual */ +LogicalSize +nsTableFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + // Tables always shrink-wrap. + nscoord cbBased = aAvailableISize - aMargin.ISize(aWM) - aBorder.ISize(aWM) - + aPadding.ISize(aWM); + return LogicalSize(aWM, TableShrinkISizeToFit(aRenderingContext, cbBased), + NS_UNCONSTRAINEDSIZE); +} + +// Return true if aParentReflowInput.frame or any of its ancestors within +// the containing table have non-auto bsize. (e.g. pct or fixed bsize) +bool +nsTableFrame::AncestorsHaveStyleBSize(const ReflowInput& aParentReflowInput) +{ + WritingMode wm = aParentReflowInput.GetWritingMode(); + for (const ReflowInput* rs = &aParentReflowInput; + rs && rs->mFrame; rs = rs->mParentReflowInput) { + nsIAtom* frameType = rs->mFrame->GetType(); + if (IS_TABLE_CELL(frameType) || + (nsGkAtoms::tableRowFrame == frameType) || + (nsGkAtoms::tableRowGroupFrame == frameType)) { + const nsStyleCoord &bsize = rs->mStylePosition->BSize(wm); + // calc() with percentages treated like 'auto' on internal table elements + if (bsize.GetUnit() != eStyleUnit_Auto && + (!bsize.IsCalcUnit() || !bsize.HasPercent())) { + return true; + } + } + else if (nsGkAtoms::tableFrame == frameType) { + // we reached the containing table, so always return + return rs->mStylePosition->BSize(wm).GetUnit() != eStyleUnit_Auto; + } + } + return false; +} + +// See if a special block-size reflow needs to occur and if so, +// call RequestSpecialBSizeReflow +void +nsTableFrame::CheckRequestSpecialBSizeReflow(const ReflowInput& aReflowInput) +{ + NS_ASSERTION(IS_TABLE_CELL(aReflowInput.mFrame->GetType()) || + aReflowInput.mFrame->GetType() == nsGkAtoms::tableRowFrame || + aReflowInput.mFrame->GetType() == nsGkAtoms::tableRowGroupFrame || + aReflowInput.mFrame->GetType() == nsGkAtoms::tableFrame, + "unexpected frame type"); + WritingMode wm = aReflowInput.GetWritingMode(); + if (!aReflowInput.mFrame->GetPrevInFlow() && // 1st in flow + (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedBSize() || // no computed bsize + 0 == aReflowInput.ComputedBSize()) && + eStyleUnit_Percent == aReflowInput.mStylePosition->BSize(wm).GetUnit() && // pct bsize + nsTableFrame::AncestorsHaveStyleBSize(*aReflowInput.mParentReflowInput)) { + nsTableFrame::RequestSpecialBSizeReflow(aReflowInput); + } +} + +// Notify the frame and its ancestors (up to the containing table) that a special +// bsize reflow will occur. During a special bsize reflow, a table, row group, +// row, or cell returns the last size it was reflowed at. However, the table may +// change the bsize of row groups, rows, cells in DistributeBSizeToRows after. +// And the row group can change the bsize of rows, cells in CalculateRowBSizes. +void +nsTableFrame::RequestSpecialBSizeReflow(const ReflowInput& aReflowInput) +{ + // notify the frame and its ancestors of the special reflow, stopping at the containing table + for (const ReflowInput* rs = &aReflowInput; rs && rs->mFrame; rs = rs->mParentReflowInput) { + nsIAtom* frameType = rs->mFrame->GetType(); + NS_ASSERTION(IS_TABLE_CELL(frameType) || + nsGkAtoms::tableRowFrame == frameType || + nsGkAtoms::tableRowGroupFrame == frameType || + nsGkAtoms::tableFrame == frameType, + "unexpected frame type"); + + rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + if (nsGkAtoms::tableFrame == frameType) { + NS_ASSERTION(rs != &aReflowInput, + "should not request special bsize reflow for table"); + // always stop when we reach a table + break; + } + } +} + +/****************************************************************************************** + * Before reflow, intrinsic inline-size calculation is done using GetMinISize + * and GetPrefISize. This used to be known as pass 1 reflow. + * + * After the intrinsic isize calculation, the table determines the + * column widths using BalanceColumnISizes() and + * then reflows each child again with a constrained avail isize. This reflow is referred to + * as the pass 2 reflow. + * + * A special bsize reflow (pass 3 reflow) can occur during an initial or resize reflow + * if (a) a row group, row, cell, or a frame inside a cell has a percent bsize but no computed + * bsize or (b) in paginated mode, a table has a bsize. (a) supports percent nested tables + * contained inside cells whose bsizes aren't known until after the pass 2 reflow. (b) is + * necessary because the table cannot split until after the pass 2 reflow. The mechanics of + * the special bsize reflow (variety a) are as follows: + * + * 1) Each table related frame (table, row group, row, cell) implements NeedsSpecialReflow() + * to indicate that it should get the reflow. It does this when it has a percent bsize but + * no computed bsize by calling CheckRequestSpecialBSizeReflow(). This method calls + * RequestSpecialBSizeReflow() which calls SetNeedSpecialReflow() on its ancestors until + * it reaches the containing table and calls SetNeedToInitiateSpecialReflow() on it. For + * percent bsize frames inside cells, during DidReflow(), the cell's NotifyPercentBSize() + * is called (the cell is the reflow state's mPercentBSizeObserver in this case). + * NotifyPercentBSize() calls RequestSpecialBSizeReflow(). + * + * XXX (jfkthame) This comment appears to be out of date; it refers to methods/flags + * that are no longer present in the code. + * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true) was called, it + * will do the special bsize reflow, setting the reflow state's mFlags.mSpecialBSizeReflow + * to true and mSpecialHeightInitiator to itself. It won't do this if IsPrematureSpecialHeightReflow() + * returns true because in that case another special bsize reflow will be coming along with the + * containing table as the mSpecialHeightInitiator. It is only relevant to do the reflow when + * the mSpecialHeightInitiator is the containing table, because if it is a remote ancestor, then + * appropriate bsizes will not be known. + * + * 3) Since the bsizes of the table, row groups, rows, and cells was determined during the pass 2 + * reflow, they return their last desired sizes during the special bsize reflow. The reflow only + * permits percent bsize frames inside the cells to resize based on the cells bsize and that bsize + * was determined during the pass 2 reflow. + * + * So, in the case of deeply nested tables, all of the tables that were told to initiate a special + * reflow will do so, but if a table is already in a special reflow, it won't inititate the reflow + * until the current initiator is its containing table. Since these reflows are only received by + * frames that need them and they don't cause any rebalancing of tables, the extra overhead is minimal. + * + * The type of special reflow that occurs during printing (variety b) follows the same mechanism except + * that all frames will receive the reflow even if they don't really need them. + * + * Open issues with the special bsize reflow: + * + * 1) At some point there should be 2 kinds of special bsize reflows because (a) and (b) above are + * really quite different. This would avoid unnecessary reflows during printing. + * 2) When a cell contains frames whose percent bsizes > 100%, there is data loss (see bug 115245). + * However, this can also occur if a cell has a fixed bsize and there is no special bsize reflow. + * + * XXXldb Special bsize reflow should really be its own method, not + * part of nsIFrame::Reflow. It should then call nsIFrame::Reflow on + * the contents of the cells to do the necessary block-axis resizing. + * + ******************************************************************************************/ + +/* Layout the entire inner table. */ +void +nsTableFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + bool isPaginated = aPresContext->IsPaginated(); + WritingMode wm = aReflowInput.GetWritingMode(); + + aStatus = NS_FRAME_COMPLETE; + if (!GetPrevInFlow() && !mTableLayoutStrategy) { + NS_ERROR("strategy should have been created in Init"); + return; + } + + // see if collapsing borders need to be calculated + if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) { + CalcBCBorders(); + } + + aDesiredSize.ISize(wm) = aReflowInput.AvailableISize(); + + // Check for an overflow list, and append any row group frames being pushed + MoveOverflowToChildList(); + + bool haveDesiredBSize = false; + SetHaveReflowedColGroups(false); + + // Reflow the entire table (pass 2 and possibly pass 3). This phase is necessary during a + // constrained initial reflow and other reflows which require either a strategy init or balance. + // This isn't done during an unconstrained reflow, because it will occur later when the parent + // reflows with a constrained isize. + bool fixupKidPositions = false; + if (NS_SUBTREE_DIRTY(this) || + aReflowInput.ShouldReflowAllKids() || + IsGeometryDirty() || + aReflowInput.IsBResize()) { + + if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE || + // Also check IsBResize(), to handle the first Reflow preceding a + // special bsize Reflow, when we've already had a special bsize + // Reflow (where ComputedBSize() would not be + // NS_UNCONSTRAINEDSIZE, but without a style change in between). + aReflowInput.IsBResize()) { + // XXX Eventually, we should modify DistributeBSizeToRows to use + // nsTableRowFrame::GetInitialBSize instead of nsIFrame::BSize(). + // That way, it will make its calculations based on internal table + // frame bsizes as they are before they ever had any extra bsize + // distributed to them. In the meantime, this reflows all the + // internal table frames, which restores them to their state before + // DistributeBSizeToRows was called. + SetGeometryDirty(); + } + + bool needToInitiateSpecialReflow = + HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + // see if an extra reflow will be necessary in pagination mode + // when there is a specified table bsize + if (isPaginated && !GetPrevInFlow() && (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize())) { + nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput); + if ((tableSpecifiedBSize > 0) && + (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE)) { + needToInitiateSpecialReflow = true; + } + } + nsIFrame* lastChildReflowed = nullptr; + + NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow, + "Shouldn't be in special bsize reflow here!"); + + // do the pass 2 reflow unless this is a special bsize reflow and we will be + // initiating a special bsize reflow + // XXXldb I changed this. Should I change it back? + + // if we need to initiate a special bsize reflow, then don't constrain the + // bsize of the reflow before that + nscoord availBSize = needToInitiateSpecialReflow + ? NS_UNCONSTRAINEDSIZE + : aReflowInput.AvailableBSize(); + + ReflowTable(aDesiredSize, aReflowInput, availBSize, + lastChildReflowed, aStatus); + // If ComputedWidth is unconstrained, we may need to fix child positions + // later (in vertical-rl mode) due to use of 0 as a dummy + // containerSize.width during ReflowChildren. + fixupKidPositions = wm.IsVerticalRL() && + aReflowInput.ComputedWidth() == NS_UNCONSTRAINEDSIZE; + + // reevaluate special bsize reflow conditions + if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { + needToInitiateSpecialReflow = true; + } + + // XXXldb Are all these conditions correct? + if (needToInitiateSpecialReflow && NS_FRAME_IS_COMPLETE(aStatus)) { + // XXXldb Do we need to set the IsBResize flag on any reflow states? + + ReflowInput &mutable_rs = + const_cast(aReflowInput); + + // distribute extra block-direction space to rows + CalcDesiredBSize(aReflowInput, aDesiredSize); + mutable_rs.mFlags.mSpecialBSizeReflow = true; + + ReflowTable(aDesiredSize, aReflowInput, aReflowInput.AvailableBSize(), + lastChildReflowed, aStatus); + + if (lastChildReflowed && NS_FRAME_IS_NOT_COMPLETE(aStatus)) { + // if there is an incomplete child, then set the desired bsize + // to include it but not the next one + LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput); + aDesiredSize.BSize(wm) = + borderPadding.BEnd(wm) + GetRowSpacing(GetRowCount()) + + lastChildReflowed->GetNormalRect().YMost(); // XXX YMost should be B-flavored + } + haveDesiredBSize = true; + + mutable_rs.mFlags.mSpecialBSizeReflow = false; + } + } + + aDesiredSize.ISize(wm) = aReflowInput.ComputedISize() + + aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm); + if (!haveDesiredBSize) { + CalcDesiredBSize(aReflowInput, aDesiredSize); + } + if (IsRowInserted()) { + ProcessRowInserted(aDesiredSize.BSize(wm)); + } + + if (fixupKidPositions) { + // If we didn't already know the containerSize (and so used zero during + // ReflowChildren), then we need to update the block-position of our kids. + for (nsIFrame* kid : mFrames) { + kid->MovePositionBy(nsPoint(aDesiredSize.Width(), 0)); + RePositionViews(kid); + } + } + + // Calculate the overflow area contribution from our children. We couldn't + // do this on the fly during ReflowChildren(), because in vertical-rl mode + // with unconstrained width, we weren't placing them in their final positions + // until the fixupKidPositions loop just above. + for (nsIFrame* kid : mFrames) { + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid); + } + + LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput); + SetColumnDimensions(aDesiredSize.BSize(wm), wm, borderPadding, + aDesiredSize.PhysicalSize()); + if (NeedToCollapse() && + (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize())) { + AdjustForCollapsingRowsCols(aDesiredSize, wm, borderPadding); + } + + // If there are any relatively-positioned table parts, we need to reflow their + // absolutely-positioned descendants now that their dimensions are final. + FixupPositionedTableParts(aPresContext, aDesiredSize, aReflowInput); + + // make sure the table overflow area does include the table rect. + nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()) ; + + if (!ShouldApplyOverflowClipping(this, aReflowInput.mStyleDisplay)) { + // collapsed border may leak out + LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm); + tableRect.Inflate(bcMargin.GetPhysicalMargin(wm)); + } + aDesiredSize.mOverflowAreas.UnionAllWith(tableRect); + + if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) || + nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { + nsIFrame::InvalidateFrame(); + } + + FinishAndStoreOverflow(&aDesiredSize); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +void +nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput) +{ + FrameTArray* positionedParts = Properties().Get(PositionedTablePartArray()); + if (!positionedParts) { + return; + } + + OverflowChangedTracker overflowTracker; + overflowTracker.SetSubtreeRoot(this); + + for (size_t i = 0; i < positionedParts->Length(); ++i) { + nsIFrame* positionedPart = positionedParts->ElementAt(i); + + // As we've already finished reflow, positionedParts's size and overflow + // areas have already been assigned, so we just pull them back out. + nsSize size(positionedPart->GetSize()); + ReflowOutput desiredSize(aReflowInput.GetWritingMode()); + desiredSize.Width() = size.width; + desiredSize.Height() = size.height; + desiredSize.mOverflowAreas = positionedPart->GetOverflowAreasRelativeToSelf(); + + // Construct a dummy reflow state and reflow status. + // XXX(seth): Note that the dummy reflow state doesn't have a correct + // chain of parent reflow states. It also doesn't necessarily have a + // correct containing block. + WritingMode wm = positionedPart->GetWritingMode(); + LogicalSize availSize(wm, size); + availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput reflowInput(aPresContext, positionedPart, + aReflowInput.mRenderingContext, availSize, + ReflowInput::DUMMY_PARENT_REFLOW_STATE); + nsReflowStatus reflowStatus = NS_FRAME_COMPLETE; + + // Reflow absolutely-positioned descendants of the positioned part. + // FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the bsize and + // ignoring any change to the reflow status aren't correct. We'll never + // paginate absolutely positioned frames. + nsFrame* positionedFrame = static_cast(positionedPart); + positionedFrame->FinishReflowWithAbsoluteFrames(PresContext(), + desiredSize, + reflowInput, + reflowStatus, + true); + + // FinishReflowWithAbsoluteFrames has updated overflow on + // |positionedPart|. We need to make sure that update propagates + // through the intermediate frames between it and this frame. + nsIFrame* positionedFrameParent = positionedPart->GetParent(); + if (positionedFrameParent != this) { + overflowTracker.AddFrame(positionedFrameParent, + OverflowChangedTracker::CHILDREN_CHANGED); + } + } + + // Propagate updated overflow areas up the tree. + overflowTracker.Flush(); + + // Update our own overflow areas. (OverflowChangedTracker doesn't update the + // subtree root itself.) + aDesiredSize.SetOverflowAreasToDesiredBounds(); + nsLayoutUtils::UnionChildOverflow(this, aDesiredSize.mOverflowAreas); +} + +bool +nsTableFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) +{ + // As above in Reflow, make sure the table overflow area includes the table + // rect, and check for collapsed borders leaking out. + if (!ShouldApplyOverflowClipping(this, StyleDisplay())) { + nsRect bounds(nsPoint(0, 0), GetSize()); + WritingMode wm = GetWritingMode(); + LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm); + bounds.Inflate(bcMargin.GetPhysicalMargin(wm)); + + aOverflowAreas.UnionAllWith(bounds); + } + return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); +} + +void +nsTableFrame::ReflowTable(ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nscoord aAvailBSize, + nsIFrame*& aLastChildReflowed, + nsReflowStatus& aStatus) +{ + aLastChildReflowed = nullptr; + + if (!GetPrevInFlow()) { + mTableLayoutStrategy->ComputeColumnISizes(aReflowInput); + } + // Constrain our reflow isize to the computed table isize (of the 1st in flow). + // and our reflow bsize to our avail bsize minus border, padding, cellspacing + WritingMode wm = aReflowInput.GetWritingMode(); + aDesiredSize.ISize(wm) = aReflowInput.ComputedISize() + + aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm); + TableReflowInput reflowInput(aReflowInput, + LogicalSize(wm, aDesiredSize.ISize(wm), + aAvailBSize)); + ReflowChildren(reflowInput, aStatus, aLastChildReflowed, + aDesiredSize.mOverflowAreas); + + ReflowColGroups(aReflowInput.mRenderingContext); +} + +nsIFrame* +nsTableFrame::GetFirstBodyRowGroupFrame() +{ + nsIFrame* headerFrame = nullptr; + nsIFrame* footerFrame = nullptr; + + for (nsIFrame* kidFrame : mFrames) { + const nsStyleDisplay* childDisplay = kidFrame->StyleDisplay(); + + // We expect the header and footer row group frames to be first, and we only + // allow one header and one footer + if (mozilla::StyleDisplay::TableHeaderGroup == childDisplay->mDisplay) { + if (headerFrame) { + // We already have a header frame and so this header frame is treated + // like an ordinary body row group frame + return kidFrame; + } + headerFrame = kidFrame; + + } else if (mozilla::StyleDisplay::TableFooterGroup == childDisplay->mDisplay) { + if (footerFrame) { + // We already have a footer frame and so this footer frame is treated + // like an ordinary body row group frame + return kidFrame; + } + footerFrame = kidFrame; + + } else if (mozilla::StyleDisplay::TableRowGroup == childDisplay->mDisplay) { + return kidFrame; + } + } + + return nullptr; +} + +// Table specific version that takes into account repeated header and footer +// frames when continuing table frames +void +nsTableFrame::PushChildren(const RowGroupArray& aRowGroups, + int32_t aPushFrom) +{ + NS_PRECONDITION(aPushFrom > 0, "pushing first child"); + + // extract the frames from the array into a sibling list + nsFrameList frames; + uint32_t childX; + for (childX = aPushFrom; childX < aRowGroups.Length(); ++childX) { + nsTableRowGroupFrame* rgFrame = aRowGroups[childX]; + if (!rgFrame->IsRepeatable()) { + mFrames.RemoveFrame(rgFrame); + frames.AppendFrame(nullptr, rgFrame); + } + } + + if (frames.IsEmpty()) { + return; + } + + nsTableFrame* nextInFlow = static_cast(GetNextInFlow()); + if (nextInFlow) { + // Insert the frames after any repeated header and footer frames. + nsIFrame* firstBodyFrame = nextInFlow->GetFirstBodyRowGroupFrame(); + nsIFrame* prevSibling = nullptr; + if (firstBodyFrame) { + prevSibling = firstBodyFrame->GetPrevSibling(); + } + // When pushing and pulling frames we need to check for whether any + // views need to be reparented. + ReparentFrameViewList(frames, this, nextInFlow); + nextInFlow->mFrames.InsertFrames(nextInFlow, prevSibling, + frames); + } + else { + // Add the frames to our overflow list. + SetOverflowFrames(frames); + } +} + +// collapsing row groups, rows, col groups and cols are accounted for after both passes of +// reflow so that it has no effect on the calculations of reflow. +void +nsTableFrame::AdjustForCollapsingRowsCols(ReflowOutput& aDesiredSize, + const WritingMode aWM, + const LogicalMargin& aBorderPadding) +{ + nscoord bTotalOffset = 0; // total offset among all rows in all row groups + + // reset the bit, it will be set again if row/rowgroup or col/colgroup are + // collapsed + SetNeedToCollapse(false); + + // collapse the rows and/or row groups as necessary + // Get the ordered children + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + nsTableFrame* firstInFlow = static_cast(FirstInFlow()); + nscoord iSize = firstInFlow->GetCollapsedISize(aWM, aBorderPadding); + nscoord rgISize = iSize - GetColSpacing(-1) - + GetColSpacing(GetColCount()); + nsOverflowAreas overflow; + // Walk the list of children + for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[childX]; + NS_ASSERTION(rgFrame, "Must have row group frame here"); + bTotalOffset += rgFrame->CollapseRowGroupIfNecessary(bTotalOffset, rgISize, + aWM); + ConsiderChildOverflow(overflow, rgFrame); + } + + aDesiredSize.BSize(aWM) -= bTotalOffset; + aDesiredSize.ISize(aWM) = iSize; + overflow.UnionAllWith(nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height())); + FinishAndStoreOverflow(overflow, + nsSize(aDesiredSize.Width(), aDesiredSize.Height())); +} + + +nscoord +nsTableFrame::GetCollapsedISize(const WritingMode aWM, + const LogicalMargin& aBorderPadding) +{ + NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedISize called on next in flow"); + nscoord iSize = GetColSpacing(GetColCount()); + iSize += aBorderPadding.IStartEnd(aWM); + nsTableFrame* fif = static_cast(FirstInFlow()); + for (nsIFrame* groupFrame : mColGroups) { + const nsStyleVisibility* groupVis = groupFrame->StyleVisibility(); + bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); + nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame; + for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame; + colFrame = colFrame->GetNextCol()) { + const nsStyleDisplay* colDisplay = colFrame->StyleDisplay(); + nscoord colIdx = colFrame->GetColIndex(); + if (mozilla::StyleDisplay::TableColumn == colDisplay->mDisplay) { + const nsStyleVisibility* colVis = colFrame->StyleVisibility(); + bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible); + nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx); + if (!collapseGroup && !collapseCol) { + iSize += colISize; + if (ColumnHasCellSpacingBefore(colIdx)) { + iSize += GetColSpacing(colIdx - 1); + } + } + else { + SetNeedToCollapse(true); + } + } + } + } + return iSize; +} + +/* virtual */ void +nsTableFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsContainerFrame::DidSetStyleContext(aOldStyleContext); + + if (!aOldStyleContext) //avoid this on init + return; + + if (IsBorderCollapse() && + BCRecalcNeeded(aOldStyleContext, StyleContext())) { + SetFullBCDamageArea(); + } + + //avoid this on init or nextinflow + if (!mTableLayoutStrategy || GetPrevInFlow()) + return; + + bool isAuto = IsAutoLayout(); + if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) { + nsITableLayoutStrategy* temp; + if (isAuto) + temp = new BasicTableLayoutStrategy(this); + else + temp = new FixedTableLayoutStrategy(this); + + if (temp) { + delete mTableLayoutStrategy; + mTableLayoutStrategy = temp; + } + } +} + + + +void +nsTableFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList || aListID == kColGroupList, + "unexpected child list"); + + // Because we actually have two child lists, one for col group frames and one + // for everything else, we need to look at each frame individually + // XXX The frame construction code should be separating out child frames + // based on the type, bug 343048. + while (!aFrameList.IsEmpty()) { + nsIFrame* f = aFrameList.FirstChild(); + aFrameList.RemoveFrame(f); + + // See what kind of frame we have + const nsStyleDisplay* display = f->StyleDisplay(); + + if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) { + if (MOZ_UNLIKELY(GetPrevInFlow())) { + nsFrameList colgroupFrame(f, f); + auto firstInFlow = static_cast(FirstInFlow()); + firstInFlow->AppendFrames(aListID, colgroupFrame); + continue; + } + nsTableColGroupFrame* lastColGroup = + nsTableColGroupFrame::GetLastRealColGroup(this); + int32_t startColIndex = (lastColGroup) + ? lastColGroup->GetStartColumnIndex() + lastColGroup->GetColCount() : 0; + mColGroups.InsertFrame(this, lastColGroup, f); + // Insert the colgroup and its cols into the table + InsertColGroups(startColIndex, + nsFrameList::Slice(mColGroups, f, f->GetNextSibling())); + } else if (IsRowGroup(display->mDisplay)) { + DrainSelfOverflowList(); // ensure the last frame is in mFrames + // Append the new row group frame to the sibling chain + mFrames.AppendFrame(nullptr, f); + + // insert the row group and its rows into the table + InsertRowGroups(nsFrameList::Slice(mFrames, f, nullptr)); + } else { + // Nothing special to do, just add the frame to our child list + NS_NOTREACHED("How did we get here? Frame construction screwed up"); + mFrames.AppendFrame(nullptr, f); + } + } + +#ifdef DEBUG_TABLE_CELLMAP + printf("=== TableFrame::AppendFrames\n"); + Dump(true, true, true); +#endif + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + SetGeometryDirty(); +} + +// Needs to be at file scope or ArrayLength fails to compile. +struct ChildListInsertions { + nsIFrame::ChildListID mID; + nsFrameList mList; +}; + +void +nsTableFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + // The frames in aFrameList can be a mix of row group frames and col group + // frames. The problem is that they should go in separate child lists so + // we need to deal with that here... + // XXX The frame construction code should be separating out child frames + // based on the type, bug 343048. + + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + + if ((aPrevFrame && !aPrevFrame->GetNextSibling()) || + (!aPrevFrame && GetChildList(aListID).IsEmpty())) { + // Treat this like an append; still a workaround for bug 343048. + AppendFrames(aListID, aFrameList); + return; + } + + // Collect ColGroupFrames into a separate list and insert those separately + // from the other frames (bug 759249). + ChildListInsertions insertions[2]; // ColGroup, other + const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay(); + nsFrameList::FrameLinkEnumerator e(aFrameList); + for (; !aFrameList.IsEmpty(); e.Next()) { + nsIFrame* next = e.NextFrame(); + if (!next || next->StyleDisplay()->mDisplay != display->mDisplay) { + nsFrameList head = aFrameList.ExtractHead(e); + if (display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) { + insertions[0].mID = kColGroupList; + insertions[0].mList.AppendFrames(nullptr, head); + } else { + insertions[1].mID = kPrincipalList; + insertions[1].mList.AppendFrames(nullptr, head); + } + if (!next) { + break; + } + display = next->StyleDisplay(); + } + } + for (uint32_t i = 0; i < ArrayLength(insertions); ++i) { + // We pass aPrevFrame for both ColGroup and other frames since + // HomogenousInsertFrames will only use it if it's a suitable + // prev-sibling for the frames in the frame list. + if (!insertions[i].mList.IsEmpty()) { + HomogenousInsertFrames(insertions[i].mID, aPrevFrame, + insertions[i].mList); + } + } +} + +void +nsTableFrame::HomogenousInsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + // See what kind of frame we have + const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay(); + bool isColGroup = mozilla::StyleDisplay::TableColumnGroup == display->mDisplay; +#ifdef DEBUG + // Verify that either all siblings have display:table-column-group, or they + // all have display values different from table-column-group. + for (nsIFrame* frame : aFrameList) { + auto nextDisplay = frame->StyleDisplay()->mDisplay; + MOZ_ASSERT(isColGroup == + (nextDisplay == mozilla::StyleDisplay::TableColumnGroup), + "heterogenous childlist"); + } +#endif + if (MOZ_UNLIKELY(isColGroup && GetPrevInFlow())) { + auto firstInFlow = static_cast(FirstInFlow()); + firstInFlow->AppendFrames(aListID, aFrameList); + return; + } + if (aPrevFrame) { + const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay(); + // Make sure they belong on the same frame list + if ((display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) != + (prevDisplay->mDisplay == mozilla::StyleDisplay::TableColumnGroup)) { + // the previous frame is not valid, see comment at ::AppendFrames + // XXXbz Using content indices here means XBL will get screwed + // over... Oh, well. + nsIFrame* pseudoFrame = aFrameList.FirstChild(); + nsIContent* parentContent = GetContent(); + nsIContent* content = nullptr; + aPrevFrame = nullptr; + while (pseudoFrame && (parentContent == + (content = pseudoFrame->GetContent()))) { + pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild(); + } + nsCOMPtr container = content->GetParent(); + if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823. + int32_t newIndex = container->IndexOf(content); + nsIFrame* kidFrame; + nsTableColGroupFrame* lastColGroup = nullptr; + if (isColGroup) { + kidFrame = mColGroups.FirstChild(); + lastColGroup = nsTableColGroupFrame::GetLastRealColGroup(this); + } + else { + kidFrame = mFrames.FirstChild(); + } + // Important: need to start at a value smaller than all valid indices + int32_t lastIndex = -1; + while (kidFrame) { + if (isColGroup) { + if (kidFrame == lastColGroup) { + aPrevFrame = kidFrame; // there is no real colgroup after this one + break; + } + } + pseudoFrame = kidFrame; + while (pseudoFrame && (parentContent == + (content = pseudoFrame->GetContent()))) { + pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild(); + } + int32_t index = container->IndexOf(content); + if (index > lastIndex && index < newIndex) { + lastIndex = index; + aPrevFrame = kidFrame; + } + kidFrame = kidFrame->GetNextSibling(); + } + } + } + } + if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) { + NS_ASSERTION(aListID == kColGroupList, "unexpected child list"); + // Insert the column group frames + const nsFrameList::Slice& newColgroups = + mColGroups.InsertFrames(this, aPrevFrame, aFrameList); + // find the starting col index for the first new col group + int32_t startColIndex = 0; + if (aPrevFrame) { + nsTableColGroupFrame* prevColGroup = + (nsTableColGroupFrame*)GetFrameAtOrBefore(this, aPrevFrame, + nsGkAtoms::tableColGroupFrame); + if (prevColGroup) { + startColIndex = prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount(); + } + } + InsertColGroups(startColIndex, newColgroups); + } else if (IsRowGroup(display->mDisplay)) { + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames + // Insert the frames in the sibling chain + const nsFrameList::Slice& newRowGroups = + mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); + + InsertRowGroups(newRowGroups); + } else { + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_NOTREACHED("How did we even get here?"); + // Just insert the frame and don't worry about reflowing it + mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); + return; + } + + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + SetGeometryDirty(); +#ifdef DEBUG_TABLE_CELLMAP + printf("=== TableFrame::InsertFrames\n"); + Dump(true, true, true); +#endif + return; +} + +void +nsTableFrame::DoRemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + if (aListID == kColGroupList) { + nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling(); + nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame; + int32_t firstColIndex = colGroup->GetStartColumnIndex(); + int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1; + mColGroups.DestroyFrame(aOldFrame); + nsTableColGroupFrame::ResetColIndices(nextColGroupFrame, firstColIndex); + // remove the cols from the table + int32_t colIdx; + for (colIdx = lastColIndex; colIdx >= firstColIndex; colIdx--) { + nsTableColFrame* colFrame = mColFrames.SafeElementAt(colIdx); + if (colFrame) { + RemoveCol(colGroup, colIdx, true, false); + } + } + + // If we have some anonymous cols at the end already, we just + // add more of them. + if (!mColFrames.IsEmpty() && + mColFrames.LastElement() && // XXXbz is this ever null? + mColFrames.LastElement()->GetColType() == eColAnonymousCell) { + int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length(); + if (numAnonymousColsToAdd > 0) { + // this sets the child list, updates the col cache and cell map + AppendAnonymousColFrames(numAnonymousColsToAdd); + } + } else { + // All of our colframes correspond to actual tags. It's possible + // that we still have at least as many tags as we have logical + // columns from cells, but we might have one less. Handle the latter case + // as follows: First ask the cellmap to drop its last col if it doesn't + // have any actual cells in it. Then call MatchCellMapToColCache to + // append an anonymous column if it's needed; this needs to be after + // RemoveColsAtEnd, since it will determine the need for a new column + // frame based on the width of the cell map. + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { // XXXbz is this ever null? + cellMap->RemoveColsAtEnd(); + MatchCellMapToColCache(cellMap); + } + } + + } else { + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + nsTableRowGroupFrame* rgFrame = + static_cast(aOldFrame); + // remove the row group from the cell map + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + cellMap->RemoveGroupCellMap(rgFrame); + } + + // remove the row group frame from the sibling chain + mFrames.DestroyFrame(aOldFrame); + + // the removal of a row group changes the cellmap, the columns might change + if (cellMap) { + cellMap->Synchronize(this); + // Create an empty slice + ResetRowIndices(nsFrameList::Slice(mFrames, nullptr, nullptr)); + TableArea damageArea; + cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false, damageArea); + + static_cast(FirstInFlow())->MatchCellMapToColCache(cellMap); + } + } +} + +void +nsTableFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kColGroupList || + mozilla::StyleDisplay::TableColumnGroup != + aOldFrame->StyleDisplay()->mDisplay, + "Wrong list name; use kColGroupList iff colgroup"); + nsIPresShell* shell = PresContext()->PresShell(); + nsTableFrame* lastParent = nullptr; + while (aOldFrame) { + nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation(); + nsTableFrame* parent = static_cast(aOldFrame->GetParent()); + if (parent != lastParent) { + parent->DrainSelfOverflowList(); + } + parent->DoRemoveFrame(aListID, aOldFrame); + aOldFrame = oldFrameNextContinuation; + if (parent != lastParent) { + // for now, just bail and recalc all of the collapsing borders + // as the cellmap changes we need to recalc + if (parent->IsBorderCollapse()) { + parent->SetFullBCDamageArea(); + } + parent->SetGeometryDirty(); + shell->FrameNeedsReflow(parent, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + lastParent = parent; + } + } +#ifdef DEBUG_TABLE_CELLMAP + printf("=== TableFrame::RemoveFrame\n"); + Dump(true, true, true); +#endif +} + +/* virtual */ nsMargin +nsTableFrame::GetUsedBorder() const +{ + if (!IsBorderCollapse()) + return nsContainerFrame::GetUsedBorder(); + + WritingMode wm = GetWritingMode(); + return GetIncludedOuterBCBorder(wm).GetPhysicalMargin(wm); +} + +/* virtual */ nsMargin +nsTableFrame::GetUsedPadding() const +{ + if (!IsBorderCollapse()) + return nsContainerFrame::GetUsedPadding(); + + return nsMargin(0,0,0,0); +} + +/* virtual */ nsMargin +nsTableFrame::GetUsedMargin() const +{ + // The margin is inherited to the table wrapper frame via + // the ::-moz-table-wrapper rule in ua.css. + return nsMargin(0, 0, 0, 0); +} + +NS_DECLARE_FRAME_PROPERTY_DELETABLE(TableBCProperty, BCPropertyData) + +BCPropertyData* +nsTableFrame::GetBCProperty(bool aCreateIfNecessary) const +{ + FrameProperties props = Properties(); + BCPropertyData* value = props.Get(TableBCProperty()); + if (!value && aCreateIfNecessary) { + value = new BCPropertyData(); + props.Set(TableBCProperty(), value); + } + + return value; +} + +static void +DivideBCBorderSize(BCPixelSize aPixelSize, + BCPixelSize& aSmallHalf, + BCPixelSize& aLargeHalf) +{ + aSmallHalf = aPixelSize / 2; + aLargeHalf = aPixelSize - aSmallHalf; +} + +LogicalMargin +nsTableFrame::GetOuterBCBorder(const WritingMode aWM) const +{ + if (NeedToCalcBCBorders()) { + const_cast(this)->CalcBCBorders(); + } + + int32_t p2t = nsPresContext::AppUnitsPerCSSPixel(); + BCPropertyData* propData = GetBCProperty(); + if (propData) { + return LogicalMargin(aWM, + BC_BORDER_START_HALF_COORD(p2t, propData->mBStartBorderWidth), + BC_BORDER_END_HALF_COORD(p2t, propData->mIEndBorderWidth), + BC_BORDER_END_HALF_COORD(p2t, propData->mBEndBorderWidth), + BC_BORDER_START_HALF_COORD(p2t, propData->mIStartBorderWidth)); + } + return LogicalMargin(aWM); +} + +LogicalMargin +nsTableFrame::GetIncludedOuterBCBorder(const WritingMode aWM) const +{ + if (NeedToCalcBCBorders()) { + const_cast(this)->CalcBCBorders(); + } + + int32_t p2t = nsPresContext::AppUnitsPerCSSPixel(); + BCPropertyData* propData = GetBCProperty(); + if (propData) { + return LogicalMargin(aWM, + BC_BORDER_START_HALF_COORD(p2t, propData->mBStartBorderWidth), + BC_BORDER_END_HALF_COORD(p2t, propData->mIEndCellBorderWidth), + BC_BORDER_END_HALF_COORD(p2t, propData->mBEndBorderWidth), + BC_BORDER_START_HALF_COORD(p2t, propData->mIStartCellBorderWidth)); + } + return LogicalMargin(aWM); +} + +LogicalMargin +nsTableFrame::GetExcludedOuterBCBorder(const WritingMode aWM) const +{ + return GetOuterBCBorder(aWM) - GetIncludedOuterBCBorder(aWM); +} + +static LogicalMargin +GetSeparateModelBorderPadding(const WritingMode aWM, + const ReflowInput* aReflowInput, + nsStyleContext* aStyleContext) +{ + // XXXbz Either we _do_ have a reflow state and then we can use its + // mComputedBorderPadding or we don't and then we get the padding + // wrong! + const nsStyleBorder* border = aStyleContext->StyleBorder(); + LogicalMargin borderPadding(aWM, border->GetComputedBorder()); + if (aReflowInput) { + borderPadding += aReflowInput->ComputedLogicalPadding(); + } + return borderPadding; +} + +LogicalMargin +nsTableFrame::GetChildAreaOffset(const WritingMode aWM, + const ReflowInput* aReflowInput) const +{ + return IsBorderCollapse() ? GetIncludedOuterBCBorder(aWM) : + GetSeparateModelBorderPadding(aWM, aReflowInput, mStyleContext); +} + +void +nsTableFrame::InitChildReflowInput(ReflowInput& aReflowInput) +{ + nsMargin collapseBorder; + nsMargin padding(0,0,0,0); + nsMargin* pCollapseBorder = nullptr; + nsPresContext* presContext = PresContext(); + if (IsBorderCollapse()) { + nsTableRowGroupFrame* rgFrame = + static_cast(aReflowInput.mFrame); + WritingMode wm = GetWritingMode(); + LogicalMargin border = rgFrame->GetBCBorderWidth(wm); + collapseBorder = border.GetPhysicalMargin(wm); + pCollapseBorder = &collapseBorder; + } + aReflowInput.Init(presContext, nullptr, pCollapseBorder, &padding); + + NS_ASSERTION(!mBits.mResizedColumns || + !aReflowInput.mParentReflowInput->mFlags.mSpecialBSizeReflow, + "should not resize columns on special bsize reflow"); + if (mBits.mResizedColumns) { + aReflowInput.SetIResize(true); + } +} + +// Position and size aKidFrame and update our reflow state. The origin of +// aKidRect is relative to the upper-left origin of our frame +void +nsTableFrame::PlaceChild(TableReflowInput& aReflowInput, + nsIFrame* aKidFrame, + nsPoint aKidPosition, + ReflowOutput& aKidDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidVisualOverflow) +{ + WritingMode wm = aReflowInput.reflowInput.GetWritingMode(); + bool isFirstReflow = + aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + // Place and size the child + FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, nullptr, + aKidPosition.x, aKidPosition.y, 0); + + InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow, + isFirstReflow); + + // Adjust the running block-offset + aReflowInput.bCoord += aKidDesiredSize.BSize(wm); + + // If our bsize is constrained, then update the available bsize + if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) { + aReflowInput.availSize.BSize(wm) -= aKidDesiredSize.BSize(wm); + } +} + +void +nsTableFrame::OrderRowGroups(RowGroupArray& aChildren, + nsTableRowGroupFrame** aHead, + nsTableRowGroupFrame** aFoot) const +{ + aChildren.Clear(); + nsTableRowGroupFrame* head = nullptr; + nsTableRowGroupFrame* foot = nullptr; + + nsIFrame* kidFrame = mFrames.FirstChild(); + while (kidFrame) { + const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay(); + nsTableRowGroupFrame* rowGroup = + static_cast(kidFrame); + + switch (kidDisplay->mDisplay) { + case mozilla::StyleDisplay::TableHeaderGroup: + if (head) { // treat additional thead like tbody + aChildren.AppendElement(rowGroup); + } + else { + head = rowGroup; + } + break; + case mozilla::StyleDisplay::TableFooterGroup: + if (foot) { // treat additional tfoot like tbody + aChildren.AppendElement(rowGroup); + } + else { + foot = rowGroup; + } + break; + case mozilla::StyleDisplay::TableRowGroup: + aChildren.AppendElement(rowGroup); + break; + default: + NS_NOTREACHED("How did this produce an nsTableRowGroupFrame?"); + // Just ignore it + break; + } + // Get the next sibling but skip it if it's also the next-in-flow, since + // a next-in-flow will not be part of the current table. + while (kidFrame) { + nsIFrame* nif = kidFrame->GetNextInFlow(); + kidFrame = kidFrame->GetNextSibling(); + if (kidFrame != nif) + break; + } + } + + // put the thead first + if (head) { + aChildren.InsertElementAt(0, head); + } + if (aHead) + *aHead = head; + // put the tfoot after the last tbody + if (foot) { + aChildren.AppendElement(foot); + } + if (aFoot) + *aFoot = foot; +} + +nsTableRowGroupFrame* +nsTableFrame::GetTHead() const +{ + nsIFrame* kidFrame = mFrames.FirstChild(); + while (kidFrame) { + if (kidFrame->StyleDisplay()->mDisplay == + mozilla::StyleDisplay::TableHeaderGroup) { + return static_cast(kidFrame); + } + + // Get the next sibling but skip it if it's also the next-in-flow, since + // a next-in-flow will not be part of the current table. + while (kidFrame) { + nsIFrame* nif = kidFrame->GetNextInFlow(); + kidFrame = kidFrame->GetNextSibling(); + if (kidFrame != nif) + break; + } + } + + return nullptr; +} + +nsTableRowGroupFrame* +nsTableFrame::GetTFoot() const +{ + nsIFrame* kidFrame = mFrames.FirstChild(); + while (kidFrame) { + if (kidFrame->StyleDisplay()->mDisplay == + mozilla::StyleDisplay::TableFooterGroup) { + return static_cast(kidFrame); + } + + // Get the next sibling but skip it if it's also the next-in-flow, since + // a next-in-flow will not be part of the current table. + while (kidFrame) { + nsIFrame* nif = kidFrame->GetNextInFlow(); + kidFrame = kidFrame->GetNextSibling(); + if (kidFrame != nif) + break; + } + } + + return nullptr; +} + +static bool +IsRepeatable(nscoord aFrameHeight, nscoord aPageHeight) +{ + return aFrameHeight < (aPageHeight / 4); +} + +nsresult +nsTableFrame::SetupHeaderFooterChild(const TableReflowInput& aReflowInput, + nsTableRowGroupFrame* aFrame, + nscoord* aDesiredHeight) +{ + nsPresContext* presContext = PresContext(); + nscoord pageHeight = presContext->GetPageSize().height; + + // Reflow the child with unconstrained height + WritingMode wm = aFrame->GetWritingMode(); + LogicalSize availSize = aReflowInput.reflowInput.AvailableSize(wm); + + nsSize containerSize = availSize.GetPhysicalSize(wm); + // XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE + + availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput kidReflowInput(presContext, aReflowInput.reflowInput, + aFrame, availSize, nullptr, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(kidReflowInput); + kidReflowInput.mFlags.mIsTopOfPage = true; + ReflowOutput desiredSize(aReflowInput.reflowInput); + desiredSize.ClearSize(); + nsReflowStatus status; + ReflowChild(aFrame, presContext, desiredSize, kidReflowInput, + wm, LogicalPoint(wm, aReflowInput.iCoord, aReflowInput.bCoord), + containerSize, 0, status); + // The child will be reflowed again "for real" so no need to place it now + + aFrame->SetRepeatable(IsRepeatable(desiredSize.Height(), pageHeight)); + *aDesiredHeight = desiredSize.Height(); + return NS_OK; +} + +void +nsTableFrame::PlaceRepeatedFooter(TableReflowInput& aReflowInput, + nsTableRowGroupFrame *aTfoot, + nscoord aFooterHeight) +{ + nsPresContext* presContext = PresContext(); + WritingMode wm = aTfoot->GetWritingMode(); + LogicalSize kidAvailSize = aReflowInput.availSize; + + nsSize containerSize = kidAvailSize.GetPhysicalSize(wm); + // XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE + + kidAvailSize.BSize(wm) = aFooterHeight; + ReflowInput footerReflowInput(presContext, + aReflowInput.reflowInput, + aTfoot, kidAvailSize, + nullptr, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(footerReflowInput); + aReflowInput.bCoord += GetRowSpacing(GetRowCount()); + + nsRect origTfootRect = aTfoot->GetRect(); + nsRect origTfootVisualOverflow = aTfoot->GetVisualOverflowRect(); + + nsReflowStatus footerStatus; + ReflowOutput desiredSize(aReflowInput.reflowInput); + desiredSize.ClearSize(); + LogicalPoint kidPosition(wm, aReflowInput.iCoord, aReflowInput.bCoord); + ReflowChild(aTfoot, presContext, desiredSize, footerReflowInput, + wm, kidPosition, containerSize, 0, footerStatus); + footerReflowInput.ApplyRelativePositioning(&kidPosition, containerSize); + + PlaceChild(aReflowInput, aTfoot, + // We subtract desiredSize.PhysicalSize() from containerSize here + // to account for the fact that in RTL modes, the origin is + // on the right-hand side so we're not simply converting a + // point, we're also swapping the child's origin side. + kidPosition.GetPhysicalPoint(wm, containerSize - + desiredSize.PhysicalSize()), + desiredSize, origTfootRect, origTfootVisualOverflow); +} + +// Reflow the children based on the avail size and reason in aReflowInput +void +nsTableFrame::ReflowChildren(TableReflowInput& aReflowInput, + nsReflowStatus& aStatus, + nsIFrame*& aLastChildReflowed, + nsOverflowAreas& aOverflowAreas) +{ + aStatus = NS_FRAME_COMPLETE; + aLastChildReflowed = nullptr; + + nsIFrame* prevKidFrame = nullptr; + WritingMode wm = aReflowInput.reflowInput.GetWritingMode(); + NS_WARNING_ASSERTION( + wm.IsVertical() || + NS_UNCONSTRAINEDSIZE != aReflowInput.reflowInput.ComputedWidth(), + "shouldn't have unconstrained width in horizontal mode"); + nsSize containerSize = + aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained(); + + nsPresContext* presContext = PresContext(); + // XXXldb Should we be checking constrained height instead? + // tables are not able to pull back children from its next inflow, so even + // under paginated contexts tables are should not paginate if they are inside + // column set + bool isPaginated = presContext->IsPaginated() && + NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm) && + aReflowInput.reflowInput.mFlags.mTableIsSplittable; + + aOverflowAreas.Clear(); + + bool reflowAllKids = aReflowInput.reflowInput.ShouldReflowAllKids() || + mBits.mResizedColumns || + IsGeometryDirty(); + + RowGroupArray rowGroups; + nsTableRowGroupFrame *thead, *tfoot; + OrderRowGroups(rowGroups, &thead, &tfoot); + bool pageBreak = false; + nscoord footerHeight = 0; + + // Determine the repeatablility of headers and footers, and also the desired + // height of any repeatable footer. + // The repeatability of headers on continued tables is handled + // when they are created in nsCSSFrameConstructor::CreateContinuingTableFrame. + // We handle the repeatability of footers again here because we need to + // determine the footer's height anyway. We could perhaps optimize by + // using the footer's prev-in-flow's height instead of reflowing it again, + // but there's no real need. + if (isPaginated) { + if (thead && !GetPrevInFlow()) { + nscoord desiredHeight; + nsresult rv = SetupHeaderFooterChild(aReflowInput, thead, &desiredHeight); + if (NS_FAILED(rv)) + return; + } + if (tfoot) { + nsresult rv = SetupHeaderFooterChild(aReflowInput, tfoot, &footerHeight); + if (NS_FAILED(rv)) + return; + } + } + // if the child is a tbody in paginated mode reduce the height by a repeated footer + bool allowRepeatedFooter = false; + for (size_t childX = 0; childX < rowGroups.Length(); childX++) { + nsIFrame* kidFrame = rowGroups[childX]; + nsTableRowGroupFrame* rowGroupFrame = rowGroups[childX]; + nscoord cellSpacingB = GetRowSpacing(rowGroupFrame->GetStartRowIndex()+ + rowGroupFrame->GetRowCount()); + // Get the frame state bits + // See if we should only reflow the dirty child frames + if (reflowAllKids || + NS_SUBTREE_DIRTY(kidFrame) || + (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow && + (isPaginated || kidFrame->HasAnyStateBits( + NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { + if (pageBreak) { + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight); + } + else if (tfoot && tfoot->IsRepeatable()) { + tfoot->SetRepeatable(false); + } + PushChildren(rowGroups, childX); + aStatus = NS_FRAME_NOT_COMPLETE; + break; + } + + LogicalSize kidAvailSize(aReflowInput.availSize); + allowRepeatedFooter = false; + if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.BSize(wm))) { + nsTableRowGroupFrame* kidRG = + static_cast(kidFrame); + if (kidRG != thead && kidRG != tfoot && tfoot && tfoot->IsRepeatable()) { + // the child is a tbody and there is a repeatable footer + NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1], "Missing footer!"); + if (footerHeight + cellSpacingB < kidAvailSize.BSize(wm)) { + allowRepeatedFooter = true; + kidAvailSize.BSize(wm) -= footerHeight + cellSpacingB; + } + } + } + + nsRect oldKidRect = kidFrame->GetRect(); + nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect(); + + ReflowOutput desiredSize(aReflowInput.reflowInput); + desiredSize.ClearSize(); + + // Reflow the child into the available space + ReflowInput kidReflowInput(presContext, aReflowInput.reflowInput, + kidFrame, + kidAvailSize, + nullptr, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(kidReflowInput); + + // If this isn't the first row group, and the previous row group has a + // nonzero YMost, then we can't be at the top of the page. + // We ignore a repeated head row group in this check to avoid causing + // infinite loops in some circumstances - see bug 344883. + if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) && + (rowGroups[childX - 1]->GetNormalRect().YMost() > 0)) { + kidReflowInput.mFlags.mIsTopOfPage = false; + } + aReflowInput.bCoord += cellSpacingB; + if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) { + aReflowInput.availSize.BSize(wm) -= cellSpacingB; + } + // record the presence of a next in flow, it might get destroyed so we + // need to reorder the row group array + bool reorder = false; + if (kidFrame->GetNextInFlow()) + reorder = true; + + LogicalPoint kidPosition(wm, aReflowInput.iCoord, aReflowInput.bCoord); + ReflowChild(kidFrame, presContext, desiredSize, kidReflowInput, + wm, kidPosition, containerSize, 0, aStatus); + kidReflowInput.ApplyRelativePositioning(&kidPosition, containerSize); + + if (reorder) { + // reorder row groups the reflow may have changed the nextinflows + OrderRowGroups(rowGroups, &thead, &tfoot); + childX = rowGroups.IndexOf(kidFrame); + if (childX == RowGroupArray::NoIndex) { + // XXXbz can this happen? + childX = rowGroups.Length(); + } + } + if (isPaginated && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) && + ShouldAvoidBreakInside(aReflowInput.reflowInput)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + break; + } + // see if the rowgroup did not fit on this page might be pushed on + // the next page + if (isPaginated && + (NS_INLINE_IS_BREAK_BEFORE(aStatus) || + (NS_FRAME_IS_COMPLETE(aStatus) && + (NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight()) && + kidReflowInput.AvailableHeight() < desiredSize.Height()))) { + if (ShouldAvoidBreakInside(aReflowInput.reflowInput)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + break; + } + // if we are on top of the page place with dataloss + if (kidReflowInput.mFlags.mIsTopOfPage) { + if (childX+1 < rowGroups.Length()) { + nsIFrame* nextRowGroupFrame = rowGroups[childX + 1]; + if (nextRowGroupFrame) { + PlaceChild(aReflowInput, kidFrame, + kidPosition.GetPhysicalPoint(wm, + containerSize - desiredSize.PhysicalSize()), + desiredSize, oldKidRect, oldKidVisualOverflow); + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight); + } + else if (tfoot && tfoot->IsRepeatable()) { + tfoot->SetRepeatable(false); + } + aStatus = NS_FRAME_NOT_COMPLETE; + PushChildren(rowGroups, childX + 1); + aLastChildReflowed = kidFrame; + break; + } + } + } + else { // we are not on top, push this rowgroup onto the next page + if (prevKidFrame) { // we had a rowgroup before so push this + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight); + } + else if (tfoot && tfoot->IsRepeatable()) { + tfoot->SetRepeatable(false); + } + aStatus = NS_FRAME_NOT_COMPLETE; + PushChildren(rowGroups, childX); + aLastChildReflowed = prevKidFrame; + break; + } + else { // we can't push so lets make clear how much space we need + PlaceChild(aReflowInput, kidFrame, + kidPosition.GetPhysicalPoint(wm, + containerSize - desiredSize.PhysicalSize()), + desiredSize, oldKidRect, oldKidVisualOverflow); + aLastChildReflowed = kidFrame; + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight); + aLastChildReflowed = tfoot; + } + break; + } + } + } + + aLastChildReflowed = kidFrame; + + pageBreak = false; + // see if there is a page break after this row group or before the next one + if (NS_FRAME_IS_COMPLETE(aStatus) && isPaginated && + (NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight())) { + nsIFrame* nextKid = + (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr; + pageBreak = PageBreakAfter(kidFrame, nextKid); + } + + // Place the child + PlaceChild(aReflowInput, kidFrame, + kidPosition.GetPhysicalPoint(wm, containerSize - + desiredSize.PhysicalSize()), + desiredSize, oldKidRect, oldKidVisualOverflow); + + // Remember where we just were in case we end up pushing children + prevKidFrame = kidFrame; + + MOZ_ASSERT(!NS_FRAME_IS_NOT_COMPLETE(aStatus) || isPaginated, + "Table contents should only fragment in paginated contexts"); + + // Special handling for incomplete children + if (isPaginated && NS_FRAME_IS_NOT_COMPLETE(aStatus)) { + nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow(); + if (!kidNextInFlow) { + // The child doesn't have a next-in-flow so create a continuing + // frame. This hooks the child into the flow + kidNextInFlow = presContext->PresShell()->FrameConstructor()-> + CreateContinuingFrame(presContext, kidFrame, this); + + // Insert the kid's new next-in-flow into our sibling list... + mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow); + // and in rowGroups after childX so that it will get pushed below. + rowGroups.InsertElementAt(childX + 1, + static_cast(kidNextInFlow)); + } else if (kidNextInFlow == kidFrame->GetNextSibling()) { + // OrderRowGroups excludes NIFs in the child list from 'rowGroups' + // so we deal with that here to make sure they get pushed. + MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow), + "OrderRowGroups must not put our NIF in 'rowGroups'"); + rowGroups.InsertElementAt(childX + 1, + static_cast(kidNextInFlow)); + } + + // We've used up all of our available space so push the remaining + // children. + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight); + } + else if (tfoot && tfoot->IsRepeatable()) { + tfoot->SetRepeatable(false); + } + + nsIFrame* nextSibling = kidFrame->GetNextSibling(); + if (nextSibling) { + PushChildren(rowGroups, childX + 1); + } + break; + } + } + else { // it isn't being reflowed + aReflowInput.bCoord += cellSpacingB; + LogicalRect kidRect(wm, kidFrame->GetNormalRect(), containerSize); + if (kidRect.BStart(wm) != aReflowInput.bCoord) { + // invalidate the old position + kidFrame->InvalidateFrameSubtree(); + // move to the new position + kidFrame->MovePositionBy(wm, LogicalPoint(wm, 0, aReflowInput.bCoord - + kidRect.BStart(wm))); + RePositionViews(kidFrame); + // invalidate the new position + kidFrame->InvalidateFrameSubtree(); + } + aReflowInput.bCoord += kidRect.BSize(wm); + + // If our bsize is constrained then update the available bsize. + if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) { + aReflowInput.availSize.BSize(wm) -= cellSpacingB + kidRect.BSize(wm); + } + } + } + + // We've now propagated the column resizes and geometry changes to all + // the children. + mBits.mResizedColumns = false; + ClearGeometryDirty(); +} + +void +nsTableFrame::ReflowColGroups(nsRenderingContext *aRenderingContext) +{ + if (!GetPrevInFlow() && !HaveReflowedColGroups()) { + ReflowOutput kidMet(GetWritingMode()); + nsPresContext *presContext = PresContext(); + for (nsIFrame* kidFrame : mColGroups) { + if (NS_SUBTREE_DIRTY(kidFrame)) { + // The column groups don't care about dimensions or reflow states. + ReflowInput + kidReflowInput(presContext, kidFrame, aRenderingContext, + LogicalSize(kidFrame->GetWritingMode())); + nsReflowStatus cgStatus; + ReflowChild(kidFrame, presContext, kidMet, kidReflowInput, 0, 0, 0, + cgStatus); + FinishReflowChild(kidFrame, presContext, kidMet, nullptr, 0, 0, 0); + } + } + SetHaveReflowedColGroups(true); + } +} + +void +nsTableFrame::CalcDesiredBSize(const ReflowInput& aReflowInput, + ReflowOutput& aDesiredSize) +{ + WritingMode wm = aReflowInput.GetWritingMode(); + nsTableCellMap* cellMap = GetCellMap(); + if (!cellMap) { + NS_ERROR("never ever call me until the cell map is built!"); + aDesiredSize.BSize(wm) = 0; + return; + } + LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput); + + // get the natural bsize based on the last child's (row group) rect + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + if (rowGroups.IsEmpty()) { + // tables can be used as rectangular items without content + nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput); + if ((NS_UNCONSTRAINEDSIZE != tableSpecifiedBSize) && + (tableSpecifiedBSize > 0) && + eCompatibility_NavQuirks != PresContext()->CompatibilityMode()) { + // empty tables should not have a size in quirks mode + aDesiredSize.BSize(wm) = tableSpecifiedBSize; + } else { + aDesiredSize.BSize(wm) = 0; + } + return; + } + int32_t rowCount = cellMap->GetRowCount(); + int32_t colCount = cellMap->GetColCount(); + nscoord desiredBSize = borderPadding.BStartEnd(wm); + if (rowCount > 0 && colCount > 0) { + desiredBSize += GetRowSpacing(-1); + for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + desiredBSize += rowGroups[rgIdx]->BSize(wm) + + GetRowSpacing(rowGroups[rgIdx]->GetRowCount() + + rowGroups[rgIdx]->GetStartRowIndex()); + } + } + + // see if a specified table bsize requires dividing additional space to rows + if (!GetPrevInFlow()) { + nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput); + if ((tableSpecifiedBSize > 0) && + (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE) && + (tableSpecifiedBSize > desiredBSize)) { + // proportionately distribute the excess bsize to unconstrained rows in each + // unconstrained row group. + DistributeBSizeToRows(aReflowInput, tableSpecifiedBSize - desiredBSize); + // this might have changed the overflow area incorporate the childframe overflow area. + for (nsIFrame* kidFrame : mFrames) { + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); + } + desiredBSize = tableSpecifiedBSize; + } + } + aDesiredSize.BSize(wm) = desiredBSize; +} + +static +void ResizeCells(nsTableFrame& aTableFrame) +{ + nsTableFrame::RowGroupArray rowGroups; + aTableFrame.OrderRowGroups(rowGroups); + WritingMode wm = aTableFrame.GetWritingMode(); + ReflowOutput tableDesiredSize(wm); + tableDesiredSize.SetSize(wm, aTableFrame.GetLogicalSize(wm)); + tableDesiredSize.SetOverflowAreasToDesiredBounds(); + + for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + + ReflowOutput groupDesiredSize(wm); + groupDesiredSize.SetSize(wm, rgFrame->GetLogicalSize(wm)); + groupDesiredSize.SetOverflowAreasToDesiredBounds(); + + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + rowFrame->DidResize(); + rgFrame->ConsiderChildOverflow(groupDesiredSize.mOverflowAreas, rowFrame); + rowFrame = rowFrame->GetNextRow(); + } + rgFrame->FinishAndStoreOverflow(&groupDesiredSize); + tableDesiredSize.mOverflowAreas.UnionWith(groupDesiredSize.mOverflowAreas + + rgFrame->GetPosition()); + } + aTableFrame.FinishAndStoreOverflow(&tableDesiredSize); +} + +void +nsTableFrame::DistributeBSizeToRows(const ReflowInput& aReflowInput, + nscoord aAmount) +{ + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput); + + nsSize containerSize = + aReflowInput.ComputedSizeAsContainerIfConstrained(); + + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + nscoord amountUsed = 0; + // distribute space to each pct bsize row whose row group doesn't have a computed + // bsize, and base the pct on the table bsize. If the row group had a computed + // bsize, then this was already done in nsTableRowGroupFrame::CalculateRowBSizes + nscoord pctBasis = aReflowInput.ComputedBSize() - GetRowSpacing(-1, GetRowCount()); + nscoord bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(0); + nscoord bEndRG = bOriginRG; + uint32_t rgIdx; + for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + nscoord amountUsedByRG = 0; + nscoord bOriginRow = 0; + LogicalRect rgNormalRect(wm, rgFrame->GetNormalRect(), containerSize); + if (!rgFrame->HasStyleBSize()) { + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + // We don't know the final width of the rowGroupFrame yet, so use 0,0 + // as a dummy containerSize here; we'll adjust the row positions at + // the end, after the rowGroup size is finalized. + const nsSize dummyContainerSize; + LogicalRect rowNormalRect(wm, rowFrame->GetNormalRect(), + dummyContainerSize); + nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex()); + if ((amountUsed < aAmount) && rowFrame->HasPctBSize()) { + nscoord pctBSize = rowFrame->GetInitialBSize(pctBasis); + nscoord amountForRow = std::min(aAmount - amountUsed, + pctBSize - rowNormalRect.BSize(wm)); + if (amountForRow > 0) { + // XXXbz we don't need to move the row's b-position to bOriginRow? + nsRect origRowRect = rowFrame->GetRect(); + nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow; + rowFrame->SetSize(wm, LogicalSize(wm, rowNormalRect.ISize(wm), + newRowBSize)); + bOriginRow += newRowBSize + cellSpacingB; + bEndRG += newRowBSize + cellSpacingB; + amountUsed += amountForRow; + amountUsedByRG += amountForRow; + //rowFrame->DidResize(); + nsTableFrame::RePositionViews(rowFrame); + + rgFrame->InvalidateFrameWithRect(origRowRect); + rgFrame->InvalidateFrame(); + } + } + else { + if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm) && + !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + rowFrame->InvalidateFrameSubtree(); + rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow - + rowNormalRect.BStart(wm))); + nsTableFrame::RePositionViews(rowFrame); + rowFrame->InvalidateFrameSubtree(); + } + bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB; + bEndRG += rowNormalRect.BSize(wm) + cellSpacingB; + } + rowFrame = rowFrame->GetNextRow(); + } + if (amountUsed > 0) { + if (rgNormalRect.BStart(wm) != bOriginRG) { + rgFrame->InvalidateFrameSubtree(); + } + + nsRect origRgNormalRect = rgFrame->GetRect(); + nsRect origRgVisualOverflow = rgFrame->GetVisualOverflowRect(); + + rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG - + rgNormalRect.BStart(wm))); + rgFrame->SetSize(wm, LogicalSize(wm, rgNormalRect.ISize(wm), + rgNormalRect.BSize(wm) + amountUsedByRG)); + + nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect, + origRgVisualOverflow, false); + } + } + else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) { + rgFrame->InvalidateFrameSubtree(); + rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG - + rgNormalRect.BStart(wm))); + // Make sure child views are properly positioned + nsTableFrame::RePositionViews(rgFrame); + rgFrame->InvalidateFrameSubtree(); + } + bOriginRG = bEndRG; + } + + if (amountUsed >= aAmount) { + ResizeCells(*this); + return; + } + + // get the first row without a style bsize where its row group has an + // unconstrained bsize + nsTableRowGroupFrame* firstUnStyledRG = nullptr; + nsTableRowFrame* firstUnStyledRow = nullptr; + for (rgIdx = 0; rgIdx < rowGroups.Length() && !firstUnStyledRG; rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + if (!rgFrame->HasStyleBSize()) { + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + if (!rowFrame->HasStyleBSize()) { + firstUnStyledRG = rgFrame; + firstUnStyledRow = rowFrame; + break; + } + rowFrame = rowFrame->GetNextRow(); + } + } + } + + nsTableRowFrame* lastEligibleRow = nullptr; + // Accumulate the correct divisor. This will be the total bsize of all + // unstyled rows inside unstyled row groups, unless there are none, in which + // case, it will be number of all rows. If the unstyled rows don't have a + // bsize, divide the space equally among them. + nscoord divisor = 0; + int32_t eligibleRows = 0; + bool expandEmptyRows = false; + + if (!firstUnStyledRow) { + // there is no unstyled row + divisor = GetRowCount(); + } + else { + for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + if (!firstUnStyledRG || !rgFrame->HasStyleBSize()) { + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + if (!firstUnStyledRG || !rowFrame->HasStyleBSize()) { + NS_ASSERTION(rowFrame->BSize(wm) >= 0, + "negative row frame block-size"); + divisor += rowFrame->BSize(wm); + eligibleRows++; + lastEligibleRow = rowFrame; + } + rowFrame = rowFrame->GetNextRow(); + } + } + } + if (divisor <= 0) { + if (eligibleRows > 0) { + expandEmptyRows = true; + } + else { + NS_ERROR("invalid divisor"); + return; + } + } + } + // allocate the extra bsize to the unstyled row groups and rows + nscoord bSizeToDistribute = aAmount - amountUsed; + bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(-1); + bEndRG = bOriginRG; + for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + nscoord amountUsedByRG = 0; + nscoord bOriginRow = 0; + LogicalRect rgNormalRect(wm, rgFrame->GetNormalRect(), containerSize); + nsRect rgVisualOverflow = rgFrame->GetVisualOverflowRect(); + // see if there is an eligible row group or we distribute to all rows + if (!firstUnStyledRG || !rgFrame->HasStyleBSize() || !eligibleRows) { + for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + rowFrame; rowFrame = rowFrame->GetNextRow()) { + nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex()); + // We don't know the final width of the rowGroupFrame yet, so use 0,0 + // as a dummy containerSize here; we'll adjust the row positions at + // the end, after the rowGroup size is finalized. + const nsSize dummyContainerSize; + LogicalRect rowNormalRect(wm, rowFrame->GetNormalRect(), + dummyContainerSize); + nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect(); + // see if there is an eligible row or we distribute to all rows + if (!firstUnStyledRow || !rowFrame->HasStyleBSize() || !eligibleRows) { + float ratio; + if (eligibleRows) { + if (!expandEmptyRows) { + // The amount of additional space each row gets is proportional + // to its bsize + ratio = float(rowNormalRect.BSize(wm)) / float(divisor); + } else { + // empty rows get all the same additional space + ratio = 1.0f / float(eligibleRows); + } + } + else { + // all rows get the same additional space + ratio = 1.0f / float(divisor); + } + // give rows their additional space, except for the last row which + // gets the remainder + nscoord amountForRow = + (rowFrame == lastEligibleRow) + ? aAmount - amountUsed + : NSToCoordRound(((float)(bSizeToDistribute)) * ratio); + amountForRow = std::min(amountForRow, aAmount - amountUsed); + + if (bOriginRow != rowNormalRect.BStart(wm)) { + rowFrame->InvalidateFrameSubtree(); + } + + // update the row bsize + nsRect origRowRect = rowFrame->GetRect(); + nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow; + rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow - + rowNormalRect.BStart(wm))); + rowFrame->SetSize(wm, LogicalSize(wm, rowNormalRect.ISize(wm), + newRowBSize)); + + bOriginRow += newRowBSize + cellSpacingB; + bEndRG += newRowBSize + cellSpacingB; + + amountUsed += amountForRow; + amountUsedByRG += amountForRow; + NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation"); + //rowFrame->DidResize(); + nsTableFrame::RePositionViews(rowFrame); + + nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect, + rowVisualOverflow, false); + } + else { + if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm)) { + rowFrame->InvalidateFrameSubtree(); + rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow - + rowNormalRect.BStart(wm))); + nsTableFrame::RePositionViews(rowFrame); + rowFrame->InvalidateFrameSubtree(); + } + bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB; + bEndRG += rowNormalRect.BSize(wm) + cellSpacingB; + } + } + + if (amountUsed > 0) { + if (rgNormalRect.BStart(wm) != bOriginRG) { + rgFrame->InvalidateFrameSubtree(); + } + + nsRect origRgNormalRect = rgFrame->GetRect(); + rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG - + rgNormalRect.BStart(wm))); + rgFrame->SetSize(wm, LogicalSize(wm, rgNormalRect.ISize(wm), + rgNormalRect.BSize(wm) + amountUsedByRG)); + + nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect, + rgVisualOverflow, false); + } + + // For vertical-rl mode, we needed to position the rows relative to the + // right-hand (block-start) side of the group; but we couldn't do that + // above, as we didn't know the rowGroupFrame's final block size yet. + // So we used a dummyContainerSize of 0,0 earlier, placing the rows to + // the left of the rowGroupFrame's (physical) origin. Now we move them + // all rightwards by its final width. + if (wm.IsVerticalRL()) { + nscoord rgWidth = rgFrame->GetSize().width; + for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + rowFrame; rowFrame = rowFrame->GetNextRow()) { + rowFrame->InvalidateFrameSubtree(); + rowFrame->MovePositionBy(nsPoint(rgWidth, 0)); + nsTableFrame::RePositionViews(rowFrame); + rowFrame->InvalidateFrameSubtree(); + } + } + } + else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) { + rgFrame->InvalidateFrameSubtree(); + rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG - + rgNormalRect.BStart(wm))); + // Make sure child views are properly positioned + nsTableFrame::RePositionViews(rgFrame); + rgFrame->InvalidateFrameSubtree(); + } + bOriginRG = bEndRG; + } + + ResizeCells(*this); +} + +nscoord +nsTableFrame::GetColumnISizeFromFirstInFlow(int32_t aColIndex) +{ + MOZ_ASSERT(this == FirstInFlow()); + nsTableColFrame* colFrame = GetColFrame(aColIndex); + return colFrame ? colFrame->GetFinalISize() : 0; +} + +nscoord +nsTableFrame::GetColSpacing() +{ + if (IsBorderCollapse()) + return 0; + + return StyleTableBorder()->mBorderSpacingCol; +} + +// XXX: could cache this. But be sure to check style changes if you do! +nscoord +nsTableFrame::GetColSpacing(int32_t aColIndex) +{ + NS_ASSERTION(aColIndex >= -1 && aColIndex <= GetColCount(), + "Column index exceeds the bounds of the table"); + // Index is irrelevant for ordinary tables. We check that it falls within + // appropriate bounds to increase confidence of correctness in situations + // where it does matter. + return GetColSpacing(); +} + +nscoord +nsTableFrame::GetColSpacing(int32_t aStartColIndex, + int32_t aEndColIndex) +{ + NS_ASSERTION(aStartColIndex >= -1 && aStartColIndex <= GetColCount(), + "Start column index exceeds the bounds of the table"); + NS_ASSERTION(aEndColIndex >= -1 && aEndColIndex <= GetColCount(), + "End column index exceeds the bounds of the table"); + NS_ASSERTION(aStartColIndex <= aEndColIndex, + "End index must not be less than start index"); + // Only one possible value so just multiply it out. Tables where index + // matters will override this function + return GetColSpacing() * (aEndColIndex - aStartColIndex); +} + +nscoord +nsTableFrame::GetRowSpacing() +{ + if (IsBorderCollapse()) + return 0; + + return StyleTableBorder()->mBorderSpacingRow; +} + +// XXX: could cache this. But be sure to check style changes if you do! +nscoord +nsTableFrame::GetRowSpacing(int32_t aRowIndex) +{ + NS_ASSERTION(aRowIndex >= -1 && aRowIndex <= GetRowCount(), + "Row index exceeds the bounds of the table"); + // Index is irrelevant for ordinary tables. We check that it falls within + // appropriate bounds to increase confidence of correctness in situations + // where it does matter. + return GetRowSpacing(); +} + +nscoord +nsTableFrame::GetRowSpacing(int32_t aStartRowIndex, + int32_t aEndRowIndex) +{ + NS_ASSERTION(aStartRowIndex >= -1 && aStartRowIndex <= GetRowCount(), + "Start row index exceeds the bounds of the table"); + NS_ASSERTION(aEndRowIndex >= -1 && aEndRowIndex <= GetRowCount(), + "End row index exceeds the bounds of the table"); + NS_ASSERTION(aStartRowIndex <= aEndRowIndex, + "End index must not be less than start index"); + // Only one possible value so just multiply it out. Tables where index + // matters will override this function + return GetRowSpacing() * (aEndRowIndex - aStartRowIndex); +} + +/* virtual */ nscoord +nsTableFrame::GetLogicalBaseline(WritingMode aWM) const +{ + nscoord baseline; + if (!GetNaturalBaselineBOffset(aWM, BaselineSharingGroup::eFirst, &baseline)) { + baseline = BSize(aWM); + } + return baseline; +} + +/* virtual */ bool +nsTableFrame::GetNaturalBaselineBOffset(WritingMode aWM, + BaselineSharingGroup aBaselineGroup, + nscoord* aBaseline) const +{ + RowGroupArray orderedRowGroups; + OrderRowGroups(orderedRowGroups); + // XXX not sure if this should be the size of the containing block instead. + nsSize containerSize = mRect.Size(); + auto TableBaseline = [aWM, containerSize] (nsTableRowGroupFrame* aRowGroup, + nsTableRowFrame* aRow) { + nscoord rgBStart = LogicalRect(aWM, aRowGroup->GetNormalRect(), + containerSize).BStart(aWM); + nscoord rowBStart = LogicalRect(aWM, aRow->GetNormalRect(), + containerSize).BStart(aWM); + return rgBStart + rowBStart + aRow->GetRowBaseline(aWM); + }; + if (aBaselineGroup == BaselineSharingGroup::eFirst) { + for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; + nsTableRowFrame* row = rgFrame->GetFirstRow(); + if (row) { + *aBaseline = TableBaseline(rgFrame, row); + return true; + } + } + } else { + for (uint32_t rgIndex = orderedRowGroups.Length(); rgIndex-- > 0;) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; + nsTableRowFrame* row = rgFrame->GetLastRow(); + if (row) { + *aBaseline = BSize(aWM) - TableBaseline(rgFrame, row); + return true; + } + } + } + return false; +} + +/* ----- global methods ----- */ + +nsTableFrame* +NS_NewTableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame) + +nsTableFrame* +nsTableFrame::GetTableFrame(nsIFrame* aFrame) +{ + for (nsIFrame* ancestor = aFrame->GetParent(); ancestor; + ancestor = ancestor->GetParent()) { + if (nsGkAtoms::tableFrame == ancestor->GetType()) { + return static_cast(ancestor); + } + } + NS_RUNTIMEABORT("unable to find table parent"); + return nullptr; +} + +nsTableFrame* +nsTableFrame::GetTableFramePassingThrough(nsIFrame* aMustPassThrough, + nsIFrame* aFrame, + bool* aDidPassThrough) +{ + MOZ_ASSERT(aMustPassThrough == aFrame || + nsLayoutUtils::IsProperAncestorFrame(aMustPassThrough, aFrame), + "aMustPassThrough should be an ancestor"); + + // Retrieve the table frame, and check if we hit aMustPassThrough on the + // way. + *aDidPassThrough = false; + nsTableFrame* tableFrame = nullptr; + for (nsIFrame* ancestor = aFrame; ancestor; ancestor = ancestor->GetParent()) { + if (ancestor == aMustPassThrough) { + *aDidPassThrough = true; + } + if (nsGkAtoms::tableFrame == ancestor->GetType()) { + tableFrame = static_cast(ancestor); + break; + } + } + + MOZ_ASSERT(tableFrame, "Should have a table frame here"); + return tableFrame; +} + +bool +nsTableFrame::IsAutoBSize(WritingMode aWM) +{ + const nsStyleCoord &bsize = StylePosition()->BSize(aWM); + // Don't consider calc() here like this quirk for percent. + return bsize.GetUnit() == eStyleUnit_Auto || + (bsize.GetUnit() == eStyleUnit_Percent && + bsize.GetPercentValue() <= 0.0f); +} + +nscoord +nsTableFrame::CalcBorderBoxBSize(const ReflowInput& aState) +{ + nscoord bSize = aState.ComputedBSize(); + if (NS_AUTOHEIGHT != bSize) { + WritingMode wm = aState.GetWritingMode(); + LogicalMargin borderPadding = GetChildAreaOffset(wm, &aState); + bSize += borderPadding.BStartEnd(wm); + } + bSize = std::max(0, bSize); + + return bSize; +} + +bool +nsTableFrame::IsAutoLayout() +{ + if (StyleTable()->mLayoutStrategy == NS_STYLE_TABLE_LAYOUT_AUTO) + return true; + // a fixed-layout inline-table must have a inline size + // and tables with inline size set to '-moz-max-content' must be + // auto-layout (at least as long as + // FixedTableLayoutStrategy::GetPrefISize returns nscoord_MAX) + const nsStyleCoord &iSize = StylePosition()->ISize(GetWritingMode()); + return (iSize.GetUnit() == eStyleUnit_Auto) || + (iSize.GetUnit() == eStyleUnit_Enumerated && + iSize.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("Table"), aResult); +} +#endif + +// Find the closet sibling before aPriorChildFrame (including aPriorChildFrame) that +// is of type aChildType +nsIFrame* +nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame, + nsIFrame* aPriorChildFrame, + nsIAtom* aChildType) +{ + nsIFrame* result = nullptr; + if (!aPriorChildFrame) { + return result; + } + if (aChildType == aPriorChildFrame->GetType()) { + return aPriorChildFrame; + } + + // aPriorChildFrame is not of type aChildType, so we need start from + // the beginnng and find the closest one + nsIFrame* lastMatchingFrame = nullptr; + nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild(); + while (childFrame && (childFrame != aPriorChildFrame)) { + if (aChildType == childFrame->GetType()) { + lastMatchingFrame = childFrame; + } + childFrame = childFrame->GetNextSibling(); + } + return lastMatchingFrame; +} + +#ifdef DEBUG +void +nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame) +{ + if (!aKidFrame) + return; + + for (nsIFrame* cFrame : aKidFrame->PrincipalChildList()) { + nsTableRowFrame *rowFrame = do_QueryFrame(cFrame); + if (rowFrame) { + printf("row(%d)=%p ", rowFrame->GetRowIndex(), + static_cast(rowFrame)); + for (nsIFrame* childFrame : cFrame->PrincipalChildList()) { + nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); + if (cellFrame) { + int32_t colIndex; + cellFrame->GetColIndex(colIndex); + printf("cell(%d)=%p ", colIndex, static_cast(childFrame)); + } + } + printf("\n"); + } + else { + DumpRowGroup(rowFrame); + } + } +} + +void +nsTableFrame::Dump(bool aDumpRows, + bool aDumpCols, + bool aDumpCellMap) +{ + printf("***START TABLE DUMP*** \n"); + // dump the columns widths array + printf("mColWidths="); + int32_t numCols = GetColCount(); + int32_t colIdx; + nsTableFrame* fif = static_cast(FirstInFlow()); + for (colIdx = 0; colIdx < numCols; colIdx++) { + printf("%d ", fif->GetColumnISizeFromFirstInFlow(colIdx)); + } + printf("\n"); + + if (aDumpRows) { + nsIFrame* kidFrame = mFrames.FirstChild(); + while (kidFrame) { + DumpRowGroup(kidFrame); + kidFrame = kidFrame->GetNextSibling(); + } + } + + if (aDumpCols) { + // output col frame cache + printf("\n col frame cache ->"); + for (colIdx = 0; colIdx < numCols; colIdx++) { + nsTableColFrame* colFrame = mColFrames.ElementAt(colIdx); + if (0 == (colIdx % 8)) { + printf("\n"); + } + printf ("%d=%p ", colIdx, static_cast(colFrame)); + nsTableColType colType = colFrame->GetColType(); + switch (colType) { + case eColContent: + printf(" content "); + break; + case eColAnonymousCol: + printf(" anonymous-column "); + break; + case eColAnonymousColGroup: + printf(" anonymous-colgroup "); + break; + case eColAnonymousCell: + printf(" anonymous-cell "); + break; + } + } + printf("\n colgroups->"); + for (nsIFrame* childFrame : mColGroups) { + if (nsGkAtoms::tableColGroupFrame == childFrame->GetType()) { + nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame *)childFrame; + colGroupFrame->Dump(1); + } + } + for (colIdx = 0; colIdx < numCols; colIdx++) { + printf("\n"); + nsTableColFrame* colFrame = GetColFrame(colIdx); + colFrame->Dump(1); + } + } + if (aDumpCellMap) { + nsTableCellMap* cellMap = GetCellMap(); + cellMap->Dump(); + } + printf(" ***END TABLE DUMP*** \n"); +} +#endif + +bool +nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex) const +{ + // Since fixed-layout tables should not have their column sizes change + // as they load, we assume that all columns are significant. + if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed) + return true; + // the first column is always significant + if (aColIndex == 0) + return true; + nsTableCellMap* cellMap = GetCellMap(); + if (!cellMap) + return false; + return cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0; +} + +/******************************************************************************** + * Collapsing Borders + * + * The CSS spec says to resolve border conflicts in this order: + * 1) any border with the style HIDDEN wins + * 2) the widest border with a style that is not NONE wins + * 3) the border styles are ranked in this order, highest to lowest precedence: + * double, solid, dashed, dotted, ridge, outset, groove, inset + * 4) borders that are of equal width and style (differ only in color) have this precedence: + * cell, row, rowgroup, col, colgroup, table + * 5) if all border styles are NONE, then that's the computed border style. + *******************************************************************************/ + +#ifdef DEBUG +#define VerifyNonNegativeDamageRect(r) \ + NS_ASSERTION((r).StartCol() >= 0, "negative col index"); \ + NS_ASSERTION((r).StartRow() >= 0, "negative row index"); \ + NS_ASSERTION((r).ColCount() >= 0, "negative cols damage"); \ + NS_ASSERTION((r).RowCount() >= 0, "negative rows damage"); +#define VerifyDamageRect(r) \ + VerifyNonNegativeDamageRect(r); \ + NS_ASSERTION((r).EndCol() <= GetColCount(), \ + "cols damage extends outside table"); \ + NS_ASSERTION((r).EndRow() <= GetRowCount(), \ + "rows damage extends outside table"); +#endif + +void +nsTableFrame::AddBCDamageArea(const TableArea& aValue) +{ + NS_ASSERTION(IsBorderCollapse(), "invalid AddBCDamageArea call"); +#ifdef DEBUG + VerifyDamageRect(aValue); +#endif + + SetNeedToCalcBCBorders(true); + // Get the property + BCPropertyData* value = GetBCProperty(true); + if (value) { +#ifdef DEBUG + VerifyNonNegativeDamageRect(value->mDamageArea); +#endif + // Clamp the old damage area to the current table area in case it shrunk. + int32_t cols = GetColCount(); + if (value->mDamageArea.EndCol() > cols) { + if (value->mDamageArea.StartCol() > cols) { + value->mDamageArea.StartCol() = cols; + value->mDamageArea.ColCount() = 0; + } + else { + value->mDamageArea.ColCount() = cols - value->mDamageArea.StartCol(); + } + } + int32_t rows = GetRowCount(); + if (value->mDamageArea.EndRow() > rows) { + if (value->mDamageArea.StartRow() > rows) { + value->mDamageArea.StartRow() = rows; + value->mDamageArea.RowCount() = 0; + } + else { + value->mDamageArea.RowCount() = rows - value->mDamageArea.StartRow(); + } + } + + // Construct a union of the new and old damage areas. + value->mDamageArea.UnionArea(value->mDamageArea, aValue); + } +} + + +void +nsTableFrame::SetFullBCDamageArea() +{ + NS_ASSERTION(IsBorderCollapse(), "invalid SetFullBCDamageArea call"); + + SetNeedToCalcBCBorders(true); + + BCPropertyData* value = GetBCProperty(true); + if (value) { + value->mDamageArea = TableArea(0, 0, GetColCount(), GetRowCount()); + } +} + + +/* BCCellBorder represents a border segment which can be either an inline-dir + * or a block-dir segment. For each segment we need to know the color, width, + * style, who owns it and how long it is in cellmap coordinates. + * Ownership of these segments is important to calculate which corners should + * be bevelled. This structure has dual use, its used first to compute the + * dominant border for inline-dir and block-dir segments and to store the + * preliminary computed border results in the BCCellBorders structure. + * This temporary storage is not symmetric with respect to inline-dir and + * block-dir border segments, its always column oriented. For each column in + * the cellmap there is a temporary stored block-dir and inline-dir segment. + * XXX_Bernd this asymmetry is the root of those rowspan bc border errors + */ +struct BCCellBorder +{ + BCCellBorder() { Reset(0, 1); } + void Reset(uint32_t aRowIndex, uint32_t aRowSpan); + nscolor color; // border segment color + BCPixelSize width; // border segment width in pixel coordinates !! + uint8_t style; // border segment style, possible values are defined + // in nsStyleConsts.h as NS_STYLE_BORDER_STYLE_* + BCBorderOwner owner; // border segment owner, possible values are defined + // in celldata.h. In the cellmap for each border + // segment we store the owner and later when + // painting we know the owner and can retrieve the + // style info from the corresponding frame + int32_t rowIndex; // rowIndex of temporary stored inline-dir border + // segments relative to the table + int32_t rowSpan; // row span of temporary stored inline-dir border + // segments +}; + +void +BCCellBorder::Reset(uint32_t aRowIndex, + uint32_t aRowSpan) +{ + style = NS_STYLE_BORDER_STYLE_NONE; + color = 0; + width = 0; + owner = eTableOwner; + rowIndex = aRowIndex; + rowSpan = aRowSpan; +} + +class BCMapCellIterator; + +/***************************************************************** + * BCMapCellInfo + * This structure stores information about the cellmap and all involved + * table related frames that are used during the computation of winning borders + * in CalcBCBorders so that they do need to be looked up again and again when + * iterating over the cells. + ****************************************************************/ +struct BCMapCellInfo +{ + explicit BCMapCellInfo(nsTableFrame* aTableFrame); + void ResetCellInfo(); + void SetInfo(nsTableRowFrame* aNewRow, + int32_t aColIndex, + BCCellData* aCellData, + BCMapCellIterator* aIter, + nsCellMap* aCellMap = nullptr); + // The BCMapCellInfo has functions to set the continous + // border widths (see nsTablePainter.cpp for a description of the continous + // borders concept). The widths are computed inside these functions based on + // the current position inside the table and the cached frames that correspond + // to this position. The widths are stored in member variables of the internal + // table frames. + void SetTableBStartIStartContBCBorder(); + void SetRowGroupIStartContBCBorder(); + void SetRowGroupIEndContBCBorder(); + void SetRowGroupBEndContBCBorder(); + void SetRowIStartContBCBorder(); + void SetRowIEndContBCBorder(); + void SetColumnBStartIEndContBCBorder(); + void SetColumnBEndContBCBorder(); + void SetColGroupBEndContBCBorder(); + void SetInnerRowGroupBEndContBCBorder(const nsIFrame* aNextRowGroup, + nsTableRowFrame* aNextRow); + + // functions to set the border widths on the table related frames, where the + // knowledge about the current position in the table is used. + void SetTableBStartBorderWidth(BCPixelSize aWidth); + void SetTableIStartBorderWidth(int32_t aRowB, BCPixelSize aWidth); + void SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth); + void SetTableBEndBorderWidth(BCPixelSize aWidth); + void SetIStartBorderWidths(BCPixelSize aWidth); + void SetIEndBorderWidths(BCPixelSize aWidth); + void SetBStartBorderWidths(BCPixelSize aWidth); + void SetBEndBorderWidths(BCPixelSize aWidth); + + // functions to compute the borders; they depend on the + // knowledge about the current position in the table. The edge functions + // should be called if a table edge is involved, otherwise the internal + // functions should be called. + BCCellBorder GetBStartEdgeBorder(); + BCCellBorder GetBEndEdgeBorder(); + BCCellBorder GetIStartEdgeBorder(); + BCCellBorder GetIEndEdgeBorder(); + BCCellBorder GetIEndInternalBorder(); + BCCellBorder GetIStartInternalBorder(); + BCCellBorder GetBStartInternalBorder(); + BCCellBorder GetBEndInternalBorder(); + + // functions to set the internal position information + void SetColumn(int32_t aColX); + // Increment the row as we loop over the rows of a rowspan + void IncrementRow(bool aResetToBStartRowOfCell = false); + + // Helper functions to get extent of the cell + int32_t GetCellEndRowIndex() const; + int32_t GetCellEndColIndex() const; + + // storage of table information + nsTableFrame* mTableFrame; + int32_t mNumTableRows; + int32_t mNumTableCols; + BCPropertyData* mTableBCData; + WritingMode mTableWM; + + // a cell can only belong to one rowgroup + nsTableRowGroupFrame* mRowGroup; + + // a cell with a rowspan has a bstart and a bend row, and rows in between + nsTableRowFrame* mStartRow; + nsTableRowFrame* mEndRow; + nsTableRowFrame* mCurrentRowFrame; + + // a cell with a colspan has an istart and iend column and columns in between + // they can belong to different colgroups + nsTableColGroupFrame* mColGroup; + nsTableColGroupFrame* mCurrentColGroupFrame; + + nsTableColFrame* mStartCol; + nsTableColFrame* mEndCol; + nsTableColFrame* mCurrentColFrame; + + // cell information + BCCellData* mCellData; + nsBCTableCellFrame* mCell; + + int32_t mRowIndex; + int32_t mRowSpan; + int32_t mColIndex; + int32_t mColSpan; + + // flags to describe the position of the cell with respect to the row- and + // colgroups, for instance mRgAtStart documents that the bStart cell border hits + // a rowgroup border + bool mRgAtStart; + bool mRgAtEnd; + bool mCgAtStart; + bool mCgAtEnd; + +}; + + +BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame) + : mTableFrame(aTableFrame) + , mNumTableRows(aTableFrame->GetRowCount()) + , mNumTableCols(aTableFrame->GetColCount()) + , mTableBCData(mTableFrame->Properties().Get(TableBCProperty())) + , mTableWM(aTableFrame->StyleContext()) +{ + ResetCellInfo(); +} + +void +BCMapCellInfo::ResetCellInfo() +{ + mCellData = nullptr; + mRowGroup = nullptr; + mStartRow = nullptr; + mEndRow = nullptr; + mColGroup = nullptr; + mStartCol = nullptr; + mEndCol = nullptr; + mCell = nullptr; + mRowIndex = mRowSpan = mColIndex = mColSpan = 0; + mRgAtStart = mRgAtEnd = mCgAtStart = mCgAtEnd = false; +} + +inline int32_t +BCMapCellInfo::GetCellEndRowIndex() const +{ + return mRowIndex + mRowSpan - 1; +} + +inline int32_t +BCMapCellInfo::GetCellEndColIndex() const +{ + return mColIndex + mColSpan - 1; +} + + +class BCMapCellIterator +{ +public: + BCMapCellIterator(nsTableFrame* aTableFrame, + const TableArea& aDamageArea); + + void First(BCMapCellInfo& aMapCellInfo); + + void Next(BCMapCellInfo& aMapCellInfo); + + void PeekIEnd(BCMapCellInfo& aRefInfo, + uint32_t aRowIndex, + BCMapCellInfo& aAjaInfo); + + void PeekBEnd(BCMapCellInfo& aRefInfo, + uint32_t aColIndex, + BCMapCellInfo& aAjaInfo); + + bool IsNewRow() { return mIsNewRow; } + + nsTableRowFrame* GetPrevRow() const { return mPrevRow; } + nsTableRowFrame* GetCurrentRow() const { return mRow; } + nsTableRowGroupFrame* GetCurrentRowGroup() const { return mRowGroup; } + + int32_t mRowGroupStart; + int32_t mRowGroupEnd; + bool mAtEnd; + nsCellMap* mCellMap; + +private: + bool SetNewRow(nsTableRowFrame* row = nullptr); + bool SetNewRowGroup(bool aFindFirstDamagedRow); + + nsTableFrame* mTableFrame; + nsTableCellMap* mTableCellMap; + nsTableFrame::RowGroupArray mRowGroups; + nsTableRowGroupFrame* mRowGroup; + int32_t mRowGroupIndex; + uint32_t mNumTableRows; + nsTableRowFrame* mRow; + nsTableRowFrame* mPrevRow; + bool mIsNewRow; + int32_t mRowIndex; + uint32_t mNumTableCols; + int32_t mColIndex; + nsPoint mAreaStart; // These are not really points in the usual + nsPoint mAreaEnd; // sense; they're column/row coordinates + // in the cell map. +}; + +BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame, + const TableArea& aDamageArea) + : mTableFrame(aTableFrame) +{ + mTableCellMap = aTableFrame->GetCellMap(); + + mAreaStart.x = aDamageArea.StartCol(); + mAreaStart.y = aDamageArea.StartRow(); + mAreaEnd.x = aDamageArea.EndCol() - 1; + mAreaEnd.y = aDamageArea.EndRow() - 1; + + mNumTableRows = mTableFrame->GetRowCount(); + mRow = nullptr; + mRowIndex = 0; + mNumTableCols = mTableFrame->GetColCount(); + mColIndex = 0; + mRowGroupIndex = -1; + + // Get the ordered row groups + aTableFrame->OrderRowGroups(mRowGroups); + + mAtEnd = true; // gets reset when First() is called +} + +// fill fields that we need for border collapse computation on a given cell +void +BCMapCellInfo::SetInfo(nsTableRowFrame* aNewRow, + int32_t aColIndex, + BCCellData* aCellData, + BCMapCellIterator* aIter, + nsCellMap* aCellMap) +{ + // fill the cell information + mCellData = aCellData; + mColIndex = aColIndex; + + // initialize the row information if it was not previously set for cells in + // this row + mRowIndex = 0; + if (aNewRow) { + mStartRow = aNewRow; + mRowIndex = aNewRow->GetRowIndex(); + } + + // fill cell frame info and row information + mCell = nullptr; + mRowSpan = 1; + mColSpan = 1; + if (aCellData) { + mCell = static_cast(aCellData->GetCellFrame()); + if (mCell) { + if (!mStartRow) { + mStartRow = mCell->GetTableRowFrame(); + if (!mStartRow) ABORT0(); + mRowIndex = mStartRow->GetRowIndex(); + } + mColSpan = mTableFrame->GetEffectiveColSpan(*mCell, aCellMap); + mRowSpan = mTableFrame->GetEffectiveRowSpan(*mCell, aCellMap); + } + } + + if (!mStartRow) { + mStartRow = aIter->GetCurrentRow(); + } + if (1 == mRowSpan) { + mEndRow = mStartRow; + } + else { + mEndRow = mStartRow->GetNextRow(); + if (mEndRow) { + for (int32_t span = 2; mEndRow && span < mRowSpan; span++) { + mEndRow = mEndRow->GetNextRow(); + } + NS_ASSERTION(mEndRow, "spanned row not found"); + } + else { + NS_ERROR("error in cell map"); + mRowSpan = 1; + mEndRow = mStartRow; + } + } + // row group frame info + // try to reuse the rgStart and rgEnd from the iterator as calls to + // GetRowCount() are computationally expensive and should be avoided if + // possible + uint32_t rgStart = aIter->mRowGroupStart; + uint32_t rgEnd = aIter->mRowGroupEnd; + mRowGroup = mStartRow->GetTableRowGroupFrame(); + if (mRowGroup != aIter->GetCurrentRowGroup()) { + rgStart = mRowGroup->GetStartRowIndex(); + rgEnd = rgStart + mRowGroup->GetRowCount() - 1; + } + uint32_t rowIndex = mStartRow->GetRowIndex(); + mRgAtStart = rgStart == rowIndex; + mRgAtEnd = rgEnd == rowIndex + mRowSpan - 1; + + // col frame info + mStartCol = mTableFrame->GetColFrame(aColIndex); + if (!mStartCol) ABORT0(); + + mEndCol = mStartCol; + if (mColSpan > 1) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(aColIndex + + mColSpan -1); + if (!colFrame) ABORT0(); + mEndCol = colFrame; + } + + // col group frame info + mColGroup = mStartCol->GetTableColGroupFrame(); + int32_t cgStart = mColGroup->GetStartColumnIndex(); + int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1); + mCgAtStart = cgStart == aColIndex; + mCgAtEnd = cgEnd == aColIndex + mColSpan - 1; +} + +bool +BCMapCellIterator::SetNewRow(nsTableRowFrame* aRow) +{ + mAtEnd = true; + mPrevRow = mRow; + if (aRow) { + mRow = aRow; + } + else if (mRow) { + mRow = mRow->GetNextRow(); + } + if (mRow) { + mRowIndex = mRow->GetRowIndex(); + // get to the first entry with an originating cell + int32_t rgRowIndex = mRowIndex - mRowGroupStart; + if (uint32_t(rgRowIndex) >= mCellMap->mRows.Length()) + ABORT1(false); + const nsCellMap::CellDataArray& row = mCellMap->mRows[rgRowIndex]; + + for (mColIndex = mAreaStart.x; mColIndex <= mAreaEnd.x; mColIndex++) { + CellData* cellData = row.SafeElementAt(mColIndex); + if (!cellData) { // add a dead cell data + TableArea damageArea; + cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex, + false, 0, damageArea); + if (!cellData) ABORT1(false); + } + if (cellData && (cellData->IsOrig() || cellData->IsDead())) { + break; + } + } + mIsNewRow = true; + mAtEnd = false; + } + else ABORT1(false); + + return !mAtEnd; +} + +bool +BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow) +{ + mAtEnd = true; + int32_t numRowGroups = mRowGroups.Length(); + mCellMap = nullptr; + for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) { + mRowGroup = mRowGroups[mRowGroupIndex]; + int32_t rowCount = mRowGroup->GetRowCount(); + mRowGroupStart = mRowGroup->GetStartRowIndex(); + mRowGroupEnd = mRowGroupStart + rowCount - 1; + if (rowCount > 0) { + mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap); + if (!mCellMap) ABORT1(false); + nsTableRowFrame* firstRow = mRowGroup->GetFirstRow(); + if (aFindFirstDamagedRow) { + if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) { + // the damage area starts in the row group + if (aFindFirstDamagedRow) { + // find the correct first damaged row + int32_t numRows = mAreaStart.y - mRowGroupStart; + for (int32_t i = 0; i < numRows; i++) { + firstRow = firstRow->GetNextRow(); + if (!firstRow) ABORT1(false); + } + } + } + else { + continue; + } + } + if (SetNewRow(firstRow)) { // sets mAtEnd + break; + } + } + } + + return !mAtEnd; +} + +void +BCMapCellIterator::First(BCMapCellInfo& aMapInfo) +{ + aMapInfo.ResetCellInfo(); + + SetNewRowGroup(true); // sets mAtEnd + while (!mAtEnd) { + if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) { + BCCellData* cellData = + static_cast(mCellMap->GetDataAt(mAreaStart.y - + mRowGroupStart, + mAreaStart.x)); + if (cellData && (cellData->IsOrig() || cellData->IsDead())) { + aMapInfo.SetInfo(mRow, mAreaStart.x, cellData, this); + return; + } + else { + NS_ASSERTION(((0 == mAreaStart.x) && (mRowGroupStart == mAreaStart.y)) , + "damage area expanded incorrectly"); + } + } + SetNewRowGroup(true); // sets mAtEnd + } +} + +void +BCMapCellIterator::Next(BCMapCellInfo& aMapInfo) +{ + if (mAtEnd) ABORT0(); + aMapInfo.ResetCellInfo(); + + mIsNewRow = false; + mColIndex++; + while ((mRowIndex <= mAreaEnd.y) && !mAtEnd) { + for (; mColIndex <= mAreaEnd.x; mColIndex++) { + int32_t rgRowIndex = mRowIndex - mRowGroupStart; + BCCellData* cellData = + static_cast(mCellMap->GetDataAt(rgRowIndex, mColIndex)); + if (!cellData) { // add a dead cell data + TableArea damageArea; + cellData = + static_cast(mCellMap->AppendCell(*mTableCellMap, nullptr, + rgRowIndex, false, 0, + damageArea)); + if (!cellData) ABORT0(); + } + if (cellData && (cellData->IsOrig() || cellData->IsDead())) { + aMapInfo.SetInfo(mRow, mColIndex, cellData, this); + return; + } + } + if (mRowIndex >= mRowGroupEnd) { + SetNewRowGroup(false); // could set mAtEnd + } + else { + SetNewRow(); // could set mAtEnd + } + } + mAtEnd = true; +} + +void +BCMapCellIterator::PeekIEnd(BCMapCellInfo& aRefInfo, + uint32_t aRowIndex, + BCMapCellInfo& aAjaInfo) +{ + aAjaInfo.ResetCellInfo(); + int32_t colIndex = aRefInfo.mColIndex + aRefInfo.mColSpan; + uint32_t rgRowIndex = aRowIndex - mRowGroupStart; + + BCCellData* cellData = + static_cast(mCellMap->GetDataAt(rgRowIndex, colIndex)); + if (!cellData) { // add a dead cell data + NS_ASSERTION(colIndex < mTableCellMap->GetColCount(), "program error"); + TableArea damageArea; + cellData = + static_cast(mCellMap->AppendCell(*mTableCellMap, nullptr, + rgRowIndex, false, 0, + damageArea)); + if (!cellData) ABORT0(); + } + nsTableRowFrame* row = nullptr; + if (cellData->IsRowSpan()) { + rgRowIndex -= cellData->GetRowSpanOffset(); + cellData = + static_cast(mCellMap->GetDataAt(rgRowIndex, colIndex)); + if (!cellData) + ABORT0(); + } + else { + row = mRow; + } + aAjaInfo.SetInfo(row, colIndex, cellData, this); +} + +void +BCMapCellIterator::PeekBEnd(BCMapCellInfo& aRefInfo, + uint32_t aColIndex, + BCMapCellInfo& aAjaInfo) +{ + aAjaInfo.ResetCellInfo(); + int32_t rowIndex = aRefInfo.mRowIndex + aRefInfo.mRowSpan; + int32_t rgRowIndex = rowIndex - mRowGroupStart; + nsTableRowGroupFrame* rg = mRowGroup; + nsCellMap* cellMap = mCellMap; + nsTableRowFrame* nextRow = nullptr; + if (rowIndex > mRowGroupEnd) { + int32_t nextRgIndex = mRowGroupIndex; + do { + nextRgIndex++; + rg = mRowGroups.SafeElementAt(nextRgIndex); + if (rg) { + cellMap = mTableCellMap->GetMapFor(rg, cellMap); if (!cellMap) ABORT0(); + rgRowIndex = 0; + nextRow = rg->GetFirstRow(); + } + } + while (rg && !nextRow); + if(!rg) return; + } + else { + // get the row within the same row group + nextRow = mRow; + for (int32_t i = 0; i < aRefInfo.mRowSpan; i++) { + nextRow = nextRow->GetNextRow(); if (!nextRow) ABORT0(); + } + } + + BCCellData* cellData = + static_cast(cellMap->GetDataAt(rgRowIndex, aColIndex)); + if (!cellData) { // add a dead cell data + NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error"); + TableArea damageArea; + cellData = + static_cast(cellMap->AppendCell(*mTableCellMap, nullptr, + rgRowIndex, false, 0, + damageArea)); + if (!cellData) ABORT0(); + } + if (cellData->IsColSpan()) { + aColIndex -= cellData->GetColSpanOffset(); + cellData = + static_cast(cellMap->GetDataAt(rgRowIndex, aColIndex)); + } + aAjaInfo.SetInfo(nextRow, aColIndex, cellData, this, cellMap); +} + +// Assign priorities to border styles. For example, styleToPriority(NS_STYLE_BORDER_STYLE_SOLID) +// will return the priority of NS_STYLE_BORDER_STYLE_SOLID. +static uint8_t styleToPriority[13] = { 0, // NS_STYLE_BORDER_STYLE_NONE + 2, // NS_STYLE_BORDER_STYLE_GROOVE + 4, // NS_STYLE_BORDER_STYLE_RIDGE + 5, // NS_STYLE_BORDER_STYLE_DOTTED + 6, // NS_STYLE_BORDER_STYLE_DASHED + 7, // NS_STYLE_BORDER_STYLE_SOLID + 8, // NS_STYLE_BORDER_STYLE_DOUBLE + 1, // NS_STYLE_BORDER_STYLE_INSET + 3, // NS_STYLE_BORDER_STYLE_OUTSET + 9 };// NS_STYLE_BORDER_STYLE_HIDDEN +// priority rules follow CSS 2.1 spec +// 'hidden', 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', +// and the lowest: 'inset'. none is even weaker +#define CELL_CORNER true + +/** return the border style, border color and optionally the width in + * pixel for a given frame and side + * @param aFrame - query the info for this frame + * @param aTableWM - the writing-mode of the frame + * @param aSide - the side of the frame + * @param aStyle - the border style + * @param aColor - the border color + * @param aWidth - the border width in px + */ +static void +GetColorAndStyle(const nsIFrame* aFrame, + WritingMode aTableWM, + LogicalSide aSide, + uint8_t* aStyle, + nscolor* aColor, + BCPixelSize* aWidth = nullptr) +{ + NS_PRECONDITION(aFrame, "null frame"); + NS_PRECONDITION(aStyle && aColor, "null argument"); + // initialize out arg + *aColor = 0; + if (aWidth) { + *aWidth = 0; + } + + const nsStyleBorder* styleData = aFrame->StyleBorder(); + mozilla::Side physicalSide = aTableWM.PhysicalSide(aSide); + *aStyle = styleData->GetBorderStyle(physicalSide); + + if ((NS_STYLE_BORDER_STYLE_NONE == *aStyle) || + (NS_STYLE_BORDER_STYLE_HIDDEN == *aStyle)) { + return; + } + *aColor = aFrame->StyleContext()->GetVisitedDependentColor( + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[physicalSide]); + + if (aWidth) { + nscoord width = styleData->GetComputedBorderWidth(physicalSide); + *aWidth = nsPresContext::AppUnitsToIntCSSPixels(width); + } +} + +/** coerce the paint style as required by CSS2.1 + * @param aFrame - query the info for this frame + * @param aTableWM - the writing mode of the frame + * @param aSide - the side of the frame + * @param aStyle - the border style + * @param aColor - the border color + */ +static void +GetPaintStyleInfo(const nsIFrame* aFrame, + WritingMode aTableWM, + LogicalSide aSide, + uint8_t* aStyle, + nscolor* aColor) +{ + GetColorAndStyle(aFrame, aTableWM, aSide, aStyle, aColor); + if (NS_STYLE_BORDER_STYLE_INSET == *aStyle) { + *aStyle = NS_STYLE_BORDER_STYLE_RIDGE; + } else if (NS_STYLE_BORDER_STYLE_OUTSET == *aStyle) { + *aStyle = NS_STYLE_BORDER_STYLE_GROOVE; + } +} + +class nsDelayedCalcBCBorders : public Runnable { +public: + explicit nsDelayedCalcBCBorders(nsIFrame* aFrame) : + mFrame(aFrame) {} + + NS_IMETHOD Run() override { + if (mFrame) { + nsTableFrame* tableFrame = static_cast (mFrame.GetFrame()); + if (tableFrame->NeedToCalcBCBorders()) { + tableFrame->CalcBCBorders(); + } + } + return NS_OK; + } +private: + nsWeakFrame mFrame; +}; + +bool +nsTableFrame::BCRecalcNeeded(nsStyleContext* aOldStyleContext, + nsStyleContext* aNewStyleContext) +{ + // Attention: the old style context is the one we're forgetting, + // and hence possibly completely bogus for GetStyle* purposes. + // We use PeekStyleData instead. + + const nsStyleBorder* oldStyleData = aOldStyleContext->PeekStyleBorder(); + if (!oldStyleData) + return false; + + const nsStyleBorder* newStyleData = aNewStyleContext->StyleBorder(); + nsChangeHint change = newStyleData->CalcDifference(*oldStyleData); + if (!change) + return false; + if (change & nsChangeHint_NeedReflow) + return true; // the caller only needs to mark the bc damage area + if (change & nsChangeHint_RepaintFrame) { + // we need to recompute the borders and the caller needs to mark + // the bc damage area + // XXX In principle this should only be necessary for border style changes + // However the bc painting code tries to maximize the drawn border segments + // so it stores in the cellmap where a new border segment starts and this + // introduces a unwanted cellmap data dependence on color + nsCOMPtr evt = new nsDelayedCalcBCBorders(this); + NS_DispatchToCurrentThread(evt); + return true; + } + return false; +} + + +// Compare two border segments, this comparison depends whether the two +// segments meet at a corner and whether the second segment is inline-dir. +// The return value is whichever of aBorder1 or aBorder2 dominates. +static const BCCellBorder& +CompareBorders(bool aIsCorner, // Pass true for corner calculations + const BCCellBorder& aBorder1, + const BCCellBorder& aBorder2, + bool aSecondIsInlineDir, + bool* aFirstDominates = nullptr) +{ + bool firstDominates = true; + + if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder1.style) { + firstDominates = (aIsCorner) ? false : true; + } + else if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder2.style) { + firstDominates = (aIsCorner) ? true : false; + } + else if (aBorder1.width < aBorder2.width) { + firstDominates = false; + } + else if (aBorder1.width == aBorder2.width) { + if (styleToPriority[aBorder1.style] < styleToPriority[aBorder2.style]) { + firstDominates = false; + } + else if (styleToPriority[aBorder1.style] == styleToPriority[aBorder2.style]) { + if (aBorder1.owner == aBorder2.owner) { + firstDominates = !aSecondIsInlineDir; + } + else if (aBorder1.owner < aBorder2.owner) { + firstDominates = false; + } + } + } + + if (aFirstDominates) + *aFirstDominates = firstDominates; + + if (firstDominates) + return aBorder1; + return aBorder2; +} + +/** calc the dominant border by considering the table, row/col group, row/col, + * cell. + * Depending on whether the side is block-dir or inline-dir and whether + * adjacent frames are taken into account the ownership of a single border + * segment is defined. The return value is the dominating border + * The cellmap stores only bstart and istart borders for each cellmap position. + * If the cell border is owned by the cell that is istart-wards of the border + * it will be an adjacent owner aka eAjaCellOwner. See celldata.h for the other + * scenarios with a adjacent owner. + * @param xxxFrame - the frame for style information, might be zero if + * it should not be considered + * @param aTableWM - the writing mode of the frame + * @param aSide - side of the frames that should be considered + * @param aAja - the border comparison takes place from the point of + * a frame that is adjacent to the cellmap entry, for + * when a cell owns its lower border it will be the + * adjacent owner as in the cellmap only bstart and + * istart borders are stored. + */ +static BCCellBorder +CompareBorders(const nsIFrame* aTableFrame, + const nsIFrame* aColGroupFrame, + const nsIFrame* aColFrame, + const nsIFrame* aRowGroupFrame, + const nsIFrame* aRowFrame, + const nsIFrame* aCellFrame, + WritingMode aTableWM, + LogicalSide aSide, + bool aAja) +{ + BCCellBorder border, tempBorder; + bool inlineAxis = IsBlock(aSide); + + // start with the table as dominant if present + if (aTableFrame) { + GetColorAndStyle(aTableFrame, aTableWM, aSide, + &border.style, &border.color, &border.width); + border.owner = eTableOwner; + if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { + return border; + } + } + // see if the colgroup is dominant + if (aColGroupFrame) { + GetColorAndStyle(aColGroupFrame, aTableWM, aSide, + &tempBorder.style, &tempBorder.color, &tempBorder.width); + tempBorder.owner = aAja && !inlineAxis ? eAjaColGroupOwner : eColGroupOwner; + // pass here and below false for aSecondIsInlineDir as it is only used for corner calculations. + border = CompareBorders(!CELL_CORNER, border, tempBorder, false); + if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { + return border; + } + } + // see if the col is dominant + if (aColFrame) { + GetColorAndStyle(aColFrame, aTableWM, aSide, + &tempBorder.style, &tempBorder.color, &tempBorder.width); + tempBorder.owner = aAja && !inlineAxis ? eAjaColOwner : eColOwner; + border = CompareBorders(!CELL_CORNER, border, tempBorder, false); + if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { + return border; + } + } + // see if the rowgroup is dominant + if (aRowGroupFrame) { + GetColorAndStyle(aRowGroupFrame, aTableWM, aSide, + &tempBorder.style, &tempBorder.color, &tempBorder.width); + tempBorder.owner = aAja && inlineAxis ? eAjaRowGroupOwner : eRowGroupOwner; + border = CompareBorders(!CELL_CORNER, border, tempBorder, false); + if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { + return border; + } + } + // see if the row is dominant + if (aRowFrame) { + GetColorAndStyle(aRowFrame, aTableWM, aSide, + &tempBorder.style, &tempBorder.color, &tempBorder.width); + tempBorder.owner = aAja && inlineAxis ? eAjaRowOwner : eRowOwner; + border = CompareBorders(!CELL_CORNER, border, tempBorder, false); + if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { + return border; + } + } + // see if the cell is dominant + if (aCellFrame) { + GetColorAndStyle(aCellFrame, aTableWM, aSide, + &tempBorder.style, &tempBorder.color, &tempBorder.width); + tempBorder.owner = aAja ? eAjaCellOwner : eCellOwner; + border = CompareBorders(!CELL_CORNER, border, tempBorder, false); + } + return border; +} + +static bool +Perpendicular(mozilla::LogicalSide aSide1, + mozilla::LogicalSide aSide2) +{ + return IsInline(aSide1) != IsInline(aSide2); +} + +// XXX allocate this as number-of-cols+1 instead of number-of-cols+1 * number-of-rows+1 +struct BCCornerInfo +{ + BCCornerInfo() { ownerColor = 0; ownerWidth = subWidth = ownerElem = subSide = + subElem = hasDashDot = numSegs = bevel = 0; ownerSide = eLogicalSideBStart; + ownerStyle = 0xFF; subStyle = NS_STYLE_BORDER_STYLE_SOLID; } + void Set(mozilla::LogicalSide aSide, + BCCellBorder border); + + void Update(mozilla::LogicalSide aSide, + BCCellBorder border); + + nscolor ownerColor; // color of borderOwner + uint16_t ownerWidth; // pixel width of borderOwner + uint16_t subWidth; // pixel width of the largest border intersecting the border perpendicular + // to ownerSide + uint32_t ownerSide:2; // LogicalSide (e.g eLogicalSideBStart, etc) of the border + // owning the corner relative to the corner + uint32_t ownerElem:3; // elem type (e.g. eTable, eGroup, etc) owning the corner + uint32_t ownerStyle:8; // border style of ownerElem + uint32_t subSide:2; // side of border with subWidth relative to the corner + uint32_t subElem:3; // elem type (e.g. eTable, eGroup, etc) of sub owner + uint32_t subStyle:8; // border style of subElem + uint32_t hasDashDot:1; // does a dashed, dotted segment enter the corner, they cannot be beveled + uint32_t numSegs:3; // number of segments entering corner + uint32_t bevel:1; // is the corner beveled (uses the above two fields together with subWidth) + uint32_t unused:1; +}; + +void +BCCornerInfo::Set(mozilla::LogicalSide aSide, + BCCellBorder aBorder) +{ + ownerElem = aBorder.owner; + ownerStyle = aBorder.style; + ownerWidth = aBorder.width; + ownerColor = aBorder.color; + ownerSide = aSide; + hasDashDot = 0; + numSegs = 0; + if (aBorder.width > 0) { + numSegs++; + hasDashDot = (NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) || + (NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style); + } + bevel = 0; + subWidth = 0; + // the following will get set later + subSide = IsInline(aSide) ? eLogicalSideBStart : eLogicalSideIStart; + subElem = eTableOwner; + subStyle = NS_STYLE_BORDER_STYLE_SOLID; +} + +void +BCCornerInfo::Update(mozilla::LogicalSide aSide, + BCCellBorder aBorder) +{ + bool existingWins = false; + if (0xFF == ownerStyle) { // initial value indiating that it hasn't been set yet + Set(aSide, aBorder); + } + else { + bool isInline = IsInline(aSide); // relative to the corner + BCCellBorder oldBorder, tempBorder; + oldBorder.owner = (BCBorderOwner) ownerElem; + oldBorder.style = ownerStyle; + oldBorder.width = ownerWidth; + oldBorder.color = ownerColor; + + LogicalSide oldSide = LogicalSide(ownerSide); + + tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline, &existingWins); + + ownerElem = tempBorder.owner; + ownerStyle = tempBorder.style; + ownerWidth = tempBorder.width; + ownerColor = tempBorder.color; + if (existingWins) { // existing corner is dominant + if (::Perpendicular(LogicalSide(ownerSide), aSide)) { + // see if the new sub info replaces the old + BCCellBorder subBorder; + subBorder.owner = (BCBorderOwner) subElem; + subBorder.style = subStyle; + subBorder.width = subWidth; + subBorder.color = 0; // we are not interested in subBorder color + bool firstWins; + + tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, isInline, &firstWins); + + subElem = tempBorder.owner; + subStyle = tempBorder.style; + subWidth = tempBorder.width; + if (!firstWins) { + subSide = aSide; + } + } + } + else { // input args are dominant + ownerSide = aSide; + if (::Perpendicular(oldSide, LogicalSide(ownerSide))) { + subElem = oldBorder.owner; + subStyle = oldBorder.style; + subWidth = oldBorder.width; + subSide = oldSide; + } + } + if (aBorder.width > 0) { + numSegs++; + if (!hasDashDot && ((NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) || + (NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style))) { + hasDashDot = 1; + } + } + + // bevel the corner if only two perpendicular non dashed/dotted segments enter the corner + bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot); + } +} + +struct BCCorners +{ + BCCorners(int32_t aNumCorners, + int32_t aStartIndex); + + ~BCCorners() { delete [] corners; } + + BCCornerInfo& operator [](int32_t i) const + { NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error"); + return corners[clamped(i, startIndex, endIndex) - startIndex]; } + + int32_t startIndex; + int32_t endIndex; + BCCornerInfo* corners; +}; + +BCCorners::BCCorners(int32_t aNumCorners, + int32_t aStartIndex) +{ + NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error"); + startIndex = aStartIndex; + endIndex = aStartIndex + aNumCorners - 1; + corners = new BCCornerInfo[aNumCorners]; +} + + +struct BCCellBorders +{ + BCCellBorders(int32_t aNumBorders, + int32_t aStartIndex); + + ~BCCellBorders() { delete [] borders; } + + BCCellBorder& operator [](int32_t i) const + { NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error"); + return borders[clamped(i, startIndex, endIndex) - startIndex]; } + + int32_t startIndex; + int32_t endIndex; + BCCellBorder* borders; +}; + +BCCellBorders::BCCellBorders(int32_t aNumBorders, + int32_t aStartIndex) +{ + NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error"); + startIndex = aStartIndex; + endIndex = aStartIndex + aNumBorders - 1; + borders = new BCCellBorder[aNumBorders]; +} + +// this function sets the new border properties and returns true if the border +// segment will start a new segment and not be accumulated into the previous +// segment. +static bool +SetBorder(const BCCellBorder& aNewBorder, + BCCellBorder& aBorder) +{ + bool changed = (aNewBorder.style != aBorder.style) || + (aNewBorder.width != aBorder.width) || + (aNewBorder.color != aBorder.color); + aBorder.color = aNewBorder.color; + aBorder.width = aNewBorder.width; + aBorder.style = aNewBorder.style; + aBorder.owner = aNewBorder.owner; + + return changed; +} + +// this function will set the inline-dir border. It will return true if the +// existing segment will not be continued. Having a block-dir owner of a corner +// should also start a new segment. +static bool +SetInlineDirBorder(const BCCellBorder& aNewBorder, + const BCCornerInfo& aCorner, + BCCellBorder& aBorder) +{ + bool startSeg = ::SetBorder(aNewBorder, aBorder); + if (!startSeg) { + startSeg = !IsInline(LogicalSide(aCorner.ownerSide)); + } + return startSeg; +} + +// Make the damage area larger on the top and bottom by at least one row and on the left and right +// at least one column. This is done so that adjacent elements are part of the border calculations. +// The extra segments and borders outside the actual damage area will not be updated in the cell map, +// because they in turn would need info from adjacent segments outside the damage area to be accurate. +void +nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const +{ + int32_t numRows = GetRowCount(); + int32_t numCols = GetColCount(); + + int32_t dStartX = aArea.StartCol(); + int32_t dEndX = aArea.EndCol() - 1; + int32_t dStartY = aArea.StartRow(); + int32_t dEndY = aArea.EndRow() - 1; + + // expand the damage area in each direction + if (dStartX > 0) { + dStartX--; + } + if (dEndX < (numCols - 1)) { + dEndX++; + } + if (dStartY > 0) { + dStartY--; + } + if (dEndY < (numRows - 1)) { + dEndY++; + } + // Check the damage area so that there are no cells spanning in or out. If there are any then + // make the damage area as big as the table, similarly to the way the cell map decides whether + // to rebuild versus expand. This could be optimized to expand to the smallest area that contains + // no spanners, but it may not be worth the effort in general, and it would need to be done in the + // cell map as well. + bool haveSpanner = false; + if ((dStartX > 0) || (dEndX < (numCols - 1)) || (dStartY > 0) || (dEndY < (numRows - 1))) { + nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT0(); + // Get the ordered row groups + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + // Scope outside loop to be used as hint. + nsCellMap* cellMap = nullptr; + for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + int32_t rgStartY = rgFrame->GetStartRowIndex(); + int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1; + if (dEndY < rgStartY) + break; + cellMap = tableCellMap->GetMapFor(rgFrame, cellMap); + if (!cellMap) ABORT0(); + // check for spanners from above and below + if ((dStartY > 0) && (dStartY >= rgStartY) && (dStartY <= rgEndY)) { + if (uint32_t(dStartY - rgStartY) >= cellMap->mRows.Length()) + ABORT0(); + const nsCellMap::CellDataArray& row = + cellMap->mRows[dStartY - rgStartY]; + for (int32_t x = dStartX; x <= dEndX; x++) { + CellData* cellData = row.SafeElementAt(x); + if (cellData && (cellData->IsRowSpan())) { + haveSpanner = true; + break; + } + } + if (dEndY < rgEndY) { + if (uint32_t(dEndY + 1 - rgStartY) >= cellMap->mRows.Length()) + ABORT0(); + const nsCellMap::CellDataArray& row2 = + cellMap->mRows[dEndY + 1 - rgStartY]; + for (int32_t x = dStartX; x <= dEndX; x++) { + CellData* cellData = row2.SafeElementAt(x); + if (cellData && (cellData->IsRowSpan())) { + haveSpanner = true; + break; + } + } + } + } + // check for spanners on the left and right + int32_t iterStartY = -1; + int32_t iterEndY = -1; + if ((dStartY >= rgStartY) && (dStartY <= rgEndY)) { + // the damage area starts in the row group + iterStartY = dStartY; + iterEndY = std::min(dEndY, rgEndY); + } + else if ((dEndY >= rgStartY) && (dEndY <= rgEndY)) { + // the damage area ends in the row group + iterStartY = rgStartY; + iterEndY = dEndY; + } + else if ((rgStartY >= dStartY) && (rgEndY <= dEndY)) { + // the damage area contains the row group + iterStartY = rgStartY; + iterEndY = rgEndY; + } + if ((iterStartY >= 0) && (iterEndY >= 0)) { + for (int32_t y = iterStartY; y <= iterEndY; y++) { + if (uint32_t(y - rgStartY) >= cellMap->mRows.Length()) + ABORT0(); + const nsCellMap::CellDataArray& row = + cellMap->mRows[y - rgStartY]; + CellData* cellData = row.SafeElementAt(dStartX); + if (cellData && (cellData->IsColSpan())) { + haveSpanner = true; + break; + } + if (dEndX < (numCols - 1)) { + cellData = row.SafeElementAt(dEndX + 1); + if (cellData && (cellData->IsColSpan())) { + haveSpanner = true; + break; + } + } + } + } + } + } + if (haveSpanner) { + // make the damage area the whole table + aArea.StartCol() = 0; + aArea.StartRow() = 0; + aArea.ColCount() = numCols; + aArea.RowCount() = numRows; + } + else { + aArea.StartCol() = dStartX; + aArea.StartRow() = dStartY; + aArea.ColCount() = 1 + dEndX - dStartX; + aArea.RowCount() = 1 + dEndY - dStartY; + } +} + + +#define ADJACENT true +#define INLINE_DIR true + +void +BCMapCellInfo::SetTableBStartIStartContBCBorder() +{ + BCCellBorder currentBorder; + //calculate continuous top first row & rowgroup border: special case + //because it must include the table in the collapse + if (mStartRow) { + currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, + mStartRow, nullptr, mTableWM, + eLogicalSideBStart, !ADJACENT); + mStartRow->SetContinuousBCBorderWidth(eLogicalSideBStart, + currentBorder.width); + } + if (mCgAtEnd && mColGroup) { + //calculate continuous top colgroup border once per colgroup + currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, + mStartRow, nullptr, mTableWM, + eLogicalSideBStart, !ADJACENT); + mColGroup->SetContinuousBCBorderWidth(eLogicalSideBStart, + currentBorder.width); + } + if (0 == mColIndex) { + currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol, nullptr, + nullptr, nullptr, mTableWM, + eLogicalSideIStart, !ADJACENT); + mTableFrame->SetContinuousIStartBCBorderWidth(currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowGroupIStartContBCBorder() +{ + BCCellBorder currentBorder; + //get row group continuous borders + if (mRgAtEnd && mRowGroup) { //once per row group, so check for bottom + currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol, + mRowGroup, nullptr, nullptr, mTableWM, + eLogicalSideIStart, !ADJACENT); + mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIStart, + currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowGroupIEndContBCBorder() +{ + BCCellBorder currentBorder; + //get row group continuous borders + if (mRgAtEnd && mRowGroup) { //once per mRowGroup, so check for bottom + currentBorder = CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup, + nullptr, nullptr, mTableWM, eLogicalSideIEnd, + ADJACENT); + mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIEnd, + currentBorder.width); + } +} + +void +BCMapCellInfo::SetColumnBStartIEndContBCBorder() +{ + BCCellBorder currentBorder; + //calculate column continuous borders + //we only need to do this once, so we'll do it only on the first row + currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, + mCurrentColFrame, mRowGroup, mStartRow, + nullptr, mTableWM, eLogicalSideBStart, + !ADJACENT); + mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBStart, + currentBorder.width); + if (mNumTableCols == GetCellEndColIndex() + 1) { + currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, + mCurrentColFrame, nullptr, nullptr, nullptr, + mTableWM, eLogicalSideIEnd, !ADJACENT); + } + else { + currentBorder = CompareBorders(nullptr, mCurrentColGroupFrame, + mCurrentColFrame, nullptr,nullptr, nullptr, + mTableWM, eLogicalSideIEnd, !ADJACENT); + } + mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd, + currentBorder.width); +} + +void +BCMapCellInfo::SetColumnBEndContBCBorder() +{ + BCCellBorder currentBorder; + //get col continuous border + currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, + mCurrentColFrame, mRowGroup, mEndRow, + nullptr, mTableWM, eLogicalSideBEnd, ADJACENT); + mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBEnd, + currentBorder.width); +} + +void +BCMapCellInfo::SetColGroupBEndContBCBorder() +{ + BCCellBorder currentBorder; + if (mColGroup) { + currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, + mEndRow, nullptr, mTableWM, + eLogicalSideBEnd, ADJACENT); + mColGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowGroupBEndContBCBorder() +{ + BCCellBorder currentBorder; + if (mRowGroup) { + currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, + mEndRow, nullptr, mTableWM, + eLogicalSideBEnd, ADJACENT); + mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width); + } +} + +void +BCMapCellInfo::SetInnerRowGroupBEndContBCBorder(const nsIFrame* aNextRowGroup, + nsTableRowFrame* aNextRow) +{ + BCCellBorder currentBorder, adjacentBorder; + + const nsIFrame* rowgroup = mRgAtEnd ? mRowGroup : nullptr; + currentBorder = CompareBorders(nullptr, nullptr, nullptr, rowgroup, mEndRow, + nullptr, mTableWM, eLogicalSideBEnd, ADJACENT); + + adjacentBorder = CompareBorders(nullptr, nullptr, nullptr, aNextRowGroup, + aNextRow, nullptr, mTableWM, eLogicalSideBStart, + !ADJACENT); + currentBorder = CompareBorders(false, currentBorder, adjacentBorder, + INLINE_DIR); + if (aNextRow) { + aNextRow->SetContinuousBCBorderWidth(eLogicalSideBStart, + currentBorder.width); + } + if (mRgAtEnd && mRowGroup) { + mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowIStartContBCBorder() +{ + //get row continuous borders + if (mCurrentRowFrame) { + BCCellBorder currentBorder; + currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol, + mRowGroup, mCurrentRowFrame, nullptr, + mTableWM, eLogicalSideIStart, !ADJACENT); + mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIStart, + currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowIEndContBCBorder() +{ + if (mCurrentRowFrame) { + BCCellBorder currentBorder; + currentBorder = CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup, + mCurrentRowFrame, nullptr, mTableWM, + eLogicalSideIEnd, ADJACENT); + mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd, + currentBorder.width); + } +} +void +BCMapCellInfo::SetTableBStartBorderWidth(BCPixelSize aWidth) +{ + mTableBCData->mBStartBorderWidth = std::max(mTableBCData->mBStartBorderWidth, + aWidth); +} + +void +BCMapCellInfo::SetTableIStartBorderWidth(int32_t aRowB, BCPixelSize aWidth) +{ + // update the iStart first cell border + if (aRowB == 0) { + mTableBCData->mIStartCellBorderWidth = aWidth; + } + mTableBCData->mIStartBorderWidth = std::max(mTableBCData->mIStartBorderWidth, + aWidth); +} + +void +BCMapCellInfo::SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth) +{ + // update the iEnd first cell border + if (aRowB == 0) { + mTableBCData->mIEndCellBorderWidth = aWidth; + } + mTableBCData->mIEndBorderWidth = std::max(mTableBCData->mIEndBorderWidth, + aWidth); +} + +void +BCMapCellInfo::SetIEndBorderWidths(BCPixelSize aWidth) +{ + // update the borders of the cells and cols affected + if (mCell) { + mCell->SetBorderWidth(eLogicalSideIEnd, std::max(aWidth, + mCell->GetBorderWidth(eLogicalSideIEnd))); + } + if (mEndCol) { + BCPixelSize half = BC_BORDER_START_HALF(aWidth); + mEndCol->SetIEndBorderWidth( + std::max(nscoord(half), mEndCol->GetIEndBorderWidth())); + } +} + +void +BCMapCellInfo::SetBEndBorderWidths(BCPixelSize aWidth) +{ + // update the borders of the affected cells and rows + if (mCell) { + mCell->SetBorderWidth(eLogicalSideBEnd, std::max(aWidth, + mCell->GetBorderWidth(eLogicalSideBEnd))); + } + if (mEndRow) { + BCPixelSize half = BC_BORDER_START_HALF(aWidth); + mEndRow->SetBEndBCBorderWidth( + std::max(nscoord(half), mEndRow->GetBEndBCBorderWidth())); + } +} +void +BCMapCellInfo::SetBStartBorderWidths(BCPixelSize aWidth) +{ + if (mCell) { + mCell->SetBorderWidth(eLogicalSideBStart, std::max(aWidth, + mCell->GetBorderWidth(eLogicalSideBStart))); + } + if (mStartRow) { + BCPixelSize half = BC_BORDER_END_HALF(aWidth); + mStartRow->SetBStartBCBorderWidth( + std::max(nscoord(half), mStartRow->GetBStartBCBorderWidth())); + } +} +void +BCMapCellInfo::SetIStartBorderWidths(BCPixelSize aWidth) +{ + if (mCell) { + mCell->SetBorderWidth(eLogicalSideIStart, std::max(aWidth, + mCell->GetBorderWidth(eLogicalSideIStart))); + } + if (mStartCol) { + BCPixelSize half = BC_BORDER_END_HALF(aWidth); + mStartCol->SetIStartBorderWidth( + std::max(nscoord(half), mStartCol->GetIStartBorderWidth())); + } +} + +void +BCMapCellInfo::SetTableBEndBorderWidth(BCPixelSize aWidth) +{ + mTableBCData->mBEndBorderWidth = std::max(mTableBCData->mBEndBorderWidth, + aWidth); +} + +void +BCMapCellInfo::SetColumn(int32_t aColX) +{ + mCurrentColFrame = mTableFrame->GetColFrame(aColX); + if (!mCurrentColFrame) { + NS_ERROR("null mCurrentColFrame"); + } + mCurrentColGroupFrame = static_cast + (mCurrentColFrame->GetParent()); + if (!mCurrentColGroupFrame) { + NS_ERROR("null mCurrentColGroupFrame"); + } +} + +void +BCMapCellInfo::IncrementRow(bool aResetToBStartRowOfCell) +{ + mCurrentRowFrame = + aResetToBStartRowOfCell ? mStartRow : mCurrentRowFrame->GetNextRow(); +} + +BCCellBorder +BCMapCellInfo::GetBStartEdgeBorder() +{ + return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, + mRowGroup, mStartRow, mCell, mTableWM, + eLogicalSideBStart, !ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetBEndEdgeBorder() +{ + return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, + mRowGroup, mEndRow, mCell, mTableWM, + eLogicalSideBEnd, ADJACENT); +} +BCCellBorder +BCMapCellInfo::GetIStartEdgeBorder() +{ + return CompareBorders(mTableFrame, mColGroup, mStartCol, mRowGroup, + mCurrentRowFrame, mCell, mTableWM, eLogicalSideIStart, + !ADJACENT); +} +BCCellBorder +BCMapCellInfo::GetIEndEdgeBorder() +{ + return CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup, + mCurrentRowFrame, mCell, mTableWM, eLogicalSideIEnd, + ADJACENT); +} +BCCellBorder +BCMapCellInfo::GetIEndInternalBorder() +{ + const nsIFrame* cg = mCgAtEnd ? mColGroup : nullptr; + return CompareBorders(nullptr, cg, mEndCol, nullptr, nullptr, mCell, + mTableWM, eLogicalSideIEnd, ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetIStartInternalBorder() +{ + const nsIFrame* cg = mCgAtStart ? mColGroup : nullptr; + return CompareBorders(nullptr, cg, mStartCol, nullptr, nullptr, mCell, + mTableWM, eLogicalSideIStart, !ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetBEndInternalBorder() +{ + const nsIFrame* rg = mRgAtEnd ? mRowGroup : nullptr; + return CompareBorders(nullptr, nullptr, nullptr, rg, mEndRow, mCell, + mTableWM, eLogicalSideBEnd, ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetBStartInternalBorder() +{ + const nsIFrame* rg = mRgAtStart ? mRowGroup : nullptr; + return CompareBorders(nullptr, nullptr, nullptr, rg, mStartRow, mCell, + mTableWM, eLogicalSideBStart, !ADJACENT); +} + +/* XXX This comment is still written in physical (horizontal-tb) terms. + + Here is the order for storing border edges in the cell map as a cell is processed. There are + n=colspan top and bottom border edges per cell and n=rowspan left and right border edges per cell. + + 1) On the top edge of the table, store the top edge. Never store the top edge otherwise, since + a bottom edge from a cell above will take care of it. + 2) On the left edge of the table, store the left edge. Never store the left edge othewise, since + a right edge from a cell to the left will take care of it. + 3) Store the right edge (or edges if a row span) + 4) Store the bottom edge (or edges if a col span) + + Since corners are computed with only an array of BCCornerInfo indexed by the number-of-cols, corner + calculations are somewhat complicated. Using an array with number-of-rows * number-of-col entries + would simplify this, but at an extra in memory cost of nearly 12 bytes per cell map entry. Collapsing + borders already have about an extra 8 byte per cell map entry overhead (this could be + reduced to 4 bytes if we are willing to not store border widths in nsTableCellFrame), Here are the + rules in priority order for storing cornes in the cell map as a cell is processed. top-left means the + left endpoint of the border edge on the top of the cell. There are n=colspan top and bottom border + edges per cell and n=rowspan left and right border edges per cell. + + 1) On the top edge of the table, store the top-left corner, unless on the left edge of the table. + Never store the top-right corner, since it will get stored as a right-top corner. + 2) On the left edge of the table, store the left-top corner. Never store the left-bottom corner, + since it will get stored as a bottom-left corner. + 3) Store the right-top corner if (a) it is the top right corner of the table or (b) it is not on + the top edge of the table. Never store the right-bottom corner since it will get stored as a + bottom-right corner. + 4) Store the bottom-right corner, if it is the bottom right corner of the table. Never store it + otherwise, since it will get stored as either a right-top corner by a cell below or + a bottom-left corner from a cell to the right. + 5) Store the bottom-left corner, if (a) on the bottom edge of the table or (b) if the left edge hits + the top side of a colspan in its interior. Never store the corner otherwise, since it will + get stored as a right-top corner by a cell from below. + + XXX the BC-RTL hack - The correct fix would be a rewrite as described in bug 203686. + In order to draw borders in rtl conditions somehow correct, the existing structure which relies + heavily on the assumption that the next cell sibling will be on the right side, has been modified. + We flip the border during painting and during style lookup. Look for tableIsLTR for places where + the flipping is done. + */ + + + +// Calc the dominant border at every cell edge and corner within the current damage area +void +nsTableFrame::CalcBCBorders() +{ + NS_ASSERTION(IsBorderCollapse(), + "calling CalcBCBorders on separated-border table"); + nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT0(); + int32_t numRows = GetRowCount(); + int32_t numCols = GetColCount(); + if (!numRows || !numCols) + return; // nothing to do + + // Get the property holding the table damage area and border widths + BCPropertyData* propData = GetBCProperty(); + if (!propData) ABORT0(); + + + + // calculate an expanded damage area + TableArea damageArea(propData->mDamageArea); + ExpandBCDamageArea(damageArea); + + // segments that are on the table border edges need + // to be initialized only once + bool tableBorderReset[4]; + for (uint32_t sideX = 0; sideX < ArrayLength(tableBorderReset); sideX++) { + tableBorderReset[sideX] = false; + } + + // block-dir borders indexed in inline-direction (cols) + BCCellBorders lastBlockDirBorders(damageArea.ColCount() + 1, + damageArea.StartCol()); + if (!lastBlockDirBorders.borders) ABORT0(); + BCCellBorder lastBStartBorder, lastBEndBorder; + // inline-dir borders indexed in inline-direction (cols) + BCCellBorders lastBEndBorders(damageArea.ColCount() + 1, + damageArea.StartCol()); + if (!lastBEndBorders.borders) ABORT0(); + bool startSeg; + bool gotRowBorder = false; + + BCMapCellInfo info(this), ajaInfo(this); + + BCCellBorder currentBorder, adjacentBorder; + BCCorners bStartCorners(damageArea.ColCount() + 1, damageArea.StartCol()); + if (!bStartCorners.corners) ABORT0(); + BCCorners bEndCorners(damageArea.ColCount() + 1, damageArea.StartCol()); + if (!bEndCorners.corners) ABORT0(); + + BCMapCellIterator iter(this, damageArea); + for (iter.First(info); !iter.mAtEnd; iter.Next(info)) { + // see if lastBStartBorder, lastBEndBorder need to be reset + if (iter.IsNewRow()) { + gotRowBorder = false; + lastBStartBorder.Reset(info.mRowIndex, info.mRowSpan); + lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); + } + else if (info.mColIndex > damageArea.StartCol()) { + lastBEndBorder = lastBEndBorders[info.mColIndex - 1]; + if (info.mRowIndex > + (lastBEndBorder.rowIndex - lastBEndBorder.rowSpan)) { + // the bStart border's iStart edge butts against the middle of a rowspan + lastBStartBorder.Reset(info.mRowIndex, info.mRowSpan); + } + if (lastBEndBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) { + // the bEnd border's iStart edge butts against the middle of a rowspan + lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); + } + } + + // find the dominant border considering the cell's bStart border and the table, + // row group, row if the border is at the bStart of the table, otherwise it was + // processed in a previous row + if (0 == info.mRowIndex) { + if (!tableBorderReset[eLogicalSideBStart]) { + propData->mBStartBorderWidth = 0; + tableBorderReset[eLogicalSideBStart] = true; + } + for (int32_t colIdx = info.mColIndex; + colIdx <= info.GetCellEndColIndex(); colIdx++) { + info.SetColumn(colIdx); + currentBorder = info.GetBStartEdgeBorder(); + // update/store the bStart-iStart & bStart-iEnd corners of the seg + BCCornerInfo& tlCorner = bStartCorners[colIdx]; // bStart-iStart + if (0 == colIdx) { + // we are on the iEnd side of the corner + tlCorner.Set(eLogicalSideIEnd, currentBorder); + } + else { + tlCorner.Update(eLogicalSideIEnd, currentBorder); + tableCellMap->SetBCBorderCorner(eBStartIStart, *iter.mCellMap, 0, 0, colIdx, + LogicalSide(tlCorner.ownerSide), + tlCorner.subWidth, + tlCorner.bevel); + } + bStartCorners[colIdx + 1].Set(eLogicalSideIStart, currentBorder); // bStart-iEnd + // update lastBStartBorder and see if a new segment starts + startSeg = SetInlineDirBorder(currentBorder, tlCorner, lastBStartBorder); + // store the border segment in the cell map + tableCellMap->SetBCBorderEdge(eLogicalSideBStart, *iter.mCellMap, 0, 0, colIdx, + 1, currentBorder.owner, + currentBorder.width, startSeg); + + info.SetTableBStartBorderWidth(currentBorder.width); + info.SetBStartBorderWidths(currentBorder.width); + info.SetColumnBStartIEndContBCBorder(); + } + info.SetTableBStartIStartContBCBorder(); + } + else { + // see if the bStart border needs to be the start of a segment due to a + // block-dir border owning the corner + if (info.mColIndex > 0) { + BCData& data = info.mCellData->mData; + if (!data.IsBStartStart()) { + LogicalSide cornerSide; + bool bevel; + data.GetCorner(cornerSide, bevel); + if (IsBlock(cornerSide)) { + data.SetBStartStart(true); + } + } + } + } + + // find the dominant border considering the cell's iStart border and the + // table, col group, col if the border is at the iStart of the table, + // otherwise it was processed in a previous col + if (0 == info.mColIndex) { + if (!tableBorderReset[eLogicalSideIStart]) { + propData->mIStartBorderWidth = 0; + tableBorderReset[eLogicalSideIStart] = true; + } + info.mCurrentRowFrame = nullptr; + for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex(); + rowB++) { + info.IncrementRow(rowB == info.mRowIndex); + currentBorder = info.GetIStartEdgeBorder(); + BCCornerInfo& tlCorner = (0 == rowB) ? bStartCorners[0] : bEndCorners[0]; + tlCorner.Update(eLogicalSideBEnd, currentBorder); + tableCellMap->SetBCBorderCorner(eBStartIStart, *iter.mCellMap, + iter.mRowGroupStart, rowB, 0, + LogicalSide(tlCorner.ownerSide), + tlCorner.subWidth, + tlCorner.bevel); + bEndCorners[0].Set(eLogicalSideBStart, currentBorder); // bEnd-iStart + + // update lastBlockDirBorders and see if a new segment starts + startSeg = SetBorder(currentBorder, lastBlockDirBorders[0]); + // store the border segment in the cell map + tableCellMap->SetBCBorderEdge(eLogicalSideIStart, *iter.mCellMap, + iter.mRowGroupStart, rowB, info.mColIndex, + 1, currentBorder.owner, + currentBorder.width, startSeg); + info.SetTableIStartBorderWidth(rowB , currentBorder.width); + info.SetIStartBorderWidths(currentBorder.width); + info.SetRowIStartContBCBorder(); + } + info.SetRowGroupIStartContBCBorder(); + } + + // find the dominant border considering the cell's iEnd border, adjacent + // cells and the table, row group, row + if (info.mNumTableCols == info.GetCellEndColIndex() + 1) { + // touches iEnd edge of table + if (!tableBorderReset[eLogicalSideIEnd]) { + propData->mIEndBorderWidth = 0; + tableBorderReset[eLogicalSideIEnd] = true; + } + info.mCurrentRowFrame = nullptr; + for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex(); + rowB++) { + info.IncrementRow(rowB == info.mRowIndex); + currentBorder = info.GetIEndEdgeBorder(); + // update/store the bStart-iEnd & bEnd-iEnd corners + BCCornerInfo& trCorner = (0 == rowB) ? + bStartCorners[info.GetCellEndColIndex() + 1] : + bEndCorners[info.GetCellEndColIndex() + 1]; + trCorner.Update(eLogicalSideBEnd, currentBorder); // bStart-iEnd + tableCellMap->SetBCBorderCorner(eBStartIEnd, *iter.mCellMap, + iter.mRowGroupStart, rowB, + info.GetCellEndColIndex(), + LogicalSide(trCorner.ownerSide), + trCorner.subWidth, + trCorner.bevel); + BCCornerInfo& brCorner = bEndCorners[info.GetCellEndColIndex() + 1]; + brCorner.Set(eLogicalSideBStart, currentBorder); // bEnd-iEnd + tableCellMap->SetBCBorderCorner(eBEndIEnd, *iter.mCellMap, + iter.mRowGroupStart, rowB, + info.GetCellEndColIndex(), + LogicalSide(brCorner.ownerSide), + brCorner.subWidth, + brCorner.bevel); + // update lastBlockDirBorders and see if a new segment starts + startSeg = SetBorder(currentBorder, + lastBlockDirBorders[info.GetCellEndColIndex() + 1]); + // store the border segment in the cell map and update cellBorders + tableCellMap->SetBCBorderEdge(eLogicalSideIEnd, *iter.mCellMap, + iter.mRowGroupStart, rowB, + info.GetCellEndColIndex(), 1, + currentBorder.owner, currentBorder.width, + startSeg); + info.SetTableIEndBorderWidth(rowB, currentBorder.width); + info.SetIEndBorderWidths(currentBorder.width); + info.SetRowIEndContBCBorder(); + } + info.SetRowGroupIEndContBCBorder(); + } + else { + int32_t segLength = 0; + BCMapCellInfo priorAjaInfo(this); + for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex(); + rowB += segLength) { + iter.PeekIEnd(info, rowB, ajaInfo); + currentBorder = info.GetIEndInternalBorder(); + adjacentBorder = ajaInfo.GetIStartInternalBorder(); + currentBorder = CompareBorders(!CELL_CORNER, currentBorder, + adjacentBorder, !INLINE_DIR); + + segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowB); + segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowB); + + // update lastBlockDirBorders and see if a new segment starts + startSeg = SetBorder(currentBorder, + lastBlockDirBorders[info.GetCellEndColIndex() + 1]); + // store the border segment in the cell map and update cellBorders + if (info.GetCellEndColIndex() < damageArea.EndCol() && + rowB >= damageArea.StartRow() && rowB < damageArea.EndRow()) { + tableCellMap->SetBCBorderEdge(eLogicalSideIEnd, *iter.mCellMap, + iter.mRowGroupStart, rowB, + info.GetCellEndColIndex(), segLength, + currentBorder.owner, + currentBorder.width, startSeg); + info.SetIEndBorderWidths(currentBorder.width); + ajaInfo.SetIStartBorderWidths(currentBorder.width); + } + // update the bStart-iEnd corner + bool hitsSpanOnIEnd = (rowB > ajaInfo.mRowIndex) && + (rowB < ajaInfo.mRowIndex + ajaInfo.mRowSpan); + BCCornerInfo* trCorner = ((0 == rowB) || hitsSpanOnIEnd) ? + &bStartCorners[info.GetCellEndColIndex() + 1] : + &bEndCorners[info.GetCellEndColIndex() + 1]; + trCorner->Update(eLogicalSideBEnd, currentBorder); + // if this is not the first time through, + // consider the segment to the iEnd side + if (rowB != info.mRowIndex) { + currentBorder = priorAjaInfo.GetBEndInternalBorder(); + adjacentBorder = ajaInfo.GetBStartInternalBorder(); + currentBorder = CompareBorders(!CELL_CORNER, currentBorder, + adjacentBorder, INLINE_DIR); + trCorner->Update(eLogicalSideIEnd, currentBorder); + } + // store the bStart-iEnd corner in the cell map + if (info.GetCellEndColIndex() < damageArea.EndCol() && + rowB >= damageArea.StartRow()) { + if (0 != rowB) { + tableCellMap->SetBCBorderCorner(eBStartIEnd, *iter.mCellMap, + iter.mRowGroupStart, rowB, + info.GetCellEndColIndex(), + LogicalSide(trCorner->ownerSide), + trCorner->subWidth, + trCorner->bevel); + } + // store any corners this cell spans together with the aja cell + for (int32_t rX = rowB + 1; rX < rowB + segLength; rX++) { + tableCellMap->SetBCBorderCorner(eBEndIEnd, *iter.mCellMap, + iter.mRowGroupStart, rX, + info.GetCellEndColIndex(), + LogicalSide(trCorner->ownerSide), + trCorner->subWidth, false); + } + } + // update bEnd-iEnd corner, bStartCorners, bEndCorners + hitsSpanOnIEnd = (rowB + segLength < + ajaInfo.mRowIndex + ajaInfo.mRowSpan); + BCCornerInfo& brCorner = (hitsSpanOnIEnd) ? + bStartCorners[info.GetCellEndColIndex() + 1] : + bEndCorners[info.GetCellEndColIndex() + 1]; + brCorner.Set(eLogicalSideBStart, currentBorder); + priorAjaInfo = ajaInfo; + } + } + for (int32_t colIdx = info.mColIndex + 1; + colIdx <= info.GetCellEndColIndex(); colIdx++) { + lastBlockDirBorders[colIdx].Reset(0,1); + } + + // find the dominant border considering the cell's bEnd border, adjacent + // cells and the table, row group, row + if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) { + // touches bEnd edge of table + if (!tableBorderReset[eLogicalSideBEnd]) { + propData->mBEndBorderWidth = 0; + tableBorderReset[eLogicalSideBEnd] = true; + } + for (int32_t colIdx = info.mColIndex; + colIdx <= info.GetCellEndColIndex(); colIdx++) { + info.SetColumn(colIdx); + currentBorder = info.GetBEndEdgeBorder(); + // update/store the bEnd-iStart & bEnd-IEnd corners + BCCornerInfo& blCorner = bEndCorners[colIdx]; // bEnd-iStart + blCorner.Update(eLogicalSideIEnd, currentBorder); + tableCellMap->SetBCBorderCorner(eBEndIStart, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), + colIdx, + LogicalSide(blCorner.ownerSide), + blCorner.subWidth, blCorner.bevel); + BCCornerInfo& brCorner = bEndCorners[colIdx + 1]; // bEnd-iEnd + brCorner.Update(eLogicalSideIStart, currentBorder); + if (info.mNumTableCols == colIdx + 1) { // bEnd-IEnd corner of the table + tableCellMap->SetBCBorderCorner(eBEndIEnd, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), colIdx, + LogicalSide(brCorner.ownerSide), + brCorner.subWidth, + brCorner.bevel, true); + } + // update lastBEndBorder and see if a new segment starts + startSeg = SetInlineDirBorder(currentBorder, blCorner, lastBEndBorder); + if (!startSeg) { + // make sure that we did not compare apples to oranges i.e. the + // current border should be a continuation of the lastBEndBorder, + // as it is a bEnd border + // add 1 to the info.GetCellEndRowIndex() + startSeg = (lastBEndBorder.rowIndex != + (info.GetCellEndRowIndex() + 1)); + } + // store the border segment in the cell map and update cellBorders + tableCellMap->SetBCBorderEdge(eLogicalSideBEnd, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), + colIdx, 1, currentBorder.owner, + currentBorder.width, startSeg); + // update lastBEndBorders + lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1; + lastBEndBorder.rowSpan = info.mRowSpan; + lastBEndBorders[colIdx] = lastBEndBorder; + + info.SetBEndBorderWidths(currentBorder.width); + info.SetTableBEndBorderWidth(currentBorder.width); + info.SetColumnBEndContBCBorder(); + } + info.SetRowGroupBEndContBCBorder(); + info.SetColGroupBEndContBCBorder(); + } + else { + int32_t segLength = 0; + for (int32_t colIdx = info.mColIndex; + colIdx <= info.GetCellEndColIndex(); colIdx += segLength) { + iter.PeekBEnd(info, colIdx, ajaInfo); + currentBorder = info.GetBEndInternalBorder(); + adjacentBorder = ajaInfo.GetBStartInternalBorder(); + currentBorder = CompareBorders(!CELL_CORNER, currentBorder, + adjacentBorder, INLINE_DIR); + segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colIdx); + segLength = std::min(segLength, info.mColIndex + info.mColSpan - colIdx); + + // update, store the bEnd-iStart corner + BCCornerInfo& blCorner = bEndCorners[colIdx]; // bEnd-iStart + bool hitsSpanBelow = (colIdx > ajaInfo.mColIndex) && + (colIdx < ajaInfo.mColIndex + ajaInfo.mColSpan); + bool update = true; + if (colIdx == info.mColIndex && colIdx > damageArea.StartCol()) { + int32_t prevRowIndex = lastBEndBorders[colIdx - 1].rowIndex; + if (prevRowIndex > info.GetCellEndRowIndex() + 1) { + // hits a rowspan on the iEnd side + update = false; + // the corner was taken care of during the cell on the iStart side + } + else if (prevRowIndex < info.GetCellEndRowIndex() + 1) { + // spans below the cell to the iStart side + bStartCorners[colIdx] = blCorner; + blCorner.Set(eLogicalSideIEnd, currentBorder); + update = false; + } + } + if (update) { + blCorner.Update(eLogicalSideIEnd, currentBorder); + } + if (info.GetCellEndRowIndex() < damageArea.EndRow() && + colIdx >= damageArea.StartCol()) { + if (hitsSpanBelow) { + tableCellMap->SetBCBorderCorner(eBEndIStart, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), colIdx, + LogicalSide(blCorner.ownerSide), + blCorner.subWidth, blCorner.bevel); + } + // store any corners this cell spans together with the aja cell + for (int32_t c = colIdx + 1; c < colIdx + segLength; c++) { + BCCornerInfo& corner = bEndCorners[c]; + corner.Set(eLogicalSideIEnd, currentBorder); + tableCellMap->SetBCBorderCorner(eBEndIStart, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), c, + LogicalSide(corner.ownerSide), + corner.subWidth, + false); + } + } + // update lastBEndBorders and see if a new segment starts + startSeg = SetInlineDirBorder(currentBorder, blCorner, lastBEndBorder); + if (!startSeg) { + // make sure that we did not compare apples to oranges i.e. the + // current border should be a continuation of the lastBEndBorder, + // as it is a bEnd border + // add 1 to the info.GetCellEndRowIndex() + startSeg = (lastBEndBorder.rowIndex != + info.GetCellEndRowIndex() + 1); + } + lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1; + lastBEndBorder.rowSpan = info.mRowSpan; + for (int32_t c = colIdx; c < colIdx + segLength; c++) { + lastBEndBorders[c] = lastBEndBorder; + } + + // store the border segment the cell map and update cellBorders + if (info.GetCellEndRowIndex() < damageArea.EndRow() && + colIdx >= damageArea.StartCol() && colIdx < damageArea.EndCol()) { + tableCellMap->SetBCBorderEdge(eLogicalSideBEnd, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), + colIdx, segLength, currentBorder.owner, + currentBorder.width, startSeg); + info.SetBEndBorderWidths(currentBorder.width); + ajaInfo.SetBStartBorderWidths(currentBorder.width); + } + // update bEnd-iEnd corner + BCCornerInfo& brCorner = bEndCorners[colIdx + segLength]; + brCorner.Update(eLogicalSideIStart, currentBorder); + } + if (!gotRowBorder && 1 == info.mRowSpan && + (ajaInfo.mStartRow || info.mRgAtEnd)) { + //get continuous row/row group border + //we need to check the row group's bEnd border if this is + //the last row in the row group, but only a cell with rowspan=1 + //will know whether *this* row is at the bEnd + const nsIFrame* nextRowGroup = + ajaInfo.mRgAtStart ? ajaInfo.mRowGroup : nullptr; + info.SetInnerRowGroupBEndContBCBorder(nextRowGroup, ajaInfo.mStartRow); + gotRowBorder = true; + } + } + + // see if the cell to the iEnd side had a rowspan and its bEnd-iStart border + // needs be joined with this one's bEnd + // if there is a cell to the iEnd and the cell to iEnd side was a rowspan + if ((info.mNumTableCols != info.GetCellEndColIndex() + 1) && + (lastBEndBorders[info.GetCellEndColIndex() + 1].rowSpan > 1)) { + BCCornerInfo& corner = bEndCorners[info.GetCellEndColIndex() + 1]; + if (!IsBlock(LogicalSide(corner.ownerSide))) { + // not a block-dir owner + BCCellBorder& thisBorder = lastBEndBorder; + BCCellBorder& nextBorder = lastBEndBorders[info.mColIndex + 1]; + if ((thisBorder.color == nextBorder.color) && + (thisBorder.width == nextBorder.width) && + (thisBorder.style == nextBorder.style)) { + // set the flag on the next border indicating it is not the start of a + // new segment + if (iter.mCellMap) { + tableCellMap->ResetBStartStart(eLogicalSideBEnd, *iter.mCellMap, + info.GetCellEndRowIndex(), + info.GetCellEndColIndex() + 1); + } + } + } + } + } // for (iter.First(info); info.mCell; iter.Next(info)) { + // reset the bc flag and damage area + SetNeedToCalcBCBorders(false); + propData->mDamageArea = TableArea(0, 0, 0, 0); +#ifdef DEBUG_TABLE_CELLMAP + mCellMap->Dump(); +#endif +} + +class BCPaintBorderIterator; + +struct BCBlockDirSeg +{ + BCBlockDirSeg(); + + void Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aBlockSegISize, + BCPixelSize aInlineSegBSize); + + void Initialize(BCPaintBorderIterator& aIter); + void GetBEndCorner(BCPaintBorderIterator& aIter, + BCPixelSize aInlineSegBSize); + + + void Paint(BCPaintBorderIterator& aIter, + DrawTarget& aDrawTarget, + BCPixelSize aInlineSegBSize); + void AdvanceOffsetB(); + void IncludeCurrentBorder(BCPaintBorderIterator& aIter); + + + union { + nsTableColFrame* mCol; + int32_t mColWidth; + }; + nscoord mOffsetI; // i-offset with respect to the table edge + nscoord mOffsetB; // b-offset with respect to the table edge + nscoord mLength; // block-dir length including corners + BCPixelSize mWidth; // thickness in pixels + + nsTableCellFrame* mAjaCell; // previous sibling to the first cell + // where the segment starts, it can be + // the owner of a segment + nsTableCellFrame* mFirstCell; // cell at the start of the segment + nsTableRowGroupFrame* mFirstRowGroup; // row group at the start of the segment + nsTableRowFrame* mFirstRow; // row at the start of the segment + nsTableCellFrame* mLastCell; // cell at the current end of the + // segment + + + uint8_t mOwner; // owner of the border, defines the + // style + LogicalSide mBStartBevelSide; // direction to bevel at the bStart + nscoord mBStartBevelOffset; // how much to bevel at the bStart + BCPixelSize mBEndInlineSegBSize; // bSize of the crossing + // inline-dir border + nscoord mBEndOffset; // how much longer is the segment due + // to the inline-dir border, by this + // amount the next segment needs to be + // shifted. + bool mIsBEndBevel; // should we bevel at the bEnd +}; + +struct BCInlineDirSeg +{ + BCInlineDirSeg(); + + void Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aBEndBlockSegISize, + BCPixelSize aInlineSegBSize); + void GetIEndCorner(BCPaintBorderIterator& aIter, + BCPixelSize aIStartSegISize); + void AdvanceOffsetI(); + void IncludeCurrentBorder(BCPaintBorderIterator& aIter); + void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget); + + nscoord mOffsetI; // i-offset with respect to the table edge + nscoord mOffsetB; // b-offset with respect to the table edge + nscoord mLength; // inline-dir length including corners + BCPixelSize mWidth; // border thickness in pixels + nscoord mIStartBevelOffset; // how much to bevel at the iStart + LogicalSide mIStartBevelSide; // direction to bevel at the iStart + bool mIsIEndBevel; // should we bevel at the iEnd end + nscoord mIEndBevelOffset; // how much to bevel at the iEnd + LogicalSide mIEndBevelSide; // direction to bevel at the iEnd + nscoord mEndOffset; // how much longer is the segment due + // to the block-dir border, by this + // amount the next segment needs to be + // shifted. + uint8_t mOwner; // owner of the border, defines the + // style + nsTableCellFrame* mFirstCell; // cell at the start of the segment + nsTableCellFrame* mAjaCell; // neighboring cell to the first cell + // where the segment starts, it can be + // the owner of a segment +}; + +// Iterates over borders (iStart border, corner, bStart border) in the cell map within a damage area +// from iStart to iEnd, bStart to bEnd. All members are in terms of the 1st in flow frames, except +// where suffixed by InFlow. +class BCPaintBorderIterator +{ +public: + explicit BCPaintBorderIterator(nsTableFrame* aTable); + ~BCPaintBorderIterator() { if (mBlockDirInfo) { + delete [] mBlockDirInfo; + }} + void Reset(); + + /** + * Determine the damage area in terms of rows and columns and finalize + * mInitialOffsetI and mInitialOffsetB. + * @param aDirtyRect - dirty rect in table coordinates + * @return - true if we need to paint something given dirty rect + */ + bool SetDamageArea(const nsRect& aDamageRect); + void First(); + void Next(); + void AccumulateOrPaintInlineDirSegment(DrawTarget& aDrawTarget); + void AccumulateOrPaintBlockDirSegment(DrawTarget& aDrawTarget); + void ResetVerInfo(); + void StoreColumnWidth(int32_t aIndex); + bool BlockDirSegmentOwnsCorner(); + + nsTableFrame* mTable; + nsTableFrame* mTableFirstInFlow; + nsTableCellMap* mTableCellMap; + nsCellMap* mCellMap; + WritingMode mTableWM; + const nsStyleBackground* mTableBgColor; + nsTableFrame::RowGroupArray mRowGroups; + + nsTableRowGroupFrame* mPrevRg; + nsTableRowGroupFrame* mRg; + bool mIsRepeatedHeader; + bool mIsRepeatedFooter; + nsTableRowGroupFrame* mStartRg; // first row group in the damagearea + int32_t mRgIndex; // current row group index in the + // mRowgroups array + int32_t mFifRgFirstRowIndex; // start row index of the first in + // flow of the row group + int32_t mRgFirstRowIndex; // row index of the first row in the + // row group + int32_t mRgLastRowIndex; // row index of the last row in the row + // group + int32_t mNumTableRows; // number of rows in the table and all + // continuations + int32_t mNumTableCols; // number of columns in the table + int32_t mColIndex; // with respect to the table + int32_t mRowIndex; // with respect to the table + int32_t mRepeatedHeaderRowIndex; // row index in a repeated + //header, it's equivalent to + // mRowIndex when we're in a repeated + // header, and set to the last row + // index of a repeated header when + // we're not + bool mIsNewRow; + bool mAtEnd; // the iterator cycled over all + // borders + nsTableRowFrame* mPrevRow; + nsTableRowFrame* mRow; + nsTableRowFrame* mStartRow; //first row in a inside the damagearea + + + // cell properties + nsTableCellFrame* mPrevCell; + nsTableCellFrame* mCell; + BCCellData* mPrevCellData; + BCCellData* mCellData; + BCData* mBCData; + + bool IsTableBStartMost() {return (mRowIndex == 0) && !mTable->GetPrevInFlow();} + bool IsTableIEndMost() {return (mColIndex >= mNumTableCols);} + bool IsTableBEndMost() {return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow();} + bool IsTableIStartMost() {return (mColIndex == 0);} + bool IsDamageAreaBStartMost() const + { return mRowIndex == mDamageArea.StartRow(); } + bool IsDamageAreaIEndMost() const + { return mColIndex >= mDamageArea.EndCol(); } + bool IsDamageAreaBEndMost() const + { return mRowIndex >= mDamageArea.EndRow(); } + bool IsDamageAreaIStartMost() const + { return mColIndex == mDamageArea.StartCol(); } + int32_t GetRelativeColIndex() const + { return mColIndex - mDamageArea.StartCol(); } + + TableArea mDamageArea; // damageArea in cellmap coordinates + bool IsAfterRepeatedHeader() + { return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1)); } + bool StartRepeatedFooter() const + { + return mIsRepeatedFooter && mRowIndex == mRgFirstRowIndex && + mRowIndex != mDamageArea.StartRow(); + } + + nscoord mInitialOffsetI; // offsetI of the first border with + // respect to the table + nscoord mInitialOffsetB; // offsetB of the first border with + // respect to the table + nscoord mNextOffsetB; // offsetB of the next segment + BCBlockDirSeg* mBlockDirInfo; // this array is used differently when + // inline-dir and block-dir borders are drawn + // When inline-dir border are drawn we cache + // the column widths and the width of the + // block-dir borders that arrive from bStart + // When we draw block-dir borders we store + // lengths and width for block-dir borders + // before they are drawn while we move over + // the columns in the damage area + // It has one more elements than columns are + // in the table. + BCInlineDirSeg mInlineSeg; // the inline-dir segment while we + // move over the colums + BCPixelSize mPrevInlineSegBSize; // the bSize of the previous + // inline-dir border + +private: + + bool SetNewRow(nsTableRowFrame* aRow = nullptr); + bool SetNewRowGroup(); + void SetNewData(int32_t aRowIndex, int32_t aColIndex); + +}; + + + +BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable) + : mTable(aTable) + , mTableFirstInFlow(static_cast(aTable->FirstInFlow())) + , mTableCellMap(aTable->GetCellMap()) + , mTableWM(aTable->StyleContext()) +{ + mBlockDirInfo = nullptr; + LogicalMargin childAreaOffset = mTable->GetChildAreaOffset(mTableWM, nullptr); + // y position of first row in damage area + mInitialOffsetB = + mTable->GetPrevInFlow() ? 0 : childAreaOffset.BStart(mTableWM); + mNumTableRows = mTable->GetRowCount(); + mNumTableCols = mTable->GetColCount(); + + // Get the ordered row groups + mTable->OrderRowGroups(mRowGroups); + // initialize to a non existing index + mRepeatedHeaderRowIndex = -99; + + nsIFrame* bgFrame = + nsCSSRendering::FindNonTransparentBackgroundFrame(aTable); + mTableBgColor = bgFrame->StyleBackground(); +} + +bool +BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect) +{ + nsSize containerSize = mTable->GetSize(); + LogicalRect dirtyRect(mTableWM, aDirtyRect, containerSize); + uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex; + startRowIndex = endRowIndex = startColIndex = endColIndex = 0; + bool done = false; + bool haveIntersect = false; + // find startRowIndex, endRowIndex + nscoord rowB = mInitialOffsetB; + for (uint32_t rgIdx = 0; rgIdx < mRowGroups.Length() && !done; rgIdx++) { + nsTableRowGroupFrame* rgFrame = mRowGroups[rgIdx]; + for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame; + rowFrame = rowFrame->GetNextRow()) { + // get the row rect relative to the table rather than the row group + nscoord rowBSize = rowFrame->BSize(mTableWM); + if (haveIntersect) { + // conservatively estimate the half border widths outside the row + nscoord borderHalf = mTable->GetPrevInFlow() ? 0 : nsPresContext:: + CSSPixelsToAppUnits(rowFrame->GetBStartBCBorderWidth() + 1); + if (dirtyRect.BEnd(mTableWM) >= rowB - borderHalf) { + nsTableRowFrame* fifRow = + static_cast(rowFrame->FirstInFlow()); + endRowIndex = fifRow->GetRowIndex(); + } + else done = true; + } + else { + // conservatively estimate the half border widths outside the row + nscoord borderHalf = mTable->GetNextInFlow() ? 0 : nsPresContext:: + CSSPixelsToAppUnits(rowFrame->GetBEndBCBorderWidth() + 1); + if (rowB + rowBSize + borderHalf >= dirtyRect.BStart(mTableWM)) { + mStartRg = rgFrame; + mStartRow = rowFrame; + nsTableRowFrame* fifRow = + static_cast(rowFrame->FirstInFlow()); + startRowIndex = endRowIndex = fifRow->GetRowIndex(); + haveIntersect = true; + } + else { + mInitialOffsetB += rowBSize; + } + } + rowB += rowBSize; + } + } + mNextOffsetB = mInitialOffsetB; + + // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag + // XXX but I don't understand it, so not changing it for now + // table wrapper borders overflow the table, so the table might be + // target to other areas as the NS_FRAME_OUTSIDE_CHILDREN is set + // on the table + if (!haveIntersect) + return false; + // find startColIndex, endColIndex, startColX + haveIntersect = false; + if (0 == mNumTableCols) + return false; + + LogicalMargin childAreaOffset = mTable->GetChildAreaOffset(mTableWM, nullptr); + + // inline position of first col in damage area + mInitialOffsetI = childAreaOffset.IStart(mTableWM); + + nscoord x = 0; + int32_t colIdx; + for (colIdx = 0; colIdx != mNumTableCols; colIdx++) { + nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colIdx); + if (!colFrame) ABORT1(false); + // get the col rect relative to the table rather than the col group + nscoord colISize = colFrame->ISize(mTableWM); + if (haveIntersect) { + // conservatively estimate the iStart half border width outside the col + nscoord iStartBorderHalf = nsPresContext:: + CSSPixelsToAppUnits(colFrame->GetIStartBorderWidth() + 1); + if (dirtyRect.IEnd(mTableWM) >= x - iStartBorderHalf) { + endColIndex = colIdx; + } + else break; + } + else { + // conservatively estimate the iEnd half border width outside the col + nscoord iEndBorderHalf = nsPresContext:: + CSSPixelsToAppUnits(colFrame->GetIEndBorderWidth() + 1); + if (x + colISize + iEndBorderHalf >= dirtyRect.IStart(mTableWM)) { + startColIndex = endColIndex = colIdx; + haveIntersect = true; + } + else { + mInitialOffsetI += colISize; + } + } + x += colISize; + } + if (!haveIntersect) + return false; + mDamageArea = TableArea(startColIndex, startRowIndex, + 1 + DeprecatedAbs(endColIndex - startColIndex), + 1 + endRowIndex - startRowIndex); + + Reset(); + mBlockDirInfo = new BCBlockDirSeg[mDamageArea.ColCount() + 1]; + if (!mBlockDirInfo) + return false; + return true; +} + +void +BCPaintBorderIterator::Reset() +{ + mAtEnd = true; // gets reset when First() is called + mRg = mStartRg; + mPrevRow = nullptr; + mRow = mStartRow; + mRowIndex = 0; + mColIndex = 0; + mRgIndex = -1; + mPrevCell = nullptr; + mCell = nullptr; + mPrevCellData = nullptr; + mCellData = nullptr; + mBCData = nullptr; + ResetVerInfo(); +} + +/** + * Set the iterator data to a new cellmap coordinate + * @param aRowIndex - the row index + * @param aColIndex - the col index + */ +void +BCPaintBorderIterator::SetNewData(int32_t aY, + int32_t aX) +{ + if (!mTableCellMap || !mTableCellMap->mBCInfo) ABORT0(); + + mColIndex = aX; + mRowIndex = aY; + mPrevCellData = mCellData; + if (IsTableIEndMost() && IsTableBEndMost()) { + mCell = nullptr; + mBCData = &mTableCellMap->mBCInfo->mBEndIEndCorner; + } + else if (IsTableIEndMost()) { + mCellData = nullptr; + mBCData = &mTableCellMap->mBCInfo->mIEndBorders.ElementAt(aY); + } + else if (IsTableBEndMost()) { + mCellData = nullptr; + mBCData = &mTableCellMap->mBCInfo->mBEndBorders.ElementAt(aX); + } + else { + if (uint32_t(mRowIndex - mFifRgFirstRowIndex) < mCellMap->mRows.Length()) { + mBCData = nullptr; + mCellData = + (BCCellData*)mCellMap->mRows[mRowIndex - mFifRgFirstRowIndex].SafeElementAt(mColIndex); + if (mCellData) { + mBCData = &mCellData->mData; + if (!mCellData->IsOrig()) { + if (mCellData->IsRowSpan()) { + aY -= mCellData->GetRowSpanOffset(); + } + if (mCellData->IsColSpan()) { + aX -= mCellData->GetColSpanOffset(); + } + if ((aX >= 0) && (aY >= 0)) { + mCellData = (BCCellData*)mCellMap->mRows[aY - mFifRgFirstRowIndex][aX]; + } + } + if (mCellData->IsOrig()) { + mPrevCell = mCell; + mCell = mCellData->GetCellFrame(); + } + } + } + } +} + +/** + * Set the iterator to a new row + * @param aRow - the new row frame, if null the iterator will advance to the + * next row + */ +bool +BCPaintBorderIterator::SetNewRow(nsTableRowFrame* aRow) +{ + mPrevRow = mRow; + mRow = (aRow) ? aRow : mRow->GetNextRow(); + if (mRow) { + mIsNewRow = true; + mRowIndex = mRow->GetRowIndex(); + mColIndex = mDamageArea.StartCol(); + mPrevInlineSegBSize = 0; + if (mIsRepeatedHeader) { + mRepeatedHeaderRowIndex = mRowIndex; + } + } + else { + mAtEnd = true; + } + return !mAtEnd; +} + +/** + * Advance the iterator to the next row group + */ +bool +BCPaintBorderIterator::SetNewRowGroup() +{ + + mRgIndex++; + + mIsRepeatedHeader = false; + mIsRepeatedFooter = false; + + NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds"); + if (uint32_t(mRgIndex) < mRowGroups.Length()) { + mPrevRg = mRg; + mRg = mRowGroups[mRgIndex]; + nsTableRowGroupFrame* fifRg = + static_cast(mRg->FirstInFlow()); + mFifRgFirstRowIndex = fifRg->GetStartRowIndex(); + mRgFirstRowIndex = mRg->GetStartRowIndex(); + mRgLastRowIndex = mRgFirstRowIndex + mRg->GetRowCount() - 1; + + if (SetNewRow(mRg->GetFirstRow())) { + mCellMap = mTableCellMap->GetMapFor(fifRg, nullptr); + if (!mCellMap) ABORT1(false); + } + if (mRg && mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) { + // if mRowGroup doesn't have a prev in flow, then it may be a repeated + // header or footer + const nsStyleDisplay* display = mRg->StyleDisplay(); + if (mRowIndex == mDamageArea.StartRow()) { + mIsRepeatedHeader = (mozilla::StyleDisplay::TableHeaderGroup == display->mDisplay); + } else { + mIsRepeatedFooter = (mozilla::StyleDisplay::TableFooterGroup == display->mDisplay); + } + } + } + else { + mAtEnd = true; + } + return !mAtEnd; +} + +/** + * Move the iterator to the first position in the damageArea + */ +void +BCPaintBorderIterator::First() +{ + if (!mTable || mDamageArea.StartCol() >= mNumTableCols || + mDamageArea.StartRow() >= mNumTableRows) ABORT0(); + + mAtEnd = false; + + uint32_t numRowGroups = mRowGroups.Length(); + for (uint32_t rgY = 0; rgY < numRowGroups; rgY++) { + nsTableRowGroupFrame* rowG = mRowGroups[rgY]; + int32_t start = rowG->GetStartRowIndex(); + int32_t end = start + rowG->GetRowCount() - 1; + if (mDamageArea.StartRow() >= start && mDamageArea.StartRow() <= end) { + mRgIndex = rgY - 1; // SetNewRowGroup increments rowGroupIndex + if (SetNewRowGroup()) { + while (mRowIndex < mDamageArea.StartRow() && !mAtEnd) { + SetNewRow(); + } + if (!mAtEnd) { + SetNewData(mDamageArea.StartRow(), mDamageArea.StartCol()); + } + } + return; + } + } + mAtEnd = true; +} + +/** + * Advance the iterator to the next position + */ +void +BCPaintBorderIterator::Next() +{ + if (mAtEnd) ABORT0(); + mIsNewRow = false; + + mColIndex++; + if (mColIndex > mDamageArea.EndCol()) { + mRowIndex++; + if (mRowIndex == mDamageArea.EndRow()) { + mColIndex = mDamageArea.StartCol(); + } + else if (mRowIndex < mDamageArea.EndRow()) { + if (mRowIndex <= mRgLastRowIndex) { + SetNewRow(); + } + else { + SetNewRowGroup(); + } + } + else { + mAtEnd = true; + } + } + if (!mAtEnd) { + SetNewData(mRowIndex, mColIndex); + } +} + +// XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine +// them +// XXX Update terminology from physical to logical +/** Compute the vertical offset of a vertical border segment + * @param aCornerOwnerSide - which side owns the corner + * @param aCornerSubWidth - how wide is the nonwinning side of the corner + * @param aHorWidth - how wide is the horizontal edge of the corner + * @param aIsStartOfSeg - does this corner start a new segment + * @param aIsBevel - is this corner beveled + * @return - offset in twips + */ +static nscoord +CalcVerCornerOffset(LogicalSide aCornerOwnerSide, + BCPixelSize aCornerSubWidth, + BCPixelSize aHorWidth, + bool aIsStartOfSeg, + bool aIsBevel) +{ + nscoord offset = 0; + // XXX These should be replaced with appropriate side-specific macros (which?) + BCPixelSize smallHalf, largeHalf; + if (IsBlock(aCornerOwnerSide)) { + DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); + if (aIsBevel) { + offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; + } + else { + offset = (eLogicalSideBStart == aCornerOwnerSide) ? smallHalf : -largeHalf; + } + } + else { + DivideBCBorderSize(aHorWidth, smallHalf, largeHalf); + if (aIsBevel) { + offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; + } + else { + offset = (aIsStartOfSeg) ? smallHalf : -largeHalf; + } + } + return nsPresContext::CSSPixelsToAppUnits(offset); +} + +/** Compute the horizontal offset of a horizontal border segment + * @param aCornerOwnerSide - which side owns the corner + * @param aCornerSubWidth - how wide is the nonwinning side of the corner + * @param aVerWidth - how wide is the vertical edge of the corner + * @param aIsStartOfSeg - does this corner start a new segment + * @param aIsBevel - is this corner beveled + * @return - offset in twips + */ +static nscoord +CalcHorCornerOffset(LogicalSide aCornerOwnerSide, + BCPixelSize aCornerSubWidth, + BCPixelSize aVerWidth, + bool aIsStartOfSeg, + bool aIsBevel) +{ + nscoord offset = 0; + // XXX These should be replaced with appropriate side-specific macros (which?) + BCPixelSize smallHalf, largeHalf; + if (IsInline(aCornerOwnerSide)) { + DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); + if (aIsBevel) { + offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; + } + else { + offset = (eLogicalSideIStart == aCornerOwnerSide) ? smallHalf : -largeHalf; + } + } + else { + DivideBCBorderSize(aVerWidth, smallHalf, largeHalf); + if (aIsBevel) { + offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; + } + else { + offset = (aIsStartOfSeg) ? smallHalf : -largeHalf; + } + } + return nsPresContext::CSSPixelsToAppUnits(offset); +} + +BCBlockDirSeg::BCBlockDirSeg() +{ + mCol = nullptr; + mFirstCell = mLastCell = mAjaCell = nullptr; + mOffsetI = mOffsetB = mLength = mWidth = mBStartBevelOffset = 0; + mBStartBevelSide = eLogicalSideBStart; + mOwner = eCellOwner; +} + +/** + * Start a new block-direction segment + * @param aIter - iterator containing the structural information + * @param aBorderOwner - determines the border style + * @param aBlockSegISize - the width of segment in pixel + * @param aInlineSegBSize - the width of the inline-dir segment joining the corner + * at the start + */ +void +BCBlockDirSeg::Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aBlockSegISize, + BCPixelSize aInlineSegBSize) +{ + LogicalSide ownerSide = eLogicalSideBStart; + bool bevel = false; + + nscoord cornerSubWidth = (aIter.mBCData) ? + aIter.mBCData->GetCorner(ownerSide, bevel) : 0; + + bool bStartBevel = (aBlockSegISize > 0) ? bevel : false; + BCPixelSize maxInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize); + nscoord offset = CalcVerCornerOffset(ownerSide, cornerSubWidth, + maxInlineSegBSize, true, + bStartBevel); + + mBStartBevelOffset = bStartBevel ? + nsPresContext::CSSPixelsToAppUnits(maxInlineSegBSize): 0; + // XXX this assumes that only corners where 2 segments join can be beveled + mBStartBevelSide = (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart; + mOffsetB += offset; + mLength = -offset; + mWidth = aBlockSegISize; + mOwner = aBorderOwner; + mFirstCell = aIter.mCell; + mFirstRowGroup = aIter.mRg; + mFirstRow = aIter.mRow; + if (aIter.GetRelativeColIndex() > 0) { + mAjaCell = aIter.mBlockDirInfo[aIter.GetRelativeColIndex() - 1].mLastCell; + } +} + +/** + * Initialize the block-dir segments with information that will persist for any + * block-dir segment in this column + * @param aIter - iterator containing the structural information + */ +void +BCBlockDirSeg::Initialize(BCPaintBorderIterator& aIter) +{ + int32_t relColIndex = aIter.GetRelativeColIndex(); + mCol = aIter.IsTableIEndMost() ? aIter.mBlockDirInfo[relColIndex - 1].mCol : + aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex); + if (!mCol) ABORT0(); + if (0 == relColIndex) { + mOffsetI = aIter.mInitialOffsetI; + } + // set mOffsetI for the next column + if (!aIter.IsDamageAreaIEndMost()) { + aIter.mBlockDirInfo[relColIndex + 1].mOffsetI = + mOffsetI + mCol->ISize(aIter.mTableWM); + } + mOffsetB = aIter.mInitialOffsetB; + mLastCell = aIter.mCell; +} + +/** + * Compute the offsets for the bEnd corner of a block-dir segment + * @param aIter - iterator containing the structural information + * @param aInlineSegBSize - the width of the inline-dir segment joining the corner + * at the start + */ +void +BCBlockDirSeg::GetBEndCorner(BCPaintBorderIterator& aIter, + BCPixelSize aInlineSegBSize) +{ + LogicalSide ownerSide = eLogicalSideBStart; + nscoord cornerSubWidth = 0; + bool bevel = false; + if (aIter.mBCData) { + cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); + } + mIsBEndBevel = (mWidth > 0) ? bevel : false; + mBEndInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize); + mBEndOffset = CalcVerCornerOffset(ownerSide, cornerSubWidth, + mBEndInlineSegBSize, + false, mIsBEndBevel); + mLength += mBEndOffset; +} + +/** + * Paint the block-dir segment + * @param aIter - iterator containing the structural information + * @param aDrawTarget - the draw target + * @param aInlineSegBSize - the width of the inline-dir segment joining the + * corner at the start + */ +void +BCBlockDirSeg::Paint(BCPaintBorderIterator& aIter, + DrawTarget& aDrawTarget, + BCPixelSize aInlineSegBSize) +{ + // get the border style, color and paint the segment + LogicalSide side = + aIter.IsDamageAreaIEndMost() ? eLogicalSideIEnd : eLogicalSideIStart; + int32_t relColIndex = aIter.GetRelativeColIndex(); + nsTableColFrame* col = mCol; if (!col) ABORT0(); + nsTableCellFrame* cell = mFirstCell; // ??? + nsIFrame* owner = nullptr; + uint8_t style = NS_STYLE_BORDER_STYLE_SOLID; + nscolor color = 0xFFFFFFFF; + + // All the tables frames have the same presContext, so we just use any one + // that exists here: + int32_t appUnitsPerDevPixel = col->PresContext()->AppUnitsPerDevPixel(); + + switch (mOwner) { + case eTableOwner: + owner = aIter.mTable; + break; + case eAjaColGroupOwner: + side = eLogicalSideIEnd; + if (!aIter.IsTableIEndMost() && (relColIndex > 0)) { + col = aIter.mBlockDirInfo[relColIndex - 1].mCol; + } + MOZ_FALLTHROUGH; + case eColGroupOwner: + if (col) { + owner = col->GetParent(); + } + break; + case eAjaColOwner: + side = eLogicalSideIEnd; + if (!aIter.IsTableIEndMost() && (relColIndex > 0)) { + col = aIter.mBlockDirInfo[relColIndex - 1].mCol; + } + MOZ_FALLTHROUGH; + case eColOwner: + owner = col; + break; + case eAjaRowGroupOwner: + NS_ERROR("a neighboring rowgroup can never own a vertical border"); + MOZ_FALLTHROUGH; + case eRowGroupOwner: + NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(), + "row group can own border only at table edge"); + owner = mFirstRowGroup; + break; + case eAjaRowOwner: + NS_ERROR("program error"); + MOZ_FALLTHROUGH; + case eRowOwner: + NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(), + "row can own border only at table edge"); + owner = mFirstRow; + break; + case eAjaCellOwner: + side = eLogicalSideIEnd; + cell = mAjaCell; + MOZ_FALLTHROUGH; + case eCellOwner: + owner = cell; + break; + } + if (owner) { + ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &style, &color); + } + BCPixelSize smallHalf, largeHalf; + DivideBCBorderSize(mWidth, smallHalf, largeHalf); + LogicalRect segRect(aIter.mTableWM, + mOffsetI - nsPresContext::CSSPixelsToAppUnits(largeHalf), + mOffsetB, + nsPresContext::CSSPixelsToAppUnits(mWidth), mLength); + nscoord bEndBevelOffset = (mIsBEndBevel) ? + nsPresContext::CSSPixelsToAppUnits(mBEndInlineSegBSize) : 0; + LogicalSide bEndBevelSide = + (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart; + + // Convert logical to physical sides/coordinates for DrawTableBorderSegment. + + nsRect physicalRect = segRect.GetPhysicalRect(aIter.mTableWM, + aIter.mTable->GetSize()); + // XXX For reversed vertical writing-modes (with direction:rtl), we need to + // invert physicalRect's y-position here, with respect to the table. + // However, it's not worth fixing the border positions here until the + // ordering of the table columns themselves is also fixed (bug 1180528). + + uint8_t startBevelSide = aIter.mTableWM.PhysicalSide(mBStartBevelSide); + uint8_t endBevelSide = aIter.mTableWM.PhysicalSide(bEndBevelSide); + nscoord startBevelOffset = mBStartBevelOffset; + nscoord endBevelOffset = bEndBevelOffset; + // In vertical-rl mode, the 'start' and 'end' of the block-dir (horizontal) + // border segment need to be swapped because DrawTableBorderSegment will + // apply the 'start' bevel at the left edge, and 'end' at the right. + // (Note: In this case, startBevelSide/endBevelSide will usually both be + // "top" or "bottom". DrawTableBorderSegment works purely with physical + // coordinates, so it expects startBevelOffset to be the indentation-from- + // the-left for the "start" (left) end of the border-segment, and + // endBevelOffset is the indentation-from-the-right for the "end" (right) + // end of the border-segment. We've got them reversed, since our block dir + // is RTL, so we have to swap them here.) + if (aIter.mTableWM.IsVerticalRL()) { + Swap(startBevelSide, endBevelSide); + Swap(startBevelOffset, endBevelOffset); + } + nsCSSRendering::DrawTableBorderSegment(aDrawTarget, style, color, + aIter.mTableBgColor, physicalRect, + appUnitsPerDevPixel, + nsPresContext::AppUnitsPerCSSPixel(), + startBevelSide, startBevelOffset, + endBevelSide, endBevelOffset); +} + +/** + * Advance the start point of a segment + */ +void +BCBlockDirSeg::AdvanceOffsetB() +{ + mOffsetB += mLength - mBEndOffset; +} + +/** + * Accumulate the current segment + */ +void +BCBlockDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) +{ + mLastCell = aIter.mCell; + mLength += aIter.mRow->BSize(aIter.mTableWM); +} + +BCInlineDirSeg::BCInlineDirSeg() +{ + mOffsetI = mOffsetB = mLength = mWidth = mIStartBevelOffset = 0; + mIStartBevelSide = eLogicalSideBStart; + mFirstCell = mAjaCell = nullptr; +} + +/** Initialize an inline-dir border segment for painting + * @param aIter - iterator storing the current and adjacent frames + * @param aBorderOwner - which frame owns the border + * @param aBEndBlockSegISize - block-dir segment width coming from up + * @param aInlineSegBSize - the thickness of the segment + + */ +void +BCInlineDirSeg::Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aBEndBlockSegISize, + BCPixelSize aInlineSegBSize) +{ + LogicalSide cornerOwnerSide = eLogicalSideBStart; + bool bevel = false; + + mOwner = aBorderOwner; + nscoord cornerSubWidth = (aIter.mBCData) ? + aIter.mBCData->GetCorner(cornerOwnerSide, + bevel) : 0; + + bool iStartBevel = (aInlineSegBSize > 0) ? bevel : false; + int32_t relColIndex = aIter.GetRelativeColIndex(); + nscoord maxBlockSegISize = std::max(aIter.mBlockDirInfo[relColIndex].mWidth, + aBEndBlockSegISize); + nscoord offset = CalcHorCornerOffset(cornerOwnerSide, cornerSubWidth, + maxBlockSegISize, true, iStartBevel); + mIStartBevelOffset = (iStartBevel && (aInlineSegBSize > 0)) ? maxBlockSegISize : 0; + // XXX this assumes that only corners where 2 segments join can be beveled + mIStartBevelSide = (aBEndBlockSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart; + mOffsetI += offset; + mLength = -offset; + mWidth = aInlineSegBSize; + mFirstCell = aIter.mCell; + mAjaCell = (aIter.IsDamageAreaBStartMost()) ? nullptr : + aIter.mBlockDirInfo[relColIndex].mLastCell; +} + +/** + * Compute the offsets for the iEnd corner of an inline-dir segment + * @param aIter - iterator containing the structural information + * @param aIStartSegISize - the iSize of the block-dir segment joining the corner + * at the start + */ +void +BCInlineDirSeg::GetIEndCorner(BCPaintBorderIterator& aIter, + BCPixelSize aIStartSegISize) +{ + LogicalSide ownerSide = eLogicalSideBStart; + nscoord cornerSubWidth = 0; + bool bevel = false; + if (aIter.mBCData) { + cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); + } + + mIsIEndBevel = (mWidth > 0) ? bevel : 0; + int32_t relColIndex = aIter.GetRelativeColIndex(); + nscoord verWidth = std::max(aIter.mBlockDirInfo[relColIndex].mWidth, + aIStartSegISize); + mEndOffset = CalcHorCornerOffset(ownerSide, cornerSubWidth, verWidth, + false, mIsIEndBevel); + mLength += mEndOffset; + mIEndBevelOffset = (mIsIEndBevel) ? + nsPresContext::CSSPixelsToAppUnits(verWidth) : 0; + mIEndBevelSide = (aIStartSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart; +} + +/** + * Paint the inline-dir segment + * @param aIter - iterator containing the structural information + * @param aDrawTarget - the draw target + */ +void +BCInlineDirSeg::Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget) +{ + // get the border style, color and paint the segment + LogicalSide side = + aIter.IsDamageAreaBEndMost() ? eLogicalSideBEnd : eLogicalSideBStart; + nsIFrame* rg = aIter.mRg; if (!rg) ABORT0(); + nsIFrame* row = aIter.mRow; if (!row) ABORT0(); + nsIFrame* cell = mFirstCell; + nsIFrame* col; + nsIFrame* owner = nullptr; + + // All the tables frames have the same presContext, so we just use any one + // that exists here: + int32_t appUnitsPerDevPixel = row->PresContext()->AppUnitsPerDevPixel(); + + uint8_t style = NS_STYLE_BORDER_STYLE_SOLID; + nscolor color = 0xFFFFFFFF; + + switch (mOwner) { + case eTableOwner: + owner = aIter.mTable; + break; + case eAjaColGroupOwner: + NS_ERROR("neighboring colgroups can never own an inline-dir border"); + MOZ_FALLTHROUGH; + case eColGroupOwner: + NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(), + "col group can own border only at the table edge"); + col = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1); + if (!col) ABORT0(); + owner = col->GetParent(); + break; + case eAjaColOwner: + NS_ERROR("neighboring column can never own an inline-dir border"); + MOZ_FALLTHROUGH; + case eColOwner: + NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(), + "col can own border only at the table edge"); + owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1); + break; + case eAjaRowGroupOwner: + side = eLogicalSideBEnd; + rg = (aIter.IsTableBEndMost()) ? aIter.mRg : aIter.mPrevRg; + MOZ_FALLTHROUGH; + case eRowGroupOwner: + owner = rg; + break; + case eAjaRowOwner: + side = eLogicalSideBEnd; + row = (aIter.IsTableBEndMost()) ? aIter.mRow : aIter.mPrevRow; + MOZ_FALLTHROUGH; + case eRowOwner: + owner = row; + break; + case eAjaCellOwner: + side = eLogicalSideBEnd; + // if this is null due to the damage area origin-y > 0, then the border + // won't show up anyway + cell = mAjaCell; + MOZ_FALLTHROUGH; + case eCellOwner: + owner = cell; + break; + } + if (owner) { + ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &style, &color); + } + BCPixelSize smallHalf, largeHalf; + DivideBCBorderSize(mWidth, smallHalf, largeHalf); + LogicalRect segRect(aIter.mTableWM, mOffsetI, + mOffsetB - nsPresContext::CSSPixelsToAppUnits(largeHalf), + mLength, + nsPresContext::CSSPixelsToAppUnits(mWidth)); + + // Convert logical to physical sides/coordinates for DrawTableBorderSegment. + nsRect physicalRect = segRect.GetPhysicalRect(aIter.mTableWM, + aIter.mTable->GetSize()); + uint8_t startBevelSide = aIter.mTableWM.PhysicalSide(mIStartBevelSide); + uint8_t endBevelSide = aIter.mTableWM.PhysicalSide(mIEndBevelSide); + nscoord startBevelOffset = + nsPresContext::CSSPixelsToAppUnits(mIStartBevelOffset); + nscoord endBevelOffset = mIEndBevelOffset; + // With inline-RTL directionality, the 'start' and 'end' of the inline-dir + // border segment need to be swapped because DrawTableBorderSegment will + // apply the 'start' bevel physically at the left or top edge, and 'end' at + // the right or bottom. + // (Note: startBevelSide/endBevelSide will be "top" or "bottom" in horizontal + // writing mode, or "left" or "right" in vertical mode. + // DrawTableBorderSegment works purely with physical coordinates, so it + // expects startBevelOffset to be the indentation-from-the-left or top end + // of the border-segment, and endBevelOffset is the indentation-from-the- + // right or bottom end. If the writing mode is inline-RTL, our "start" and + // "end" will be reversed from this physical-coord view, so we have to swap + // them here. + if (!aIter.mTableWM.IsBidiLTR()) { + Swap(startBevelSide, endBevelSide); + Swap(startBevelOffset, endBevelOffset); + } + nsCSSRendering::DrawTableBorderSegment(aDrawTarget, style, color, + aIter.mTableBgColor, physicalRect, + appUnitsPerDevPixel, + nsPresContext::AppUnitsPerCSSPixel(), + startBevelSide, startBevelOffset, + endBevelSide, endBevelOffset); +} + +/** + * Advance the start point of a segment + */ +void +BCInlineDirSeg::AdvanceOffsetI() +{ + mOffsetI += (mLength - mEndOffset); +} + +/** + * Accumulate the current segment + */ +void +BCInlineDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) +{ + mLength += aIter.mBlockDirInfo[aIter.GetRelativeColIndex()].mColWidth; +} + +/** + * store the column width information while painting inline-dir segment + */ +void +BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex) +{ + if (IsTableIEndMost()) { + mBlockDirInfo[aIndex].mColWidth = mBlockDirInfo[aIndex - 1].mColWidth; + } + else { + nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex); + if (!col) ABORT0(); + mBlockDirInfo[aIndex].mColWidth = col->ISize(mTableWM); + } +} +/** + * Determine if a block-dir segment owns the corner + */ +bool +BCPaintBorderIterator::BlockDirSegmentOwnsCorner() +{ + LogicalSide cornerOwnerSide = eLogicalSideBStart; + bool bevel = false; + if (mBCData) { + mBCData->GetCorner(cornerOwnerSide, bevel); + } + // unitialized ownerside, bevel + return (eLogicalSideBStart == cornerOwnerSide) || + (eLogicalSideBEnd == cornerOwnerSide); +} + +/** + * Paint if necessary an inline-dir segment, otherwise accumulate it + * @param aDrawTarget - the draw target + */ +void +BCPaintBorderIterator::AccumulateOrPaintInlineDirSegment(DrawTarget& aDrawTarget) +{ + + int32_t relColIndex = GetRelativeColIndex(); + // store the current col width if it hasn't been already + if (mBlockDirInfo[relColIndex].mColWidth < 0) { + StoreColumnWidth(relColIndex); + } + + BCBorderOwner borderOwner = eCellOwner; + BCBorderOwner ignoreBorderOwner; + bool isSegStart = true; + bool ignoreSegStart; + + nscoord iStartSegISize = + mBCData ? mBCData->GetIStartEdge(ignoreBorderOwner, ignoreSegStart) : 0; + nscoord bStartSegBSize = + mBCData ? mBCData->GetBStartEdge(borderOwner, isSegStart) : 0; + + if (mIsNewRow || (IsDamageAreaIStartMost() && IsDamageAreaBEndMost())) { + // reset for every new row and on the bottom of the last row + mInlineSeg.mOffsetB = mNextOffsetB; + mNextOffsetB = mNextOffsetB + mRow->BSize(mTableWM); + mInlineSeg.mOffsetI = mInitialOffsetI; + mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize); + } + + if (!IsDamageAreaIStartMost() && (isSegStart || IsDamageAreaIEndMost() || + BlockDirSegmentOwnsCorner())) { + // paint the previous seg or the current one if IsDamageAreaIEndMost() + if (mInlineSeg.mLength > 0) { + mInlineSeg.GetIEndCorner(*this, iStartSegISize); + if (mInlineSeg.mWidth > 0) { + mInlineSeg.Paint(*this, aDrawTarget); + } + mInlineSeg.AdvanceOffsetI(); + } + mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize); + } + mInlineSeg.IncludeCurrentBorder(*this); + mBlockDirInfo[relColIndex].mWidth = iStartSegISize; + mBlockDirInfo[relColIndex].mLastCell = mCell; +} +/** + * Paint if necessary a block-dir segment, otherwise accumulate it + * @param aDrawTarget - the draw target + */ +void +BCPaintBorderIterator::AccumulateOrPaintBlockDirSegment(DrawTarget& aDrawTarget) +{ + BCBorderOwner borderOwner = eCellOwner; + BCBorderOwner ignoreBorderOwner; + bool isSegStart = true; + bool ignoreSegStart; + + nscoord blockSegISize = + mBCData ? mBCData->GetIStartEdge(borderOwner, isSegStart) : 0; + nscoord inlineSegBSize = + mBCData ? mBCData->GetBStartEdge(ignoreBorderOwner, ignoreSegStart) : 0; + + int32_t relColIndex = GetRelativeColIndex(); + BCBlockDirSeg& blockDirSeg = mBlockDirInfo[relColIndex]; + if (!blockDirSeg.mCol) { // on the first damaged row and the first segment in the + // col + blockDirSeg.Initialize(*this); + blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize); + } + + if (!IsDamageAreaBStartMost() && (isSegStart || IsDamageAreaBEndMost() || + IsAfterRepeatedHeader() || + StartRepeatedFooter())) { + // paint the previous seg or the current one if IsDamageAreaBEndMost() + if (blockDirSeg.mLength > 0) { + blockDirSeg.GetBEndCorner(*this, inlineSegBSize); + if (blockDirSeg.mWidth > 0) { + blockDirSeg.Paint(*this, aDrawTarget, inlineSegBSize); + } + blockDirSeg.AdvanceOffsetB(); + } + blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize); + } + blockDirSeg.IncludeCurrentBorder(*this); + mPrevInlineSegBSize = inlineSegBSize; +} + +/** + * Reset the block-dir information cache + */ +void +BCPaintBorderIterator::ResetVerInfo() +{ + if (mBlockDirInfo) { + memset(mBlockDirInfo, 0, mDamageArea.ColCount() * sizeof(BCBlockDirSeg)); + // XXX reinitialize properly + for (auto xIndex : MakeRange(mDamageArea.ColCount())) { + mBlockDirInfo[xIndex].mColWidth = -1; + } + } +} + +/** + * Method to paint BCBorders, this does not use currently display lists although + * it will do this in future + * @param aDrawTarget - the rendering context + * @param aDirtyRect - inside this rectangle the BC Borders will redrawn + */ +void +nsTableFrame::PaintBCBorders(DrawTarget& aDrawTarget, const nsRect& aDirtyRect) +{ + // We first transfer the aDirtyRect into cellmap coordinates to compute which + // cell borders need to be painted + BCPaintBorderIterator iter(this); + if (!iter.SetDamageArea(aDirtyRect)) + return; + + // XXX comment still has physical terminology + // First, paint all of the vertical borders from top to bottom and left to + // right as they become complete. They are painted first, since they are less + // efficient to paint than horizontal segments. They were stored with as few + // segments as possible (since horizontal borders are painted last and + // possibly over them). For every cell in a row that fails in the damage are + // we look up if the current border would start a new segment, if so we paint + // the previously stored vertical segment and start a new segment. After + // this we the now active segment with the current border. These + // segments are stored in mBlockDirInfo to be used on the next row + for (iter.First(); !iter.mAtEnd; iter.Next()) { + iter.AccumulateOrPaintBlockDirSegment(aDrawTarget); + } + + // Next, paint all of the inline-dir border segments from bStart to bEnd reuse + // the mBlockDirInfo array to keep track of col widths and block-dir segments for + // corner calculations + iter.Reset(); + for (iter.First(); !iter.mAtEnd; iter.Next()) { + iter.AccumulateOrPaintInlineDirSegment(aDrawTarget); + } +} + +bool +nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) +{ + bool result = false; + nsTableCellMap* cellMap = GetCellMap(); + NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated."); + if (cellMap) { + result = cellMap->RowHasSpanningCells(aRowIndex, aNumEffCols); + } + return result; +} + +bool +nsTableFrame::RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols) +{ + bool result = false; + nsTableCellMap* cellMap = GetCellMap(); + NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated."); + if (cellMap) { + result = cellMap->RowIsSpannedInto(aRowIndex, aNumEffCols); + } + return result; +} + +/* static */ +void +nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame, + const nsRect& aOrigRect, + const nsRect& aOrigVisualOverflow, + bool aIsFirstReflow) +{ + nsIFrame* parent = aFrame->GetParent(); + NS_ASSERTION(parent, "What happened here?"); + + if (parent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + // Don't bother; we'll invalidate the parent's overflow rect when + // we finish reflowing it. + return; + } + + // The part that looks at both the rect and the overflow rect is a + // bit of a hack. See nsBlockFrame::ReflowLine for an eloquent + // description of its hackishness. + // + // This doesn't really make sense now that we have DLBI. + // This code can probably be simplified a fair bit. + nsRect visualOverflow = aFrame->GetVisualOverflowRect(); + if (aIsFirstReflow || + aOrigRect.TopLeft() != aFrame->GetPosition() || + aOrigVisualOverflow.TopLeft() != visualOverflow.TopLeft()) { + // Invalidate the old and new overflow rects. Note that if the + // frame moved, we can't just use aOrigVisualOverflow, since it's in + // coordinates relative to the old position. So invalidate via + // aFrame's parent, and reposition that overflow rect to the right + // place. + // XXXbz this doesn't handle outlines, does it? + aFrame->InvalidateFrame(); + parent->InvalidateFrameWithRect(aOrigVisualOverflow + aOrigRect.TopLeft()); + } else if (aOrigRect.Size() != aFrame->GetSize() || + aOrigVisualOverflow.Size() != visualOverflow.Size()){ + aFrame->InvalidateFrameWithRect(aOrigVisualOverflow); + aFrame->InvalidateFrame(); + parent->InvalidateFrameWithRect(aOrigRect); + parent->InvalidateFrame(); + } +} diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h new file mode 100644 index 000000000..a78625339 --- /dev/null +++ b/layout/tables/nsTableFrame.h @@ -0,0 +1,997 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTableFrame_h__ +#define nsTableFrame_h__ + +#include "mozilla/Attributes.h" +#include "celldata.h" +#include "imgIContainer.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsStyleCoord.h" +#include "nsStyleConsts.h" +#include "nsCellMap.h" +#include "nsGkAtoms.h" +#include "nsDisplayList.h" +#include "TableArea.h" + +class nsTableCellFrame; +class nsTableCellMap; +class nsTableColFrame; +class nsTableRowGroupFrame; +class nsTableRowFrame; +class nsTableColGroupFrame; +class nsITableLayoutStrategy; +class nsStyleContext; +namespace mozilla { +class WritingMode; +class LogicalMargin; +struct TableReflowInput; +} // namespace mozilla + +struct BCPropertyData; + +static inline bool IS_TABLE_CELL(nsIAtom* frameType) { + return nsGkAtoms::tableCellFrame == frameType || + nsGkAtoms::bcTableCellFrame == frameType; +} + +static inline bool FrameHasBorderOrBackground(nsIFrame* f) { + return (f->StyleVisibility()->IsVisible() && + (!f->StyleBackground()->IsTransparent() || + f->StyleDisplay()->mAppearance || + f->StyleBorder()->HasBorder())); +} + +class nsDisplayTableItem : public nsDisplayItem +{ +public: + nsDisplayTableItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + bool aDrawsBackground = true) : + nsDisplayItem(aBuilder, aFrame), + mPartHasFixedBackground(false), + mDrawsBackground(aDrawsBackground) {} + + // With collapsed borders, parts of the collapsed border can extend outside + // the table part frames, so allow this display element to blow out to our + // overflow rect. This is also useful for row frames that have spanning + // cells extending outside them. + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override; + + virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override; + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) override; + + void UpdateForFrameBackground(nsIFrame* aFrame); + +private: + bool mPartHasFixedBackground; + bool mDrawsBackground; +}; + +class nsAutoPushCurrentTableItem +{ +public: + nsAutoPushCurrentTableItem() : mBuilder(nullptr) {} + + void Push(nsDisplayListBuilder* aBuilder, nsDisplayTableItem* aPushItem) + { + mBuilder = aBuilder; + mOldCurrentItem = aBuilder->GetCurrentTableItem(); + aBuilder->SetCurrentTableItem(aPushItem); +#ifdef DEBUG + mPushedItem = aPushItem; +#endif + } + ~nsAutoPushCurrentTableItem() { + if (!mBuilder) + return; +#ifdef DEBUG + NS_ASSERTION(mBuilder->GetCurrentTableItem() == mPushedItem, + "Someone messed with the current table item behind our back!"); +#endif + mBuilder->SetCurrentTableItem(mOldCurrentItem); + } + +private: + nsDisplayListBuilder* mBuilder; + nsDisplayTableItem* mOldCurrentItem; +#ifdef DEBUG + nsDisplayTableItem* mPushedItem; +#endif +}; + +/* ============================================================================ */ + +enum nsTableColGroupType { + eColGroupContent = 0, // there is real col group content associated + eColGroupAnonymousCol = 1, // the result of a col + eColGroupAnonymousCell = 2 // the result of a cell alone +}; + +enum nsTableColType { + eColContent = 0, // there is real col content associated + eColAnonymousCol = 1, // the result of a span on a col + eColAnonymousColGroup = 2, // the result of a span on a col group + eColAnonymousCell = 3 // the result of a cell alone +}; + +/** + * nsTableFrame maps the inner portion of a table (everything except captions.) + * Used as a pseudo-frame within nsTableWrapperFrame, it may also be used + * stand-alone as the top-level frame. + * + * The principal child list contains row group frames. There is also an + * additional child list, kColGroupList, which contains the col group frames. + */ +class nsTableFrame : public nsContainerFrame +{ + typedef mozilla::image::DrawResult DrawResult; + typedef mozilla::WritingMode WritingMode; + typedef mozilla::LogicalMargin LogicalMargin; + typedef mozilla::TableReflowInput TableReflowInput; + +public: + NS_DECL_QUERYFRAME_TARGET(nsTableFrame) + NS_DECL_FRAMEARENA_HELPERS + + typedef nsTArray FrameTArray; + NS_DECLARE_FRAME_PROPERTY_DELETABLE(PositionedTablePartArray, FrameTArray) + + /** nsTableWrapperFrame has intimate knowledge of the inner table frame */ + friend class nsTableWrapperFrame; + + /** instantiate a new instance of nsTableRowFrame. + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableFrame* NS_NewTableFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + /** sets defaults for table-specific style. + * @see nsIFrame::Init + */ + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + static float GetTwipsToPixels(nsPresContext* aPresContext); + + // Return true if aParentReflowInput.frame or any of its ancestors within + // the containing table have non-auto bsize. (e.g. pct or fixed bsize) + static bool AncestorsHaveStyleBSize(const ReflowInput& aParentReflowInput); + + // See if a special bsize reflow will occur due to having a pct bsize when + // the pct bsize basis may not yet be valid. + static void CheckRequestSpecialBSizeReflow(const ReflowInput& aReflowInput); + + // Notify the frame and its ancestors (up to the containing table) that a special + // height reflow will occur. + static void RequestSpecialBSizeReflow(const ReflowInput& aReflowInput); + + static void RePositionViews(nsIFrame* aFrame); + + static bool PageBreakAfter(nsIFrame* aSourceFrame, + nsIFrame* aNextFrame); + + // Register a positioned table part with its nsTableFrame. These objects will + // be visited by FixupPositionedTableParts after reflow is complete. (See that + // function for more explanation.) Should be called during frame construction. + static void RegisterPositionedTablePart(nsIFrame* aFrame); + + // Unregister a positioned table part with its nsTableFrame. + static void UnregisterPositionedTablePart(nsIFrame* aFrame, + nsIFrame* aDestructRoot); + + nsPoint GetFirstSectionOrigin(const ReflowInput& aReflowInput) const; + /* + * Notification that aAttribute has changed for content inside a table (cell, row, etc) + */ + void AttributeChangedFor(nsIFrame* aFrame, + nsIContent* aContent, + nsIAtom* aAttribute); + + /** @see nsIFrame::DestroyFrom */ + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + /** @see nsIFrame::DidSetStyleContext */ + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual nsMargin GetUsedBorder() const override; + virtual nsMargin GetUsedPadding() const override; + virtual nsMargin GetUsedMargin() const override; + + // Get the offset from the border box to the area where the row groups fit + LogicalMargin GetChildAreaOffset(const WritingMode aWM, + const ReflowInput* aReflowInput) const; + + /** helper method to find the table parent of any table frame object */ + static nsTableFrame* GetTableFrame(nsIFrame* aSourceFrame); + + /* Like GetTableFrame, but will set *aDidPassThrough to false if we don't + * pass through aMustPassThrough on the way to the table. + */ + static nsTableFrame* GetTableFramePassingThrough(nsIFrame* aMustPassThrough, + nsIFrame* aSourceFrame, + bool* aDidPassThrough); + + typedef void (* DisplayGenericTablePartTraversal) + (nsDisplayListBuilder* aBuilder, nsFrame* aFrame, + const nsRect& aDirtyRect, const nsDisplayListSet& aLists); + static void GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, + const nsRect& aDirtyRect, const nsDisplayListSet& aLists); + + /** + * Helper method to handle display common to table frames, rowgroup frames + * and row frames. It creates a background display item for handling events + * if necessary, an outline display item if necessary, and displays + * all the the frame's children. + * @param aDisplayItem the display item created for this part, or null + * if this part's border/background painting is delegated to an ancestor + * @param aTraversal a function that gets called to traverse the table + * part's child frames and add their display list items to a + * display list set. + */ + static void DisplayGenericTablePart(nsDisplayListBuilder* aBuilder, + nsFrame* aFrame, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists, + nsDisplayTableItem* aDisplayItem, + DisplayGenericTablePartTraversal aTraversal = GenericTraversal); + + // Return the closest sibling of aPriorChildFrame (including aPriroChildFrame) + // of type aChildType. + static nsIFrame* GetFrameAtOrBefore(nsIFrame* aParentFrame, + nsIFrame* aPriorChildFrame, + nsIAtom* aChildType); + bool IsAutoBSize(mozilla::WritingMode aWM); + + /** @return true if aDisplayType represents a rowgroup of any sort + * (header, footer, or body) + */ + bool IsRowGroup(mozilla::StyleDisplay aDisplayType) const; + + virtual const nsFrameList& GetChildList(ChildListID aListID) const override; + virtual void GetChildLists(nsTArray* aLists) const override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + /** + * Paint the background of the table and its parts (column groups, + * columns, row groups, rows, and cells), and the table border, and all + * internal borders if border-collapse is on. + */ + DrawResult PaintTableBorderBackground(nsDisplayListBuilder* aBuilder, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt); + + /** Get the outer half (i.e., the part outside the height and width of + * the table) of the largest segment (?) of border-collapsed border on + * the table on each side, or 0 for non border-collapsed tables. + */ + LogicalMargin GetOuterBCBorder(const WritingMode aWM) const; + + /** Same as above, but only if it's included from the border-box width + * of the table. + */ + LogicalMargin GetIncludedOuterBCBorder(const WritingMode aWM) const; + + /** Same as above, but only if it's excluded from the border-box width + * of the table. This is the area that leaks out into the margin + * (or potentially past it, if there is no margin). + */ + LogicalMargin GetExcludedOuterBCBorder(const WritingMode aWM) const; + + /** + * In quirks mode, the size of the table background is reduced + * by the outer BC border. Compute the reduction needed. + */ + nsMargin GetDeflationForBackground(nsPresContext* aPresContext) const; + + /** Get width of table + colgroup + col collapse: elements that + * continue along the length of the whole iStart side. + * see nsTablePainter about continuous borders + */ + nscoord GetContinuousIStartBCBorderWidth() const; + void SetContinuousIStartBCBorderWidth(nscoord aValue); + + friend class nsDelayedCalcBCBorders; + + void AddBCDamageArea(const mozilla::TableArea& aValue); + bool BCRecalcNeeded(nsStyleContext* aOldStyleContext, + nsStyleContext* aNewStyleContext); + void PaintBCBorders(DrawTarget& aDrawTarget, const nsRect& aDirtyRect); + + virtual void MarkIntrinsicISizesDirty() override; + // For border-collapse tables, the caller must not add padding and + // border to the results of these functions. + virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; + virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override; + + virtual mozilla::LogicalSize + ComputeSize(nsRenderingContext* aRenderingContext, + mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, + nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorder, + const mozilla::LogicalSize& aPadding, + ComputeSizeFlags aFlags) override; + + virtual mozilla::LogicalSize + ComputeAutoSize(nsRenderingContext* aRenderingContext, + mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, + nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorder, + const mozilla::LogicalSize& aPadding, + ComputeSizeFlags aFlags) override; + + /** + * A copy of nsFrame::ShrinkWidthToFit that calls a different + * GetPrefISize, since tables have two different ones. + */ + nscoord TableShrinkISizeToFit(nsRenderingContext *aRenderingContext, + nscoord aWidthInCB); + + // XXXldb REWRITE THIS COMMENT! + /** inner tables are reflowed in two steps. + *
    +    * if mFirstPassValid is false, this is our first time through since content was last changed
    +    *   set pass to 1
    +    *   do pass 1
    +    *     get min/max info for all cells in an infinite space
    +    *   do column balancing
    +    *   set mFirstPassValid to true
    +    *   do pass 2
    +    *     use column widths to Reflow cells
    +    * 
    + * + * @see nsIFrame::Reflow + */ + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void ReflowTable(ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nscoord aAvailBSize, + nsIFrame*& aLastChildReflowed, + nsReflowStatus& aStatus); + + nsFrameList& GetColGroups(); + + virtual nsStyleContext* + GetParentStyleContext(nsIFrame** aProviderFrame) const override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::tableFrame + */ + virtual nsIAtom* GetType() const override; + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + if (aFlags & eSupportsCSSTransforms) { + return false; + } + return nsContainerFrame::IsFrameOfType(aFlags); + } + +#ifdef DEBUG_FRAME_DUMP + /** @see nsIFrame::GetFrameName */ + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + /** Return the isize of the column at aColIndex. + * This may only be called on the table's first-in-flow. + */ + nscoord GetColumnISizeFromFirstInFlow(int32_t aColIndex); + + /** Helper to get the column spacing style value. + * The argument refers to the space between column aColIndex and column + * aColIndex + 1. An index of -1 indicates the padding between the table + * and the left border, an index equal to the number of columns indicates + * the padding between the table and the right border. + * + * Although in this class cell spacing does not depend on the index, it + * may be important for overriding classes. + */ + virtual nscoord GetColSpacing(int32_t aColIndex); + + /** Helper to find the sum of the cell spacing between arbitrary columns. + * The argument refers to the space between column aColIndex and column + * aColIndex + 1. An index of -1 indicates the padding between the table + * and the left border, an index equal to the number of columns indicates + * the padding between the table and the right border. + * + * This method is equivalent to + * nscoord result = 0; + * for (i = aStartColIndex; i < aEndColIndex; i++) { + * result += GetColSpacing(i); + * } + * return result; + */ + virtual nscoord GetColSpacing(int32_t aStartColIndex, + int32_t aEndColIndex); + + /** Helper to get the row spacing style value. + * The argument refers to the space between row aRowIndex and row + * aRowIndex + 1. An index of -1 indicates the padding between the table + * and the top border, an index equal to the number of rows indicates + * the padding between the table and the bottom border. + * + * Although in this class cell spacing does not depend on the index, it + * may be important for overriding classes. + */ + virtual nscoord GetRowSpacing(int32_t aRowIndex); + + /** Helper to find the sum of the cell spacing between arbitrary rows. + * The argument refers to the space between row aRowIndex and row + * aRowIndex + 1. An index of -1 indicates the padding between the table + * and the top border, an index equal to the number of rows indicates + * the padding between the table and the bottom border. + * + * This method is equivalent to + * nscoord result = 0; + * for (i = aStartRowIndex; i < aEndRowIndex; i++) { + * result += GetRowSpacing(i); + * } + * return result; + */ + virtual nscoord GetRowSpacing(int32_t aStartRowIndex, + int32_t aEndRowIndex); + +private: + /* For the base implementation of nsTableFrame, cell spacing does not depend + * on row/column indexing. + */ + nscoord GetColSpacing(); + nscoord GetRowSpacing(); + +public: + virtual nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const override; + bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM, + BaselineSharingGroup aBaselineGroup, + nscoord* aBaseline) const override; + + /** return the row span of a cell, taking into account row span magic at the bottom + * of a table. The row span equals the number of rows spanned by aCell starting at + * aStartRowIndex, and can be smaller if aStartRowIndex is greater than the row + * index in which aCell originates. + * + * @param aStartRowIndex the cell + * @param aCell the cell + * + * @return the row span, correcting for row spans that extend beyond the bottom + * of the table. + */ + int32_t GetEffectiveRowSpan(int32_t aStartRowIndex, + const nsTableCellFrame& aCell) const; + int32_t GetEffectiveRowSpan(const nsTableCellFrame& aCell, + nsCellMap* aCellMap = nullptr); + + /** return the col span of a cell, taking into account col span magic at the edge + * of a table. + * + * @param aCell the cell + * + * @return the col span, correcting for col spans that extend beyond the edge + * of the table. + */ + int32_t GetEffectiveColSpan(const nsTableCellFrame& aCell, + nsCellMap* aCellMap = nullptr) const; + + /** indicate whether the row has more than one cell that either originates + * or is spanned from the rows above + */ + bool HasMoreThanOneCell(int32_t aRowIndex) const; + + /** return the column frame associated with aColIndex + * returns nullptr if the col frame has not yet been allocated, or if + * aColIndex is out of range + */ + nsTableColFrame* GetColFrame(int32_t aColIndex) const; + + /** Insert a col frame reference into the colframe cache and adapt the cellmap + * @param aColFrame - the column frame + * @param aColIndex - index where the column should be inserted into the + * colframe cache + */ + void InsertCol(nsTableColFrame& aColFrame, + int32_t aColIndex); + + nsTableColGroupFrame* CreateAnonymousColGroupFrame(nsTableColGroupType aType); + + int32_t DestroyAnonymousColFrames(int32_t aNumFrames); + + // Append aNumColsToAdd anonymous col frames of type eColAnonymousCell to our + // last eColGroupAnonymousCell colgroup. If we have no such colgroup, then + // create one. + void AppendAnonymousColFrames(int32_t aNumColsToAdd); + + // Append aNumColsToAdd anonymous col frames of type aColType to + // aColGroupFrame. If aAddToTable is true, also call AddColsToTable on the + // new cols. + void AppendAnonymousColFrames(nsTableColGroupFrame* aColGroupFrame, + int32_t aNumColsToAdd, + nsTableColType aColType, + bool aAddToTable); + + void MatchCellMapToColCache(nsTableCellMap* aCellMap); + /** empty the column frame cache */ + void ClearColCache(); + + void DidResizeColumns(); + + void AppendCell(nsTableCellFrame& aCellFrame, + int32_t aRowIndex); + + void InsertCells(nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndexBefore); + + void RemoveCell(nsTableCellFrame* aCellFrame, + int32_t aRowIndex); + + void AppendRows(nsTableRowGroupFrame* aRowGroupFrame, + int32_t aRowIndex, + nsTArray& aRowFrames); + + int32_t InsertRows(nsTableRowGroupFrame* aRowGroupFrame, + nsTArray& aFrames, + int32_t aRowIndex, + bool aConsiderSpans); + + void RemoveRows(nsTableRowFrame& aFirstRowFrame, + int32_t aNumRowsToRemove, + bool aConsiderSpans); + + /** Insert multiple rowgroups into the table cellmap handling + * @param aRowGroups - iterator that iterates over the rowgroups to insert + */ + void InsertRowGroups(const nsFrameList::Slice& aRowGroups); + + void InsertColGroups(int32_t aStartColIndex, + const nsFrameList::Slice& aColgroups); + + void RemoveCol(nsTableColGroupFrame* aColGroupFrame, + int32_t aColIndex, + bool aRemoveFromCache, + bool aRemoveFromCellMap); + + bool ColumnHasCellSpacingBefore(int32_t aColIndex) const; + + bool HasPctCol() const; + void SetHasPctCol(bool aValue); + + bool HasCellSpanningPctCol() const; + void SetHasCellSpanningPctCol(bool aValue); + + /** + * To be called on a frame by its parent after setting its size/position and + * calling DidReflow (possibly via FinishReflowChild()). This can also be + * used for child frames which are not being reflowed but did have their size + * or position changed. + * + * @param aFrame The frame to invalidate + * @param aOrigRect The original rect of aFrame (before the change). + * @param aOrigVisualOverflow The original overflow rect of aFrame. + * @param aIsFirstReflow True if the size/position change is due to the + * first reflow of aFrame. + */ + static void InvalidateTableFrame(nsIFrame* aFrame, + const nsRect& aOrigRect, + const nsRect& aOrigVisualOverflow, + bool aIsFirstReflow); + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override; + +protected: + + /** protected constructor. + * @see NewFrame + */ + explicit nsTableFrame(nsStyleContext* aContext); + + /** destructor, responsible for mColumnLayoutData */ + virtual ~nsTableFrame(); + + void InitChildReflowInput(ReflowInput& aReflowInput); + + virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override; + +public: + bool IsRowInserted() const; + void SetRowInserted(bool aValue); + +protected: + + // A helper function to reflow a header or footer with unconstrained height + // to see if it should be made repeatable and also to determine its desired + // height. + nsresult SetupHeaderFooterChild(const TableReflowInput& aReflowInput, + nsTableRowGroupFrame* aFrame, + nscoord* aDesiredHeight); + + void ReflowChildren(TableReflowInput& aReflowInput, + nsReflowStatus& aStatus, + nsIFrame*& aLastChildReflowed, + nsOverflowAreas& aOverflowAreas); + + // This calls the col group and column reflow methods, which do two things: + // (1) set all the dimensions to 0 + // (2) notify the table about colgroups or columns with hidden visibility + void ReflowColGroups(nsRenderingContext* aRenderingContext); + + /** return the isize of the table taking into account visibility collapse + * on columns and colgroups + * @param aBorderPadding the border and padding of the table + */ + nscoord GetCollapsedISize(const WritingMode aWM, + const LogicalMargin& aBorderPadding); + + + /** Adjust the table for visibility.collapse set on rowgroups, rows, + * colgroups and cols + * @param aDesiredSize the metrics of the table + * @param aBorderPadding the border and padding of the table + */ + void AdjustForCollapsingRowsCols(ReflowOutput& aDesiredSize, + const WritingMode aWM, + const LogicalMargin& aBorderPadding); + + /** FixupPositionedTableParts is called at the end of table reflow to reflow + * the absolutely positioned descendants of positioned table parts. This is + * necessary because the dimensions of table parts may change after they've + * been reflowed (e.g. in AdjustForCollapsingRowsCols). + */ + void FixupPositionedTableParts(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput); + + // Clears the list of positioned table parts. + void ClearAllPositionedTableParts(); + + nsITableLayoutStrategy* LayoutStrategy() const { + return static_cast(FirstInFlow())-> + mTableLayoutStrategy; + } + + // Helper for InsertFrames. + void HomogenousInsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList); +private: + /* Handle a row that got inserted during reflow. aNewHeight is the + new height of the table after reflow. */ + void ProcessRowInserted(nscoord aNewHeight); + + // WIDTH AND HEIGHT CALCULATION + +public: + + // calculate the computed block-size of aFrame including its border and + // padding given its reflow state. + nscoord CalcBorderBoxBSize(const ReflowInput& aReflowInput); + +protected: + + // update the desired block-size of this table taking into account the current + // reflow state, the table attributes and the content driven rowgroup bsizes + // this function can change the overflow area + void CalcDesiredBSize(const ReflowInput& aReflowInput, + ReflowOutput& aDesiredSize); + + // The following is a helper for CalcDesiredBSize + + void DistributeBSizeToRows(const ReflowInput& aReflowInput, + nscoord aAmount); + + void PlaceChild(TableReflowInput& aReflowInput, + nsIFrame* aKidFrame, + nsPoint aKidPosition, + ReflowOutput& aKidDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidVisualOverflow); + void PlaceRepeatedFooter(TableReflowInput& aReflowInput, + nsTableRowGroupFrame *aTfoot, + nscoord aFooterHeight); + + nsIFrame* GetFirstBodyRowGroupFrame(); +public: + typedef AutoTArray RowGroupArray; + /** + * Push all our child frames from the aRowGroups array, in order, starting + * from the frame at aPushFrom to the end of the array. The frames are put on + * our overflow list or moved directly to our next-in-flow if one exists. + */ +protected: + void PushChildren(const RowGroupArray& aRowGroups, int32_t aPushFrom); + +public: + // put the children frames in the display order (e.g. thead before tbodies + // before tfoot). This will handle calling GetRowGroupFrame() on the + // children, and not append nulls, so the array is guaranteed to contain + // nsTableRowGroupFrames. If there are multiple theads or tfoots, all but + // the first one are treated as tbodies instead. + + void OrderRowGroups(RowGroupArray& aChildren, + nsTableRowGroupFrame** aHead = nullptr, + nsTableRowGroupFrame** aFoot = nullptr) const; + + // Return the thead, if any + nsTableRowGroupFrame* GetTHead() const; + + // Return the tfoot, if any + nsTableRowGroupFrame* GetTFoot() const; + + // Returns true if there are any cells above the row at + // aRowIndex and spanning into the row at aRowIndex, the number of + // effective columns limits the search up to that column + bool RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols); + + // Returns true if there is a cell originating in aRowIndex + // which spans into the next row, the number of effective + // columns limits the search up to that column + bool RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols); + +protected: + + bool HaveReflowedColGroups() const; + void SetHaveReflowedColGroups(bool aValue); + +public: + bool IsBorderCollapse() const; + + bool NeedToCalcBCBorders() const; + void SetNeedToCalcBCBorders(bool aValue); + + bool NeedToCollapse() const; + void SetNeedToCollapse(bool aValue); + + /** The GeometryDirty bit is similar to the NS_FRAME_IS_DIRTY frame + * state bit, which implies that all descendants are dirty. The + * GeometryDirty still implies that all the parts of the table are + * dirty, but resizing optimizations should still apply to the + * contents of the individual cells. + */ + void SetGeometryDirty() { mBits.mGeometryDirty = true; } + void ClearGeometryDirty() { mBits.mGeometryDirty = false; } + bool IsGeometryDirty() const { return mBits.mGeometryDirty; } + + /** Get the cell map for this table frame. It is not always mCellMap. + * Only the firstInFlow has a legit cell map + */ + nsTableCellMap* GetCellMap() const; + + /** Iterate over the row groups and adjust the row indices of all rows + * whose index is >= aRowIndex. + * @param aRowIndex - start adjusting with this index + * @param aAdjustment - shift the row index by this amount + */ + void AdjustRowIndices(int32_t aRowIndex, + int32_t aAdjustment); + + /** Reset the rowindices of all rows as they might have changed due to + * rowgroup reordering, exclude new row group frames that show in the + * reordering but are not yet inserted into the cellmap + * @param aRowGroupsToExclude - an iterator that will produce the row groups + * to exclude. + */ + void ResetRowIndices(const nsFrameList::Slice& aRowGroupsToExclude); + + nsTArray& GetColCache(); + + +protected: + + void SetBorderCollapse(bool aValue); + + BCPropertyData* GetBCProperty(bool aCreateIfNecessary = false) const; + void SetFullBCDamageArea(); + void CalcBCBorders(); + + void ExpandBCDamageArea(mozilla::TableArea& aRect) const; + + void SetColumnDimensions(nscoord aHeight, WritingMode aWM, + const LogicalMargin& aBorderPadding, + const nsSize& aContainerSize); + + int32_t CollectRows(nsIFrame* aFrame, + nsTArray& aCollection); + +public: /* ----- Cell Map public methods ----- */ + + int32_t GetStartRowIndex(nsTableRowGroupFrame* aRowGroupFrame); + + /** returns the number of rows in this table. + */ + int32_t GetRowCount () const + { + return GetCellMap()->GetRowCount(); + } + + /** returns the number of columns in this table after redundant columns have been removed + */ + int32_t GetEffectiveColCount() const; + + /* return the col count including dead cols */ + int32_t GetColCount () const + { + return GetCellMap()->GetColCount(); + } + + // return the last col index which isn't of type eColAnonymousCell + int32_t GetIndexOfLastRealCol(); + + /** returns true if table-layout:auto */ + bool IsAutoLayout(); + +public: + +#ifdef DEBUG + void Dump(bool aDumpRows, + bool aDumpCols, + bool aDumpCellMap); +#endif + +protected: + /** + * Helper method for RemoveFrame. + */ + void DoRemoveFrame(ChildListID aListID, nsIFrame* aOldFrame); +#ifdef DEBUG + void DumpRowGroup(nsIFrame* aChildFrame); +#endif + // DATA MEMBERS + AutoTArray mColFrames; + + struct TableBits { + uint32_t mHaveReflowedColGroups:1; // have the col groups gotten their initial reflow + uint32_t mHasPctCol:1; // does any cell or col have a pct width + uint32_t mCellSpansPctCol:1; // does any cell span a col with a pct width (or containing a cell with a pct width) + uint32_t mIsBorderCollapse:1; // border collapsing model vs. separate model + uint32_t mRowInserted:1; + uint32_t mNeedToCalcBCBorders:1; + uint32_t mGeometryDirty:1; + uint32_t mIStartContBCBorder:8; + uint32_t mNeedToCollapse:1; // rows, cols that have visibility:collapse need to be collapsed + uint32_t mResizedColumns:1; // have we resized columns since last reflow? + } mBits; + + nsTableCellMap* mCellMap; // maintains the relationships between rows, cols, and cells + nsITableLayoutStrategy* mTableLayoutStrategy;// the layout strategy for this frame + nsFrameList mColGroups; // the list of colgroup frames +}; + + +inline bool nsTableFrame::IsRowGroup(mozilla::StyleDisplay aDisplayType) const +{ + return mozilla::StyleDisplay::TableHeaderGroup == aDisplayType || + mozilla::StyleDisplay::TableFooterGroup == aDisplayType || + mozilla::StyleDisplay::TableRowGroup == aDisplayType; +} + +inline void nsTableFrame::SetHaveReflowedColGroups(bool aValue) +{ + mBits.mHaveReflowedColGroups = aValue; +} + +inline bool nsTableFrame::HaveReflowedColGroups() const +{ + return (bool)mBits.mHaveReflowedColGroups; +} + +inline bool nsTableFrame::HasPctCol() const +{ + return (bool)mBits.mHasPctCol; +} + +inline void nsTableFrame::SetHasPctCol(bool aValue) +{ + mBits.mHasPctCol = (unsigned)aValue; +} + +inline bool nsTableFrame::HasCellSpanningPctCol() const +{ + return (bool)mBits.mCellSpansPctCol; +} + +inline void nsTableFrame::SetHasCellSpanningPctCol(bool aValue) +{ + mBits.mCellSpansPctCol = (unsigned)aValue; +} + +inline bool nsTableFrame::IsRowInserted() const +{ + return (bool)mBits.mRowInserted; +} + +inline void nsTableFrame::SetRowInserted(bool aValue) +{ + mBits.mRowInserted = (unsigned)aValue; +} + +inline void nsTableFrame::SetNeedToCollapse(bool aValue) +{ + static_cast(FirstInFlow())->mBits.mNeedToCollapse = (unsigned)aValue; +} + +inline bool nsTableFrame::NeedToCollapse() const +{ + return (bool) static_cast(FirstInFlow())->mBits.mNeedToCollapse; +} + +inline nsFrameList& nsTableFrame::GetColGroups() +{ + return static_cast(FirstInFlow())->mColGroups; +} + +inline nsTArray& nsTableFrame::GetColCache() +{ + return mColFrames; +} + +inline bool nsTableFrame::IsBorderCollapse() const +{ + return (bool)mBits.mIsBorderCollapse; +} + +inline void nsTableFrame::SetBorderCollapse(bool aValue) +{ + mBits.mIsBorderCollapse = aValue; +} + +inline bool nsTableFrame::NeedToCalcBCBorders() const +{ + return (bool)mBits.mNeedToCalcBCBorders; +} + +inline void nsTableFrame::SetNeedToCalcBCBorders(bool aValue) +{ + mBits.mNeedToCalcBCBorders = (unsigned)aValue; +} + +inline nscoord +nsTableFrame::GetContinuousIStartBCBorderWidth() const +{ + int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel(); + return BC_BORDER_END_HALF_COORD(aPixelsToTwips, mBits.mIStartContBCBorder); +} + +inline void nsTableFrame::SetContinuousIStartBCBorderWidth(nscoord aValue) +{ + mBits.mIStartContBCBorder = (unsigned) aValue; +} + +#define ABORT0() \ +{NS_ASSERTION(false, "CellIterator program error"); \ +return;} + +#define ABORT1(aReturn) \ +{NS_ASSERTION(false, "CellIterator program error"); \ +return aReturn;} + +#endif diff --git a/layout/tables/nsTablePainter.cpp b/layout/tables/nsTablePainter.cpp new file mode 100644 index 000000000..bfe2a7d42 --- /dev/null +++ b/layout/tables/nsTablePainter.cpp @@ -0,0 +1,696 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTableFrame.h" +#include "nsTableRowGroupFrame.h" +#include "nsTableRowFrame.h" +#include "nsTableColGroupFrame.h" +#include "nsTableColFrame.h" +#include "nsTableCellFrame.h" +#include "nsTablePainter.h" +#include "nsCSSRendering.h" +#include "nsDisplayList.h" +#include "mozilla/WritingModes.h" + +/* ~*~ Table Background Painting ~*~ + + Mozilla's Table Background painting follows CSS2.1:17.5.1 + That section does not, however, describe the effect of + borders on background image positioning. What we do is: + + - in separate borders, the borders are passed in so that + their width figures in image positioning, even for rows/cols, which + don't have visible borders. This is done to allow authors + to position row backgrounds by, for example, aligning the + top left corner with the top left padding corner of the + top left table cell in the row in cases where all cells + have consistent border widths. If we didn't honor these + invisible borders, there would be no way to align + backgrounds with the padding edges, and designs would be + lost underneath the border. + + - in collapsing borders, because the borders collapse, we + use the -continuous border- width to synthesize a border + style and pass that in instead of using the element's + assigned style directly. + + The continuous border on a given edge of an element is + the collapse of all borders guaranteed to be continuous + along that edge. Cell borders are ignored (because, for + example, setting a thick border on the leftmost cell + should not shift the row background over; this way a + striped background set on will line up across rows + even if the cells are assigned arbitrary border widths. + + For example, the continuous border on the top edge of a + row group is the collapse of any row group, row, and + table borders involved. (The first row group's top would + be [table-top + row group top + first row top]. It's bottom + would be [row group bottom + last row bottom + next row + top + next row group top].) + The top edge of a column group likewise includes the + table top, row group top, and first row top borders. However, + it *also* includes its own top border, since that is guaranteed + to be continuous. It does not include column borders because + those are not guaranteed to be continuous: there may be two + columns with different borders in a single column group. + + An alternative would be to define the continuous border as + [table? + row group + row] for horizontal + [table? + col group + col] for vertical + This makes it easier to line up backgrounds across elements + despite varying border widths, but it does not give much + flexibility in aligning /to/ those border widths. +*/ + + +/* ~*~ TableBackgroundPainter ~*~ + + The TableBackgroundPainter is created and destroyed in one painting call. + Its principal function is PaintTable, which paints all table element + backgrounds. The initial code in that method sets up an array of column + data that caches the background styles and the border sizes for the + columns and colgroups in TableBackgroundData structs in mCols. Data for + BC borders are calculated and stashed in a synthesized border style struct + in the data struct since collapsed borders aren't the same width as style- + assigned borders. The data struct optimizes by only doing this if there's + an image background; otherwise we don't care. //XXX should also check background-origin + The class then loops through the row groups, rows, and cells. At the cell + level, it paints the backgrounds, one over the other, inside the cell rect. + + The exception to this pattern is when a table element creates a (pseudo) + stacking context. Elements with stacking contexts (e.g., 'opacity' applied) + are passed through, which means their data (and their + descendants' data) are not cached. The full loop is still executed, however, + so that underlying layers can get painted at the cell level. + + The TableBackgroundPainter is then destroyed. + + Elements with stacking contexts set up their own painter to finish the + painting process, since they were skipped. They call the appropriate + sub-part of the loop (e.g. PaintRow) which will paint the frame and + descendants. + + XXX views are going + */ + +using namespace mozilla; +using namespace mozilla::image; + +TableBackgroundPainter::TableBackgroundData::TableBackgroundData() + : mFrame(nullptr) + , mVisible(false) + , mUsesSynthBorder(false) +{ +} + +TableBackgroundPainter::TableBackgroundData::TableBackgroundData(nsIFrame* aFrame) + : mFrame(aFrame) + , mRect(aFrame->GetRect()) + , mVisible(mFrame->IsVisibleForPainting()) + , mUsesSynthBorder(false) +{ +} + +inline bool +TableBackgroundPainter::TableBackgroundData::ShouldSetBCBorder() const +{ + /* we only need accurate border data when positioning background images*/ + if (!mVisible) { + return false; + } + + const nsStyleImageLayers& layers = mFrame->StyleBackground()->mImage; + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) { + if (!layers.mLayers[i].mImage.IsEmpty()) + return true; + } + return false; +} + +void +TableBackgroundPainter::TableBackgroundData::SetBCBorder(const nsMargin& aBorder) +{ + mUsesSynthBorder = true; + mSynthBorderWidths = aBorder; +} + +nsStyleBorder +TableBackgroundPainter::TableBackgroundData::StyleBorder(const nsStyleBorder& aZeroBorder) const +{ + MOZ_ASSERT(mVisible, "Don't call StyleBorder on an invisible TableBackgroundData"); + + if (mUsesSynthBorder) { + nsStyleBorder result = aZeroBorder; + NS_FOR_CSS_SIDES(side) { + result.SetBorderWidth(side, mSynthBorderWidths.Side(side)); + } + return result; + } + + MOZ_ASSERT(mFrame); + + return *mFrame->StyleBorder(); +} + +TableBackgroundPainter::TableBackgroundPainter(nsTableFrame* aTableFrame, + Origin aOrigin, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + const nsPoint& aRenderPt, + uint32_t aBGPaintFlags) + : mPresContext(aPresContext), + mRenderingContext(aRenderingContext), + mRenderPt(aRenderPt), + mDirtyRect(aDirtyRect), + mOrigin(aOrigin), + mZeroBorder(aPresContext), + mBGPaintFlags(aBGPaintFlags) +{ + MOZ_COUNT_CTOR(TableBackgroundPainter); + + NS_FOR_CSS_SIDES(side) { + mZeroBorder.SetBorderStyle(side, NS_STYLE_BORDER_STYLE_SOLID); + mZeroBorder.SetBorderWidth(side, 0); + } + + mIsBorderCollapse = aTableFrame->IsBorderCollapse(); +#ifdef DEBUG + mCompatMode = mPresContext->CompatibilityMode(); +#endif + mNumCols = aTableFrame->GetColCount(); +} + +TableBackgroundPainter::~TableBackgroundPainter() +{ + MOZ_COUNT_DTOR(TableBackgroundPainter); +} + +DrawResult +TableBackgroundPainter::PaintTableFrame(nsTableFrame* aTableFrame, + nsTableRowGroupFrame* aFirstRowGroup, + nsTableRowGroupFrame* aLastRowGroup, + const nsMargin& aDeflate) +{ + MOZ_ASSERT(aTableFrame, "null frame"); + TableBackgroundData tableData(aTableFrame); + tableData.mRect.MoveTo(0,0); //using table's coords + tableData.mRect.Deflate(aDeflate); + WritingMode wm = aTableFrame->GetWritingMode(); + if (mIsBorderCollapse && tableData.ShouldSetBCBorder()) { + if (aFirstRowGroup && aLastRowGroup && mNumCols > 0) { + //only handle non-degenerate tables; we need a more robust BC model + //to make degenerate tables' borders reasonable to deal with + LogicalMargin border(wm); + LogicalMargin tempBorder(wm); + nsTableColFrame* colFrame = aTableFrame->GetColFrame(mNumCols - 1); + if (colFrame) { + colFrame->GetContinuousBCBorderWidth(wm, tempBorder); + } + border.IEnd(wm) = tempBorder.IEnd(wm); + + aLastRowGroup->GetContinuousBCBorderWidth(wm, tempBorder); + border.BEnd(wm) = tempBorder.BEnd(wm); + + nsTableRowFrame* rowFrame = aFirstRowGroup->GetFirstRow(); + if (rowFrame) { + rowFrame->GetContinuousBCBorderWidth(wm, tempBorder); + border.BStart(wm) = tempBorder.BStart(wm); + } + + border.IStart(wm) = aTableFrame->GetContinuousIStartBCBorderWidth(); + + tableData.SetBCBorder(border.GetPhysicalMargin(wm)); + } + } + + DrawResult result = DrawResult::SUCCESS; + + if (tableData.IsVisible()) { + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, + mRenderingContext, + mDirtyRect, + tableData.mRect + mRenderPt, + tableData.mFrame, + mBGPaintFlags); + + result &= + nsCSSRendering::PaintBackgroundWithSC(params, + tableData.mFrame->StyleContext(), + tableData.StyleBorder(mZeroBorder)); + } + + return result; +} + +void +TableBackgroundPainter::TranslateContext(nscoord aDX, + nscoord aDY) +{ + mRenderPt += nsPoint(aDX, aDY); + for (auto& col : mCols) { + col.mCol.mRect.MoveBy(-aDX, -aDY); + } + for (auto& colGroup : mColGroups) { + colGroup.mRect.MoveBy(-aDX, -aDY); + } +} + +TableBackgroundPainter::ColData::ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData) + : mCol(aFrame) + , mColGroup(aColGroupBGData) +{ +} + +DrawResult +TableBackgroundPainter::PaintTable(nsTableFrame* aTableFrame, + const nsMargin& aDeflate, + bool aPaintTableBackground) +{ + NS_PRECONDITION(aTableFrame, "null table frame"); + + nsTableFrame::RowGroupArray rowGroups; + aTableFrame->OrderRowGroups(rowGroups); + WritingMode wm = aTableFrame->GetWritingMode(); + + DrawResult result = DrawResult::SUCCESS; + + if (rowGroups.Length() < 1) { //degenerate case + if (aPaintTableBackground) { + result &= PaintTableFrame(aTableFrame, nullptr, nullptr, nsMargin(0,0,0,0)); + } + /* No cells; nothing else to paint */ + return result; + } + + if (aPaintTableBackground) { + result &= + PaintTableFrame(aTableFrame, rowGroups[0], rowGroups[rowGroups.Length() - 1], + aDeflate); + } + + /*Set up column background/border data*/ + if (mNumCols > 0) { + nsFrameList& colGroupList = aTableFrame->GetColGroups(); + NS_ASSERTION(colGroupList.FirstChild(), "table should have at least one colgroup"); + + // Collect all col group frames first so that we know how many there are. + nsTArray colGroupFrames; + for (nsTableColGroupFrame* cgFrame = static_cast(colGroupList.FirstChild()); + cgFrame; cgFrame = static_cast(cgFrame->GetNextSibling())) { + + if (cgFrame->GetColCount() < 1) { + //No columns, no cells, so no need for data + continue; + } + colGroupFrames.AppendElement(cgFrame); + } + + // Ensure that mColGroups won't reallocate during the loop below, because + // we grab references to its contents and need those to stay valid until + // mColGroups is destroyed as part of TablePainter destruction. + mColGroups.SetCapacity(colGroupFrames.Length()); + + LogicalMargin border(wm); + /* BC iStart borders aren't stored on cols, but the previous column's + iEnd border is the next one's iStart border.*/ + //Start with table's iStart border. + nscoord lastIStartBorder = aTableFrame->GetContinuousIStartBCBorderWidth(); + + for (nsTableColGroupFrame* cgFrame : colGroupFrames) { + /*Create data struct for column group*/ + TableBackgroundData& cgData = *mColGroups.AppendElement(TableBackgroundData(cgFrame)); + if (mIsBorderCollapse && cgData.ShouldSetBCBorder()) { + border.IStart(wm) = lastIStartBorder; + cgFrame->GetContinuousBCBorderWidth(wm, border); + cgData.SetBCBorder(border.GetPhysicalMargin(wm)); + } + + /*Loop over columns in this colgroup*/ + for (nsTableColFrame* col = cgFrame->GetFirstColumn(); col; + col = static_cast(col->GetNextSibling())) { + MOZ_ASSERT(size_t(col->GetColIndex()) == mCols.Length()); + // Store a reference to the colGroup in the ColData element. + ColData& colData = *mCols.AppendElement(ColData(col, cgData)); + //Bring column mRect into table's coord system + colData.mCol.mRect.MoveBy(cgData.mRect.x, cgData.mRect.y); + if (mIsBorderCollapse) { + border.IStart(wm) = lastIStartBorder; + lastIStartBorder = col->GetContinuousBCBorderWidth(wm, border); + if (colData.mCol.ShouldSetBCBorder()) { + colData.mCol.SetBCBorder(border.GetPhysicalMargin(wm)); + } + } + } + } + } + + for (uint32_t i = 0; i < rowGroups.Length(); i++) { + nsTableRowGroupFrame* rg = rowGroups[i]; + TableBackgroundData rowGroupBGData(rg); + // Need to compute the right rect via GetOffsetTo, since the row + // group may not be a child of the table. + rowGroupBGData.mRect.MoveTo(rg->GetOffsetTo(aTableFrame)); + + // We have to draw backgrounds not only within the overflow region of this + // row group, but also possibly (in the case of column / column group + // backgrounds) at its pre-relative-positioning location. + nsRect rgVisualOverflow = rg->GetVisualOverflowRectRelativeToSelf(); + nsRect rgOverflowRect = rgVisualOverflow + rg->GetPosition(); + nsRect rgNormalRect = rgVisualOverflow + rg->GetNormalPosition(); + + if (rgOverflowRect.Union(rgNormalRect).Intersects(mDirtyRect - mRenderPt)) { + result &= + PaintRowGroup(rg, rowGroupBGData, rg->IsPseudoStackingContextFromStyle()); + } + } + + return result; +} + +DrawResult +TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame) +{ + return PaintRowGroup(aFrame, TableBackgroundData(aFrame), false); +} + +DrawResult +TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame, + TableBackgroundData aRowGroupBGData, + bool aPassThrough) +{ + MOZ_ASSERT(aFrame, "null frame"); + + nsTableRowFrame* firstRow = aFrame->GetFirstRow(); + WritingMode wm = aFrame->GetWritingMode(); + + /* Load row group data */ + if (aPassThrough) { + aRowGroupBGData.MakeInvisible(); + } else { + if (mIsBorderCollapse && aRowGroupBGData.ShouldSetBCBorder()) { + LogicalMargin border(wm); + if (firstRow) { + //pick up first row's bstart border (= rg bstart border) + firstRow->GetContinuousBCBorderWidth(wm, border); + /* (row group doesn't store its bstart border) */ + } + //overwrite sides+bottom borders with rg's own + aFrame->GetContinuousBCBorderWidth(wm, border); + aRowGroupBGData.SetBCBorder(border.GetPhysicalMargin(wm)); + } + aPassThrough = !aRowGroupBGData.IsVisible(); + } + + /* translate everything into row group coord system*/ + if (eOrigin_TableRowGroup != mOrigin) { + TranslateContext(aRowGroupBGData.mRect.x, aRowGroupBGData.mRect.y); + } + nsRect rgRect = aRowGroupBGData.mRect; + aRowGroupBGData.mRect.MoveTo(0, 0); + + /* Find the right row to start with */ + + // Note that mDirtyRect - mRenderPt is guaranteed to be in the row + // group's coordinate system here, so passing its .y to + // GetFirstRowContaining is ok. + nscoord overflowAbove; + nsIFrame* cursor = aFrame->GetFirstRowContaining(mDirtyRect.y - mRenderPt.y, &overflowAbove); + + // Sadly, it seems like there may be non-row frames in there... or something? + // There are certainly null-checks in GetFirstRow() and GetNextRow(). :( + while (cursor && cursor->GetType() != nsGkAtoms::tableRowFrame) { + cursor = cursor->GetNextSibling(); + } + + // It's OK if cursor is null here. + nsTableRowFrame* row = static_cast(cursor); + if (!row) { + // No useful cursor; just start at the top. Don't bother to set up a + // cursor; if we've gotten this far then we've already built the display + // list for the rowgroup, so not having a cursor means that there's some + // good reason we don't have a cursor and we shouldn't create one here. + row = firstRow; + } + + DrawResult result = DrawResult::SUCCESS; + + /* Finally paint */ + for (; row; row = row->GetNextRow()) { + TableBackgroundData rowBackgroundData(row); + + // Be sure to consider our positions both pre- and post-relative + // positioning, since we potentially need to paint at both places. + nscoord rowY = std::min(rowBackgroundData.mRect.y, row->GetNormalPosition().y); + + // Intersect wouldn't handle rowspans. + if (cursor && + (mDirtyRect.YMost() - mRenderPt.y) <= (rowY - overflowAbove)) { + // All done; cells originating in later rows can't intersect mDirtyRect. + break; + } + + result &= + PaintRow(row, aRowGroupBGData, rowBackgroundData, + aPassThrough || row->IsPseudoStackingContextFromStyle()); + } + + /* translate back into table coord system */ + if (eOrigin_TableRowGroup != mOrigin) { + TranslateContext(-rgRect.x, -rgRect.y); + } + + return result; +} + +DrawResult +TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame) +{ + return PaintRow(aFrame, TableBackgroundData(), TableBackgroundData(aFrame), false); +} + +DrawResult +TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame, + const TableBackgroundData& aRowGroupBGData, + TableBackgroundData aRowBGData, + bool aPassThrough) +{ + MOZ_ASSERT(aFrame, "null frame"); + + /* Load row data */ + WritingMode wm = aFrame->GetWritingMode(); + if (aPassThrough) { + aRowBGData.MakeInvisible(); + } else { + if (mIsBorderCollapse && aRowBGData.ShouldSetBCBorder()) { + LogicalMargin border(wm); + nsTableRowFrame* nextRow = aFrame->GetNextRow(); + if (nextRow) { //outer bStart after us is inner bEnd for us + border.BEnd(wm) = nextRow->GetOuterBStartContBCBorderWidth(); + } + else { //acquire rg's bEnd border + nsTableRowGroupFrame* rowGroup = static_cast(aFrame->GetParent()); + rowGroup->GetContinuousBCBorderWidth(wm, border); + } + //get the rest of the borders; will overwrite all but bEnd + aFrame->GetContinuousBCBorderWidth(wm, border); + + aRowBGData.SetBCBorder(border.GetPhysicalMargin(wm)); + } + aPassThrough = !aRowBGData.IsVisible(); + } + + /* Translate */ + if (eOrigin_TableRow == mOrigin) { + /* If we originate from the row, then make the row the origin. */ + aRowBGData.mRect.MoveTo(0, 0); + } + //else: Use row group's coord system -> no translation necessary + + DrawResult result = DrawResult::SUCCESS; + + for (nsTableCellFrame* cell = aFrame->GetFirstCell(); cell; cell = cell->GetNextCell()) { + nsRect cellBGRect, rowBGRect, rowGroupBGRect, colBGRect; + ComputeCellBackgrounds(cell, aRowGroupBGData, aRowBGData, + cellBGRect, rowBGRect, + rowGroupBGRect, colBGRect); + + // Find the union of all the cell background layers. + nsRect combinedRect(cellBGRect); + combinedRect.UnionRect(combinedRect, rowBGRect); + combinedRect.UnionRect(combinedRect, rowGroupBGRect); + combinedRect.UnionRect(combinedRect, colBGRect); + + if (combinedRect.Intersects(mDirtyRect)) { + bool passCell = aPassThrough || cell->IsPseudoStackingContextFromStyle(); + result &= + PaintCell(cell, aRowGroupBGData, aRowBGData, cellBGRect, rowBGRect, + rowGroupBGRect, colBGRect, passCell); + } + } + + return result; +} + +DrawResult +TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell, + const TableBackgroundData& aRowGroupBGData, + const TableBackgroundData& aRowBGData, + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect, + bool aPassSelf) +{ + MOZ_ASSERT(aCell, "null frame"); + + const nsStyleTableBorder* cellTableStyle; + cellTableStyle = aCell->StyleTableBorder(); + if (NS_STYLE_TABLE_EMPTY_CELLS_SHOW != cellTableStyle->mEmptyCells && + aCell->GetContentEmpty() && !mIsBorderCollapse) { + return DrawResult::SUCCESS; + } + + int32_t colIndex; + aCell->GetColIndex(colIndex); + // We're checking mNumCols instead of mCols.Length() here because mCols can + // be empty even if mNumCols > 0. + NS_ASSERTION(size_t(colIndex) < mNumCols, "out-of-bounds column index"); + if (size_t(colIndex) >= mNumCols) { + return DrawResult::SUCCESS; + } + + // If callers call PaintRowGroup or PaintRow directly, we haven't processed + // our columns. Ignore column / col group backgrounds in that case. + bool haveColumns = !mCols.IsEmpty(); + + DrawResult result = DrawResult::SUCCESS; + + //Paint column group background + if (haveColumns && mCols[colIndex].mColGroup.IsVisible()) { + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, + mDirtyRect, + mCols[colIndex].mColGroup.mRect + mRenderPt, + mCols[colIndex].mColGroup.mFrame, + mBGPaintFlags); + params.bgClipRect = &aColBGRect; + result &= + nsCSSRendering::PaintBackgroundWithSC(params, + mCols[colIndex].mColGroup.mFrame->StyleContext(), + mCols[colIndex].mColGroup.StyleBorder(mZeroBorder)); + } + + //Paint column background + if (haveColumns && mCols[colIndex].mCol.IsVisible()) { + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, + mDirtyRect, + mCols[colIndex].mCol.mRect + mRenderPt, + mCols[colIndex].mCol.mFrame, + mBGPaintFlags); + params.bgClipRect = &aColBGRect; + result &= + nsCSSRendering::PaintBackgroundWithSC(params, + mCols[colIndex].mCol.mFrame->StyleContext(), + mCols[colIndex].mCol.StyleBorder(mZeroBorder)); + } + + //Paint row group background + if (aRowGroupBGData.IsVisible()) { + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, + mDirtyRect, + aRowGroupBGData.mRect + mRenderPt, + aRowGroupBGData.mFrame, mBGPaintFlags); + params.bgClipRect = &aRowGroupBGRect; + result &= + nsCSSRendering::PaintBackgroundWithSC(params, + aRowGroupBGData.mFrame->StyleContext(), + aRowGroupBGData.StyleBorder(mZeroBorder)); + } + + //Paint row background + if (aRowBGData.IsVisible()) { + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext, + mDirtyRect, + aRowBGData.mRect + mRenderPt, + aRowBGData.mFrame, mBGPaintFlags); + params.bgClipRect = &aRowBGRect; + result &= + nsCSSRendering::PaintBackgroundWithSC(params, + aRowBGData.mFrame->StyleContext(), + aRowBGData.StyleBorder(mZeroBorder)); + } + + //Paint cell background in border-collapse unless we're just passing + if (mIsBorderCollapse && !aPassSelf) { + result &= + aCell->PaintCellBackground(mRenderingContext, mDirtyRect, + aCellBGRect.TopLeft(), mBGPaintFlags); + } + + return result; +} + +void +TableBackgroundPainter::ComputeCellBackgrounds(nsTableCellFrame* aCell, + const TableBackgroundData& aRowGroupBGData, + const TableBackgroundData& aRowBGData, + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect) +{ + // We need to compute table background layer rects for this cell space, + // adjusted for possible relative positioning. This behavior is not specified + // at the time of this writing, but the approach below should be web + // compatible. + // + // Our goal is that relative positioning of a table part should leave + // backgrounds *under* that part unchanged. ("Under" being defined by CSS 2.1 + // Section 17.5.1.) If a cell is positioned, we do not expect the row + // background to move. On the other hand, the backgrounds of layers *above* + // the positioned part are taken along for the ride -- for example, + // positioning a row group will also cause the row background to be drawn in + // the new location, unless it has further positioning applied. + // + // Each table part layer has its position stored in the coordinate space of + // the layer below (which is to say, its geometric parent), and the stored + // position is the post-relative-positioning one. The position of each + // background layer rect is thus determined by peeling off successive table + // part layers, removing the contribution of each layer's positioning one by + // one. Every rect we generate will be the same size, the size of the cell + // space. + + // We cannot rely on the row group background data to be available, since some + // callers enter through PaintRow. + nsIFrame* rowGroupFrame = + aRowGroupBGData.mFrame ? aRowGroupBGData.mFrame : aRowBGData.mFrame->GetParent(); + + // The cell background goes at the cell's position, translated to use the same + // coordinate system as aRowBGData. + aCellBGRect = aCell->GetRect() + aRowBGData.mRect.TopLeft() + mRenderPt; + + // The row background goes at the normal position of the cell, which is to say + // the position without relative positioning applied. + aRowBGRect = aCellBGRect + (aCell->GetNormalPosition() - aCell->GetPosition()); + + // The row group background goes at the position we'd find the cell if neither + // the cell's relative positioning nor the row's were applied. + aRowGroupBGRect = aRowBGRect + + (aRowBGData.mFrame->GetNormalPosition() - aRowBGData.mFrame->GetPosition()); + + // The column and column group backgrounds (they're always at the same + // location, since relative positioning doesn't apply to columns or column + // groups) are drawn at the position we'd find the cell if none of the cell's, + // row's, or row group's relative positioning were applied. + aColBGRect = aRowGroupBGRect + + (rowGroupFrame->GetNormalPosition() - rowGroupFrame->GetPosition()); + +} diff --git a/layout/tables/nsTablePainter.h b/layout/tables/nsTablePainter.h new file mode 100644 index 000000000..dfba42156 --- /dev/null +++ b/layout/tables/nsTablePainter.h @@ -0,0 +1,268 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsTablePainter_h__ +#define nsTablePainter_h__ + +#include "imgIContainer.h" + +#include "celldata.h" + +// flags for Paint, PaintChild, PaintChildren are currently only used by tables. +//Table-based paint call; not a direct call as with views +#define NS_PAINT_FLAG_TABLE_BG_PAINT 0x00000001 +//Cells should paint their backgrounds only, no children +#define NS_PAINT_FLAG_TABLE_CELL_BG_PASS 0x00000002 + +class nsIFrame; +class nsTableFrame; +class nsTableRowGroupFrame; +class nsTableRowFrame; +class nsTableCellFrame; + +class TableBackgroundPainter +{ + /* + * Helper class for painting table backgrounds + * + */ + + typedef mozilla::image::DrawResult DrawResult; + + public: + + enum Origin { eOrigin_Table, eOrigin_TableRowGroup, eOrigin_TableRow }; + + /** Public constructor + * @param aTableFrame - the table's table frame + * @param aOrigin - what type of table frame is creating this instance + * @param aPresContext - the presentation context + * @param aRenderingContext - the rendering context + * @param aDirtyRect - the area that needs to be painted, + * relative to aRenderingContext + * @param aPt - offset of the table frame relative to + * aRenderingContext + * @param aBGPaintFlags - Flags of the nsCSSRendering::PAINTBG_* variety + */ + TableBackgroundPainter(nsTableFrame* aTableFrame, + Origin aOrigin, + nsPresContext* aPresContext, + nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + const nsPoint& aPt, + uint32_t aBGPaintFlags); + + /** Destructor */ + ~TableBackgroundPainter(); + + /* ~*~ The Border Collapse Painting Issue ~*~ + + In border-collapse, the *table* paints the cells' borders, + so we need to make sure the backgrounds get painted first + (underneath) by doing a cell-background-only painting pass. + */ + + /* ~*~ Using nsTablePainter Background Painting ~*~ + + A call to PaintTable will normally paint all of the table's + elements (except for the table background, if aPaintTableBackground + is false). + Elements with views however, will be skipped and must create their + own painter to call the appropriate paint function in their ::Paint + method (e.g. painter.PaintRow in nsTableRow::Paint) + */ + + /** Paint background for the table frame (if requested) and its children + * down through cells. + * (Cells themselves will only be painted in border collapse) + * Table must do a flagged TABLE_BG_PAINT ::Paint call on its + * children afterwards + * @param aTableFrame - the table frame + * @param aDeflate - deflation needed to bring table's mRect + * to the outer grid lines in border-collapse + * @param aPaintTableBackground - if true, the table background + * is included, otherwise it isn't + * @returns DrawResult::SUCCESS if all painting was successful. If some + * painting failed or an improved result could be achieved by sync + * decoding images, returns another value. + */ + DrawResult PaintTable(nsTableFrame* aTableFrame, const nsMargin& aDeflate, + bool aPaintTableBackground); + + /** Paint background for the row group and its children down through cells + * (Cells themselves will only be painted in border collapse) + * Standards mode only + * Table Row Group must do a flagged TABLE_BG_PAINT ::Paint call on its + * children afterwards + * @param aFrame - the table row group frame + * @returns DrawResult::SUCCESS if all painting was successful. If some + * painting failed or an improved result could be achieved by sync + * decoding images, returns another value. + */ + DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame); + + /** Paint background for the row and its children down through cells + * (Cells themselves will only be painted in border collapse) + * Standards mode only + * Table Row must do a flagged TABLE_BG_PAINT ::Paint call on its + * children afterwards + * @param aFrame - the table row frame + * @returns DrawResult::SUCCESS if all painting was successful. If some + * painting failed or an improved result could be achieved by sync + * decoding images, returns another value. + */ + DrawResult PaintRow(nsTableRowFrame* aFrame); + + private: + struct TableBackgroundData; + + /** Paint table frame's background + * @param aTableFrame - the table frame + * @param aFirstRowGroup - the first (in layout order) row group + * may be null + * @param aLastRowGroup - the last (in layout order) row group + * may be null + * @param aDeflate - adjustment to frame's rect (used for quirks BC) + * may be null + */ + DrawResult PaintTableFrame(nsTableFrame* aTableFrame, + nsTableRowGroupFrame* aFirstRowGroup, + nsTableRowGroupFrame* aLastRowGroup, + const nsMargin& aDeflate); + + /* aPassThrough params indicate whether to paint the element or to just + * pass through and paint underlying layers only. + * aRowGroupBGData is not a const reference because the function modifies + * its copy. Same for aRowBGData in PaintRow. + * See Public versions for function descriptions + */ + DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame, + TableBackgroundData aRowGroupBGData, + bool aPassThrough); + + DrawResult PaintRow(nsTableRowFrame* aFrame, + const TableBackgroundData& aRowGroupBGData, + TableBackgroundData aRowBGData, + bool aPassThrough); + + /** Paint table background layers for this cell space + * Also paints cell's own background in border-collapse mode + * @param aCell - the cell + * @param aRowGroupBGData - background drawing info for the row group + * @param aRowBGData - background drawing info for the row + * @param aCellBGRect - background rect for the cell + * @param aRowBGRect - background rect for the row + * @param aRowGroupBGRect - background rect for the row group + * @param aColBGRect - background rect for the column and column group + * @param aPassSelf - pass this cell; i.e. paint only underlying layers + */ + DrawResult PaintCell(nsTableCellFrame* aCell, + const TableBackgroundData& aRowGroupBGData, + const TableBackgroundData& aRowBGData, + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect, + bool aPassSelf); + + /** Compute table background layer positions for this cell space + * @param aCell - the cell + * @param aRowGroupBGData - background drawing info for the row group + * @param aRowBGData - background drawing info for the row + * @param aCellBGRectOut - outparam: background rect for the cell + * @param aRowBGRectOut - outparam: background rect for the row + * @param aRowGroupBGRectOut - outparam: background rect for the row group + * @param aColBGRectOut - outparam: background rect for the column + and column group + */ + void ComputeCellBackgrounds(nsTableCellFrame* aCell, + const TableBackgroundData& aRowGroupBGData, + const TableBackgroundData& aRowBGData, + nsRect& aCellBGRect, + nsRect& aRowBGRect, + nsRect& aRowGroupBGRect, + nsRect& aColBGRect); + + /** Translate mRenderingContext, mDirtyRect, and mCols' column and + * colgroup coords + * @param aDX - origin's x-coord change + * @param aDY - origin's y-coord change + */ + void TranslateContext(nscoord aDX, + nscoord aDY); + + struct TableBackgroundData { + public: + /** + * Construct an empty TableBackgroundData instance, which is invisible. + */ + TableBackgroundData(); + + /** + * Construct a TableBackgroundData instance for a frame. Visibility will + * be derived from the frame and can be overridden using MakeInvisible(). + */ + explicit TableBackgroundData(nsIFrame* aFrame); + + /** Destructor */ + ~TableBackgroundData() {} + + /** Data is valid & frame is visible */ + bool IsVisible() const { return mVisible; } + + /** Override visibility of the frame, force it to be invisible */ + void MakeInvisible() { mVisible = false; } + + /** True if need to set border-collapse border; must call SetFull beforehand */ + bool ShouldSetBCBorder() const; + + /** Set border-collapse border with aBorderWidth as widths */ + void SetBCBorder(const nsMargin& aBorderWidth); + + /** + * @param aZeroBorder An nsStyleBorder instance that has been initialized + * for the right nsPresContext, with all border widths + * set to zero and border styles set to solid. + * @return The nsStyleBorder that should be used for rendering + * this background. + */ + nsStyleBorder StyleBorder(const nsStyleBorder& aZeroBorder) const; + + nsIFrame* const mFrame; + + /** mRect is the rect of mFrame in the current coordinate system */ + nsRect mRect; + + private: + nsMargin mSynthBorderWidths; + bool mVisible; + bool mUsesSynthBorder; + }; + + struct ColData { + ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData); + TableBackgroundData mCol; + TableBackgroundData& mColGroup; // reference to col's parent colgroup's data, owned by TablePainter in mColGroups + }; + + nsPresContext* mPresContext; + nsRenderingContext& mRenderingContext; + nsPoint mRenderPt; + nsRect mDirtyRect; +#ifdef DEBUG + nsCompatibility mCompatMode; +#endif + bool mIsBorderCollapse; + Origin mOrigin; //user's table frame type + + nsTArray mColGroups; + nsTArray mCols; + size_t mNumCols; + + nsStyleBorder mZeroBorder; //cached zero-width border + uint32_t mBGPaintFlags; +}; + +#endif diff --git a/layout/tables/nsTableRowFrame.cpp b/layout/tables/nsTableRowFrame.cpp new file mode 100644 index 000000000..81b5d6699 --- /dev/null +++ b/layout/tables/nsTableRowFrame.cpp @@ -0,0 +1,1517 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Maybe.h" + +#include "nsTableRowFrame.h" +#include "nsTableRowGroupFrame.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsCSSRendering.h" +#include "nsHTMLParts.h" +#include "nsTableColGroupFrame.h" +#include "nsTableColFrame.h" +#include "nsCOMPtr.h" +#include "nsDisplayList.h" +#include "nsIFrameInlines.h" +#include + +using namespace mozilla; + +namespace mozilla { + +struct TableCellReflowInput : public ReflowInput +{ + TableCellReflowInput(nsPresContext* aPresContext, + const ReflowInput& aParentReflowInput, + nsIFrame* aFrame, + const LogicalSize& aAvailableSpace, + uint32_t aFlags = 0) + : ReflowInput(aPresContext, aParentReflowInput, aFrame, + aAvailableSpace, nullptr, aFlags) + { + } + + void FixUp(const LogicalSize& aAvailSpace); +}; + +} // namespace mozilla + +void TableCellReflowInput::FixUp(const LogicalSize& aAvailSpace) +{ + // fix the mComputed values during a pass 2 reflow since the cell can be a percentage base + NS_WARNING_ASSERTION( + NS_UNCONSTRAINEDSIZE != aAvailSpace.ISize(mWritingMode), + "have unconstrained inline-size; this should only result from very large " + "sizes, not attempts at intrinsic inline size calculation"); + if (NS_UNCONSTRAINEDSIZE != ComputedISize()) { + nscoord computedISize = aAvailSpace.ISize(mWritingMode) - + ComputedLogicalBorderPadding().IStartEnd(mWritingMode); + computedISize = std::max(0, computedISize); + SetComputedISize(computedISize); + } + if (NS_UNCONSTRAINEDSIZE != ComputedBSize() && + NS_UNCONSTRAINEDSIZE != aAvailSpace.BSize(mWritingMode)) { + nscoord computedBSize = aAvailSpace.BSize(mWritingMode) - + ComputedLogicalBorderPadding().BStartEnd(mWritingMode); + computedBSize = std::max(0, computedBSize); + SetComputedBSize(computedBSize); + } +} + +void +nsTableRowFrame::InitChildReflowInput(nsPresContext& aPresContext, + const LogicalSize& aAvailSize, + bool aBorderCollapse, + TableCellReflowInput& aReflowInput) +{ + nsMargin collapseBorder; + nsMargin* pCollapseBorder = nullptr; + if (aBorderCollapse) { + // we only reflow cells, so don't need to check frame type + nsBCTableCellFrame* bcCellFrame = (nsBCTableCellFrame*)aReflowInput.mFrame; + if (bcCellFrame) { + WritingMode wm = GetWritingMode(); + collapseBorder = bcCellFrame->GetBorderWidth(wm).GetPhysicalMargin(wm); + pCollapseBorder = &collapseBorder; + } + } + aReflowInput.Init(&aPresContext, nullptr, pCollapseBorder); + aReflowInput.FixUp(aAvailSize); +} + +void +nsTableRowFrame::SetFixedBSize(nscoord aValue) +{ + nscoord bsize = std::max(0, aValue); + if (HasFixedBSize()) { + if (bsize > mStyleFixedBSize) { + mStyleFixedBSize = bsize; + } + } + else { + mStyleFixedBSize = bsize; + if (bsize > 0) { + SetHasFixedBSize(true); + } + } +} + +void +nsTableRowFrame::SetPctBSize(float aPctValue, + bool aForce) +{ + nscoord bsize = std::max(0, NSToCoordRound(aPctValue * 100.0f)); + if (HasPctBSize()) { + if ((bsize > mStylePctBSize) || aForce) { + mStylePctBSize = bsize; + } + } + else { + mStylePctBSize = bsize; + if (bsize > 0) { + SetHasPctBSize(true); + } + } +} + +/* ----------- nsTableRowFrame ---------- */ + +NS_QUERYFRAME_HEAD(nsTableRowFrame) + NS_QUERYFRAME_ENTRY(nsTableRowFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +nsTableRowFrame::nsTableRowFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext) +{ + mBits.mRowIndex = mBits.mFirstInserted = 0; + ResetBSize(0); +} + +nsTableRowFrame::~nsTableRowFrame() +{ +} + +void +nsTableRowFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + // Let the base class do its initialization + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + + NS_ASSERTION(mozilla::StyleDisplay::TableRow == StyleDisplay()->mDisplay, + "wrong display on table row frame"); + + if (aPrevInFlow) { + // Set the row index + nsTableRowFrame* rowFrame = (nsTableRowFrame*)aPrevInFlow; + + SetRowIndex(rowFrame->GetRowIndex()); + } +} + +void +nsTableRowFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { + nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot); + } + + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +/* virtual */ void +nsTableRowFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsContainerFrame::DidSetStyleContext(aOldStyleContext); + + if (!aOldStyleContext) //avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) { + TableArea damageArea(0, GetRowIndex(), tableFrame->GetColCount(), 1); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void +nsTableRowFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + + DrainSelfOverflowList(); // ensure the last frame is in mFrames + const nsFrameList::Slice& newCells = mFrames.AppendFrames(nullptr, aFrameList); + + // Add the new cell frames to the table + nsTableFrame* tableFrame = GetTableFrame(); + for (nsFrameList::Enumerator e(newCells) ; !e.AtEnd(); e.Next()) { + nsIFrame *childFrame = e.get(); + NS_ASSERTION(IS_TABLE_CELL(childFrame->GetType()), + "Not a table cell frame/pseudo frame construction failure"); + tableFrame->AppendCell(static_cast(*childFrame), GetRowIndex()); + } + + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); +} + + +void +nsTableRowFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames + //Insert Frames in the frame list + const nsFrameList::Slice& newCells = mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); + + // Get the table frame + nsTableFrame* tableFrame = GetTableFrame(); + nsIAtom* cellFrameType = tableFrame->IsBorderCollapse() ? nsGkAtoms::bcTableCellFrame : nsGkAtoms::tableCellFrame; + nsTableCellFrame* prevCellFrame = (nsTableCellFrame *)nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, cellFrameType); + nsTArray cellChildren; + for (nsFrameList::Enumerator e(newCells); !e.AtEnd(); e.Next()) { + nsIFrame *childFrame = e.get(); + NS_ASSERTION(IS_TABLE_CELL(childFrame->GetType()), + "Not a table cell frame/pseudo frame construction failure"); + cellChildren.AppendElement(static_cast(childFrame)); + } + // insert the cells into the cell map + int32_t colIndex = -1; + if (prevCellFrame) { + prevCellFrame->GetColIndex(colIndex); + } + tableFrame->InsertCells(cellChildren, GetRowIndex(), colIndex); + + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); +} + +void +nsTableRowFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + + MOZ_ASSERT((nsTableCellFrame*)do_QueryFrame(aOldFrame)); + nsTableCellFrame* cellFrame = static_cast(aOldFrame); + // remove the cell from the cell map + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->RemoveCell(cellFrame, GetRowIndex()); + + // Remove the frame and destroy it + mFrames.DestroyFrame(aOldFrame); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + + tableFrame->SetGeometryDirty(); +} + +/* virtual */ nsMargin +nsTableRowFrame::GetUsedMargin() const +{ + return nsMargin(0,0,0,0); +} + +/* virtual */ nsMargin +nsTableRowFrame::GetUsedBorder() const +{ + return nsMargin(0,0,0,0); +} + +/* virtual */ nsMargin +nsTableRowFrame::GetUsedPadding() const +{ + return nsMargin(0,0,0,0); +} + +nscoord +GetBSizeOfRowsSpannedBelowFirst(nsTableCellFrame& aTableCellFrame, + nsTableFrame& aTableFrame, + const WritingMode aWM) +{ + nscoord bsize = 0; + int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(aTableCellFrame); + // add in bsize of rows spanned beyond the 1st one + nsIFrame* nextRow = aTableCellFrame.GetParent()->GetNextSibling(); + for (int32_t rowX = 1; ((rowX < rowSpan) && nextRow);) { + if (nsGkAtoms::tableRowFrame == nextRow->GetType()) { + bsize += nextRow->BSize(aWM); + rowX++; + } + bsize += aTableFrame.GetRowSpacing(rowX); + nextRow = nextRow->GetNextSibling(); + } + return bsize; +} + +nsTableCellFrame* +nsTableRowFrame::GetFirstCell() +{ + for (nsIFrame* childFrame : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); + if (cellFrame) { + return cellFrame; + } + } + return nullptr; +} + +/** + * Post-reflow hook. This is where the table row does its post-processing + */ +void +nsTableRowFrame::DidResize() +{ + // Resize and re-align the cell frames based on our row bsize + nsTableFrame* tableFrame = GetTableFrame(); + + WritingMode wm = GetWritingMode(); + ReflowOutput desiredSize(wm); + desiredSize.SetSize(wm, GetLogicalSize(wm)); + desiredSize.SetOverflowAreasToDesiredBounds(); + + nsSize containerSize = mRect.Size(); + + for (nsIFrame* childFrame : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); + if (cellFrame) { + nscoord cellBSize = BSize(wm) + + GetBSizeOfRowsSpannedBelowFirst(*cellFrame, *tableFrame, wm); + + // If the bsize for the cell has changed, we need to reset it; + // and in vertical-rl mode, we need to update the cell's block position + // to account for the containerSize, which may not have been known + // earlier, so we always apply it here. + LogicalSize cellSize = cellFrame->GetLogicalSize(wm); + if (cellSize.BSize(wm) != cellBSize || wm.IsVerticalRL()) { + nsRect cellOldRect = cellFrame->GetRect(); + nsRect cellVisualOverflow = cellFrame->GetVisualOverflowRect(); + + if (wm.IsVerticalRL()) { + // Get the old position of the cell, as we want to preserve its + // inline coordinate. + LogicalPoint oldPos = + cellFrame->GetLogicalPosition(wm, containerSize); + + // The cell should normally be aligned with the row's block-start, + // so set the B component of the position to zero: + LogicalPoint newPos(wm, oldPos.I(wm), 0); + + // ...unless relative positioning is in effect, in which case the + // cell may have been moved away from the row's block-start + if (cellFrame->IsRelativelyPositioned()) { + // Find out where the cell would have been without relative + // positioning. + LogicalPoint oldNormalPos = + cellFrame->GetLogicalNormalPosition(wm, containerSize); + // The difference (if any) between oldPos and oldNormalPos reflects + // relative positioning that was applied to the cell, and which we + // need to incorporate when resetting the position. + newPos.B(wm) = oldPos.B(wm) - oldNormalPos.B(wm); + } + + if (oldPos != newPos) { + cellFrame->SetPosition(wm, newPos, containerSize); + nsTableFrame::RePositionViews(cellFrame); + } + } + + cellSize.BSize(wm) = cellBSize; + cellFrame->SetSize(wm, cellSize); + nsTableFrame::InvalidateTableFrame(cellFrame, cellOldRect, + cellVisualOverflow, + false); + } + + // realign cell content based on the new bsize. We might be able to + // skip this if the bsize didn't change... maybe. Hard to tell. + cellFrame->BlockDirAlignChild(wm, mMaxCellAscent); + + // Always store the overflow, even if the height didn't change, since + // we'll lose part of our overflow area otherwise. + ConsiderChildOverflow(desiredSize.mOverflowAreas, cellFrame); + + // Note that if the cell's *content* needs to change in response + // to this height, it will get a special bsize reflow. + } + } + FinishAndStoreOverflow(&desiredSize); + if (HasView()) { + nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this, GetView(), + desiredSize.VisualOverflow(), 0); + } + // Let our base class do the usual work +} + +// returns max-ascent amongst all cells that have 'vertical-align: baseline' +// *including* cells with rowspans +nscoord nsTableRowFrame::GetMaxCellAscent() const +{ + return mMaxCellAscent; +} + +nscoord nsTableRowFrame::GetRowBaseline(WritingMode aWM) +{ + if (mMaxCellAscent) { + return mMaxCellAscent; + } + + // If we don't have a baseline on any of the cells we go for the lowest + // content edge of the inner block frames. + // Every table cell has a cell frame with its border and padding. Inside + // the cell is a block frame. The cell is as high as the tallest cell in + // the parent row. As a consequence the block frame might not touch both + // the top and the bottom padding of it parent cell frame at the same time. + // + // bbbbbbbbbbbbbbbbbb cell border: b + // bppppppppppppppppb cell padding: p + // bpxxxxxxxxxxxxxxpb inner block: x + // bpx xpb + // bpx xpb + // bpx xpb + // bpxxxxxxxxxxxxxxpb base line + // bp pb + // bp pb + // bppppppppppppppppb + // bbbbbbbbbbbbbbbbbb + + nscoord ascent = 0; + nsSize containerSize = GetSize(); + for (nsIFrame* childFrame : mFrames) { + if (IS_TABLE_CELL(childFrame->GetType())) { + nsIFrame* firstKid = childFrame->PrincipalChildList().FirstChild(); + ascent = std::max(ascent, + LogicalRect(aWM, firstKid->GetNormalRect(), + containerSize).BEnd(aWM)); + } + } + return ascent; +} + +nscoord +nsTableRowFrame::GetInitialBSize(nscoord aPctBasis) const +{ + nscoord bsize = 0; + if ((aPctBasis > 0) && HasPctBSize()) { + bsize = NSToCoordRound(GetPctBSize() * (float)aPctBasis); + } + if (HasFixedBSize()) { + bsize = std::max(bsize, GetFixedBSize()); + } + return std::max(bsize, GetContentBSize()); +} + +void +nsTableRowFrame::ResetBSize(nscoord aFixedBSize) +{ + SetHasFixedBSize(false); + SetHasPctBSize(false); + SetFixedBSize(0); + SetPctBSize(0); + SetContentBSize(0); + + if (aFixedBSize > 0) { + SetFixedBSize(aFixedBSize); + } + + mMaxCellAscent = 0; + mMaxCellDescent = 0; +} + +void +nsTableRowFrame::UpdateBSize(nscoord aBSize, + nscoord aAscent, + nscoord aDescent, + nsTableFrame* aTableFrame, + nsTableCellFrame* aCellFrame) +{ + if (!aTableFrame || !aCellFrame) { + NS_ASSERTION(false , "invalid call"); + return; + } + + if (aBSize != NS_UNCONSTRAINEDSIZE) { + if (!(aCellFrame->HasVerticalAlignBaseline())) { // only the cell's height matters + if (GetInitialBSize() < aBSize) { + int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame); + if (rowSpan == 1) { + SetContentBSize(aBSize); + } + } + } + else { // the alignment on the baseline can change the bsize + NS_ASSERTION((aAscent != NS_UNCONSTRAINEDSIZE) && + (aDescent != NS_UNCONSTRAINEDSIZE), "invalid call"); + // see if this is a long ascender + if (mMaxCellAscent < aAscent) { + mMaxCellAscent = aAscent; + } + // see if this is a long descender and without rowspan + if (mMaxCellDescent < aDescent) { + int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame); + if (rowSpan == 1) { + mMaxCellDescent = aDescent; + } + } + // keep the tallest bsize in sync + if (GetInitialBSize() < mMaxCellAscent + mMaxCellDescent) { + SetContentBSize(mMaxCellAscent + mMaxCellDescent); + } + } + } +} + +nscoord +nsTableRowFrame::CalcBSize(const ReflowInput& aReflowInput) +{ + nsTableFrame* tableFrame = GetTableFrame(); + nscoord computedBSize = (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedBSize()) + ? 0 : aReflowInput.ComputedBSize(); + ResetBSize(computedBSize); + + WritingMode wm = aReflowInput.GetWritingMode(); + const nsStylePosition* position = StylePosition(); + const nsStyleCoord& bsizeStyleCoord = position->BSize(wm); + if (bsizeStyleCoord.ConvertsToLength()) { + SetFixedBSize(nsRuleNode::ComputeCoordPercentCalc(bsizeStyleCoord, 0)); + } + else if (eStyleUnit_Percent == bsizeStyleCoord.GetUnit()) { + SetPctBSize(bsizeStyleCoord.GetPercentValue()); + } + // calc() with percentages is treated like 'auto' on table rows. + + for (nsIFrame* kidFrame : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); + if (cellFrame) { + MOZ_ASSERT(cellFrame->GetWritingMode() == wm); + LogicalSize desSize = cellFrame->GetDesiredSize(); + if ((NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) && !GetPrevInFlow()) { + CalculateCellActualBSize(cellFrame, desSize.BSize(wm), wm); + } + // bsize may have changed, adjust descent to absorb any excess difference + nscoord ascent; + if (!kidFrame->PrincipalChildList().FirstChild()->PrincipalChildList().FirstChild()) + ascent = desSize.BSize(wm); + else + ascent = cellFrame->GetCellBaseline(); + nscoord descent = desSize.BSize(wm) - ascent; + UpdateBSize(desSize.BSize(wm), ascent, descent, tableFrame, cellFrame); + } + } + return GetInitialBSize(); +} + +/** + * We need a custom display item for table row backgrounds. This is only used + * when the table row is the root of a stacking context (e.g., has 'opacity'). + * Table row backgrounds can extend beyond the row frame bounds, when + * the row contains row-spanning cells. + */ +class nsDisplayTableRowBackground : public nsDisplayTableItem { +public: + nsDisplayTableRowBackground(nsDisplayListBuilder* aBuilder, + nsTableRowFrame* aFrame) : + nsDisplayTableItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableRowBackground); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTableRowBackground() { + MOZ_COUNT_DTOR(nsDisplayTableRowBackground); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + NS_DISPLAY_DECL_NAME("TableRowBackground", TYPE_TABLE_ROW_BACKGROUND) +}; + +void +nsDisplayTableRowBackground::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + auto rowFrame = static_cast(mFrame); + TableBackgroundPainter painter(rowFrame->GetTableFrame(), + TableBackgroundPainter::eOrigin_TableRow, + mFrame->PresContext(), *aCtx, + mVisibleRect, ToReferenceFrame(), + aBuilder->GetBackgroundPaintFlags()); + + DrawResult result = painter.PaintRow(rowFrame); + nsDisplayTableItemGeometry::UpdateDrawResult(this, result); +} + +void +nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsDisplayTableItem* item = nullptr; + if (IsVisibleInSelection(aBuilder)) { + bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext(); + if (isRoot) { + // This background is created regardless of whether this frame is + // visible or not. Visibility decisions are delegated to the + // table background painter. + // We would use nsDisplayGeneric for this rare case except that we + // need the background to be larger than the row frame in some + // cases. + item = new (aBuilder) nsDisplayTableRowBackground(aBuilder, this); + aLists.BorderBackground()->AppendNewToTop(item); + } + } + nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item); +} + +nsIFrame::LogicalSides +nsTableRowFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const +{ + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return LogicalSides(); + } + + LogicalSides skip; + if (nullptr != GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (nullptr != GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +// Calculate the cell's actual bsize given its pass2 bsize. +// Takes into account the specified bsize (in the style). +// Modifies the desired bsize that is passed in. +nsresult +nsTableRowFrame::CalculateCellActualBSize(nsTableCellFrame* aCellFrame, + nscoord& aDesiredBSize, + WritingMode aWM) +{ + nscoord specifiedBSize = 0; + + // Get the bsize specified in the style information + const nsStylePosition* position = aCellFrame->StylePosition(); + + int32_t rowSpan = GetTableFrame()->GetEffectiveRowSpan(*aCellFrame); + + const nsStyleCoord& bsizeStyleCoord = position->BSize(aWM); + switch (bsizeStyleCoord.GetUnit()) { + case eStyleUnit_Calc: { + if (bsizeStyleCoord.CalcHasPercent()) { + // Treat this like "auto" + break; + } + // Fall through to the coord case + MOZ_FALLTHROUGH; + } + case eStyleUnit_Coord: { + // In quirks mode, table cell isize should be content-box, but bsize + // should be border-box. + // Because of this historic anomaly, we do not use quirk.css + // (since we can't specify one value of box-sizing for isize and another + // for bsize) + specifiedBSize = nsRuleNode::ComputeCoordPercentCalc(bsizeStyleCoord, 0); + if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks && + position->mBoxSizing == StyleBoxSizing::Content) { + specifiedBSize += + aCellFrame->GetLogicalUsedBorderAndPadding(aWM).BStartEnd(aWM); + } + + if (1 == rowSpan) { + SetFixedBSize(specifiedBSize); + } + break; + } + case eStyleUnit_Percent: { + if (1 == rowSpan) { + SetPctBSize(bsizeStyleCoord.GetPercentValue()); + } + // pct bsizes are handled when all of the cells are finished, + // so don't set specifiedBSize + break; + } + case eStyleUnit_Auto: + default: + break; + } + + // If the specified bsize is greater than the desired bsize, + // then use the specified bsize + if (specifiedBSize > aDesiredBSize) { + aDesiredBSize = specifiedBSize; + } + + return NS_OK; +} + +// Calculates the available isize for the table cell based on the known +// column isizes taking into account column spans and column spacing +static nscoord +CalcAvailISize(nsTableFrame& aTableFrame, + nsTableCellFrame& aCellFrame) +{ + nscoord cellAvailISize = 0; + int32_t colIndex; + aCellFrame.GetColIndex(colIndex); + int32_t colspan = aTableFrame.GetEffectiveColSpan(aCellFrame); + NS_ASSERTION(colspan > 0, "effective colspan should be positive"); + nsTableFrame* fifTable = + static_cast(aTableFrame.FirstInFlow()); + + for (int32_t spanX = 0; spanX < colspan; spanX++) { + cellAvailISize += + fifTable->GetColumnISizeFromFirstInFlow(colIndex + spanX); + if (spanX > 0 && + aTableFrame.ColumnHasCellSpacingBefore(colIndex + spanX)) { + cellAvailISize += aTableFrame.GetColSpacing(colIndex + spanX - 1); + } + } + return cellAvailISize; +} + +nscoord +GetSpaceBetween(int32_t aPrevColIndex, + int32_t aColIndex, + int32_t aColSpan, + nsTableFrame& aTableFrame, + bool aCheckVisibility) +{ + nscoord space = 0; + int32_t colIdx; + nsTableFrame* fifTable = + static_cast(aTableFrame.FirstInFlow()); + for (colIdx = aPrevColIndex + 1; aColIndex > colIdx; colIdx++) { + bool isCollapsed = false; + if (!aCheckVisibility) { + space += fifTable->GetColumnISizeFromFirstInFlow(colIdx); + } + else { + nsTableColFrame* colFrame = aTableFrame.GetColFrame(colIdx); + const nsStyleVisibility* colVis = colFrame->StyleVisibility(); + bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible); + nsIFrame* cgFrame = colFrame->GetParent(); + const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); + bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == + groupVis->mVisible); + isCollapsed = collapseCol || collapseGroup; + if (!isCollapsed) + space += fifTable->GetColumnISizeFromFirstInFlow(colIdx); + } + if (!isCollapsed && aTableFrame.ColumnHasCellSpacingBefore(colIdx)) { + space += aTableFrame.GetColSpacing(colIdx - 1); + } + } + return space; +} + +// subtract the bsizes of aRow's prev in flows from the unpaginated bsize +static +nscoord CalcBSizeFromUnpaginatedBSize(nsTableRowFrame& aRow, + WritingMode aWM) +{ + nscoord bsize = 0; + nsTableRowFrame* firstInFlow = + static_cast(aRow.FirstInFlow()); + if (firstInFlow->HasUnpaginatedBSize()) { + bsize = firstInFlow->GetUnpaginatedBSize(); + for (nsIFrame* prevInFlow = aRow.GetPrevInFlow(); prevInFlow; + prevInFlow = prevInFlow->GetPrevInFlow()) { + bsize -= prevInFlow->BSize(aWM); + } + } + return std::max(bsize, 0); +} + +void +nsTableRowFrame::ReflowChildren(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsTableFrame& aTableFrame, + nsReflowStatus& aStatus) +{ + aStatus = NS_FRAME_COMPLETE; + + // XXXldb Should we be checking constrained bsize instead? + const bool isPaginated = aPresContext->IsPaginated(); + const bool borderCollapse = aTableFrame.IsBorderCollapse(); + + int32_t cellColSpan = 1; // must be defined here so it's set properly for non-cell kids + + // remember the col index of the previous cell to handle rowspans into this row + int32_t prevColIndex = -1; + nscoord iCoord = 0; // running total of children inline-coord offset + + // This computes the max of all cell bsizes + nscoord cellMaxBSize = 0; + + // Reflow each of our existing cell frames + WritingMode wm = aReflowInput.GetWritingMode(); + nsSize containerSize = + aReflowInput.ComputedSizeAsContainerIfConstrained(); + + for (nsIFrame* kidFrame : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); + if (!cellFrame) { + // XXXldb nsCSSFrameConstructor needs to enforce this! + NS_NOTREACHED("yikes, a non-row child"); + + // it's an unknown frame type, give it a generic reflow and ignore the results + TableCellReflowInput + kidReflowInput(aPresContext, aReflowInput, kidFrame, + LogicalSize(kidFrame->GetWritingMode(), 0, 0), + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(*aPresContext, LogicalSize(wm), false, kidReflowInput); + ReflowOutput desiredSize(aReflowInput); + nsReflowStatus status; + ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowInput, 0, 0, 0, status); + kidFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); + + continue; + } + + // See if we should only reflow the dirty child frames + bool doReflowChild = true; + if (!aReflowInput.ShouldReflowAllKids() && + !aTableFrame.IsGeometryDirty() && + !NS_SUBTREE_DIRTY(kidFrame)) { + if (!aReflowInput.mFlags.mSpecialBSizeReflow) + doReflowChild = false; + } + else if ((NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize())) { + // We don't reflow a rowspan >1 cell here with a constrained bsize. + // That happens in nsTableRowGroupFrame::SplitSpanningCells. + if (aTableFrame.GetEffectiveRowSpan(*cellFrame) > 1) { + doReflowChild = false; + } + } + if (aReflowInput.mFlags.mSpecialBSizeReflow) { + if (!isPaginated && + !cellFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { + continue; + } + } + + int32_t cellColIndex; + cellFrame->GetColIndex(cellColIndex); + cellColSpan = aTableFrame.GetEffectiveColSpan(*cellFrame); + + // If the adjacent cell is in a prior row (because of a rowspan) add in the space + if (prevColIndex != (cellColIndex - 1)) { + iCoord += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, aTableFrame, + false); + } + + // remember the rightmost (ltr) or leftmost (rtl) column this cell spans into + prevColIndex = cellColIndex + (cellColSpan - 1); + + // Reflow the child frame + nsRect kidRect = kidFrame->GetRect(); + LogicalPoint origKidNormalPosition = + kidFrame->GetLogicalNormalPosition(wm, containerSize); + // All cells' no-relative-positioning position should be snapped to the + // row's bstart edge. + // This doesn't hold in vertical-rl mode, where we don't yet know the + // correct containerSize for the row frame. In that case, we'll have to + // fix up child positions later, after determining our desiredSize. + NS_ASSERTION(origKidNormalPosition.B(wm) == 0 || wm.IsVerticalRL(), + "unexpected kid position"); + + nsRect kidVisualOverflow = kidFrame->GetVisualOverflowRect(); + LogicalPoint kidPosition(wm, iCoord, 0); + bool firstReflow = kidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + if (doReflowChild) { + // Calculate the available isize for the table cell using the known + // column isizes + nscoord availCellISize = CalcAvailISize(aTableFrame, *cellFrame); + + Maybe kidReflowInput; + ReflowOutput desiredSize(aReflowInput); + + // If the avail isize is not the same as last time we reflowed the cell or + // the cell wants to be bigger than what was available last time or + // it is a style change reflow or we are printing, then we must reflow the + // cell. Otherwise we can skip the reflow. + // XXXldb Why is this condition distinct from doReflowChild above? + WritingMode wm = aReflowInput.GetWritingMode(); + NS_ASSERTION(cellFrame->GetWritingMode() == wm, + "expected consistent writing-mode within table"); + LogicalSize cellDesiredSize = cellFrame->GetDesiredSize(); + if ((availCellISize != cellFrame->GetPriorAvailISize()) || + (cellDesiredSize.ISize(wm) > cellFrame->GetPriorAvailISize()) || + HasAnyStateBits(NS_FRAME_IS_DIRTY) || + isPaginated || + NS_SUBTREE_DIRTY(cellFrame) || + // See if it needs a special reflow, or if it had one that we need to undo. + cellFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE) || + HasPctBSize()) { + // Reflow the cell to fit the available isize, bsize + // XXX The old IR_ChildIsDirty code used availCellISize here. + LogicalSize kidAvailSize(wm, availCellISize, aReflowInput.AvailableBSize()); + + // Reflow the child + kidReflowInput.emplace(aPresContext, aReflowInput, kidFrame, + kidAvailSize, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(*aPresContext, kidAvailSize, borderCollapse, + *kidReflowInput); + + nsReflowStatus status; + ReflowChild(kidFrame, aPresContext, desiredSize, *kidReflowInput, + wm, kidPosition, containerSize, 0, status); + + // allow the table to determine if/how the table needs to be rebalanced + // If any of the cells are not complete, then we're not complete + if (NS_FRAME_IS_NOT_COMPLETE(status)) { + aStatus = NS_FRAME_NOT_COMPLETE; + } + } else { + if (iCoord != origKidNormalPosition.I(wm)) { + kidFrame->InvalidateFrameSubtree(); + } + + desiredSize.SetSize(wm, cellDesiredSize); + desiredSize.mOverflowAreas = cellFrame->GetOverflowAreas(); + + // if we are in a floated table, our position is not yet established, so we cannot reposition our views + // the containing block will do this for us after positioning the table + if (!aTableFrame.IsFloating()) { + // Because we may have moved the frame we need to make sure any views are + // positioned properly. We have to do this, because any one of our parent + // frames could have moved and we have no way of knowing... + nsTableFrame::RePositionViews(kidFrame); + } + } + + if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) { + if (!GetPrevInFlow()) { + // Calculate the cell's actual bsize given its pass2 bsize. This + // function takes into account the specified bsize (in the style) + CalculateCellActualBSize(cellFrame, desiredSize.BSize(wm), wm); + } + // bsize may have changed, adjust descent to absorb any excess difference + nscoord ascent; + if (!kidFrame->PrincipalChildList().FirstChild()->PrincipalChildList().FirstChild()) { + ascent = desiredSize.BSize(wm); + } else { + ascent = ((nsTableCellFrame *)kidFrame)->GetCellBaseline(); + } + nscoord descent = desiredSize.BSize(wm) - ascent; + UpdateBSize(desiredSize.BSize(wm), ascent, descent, &aTableFrame, cellFrame); + } else { + cellMaxBSize = std::max(cellMaxBSize, desiredSize.BSize(wm)); + int32_t rowSpan = aTableFrame.GetEffectiveRowSpan((nsTableCellFrame&)*kidFrame); + if (1 == rowSpan) { + SetContentBSize(cellMaxBSize); + } + } + + // Place the child + desiredSize.ISize(wm) = availCellISize; + + if (kidReflowInput) { + // We reflowed. Apply relative positioning in the normal way. + kidReflowInput->ApplyRelativePositioning(&kidPosition, containerSize); + } else if (kidFrame->IsRelativelyPositioned()) { + // We didn't reflow. Do the positioning part of what + // MovePositionBy does internally. (This codepath should really + // be merged into the else below if we can.) + nsMargin* computedOffsetProp = + kidFrame->Properties().Get(nsIFrame::ComputedOffsetProperty()); + // Bug 975644: a position:sticky kid can end up with a null + // property value here. + LogicalMargin computedOffsets(wm, computedOffsetProp ? + *computedOffsetProp : nsMargin()); + ReflowInput::ApplyRelativePositioning(kidFrame, wm, computedOffsets, + &kidPosition, containerSize); + } + + // In vertical-rl mode, we are likely to have containerSize.width = 0 + // because ComputedWidth() was NS_UNCONSTRAINEDSIZE. + // For cases where that's wrong, we will fix up the position later. + FinishReflowChild(kidFrame, aPresContext, desiredSize, nullptr, + wm, kidPosition, containerSize, 0); + + nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidVisualOverflow, + firstReflow); + + iCoord += desiredSize.ISize(wm); + } else { + if (iCoord != origKidNormalPosition.I(wm)) { + // Invalidate the old position + kidFrame->InvalidateFrameSubtree(); + // Move to the new position. As above, we need to account for relative + // positioning. + kidFrame->MovePositionBy(wm, + LogicalPoint(wm, iCoord - origKidNormalPosition.I(wm), 0)); + nsTableFrame::RePositionViews(kidFrame); + // invalidate the new position + kidFrame->InvalidateFrameSubtree(); + } + // we need to account for the cell's isize even if it isn't reflowed + iCoord += kidFrame->ISize(wm); + + if (kidFrame->GetNextInFlow()) { + aStatus = NS_FRAME_NOT_COMPLETE; + } + } + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); + iCoord += aTableFrame.GetColSpacing(cellColIndex); + } + + // Just set our isize to what was available. + // The table will calculate the isize and not use our value. + aDesiredSize.ISize(wm) = aReflowInput.AvailableISize(); + + if (aReflowInput.mFlags.mSpecialBSizeReflow) { + aDesiredSize.BSize(wm) = BSize(wm); + } else if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) { + aDesiredSize.BSize(wm) = CalcBSize(aReflowInput); + if (GetPrevInFlow()) { + nscoord bsize = CalcBSizeFromUnpaginatedBSize(*this, wm); + aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bsize); + } else { + if (isPaginated && HasStyleBSize()) { + // set the unpaginated bsize so next in flows can try to honor it + SetHasUnpaginatedBSize(true); + SetUnpaginatedBSize(aPresContext, aDesiredSize.BSize(wm)); + } + if (isPaginated && HasUnpaginatedBSize()) { + aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), + GetUnpaginatedBSize()); + } + } + } else { // constrained bsize, paginated + // Compute the bsize we should have from style (subtracting the + // bsize from our prev-in-flows from the style bsize) + nscoord styleBSize = CalcBSizeFromUnpaginatedBSize(*this, wm); + if (styleBSize > aReflowInput.AvailableBSize()) { + styleBSize = aReflowInput.AvailableBSize(); + NS_FRAME_SET_INCOMPLETE(aStatus); + } + aDesiredSize.BSize(wm) = std::max(cellMaxBSize, styleBSize); + } + + if (wm.IsVerticalRL()) { + // Any children whose width was not the same as our final + // aDesiredSize.BSize will have been misplaced earlier at the + // FinishReflowChild stage. So fix them up now. + for (nsIFrame* kidFrame : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); + if (!cellFrame) { + continue; + } + if (kidFrame->BSize(wm) != aDesiredSize.BSize(wm)) { + kidFrame->MovePositionBy(wm, + LogicalPoint(wm, 0, kidFrame->BSize(wm) - aDesiredSize.BSize(wm))); + nsTableFrame::RePositionViews(kidFrame); + // Do we need to InvalidateFrameSubtree() here? + } + } + } + + aDesiredSize.UnionOverflowAreasWithDesiredBounds(); + FinishAndStoreOverflow(&aDesiredSize); +} + +/** Layout the entire row. + * This method stacks cells in the inline dir according to HTML 4.0 rules. + */ +void +nsTableRowFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableRowFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + + WritingMode wm = aReflowInput.GetWritingMode(); + + nsTableFrame* tableFrame = GetTableFrame(); + const nsStyleVisibility* rowVis = StyleVisibility(); + bool collapseRow = (NS_STYLE_VISIBILITY_COLLAPSE == rowVis->mVisible); + if (collapseRow) { + tableFrame->SetNeedToCollapse(true); + } + + // see if a special bsize reflow needs to occur due to having a pct bsize + nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput); + + // See if we have a cell with specified/pct bsize + InitHasCellWithStyleBSize(tableFrame); + + ReflowChildren(aPresContext, aDesiredSize, aReflowInput, *tableFrame, aStatus); + + if (aPresContext->IsPaginated() && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) && + ShouldAvoidBreakInside(aReflowInput)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + } + + // Just set our isize to what was available. + // The table will calculate the isize and not use our value. + aDesiredSize.ISize(wm) = aReflowInput.AvailableISize(); + + // If our parent is in initial reflow, it'll handle invalidating our + // entire overflow rect. + if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && + nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { + InvalidateFrame(); + } + + // Any absolutely-positioned children will get reflowed in + // nsFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +/** + * This function is called by the row group frame's SplitRowGroup() code when + * pushing a row frame that has cell frames that span into it. The cell frame + * should be reflowed with the specified height + */ +nscoord +nsTableRowFrame::ReflowCellFrame(nsPresContext* aPresContext, + const ReflowInput& aReflowInput, + bool aIsTopOfPage, + nsTableCellFrame* aCellFrame, + nscoord aAvailableBSize, + nsReflowStatus& aStatus) +{ + WritingMode wm = aReflowInput.GetWritingMode(); + + // Reflow the cell frame with the specified height. Use the existing width + nsSize containerSize = aCellFrame->GetSize(); + LogicalRect cellRect = aCellFrame->GetLogicalRect(wm, containerSize); + nsRect cellVisualOverflow = aCellFrame->GetVisualOverflowRect(); + + LogicalSize cellSize = cellRect.Size(wm); + LogicalSize availSize(wm, cellRect.ISize(wm), aAvailableBSize); + bool borderCollapse = GetTableFrame()->IsBorderCollapse(); + NS_ASSERTION(aCellFrame->GetWritingMode() == wm, + "expected consistent writing-mode within table"); + TableCellReflowInput + cellReflowInput(aPresContext, aReflowInput, aCellFrame, availSize, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(*aPresContext, availSize, borderCollapse, cellReflowInput); + cellReflowInput.mFlags.mIsTopOfPage = aIsTopOfPage; + + ReflowOutput desiredSize(aReflowInput); + + ReflowChild(aCellFrame, aPresContext, desiredSize, cellReflowInput, + 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus); + bool fullyComplete = NS_FRAME_IS_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus); + if (fullyComplete) { + desiredSize.BSize(wm) = aAvailableBSize; + } + aCellFrame->SetSize(wm, LogicalSize(wm, cellSize.ISize(wm), + desiredSize.BSize(wm))); + + // Note: BlockDirAlignChild can affect the overflow rect. + // XXX What happens if this cell has 'vertical-align: baseline' ? + // XXX Why is it assumed that the cell's ascent hasn't changed ? + if (fullyComplete) { + aCellFrame->BlockDirAlignChild(wm, mMaxCellAscent); + } + + nsTableFrame::InvalidateTableFrame(aCellFrame, + cellRect.GetPhysicalRect(wm, containerSize), + cellVisualOverflow, + aCellFrame-> + HasAnyStateBits(NS_FRAME_FIRST_REFLOW)); + + aCellFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); + + return desiredSize.BSize(wm); +} + +nscoord +nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset, + nscoord aISize, + bool aCollapseGroup, + bool& aDidCollapse) +{ + const nsStyleVisibility* rowVis = StyleVisibility(); + bool collapseRow = (NS_STYLE_VISIBILITY_COLLAPSE == rowVis->mVisible); + nsTableFrame* tableFrame = + static_cast(GetTableFrame()->FirstInFlow()); + if (collapseRow) { + tableFrame->SetNeedToCollapse(true); + } + + if (aRowOffset != 0) { + // We're moving, so invalidate our old position + InvalidateFrameSubtree(); + } + + WritingMode wm = GetWritingMode(); + + nsSize parentSize = GetParent()->GetSize(); + LogicalRect rowRect = GetLogicalRect(wm, parentSize); + nsRect oldRect = mRect; + nsRect oldVisualOverflow = GetVisualOverflowRect(); + + rowRect.BStart(wm) -= aRowOffset; + rowRect.ISize(wm) = aISize; + nsOverflowAreas overflow; + nscoord shift = 0; + nsSize containerSize = mRect.Size(); + + if (aCollapseGroup || collapseRow) { + aDidCollapse = true; + shift = rowRect.BSize(wm); + nsTableCellFrame* cellFrame = GetFirstCell(); + if (cellFrame) { + int32_t rowIndex; + cellFrame->GetRowIndex(rowIndex); + shift += tableFrame->GetRowSpacing(rowIndex); + while (cellFrame) { + LogicalRect cRect = cellFrame->GetLogicalRect(wm, containerSize); + // If aRowOffset != 0, there's no point in invalidating the cells, since + // we've already invalidated our overflow area. Note that we _do_ still + // need to invalidate if our row is not moving, because the cell might + // span out of this row, so invalidating our row rect won't do enough. + if (aRowOffset == 0) { + InvalidateFrame(); + } + cRect.BSize(wm) = 0; + cellFrame->SetRect(wm, cRect, containerSize); + cellFrame = cellFrame->GetNextCell(); + } + } else { + shift += tableFrame->GetRowSpacing(GetRowIndex()); + } + rowRect.BSize(wm) = 0; + } + else { // row is not collapsed + // remember the col index of the previous cell to handle rowspans into this + // row + int32_t prevColIndex = -1; + nscoord iPos = 0; // running total of children inline-axis offset + nsTableFrame* fifTable = + static_cast(tableFrame->FirstInFlow()); + + for (nsIFrame* kidFrame : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); + if (cellFrame) { + int32_t cellColIndex; + cellFrame->GetColIndex(cellColIndex); + int32_t cellColSpan = tableFrame->GetEffectiveColSpan(*cellFrame); + + // If the adjacent cell is in a prior row (because of a rowspan) add in + // the space + if (prevColIndex != (cellColIndex - 1)) { + iPos += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, + *tableFrame, true); + } + LogicalRect cRect(wm, iPos, 0, 0, rowRect.BSize(wm)); + + // remember the last (iend-wards-most) column this cell spans into + prevColIndex = cellColIndex + cellColSpan - 1; + int32_t actualColSpan = cellColSpan; + bool isVisible = false; + for (int32_t colIdx = cellColIndex; actualColSpan > 0; + colIdx++, actualColSpan--) { + + nsTableColFrame* colFrame = tableFrame->GetColFrame(colIdx); + const nsStyleVisibility* colVis = colFrame->StyleVisibility(); + bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == + colVis->mVisible); + nsIFrame* cgFrame = colFrame->GetParent(); + const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); + bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == + groupVis->mVisible); + bool isCollapsed = collapseCol || collapseGroup; + if (!isCollapsed) { + cRect.ISize(wm) += fifTable->GetColumnISizeFromFirstInFlow(colIdx); + isVisible = true; + if ((actualColSpan > 1)) { + nsTableColFrame* nextColFrame = + tableFrame->GetColFrame(colIdx + 1); + const nsStyleVisibility* nextColVis = + nextColFrame->StyleVisibility(); + if ( (NS_STYLE_VISIBILITY_COLLAPSE != nextColVis->mVisible) && + tableFrame->ColumnHasCellSpacingBefore(colIdx + 1)) { + cRect.ISize(wm) += tableFrame->GetColSpacing(cellColIndex); + } + } + } + } + iPos += cRect.ISize(wm); + if (isVisible) { + iPos += tableFrame->GetColSpacing(cellColIndex); + } + int32_t actualRowSpan = tableFrame->GetEffectiveRowSpan(*cellFrame); + nsTableRowFrame* rowFrame = GetNextRow(); + for (actualRowSpan--; actualRowSpan > 0 && rowFrame; actualRowSpan--) { + const nsStyleVisibility* nextRowVis = rowFrame->StyleVisibility(); + bool collapseNextRow = (NS_STYLE_VISIBILITY_COLLAPSE == + nextRowVis->mVisible); + if (!collapseNextRow) { + LogicalRect nextRect = rowFrame->GetLogicalRect(wm, + containerSize); + cRect.BSize(wm) += + nextRect.BSize(wm) + + tableFrame->GetRowSpacing(rowFrame->GetRowIndex()); + } + rowFrame = rowFrame->GetNextRow(); + } + + nsRect oldCellRect = cellFrame->GetRect(); + LogicalPoint oldCellNormalPos = + cellFrame->GetLogicalNormalPosition(wm, containerSize); + + nsRect oldCellVisualOverflow = cellFrame->GetVisualOverflowRect(); + + if (aRowOffset == 0 && cRect.Origin(wm) != oldCellNormalPos) { + // We're moving the cell. Invalidate the old overflow area + cellFrame->InvalidateFrameSubtree(); + } + + cellFrame->MovePositionBy(wm, cRect.Origin(wm) - oldCellNormalPos); + cellFrame->SetSize(wm, cRect.Size(wm)); + + // XXXbz This looks completely bogus in the cases when we didn't + // collapse the cell! + LogicalRect cellBounds(wm, 0, 0, cRect.ISize(wm), cRect.BSize(wm)); + nsRect cellPhysicalBounds = + cellBounds.GetPhysicalRect(wm, containerSize); + nsOverflowAreas cellOverflow(cellPhysicalBounds, cellPhysicalBounds); + cellFrame->FinishAndStoreOverflow(cellOverflow, + cRect.Size(wm).GetPhysicalSize(wm)); + nsTableFrame::RePositionViews(cellFrame); + ConsiderChildOverflow(overflow, cellFrame); + + if (aRowOffset == 0) { + nsTableFrame::InvalidateTableFrame(cellFrame, oldCellRect, + oldCellVisualOverflow, false); + } + } + } + } + + SetRect(wm, rowRect, containerSize); + overflow.UnionAllWith(nsRect(0, 0, rowRect.Width(wm), rowRect.Height(wm))); + FinishAndStoreOverflow(overflow, rowRect.Size(wm).GetPhysicalSize(wm)); + + nsTableFrame::RePositionViews(this); + nsTableFrame::InvalidateTableFrame(this, oldRect, oldVisualOverflow, false); + return shift; +} + +/* + * The following method is called by the row group frame's SplitRowGroup() + * when it creates a continuing cell frame and wants to insert it into the + * row's child list. + */ +void +nsTableRowFrame::InsertCellFrame(nsTableCellFrame* aFrame, + int32_t aColIndex) +{ + // Find the cell frame where col index < aColIndex + nsTableCellFrame* priorCell = nullptr; + for (nsIFrame* child : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(child); + if (cellFrame) { + int32_t colIndex; + cellFrame->GetColIndex(colIndex); + if (colIndex < aColIndex) { + priorCell = cellFrame; + } + else break; + } + } + mFrames.InsertFrame(this, priorCell, aFrame); +} + +nsIAtom* +nsTableRowFrame::GetType() const +{ + return nsGkAtoms::tableRowFrame; +} + +nsTableRowFrame* +nsTableRowFrame::GetNextRow() const +{ + nsIFrame* childFrame = GetNextSibling(); + while (childFrame) { + nsTableRowFrame *rowFrame = do_QueryFrame(childFrame); + if (rowFrame) { + NS_ASSERTION(mozilla::StyleDisplay::TableRow == childFrame->StyleDisplay()->mDisplay, + "wrong display type on rowframe"); + return rowFrame; + } + childFrame = childFrame->GetNextSibling(); + } + return nullptr; +} + +NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(RowUnpaginatedHeightProperty, nscoord) + +void +nsTableRowFrame::SetUnpaginatedBSize(nsPresContext* aPresContext, + nscoord aValue) +{ + NS_ASSERTION(!GetPrevInFlow(), "program error"); + // Get the property + aPresContext->PropertyTable()-> + Set(this, RowUnpaginatedHeightProperty(), aValue); +} + +nscoord +nsTableRowFrame::GetUnpaginatedBSize() +{ + FrameProperties props = FirstInFlow()->Properties(); + return props.Get(RowUnpaginatedHeightProperty()); +} + +void nsTableRowFrame::SetContinuousBCBorderWidth(LogicalSide aForSide, + BCPixelSize aPixelValue) +{ + switch (aForSide) { + case eLogicalSideIEnd: + mIEndContBorderWidth = aPixelValue; + return; + case eLogicalSideBStart: + mBStartContBorderWidth = aPixelValue; + return; + case eLogicalSideIStart: + mIStartContBorderWidth = aPixelValue; + return; + default: + NS_ERROR("invalid NS_SIDE arg"); + } +} +#ifdef ACCESSIBILITY +a11y::AccType +nsTableRowFrame::AccessibleType() +{ + return a11y::eHTMLTableRowType; +} +#endif +/** + * Sets the NS_ROW_HAS_CELL_WITH_STYLE_BSIZE bit to indicate whether + * this row has any cells that have non-auto-bsize. (Row-spanning + * cells are ignored.) + */ +void nsTableRowFrame::InitHasCellWithStyleBSize(nsTableFrame* aTableFrame) +{ + WritingMode wm = GetWritingMode(); + + for (nsIFrame* kidFrame : mFrames) { + nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); + if (!cellFrame) { + NS_NOTREACHED("Table row has a non-cell child."); + continue; + } + // Ignore row-spanning cells + const nsStyleCoord &cellBSize = cellFrame->StylePosition()->BSize(wm); + if (aTableFrame->GetEffectiveRowSpan(*cellFrame) == 1 && + cellBSize.GetUnit() != eStyleUnit_Auto && + /* calc() with percentages treated like 'auto' */ + (!cellBSize.IsCalcUnit() || !cellBSize.HasPercent())) { + AddStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE); + return; + } + } + RemoveStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE); +} + +void +nsTableRowFrame::InvalidateFrame(uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrame(aDisplayItemKey); + GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey); +} + +void +nsTableRowFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey); + // If we have filters applied that would affects our bounds, then + // we get an inactive layer created and this is computed + // within FrameLayerBuilder + GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey); +} + +/* ----- global methods ----- */ + +nsTableRowFrame* +NS_NewTableRowFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableRowFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableRowFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableRowFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("TableRow"), aResult); +} +#endif diff --git a/layout/tables/nsTableRowFrame.h b/layout/tables/nsTableRowFrame.h new file mode 100644 index 000000000..a6aba81e7 --- /dev/null +++ b/layout/tables/nsTableRowFrame.h @@ -0,0 +1,437 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTableRowFrame_h__ +#define nsTableRowFrame_h__ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsTablePainter.h" +#include "nsTableRowGroupFrame.h" +#include "mozilla/WritingModes.h" + +class nsTableCellFrame; +namespace mozilla { +struct TableCellReflowInput; +} // namespace mozilla + +/** + * nsTableRowFrame is the frame that maps table rows + * (HTML tag TR). This class cannot be reused + * outside of an nsTableRowGroupFrame. It assumes that its parent is an nsTableRowGroupFrame, + * and its children are nsTableCellFrames. + * + * @see nsTableFrame + * @see nsTableRowGroupFrame + * @see nsTableCellFrame + */ +class nsTableRowFrame : public nsContainerFrame +{ + using TableCellReflowInput = mozilla::TableCellReflowInput; + +public: + NS_DECL_QUERYFRAME_TARGET(nsTableRowFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + virtual ~nsTableRowFrame(); + + virtual void Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + /** @see nsIFrame::DidSetStyleContext */ + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + /** instantiate a new instance of nsTableRowFrame. + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableRowFrame* NS_NewTableRowFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + nsTableRowGroupFrame* GetTableRowGroupFrame() const + { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableRowGroupFrame); + return static_cast(parent); + } + + nsTableFrame* GetTableFrame() const + { + return GetTableRowGroupFrame()->GetTableFrame(); + } + + virtual nsMargin GetUsedMargin() const override; + virtual nsMargin GetUsedBorder() const override; + virtual nsMargin GetUsedPadding() const override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + nsTableCellFrame* GetFirstCell() ; + + /** calls Reflow for all of its child cells. + * Cells with rowspan=1 are all set to the same height and stacked horizontally. + *

    Cells are not split unless absolutely necessary. + *

    Cells are resized in nsTableFrame::BalanceColumnWidths + * and nsTableFrame::ShrinkWrapChildren + * + * @param aDesiredSize width set to width of the sum of the cells, height set to + * height of cells with rowspan=1. + * + * @see nsIFrame::Reflow + * @see nsTableFrame::BalanceColumnWidths + * @see nsTableFrame::ShrinkWrapChildren + */ + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void DidResize(); + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::tableRowFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + virtual mozilla::WritingMode GetWritingMode() const override + { return GetTableFrame()->GetWritingMode(); } + + void UpdateBSize(nscoord aBSize, + nscoord aAscent, + nscoord aDescent, + nsTableFrame* aTableFrame = nullptr, + nsTableCellFrame* aCellFrame = nullptr); + + void ResetBSize(nscoord aRowStyleBSize); + + // calculate the bsize, considering content bsize of the + // cells and the style bsize of the row and cells, excluding pct bsizes + nscoord CalcBSize(const ReflowInput& aReflowInput); + + // Support for cells with 'vertical-align: baseline'. + + /** + * returns the max-ascent amongst all the cells that have + * 'vertical-align: baseline', *including* cells with rowspans. + * returns 0 if we don't have any cell with 'vertical-align: baseline' + */ + nscoord GetMaxCellAscent() const; + + /* return the row ascent + */ + nscoord GetRowBaseline(mozilla::WritingMode aWritingMode); + + /** returns the ordinal position of this row in its table */ + virtual int32_t GetRowIndex() const; + + /** set this row's starting row index */ + void SetRowIndex (int aRowIndex); + + /** used by row group frame code */ + nscoord ReflowCellFrame(nsPresContext* aPresContext, + const ReflowInput& aReflowInput, + bool aIsTopOfPage, + nsTableCellFrame* aCellFrame, + nscoord aAvailableBSize, + nsReflowStatus& aStatus); + /** + * Collapse the row if required, apply col and colgroup visibility: collapse + * info to the cells in the row. + * @return the amount to shift bstart-wards all following rows + * @param aRowOffset - shift the row bstart-wards by this amount + * @param aISize - new isize of the row + * @param aCollapseGroup - parent rowgroup is collapsed so this row needs + * to be collapsed + * @param aDidCollapse - the row has been collapsed + */ + nscoord CollapseRowIfNecessary(nscoord aRowOffset, + nscoord aISize, + bool aCollapseGroup, + bool& aDidCollapse); + + /** + * Insert a cell frame after the last cell frame that has a col index + * that is less than aColIndex. If no such cell frame is found the + * frame to insert is prepended to the child list. + * @param aFrame the cell frame to insert + * @param aColIndex the col index + */ + void InsertCellFrame(nsTableCellFrame* aFrame, + int32_t aColIndex); + + nsresult CalculateCellActualBSize(nsTableCellFrame* aCellFrame, + nscoord& aDesiredBSize, + mozilla::WritingMode aWM); + + bool IsFirstInserted() const; + void SetFirstInserted(bool aValue); + + nscoord GetContentBSize() const; + void SetContentBSize(nscoord aTwipValue); + + bool HasStyleBSize() const; + + bool HasFixedBSize() const; + void SetHasFixedBSize(bool aValue); + + bool HasPctBSize() const; + void SetHasPctBSize(bool aValue); + + nscoord GetFixedBSize() const; + void SetFixedBSize(nscoord aValue); + + float GetPctBSize() const; + void SetPctBSize(float aPctValue, + bool aForce = false); + + nscoord GetInitialBSize(nscoord aBasis = 0) const; + + nsTableRowFrame* GetNextRow() const; + + bool HasUnpaginatedBSize(); + void SetHasUnpaginatedBSize(bool aValue); + nscoord GetUnpaginatedBSize(); + void SetUnpaginatedBSize(nsPresContext* aPresContext, nscoord aValue); + + nscoord GetBStartBCBorderWidth() const { return mBStartBorderWidth; } + nscoord GetBEndBCBorderWidth() const { return mBEndBorderWidth; } + void SetBStartBCBorderWidth(BCPixelSize aWidth) { mBStartBorderWidth = aWidth; } + void SetBEndBCBorderWidth(BCPixelSize aWidth) { mBEndBorderWidth = aWidth; } + mozilla::LogicalMargin GetBCBorderWidth(mozilla::WritingMode aWM); + + /** + * Gets inner border widths before collapsing with cell borders + * Caller must get block-end border from next row or from table + * GetContinuousBCBorderWidth will not overwrite that border + * see nsTablePainter about continuous borders + */ + void GetContinuousBCBorderWidth(mozilla::WritingMode aWM, + mozilla::LogicalMargin& aBorder); + + /** + * @returns outer block-start bc border == prev row's block-end inner + */ + nscoord GetOuterBStartContBCBorderWidth(); + /** + * Sets full border widths before collapsing with cell borders + * @param aForSide - side to set; only accepts iend, istart, and bstart + */ + void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide, + BCPixelSize aPixelValue); + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart)); + } + + virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + +#ifdef ACCESSIBILITY + virtual mozilla::a11y::AccType AccessibleType() override; +#endif + +protected: + + /** protected constructor. + * @see NewFrame + */ + explicit nsTableRowFrame(nsStyleContext *aContext); + + void InitChildReflowInput(nsPresContext& aPresContext, + const mozilla::LogicalSize& aAvailSize, + bool aBorderCollapse, + TableCellReflowInput& aReflowInput); + + virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override; + + // row-specific methods + + nscoord ComputeCellXOffset(const ReflowInput& aState, + nsIFrame* aKidFrame, + const nsMargin& aKidMargin) const; + /** + * Called for incremental/dirty and resize reflows. If aDirtyOnly is true then + * only reflow dirty cells. + */ + void ReflowChildren(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsTableFrame& aTableFrame, + nsReflowStatus& aStatus); + +private: + struct RowBits { + unsigned mRowIndex:29; + unsigned mHasFixedBSize:1; // set if the dominating style bsize on the row or any cell is pixel based + unsigned mHasPctBSize:1; // set if the dominating style bsize on the row or any cell is pct based + unsigned mFirstInserted:1; // if true, then it was the bstart-most newly inserted row + } mBits; + + // the desired bsize based on the content of the tallest cell in the row + nscoord mContentBSize; + // the bsize based on a style percentage bsize on either the row or any cell + // if mHasPctBSize is set + nscoord mStylePctBSize; + // the bsize based on a style pixel bsize on the row or any + // cell if mHasFixedBSize is set + nscoord mStyleFixedBSize; + + // max-ascent and max-descent amongst all cells that have 'vertical-align: baseline' + nscoord mMaxCellAscent; // does include cells with rowspan > 1 + nscoord mMaxCellDescent; // does *not* include cells with rowspan > 1 + + // border widths in pixels in the collapsing border model of the *inner* + // half of the border only + BCPixelSize mBStartBorderWidth; + BCPixelSize mBEndBorderWidth; + BCPixelSize mIEndContBorderWidth; + BCPixelSize mBStartContBorderWidth; + BCPixelSize mIStartContBorderWidth; + + /** + * Sets the NS_ROW_HAS_CELL_WITH_STYLE_BSIZE bit to indicate whether + * this row has any cells that have non-auto-bsize. (Row-spanning + * cells are ignored.) + */ + void InitHasCellWithStyleBSize(nsTableFrame* aTableFrame); + +}; + +inline int32_t nsTableRowFrame::GetRowIndex() const +{ + return int32_t(mBits.mRowIndex); +} + +inline void nsTableRowFrame::SetRowIndex (int aRowIndex) +{ + mBits.mRowIndex = aRowIndex; +} + +inline bool nsTableRowFrame::IsFirstInserted() const +{ + return bool(mBits.mFirstInserted); +} + +inline void nsTableRowFrame::SetFirstInserted(bool aValue) +{ + mBits.mFirstInserted = aValue; +} + +inline bool nsTableRowFrame::HasStyleBSize() const +{ + return (bool)mBits.mHasFixedBSize || (bool)mBits.mHasPctBSize; +} + +inline bool nsTableRowFrame::HasFixedBSize() const +{ + return (bool)mBits.mHasFixedBSize; +} + +inline void nsTableRowFrame::SetHasFixedBSize(bool aValue) +{ + mBits.mHasFixedBSize = aValue; +} + +inline bool nsTableRowFrame::HasPctBSize() const +{ + return (bool)mBits.mHasPctBSize; +} + +inline void nsTableRowFrame::SetHasPctBSize(bool aValue) +{ + mBits.mHasPctBSize = aValue; +} + +inline nscoord nsTableRowFrame::GetContentBSize() const +{ + return mContentBSize; +} + +inline void nsTableRowFrame::SetContentBSize(nscoord aValue) +{ + mContentBSize = aValue; +} + +inline nscoord nsTableRowFrame::GetFixedBSize() const +{ + if (mBits.mHasFixedBSize) { + return mStyleFixedBSize; + } + return 0; +} + +inline float nsTableRowFrame::GetPctBSize() const +{ + if (mBits.mHasPctBSize) { + return (float)mStylePctBSize / 100.0f; + } + return 0.0f; +} + +inline bool nsTableRowFrame::HasUnpaginatedBSize() +{ + return HasAnyStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE); +} + +inline void nsTableRowFrame::SetHasUnpaginatedBSize(bool aValue) +{ + if (aValue) { + AddStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE); + } else { + RemoveStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE); + } +} + +inline mozilla::LogicalMargin +nsTableRowFrame::GetBCBorderWidth(mozilla::WritingMode aWM) +{ + return mozilla::LogicalMargin( + aWM, nsPresContext::CSSPixelsToAppUnits(mBStartBorderWidth), 0, + nsPresContext::CSSPixelsToAppUnits(mBEndBorderWidth), 0); +} + +inline void +nsTableRowFrame::GetContinuousBCBorderWidth(mozilla::WritingMode aWM, + mozilla::LogicalMargin& aBorder) +{ + int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel(); + aBorder.IEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips, + mIStartContBorderWidth); + aBorder.BStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips, + mBStartContBorderWidth); + aBorder.IStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips, + mIEndContBorderWidth); +} + +inline nscoord nsTableRowFrame::GetOuterBStartContBCBorderWidth() +{ + int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel(); + return BC_BORDER_START_HALF_COORD(aPixelsToTwips, mBStartContBorderWidth); +} + +#endif diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp new file mode 100644 index 000000000..60596f12b --- /dev/null +++ b/layout/tables/nsTableRowGroupFrame.cpp @@ -0,0 +1,2019 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsTableRowGroupFrame.h" +#include "nsTableRowFrame.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsPresContext.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsIContent.h" +#include "nsGkAtoms.h" +#include "nsIPresShell.h" +#include "nsCSSRendering.h" +#include "nsHTMLParts.h" +#include "nsCSSFrameConstructor.h" +#include "nsDisplayList.h" + +#include "nsCellMap.h"//table cell navigation +#include + +using namespace mozilla; +using namespace mozilla::layout; + +namespace mozilla { + +struct TableRowGroupReflowInput { + const ReflowInput& reflowInput; // Our reflow state + + nsTableFrame* tableFrame; + + // The available size (computed from the parent) + mozilla::LogicalSize availSize; + + // Running block-offset + nscoord bCoord; + + TableRowGroupReflowInput(const ReflowInput& aReflowInput, + nsTableFrame* aTableFrame) + : reflowInput(aReflowInput) + , tableFrame(aTableFrame) + , availSize(aReflowInput.GetWritingMode(), + aReflowInput.AvailableISize(), + aReflowInput.AvailableBSize()) + , bCoord(0) + { + } + + ~TableRowGroupReflowInput() {} +}; + +} // namespace mozilla + +nsTableRowGroupFrame::nsTableRowGroupFrame(nsStyleContext* aContext): + nsContainerFrame(aContext) +{ + SetRepeatable(false); +} + +nsTableRowGroupFrame::~nsTableRowGroupFrame() +{ +} + +void +nsTableRowGroupFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) { + nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot); + } + + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +NS_QUERYFRAME_HEAD(nsTableRowGroupFrame) + NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +int32_t +nsTableRowGroupFrame::GetRowCount() +{ +#ifdef DEBUG + for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) { + NS_ASSERTION(e.get()->StyleDisplay()->mDisplay == + mozilla::StyleDisplay::TableRow, + "Unexpected display"); + NS_ASSERTION(e.get()->GetType() == nsGkAtoms::tableRowFrame, + "Unexpected frame type"); + } +#endif + + return mFrames.GetLength(); +} + +int32_t nsTableRowGroupFrame::GetStartRowIndex() +{ + int32_t result = -1; + if (mFrames.NotEmpty()) { + NS_ASSERTION(mFrames.FirstChild()->GetType() == nsGkAtoms::tableRowFrame, + "Unexpected frame type"); + result = static_cast(mFrames.FirstChild())->GetRowIndex(); + } + // if the row group doesn't have any children, get it the hard way + if (-1 == result) { + return GetTableFrame()->GetStartRowIndex(this); + } + + return result; +} + +void nsTableRowGroupFrame::AdjustRowIndices(int32_t aRowIndex, + int32_t anAdjustment) +{ + for (nsIFrame* rowFrame : mFrames) { + if (mozilla::StyleDisplay::TableRow == rowFrame->StyleDisplay()->mDisplay) { + int32_t index = ((nsTableRowFrame*)rowFrame)->GetRowIndex(); + if (index >= aRowIndex) + ((nsTableRowFrame *)rowFrame)->SetRowIndex(index+anAdjustment); + } + } +} +nsresult +nsTableRowGroupFrame::InitRepeatedFrame(nsTableRowGroupFrame* aHeaderFooterFrame) +{ + nsTableRowFrame* copyRowFrame = GetFirstRow(); + nsTableRowFrame* originalRowFrame = aHeaderFooterFrame->GetFirstRow(); + AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP); + while (copyRowFrame && originalRowFrame) { + copyRowFrame->AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP); + int rowIndex = originalRowFrame->GetRowIndex(); + copyRowFrame->SetRowIndex(rowIndex); + + // For each table cell frame set its column index + nsTableCellFrame* originalCellFrame = originalRowFrame->GetFirstCell(); + nsTableCellFrame* copyCellFrame = copyRowFrame->GetFirstCell(); + while (copyCellFrame && originalCellFrame) { + NS_ASSERTION(originalCellFrame->GetContent() == copyCellFrame->GetContent(), + "cell frames have different content"); + int32_t colIndex; + originalCellFrame->GetColIndex(colIndex); + copyCellFrame->SetColIndex(colIndex); + + // Move to the next cell frame + copyCellFrame = copyCellFrame->GetNextCell(); + originalCellFrame = originalCellFrame->GetNextCell(); + } + + // Move to the next row frame + originalRowFrame = originalRowFrame->GetNextRow(); + copyRowFrame = copyRowFrame->GetNextRow(); + } + + return NS_OK; +} + +/** + * We need a custom display item for table row backgrounds. This is only used + * when the table row is the root of a stacking context (e.g., has 'opacity'). + * Table row backgrounds can extend beyond the row frame bounds, when + * the row contains row-spanning cells. + */ +class nsDisplayTableRowGroupBackground : public nsDisplayTableItem { +public: + nsDisplayTableRowGroupBackground(nsDisplayListBuilder* aBuilder, + nsTableRowGroupFrame* aFrame) : + nsDisplayTableItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableRowGroupBackground); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTableRowGroupBackground() { + MOZ_COUNT_DTOR(nsDisplayTableRowGroupBackground); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + + NS_DISPLAY_DECL_NAME("TableRowGroupBackground", TYPE_TABLE_ROW_GROUP_BACKGROUND) +}; + +void +nsDisplayTableRowGroupBackground::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + auto rgFrame = static_cast(mFrame); + TableBackgroundPainter painter(rgFrame->GetTableFrame(), + TableBackgroundPainter::eOrigin_TableRowGroup, + mFrame->PresContext(), *aCtx, + mVisibleRect, ToReferenceFrame(), + aBuilder->GetBackgroundPaintFlags()); + + DrawResult result = painter.PaintRowGroup(rgFrame); + nsDisplayTableItemGeometry::UpdateDrawResult(this, result); +} + +// Handle the child-traversal part of DisplayGenericTablePart +static void +DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, + const nsRect& aDirtyRect, const nsDisplayListSet& aLists) +{ + nscoord overflowAbove; + nsTableRowGroupFrame* f = static_cast(aFrame); + // Don't try to use the row cursor if we have to descend into placeholders; + // we might have rows containing placeholders, where the row's overflow + // area doesn't intersect the dirty rect but we need to descend into the row + // to see out of flows. + // Note that we really want to check ShouldDescendIntoFrame for all + // the rows in |f|, but that's exactly what we're trying to avoid, so we + // approximate it by checking it for |f|: if it's true for any row + // in |f| then it's true for |f| itself. + nsIFrame* kid = aBuilder->ShouldDescendIntoFrame(f) ? + nullptr : f->GetFirstRowContaining(aDirtyRect.y, &overflowAbove); + + if (kid) { + // have a cursor, use it + while (kid) { + if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost() && + kid->GetNormalRect().y - overflowAbove >= aDirtyRect.YMost()) + break; + f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + kid = kid->GetNextSibling(); + } + return; + } + + // No cursor. Traverse children the hard way and build a cursor while we're at it + nsTableRowGroupFrame::FrameCursorData* cursor = f->SetupRowCursor(); + kid = f->PrincipalChildList().FirstChild(); + while (kid) { + f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + + if (cursor) { + if (!cursor->AppendFrame(kid)) { + f->ClearRowCursor(); + return; + } + } + + kid = kid->GetNextSibling(); + } + if (cursor) { + cursor->FinishBuildingCursor(); + } +} + +void +nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + nsDisplayTableItem* item = nullptr; + if (IsVisibleInSelection(aBuilder)) { + bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext(); + if (isRoot) { + // This background is created regardless of whether this frame is + // visible or not. Visibility decisions are delegated to the + // table background painter. + item = new (aBuilder) nsDisplayTableRowGroupBackground(aBuilder, this); + aLists.BorderBackground()->AppendNewToTop(item); + } + } + nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, + aLists, item, DisplayRows); +} + +nsIFrame::LogicalSides +nsTableRowGroupFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const +{ + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return LogicalSides(); + } + + LogicalSides skip; + if (nullptr != GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (nullptr != GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +// Position and size aKidFrame and update our reflow state. +void +nsTableRowGroupFrame::PlaceChild(nsPresContext* aPresContext, + TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame, + WritingMode aWM, + const LogicalPoint& aKidPosition, + const nsSize& aContainerSize, + ReflowOutput& aDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidVisualOverflow) +{ + bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + // Place and size the child + FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr, + aWM, aKidPosition, aContainerSize, 0); + + nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect, + aOriginalKidVisualOverflow, isFirstReflow); + + // Adjust the running block-offset + aReflowInput.bCoord += aDesiredSize.BSize(aWM); + + // If our block-size is constrained then update the available bsize + if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(aWM)) { + aReflowInput.availSize.BSize(aWM) -= aDesiredSize.BSize(aWM); + } +} + +void +nsTableRowGroupFrame::InitChildReflowInput(nsPresContext& aPresContext, + bool aBorderCollapse, + ReflowInput& aReflowInput) +{ + nsMargin collapseBorder; + nsMargin padding(0,0,0,0); + nsMargin* pCollapseBorder = nullptr; + if (aBorderCollapse) { + nsTableRowFrame *rowFrame = do_QueryFrame(aReflowInput.mFrame); + if (rowFrame) { + WritingMode wm = GetWritingMode(); + LogicalMargin border = rowFrame->GetBCBorderWidth(wm); + collapseBorder = border.GetPhysicalMargin(wm); + pCollapseBorder = &collapseBorder; + } + } + aReflowInput.Init(&aPresContext, nullptr, pCollapseBorder, &padding); +} + +static void +CacheRowBSizesForPrinting(nsPresContext* aPresContext, + nsTableRowFrame* aFirstRow, + WritingMode aWM) +{ + for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) { + if (!row->GetPrevInFlow()) { + row->SetHasUnpaginatedBSize(true); + row->SetUnpaginatedBSize(aPresContext, row->BSize(aWM)); + } + } +} + +void +nsTableRowGroupFrame::ReflowChildren(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + TableRowGroupReflowInput& aReflowInput, + nsReflowStatus& aStatus, + bool* aPageBreakBeforeEnd) +{ + if (aPageBreakBeforeEnd) { + *aPageBreakBeforeEnd = false; + } + + WritingMode wm = aReflowInput.reflowInput.GetWritingMode(); + nsTableFrame* tableFrame = GetTableFrame(); + const bool borderCollapse = tableFrame->IsBorderCollapse(); + + // XXXldb Should we really be checking IsPaginated(), + // or should we *only* check available block-size? + // (Think about multi-column layout!) + bool isPaginated = aPresContext->IsPaginated() && + NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm); + + bool haveRow = false; + bool reflowAllKids = aReflowInput.reflowInput.ShouldReflowAllKids() || + tableFrame->IsGeometryDirty(); + + // in vertical-rl mode, we always need the row bsizes in order to + // get the necessary containerSize for placing our kids + bool needToCalcRowBSizes = reflowAllKids || wm.IsVerticalRL(); + + nsSize containerSize = + aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained(); + + nsIFrame *prevKidFrame = nullptr; + for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame; + prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextSibling()) { + nsTableRowFrame *rowFrame = do_QueryFrame(kidFrame); + if (!rowFrame) { + // XXXldb nsCSSFrameConstructor needs to enforce this! + NS_NOTREACHED("yikes, a non-row child"); + continue; + } + nscoord cellSpacingB = tableFrame->GetRowSpacing(rowFrame->GetRowIndex()); + haveRow = true; + + // Reflow the row frame + if (reflowAllKids || + NS_SUBTREE_DIRTY(kidFrame) || + (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow && + (isPaginated || + kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { + LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize); + nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect(); + + // XXXldb We used to only pass aDesiredSize.mFlags through for the + // incremental reflow codepath. + ReflowOutput desiredSize(aReflowInput.reflowInput, + aDesiredSize.mFlags); + desiredSize.ClearSize(); + + // Reflow the child into the available space, giving it as much bsize as + // it wants. We'll deal with splitting later after we've computed the row + // bsizes, taking into account cells with row spans... + LogicalSize kidAvailSize = aReflowInput.availSize; + kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput kidReflowInput(aPresContext, aReflowInput.reflowInput, + kidFrame, kidAvailSize, + nullptr, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(*aPresContext, borderCollapse, kidReflowInput); + + // This can indicate that columns were resized. + if (aReflowInput.reflowInput.IsIResize()) { + kidReflowInput.SetIResize(true); + } + + NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame, + "If we're not on the first frame, we should have a " + "previous sibling..."); + // If prev row has nonzero YMost, then we can't be at the top of the page + if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) { + kidReflowInput.mFlags.mIsTopOfPage = false; + } + + LogicalPoint kidPosition(wm, 0, aReflowInput.bCoord); + ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowInput, + wm, kidPosition, containerSize, 0, aStatus); + kidReflowInput.ApplyRelativePositioning(&kidPosition, containerSize); + + // Place the child + PlaceChild(aPresContext, aReflowInput, kidFrame, + wm, kidPosition, containerSize, + desiredSize, oldKidRect.GetPhysicalRect(wm, containerSize), + oldKidVisualOverflow); + aReflowInput.bCoord += cellSpacingB; + + if (!reflowAllKids) { + if (IsSimpleRowFrame(aReflowInput.tableFrame, rowFrame)) { + // Inform the row of its new bsize. + rowFrame->DidResize(); + // the overflow area may have changed inflate the overflow area + const nsStylePosition *stylePos = StylePosition(); + nsStyleUnit unit = stylePos->BSize(wm).GetUnit(); + if (aReflowInput.tableFrame->IsAutoBSize(wm) && + unit != eStyleUnit_Coord) { + // Because other cells in the row may need to be aligned + // differently, repaint the entire row + InvalidateFrame(); + } else if (oldKidRect.BSize(wm) != desiredSize.BSize(wm)) { + needToCalcRowBSizes = true; + } + } else { + needToCalcRowBSizes = true; + } + } + + if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) { + nsTableRowFrame* nextRow = rowFrame->GetNextRow(); + if (nextRow) { + *aPageBreakBeforeEnd = nsTableFrame::PageBreakAfter(kidFrame, nextRow); + } + } + } else { + SlideChild(aReflowInput, kidFrame); + + // Adjust the running b-offset so we know where the next row should be placed + nscoord bSize = kidFrame->BSize(wm) + cellSpacingB; + aReflowInput.bCoord += bSize; + + if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) { + aReflowInput.availSize.BSize(wm) -= bSize; + } + } + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); + } + + if (haveRow) { + aReflowInput.bCoord -= tableFrame->GetRowSpacing(GetStartRowIndex() + + GetRowCount()); + } + + // Return our desired rect + aDesiredSize.ISize(wm) = aReflowInput.reflowInput.AvailableISize(); + aDesiredSize.BSize(wm) = aReflowInput.bCoord; + + if (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow) { + DidResizeRows(aDesiredSize); + if (isPaginated) { + CacheRowBSizesForPrinting(aPresContext, GetFirstRow(), wm); + } + } + else if (needToCalcRowBSizes) { + CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.reflowInput); + if (!reflowAllKids) { + InvalidateFrame(); + } + } +} + +nsTableRowFrame* +nsTableRowGroupFrame::GetFirstRow() +{ + for (nsIFrame* childFrame : mFrames) { + nsTableRowFrame* rowFrame = do_QueryFrame(childFrame); + if (rowFrame) { + return rowFrame; + } + } + return nullptr; +} + +nsTableRowFrame* +nsTableRowGroupFrame::GetLastRow() +{ + for (auto iter = mFrames.rbegin(), end = mFrames.rend(); iter != end; ++iter) { + nsTableRowFrame* rowFrame = do_QueryFrame(*iter); + if (rowFrame) { + return rowFrame; + } + } + return nullptr; +} + + +struct RowInfo { + RowInfo() { bSize = pctBSize = hasStyleBSize = hasPctBSize = isSpecial = 0; } + unsigned bSize; // content bsize or fixed bsize, excluding pct bsize + unsigned pctBSize:29; // pct bsize + unsigned hasStyleBSize:1; + unsigned hasPctBSize:1; + unsigned isSpecial:1; // there is no cell originating in the row with rowspan=1 and there are at + // least 2 cells spanning the row and there is no style bsize on the row +}; + +static void +UpdateBSizes(RowInfo& aRowInfo, + nscoord aAdditionalBSize, + nscoord& aTotal, + nscoord& aUnconstrainedTotal) +{ + aRowInfo.bSize += aAdditionalBSize; + aTotal += aAdditionalBSize; + if (!aRowInfo.hasStyleBSize) { + aUnconstrainedTotal += aAdditionalBSize; + } +} + +void +nsTableRowGroupFrame::DidResizeRows(ReflowOutput& aDesiredSize) +{ + // Update the cells spanning rows with their new bsizes. + // This is the place where all of the cells in the row get set to the bsize + // of the row. + // Reset the overflow area. + aDesiredSize.mOverflowAreas.Clear(); + for (nsTableRowFrame* rowFrame = GetFirstRow(); + rowFrame; rowFrame = rowFrame->GetNextRow()) { + rowFrame->DidResize(); + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rowFrame); + } +} + +// This calculates the bsize of all the rows and takes into account +// style bsize on the row group, style bsizes on rows and cells, style bsizes on rowspans. +// Actual row bsizes will be adjusted later if the table has a style bsize. +// Even if rows don't change bsize, this method must be called to set the bsizes of each +// cell in the row to the bsize of its row. +void +nsTableRowGroupFrame::CalculateRowBSizes(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput) +{ + nsTableFrame* tableFrame = GetTableFrame(); + const bool isPaginated = aPresContext->IsPaginated(); + + int32_t numEffCols = tableFrame->GetEffectiveColCount(); + + int32_t startRowIndex = GetStartRowIndex(); + // find the row corresponding to the row index we just found + nsTableRowFrame* startRowFrame = GetFirstRow(); + + if (!startRowFrame) { + return; + } + + // The current row group block-size is the block-origin of the 1st row + // we are about to calculate a block-size for. + WritingMode wm = aReflowInput.GetWritingMode(); + nsSize containerSize; // actual value is unimportant as we're initially + // computing sizes, not physical positions + nscoord startRowGroupBSize = + startRowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm); + + int32_t numRows = GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex()); + // Collect the current bsize of each row. + if (numRows <= 0) + return; + + nsTArray rowInfo; + if (!rowInfo.AppendElements(numRows)) { + return; + } + + bool hasRowSpanningCell = false; + nscoord bSizeOfRows = 0; + nscoord bSizeOfUnStyledRows = 0; + // Get the bsize of each row without considering rowspans. This will be the max of + // the largest desired bsize of each cell, the largest style bsize of each cell, + // the style bsize of the row. + nscoord pctBSizeBasis = GetBSizeBasis(aReflowInput); + int32_t rowIndex; // the index in rowInfo, not among the rows in the row group + nsTableRowFrame* rowFrame; + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) { + nscoord nonPctBSize = rowFrame->GetContentBSize(); + if (isPaginated) { + nonPctBSize = std::max(nonPctBSize, rowFrame->BSize(wm)); + } + if (!rowFrame->GetPrevInFlow()) { + if (rowFrame->HasPctBSize()) { + rowInfo[rowIndex].hasPctBSize = true; + rowInfo[rowIndex].pctBSize = rowFrame->GetInitialBSize(pctBSizeBasis); + } + rowInfo[rowIndex].hasStyleBSize = rowFrame->HasStyleBSize(); + nonPctBSize = std::max(nonPctBSize, rowFrame->GetFixedBSize()); + } + UpdateBSizes(rowInfo[rowIndex], nonPctBSize, bSizeOfRows, bSizeOfUnStyledRows); + + if (!rowInfo[rowIndex].hasStyleBSize) { + if (isPaginated || tableFrame->HasMoreThanOneCell(rowIndex + startRowIndex)) { + rowInfo[rowIndex].isSpecial = true; + // iteratate the row's cell frames to see if any do not have rowspan > 1 + nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); + while (cellFrame) { + int32_t rowSpan = tableFrame->GetEffectiveRowSpan(rowIndex + startRowIndex, *cellFrame); + if (1 == rowSpan) { + rowInfo[rowIndex].isSpecial = false; + break; + } + cellFrame = cellFrame->GetNextCell(); + } + } + } + // See if a cell spans into the row. If so we'll have to do the next step + if (!hasRowSpanningCell) { + if (tableFrame->RowIsSpannedInto(rowIndex + startRowIndex, numEffCols)) { + hasRowSpanningCell = true; + } + } + } + + if (hasRowSpanningCell) { + // Get the bsize of cells with rowspans and allocate any extra space to the rows they span + // iteratate the child frames and process the row frames among them + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) { + // See if the row has an originating cell with rowspan > 1. We cannot determine this for a row in a + // continued row group by calling RowHasSpanningCells, because the row's fif may not have any originating + // cells yet the row may have a continued cell which originates in it. + if (GetPrevInFlow() || tableFrame->RowHasSpanningCells(startRowIndex + rowIndex, numEffCols)) { + nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); + // iteratate the row's cell frames + while (cellFrame) { + nscoord cellSpacingB = tableFrame->GetRowSpacing(startRowIndex + rowIndex); + int32_t rowSpan = tableFrame->GetEffectiveRowSpan(rowIndex + startRowIndex, *cellFrame); + if ((rowIndex + rowSpan) > numRows) { + // there might be rows pushed already to the nextInFlow + rowSpan = numRows - rowIndex; + } + if (rowSpan > 1) { // a cell with rowspan > 1, determine the bsize of the rows it spans + nscoord bsizeOfRowsSpanned = 0; + nscoord bsizeOfUnStyledRowsSpanned = 0; + nscoord numSpecialRowsSpanned = 0; + nscoord cellSpacingTotal = 0; + int32_t spanX; + for (spanX = 0; spanX < rowSpan; spanX++) { + bsizeOfRowsSpanned += rowInfo[rowIndex + spanX].bSize; + if (!rowInfo[rowIndex + spanX].hasStyleBSize) { + bsizeOfUnStyledRowsSpanned += rowInfo[rowIndex + spanX].bSize; + } + if (0 != spanX) { + cellSpacingTotal += cellSpacingB; + } + if (rowInfo[rowIndex + spanX].isSpecial) { + numSpecialRowsSpanned++; + } + } + nscoord bsizeOfAreaSpanned = bsizeOfRowsSpanned + cellSpacingTotal; + // get the bsize of the cell + LogicalSize cellFrameSize = cellFrame->GetLogicalSize(wm); + LogicalSize cellDesSize = cellFrame->GetDesiredSize(); + rowFrame->CalculateCellActualBSize(cellFrame, cellDesSize.BSize(wm), wm); + cellFrameSize.BSize(wm) = cellDesSize.BSize(wm); + if (cellFrame->HasVerticalAlignBaseline()) { + // to ensure that a spanning cell with a long descender doesn't + // collide with the next row, we need to take into account the shift + // that will be done to align the cell on the baseline of the row. + cellFrameSize.BSize(wm) += rowFrame->GetMaxCellAscent() - + cellFrame->GetCellBaseline(); + } + + if (bsizeOfAreaSpanned < cellFrameSize.BSize(wm)) { + // the cell's bsize is larger than the available space of the rows it + // spans so distribute the excess bsize to the rows affected + nscoord extra = cellFrameSize.BSize(wm) - bsizeOfAreaSpanned; + nscoord extraUsed = 0; + if (0 == numSpecialRowsSpanned) { + //NS_ASSERTION(bsizeOfRowsSpanned > 0, "invalid row span situation"); + bool haveUnStyledRowsSpanned = (bsizeOfUnStyledRowsSpanned > 0); + nscoord divisor = (haveUnStyledRowsSpanned) + ? bsizeOfUnStyledRowsSpanned : bsizeOfRowsSpanned; + if (divisor > 0) { + for (spanX = rowSpan - 1; spanX >= 0; spanX--) { + if (!haveUnStyledRowsSpanned || !rowInfo[rowIndex + spanX].hasStyleBSize) { + // The amount of additional space each row gets is proportional to its bsize + float percent = ((float)rowInfo[rowIndex + spanX].bSize) / ((float)divisor); + + // give rows their percentage, except for the first row which gets the remainder + nscoord extraForRow = (0 == spanX) ? extra - extraUsed + : NSToCoordRound(((float)(extra)) * percent); + extraForRow = std::min(extraForRow, extra - extraUsed); + // update the row bsize + UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, bSizeOfRows, bSizeOfUnStyledRows); + extraUsed += extraForRow; + if (extraUsed >= extra) { + NS_ASSERTION((extraUsed == extra), "invalid row bsize calculation"); + break; + } + } + } + } + else { + // put everything in the last row + UpdateBSizes(rowInfo[rowIndex + rowSpan - 1], extra, bSizeOfRows, bSizeOfUnStyledRows); + } + } + else { + // give the extra to the special rows + nscoord numSpecialRowsAllocated = 0; + for (spanX = rowSpan - 1; spanX >= 0; spanX--) { + if (rowInfo[rowIndex + spanX].isSpecial) { + // The amount of additional space each degenerate row gets is proportional to the number of them + float percent = 1.0f / ((float)numSpecialRowsSpanned); + + // give rows their percentage, except for the first row which gets the remainder + nscoord extraForRow = (numSpecialRowsSpanned - 1 == numSpecialRowsAllocated) + ? extra - extraUsed + : NSToCoordRound(((float)(extra)) * percent); + extraForRow = std::min(extraForRow, extra - extraUsed); + // update the row bsize + UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, bSizeOfRows, bSizeOfUnStyledRows); + extraUsed += extraForRow; + if (extraUsed >= extra) { + NS_ASSERTION((extraUsed == extra), "invalid row bsize calculation"); + break; + } + } + } + } + } + } // if (rowSpan > 1) + cellFrame = cellFrame->GetNextCell(); + } // while (cellFrame) + } // if (tableFrame->RowHasSpanningCells(startRowIndex + rowIndex) { + } // while (rowFrame) + } + + // pct bsize rows have already got their content bsizes. + // Give them their pct bsizes up to pctBSizeBasis + nscoord extra = pctBSizeBasis - bSizeOfRows; + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame && (extra > 0); + rowFrame = rowFrame->GetNextRow(), rowIndex++) { + RowInfo& rInfo = rowInfo[rowIndex]; + if (rInfo.hasPctBSize) { + nscoord rowExtra = (rInfo.pctBSize > rInfo.bSize) + ? rInfo.pctBSize - rInfo.bSize: 0; + rowExtra = std::min(rowExtra, extra); + UpdateBSizes(rInfo, rowExtra, bSizeOfRows, bSizeOfUnStyledRows); + extra -= rowExtra; + } + } + + bool styleBSizeAllocation = false; + nscoord rowGroupBSize = startRowGroupBSize + bSizeOfRows + + tableFrame->GetRowSpacing(0, numRows-1); + // if we have a style bsize, allocate the extra bsize to unconstrained rows + if ((aReflowInput.ComputedBSize() > rowGroupBSize) && + (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize())) { + nscoord extraComputedBSize = aReflowInput.ComputedBSize() - rowGroupBSize; + nscoord extraUsed = 0; + bool haveUnStyledRows = (bSizeOfUnStyledRows > 0); + nscoord divisor = (haveUnStyledRows) + ? bSizeOfUnStyledRows : bSizeOfRows; + if (divisor > 0) { + styleBSizeAllocation = true; + for (rowIndex = 0; rowIndex < numRows; rowIndex++) { + if (!haveUnStyledRows || !rowInfo[rowIndex].hasStyleBSize) { + // The amount of additional space each row gets is based on the + // percentage of space it occupies + float percent = ((float)rowInfo[rowIndex].bSize) / ((float)divisor); + // give rows their percentage, except for the last row which gets the remainder + nscoord extraForRow = (numRows - 1 == rowIndex) + ? extraComputedBSize - extraUsed + : NSToCoordRound(((float)extraComputedBSize) * percent); + extraForRow = std::min(extraForRow, extraComputedBSize - extraUsed); + // update the row bsize + UpdateBSizes(rowInfo[rowIndex], extraForRow, bSizeOfRows, bSizeOfUnStyledRows); + extraUsed += extraForRow; + if (extraUsed >= extraComputedBSize) { + NS_ASSERTION((extraUsed == extraComputedBSize), "invalid row bsize calculation"); + break; + } + } + } + } + rowGroupBSize = aReflowInput.ComputedBSize(); + } + + if (wm.IsVertical()) { + // we need the correct containerSize below for block positioning in + // vertical-rl writing mode + containerSize.width = rowGroupBSize; + } + + nscoord bOrigin = startRowGroupBSize; + // update the rows with their (potentially) new bsizes + for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; + rowFrame = rowFrame->GetNextRow(), rowIndex++) { + nsRect rowBounds = rowFrame->GetRect(); + LogicalSize rowBoundsSize(wm, rowBounds.Size()); + nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect(); + nscoord deltaB = + bOrigin - rowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm); + + nscoord rowBSize = (rowInfo[rowIndex].bSize > 0) ? rowInfo[rowIndex].bSize : 0; + + if (deltaB != 0 || (rowBSize != rowBoundsSize.BSize(wm))) { + // Resize/move the row to its final size and position + if (deltaB != 0) { + rowFrame->InvalidateFrameSubtree(); + } + + rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, deltaB)); + rowFrame->SetSize(LogicalSize(wm, rowBoundsSize.ISize(wm), rowBSize)); + + nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowVisualOverflow, + false); + + if (deltaB != 0) { + nsTableFrame::RePositionViews(rowFrame); + // XXXbz we don't need to update our overflow area? + } + } + bOrigin += rowBSize + tableFrame->GetRowSpacing(startRowIndex + rowIndex); + } + + if (isPaginated && styleBSizeAllocation) { + // since the row group has a style bsize, cache the row bsizes, + // so next in flows can honor them + CacheRowBSizesForPrinting(aPresContext, GetFirstRow(), wm); + } + + DidResizeRows(aDesiredSize); + + aDesiredSize.BSize(wm) = rowGroupBSize; // Adjust our desired size +} + +nscoord +nsTableRowGroupFrame::CollapseRowGroupIfNecessary(nscoord aBTotalOffset, + nscoord aISize, + WritingMode aWM) +{ + nsTableFrame* tableFrame = GetTableFrame(); + nsSize containerSize = tableFrame->GetSize(); + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); + if (collapseGroup) { + tableFrame->SetNeedToCollapse(true); + } + + nsOverflowAreas overflow; + + nsTableRowFrame* rowFrame = GetFirstRow(); + bool didCollapse = false; + nscoord bGroupOffset = 0; + while (rowFrame) { + bGroupOffset += rowFrame->CollapseRowIfNecessary(bGroupOffset, + aISize, collapseGroup, + didCollapse); + ConsiderChildOverflow(overflow, rowFrame); + rowFrame = rowFrame->GetNextRow(); + } + + LogicalRect groupRect = GetLogicalRect(aWM, containerSize); + nsRect oldGroupRect = GetRect(); + nsRect oldGroupVisualOverflow = GetVisualOverflowRect(); + + groupRect.BSize(aWM) -= bGroupOffset; + if (didCollapse) { + // add back the cellspacing between rowgroups + groupRect.BSize(aWM) += tableFrame->GetRowSpacing(GetStartRowIndex() + + GetRowCount()); + } + + groupRect.BStart(aWM) -= aBTotalOffset; + groupRect.ISize(aWM) = aISize; + + if (aBTotalOffset != 0) { + InvalidateFrameSubtree(); + } + + SetRect(aWM, groupRect, containerSize); + overflow.UnionAllWith(nsRect(0, 0, groupRect.Width(aWM), + groupRect.Height(aWM))); + FinishAndStoreOverflow(overflow, groupRect.Size(aWM).GetPhysicalSize(aWM)); + nsTableFrame::RePositionViews(this); + nsTableFrame::InvalidateTableFrame(this, oldGroupRect, oldGroupVisualOverflow, + false); + + return bGroupOffset; +} + +// Move a child that was skipped during a reflow. +void +nsTableRowGroupFrame::SlideChild(TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame) +{ + // Move the frame if we need to. + WritingMode wm = aReflowInput.reflowInput.GetWritingMode(); + const nsSize containerSize = + aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained(); + LogicalPoint oldPosition = + aKidFrame->GetLogicalNormalPosition(wm, containerSize); + LogicalPoint newPosition = oldPosition; + newPosition.B(wm) = aReflowInput.bCoord; + if (oldPosition.B(wm) != newPosition.B(wm)) { + aKidFrame->InvalidateFrameSubtree(); + aReflowInput.reflowInput.ApplyRelativePositioning(&newPosition, + containerSize); + aKidFrame->SetPosition(wm, newPosition, containerSize); + nsTableFrame::RePositionViews(aKidFrame); + aKidFrame->InvalidateFrameSubtree(); + } +} + +// Create a continuing frame, add it to the child list, and then push it +// and the frames that follow +void +nsTableRowGroupFrame::CreateContinuingRowFrame(nsPresContext& aPresContext, + nsIFrame& aRowFrame, + nsIFrame** aContRowFrame) +{ + // XXX what is the row index? + if (!aContRowFrame) {NS_ASSERTION(false, "bad call"); return;} + // create the continuing frame which will create continuing cell frames + *aContRowFrame = aPresContext.PresShell()->FrameConstructor()-> + CreateContinuingFrame(&aPresContext, &aRowFrame, this); + + // Add the continuing row frame to the child list + mFrames.InsertFrame(nullptr, &aRowFrame, *aContRowFrame); + + // Push the continuing row frame and the frames that follow + PushChildren(*aContRowFrame, &aRowFrame); +} + +// Reflow the cells with rowspan > 1 which originate between aFirstRow +// and end on or after aLastRow. aFirstTruncatedRow is the highest row on the +// page that contains a cell which cannot split on this page +void +nsTableRowGroupFrame::SplitSpanningCells(nsPresContext& aPresContext, + const ReflowInput& aReflowInput, + nsTableFrame& aTable, + nsTableRowFrame& aFirstRow, + nsTableRowFrame& aLastRow, + bool aFirstRowIsTopOfPage, + nscoord aSpanningRowBEnd, + nsTableRowFrame*& aContRow, + nsTableRowFrame*& aFirstTruncatedRow, + nscoord& aDesiredBSize) +{ + NS_ASSERTION(aSpanningRowBEnd >= 0, "Can't split negative bsizes"); + aFirstTruncatedRow = nullptr; + aDesiredBSize = 0; + + const bool borderCollapse = aTable.IsBorderCollapse(); + int32_t lastRowIndex = aLastRow.GetRowIndex(); + bool wasLast = false; + bool haveRowSpan = false; + // Iterate the rows between aFirstRow and aLastRow + for (nsTableRowFrame* row = &aFirstRow; !wasLast; row = row->GetNextRow()) { + wasLast = (row == &aLastRow); + int32_t rowIndex = row->GetRowIndex(); + nsPoint rowPos = row->GetNormalPosition(); + // Iterate the cells looking for those that have rowspan > 1 + for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) { + int32_t rowSpan = aTable.GetEffectiveRowSpan(rowIndex, *cell); + // Only reflow rowspan > 1 cells which span aLastRow. Those which don't span aLastRow + // were reflowed correctly during the unconstrained bsize reflow. + if ((rowSpan > 1) && (rowIndex + rowSpan > lastRowIndex)) { + haveRowSpan = true; + nsReflowStatus status; + // Ask the row to reflow the cell to the bsize of all the rows it spans up through aLastRow + // cellAvailBSize is the space between the row group start and the end of the page + nscoord cellAvailBSize = aSpanningRowBEnd - rowPos.y; + NS_ASSERTION(cellAvailBSize >= 0, "No space for cell?"); + bool isTopOfPage = (row == &aFirstRow) && aFirstRowIsTopOfPage; + + nsRect rowRect = row->GetNormalRect(); + nsSize rowAvailSize(aReflowInput.AvailableWidth(), + std::max(aReflowInput.AvailableHeight() - rowRect.y, + 0)); + // don't let the available height exceed what + // CalculateRowBSizes set for it + rowAvailSize.height = std::min(rowAvailSize.height, rowRect.height); + ReflowInput rowReflowInput(&aPresContext, aReflowInput, row, + LogicalSize(row->GetWritingMode(), + rowAvailSize), + nullptr, + ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput); + rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page + + nscoord cellBSize = row->ReflowCellFrame(&aPresContext, rowReflowInput, + isTopOfPage, cell, + cellAvailBSize, status); + aDesiredBSize = std::max(aDesiredBSize, rowPos.y + cellBSize); + if (NS_FRAME_IS_COMPLETE(status)) { + if (cellBSize > cellAvailBSize) { + aFirstTruncatedRow = row; + if ((row != &aFirstRow) || !aFirstRowIsTopOfPage) { + // return now, since we will be getting another reflow after either (1) row is + // moved to the next page or (2) the row group is moved to the next page + return; + } + } + } + else { + if (!aContRow) { + CreateContinuingRowFrame(aPresContext, aLastRow, (nsIFrame**)&aContRow); + } + if (aContRow) { + if (row != &aLastRow) { + // aContRow needs a continuation for cell, since cell spanned into aLastRow + // but does not originate there + nsTableCellFrame* contCell = static_cast( + aPresContext.PresShell()->FrameConstructor()-> + CreateContinuingFrame(&aPresContext, cell, &aLastRow)); + int32_t colIndex; + cell->GetColIndex(colIndex); + aContRow->InsertCellFrame(contCell, colIndex); + } + } + } + } + } + } + if (!haveRowSpan) { + aDesiredBSize = aLastRow.GetNormalRect().YMost(); + } +} + +// Remove the next-in-flow of the row, its cells and their cell blocks. This +// is necessary in case the row doesn't need a continuation later on or needs +// a continuation which doesn't have the same number of cells that now exist. +void +nsTableRowGroupFrame::UndoContinuedRow(nsPresContext* aPresContext, + nsTableRowFrame* aRow) +{ + if (!aRow) return; // allow null aRow to avoid callers doing null checks + + // rowBefore was the prev-sibling of aRow's next-sibling before aRow was created + nsTableRowFrame* rowBefore = (nsTableRowFrame*)aRow->GetPrevInFlow(); + NS_PRECONDITION(mFrames.ContainsFrame(rowBefore), + "rowBefore not in our frame list?"); + + AutoFrameListPtr overflows(aPresContext, StealOverflowFrames()); + if (!rowBefore || !overflows || overflows->IsEmpty() || + overflows->FirstChild() != aRow) { + NS_ERROR("invalid continued row"); + return; + } + + // Destroy aRow, its cells, and their cell blocks. Cell blocks that have split + // will not have reflowed yet to pick up content from any overflow lines. + overflows->DestroyFrame(aRow); + + // Put the overflow rows into our child list + if (!overflows->IsEmpty()) { + mFrames.InsertFrames(nullptr, rowBefore, *overflows); + } +} + +static nsTableRowFrame* +GetRowBefore(nsTableRowFrame& aStartRow, + nsTableRowFrame& aRow) +{ + nsTableRowFrame* rowBefore = nullptr; + for (nsTableRowFrame* sib = &aStartRow; sib && (sib != &aRow); sib = sib->GetNextRow()) { + rowBefore = sib; + } + return rowBefore; +} + +nsresult +nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsTableFrame* aTableFrame, + nsReflowStatus& aStatus, + bool aRowForcedPageBreak) +{ + NS_PRECONDITION(aPresContext->IsPaginated(), "SplitRowGroup currently supports only paged media"); + + nsTableRowFrame* prevRowFrame = nullptr; + aDesiredSize.Height() = 0; + + nscoord availWidth = aReflowInput.AvailableWidth(); + nscoord availHeight = aReflowInput.AvailableHeight(); + + const bool borderCollapse = aTableFrame->IsBorderCollapse(); + + // get the page height + nscoord pageHeight = aPresContext->GetPageSize().height; + NS_ASSERTION(pageHeight != NS_UNCONSTRAINEDSIZE, + "The table shouldn't be split when there should be space"); + + bool isTopOfPage = aReflowInput.mFlags.mIsTopOfPage; + nsTableRowFrame* firstRowThisPage = GetFirstRow(); + + // Need to dirty the table's geometry, or else the row might skip + // reflowing its cell as an optimization. + aTableFrame->SetGeometryDirty(); + + // Walk each of the row frames looking for the first row frame that doesn't fit + // in the available space + for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame; rowFrame = rowFrame->GetNextRow()) { + bool rowIsOnPage = true; + nscoord cellSpacingB = aTableFrame->GetRowSpacing(rowFrame->GetRowIndex()); + nsRect rowRect = rowFrame->GetNormalRect(); + // See if the row fits on this page + if (rowRect.YMost() > availHeight) { + nsTableRowFrame* contRow = nullptr; + // Reflow the row in the availabe space and have it split if it is the 1st + // row (on the page) or there is at least 5% of the current page available + // XXX this 5% should be made a preference + if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20)) { + nsSize availSize(availWidth, std::max(availHeight - rowRect.y, 0)); + // don't let the available height exceed what CalculateRowHeights set for it + availSize.height = std::min(availSize.height, rowRect.height); + + ReflowInput rowReflowInput(aPresContext, aReflowInput, rowFrame, + LogicalSize(rowFrame->GetWritingMode(), + availSize), + nullptr, + ReflowInput::CALLER_WILL_INIT); + + InitChildReflowInput(*aPresContext, borderCollapse, rowReflowInput); + rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page + ReflowOutput rowMetrics(aReflowInput); + + // Get the old size before we reflow. + nsRect oldRowRect = rowFrame->GetRect(); + nsRect oldRowVisualOverflow = rowFrame->GetVisualOverflowRect(); + + // Reflow the cell with the constrained height. A cell with rowspan >1 will get this + // reflow later during SplitSpanningCells. + ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowInput, + 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus); + rowFrame->SetSize(nsSize(rowMetrics.Width(), rowMetrics.Height())); + rowFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED); + rowFrame->DidResize(); + + if (!aRowForcedPageBreak && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) && + ShouldAvoidBreakInside(aReflowInput)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + break; + } + + nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect, + oldRowVisualOverflow, + false); + + if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { + // The row frame is incomplete and all of the rowspan 1 cells' block frames split + if ((rowMetrics.Height() <= rowReflowInput.AvailableHeight()) || isTopOfPage) { + // The row stays on this page because either it split ok or we're on the top of page. + // If top of page and the height exceeded the avail height, then there will be data loss + NS_ASSERTION(rowMetrics.Height() <= rowReflowInput.AvailableHeight(), + "data loss - incomplete row needed more height than available, on top of page"); + CreateContinuingRowFrame(*aPresContext, *rowFrame, (nsIFrame**)&contRow); + if (contRow) { + aDesiredSize.Height() += rowMetrics.Height(); + if (prevRowFrame) + aDesiredSize.Height() += cellSpacingB; + } + else return NS_ERROR_NULL_POINTER; + } + else { + // Put the row on the next page to give it more height + rowIsOnPage = false; + } + } + else { + // The row frame is complete because either (1) its minimum height is greater than the + // available height we gave it, or (2) it may have been given a larger height through + // style than its content, or (3) it contains a rowspan >1 cell which hasn't been + // reflowed with a constrained height yet (we will find out when SplitSpanningCells is + // called below) + if (rowMetrics.Height() > availSize.height || + (NS_INLINE_IS_BREAK_BEFORE(aStatus) && !aRowForcedPageBreak)) { + // cases (1) and (2) + if (isTopOfPage) { + // We're on top of the page, so keep the row on this page. There will be data loss. + // Push the row frame that follows + nsTableRowFrame* nextRowFrame = rowFrame->GetNextRow(); + if (nextRowFrame) { + aStatus = NS_FRAME_NOT_COMPLETE; + } + aDesiredSize.Height() += rowMetrics.Height(); + if (prevRowFrame) + aDesiredSize.Height() += cellSpacingB; + NS_WARNING("data loss - complete row needed more height than available, on top of page"); + } + else { + // We're not on top of the page, so put the row on the next page to give it more height + rowIsOnPage = false; + } + } + } + } //if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20)) + else { + // put the row on the next page to give it more height + rowIsOnPage = false; + } + + nsTableRowFrame* lastRowThisPage = rowFrame; + nscoord spanningRowBottom = availHeight; + if (!rowIsOnPage) { + NS_ASSERTION(!contRow, "We should not have created a continuation if none of this row fits"); + if (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowInput)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + break; + } + if (prevRowFrame) { + spanningRowBottom = prevRowFrame->GetNormalRect().YMost(); + lastRowThisPage = prevRowFrame; + isTopOfPage = (lastRowThisPage == firstRowThisPage) && aReflowInput.mFlags.mIsTopOfPage; + aStatus = NS_FRAME_NOT_COMPLETE; + } + else { + // We can't push children, so let our parent reflow us again with more space + aDesiredSize.Height() = rowRect.YMost(); + aStatus = NS_FRAME_COMPLETE; + break; + } + } + // reflow the cells with rowspan >1 that occur on the page + + nsTableRowFrame* firstTruncatedRow; + nscoord bMost; + SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, *firstRowThisPage, + *lastRowThisPage, aReflowInput.mFlags.mIsTopOfPage, spanningRowBottom, contRow, + firstTruncatedRow, bMost); + if (firstTruncatedRow) { + // A rowspan >1 cell did not fit (and could not split) in the space we gave it + if (firstTruncatedRow == firstRowThisPage) { + if (aReflowInput.mFlags.mIsTopOfPage) { + NS_WARNING("data loss in a row spanned cell"); + } + else { + // We can't push children, so let our parent reflow us again with more space + aDesiredSize.Height() = rowRect.YMost(); + aStatus = NS_FRAME_COMPLETE; + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } + else { // (firstTruncatedRow != firstRowThisPage) + // Try to put firstTruncateRow on the next page + nsTableRowFrame* rowBefore = ::GetRowBefore(*firstRowThisPage, *firstTruncatedRow); + nscoord oldSpanningRowBottom = spanningRowBottom; + spanningRowBottom = rowBefore->GetNormalRect().YMost(); + + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + nsTableRowFrame* oldLastRowThisPage = lastRowThisPage; + lastRowThisPage = rowBefore; + aStatus = NS_FRAME_NOT_COMPLETE; + + // Call SplitSpanningCells again with rowBefore as the last row on the page + SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, + *firstRowThisPage, *rowBefore, aReflowInput.mFlags.mIsTopOfPage, + spanningRowBottom, contRow, firstTruncatedRow, aDesiredSize.Height()); + if (firstTruncatedRow) { + if (aReflowInput.mFlags.mIsTopOfPage) { + // We were better off with the 1st call to SplitSpanningCells, do it again + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + lastRowThisPage = oldLastRowThisPage; + spanningRowBottom = oldSpanningRowBottom; + SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, *firstRowThisPage, + *lastRowThisPage, aReflowInput.mFlags.mIsTopOfPage, spanningRowBottom, contRow, + firstTruncatedRow, aDesiredSize.Height()); + NS_WARNING("data loss in a row spanned cell"); + } + else { + // Let our parent reflow us again with more space + aDesiredSize.Height() = rowRect.YMost(); + aStatus = NS_FRAME_COMPLETE; + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } + } // if (firstTruncatedRow == firstRowThisPage) + } // if (firstTruncatedRow) + else { + aDesiredSize.Height() = std::max(aDesiredSize.Height(), bMost); + if (contRow) { + aStatus = NS_FRAME_NOT_COMPLETE; + } + } + if (NS_FRAME_IS_NOT_COMPLETE(aStatus) && !contRow) { + nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow(); + if (nextRow) { + PushChildren(nextRow, lastRowThisPage); + } + } + break; + } // if (rowRect.YMost() > availHeight) + else { + aDesiredSize.Height() = rowRect.YMost(); + prevRowFrame = rowFrame; + // see if there is a page break after the row + nsTableRowFrame* nextRow = rowFrame->GetNextRow(); + if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) { + PushChildren(nextRow, rowFrame); + aStatus = NS_FRAME_NOT_COMPLETE; + break; + } + } + // after the 1st row that has a height, we can't be on top + // of the page anymore. + isTopOfPage = isTopOfPage && rowRect.YMost() == 0; + } + return NS_OK; +} + +/** Layout the entire row group. + * This method stacks rows vertically according to HTML 4.0 rules. + * Rows are responsible for layout of their children. + */ +void +nsTableRowGroupFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableRowGroupFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + + aStatus = NS_FRAME_COMPLETE; + + // Row geometry may be going to change so we need to invalidate any row cursor. + ClearRowCursor(); + + // see if a special bsize reflow needs to occur due to having a pct bsize + nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput); + + nsTableFrame* tableFrame = GetTableFrame(); + TableRowGroupReflowInput state(aReflowInput, tableFrame); + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); + if (collapseGroup) { + tableFrame->SetNeedToCollapse(true); + } + + // Check for an overflow list + MoveOverflowToChildList(); + + // Reflow the existing frames. + bool splitDueToPageBreak = false; + ReflowChildren(aPresContext, aDesiredSize, state, aStatus, + &splitDueToPageBreak); + + // See if all the frames fit. Do not try to split anything if we're + // not paginated ... we can't split across columns yet. + if (aReflowInput.mFlags.mTableIsSplittable && + NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() && + (NS_FRAME_NOT_COMPLETE == aStatus || splitDueToPageBreak || + aDesiredSize.Height() > aReflowInput.AvailableHeight())) { + // Nope, find a place to split the row group + bool specialReflow = (bool)aReflowInput.mFlags.mSpecialBSizeReflow; + ((ReflowInput::ReflowInputFlags&)aReflowInput.mFlags).mSpecialBSizeReflow = false; + + SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus, + splitDueToPageBreak); + + ((ReflowInput::ReflowInputFlags&)aReflowInput.mFlags).mSpecialBSizeReflow = specialReflow; + } + + // XXXmats The following is just bogus. We leave it here for now because + // ReflowChildren should pull up rows from our next-in-flow before returning + // a Complete status, but doesn't (bug 804888). + if (GetNextInFlow() && GetNextInFlow()->PrincipalChildList().FirstChild()) { + NS_FRAME_SET_INCOMPLETE(aStatus); + } + + SetHasStyleBSize((NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) && + (aReflowInput.ComputedBSize() > 0)); + + // Just set our isize to what was available. + // The table will calculate the isize and not use our value. + WritingMode wm = aReflowInput.GetWritingMode(); + aDesiredSize.ISize(wm) = aReflowInput.AvailableISize(); + + aDesiredSize.UnionOverflowAreasWithDesiredBounds(); + + // If our parent is in initial reflow, it'll handle invalidating our + // entire overflow rect. + if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && + nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { + InvalidateFrame(); + } + + FinishAndStoreOverflow(&aDesiredSize); + + // Any absolutely-positioned children will get reflowed in + // nsFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +bool +nsTableRowGroupFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) +{ + // Row cursor invariants depend on the visual overflow area of the rows, + // which may have changed, so we need to clear the cursor now. + ClearRowCursor(); + return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas); +} + +/* virtual */ void +nsTableRowGroupFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsContainerFrame::DidSetStyleContext(aOldStyleContext); + + if (!aOldStyleContext) //avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) { + TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(), + GetRowCount()); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void +nsTableRowGroupFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + + DrainSelfOverflowList(); // ensure the last frame is in mFrames + ClearRowCursor(); + + // collect the new row frames in an array + // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here. + AutoTArray rows; + for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { + nsTableRowFrame *rowFrame = do_QueryFrame(e.get()); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION(mozilla::StyleDisplay::TableRow == + e.get()->StyleDisplay()->mDisplay, + "wrong display type on rowframe"); + rows.AppendElement(rowFrame); + } + } + + int32_t rowIndex = GetRowCount(); + // Append the frames to the sibling chain + mFrames.AppendFrames(nullptr, aFrameList); + + if (rows.Length() > 0) { + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->AppendRows(this, rowIndex, rows); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void +nsTableRowGroupFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + + DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames + ClearRowCursor(); + + // collect the new row frames in an array + // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here. + nsTableFrame* tableFrame = GetTableFrame(); + nsTArray rows; + bool gotFirstRow = false; + for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { + nsTableRowFrame *rowFrame = do_QueryFrame(e.get()); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION(mozilla::StyleDisplay::TableRow == + e.get()->StyleDisplay()->mDisplay, + "wrong display type on rowframe"); + rows.AppendElement(rowFrame); + if (!gotFirstRow) { + rowFrame->SetFirstInserted(true); + gotFirstRow = true; + tableFrame->SetRowInserted(true); + } + } + } + + int32_t startRowIndex = GetStartRowIndex(); + // Insert the frames in the sibling chain + mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); + + int32_t numRows = rows.Length(); + if (numRows > 0) { + nsTableRowFrame* prevRow = (nsTableRowFrame *)nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, nsGkAtoms::tableRowFrame); + int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex; + tableFrame->InsertRows(this, rows, rowIndex, true); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void +nsTableRowGroupFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + + ClearRowCursor(); + + // XXX why are we doing the QI stuff? There shouldn't be any non-rows here. + nsTableRowFrame* rowFrame = do_QueryFrame(aOldFrame); + if (rowFrame) { + nsTableFrame* tableFrame = GetTableFrame(); + // remove the rows from the table (and flag a rebalance) + tableFrame->RemoveRows(*rowFrame, 1, true); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } + mFrames.DestroyFrame(aOldFrame); +} + +/* virtual */ nsMargin +nsTableRowGroupFrame::GetUsedMargin() const +{ + return nsMargin(0,0,0,0); +} + +/* virtual */ nsMargin +nsTableRowGroupFrame::GetUsedBorder() const +{ + return nsMargin(0,0,0,0); +} + +/* virtual */ nsMargin +nsTableRowGroupFrame::GetUsedPadding() const +{ + return nsMargin(0,0,0,0); +} + +nscoord +nsTableRowGroupFrame::GetBSizeBasis(const ReflowInput& aReflowInput) +{ + nscoord result = 0; + nsTableFrame* tableFrame = GetTableFrame(); + int32_t startRowIndex = GetStartRowIndex(); + if ((aReflowInput.ComputedBSize() > 0) && (aReflowInput.ComputedBSize() < NS_UNCONSTRAINEDSIZE)) { + nscoord cellSpacing = tableFrame->GetRowSpacing(startRowIndex, + std::max(startRowIndex, + startRowIndex + GetRowCount() - 1)); + result = aReflowInput.ComputedBSize() - cellSpacing; + } + else { + const ReflowInput* parentRI = aReflowInput.mParentReflowInput; + if (parentRI && (tableFrame != parentRI->mFrame)) { + parentRI = parentRI->mParentReflowInput; + } + if (parentRI && (tableFrame == parentRI->mFrame) && + (parentRI->ComputedBSize() > 0) && (parentRI->ComputedBSize() < NS_UNCONSTRAINEDSIZE)) { + nscoord cellSpacing = tableFrame->GetRowSpacing(-1, tableFrame->GetRowCount()); + result = parentRI->ComputedBSize() - cellSpacing; + } + } + + return result; +} + +bool +nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame, + nsTableRowFrame* aRowFrame) +{ + int32_t rowIndex = aRowFrame->GetRowIndex(); + + // It's a simple row frame if there are no cells that span into or + // across the row + int32_t numEffCols = aTableFrame->GetEffectiveColCount(); + if (!aTableFrame->RowIsSpannedInto(rowIndex, numEffCols) && + !aTableFrame->RowHasSpanningCells(rowIndex, numEffCols)) { + return true; + } + + return false; +} + +nsIAtom* +nsTableRowGroupFrame::GetType() const +{ + return nsGkAtoms::tableRowGroupFrame; +} + +/** find page break before the first row **/ +bool +nsTableRowGroupFrame::HasInternalBreakBefore() const +{ + nsIFrame* firstChild = mFrames.FirstChild(); + if (!firstChild) + return false; + return firstChild->StyleDisplay()->mBreakBefore; +} + +/** find page break after the last row **/ +bool +nsTableRowGroupFrame::HasInternalBreakAfter() const +{ + nsIFrame* lastChild = mFrames.LastChild(); + if (!lastChild) + return false; + return lastChild->StyleDisplay()->mBreakAfter; +} +/* ----- global methods ----- */ + +nsTableRowGroupFrame* +NS_NewTableRowGroupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableRowGroupFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("TableRowGroup"), aResult); +} +#endif + +LogicalMargin +nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM) +{ + LogicalMargin border(aWM); + nsTableRowFrame* firstRowFrame = nullptr; + nsTableRowFrame* lastRowFrame = nullptr; + for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame; rowFrame = rowFrame->GetNextRow()) { + if (!firstRowFrame) { + firstRowFrame = rowFrame; + } + lastRowFrame = rowFrame; + } + if (firstRowFrame) { + border.BStart(aWM) = nsPresContext:: + CSSPixelsToAppUnits(firstRowFrame->GetBStartBCBorderWidth()); + border.BEnd(aWM) = nsPresContext:: + CSSPixelsToAppUnits(lastRowFrame->GetBEndBCBorderWidth()); + } + return border; +} + +void nsTableRowGroupFrame::SetContinuousBCBorderWidth(LogicalSide aForSide, + BCPixelSize aPixelValue) +{ + switch (aForSide) { + case eLogicalSideIEnd: + mIEndContBorderWidth = aPixelValue; + return; + case eLogicalSideBEnd: + mBEndContBorderWidth = aPixelValue; + return; + case eLogicalSideIStart: + mIStartContBorderWidth = aPixelValue; + return; + default: + NS_ERROR("invalid LogicalSide argument"); + } +} + +//nsILineIterator methods +int32_t +nsTableRowGroupFrame::GetNumLines() +{ + return GetRowCount(); +} + +bool +nsTableRowGroupFrame::GetDirection() +{ + return (NS_STYLE_DIRECTION_RTL == + GetTableFrame()->StyleVisibility()->mDirection); +} + +NS_IMETHODIMP +nsTableRowGroupFrame::GetLine(int32_t aLineNumber, + nsIFrame** aFirstFrameOnLine, + int32_t* aNumFramesOnLine, + nsRect& aLineBounds) +{ + NS_ENSURE_ARG_POINTER(aFirstFrameOnLine); + NS_ENSURE_ARG_POINTER(aNumFramesOnLine); + + nsTableFrame* table = GetTableFrame(); + nsTableCellMap* cellMap = table->GetCellMap(); + + *aFirstFrameOnLine = nullptr; + *aNumFramesOnLine = 0; + aLineBounds.SetRect(0, 0, 0, 0); + + if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) { + return NS_OK; + } + aLineNumber += GetStartRowIndex(); + + *aNumFramesOnLine = cellMap->GetNumCellsOriginatingInRow(aLineNumber); + if (*aNumFramesOnLine == 0) { + return NS_OK; + } + int32_t colCount = table->GetColCount(); + for (int32_t i = 0; i < colCount; i++) { + CellData* data = cellMap->GetDataAt(aLineNumber, i); + if (data && data->IsOrig()) { + *aFirstFrameOnLine = (nsIFrame*)data->GetCellFrame(); + nsIFrame* parent = (*aFirstFrameOnLine)->GetParent(); + aLineBounds = parent->GetRect(); + return NS_OK; + } + } + NS_ERROR("cellmap is lying"); + return NS_ERROR_FAILURE; +} + +int32_t +nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame, int32_t aStartLine) +{ + NS_ENSURE_TRUE(aFrame, -1); + + nsTableRowFrame *rowFrame = do_QueryFrame(aFrame); + NS_ASSERTION(rowFrame, "RowGroup contains a frame that is not a row"); + + int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex(); + + return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1; +} + +NS_IMETHODIMP +nsTableRowGroupFrame::CheckLineOrder(int32_t aLine, + bool *aIsReordered, + nsIFrame **aFirstVisual, + nsIFrame **aLastVisual) +{ + *aIsReordered = false; + *aFirstVisual = nullptr; + *aLastVisual = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsTableRowGroupFrame::FindFrameAt(int32_t aLineNumber, + nsPoint aPos, + nsIFrame** aFrameFound, + bool* aPosIsBeforeFirstFrame, + bool* aPosIsAfterLastFrame) +{ + nsTableFrame* table = GetTableFrame(); + nsTableCellMap* cellMap = table->GetCellMap(); + + WritingMode wm = table->GetWritingMode(); + nsSize containerSize = table->GetSize(); + LogicalPoint pos(wm, aPos, containerSize); + + *aFrameFound = nullptr; + *aPosIsBeforeFirstFrame = true; + *aPosIsAfterLastFrame = false; + + aLineNumber += GetStartRowIndex(); + int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber); + if (numCells == 0) { + return NS_OK; + } + + nsIFrame* frame = nullptr; + int32_t colCount = table->GetColCount(); + for (int32_t i = 0; i < colCount; i++) { + CellData* data = cellMap->GetDataAt(aLineNumber, i); + if (data && data->IsOrig()) { + frame = (nsIFrame*)data->GetCellFrame(); + break; + } + } + NS_ASSERTION(frame, "cellmap is lying"); + bool isRTL = (NS_STYLE_DIRECTION_RTL == + table->StyleVisibility()->mDirection); + + nsIFrame* closestFromStart = nullptr; + nsIFrame* closestFromEnd = nullptr; + int32_t n = numCells; + nsIFrame* firstFrame = frame; + while (n--) { + LogicalRect rect = frame->GetLogicalRect(wm, containerSize); + if (rect.ISize(wm) > 0) { + // If pos.I() is inside this frame - this is it + if (rect.IStart(wm) <= pos.I(wm) && rect.IEnd(wm) > pos.I(wm)) { + closestFromStart = closestFromEnd = frame; + break; + } + if (rect.IStart(wm) < pos.I(wm)) { + if (!closestFromStart || + rect.IEnd(wm) > closestFromStart-> + GetLogicalRect(wm, containerSize).IEnd(wm)) + closestFromStart = frame; + } + else { + if (!closestFromEnd || + rect.IStart(wm) < closestFromEnd-> + GetLogicalRect(wm, containerSize).IStart(wm)) + closestFromEnd = frame; + } + } + frame = frame->GetNextSibling(); + } + if (!closestFromStart && !closestFromEnd) { + // All frames were zero-width. Just take the first one. + closestFromStart = closestFromEnd = firstFrame; + } + *aPosIsBeforeFirstFrame = isRTL ? !closestFromEnd : !closestFromStart; + *aPosIsAfterLastFrame = isRTL ? !closestFromStart : !closestFromEnd; + if (closestFromStart == closestFromEnd) { + *aFrameFound = closestFromStart; + } + else if (!closestFromStart) { + *aFrameFound = closestFromEnd; + } + else if (!closestFromEnd) { + *aFrameFound = closestFromStart; + } + else { // we're between two frames + nscoord delta = + closestFromEnd->GetLogicalRect(wm, containerSize).IStart(wm) - + closestFromStart->GetLogicalRect(wm, containerSize).IEnd(wm); + if (pos.I(wm) < closestFromStart-> + GetLogicalRect(wm, containerSize).IEnd(wm) + delta/2) { + *aFrameFound = closestFromStart; + } else { + *aFrameFound = closestFromEnd; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsTableRowGroupFrame::GetNextSiblingOnLine(nsIFrame*& aFrame, + int32_t aLineNumber) +{ + NS_ENSURE_ARG_POINTER(aFrame); + aFrame = aFrame->GetNextSibling(); + return NS_OK; +} + +//end nsLineIterator methods + +NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowCursorProperty, + nsTableRowGroupFrame::FrameCursorData) + +void +nsTableRowGroupFrame::ClearRowCursor() +{ + if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { + return; + } + + RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); + Properties().Delete(RowCursorProperty()); +} + +nsTableRowGroupFrame::FrameCursorData* +nsTableRowGroupFrame::SetupRowCursor() +{ + if (HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { + // We already have a valid row cursor. Don't waste time rebuilding it. + return nullptr; + } + + nsIFrame* f = mFrames.FirstChild(); + int32_t count; + for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) { + f = f->GetNextSibling(); + } + if (!f) { + // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother + return nullptr; + } + + FrameCursorData* data = new FrameCursorData(); + if (!data) + return nullptr; + Properties().Set(RowCursorProperty(), data); + AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR); + return data; +} + +nsIFrame* +nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove) +{ + if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) { + return nullptr; + } + + FrameCursorData* property = Properties().Get(RowCursorProperty()); + uint32_t cursorIndex = property->mCursorIndex; + uint32_t frameCount = property->mFrames.Length(); + if (cursorIndex >= frameCount) + return nullptr; + nsIFrame* cursorFrame = property->mFrames[cursorIndex]; + + // The cursor's frame list excludes frames with empty overflow-area, so + // we don't need to check that here. + + // We use property->mOverflowBelow here instead of computing the frame's + // true overflowArea.YMost(), because it is essential for the thresholds + // to form a monotonically increasing sequence. Otherwise we would break + // encountering a row whose overflowArea.YMost() is <= aY but which has + // a row above it containing cell(s) that span to include aY. + while (cursorIndex > 0 && + cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow > aY) { + --cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + while (cursorIndex + 1 < frameCount && + cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow <= aY) { + ++cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + + property->mCursorIndex = cursorIndex; + *aOverflowAbove = property->mOverflowAbove; + return cursorFrame; +} + +bool +nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) +{ + // Relative positioning can cause table parts to move, but we will still paint + // the backgrounds for the parts under them at their 'normal' position. That + // means that we must consider the overflow rects at both positions. For + // example, if we use relative positioning to move a row-spanning cell, we + // will still paint the row background for that cell at its normal position, + // which will overflow the row. + // XXX(seth): This probably isn't correct in the presence of transforms. + nsRect positionedOverflowRect = aFrame->GetVisualOverflowRect(); + nsPoint positionedToNormal = aFrame->GetNormalPosition() - aFrame->GetPosition(); + nsRect normalOverflowRect = positionedOverflowRect + positionedToNormal; + + nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect); + if (overflowRect.IsEmpty()) + return true; + nscoord overflowAbove = -overflowRect.y; + nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height; + mOverflowAbove = std::max(mOverflowAbove, overflowAbove); + mOverflowBelow = std::max(mOverflowBelow, overflowBelow); + return mFrames.AppendElement(aFrame) != nullptr; +} + +void +nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrame(aDisplayItemKey); + GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey); +} + +void +nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) +{ + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey); + // If we have filters applied that would affects our bounds, then + // we get an inactive layer created and this is computed + // within FrameLayerBuilder + GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey); +} diff --git a/layout/tables/nsTableRowGroupFrame.h b/layout/tables/nsTableRowGroupFrame.h new file mode 100644 index 000000000..7abdd4b74 --- /dev/null +++ b/layout/tables/nsTableRowGroupFrame.h @@ -0,0 +1,450 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTableRowGroupFrame_h__ +#define nsTableRowGroupFrame_h__ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsIAtom.h" +#include "nsILineIterator.h" +#include "nsTablePainter.h" +#include "nsTArray.h" +#include "nsTableFrame.h" +#include "mozilla/WritingModes.h" + +class nsTableRowFrame; +namespace mozilla { +struct TableRowGroupReflowInput; +} // namespace mozilla + +#define MIN_ROWS_NEEDING_CURSOR 20 + +/** + * nsTableRowGroupFrame is the frame that maps row groups + * (HTML tags THEAD, TFOOT, and TBODY). This class cannot be reused + * outside of an nsTableFrame. It assumes that its parent is an nsTableFrame, and + * its children are nsTableRowFrames. + * + * @see nsTableFrame + * @see nsTableRowFrame + */ +class nsTableRowGroupFrame final + : public nsContainerFrame + , public nsILineIterator +{ + using TableRowGroupReflowInput = mozilla::TableRowGroupReflowInput; + +public: + NS_DECL_QUERYFRAME_TARGET(nsTableRowGroupFrame) + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + /** instantiate a new instance of nsTableRowFrame. + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableRowGroupFrame* NS_NewTableRowGroupFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + virtual ~nsTableRowGroupFrame(); + + nsTableFrame* GetTableFrame() const + { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableFrame); + return static_cast(parent); + } + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + /** @see nsIFrame::DidSetStyleContext */ + virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override; + + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual nsMargin GetUsedMargin() const override; + virtual nsMargin GetUsedBorder() const override; + virtual nsMargin GetUsedPadding() const override; + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + /** calls Reflow for all of its child rows. + * Rows are all set to the same isize and stacked in the block direction. + *

    rows are not split unless absolutely necessary. + * + * @param aDesiredSize isize set to isize of rows, bsize set to + * sum of bsize of rows that fit in AvailableBSize. + * + * @see nsIFrame::Reflow + */ + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::tableRowGroupFrame + */ + virtual nsIAtom* GetType() const override; + + nsTableRowFrame* GetFirstRow(); + nsTableRowFrame* GetLastRow(); + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + virtual mozilla::WritingMode GetWritingMode() const override + { return GetTableFrame()->GetWritingMode(); } + + /** return the number of child rows (not necessarily == number of child frames) */ + int32_t GetRowCount(); + + /** return the table-relative row index of the first row in this rowgroup. + * if there are no rows, -1 is returned. + */ + int32_t GetStartRowIndex(); + + /** Adjust the row indices of all rows whose index is >= aRowIndex. + * @param aRowIndex - start adjusting with this index + * @param aAdjustment - shift the row index by this amount + */ + void AdjustRowIndices(int32_t aRowIndex, + int32_t anAdjustment); + + /** + * Used for header and footer row group frames that are repeated when + * splitting a table frame. + * + * Performs any table specific initialization + * + * @param aHeaderFooterFrame the original header or footer row group frame + * that was repeated + */ + nsresult InitRepeatedFrame(nsTableRowGroupFrame* aHeaderFooterFrame); + + + /** + * Get the total bsize of all the row rects + */ + nscoord GetBSizeBasis(const ReflowInput& aReflowInput); + + mozilla::LogicalMargin GetBCBorderWidth(mozilla::WritingMode aWM); + + /** + * Gets inner border widths before collapsing with cell borders + * Caller must get bstart border from previous row group or from table + * GetContinuousBCBorderWidth will not overwrite aBorder.BStart() + * see nsTablePainter about continuous borders + */ + void GetContinuousBCBorderWidth(mozilla::WritingMode aWM, + mozilla::LogicalMargin& aBorder); + + /** + * Sets full border widths before collapsing with cell borders + * @param aForSide - side to set; only IEnd, IStart, BEnd are valid + */ + void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide, + BCPixelSize aPixelValue); + /** + * Adjust to the effect of visibility:collapse on the row group and + * its children + * @return additional shift bstart-wards that should be applied + * to subsequent rowgroups due to rows and this + * rowgroup being collapsed + * @param aBTotalOffset the total amount that the rowgroup is shifted + * @param aISize new isize of the rowgroup + * @param aWM the table's writing mode + */ + nscoord CollapseRowGroupIfNecessary(nscoord aBTotalOffset, + nscoord aISize, + mozilla::WritingMode aWM); + +// nsILineIterator methods +public: + virtual void DisposeLineIterator() override { } + + // The table row is the equivalent to a line in block layout. + // The nsILineIterator assumes that a line resides in a block, this role is + // fullfilled by the row group. Rows in table are counted relative to the + // table. The row index of row corresponds to the cellmap coordinates. The + // line index with respect to a row group can be computed by substracting the + // row index of the first row in the row group. + + /** Get the number of rows in a row group + * @return the number of lines in a row group + */ + virtual int32_t GetNumLines() override; + + /** @see nsILineIterator.h GetDirection + * @return true if the table is rtl + */ + virtual bool GetDirection() override; + + /** Return structural information about a line. + * @param aLineNumber - the index of the row relative to the row group + * If the line-number is invalid then + * aFirstFrameOnLine will be nullptr and + * aNumFramesOnLine will be zero. + * @param aFirstFrameOnLine - the first cell frame that originates in row + * with a rowindex that matches a line number + * @param aNumFramesOnLine - return the numbers of cells originating in + * this row + * @param aLineBounds - rect of the row + */ + NS_IMETHOD GetLine(int32_t aLineNumber, + nsIFrame** aFirstFrameOnLine, + int32_t* aNumFramesOnLine, + nsRect& aLineBounds) override; + + /** Given a frame that's a child of the rowgroup, find which line its on. + * @param aFrame - frame, should be a row + * @param aStartLine - minimal index to return + * @return row index relative to the row group if this a row + * frame and the index is at least aStartLine. + * -1 if the frame cannot be found. + */ + virtual int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) override; + + /** Find the orginating cell frame on a row that is the nearest to the + * inline-dir coordinate of aPos. + * @param aLineNumber - the index of the row relative to the row group + * @param aPos - coordinate in twips relative to the + * origin of the row group + * @param aFrameFound - pointer to the cellframe + * @param aPosIsBeforeFirstFrame - the point is before the first originating + * cellframe + * @param aPosIsAfterLastFrame - the point is after the last originating + * cellframe + */ + NS_IMETHOD FindFrameAt(int32_t aLineNumber, + nsPoint aPos, + nsIFrame** aFrameFound, + bool* aPosIsBeforeFirstFrame, + bool* aPosIsAfterLastFrame) override; + + /** Check whether visual and logical order of cell frames within a line are + * identical. As the layout will reorder them this is always the case + * @param aLine - the index of the row relative to the table + * @param aIsReordered - returns false + * @param aFirstVisual - if the table is rtl first originating cell frame + * @param aLastVisual - if the table is rtl last originating cell frame + */ + + NS_IMETHOD CheckLineOrder(int32_t aLine, + bool *aIsReordered, + nsIFrame **aFirstVisual, + nsIFrame **aLastVisual) override; + + /** Find the next originating cell frame that originates in the row. + * @param aFrame - cell frame to start with, will return the next cell + * originating in a row + * @param aLineNumber - the index of the row relative to the table + */ + NS_IMETHOD GetNextSiblingOnLine(nsIFrame*& aFrame, int32_t aLineNumber) override; + + // row cursor methods to speed up searching for the row(s) + // containing a point. The basic idea is that we set the cursor + // property if the rows' y and yMosts are non-decreasing (considering only + // rows with nonempty overflowAreas --- empty overflowAreas never participate + // in event handling or painting), and the rowgroup has sufficient number of + // rows. The cursor property points to a "recently used" row. If we get a + // series of requests that work on rows "near" the cursor, then we can find + // those nearby rows quickly by starting our search at the cursor. + // This code is based on the line cursor code in nsBlockFrame. It's more general + // though, and could be extracted and used elsewhere. + struct FrameCursorData { + nsTArray mFrames; + uint32_t mCursorIndex; + nscoord mOverflowAbove; + nscoord mOverflowBelow; + + FrameCursorData() + : mFrames(MIN_ROWS_NEEDING_CURSOR), mCursorIndex(0), mOverflowAbove(0), + mOverflowBelow(0) {} + + bool AppendFrame(nsIFrame* aFrame); + + void FinishBuildingCursor() { + mFrames.Compact(); + } + }; + + // Clear out row cursor because we're disturbing the rows (e.g., Reflow) + void ClearRowCursor(); + + /** + * Get the first row that might contain y-coord 'aY', or nullptr if you must search + * all rows. + * The actual row returned might not contain 'aY', but if not, it is guaranteed + * to be before any row which does contain 'aY'. + * aOverflowAbove is the maximum over all rows of -row.GetOverflowRect().y. + * To find all rows that intersect the vertical interval aY/aYMost, call + * GetFirstRowContaining(aY, &overflowAbove), and then iterate through all + * rows until reaching a row where row->GetRect().y - overflowAbove >= aYMost. + * That row and all subsequent rows cannot intersect the interval. + */ + nsIFrame* GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove); + + /** + * Set up the row cursor. After this, call AppendFrame for every + * child frame in sibling order. Ensure that the child frame y and YMost values + * form non-decreasing sequences (should always be true for table rows); + * if this is violated, call ClearRowCursor(). If we return nullptr, then we + * decided not to use a cursor or we already have one set up. + */ + FrameCursorData* SetupRowCursor(); + + virtual nsILineIterator* GetLineIterator() override { return this; } + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart)); + } + + virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override; + virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + +protected: + explicit nsTableRowGroupFrame(nsStyleContext* aContext); + + void InitChildReflowInput(nsPresContext& aPresContext, + bool aBorderCollapse, + ReflowInput& aReflowInput); + + virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override; + + void PlaceChild(nsPresContext* aPresContext, + TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame, + mozilla::WritingMode aWM, + const mozilla::LogicalPoint& aKidPosition, + const nsSize& aContainerSize, + ReflowOutput& aDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidVisualOverflow); + + void CalculateRowBSizes(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput); + + void DidResizeRows(ReflowOutput& aDesiredSize); + + void SlideChild(TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame); + + /** + * Reflow the frames we've already created + * + * @param aPresContext presentation context to use + * @param aReflowInput current inline state + */ + void ReflowChildren(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + TableRowGroupReflowInput& aReflowInput, + nsReflowStatus& aStatus, + bool* aPageBreakBeforeEnd = nullptr); + + nsresult SplitRowGroup(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsTableFrame* aTableFrame, + nsReflowStatus& aStatus, + bool aRowForcedPageBreak); + + void SplitSpanningCells(nsPresContext& aPresContext, + const ReflowInput& aReflowInput, + nsTableFrame& aTableFrame, + nsTableRowFrame& aFirstRow, + nsTableRowFrame& aLastRow, + bool aFirstRowIsTopOfPage, + nscoord aSpanningRowBottom, + nsTableRowFrame*& aContRowFrame, + nsTableRowFrame*& aFirstTruncatedRow, + nscoord& aDesiredHeight); + + void CreateContinuingRowFrame(nsPresContext& aPresContext, + nsIFrame& aRowFrame, + nsIFrame** aContRowFrame); + + bool IsSimpleRowFrame(nsTableFrame* aTableFrame, + nsTableRowFrame* aRowFrame); + + void GetNextRowSibling(nsIFrame** aRowFrame); + + void UndoContinuedRow(nsPresContext* aPresContext, + nsTableRowFrame* aRow); + +private: + // border widths in pixels in the collapsing border model + BCPixelSize mIEndContBorderWidth; + BCPixelSize mBEndContBorderWidth; + BCPixelSize mIStartContBorderWidth; + +public: + bool IsRepeatable() const; + void SetRepeatable(bool aRepeatable); + bool HasStyleBSize() const; + void SetHasStyleBSize(bool aValue); + bool HasInternalBreakBefore() const; + bool HasInternalBreakAfter() const; +}; + + +inline bool nsTableRowGroupFrame::IsRepeatable() const +{ + return HasAnyStateBits(NS_ROWGROUP_REPEATABLE); +} + +inline void nsTableRowGroupFrame::SetRepeatable(bool aRepeatable) +{ + if (aRepeatable) { + AddStateBits(NS_ROWGROUP_REPEATABLE); + } else { + RemoveStateBits(NS_ROWGROUP_REPEATABLE); + } +} + +inline bool nsTableRowGroupFrame::HasStyleBSize() const +{ + return HasAnyStateBits(NS_ROWGROUP_HAS_STYLE_BSIZE); +} + +inline void nsTableRowGroupFrame::SetHasStyleBSize(bool aValue) +{ + if (aValue) { + AddStateBits(NS_ROWGROUP_HAS_STYLE_BSIZE); + } else { + RemoveStateBits(NS_ROWGROUP_HAS_STYLE_BSIZE); + } +} + +inline void +nsTableRowGroupFrame::GetContinuousBCBorderWidth(mozilla::WritingMode aWM, + mozilla::LogicalMargin& aBorder) +{ + int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel(); + aBorder.IEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips, + mIEndContBorderWidth); + aBorder.BEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips, + mBEndContBorderWidth); + aBorder.IStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips, + mIStartContBorderWidth); +} +#endif diff --git a/layout/tables/nsTableWrapperFrame.cpp b/layout/tables/nsTableWrapperFrame.cpp new file mode 100644 index 000000000..e44652a73 --- /dev/null +++ b/layout/tables/nsTableWrapperFrame.cpp @@ -0,0 +1,1101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsTableWrapperFrame.h" + +#include "nsFrameManager.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsStyleContext.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsCSSRendering.h" +#include "nsIContent.h" +#include "prinrval.h" +#include "nsGkAtoms.h" +#include "nsHTMLParts.h" +#include "nsIPresShell.h" +#include "nsIServiceManager.h" +#include "nsIDOMNode.h" +#include "nsDisplayList.h" +#include "nsLayoutUtils.h" +#include "nsIFrameInlines.h" +#include + +using namespace mozilla; +using namespace mozilla::layout; + +#define NO_SIDE 100 + +/* virtual */ nscoord +nsTableWrapperFrame::GetLogicalBaseline(WritingMode aWritingMode) const +{ + nsIFrame* kid = mFrames.FirstChild(); + if (!kid) { + NS_NOTREACHED("no inner table"); + return nsContainerFrame::GetLogicalBaseline(aWritingMode); + } + + return kid->GetLogicalBaseline(aWritingMode) + + kid->BStart(aWritingMode, mRect.Size()); +} + +nsTableWrapperFrame::nsTableWrapperFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext) +{ +} + +nsTableWrapperFrame::~nsTableWrapperFrame() +{ +} + +NS_QUERYFRAME_HEAD(nsTableWrapperFrame) + NS_QUERYFRAME_ENTRY(nsTableWrapperFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +#ifdef ACCESSIBILITY +a11y::AccType +nsTableWrapperFrame::AccessibleType() +{ + return a11y::eHTMLTableType; +} +#endif + +void +nsTableWrapperFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + DestroyAbsoluteFrames(aDestructRoot); + mCaptionFrames.DestroyFramesFrom(aDestructRoot); + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +const nsFrameList& +nsTableWrapperFrame::GetChildList(ChildListID aListID) const +{ + if (aListID == kCaptionList) { + return mCaptionFrames; + } + + return nsContainerFrame::GetChildList(aListID); +} + +void +nsTableWrapperFrame::GetChildLists(nsTArray* aLists) const +{ + nsContainerFrame::GetChildLists(aLists); + mCaptionFrames.AppendIfNonempty(aLists, kCaptionList); +} + +void +nsTableWrapperFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + if (kCaptionList == aListID) { + // the frame constructor already checked for table-caption display type + MOZ_ASSERT(mCaptionFrames.IsEmpty(), + "already have child frames in CaptionList"); + mCaptionFrames.SetFrames(aChildList); + } else { + MOZ_ASSERT(kPrincipalList != aListID || + (aChildList.FirstChild() && + aChildList.FirstChild() == aChildList.LastChild() && + nsGkAtoms::tableFrame == aChildList.FirstChild()->GetType()), + "expected a single table frame in principal child list"); + nsContainerFrame::SetInitialChildList(aListID, aChildList); + } +} + +void +nsTableWrapperFrame::AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) +{ + // We only have two child frames: the inner table and a caption frame. + // The inner frame is provided when we're initialized, and it cannot change + MOZ_ASSERT(kCaptionList == aListID, "unexpected child list"); + MOZ_ASSERT(aFrameList.IsEmpty() || + aFrameList.FirstChild()->IsTableCaption(), + "appending non-caption frame to captionList"); + mCaptionFrames.AppendFrames(this, aFrameList); + + // Reflow the new caption frame. It's already marked dirty, so + // just tell the pres shell. + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsTableWrapperFrame::InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + MOZ_ASSERT(kCaptionList == aListID, "unexpected child list"); + MOZ_ASSERT(aFrameList.IsEmpty() || + aFrameList.FirstChild()->IsTableCaption(), + "inserting non-caption frame into captionList"); + MOZ_ASSERT(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + mCaptionFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); + + // Reflow the new caption frame. It's already marked dirty, so + // just tell the pres shell. + PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void +nsTableWrapperFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + // We only have two child frames: the inner table and one caption frame. + // The inner frame can't be removed so this should be the caption + NS_PRECONDITION(kCaptionList == aListID, "can't remove inner frame"); + + if (HasSideCaption()) { + // The old caption isize had an effect on the inner table isize, so + // we're going to need to reflow it. Mark it dirty + InnerTableFrame()->AddStateBits(NS_FRAME_IS_DIRTY); + } + + // Remove the frame and destroy it + mCaptionFrames.DestroyFrame(aOldFrame); + + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); // also means child removed +} + +void +nsTableWrapperFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // No border, background or outline are painted because they all belong + // to the inner table. + + // If there's no caption, take a short cut to avoid having to create + // the special display list set and then sort it. + if (mCaptionFrames.IsEmpty()) { + BuildDisplayListForInnerTable(aBuilder, aDirtyRect, aLists); + return; + } + + nsDisplayListCollection set; + BuildDisplayListForInnerTable(aBuilder, aDirtyRect, set); + + nsDisplayListSet captionSet(set, set.BlockBorderBackgrounds()); + BuildDisplayListForChild(aBuilder, mCaptionFrames.FirstChild(), + aDirtyRect, captionSet); + + // Now we have to sort everything by content order, since the caption + // may be somewhere inside the table + set.SortAllByContentOrder(GetContent()); + set.MoveTo(aLists); +} + +void +nsTableWrapperFrame::BuildDisplayListForInnerTable(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + // Just paint the regular children, but the children's background is our + // true background (there should only be one, the real table) + nsIFrame* kid = mFrames.FirstChild(); + // The children should be in content order + while (kid) { + BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + kid = kid->GetNextSibling(); + } +} + +nsStyleContext* +nsTableWrapperFrame::GetParentStyleContext(nsIFrame** aProviderFrame) const +{ + // The table wrapper frame and the (inner) table frame split the style + // data by giving the table frame the style context associated with + // the table content node and creating a style context for the wrapper + // frame that is a *child* of the table frame's style context, + // matching the ::-moz-table-wrapper pseudo-element. html.css has a + // rule that causes that pseudo-element (and thus the wrapper table) + // to inherit *some* style properties from the table frame. The + // children of the table inherit directly from the inner table, and + // the table wrapper's style context is a leaf. + + return (*aProviderFrame = InnerTableFrame())->StyleContext(); +} + +// INCREMENTAL REFLOW HELPER FUNCTIONS + +void +nsTableWrapperFrame::InitChildReflowInput(nsPresContext& aPresContext, + ReflowInput& aReflowInput) +{ + nsMargin collapseBorder; + nsMargin collapsePadding(0,0,0,0); + nsMargin* pCollapseBorder = nullptr; + nsMargin* pCollapsePadding = nullptr; + Maybe cbSize; + if (aReflowInput.mFrame == InnerTableFrame()) { + WritingMode wm = aReflowInput.GetWritingMode(); + if (InnerTableFrame()->IsBorderCollapse()) { + LogicalMargin border = InnerTableFrame()->GetIncludedOuterBCBorder(wm); + collapseBorder = border.GetPhysicalMargin(wm); + pCollapseBorder = &collapseBorder; + pCollapsePadding = &collapsePadding; + } + // Propagate our stored CB size if present, minus any margins. + if (!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { + LogicalSize* cb = Properties().Get(GridItemCBSizeProperty()); + if (cb) { + cbSize.emplace(*cb); + *cbSize -= aReflowInput.ComputedLogicalMargin().Size(wm); + } + } + } + aReflowInput.Init(&aPresContext, cbSize.ptrOr(nullptr), pCollapseBorder, + pCollapsePadding); +} + +// get the margin and padding data. ReflowInput doesn't handle the +// case of auto margins +void +nsTableWrapperFrame::GetChildMargin(nsPresContext* aPresContext, + const ReflowInput& aOuterRI, + nsIFrame* aChildFrame, + nscoord aAvailISize, + LogicalMargin& aMargin) +{ + NS_ASSERTION(!aChildFrame->IsTableCaption(), + "didn't expect caption frame; writing-mode may be wrong!"); + + // construct a reflow state to compute margin and padding. Auto margins + // will not be computed at this time. + + // create and init the child reflow state + // XXX We really shouldn't construct a reflow state to do this. + WritingMode wm = aOuterRI.GetWritingMode(); + LogicalSize availSize(wm, aAvailISize, aOuterRI.AvailableSize(wm).BSize(wm)); + ReflowInput childRI(aPresContext, aOuterRI, aChildFrame, availSize, + nullptr, ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(*aPresContext, childRI); + + aMargin = childRI.ComputedLogicalMargin(); +} + +static nsSize +GetContainingBlockSize(const ReflowInput& aOuterRI) +{ + nsSize size(0,0); + const ReflowInput* containRS = aOuterRI.mCBReflowInput; + + if (containRS) { + size.width = containRS->ComputedWidth(); + if (NS_UNCONSTRAINEDSIZE == size.width) { + size.width = 0; + } + size.height = containRS->ComputedHeight(); + if (NS_UNCONSTRAINEDSIZE == size.height) { + size.height = 0; + } + } + return size; +} + +/* virtual */ nscoord +nsTableWrapperFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nscoord iSize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + InnerTableFrame(), nsLayoutUtils::MIN_ISIZE); + DISPLAY_MIN_WIDTH(this, iSize); + if (mCaptionFrames.NotEmpty()) { + nscoord capISize = + nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + mCaptionFrames.FirstChild(), + nsLayoutUtils::MIN_ISIZE); + if (HasSideCaption()) { + iSize += capISize; + } else { + if (capISize > iSize) { + iSize = capISize; + } + } + } + return iSize; +} + +/* virtual */ nscoord +nsTableWrapperFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nscoord maxISize; + DISPLAY_PREF_WIDTH(this, maxISize); + + maxISize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + InnerTableFrame(), nsLayoutUtils::PREF_ISIZE); + if (mCaptionFrames.NotEmpty()) { + uint8_t captionSide = GetCaptionSide(); + switch (captionSide) { + case NS_STYLE_CAPTION_SIDE_LEFT: + case NS_STYLE_CAPTION_SIDE_RIGHT: + { + nscoord capMin = + nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + mCaptionFrames.FirstChild(), + nsLayoutUtils::MIN_ISIZE); + maxISize += capMin; + } + break; + default: + { + nsLayoutUtils::IntrinsicISizeType iwt; + if (captionSide == NS_STYLE_CAPTION_SIDE_TOP || + captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) { + // Don't let the caption's pref isize expand the table's pref + // isize. + iwt = nsLayoutUtils::MIN_ISIZE; + } else { + NS_ASSERTION(captionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE || + captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE, + "unexpected caption side"); + iwt = nsLayoutUtils::PREF_ISIZE; + } + nscoord capPref = + nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + mCaptionFrames.FirstChild(), + iwt); + maxISize = std::max(maxISize, capPref); + } + break; + } + } + return maxISize; +} + +nscoord +nsTableWrapperFrame::ChildShrinkWrapISize(nsRenderingContext* aRenderingContext, + nsIFrame* aChildFrame, + WritingMode aWM, + LogicalSize aCBSize, + nscoord aAvailableISize, + nscoord* aMarginResult) const +{ + AutoMaybeDisableFontInflation an(aChildFrame); + + // For the caption frame, child's WM may differ from the table's main WM. + WritingMode childWM = aChildFrame->GetWritingMode(); + + SizeComputationInput offsets(aChildFrame, aRenderingContext, aWM, + aCBSize.ISize(aWM)); + LogicalSize marginSize = + offsets.ComputedLogicalMargin().Size(childWM).ConvertTo(aWM, childWM); + LogicalSize paddingSize = + offsets.ComputedLogicalPadding().Size(childWM).ConvertTo(aWM, childWM); + LogicalSize bpSize = + offsets.ComputedLogicalBorderPadding().Size(childWM).ConvertTo(aWM, childWM); + + // Shrink-wrap aChildFrame by default, except if we're a stretched grid item. + auto flags = ComputeSizeFlags::eShrinkWrap; + auto parent = GetParent(); + nsIAtom* parentFrameType = parent ? parent->GetType() : nullptr; + bool isGridItem = (parentFrameType == nsGkAtoms::gridContainerFrame && + !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); + if (MOZ_UNLIKELY(isGridItem) && + !StyleMargin()->HasInlineAxisAuto(aWM)) { + auto inlineAxisAlignment = aWM.IsOrthogonalTo(parent->GetWritingMode()) ? + StylePosition()->UsedAlignSelf(parent->StyleContext()) : + StylePosition()->UsedJustifySelf(parent->StyleContext()); + if (inlineAxisAlignment == NS_STYLE_ALIGN_NORMAL || + inlineAxisAlignment == NS_STYLE_ALIGN_STRETCH) { + flags = nsIFrame::ComputeSizeFlags::eDefault; + } + } + + LogicalSize size = + aChildFrame->ComputeSize(aRenderingContext, aWM, aCBSize, aAvailableISize, + marginSize, bpSize - paddingSize, paddingSize, + flags); + if (aMarginResult) { + *aMarginResult = offsets.ComputedLogicalMargin().IStartEnd(aWM); + } + return size.ISize(aWM) + marginSize.ISize(aWM) + bpSize.ISize(aWM); +} + +/* virtual */ +LogicalSize +nsTableWrapperFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + nscoord kidAvailableISize = aAvailableISize - aMargin.ISize(aWM); + NS_ASSERTION(aBorder.IsAllZero() && aPadding.IsAllZero(), + "Table wrapper frames cannot have borders or paddings"); + + // When we're shrink-wrapping, our auto size needs to wrap around the + // actual size of the table, which (if it is specified as a percent) + // could be something that is not reflected in our GetMinISize and + // GetPrefISize. See bug 349457 for an example. + + // Match the availableISize logic in Reflow. + uint8_t captionSide = GetCaptionSide(); + nscoord inlineSize; + if (captionSide == NO_SIDE) { + inlineSize = ChildShrinkWrapISize(aRenderingContext, InnerTableFrame(), aWM, + aCBSize, kidAvailableISize); + } else if (captionSide == NS_STYLE_CAPTION_SIDE_LEFT || + captionSide == NS_STYLE_CAPTION_SIDE_RIGHT) { + nscoord capISize = ChildShrinkWrapISize(aRenderingContext, + mCaptionFrames.FirstChild(), aWM, + aCBSize, kidAvailableISize); + inlineSize = capISize + ChildShrinkWrapISize(aRenderingContext, + InnerTableFrame(), aWM, + aCBSize, + kidAvailableISize - capISize); + } else if (captionSide == NS_STYLE_CAPTION_SIDE_TOP || + captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) { + nscoord margin; + inlineSize = ChildShrinkWrapISize(aRenderingContext, InnerTableFrame(), aWM, + aCBSize, kidAvailableISize, &margin); + nscoord capISize = ChildShrinkWrapISize(aRenderingContext, + mCaptionFrames.FirstChild(), aWM, + aCBSize, inlineSize - margin); + if (capISize > inlineSize) { + inlineSize = capISize; + } + } else { + NS_ASSERTION(captionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE || + captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE, + "unexpected caption-side"); + inlineSize = ChildShrinkWrapISize(aRenderingContext, InnerTableFrame(), aWM, + aCBSize, kidAvailableISize); + nscoord capISize = ChildShrinkWrapISize(aRenderingContext, + mCaptionFrames.FirstChild(), aWM, + aCBSize, kidAvailableISize); + if (capISize > inlineSize) { + inlineSize = capISize; + } + } + + return LogicalSize(aWM, inlineSize, NS_UNCONSTRAINEDSIZE); +} + +uint8_t +nsTableWrapperFrame::GetCaptionSide() +{ + if (mCaptionFrames.NotEmpty()) { + return mCaptionFrames.FirstChild()->StyleTableBorder()->mCaptionSide; + } + else { + return NO_SIDE; // no caption + } +} + +uint8_t +nsTableWrapperFrame::GetCaptionVerticalAlign() +{ + const nsStyleCoord& va = + mCaptionFrames.FirstChild()->StyleDisplay()->mVerticalAlign; + + return (va.GetUnit() == eStyleUnit_Enumerated) + ? va.GetIntValue() + : NS_STYLE_VERTICAL_ALIGN_TOP; +} + +void +nsTableWrapperFrame::SetDesiredSize(uint8_t aCaptionSide, + const LogicalSize& aInnerSize, + const LogicalSize& aCaptionSize, + const LogicalMargin& aInnerMargin, + const LogicalMargin& aCaptionMargin, + nscoord& aISize, + nscoord& aBSize, + WritingMode aWM) +{ + aISize = aBSize = 0; + + // compute the overall inline-size + switch (aCaptionSide) { + case NS_STYLE_CAPTION_SIDE_LEFT: + aISize = + std::max(aInnerMargin.LineLeft(aWM), + aCaptionMargin.IStartEnd(aWM) + aCaptionSize.ISize(aWM)) + + aInnerSize.ISize(aWM) + aInnerMargin.LineRight(aWM); + break; + case NS_STYLE_CAPTION_SIDE_RIGHT: + aISize = + std::max(aInnerMargin.LineRight(aWM), + aCaptionMargin.IStartEnd(aWM) + aCaptionSize.ISize(aWM)) + + aInnerSize.ISize(aWM) + aInnerMargin.LineLeft(aWM); + break; + default: + aISize = + std::max(aInnerMargin.IStartEnd(aWM) + aInnerSize.ISize(aWM), + aCaptionMargin.IStartEnd(aWM) + aCaptionSize.ISize(aWM)); + break; + } + + // compute the overall block-size + switch (aCaptionSide) { + case NS_STYLE_CAPTION_SIDE_TOP: + case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE: + aBSize = aInnerSize.BSize(aWM) + aInnerMargin.BEnd(aWM); + aBSize += + std::max(aInnerMargin.BStart(aWM), + aCaptionSize.BSize(aWM) + aCaptionMargin.BStartEnd(aWM)); + break; + case NS_STYLE_CAPTION_SIDE_BOTTOM: + case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE: + aBSize = aInnerSize.BSize(aWM) + aInnerMargin.BStart(aWM); + aBSize += + std::max(aInnerMargin.BEnd(aWM), + aCaptionSize.BSize(aWM) + aCaptionMargin.BStartEnd(aWM)); + break; + case NS_STYLE_CAPTION_SIDE_LEFT: + case NS_STYLE_CAPTION_SIDE_RIGHT: + aBSize = aInnerMargin.BStart(aWM); + aBSize += + std::max(aInnerSize.BSize(aWM) + aInnerMargin.BEnd(aWM), + aCaptionSize.BSize(aWM) + aCaptionMargin.BEnd(aWM)); + break; + default: + NS_ASSERTION(aCaptionSide == NO_SIDE, "unexpected caption side"); + aBSize = aInnerSize.BSize(aWM) + aInnerMargin.BStartEnd(aWM); + break; + } + + // negative sizes can upset overflow-area code + aISize = std::max(aISize, 0); + aBSize = std::max(aBSize, 0); +} + +nsresult +nsTableWrapperFrame::GetCaptionOrigin(uint32_t aCaptionSide, + const LogicalSize& aContainBlockSize, + const LogicalSize& aInnerSize, + const LogicalMargin& aInnerMargin, + const LogicalSize& aCaptionSize, + LogicalMargin& aCaptionMargin, + LogicalPoint& aOrigin, + WritingMode aWM) +{ + aOrigin.I(aWM) = aOrigin.B(aWM) = 0; + if ((NS_UNCONSTRAINEDSIZE == aInnerSize.ISize(aWM)) || + (NS_UNCONSTRAINEDSIZE == aInnerSize.BSize(aWM)) || + (NS_UNCONSTRAINEDSIZE == aCaptionSize.ISize(aWM)) || + (NS_UNCONSTRAINEDSIZE == aCaptionSize.BSize(aWM))) { + return NS_OK; + } + if (mCaptionFrames.IsEmpty()) { + return NS_OK; + } + + NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) && + NS_AUTOMARGIN != aCaptionMargin.BStart(aWM) && + NS_AUTOMARGIN != aCaptionMargin.BEnd(aWM), + "The computed caption margin is auto?"); + + // inline-dir computation + switch (aCaptionSide) { + case NS_STYLE_CAPTION_SIDE_BOTTOM: + case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE: + aOrigin.I(aWM) = aCaptionMargin.IStart(aWM); + if (aCaptionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) { + // We placed the caption using only the table's isize as available + // isize, and we should position it this way as well. + aOrigin.I(aWM) += aInnerMargin.IStart(aWM); + } + break; + case NS_STYLE_CAPTION_SIDE_LEFT: + case NS_STYLE_CAPTION_SIDE_RIGHT: + aOrigin.I(aWM) = aCaptionMargin.IStart(aWM); + if (aWM.IsBidiLTR() == (aCaptionSide == NS_STYLE_CAPTION_SIDE_RIGHT)) { + aOrigin.I(aWM) += aInnerMargin.IStart(aWM) + aInnerSize.ISize(aWM); + } + break; + default: // block-start + NS_ASSERTION(aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP || + aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE, + "unexpected caption side"); + aOrigin.I(aWM) = aCaptionMargin.IStart(aWM); + if (aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP) { + // We placed the caption using only the table's isize as available + // isize, and we should position it this way as well. + aOrigin.I(aWM) += aInnerMargin.IStart(aWM); + } + break; + } + // block-dir computation + switch (aCaptionSide) { + case NS_STYLE_CAPTION_SIDE_RIGHT: + case NS_STYLE_CAPTION_SIDE_LEFT: + aOrigin.B(aWM) = aInnerMargin.BStart(aWM); + switch (GetCaptionVerticalAlign()) { + case NS_STYLE_VERTICAL_ALIGN_MIDDLE: + aOrigin.B(aWM) = std::max(0, aInnerMargin.BStart(aWM) + + ((aInnerSize.BSize(aWM) - + aCaptionSize.BSize(aWM)) / 2)); + break; + case NS_STYLE_VERTICAL_ALIGN_BOTTOM: + aOrigin.B(aWM) = std::max(0, aInnerMargin.BStart(aWM) + + aInnerSize.BSize(aWM) - + aCaptionSize.BSize(aWM)); + break; + default: + break; + } + break; + case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE: + case NS_STYLE_CAPTION_SIDE_BOTTOM: + aOrigin.B(aWM) = aInnerMargin.BStart(aWM) + aInnerSize.BSize(aWM) + + aCaptionMargin.BStart(aWM); + break; + case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE: + case NS_STYLE_CAPTION_SIDE_TOP: + aOrigin.B(aWM) = aInnerMargin.BStart(aWM) + aCaptionMargin.BStart(aWM); + break; + default: + NS_NOTREACHED("Unknown caption alignment type"); + break; + } + return NS_OK; +} + +nsresult +nsTableWrapperFrame::GetInnerOrigin(uint32_t aCaptionSide, + const LogicalSize& aContainBlockSize, + const LogicalSize& aCaptionSize, + const LogicalMargin& aCaptionMargin, + const LogicalSize& aInnerSize, + LogicalMargin& aInnerMargin, + LogicalPoint& aOrigin, + WritingMode aWM) +{ + NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) && + NS_AUTOMARGIN != aCaptionMargin.IEnd(aWM), + "The computed caption margin is auto?"); + NS_ASSERTION(NS_AUTOMARGIN != aInnerMargin.IStart(aWM) && + NS_AUTOMARGIN != aInnerMargin.IEnd(aWM) && + NS_AUTOMARGIN != aInnerMargin.BStart(aWM) && + NS_AUTOMARGIN != aInnerMargin.BEnd(aWM), + "The computed inner margin is auto?"); + + aOrigin.I(aWM) = aOrigin.B(aWM) = 0; + if ((NS_UNCONSTRAINEDSIZE == aInnerSize.ISize(aWM)) || + (NS_UNCONSTRAINEDSIZE == aInnerSize.BSize(aWM)) || + (NS_UNCONSTRAINEDSIZE == aCaptionSize.ISize(aWM)) || + (NS_UNCONSTRAINEDSIZE == aCaptionSize.BSize(aWM))) { + return NS_OK; + } + + nscoord minCapISize = + aCaptionSize.ISize(aWM) + aCaptionMargin.IStartEnd(aWM); + + // inline-dir computation + switch (aCaptionSide) { + case NS_STYLE_CAPTION_SIDE_LEFT: + case NS_STYLE_CAPTION_SIDE_RIGHT: + if (aWM.IsBidiLTR() == (aCaptionSide == NS_STYLE_CAPTION_SIDE_LEFT)) { + if (aInnerMargin.IStart(aWM) < minCapISize) { + // shift the inner table to get some place for the caption + aInnerMargin.IEnd(aWM) += aInnerMargin.IStart(aWM) - minCapISize; + aInnerMargin.IEnd(aWM) = std::max(0, aInnerMargin.IEnd(aWM)); + aInnerMargin.IStart(aWM) = minCapISize; + } + } + aOrigin.I(aWM) = aInnerMargin.IStart(aWM); + break; + default: + NS_ASSERTION(aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP || + aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE || + aCaptionSide == NS_STYLE_CAPTION_SIDE_BOTTOM || + aCaptionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE || + aCaptionSide == NO_SIDE, + "unexpected caption side"); + aOrigin.I(aWM) = aInnerMargin.IStart(aWM); + break; + } + + // block-dir computation + switch (aCaptionSide) { + case NS_STYLE_CAPTION_SIDE_BOTTOM: + case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE: + aOrigin.B(aWM) = aInnerMargin.BStart(aWM); + break; + case NS_STYLE_CAPTION_SIDE_LEFT: + case NS_STYLE_CAPTION_SIDE_RIGHT: + aOrigin.B(aWM) = aInnerMargin.BStart(aWM); + switch (GetCaptionVerticalAlign()) { + case NS_STYLE_VERTICAL_ALIGN_MIDDLE: + aOrigin.B(aWM) = + std::max(aInnerMargin.BStart(aWM), + (aCaptionSize.BSize(aWM) - aInnerSize.BSize(aWM)) / 2); + break; + case NS_STYLE_VERTICAL_ALIGN_BOTTOM: + aOrigin.B(aWM) = + std::max(aInnerMargin.BStart(aWM), + aCaptionSize.BSize(aWM) - aInnerSize.BSize(aWM)); + break; + default: + break; + } + break; + case NO_SIDE: + case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE: + case NS_STYLE_CAPTION_SIDE_TOP: + aOrigin.B(aWM) = aInnerMargin.BStart(aWM) + aCaptionSize.BSize(aWM) + + aCaptionMargin.BStartEnd(aWM); + break; + default: + NS_NOTREACHED("Unknown caption alignment type"); + break; + } + return NS_OK; +} + +void +nsTableWrapperFrame::OuterBeginReflowChild(nsPresContext* aPresContext, + nsIFrame* aChildFrame, + const ReflowInput& aOuterRI, + Maybe& aChildRI, + nscoord aAvailISize) +{ + // work around pixel rounding errors, round down to ensure we don't exceed the avail height in + WritingMode wm = aChildFrame->GetWritingMode(); + LogicalSize outerSize = aOuterRI.AvailableSize(wm); + nscoord availBSize = outerSize.BSize(wm); + if (NS_UNCONSTRAINEDSIZE != availBSize) { + if (mCaptionFrames.FirstChild() == aChildFrame) { + availBSize = NS_UNCONSTRAINEDSIZE; + } else { + LogicalMargin margin(wm); + GetChildMargin(aPresContext, aOuterRI, aChildFrame, + outerSize.ISize(wm), margin); + + NS_ASSERTION(NS_UNCONSTRAINEDSIZE != margin.BStart(wm), + "No unconstrainedsize arithmetic, please"); + availBSize -= margin.BStart(wm); + + NS_ASSERTION(NS_UNCONSTRAINEDSIZE != margin.BEnd(wm), + "No unconstrainedsize arithmetic, please"); + availBSize -= margin.BEnd(wm); + } + } + LogicalSize availSize(wm, aAvailISize, availBSize); + // create and init the child reflow state, using passed-in Maybe<>, + // so that caller can use it after we return. + aChildRI.emplace(aPresContext, aOuterRI, aChildFrame, availSize, + nullptr, ReflowInput::CALLER_WILL_INIT); + InitChildReflowInput(*aPresContext, *aChildRI); + + // see if we need to reset top-of-page due to a caption + if (aChildRI->mFlags.mIsTopOfPage && + mCaptionFrames.FirstChild() == aChildFrame) { + uint8_t captionSide = GetCaptionSide(); + if (captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM || + captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE) { + aChildRI->mFlags.mIsTopOfPage = false; + } + } +} + +void +nsTableWrapperFrame::OuterDoReflowChild(nsPresContext* aPresContext, + nsIFrame* aChildFrame, + const ReflowInput& aChildRI, + ReflowOutput& aMetrics, + nsReflowStatus& aStatus) +{ + // Using zero as containerSize here because we want consistency between + // the GetLogicalPosition and ReflowChild calls, to avoid unnecessarily + // changing the frame's coordinates; but we don't yet know its final + // position anyway so the actual value is unimportant. + const nsSize zeroCSize; + WritingMode wm = aChildRI.GetWritingMode(); + + // Use the current position as a best guess for placement. + LogicalPoint childPt = aChildFrame->GetLogicalPosition(wm, zeroCSize); + uint32_t flags = NS_FRAME_NO_MOVE_FRAME; + + // We don't want to delete our next-in-flow's child if it's an inner table + // frame, because table wrapper frames always assume that their inner table + // frames don't go away. If a table wrapper frame is removed because it is + // a next-in-flow of an already complete table wrapper frame, then it will + // take care of removing it's inner table frame. + if (aChildFrame == InnerTableFrame()) { + flags |= NS_FRAME_NO_DELETE_NEXT_IN_FLOW_CHILD; + } + + ReflowChild(aChildFrame, aPresContext, aMetrics, aChildRI, + wm, childPt, zeroCSize, flags, aStatus); +} + +void +nsTableWrapperFrame::UpdateOverflowAreas(ReflowOutput& aMet) +{ + aMet.SetOverflowAreasToDesiredBounds(); + ConsiderChildOverflow(aMet.mOverflowAreas, InnerTableFrame()); + if (mCaptionFrames.NotEmpty()) { + ConsiderChildOverflow(aMet.mOverflowAreas, mCaptionFrames.FirstChild()); + } +} + +void +nsTableWrapperFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aOuterRI, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsTableWrapperFrame"); + DISPLAY_REFLOW(aPresContext, this, aOuterRI, aDesiredSize, aStatus); + + // Initialize out parameters + aDesiredSize.ClearSize(); + aStatus = NS_FRAME_COMPLETE; + + if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + // Set up our kids. They're already present, on an overflow list, + // or there are none so we'll create them now + MoveOverflowToChildList(); + } + + Maybe captionRI; + Maybe innerRI; + + nsRect origInnerRect = InnerTableFrame()->GetRect(); + nsRect origInnerVisualOverflow = InnerTableFrame()->GetVisualOverflowRect(); + bool innerFirstReflow = + InnerTableFrame()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + nsRect origCaptionRect; + nsRect origCaptionVisualOverflow; + bool captionFirstReflow = false; + if (mCaptionFrames.NotEmpty()) { + origCaptionRect = mCaptionFrames.FirstChild()->GetRect(); + origCaptionVisualOverflow = + mCaptionFrames.FirstChild()->GetVisualOverflowRect(); + captionFirstReflow = + mCaptionFrames.FirstChild()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + } + + // ComputeAutoSize has to match this logic. + WritingMode wm = aOuterRI.GetWritingMode(); + uint8_t captionSide = GetCaptionSide(); + WritingMode captionWM = wm; // will be changed below if necessary + + if (captionSide == NO_SIDE) { + // We don't have a caption. + OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI, + innerRI, aOuterRI.ComputedSize(wm).ISize(wm)); + } else if (captionSide == NS_STYLE_CAPTION_SIDE_LEFT || + captionSide == NS_STYLE_CAPTION_SIDE_RIGHT) { + // ComputeAutoSize takes care of making side captions small. Compute + // the caption's size first, and tell the table to fit in what's left. + OuterBeginReflowChild(aPresContext, mCaptionFrames.FirstChild(), aOuterRI, + captionRI, aOuterRI.ComputedSize(wm).ISize(wm)); + captionWM = captionRI->GetWritingMode(); + nscoord innerAvailISize = aOuterRI.ComputedSize(wm).ISize(wm) - + captionRI->ComputedSizeWithMarginBorderPadding(wm).ISize(wm); + OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI, + innerRI, innerAvailISize); + } else if (captionSide == NS_STYLE_CAPTION_SIDE_TOP || + captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) { + // Compute the table's size first, and then prevent the caption from + // being larger in the inline dir unless it has to be. + // + // Note that CSS 2.1 (but not 2.0) says: + // The width of the anonymous box is the border-edge width of the + // table box inside it + // We don't actually make our anonymous box that isize (if we did, + // it would break 'auto' margins), but this effectively does that. + OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI, + innerRI, aOuterRI.ComputedSize(wm).ISize(wm)); + // It's good that CSS 2.1 says not to include margins, since we + // can't, since they already been converted so they exactly + // fill the available isize (ignoring the margin on one side if + // neither are auto). (We take advantage of that later when we call + // GetCaptionOrigin, though.) + nscoord innerBorderISize = + innerRI->ComputedSizeWithBorderPadding(wm).ISize(wm); + OuterBeginReflowChild(aPresContext, mCaptionFrames.FirstChild(), aOuterRI, + captionRI, innerBorderISize); + captionWM = captionRI->GetWritingMode(); + } else { + NS_ASSERTION(captionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE || + captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE, + "unexpected caption-side"); + // Size the table and the caption independently. + captionWM = mCaptionFrames.FirstChild()->GetWritingMode(); + OuterBeginReflowChild(aPresContext, mCaptionFrames.FirstChild(), + aOuterRI, captionRI, + aOuterRI.ComputedSize(captionWM).ISize(captionWM)); + OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI, + innerRI, aOuterRI.ComputedSize(wm).ISize(wm)); + } + + // First reflow the caption. + Maybe captionMet; + LogicalSize captionSize(wm); + LogicalMargin captionMargin(wm); + if (mCaptionFrames.NotEmpty()) { + captionMet.emplace(wm); + nsReflowStatus capStatus; // don't let the caption cause incomplete + OuterDoReflowChild(aPresContext, mCaptionFrames.FirstChild(), + *captionRI, *captionMet, capStatus); + captionSize.ISize(wm) = captionMet->ISize(wm); + captionSize.BSize(wm) = captionMet->BSize(wm); + captionMargin = + captionRI->ComputedLogicalMargin().ConvertTo(wm, captionWM); + // Now that we know the bsize of the caption, reduce the available bsize + // for the table frame if we are bsize constrained and the caption is above + // or below the inner table. Also reduce the CB size that we store for + // our children in case we're a grid item, by the same amount. + LogicalSize* cbSize = Properties().Get(GridItemCBSizeProperty()); + if (NS_UNCONSTRAINEDSIZE != aOuterRI.AvailableBSize() || cbSize) { + nscoord captionBSize = 0; + nscoord captionISize = 0; + switch (captionSide) { + case NS_STYLE_CAPTION_SIDE_TOP: + case NS_STYLE_CAPTION_SIDE_BOTTOM: + case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE: + case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE: + captionBSize = captionSize.BSize(wm) + captionMargin.BStartEnd(wm); + break; + case NS_STYLE_CAPTION_SIDE_LEFT: + case NS_STYLE_CAPTION_SIDE_RIGHT: + captionISize = captionSize.ISize(wm) + captionMargin.IStartEnd(wm); + break; + } + if (NS_UNCONSTRAINEDSIZE != aOuterRI.AvailableBSize()) { + innerRI->AvailableBSize() = + std::max(0, innerRI->AvailableBSize() - captionBSize); + } + if (cbSize) { + // Shrink the CB size by the size reserved for the caption. + LogicalSize oldCBSize = *cbSize; + cbSize->ISize(wm) = std::max(0, cbSize->ISize(wm) - captionISize); + cbSize->BSize(wm) = std::max(0, cbSize->BSize(wm) - captionBSize); + if (oldCBSize != *cbSize) { + // Reset the inner table's ReflowInput to stretch it to the new size. + innerRI.reset(); + OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI, + innerRI, aOuterRI.ComputedSize(wm).ISize(wm)); + } + } + } + } + + // Then, now that we know how much to reduce the isize of the inner + // table to account for side captions, reflow the inner table. + ReflowOutput innerMet(innerRI->GetWritingMode()); + OuterDoReflowChild(aPresContext, InnerTableFrame(), *innerRI, + innerMet, aStatus); + LogicalSize innerSize(wm, innerMet.ISize(wm), innerMet.BSize(wm)); + LogicalMargin innerMargin = innerRI->ComputedLogicalMargin(); + + LogicalSize containSize(wm, GetContainingBlockSize(aOuterRI)); + + // Now that we've reflowed both we can place them. + // XXXldb Most of the input variables here are now uninitialized! + + // XXX Need to recompute inner table's auto margins for the case of side + // captions. (Caption's are broken too, but that should be fixed earlier.) + + // Compute the desiredSize so that we can use it as the containerSize + // for the FinishReflowChild calls below. + LogicalSize desiredSize(wm); + SetDesiredSize(captionSide, innerSize, captionSize, + innerMargin, captionMargin, + desiredSize.ISize(wm), desiredSize.BSize(wm), wm); + aDesiredSize.SetSize(wm, desiredSize); + nsSize containerSize = aDesiredSize.PhysicalSize(); + // XXX It's possible for this to be NS_UNCONSTRAINEDSIZE, which will result + // in assertions from FinishReflowChild. + + if (mCaptionFrames.NotEmpty()) { + LogicalPoint captionOrigin(wm); + GetCaptionOrigin(captionSide, containSize, innerSize, innerMargin, + captionSize, captionMargin, captionOrigin, wm); + FinishReflowChild(mCaptionFrames.FirstChild(), aPresContext, *captionMet, + captionRI.ptr(), wm, captionOrigin, containerSize, 0); + captionRI.reset(); + } + // XXX If the bsize is constrained then we need to check whether + // everything still fits... + + LogicalPoint innerOrigin(wm); + GetInnerOrigin(captionSide, containSize, captionSize, captionMargin, + innerSize, innerMargin, innerOrigin, wm); + FinishReflowChild(InnerTableFrame(), aPresContext, innerMet, innerRI.ptr(), + wm, innerOrigin, containerSize, 0); + innerRI.reset(); + + nsTableFrame::InvalidateTableFrame(InnerTableFrame(), origInnerRect, + origInnerVisualOverflow, + innerFirstReflow); + if (mCaptionFrames.NotEmpty()) { + nsTableFrame::InvalidateTableFrame(mCaptionFrames.FirstChild(), + origCaptionRect, + origCaptionVisualOverflow, + captionFirstReflow); + } + + UpdateOverflowAreas(aDesiredSize); + + if (GetPrevInFlow()) { + ReflowOverflowContainerChildren(aPresContext, aOuterRI, + aDesiredSize.mOverflowAreas, 0, + aStatus); + } + + FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aOuterRI, aStatus); + + // Return our desired rect + + NS_FRAME_SET_TRUNCATION(aStatus, aOuterRI, aDesiredSize); +} + +nsIAtom* +nsTableWrapperFrame::GetType() const +{ + return nsGkAtoms::tableWrapperFrame; +} + +/* ----- global methods ----- */ + +nsIContent* +nsTableWrapperFrame::GetCellAt(uint32_t aRowIdx, uint32_t aColIdx) const +{ + nsTableCellMap* cellMap = InnerTableFrame()->GetCellMap(); + if (!cellMap) { + return nullptr; + } + + nsTableCellFrame* cell = cellMap->GetCellInfoAt(aRowIdx, aColIdx); + if (!cell) { + return nullptr; + } + + return cell->GetContent(); +} + + +nsTableWrapperFrame* +NS_NewTableWrapperFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableWrapperFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableWrapperFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTableWrapperFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("TableWrapper"), aResult); +} +#endif + diff --git a/layout/tables/nsTableWrapperFrame.h b/layout/tables/nsTableWrapperFrame.h new file mode 100644 index 000000000..45d7c33e4 --- /dev/null +++ b/layout/tables/nsTableWrapperFrame.h @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsTableWrapperFrame_h__ +#define nsTableWrapperFrame_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsCellMap.h" +#include "nsTableFrame.h" + +/** + * Primary frame for a table element, + * the nsTableWrapperFrame contains 0 or one caption frame, and a nsTableFrame + * pseudo-frame (referred to as the "inner frame'). + */ +class nsTableWrapperFrame : public nsContainerFrame +{ +public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS + + NS_DECL_QUERYFRAME_TARGET(nsTableWrapperFrame) + + /** instantiate a new instance of nsTableRowFrame. + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableWrapperFrame* NS_NewTableWrapperFrame(nsIPresShell* aPresShell, + nsStyleContext* aContext); + + // nsIFrame overrides - see there for a description + + virtual void DestroyFrom(nsIFrame* aDestructRoot) override; + + virtual const nsFrameList& GetChildList(ChildListID aListID) const override; + virtual void GetChildLists(nsTArray* aLists) const override; + + virtual void SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) override; + virtual void AppendFrames(ChildListID aListID, + nsFrameList& aFrameList) override; + virtual void InsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) override; + virtual void RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) override; + + virtual nsContainerFrame* GetContentInsertionFrame() override { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + +#ifdef ACCESSIBILITY + virtual mozilla::a11y::AccType AccessibleType() override; +#endif + + virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) override; + + void BuildDisplayListForInnerTable(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists); + + virtual nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const override; + + bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM, + BaselineSharingGroup aBaselineGroup, + nscoord* aBaseline) const override + { + auto innerTable = InnerTableFrame(); + nscoord offset; + if (innerTable->GetNaturalBaselineBOffset(aWM, aBaselineGroup, &offset)) { + auto bStart = innerTable->BStart(aWM, mRect.Size()); + if (aBaselineGroup == BaselineSharingGroup::eFirst) { + *aBaseline = offset + bStart; + } else { + auto bEnd = bStart + innerTable->BSize(aWM); + *aBaseline = BSize(aWM) - (bEnd - offset); + } + return true; + } + return false; + } + + virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override; + virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override; + + virtual mozilla::LogicalSize + ComputeAutoSize(nsRenderingContext* aRenderingContext, + mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, + nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorder, + const mozilla::LogicalSize& aPadding, + ComputeSizeFlags aFlags) override; + + /** process a reflow command for the table. + * This involves reflowing the caption and the inner table. + * @see nsIFrame::Reflow */ + virtual void Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + /** + * Get the "type" of the frame + * + * @see nsGkAtoms::tableWrapperFrame + */ + virtual nsIAtom* GetType() const override; + +#ifdef DEBUG_FRAME_DUMP + virtual nsresult GetFrameName(nsAString& aResult) const override; +#endif + + virtual nsStyleContext* GetParentStyleContext(nsIFrame** aProviderFrame) const override; + + /** + * Return the content for the cell at the given row and column. + */ + nsIContent* GetCellAt(uint32_t aRowIdx, uint32_t aColIdx) const; + + /** + * Return the number of rows in the table. + */ + int32_t GetRowCount() const + { + return InnerTableFrame()->GetRowCount(); + } + + /** + * Return the number of columns in the table. + */ + int32_t GetColCount() const + { + return InnerTableFrame()->GetColCount(); + } + + /** + * Return the index of the cell at the given row and column. + */ + int32_t GetIndexByRowAndColumn(int32_t aRowIdx, int32_t aColIdx) const + { + nsTableCellMap* cellMap = InnerTableFrame()->GetCellMap(); + if (!cellMap) + return -1; + + return cellMap->GetIndexByRowAndColumn(aRowIdx, aColIdx); + } + + /** + * Get the row and column indices for the cell at the given index. + */ + void GetRowAndColumnByIndex(int32_t aCellIdx, int32_t* aRowIdx, + int32_t* aColIdx) const + { + *aRowIdx = *aColIdx = 0; + nsTableCellMap* cellMap = InnerTableFrame()->GetCellMap(); + if (cellMap) { + cellMap->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx); + } + } + + /** + * return the frame for the cell at the given row and column. + */ + nsTableCellFrame* GetCellFrameAt(uint32_t aRowIdx, uint32_t aColIdx) const + { + nsTableCellMap* map = InnerTableFrame()->GetCellMap(); + if (!map) { + return nullptr; + } + + return map->GetCellInfoAt(aRowIdx, aColIdx); + } + + /** + * Return the col span of the cell at the given row and column indices. + */ + uint32_t GetEffectiveColSpanAt(uint32_t aRowIdx, uint32_t aColIdx) const + { + nsTableCellMap* map = InnerTableFrame()->GetCellMap(); + return map->GetEffectiveColSpan(aRowIdx, aColIdx); + } + + /** + * Return the effective row span of the cell at the given row and column. + */ + uint32_t GetEffectiveRowSpanAt(uint32_t aRowIdx, uint32_t aColIdx) const + { + nsTableCellMap* map = InnerTableFrame()->GetCellMap(); + return map->GetEffectiveRowSpan(aRowIdx, aColIdx); + } + + /** + * The CB size to use for the inner table frame if we're a grid item. + */ + NS_DECLARE_FRAME_PROPERTY_DELETABLE(GridItemCBSizeProperty, mozilla::LogicalSize); + +protected: + + explicit nsTableWrapperFrame(nsStyleContext* aContext); + virtual ~nsTableWrapperFrame(); + + void InitChildReflowInput(nsPresContext& aPresContext, + ReflowInput& aReflowInput); + + // Get a NS_STYLE_CAPTION_SIDE_* value, or NO_SIDE if no caption is present. + // (Remember that caption-side values are interpreted logically, despite + // having "physical" names.) + uint8_t GetCaptionSide(); + + bool HasSideCaption() { + uint8_t captionSide = GetCaptionSide(); + return captionSide == NS_STYLE_CAPTION_SIDE_LEFT || + captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; + } + + uint8_t GetCaptionVerticalAlign(); + + void SetDesiredSize(uint8_t aCaptionSide, + const mozilla::LogicalSize& aInnerSize, + const mozilla::LogicalSize& aCaptionSize, + const mozilla::LogicalMargin& aInnerMargin, + const mozilla::LogicalMargin& aCaptionMargin, + nscoord& aISize, + nscoord& aBSize, + mozilla::WritingMode aWM); + + nsresult GetCaptionOrigin(uint32_t aCaptionSide, + const mozilla::LogicalSize& aContainBlockSize, + const mozilla::LogicalSize& aInnerSize, + const mozilla::LogicalMargin& aInnerMargin, + const mozilla::LogicalSize& aCaptionSize, + mozilla::LogicalMargin& aCaptionMargin, + mozilla::LogicalPoint& aOrigin, + mozilla::WritingMode aWM); + + nsresult GetInnerOrigin(uint32_t aCaptionSide, + const mozilla::LogicalSize& aContainBlockSize, + const mozilla::LogicalSize& aCaptionSize, + const mozilla::LogicalMargin& aCaptionMargin, + const mozilla::LogicalSize& aInnerSize, + mozilla::LogicalMargin& aInnerMargin, + mozilla::LogicalPoint& aOrigin, + mozilla::WritingMode aWM); + + // reflow the child (caption or innertable frame) + void OuterBeginReflowChild(nsPresContext* aPresContext, + nsIFrame* aChildFrame, + const ReflowInput& aOuterRI, + mozilla::Maybe& aChildRI, + nscoord aAvailISize); + + void OuterDoReflowChild(nsPresContext* aPresContext, + nsIFrame* aChildFrame, + const ReflowInput& aChildRI, + ReflowOutput& aMetrics, + nsReflowStatus& aStatus); + + // Set the overflow areas in our reflow metrics + void UpdateOverflowAreas(ReflowOutput& aMet); + + // Get the margin. + void GetChildMargin(nsPresContext* aPresContext, + const ReflowInput& aOuterRI, + nsIFrame* aChildFrame, + nscoord aAvailableWidth, + mozilla::LogicalMargin& aMargin); + + virtual bool IsFrameOfType(uint32_t aFlags) const override + { + return nsContainerFrame::IsFrameOfType(aFlags & + (~eCanContainOverflowContainers)); + } + + nsTableFrame* InnerTableFrame() const + { + return static_cast(mFrames.FirstChild()); + } + + /** + * Helper for ComputeAutoSize. + * Compute the margin-box inline size of aChildFrame given the inputs. + * If aMarginResult is non-null, fill it with the part of the + * margin-isize that was contributed by the margin. + */ + nscoord ChildShrinkWrapISize(nsRenderingContext* aRenderingContext, + nsIFrame* aChildFrame, + mozilla::WritingMode aWM, + mozilla::LogicalSize aCBSize, + nscoord aAvailableISize, + nscoord* aMarginResult = nullptr) const; + +private: + nsFrameList mCaptionFrames; +}; + +#endif diff --git a/layout/tables/reftests/1031934-ref.html b/layout/tables/reftests/1031934-ref.html new file mode 100644 index 000000000..660e00a75 --- /dev/null +++ b/layout/tables/reftests/1031934-ref.html @@ -0,0 +1,27 @@ + + + + Testcase for bug 1031934 + + + + + + + + + + + +
    Hello
    + + + + + + + +
    Hello
    + + + diff --git a/layout/tables/reftests/1031934.html b/layout/tables/reftests/1031934.html new file mode 100644 index 000000000..9477e0e79 --- /dev/null +++ b/layout/tables/reftests/1031934.html @@ -0,0 +1,54 @@ + + + + Testcase for bug 1031934 + + + + + + + + + + + + + + + + + + + + + + + + +
    Hello
    + + + + + + + + + + + + + + + + + +
    Hello
    + + + diff --git a/layout/tables/reftests/1220621-1-ref.html b/layout/tables/reftests/1220621-1-ref.html new file mode 100644 index 000000000..bc9e9006c --- /dev/null +++ b/layout/tables/reftests/1220621-1-ref.html @@ -0,0 +1,17 @@ + + + + + + +
    OneTwoThree
    diff --git a/layout/tables/reftests/1220621-1a.html b/layout/tables/reftests/1220621-1a.html new file mode 100644 index 000000000..70026618a --- /dev/null +++ b/layout/tables/reftests/1220621-1a.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + +
    OneTwoThree
    + diff --git a/layout/tables/reftests/1220621-1b.html b/layout/tables/reftests/1220621-1b.html new file mode 100644 index 000000000..82ab75544 --- /dev/null +++ b/layout/tables/reftests/1220621-1b.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + + +
    OneTwoThree
    + diff --git a/layout/tables/reftests/1220621-1c.html b/layout/tables/reftests/1220621-1c.html new file mode 100644 index 000000000..3d0949abc --- /dev/null +++ b/layout/tables/reftests/1220621-1c.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + +
    OneTwoThree
    + diff --git a/layout/tables/reftests/1220621-1d.html b/layout/tables/reftests/1220621-1d.html new file mode 100644 index 000000000..cf6291d87 --- /dev/null +++ b/layout/tables/reftests/1220621-1d.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + +
    OneTwoThree
    + diff --git a/layout/tables/reftests/1220621-1e.html b/layout/tables/reftests/1220621-1e.html new file mode 100644 index 000000000..44e8b94e2 --- /dev/null +++ b/layout/tables/reftests/1220621-1e.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + +
    OneTwoThree
    + diff --git a/layout/tables/reftests/1220621-1f.html b/layout/tables/reftests/1220621-1f.html new file mode 100644 index 000000000..0b5f9a84e --- /dev/null +++ b/layout/tables/reftests/1220621-1f.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + +
    OneTwoThree
    + diff --git a/layout/tables/reftests/1220621-2-ref.html b/layout/tables/reftests/1220621-2-ref.html new file mode 100644 index 000000000..b6a02820b --- /dev/null +++ b/layout/tables/reftests/1220621-2-ref.html @@ -0,0 +1,21 @@ + + + + + + + + + + +
    One
    diff --git a/layout/tables/reftests/1220621-2a.html b/layout/tables/reftests/1220621-2a.html new file mode 100644 index 000000000..a66768e0f --- /dev/null +++ b/layout/tables/reftests/1220621-2a.html @@ -0,0 +1,29 @@ + + + + + + + + + + + +
    One
    + diff --git a/layout/tables/reftests/1220621-2b.html b/layout/tables/reftests/1220621-2b.html new file mode 100644 index 000000000..379857235 --- /dev/null +++ b/layout/tables/reftests/1220621-2b.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + +
    One
    + diff --git a/layout/tables/reftests/reftest-stylo.list b/layout/tables/reftests/reftest-stylo.list new file mode 100644 index 000000000..b1315a657 --- /dev/null +++ b/layout/tables/reftests/reftest-stylo.list @@ -0,0 +1,10 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +== 1031934.html 1031934.html +== 1220621-1a.html 1220621-1a.html +== 1220621-1b.html 1220621-1b.html +== 1220621-1c.html 1220621-1c.html +== 1220621-1d.html 1220621-1d.html +== 1220621-1e.html 1220621-1e.html +== 1220621-1f.html 1220621-1f.html +== 1220621-2a.html 1220621-2a.html +== 1220621-2b.html 1220621-2b.html diff --git a/layout/tables/reftests/reftest.list b/layout/tables/reftests/reftest.list new file mode 100644 index 000000000..f6f7d5bce --- /dev/null +++ b/layout/tables/reftests/reftest.list @@ -0,0 +1,9 @@ +== 1031934.html 1031934-ref.html +== 1220621-1a.html 1220621-1-ref.html +== 1220621-1b.html 1220621-1-ref.html +== 1220621-1c.html 1220621-1-ref.html +== 1220621-1d.html 1220621-1-ref.html +== 1220621-1e.html 1220621-1-ref.html +== 1220621-1f.html 1220621-1-ref.html +== 1220621-2a.html 1220621-2-ref.html +== 1220621-2b.html 1220621-2-ref.html diff --git a/layout/tables/test/mochitest.ini b/layout/tables/test/mochitest.ini new file mode 100644 index 000000000..8172770b4 --- /dev/null +++ b/layout/tables/test/mochitest.ini @@ -0,0 +1,4 @@ +[DEFAULT] + +[test_bug337124.html] +[test_bug541668_table_event_delivery.html] diff --git a/layout/tables/test/test_bug337124.html b/layout/tables/test/test_bug337124.html new file mode 100644 index 000000000..e4f85532d --- /dev/null +++ b/layout/tables/test/test_bug337124.html @@ -0,0 +1,32 @@ + +Test for Bug 337124 + + + + +Mozilla Bug 337124 + + + + +

    + +
    + +
    +
    + + +
    + 
    +
    + + \ No newline at end of file diff --git a/layout/tables/test/test_bug541668_table_event_delivery.html b/layout/tables/test/test_bug541668_table_event_delivery.html new file mode 100644 index 000000000..94835d469 --- /dev/null +++ b/layout/tables/test/test_bug541668_table_event_delivery.html @@ -0,0 +1,49 @@ + + + + + Test for Bug 541668 + + + + + +Mozilla Bug 541668 + + + + + + + + +
    +
    +
    Cell
    Cell
    +
    +
    +
    + + -- cgit v1.2.3