From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- layout/tables/BasicTableLayoutStrategy.cpp | 999 +++ layout/tables/BasicTableLayoutStrategy.h | 74 + layout/tables/FixedTableLayoutStrategy.cpp | 402 ++ layout/tables/FixedTableLayoutStrategy.h | 38 + layout/tables/SpanningCellSorter.cpp | 144 + layout/tables/SpanningCellSorter.h | 91 + layout/tables/TableArea.h | 48 + layout/tables/celldata.h | 400 ++ layout/tables/crashtests/1027611-1.html | 24 + layout/tables/crashtests/1031934.html | 18 + layout/tables/crashtests/110523-1.html | 45 + layout/tables/crashtests/1118168.html | 1260 ++++ layout/tables/crashtests/1144641.html | 65 + layout/tables/crashtests/1183896.html | 25 + layout/tables/crashtests/1223232.html | 6 + layout/tables/crashtests/1223282.html | 11 + layout/tables/crashtests/1232881-1.html | 18 + layout/tables/crashtests/1243623-1.html | 27 + layout/tables/crashtests/1335552-1.html | 13 + layout/tables/crashtests/1335552-2.html | 13 + layout/tables/crashtests/138725-1.html | 32 + layout/tables/crashtests/1555757-1.html | 16 + layout/tables/crashtests/1555757-2.html | 27 + layout/tables/crashtests/1555757-3.html | 22 + layout/tables/crashtests/1555757-4.html | 21 + layout/tables/crashtests/159359-1.html | 13 + layout/tables/crashtests/1607045.html | 28 + layout/tables/crashtests/1710116-1.html | 21 + layout/tables/crashtests/1767364-1.html | 16 + layout/tables/crashtests/1767364-2.html | 28 + layout/tables/crashtests/1767364-3.html | 28 + layout/tables/crashtests/1767364-4.html | 28 + layout/tables/crashtests/1795030.html | 23 + layout/tables/crashtests/1795051.html | 28 + layout/tables/crashtests/1821177.html | 43 + 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.xhtml | 8 + layout/tables/crashtests/323604-1.html | 10 + layout/tables/crashtests/323604-2.xhtml | 43 + layout/tables/crashtests/32447-1.html | 13 + layout/tables/crashtests/329891.xhtml | 25 + 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/481089.html | 4 + 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/750147.html | 2 + 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/862624.html | 19 + layout/tables/crashtests/897883-1.html | 8 + layout/tables/crashtests/980223.html | 22 + layout/tables/crashtests/crashtests.list | 182 + layout/tables/moz.build | 51 + layout/tables/nsCellMap.cpp | 2508 +++++++ layout/tables/nsCellMap.h | 575 ++ layout/tables/nsITableCellLayout.h | 31 + layout/tables/nsITableLayoutStrategy.h | 59 + layout/tables/nsTableCellFrame.cpp | 1175 ++++ layout/tables/nsTableCellFrame.h | 334 + layout/tables/nsTableColFrame.cpp | 188 + layout/tables/nsTableColFrame.h | 288 + layout/tables/nsTableColGroupFrame.cpp | 460 ++ layout/tables/nsTableColGroupFrame.h | 219 + layout/tables/nsTableFrame.cpp | 7276 ++++++++++++++++++++ layout/tables/nsTableFrame.h | 952 +++ layout/tables/nsTableRowFrame.cpp | 1344 ++++ layout/tables/nsTableRowFrame.h | 386 ++ layout/tables/nsTableRowGroupFrame.cpp | 1864 +++++ layout/tables/nsTableRowGroupFrame.h | 377 + layout/tables/nsTableWrapperFrame.cpp | 857 +++ layout/tables/nsTableWrapperFrame.h | 282 + layout/tables/reftests/1017137-ref.html | 27 + layout/tables/reftests/1017137.html | 27 + 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/1564308-ref.html | 22 + layout/tables/reftests/1564308.html | 40 + .../dynamic-text-indent-table-cell-ref.html | 10 + .../reftests/dynamic-text-indent-table-cell.html | 19 + .../dynamic-text-overflow-table-cell-notref.html | 8 + .../dynamic-text-overflow-table-cell-ref.html | 8 + .../reftests/dynamic-text-overflow-table-cell.html | 19 + layout/tables/reftests/reftest.list | 14 + layout/tables/test/mochitest.toml | 7 + layout/tables/test/test_bug1832110.html | 112 + layout/tables/test/test_bug337124.html | 32 + .../test/test_bug541668_table_event_delivery.html | 48 + 237 files changed, 30654 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/1118168.html create mode 100644 layout/tables/crashtests/1144641.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/1232881-1.html create mode 100644 layout/tables/crashtests/1243623-1.html create mode 100644 layout/tables/crashtests/1335552-1.html create mode 100644 layout/tables/crashtests/1335552-2.html create mode 100644 layout/tables/crashtests/138725-1.html create mode 100644 layout/tables/crashtests/1555757-1.html create mode 100644 layout/tables/crashtests/1555757-2.html create mode 100644 layout/tables/crashtests/1555757-3.html create mode 100644 layout/tables/crashtests/1555757-4.html create mode 100644 layout/tables/crashtests/159359-1.html create mode 100644 layout/tables/crashtests/1607045.html create mode 100644 layout/tables/crashtests/1710116-1.html create mode 100644 layout/tables/crashtests/1767364-1.html create mode 100644 layout/tables/crashtests/1767364-2.html create mode 100644 layout/tables/crashtests/1767364-3.html create mode 100644 layout/tables/crashtests/1767364-4.html create mode 100644 layout/tables/crashtests/1795030.html create mode 100644 layout/tables/crashtests/1795051.html create mode 100644 layout/tables/crashtests/1821177.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.xhtml 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/329891.xhtml 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/481089.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/750147.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/862624.html create mode 100644 layout/tables/crashtests/897883-1.html create mode 100644 layout/tables/crashtests/980223.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/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/1017137-ref.html create mode 100644 layout/tables/reftests/1017137.html 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/1564308-ref.html create mode 100644 layout/tables/reftests/1564308.html create mode 100644 layout/tables/reftests/dynamic-text-indent-table-cell-ref.html create mode 100644 layout/tables/reftests/dynamic-text-indent-table-cell.html create mode 100644 layout/tables/reftests/dynamic-text-overflow-table-cell-notref.html create mode 100644 layout/tables/reftests/dynamic-text-overflow-table-cell-ref.html create mode 100644 layout/tables/reftests/dynamic-text-overflow-table-cell.html create mode 100644 layout/tables/reftests/reftest.list create mode 100644 layout/tables/test/mochitest.toml create mode 100644 layout/tables/test/test_bug1832110.html 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 0000000000..5cb890c234 --- /dev/null +++ b/layout/tables/BasicTableLayoutStrategy.cpp @@ -0,0 +1,999 @@ +/* -*- 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/. */ + +/* + * 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; + +#undef DEBUG_TABLE_STRATEGY + +BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame* aTableFrame) + : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto), + mTableFrame(aTableFrame) { + MarkIntrinsicISizesDirty(); +} + +/* virtual */ +BasicTableLayoutStrategy::~BasicTableLayoutStrategy() = default; + +/* virtual */ +nscoord BasicTableLayoutStrategy::GetMinISize(gfxContext* aRenderingContext) { + DISPLAY_MIN_INLINE_SIZE(mTableFrame, mMinISize); + if (mMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) { + ComputeIntrinsicISizes(aRenderingContext); + } + return mMinISize; +} + +/* virtual */ +nscoord BasicTableLayoutStrategy::GetPrefISize(gfxContext* aRenderingContext, + bool aComputingSize) { + DISPLAY_PREF_INLINE_SIZE(mTableFrame, mPrefISize); + NS_ASSERTION((mPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN) == + (mPrefISizePctExpand == NS_INTRINSIC_ISIZE_UNKNOWN), + "dirtyness out of sync"); + if (mPrefISize == NS_INTRINSIC_ISIZE_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(gfxContext* 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::IntrinsicSizeOffsetData 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.padding + offsets.border; + } else { + // StyleBoxSizing::Border and standards-mode + minCoord += offsets.padding + offsets.border; + prefCoord += offsets.padding + offsets.border; + } + } else { + minCoord = 0; + prefCoord = 0; + } + float prefPercent = 0.0f; + bool hasSpecifiedISize = false; + + const auto& iSize = stylePos->ISize(aWM); + // NOTE: We're ignoring calc() units with both lengths and 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; + nscoord c = iSize.ToLength(); + // 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()->AsElement()->HasAttr(nsGkAtoms::nowrap)) { + minCoord = c; + } + prefCoord = std::max(c, minCoord); + } else if (iSize.ConvertsToPercentage()) { + prefPercent = iSize.ToPercentage(); + } else if (aIsCell) { + switch (iSize.tag) { + case StyleSize::Tag::MaxContent: + // 'inline-size' only affects pref isize, not min + // isize, so don't change anything + break; + case StyleSize::Tag::MinContent: + prefCoord = minCoord; + break; + case StyleSize::Tag::MozAvailable: + case StyleSize::Tag::FitContent: + case StyleSize::Tag::FitContentFunction: + // TODO: Bug 1708310: Make sure fit-content() work properly in table. + case StyleSize::Tag::Auto: + case StyleSize::Tag::LengthPercentage: + break; + } + } + + StyleMaxSize maxISize = stylePos->MaxISize(aWM); + if (nsIFrame::ToExtremumLength(maxISize)) { + if (!aIsCell || maxISize.IsMozAvailable()) { + maxISize = StyleMaxSize::None(); + } else if (maxISize.IsFitContent() || maxISize.IsFitContentFunction()) { + // TODO: Bug 1708310: Make sure fit-content() work properly in table. + // for 'max-inline-size', '-moz-fit-content' is like 'max-content' + maxISize = StyleMaxSize::MaxContent(); + } + } + // XXX To really implement 'max-inline-size' well, we'd need to store + // it separately on the columns. + const LogicalSize zeroSize(aWM); + if (maxISize.ConvertsToLength() || nsIFrame::ToExtremumLength(maxISize)) { + nscoord c = aFrame + ->ComputeISizeValue(aRenderingContext, aWM, zeroSize, + zeroSize, 0, maxISize) + .mISize; + minCoord = std::min(c, minCoord); + prefCoord = std::min(c, prefCoord); + } else if (maxISize.ConvertsToPercentage()) { + float p = maxISize.ToPercentage(); + if (p < prefPercent) { + prefPercent = p; + } + } + + StyleSize minISize = stylePos->MinISize(aWM); + if (nsIFrame::ToExtremumLength(maxISize)) { + if (!aIsCell || minISize.IsMozAvailable()) { + minISize = StyleSize::LengthPercentage(LengthPercentage::Zero()); + } else if (minISize.IsFitContent() || minISize.IsFitContentFunction()) { + // TODO: Bug 1708310: Make sure fit-content() work properly in table. + // for 'min-inline-size', '-moz-fit-content' is like 'min-content' + minISize = StyleSize::MinContent(); + } + } + + if (minISize.ConvertsToLength() || nsIFrame::ToExtremumLength(minISize)) { + nscoord c = aFrame + ->ComputeISizeValue(aRenderingContext, aWM, zeroSize, + zeroSize, 0, minISize) + .mISize; + minCoord = std::max(c, minCoord); + prefCoord = std::max(c, prefCoord); + } else if (minISize.ConvertsToPercentage()) { + float p = minISize.ToPercentage(); + if (p > prefPercent) { + prefPercent = p; + } + } + + // 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(gfxContext* aRenderingContext, + nsTableCellFrame* aCellFrame, + WritingMode aWM) { + return GetISizeInfo(aRenderingContext, aCellFrame, aWM, true); +} + +static inline CellISizeInfo GetColISizeInfo(gfxContext* 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( + gfxContext* 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()->IsTableColGroupFrame(), + "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( + gfxContext* 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_ISIZE_UNKNOWN; + mPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN; + mPrefISizePctExpand = NS_INTRINSIC_ISIZE_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_ISIZE_UNKNOWN) == + (mPrefISize == NS_INTRINSIC_ISIZE_UNKNOWN), + "dirtyness out of sync"); + NS_ASSERTION((mMinISize == NS_INTRINSIC_ISIZE_UNKNOWN) == + (mPrefISizePctExpand == NS_INTRINSIC_ISIZE_UNKNOWN), + "dirtyness out of sync"); + // XXX Is this needed? + if (mMinISize == NS_INTRINSIC_ISIZE_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 = NSCoordSaturatingAdd(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 = NSCoordSaturatingAdd(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 = NSCoordSaturatingAdd( + 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 = NSCoordSaturatingSubtract(basis.c, pref_minus_min, + nscoord_MAX); + col_iSize = NSCoordSaturatingAdd( + 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 = NSCoordSaturatingAdd( + 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 = + NSCoordSaturatingSubtract(basis.c, col_iSize, nscoord_MAX); + col_iSize = NSCoordSaturatingAdd( + 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 = + NSCoordSaturatingSubtract(basis.c, col_iSize, nscoord_MAX); + col_iSize = NSCoordSaturatingAdd( + 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 = NSCoordSaturatingAdd(col_iSize, NSToCoordRound(pct * c)); + basis.f -= pct; + } + break; + case FLEX_ALL_LARGE: { + float c = float(space) / float(basis.c); + col_iSize = NSCoordSaturatingAdd(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 0000000000..d0aaef8ada --- /dev/null +++ b/layout/tables/BasicTableLayoutStrategy.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// 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(gfxContext* aRenderingContext) override; + virtual nscoord GetPrefISize(gfxContext* 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(gfxContext* 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(gfxContext* 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 0000000000..8d74e3ba12 --- /dev/null +++ b/layout/tables/FixedTableLayoutStrategy.cpp @@ -0,0 +1,402 @@ +/* -*- 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 "nsLayoutUtils.h" +#include "nsStyleConsts.h" +#include "nsTableFrame.h" +#include "nsTableColFrame.h" +#include "nsTableCellFrame.h" +#include "WritingModes.h" +#include + +using namespace mozilla; + +FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame* aTableFrame) + : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed), + mTableFrame(aTableFrame) { + MarkIntrinsicISizesDirty(); +} + +/* virtual */ +FixedTableLayoutStrategy::~FixedTableLayoutStrategy() = default; + +/* virtual */ +nscoord FixedTableLayoutStrategy::GetMinISize(gfxContext* aRenderingContext) { + DISPLAY_MIN_INLINE_SIZE(mTableFrame, mMinISize); + if (mMinISize != NS_INTRINSIC_ISIZE_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-inline-size' and 'max-inline-size'? + // XXX Should we really ignore inline sizes 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 auto* styleISize = &colFrame->StylePosition()->ISize(wm); + if (styleISize->ConvertsToLength()) { + result += styleISize->ToLength(); + } else if (styleISize->ConvertsToPercentage()) { + // do nothing + } else { + // 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->IsMinContent() || + styleISize->IsMaxContent()) { + nscoord cellISize = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, cellFrame, IntrinsicISizeType::MinISize); + 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->ConvertsToPercentage()) { + 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 both lengths and percentages, do nothing + } + } + } + + return (mMinISize = result); +} + +/* virtual */ +nscoord FixedTableLayoutStrategy::GetPrefISize(gfxContext* 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_INLINE_SIZE(mTableFrame, result); + return result; +} + +/* virtual */ +void FixedTableLayoutStrategy::MarkIntrinsicISizesDirty() { + mMinISize = NS_INTRINSIC_ISIZE_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 auto* styleISize = &colFrame->StylePosition()->ISize(wm); + nscoord colISize; + if (styleISize->ConvertsToLength()) { + colISize = styleISize->ToLength(); + specTotal += colISize; + } else if (styleISize->ConvertsToPercentage()) { + float pct = styleISize->ToPercentage(); + colISize = NSToCoordFloor(pct * float(tableISize)); + colFrame->AddPrefPercent(pct); + pctTotal += pct; + } else { + // 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->IsMaxContent() || + styleISize->IsMinContent()) { + // XXX This should use real percentage padding + // Note that the difference between MinISize and PrefISize + // 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, + IntrinsicISizeType::MinISize); + } else if (styleISize->ConvertsToPercentage()) { + // XXX This should use real percentage padding + float pct = styleISize->ToPercentage(); + colISize = NSToCoordFloor(pct * float(tableISize)); + + if (cellStylePos->mBoxSizing == StyleBoxSizing::Content) { + nsIFrame::IntrinsicSizeOffsetData offsets = + cellFrame->IntrinsicISizeOffsets(); + colISize += offsets.padding + offsets.border; + } + + 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->ConvertsToPercentage()) { + 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 0000000000..f5fa45e5b3 --- /dev/null +++ b/layout/tables/FixedTableLayoutStrategy.h @@ -0,0 +1,38 @@ +/* -*- 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(gfxContext* aRenderingContext) override; + virtual nscoord GetPrefISize(gfxContext* 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 0000000000..cb0cfc3bfa --- /dev/null +++ b/layout/tables/SpanningCellSorter.cpp @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// 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 "nsTArray.h" +#include "mozilla/HashFunctions.h" + +using namespace mozilla; + +// #define DEBUG_SPANNING_CELL_SORTER + +SpanningCellSorter::SpanningCellSorter() + : mState(ADDING), mHashTable(&HashTableOps, sizeof(HashTableEntry)) { + memset(mArray, 0, sizeof(mArray)); +} + +SpanningCellSorter::~SpanningCellSorter() = default; + +/* static */ const PLDHashTableOps SpanningCellSorter::HashTableOps = { + HashTableHashKey, HashTableMatchEntry, PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, nullptr}; + +/* static */ +PLDHashNumber SpanningCellSorter::HashTableHashKey(const void* key) { + return HashGeneric(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; +} + +SpanningCellSorter::Item* SpanningCellSorter::GetNext(int32_t* aColSpan) { + NS_ASSERTION(mState != DONE, "done enumerating, stop calling"); + + // Our comparator needs the SpanningCellSorter private HashTableEntry + class HashTableEntryComparator { + public: + bool Equals(HashTableEntry* left, HashTableEntry* right) const { + return left->mColSpan == right->mColSpan; + } + bool LessThan(HashTableEntry* left, HashTableEntry* right) const { + return left->mColSpan < right->mColSpan; + } + }; + + switch (mState) { + case ADDING: + /* prepare to enumerate the array */ + mState = ENUMERATING_ARRAY; + mEnumerationIndex = 0; + [[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) { + // This clear is a no-op if the array is empty and it makes us + // resilient against re-entrance. + mSortedHashTable.ClearAndRetainStorage(); + mSortedHashTable.SetCapacity(mHashTable.EntryCount()); + for (auto iter = mHashTable.ConstIter(); !iter.Done(); iter.Next()) { + mSortedHashTable.AppendElement( + static_cast(iter.Get())); + } + mSortedHashTable.Sort(HashTableEntryComparator()); + } + [[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; + [[fallthrough]]; + case DONE:; + } + return nullptr; +} diff --git a/layout/tables/SpanningCellSorter.h b/layout/tables/SpanningCellSorter.h new file mode 100644 index 0000000000..4de360497d --- /dev/null +++ b/layout/tables/SpanningCellSorter.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// 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 "nsTArray.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 CompareHashTableEntry(HashTableEntry* a, HashTableEntry* b); + + /* state used only during enumeration */ + uint32_t mEnumerationIndex; // into mArray or mSortedHashTable + nsTArray 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) noexcept(true) { return nullptr; } +}; + +#endif diff --git a/layout/tables/TableArea.h b/layout/tables/TableArea.h new file mode 100644 index 0000000000..56d7af3c0f --- /dev/null +++ b/layout/tables/TableArea.h @@ -0,0 +1,48 @@ +/* -*- 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() : mStartCol(0), mStartRow(0), mColCount(0), mRowCount(0) {} + TableArea(int32_t aStartCol, int32_t aStartRow, int32_t aColCount, + int32_t aRowCount) + : mStartCol(aStartCol), + mStartRow(aStartRow), + mColCount(aColCount), + mRowCount(aRowCount) {} + + int32_t& StartCol() { return mStartCol; } + int32_t& StartRow() { return mStartRow; } + int32_t& ColCount() { return mColCount; } + int32_t& RowCount() { return mRowCount; } + + int32_t StartCol() const { return mStartCol; } + int32_t StartRow() const { return mStartRow; } + int32_t ColCount() const { return mColCount; } + int32_t RowCount() const { return mRowCount; } + int32_t EndCol() const { return mStartCol + mColCount; } + int32_t EndRow() const { return mStartRow + mRowCount; } + + void UnionArea(const TableArea& aArea1, const TableArea& aArea2) { + nsIntRect rect(aArea1.mStartCol, aArea1.mStartRow, aArea1.mColCount, + aArea1.mRowCount); + rect.UnionRect(rect, nsIntRect(aArea2.mStartCol, aArea2.mStartRow, + aArea2.mColCount, aArea2.mRowCount)); + rect.GetRect(&mStartCol, &mStartRow, &mColCount, &mRowCount); + } + + private: + int32_t mStartCol, mStartRow, mColCount, mRowCount; +}; + +} // namespace mozilla + +#endif // mozilla_TableArea_h_ diff --git a/layout/tables/celldata.h b/layout/tables/celldata.h new file mode 100644 index 0000000000..681f0e9f28 --- /dev/null +++ b/layout/tables/celldata.h @@ -0,0 +1,400 @@ +/* -*- 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 "nsITableCellLayout.h" // for MAX_COLSPAN / MAX_ROWSPAN +#include "nsCoord.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/WritingModes.h" +#include + +class nsTableCellFrame; +class nsCellMap; +class BCCellData; + +/** + * 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; + + /** + * Implemented in nsCellMap.cpp + * + * @param aOrigCell the table cell frame which will be stored in mOrigCell. + */ + explicit CellData(nsTableCellFrame* aOrigCell); + + ~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 +}; + +// BCPixelSize is in device pixels. +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 d2a, BCPixelSize px) { + return BC_BORDER_START_HALF(px) * d2a; +} +static inline nscoord BC_BORDER_END_HALF_COORD(int32_t d2a, BCPixelSize px) { + return BC_BORDER_END_HALF(px) * d2a; +} + +// 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); + + inline bool IsIStartStart() const { return (bool)mIStartStart; } + + inline void SetIStartStart(bool aValue) { mIStartStart = aValue; } + + inline bool IsBStartStart() const { return (bool)mBStartStart; } + + inline void SetBStartStart(bool aValue) { mBStartStart = 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; + SetBStartStart(true); + SetIStartStart(true); + mIStartSize = mCornerSubSize = mBStartSize = 0; + mCornerSide = mozilla::eLogicalSideBStart; + mCornerBevel = false; +} + +inline BCData::~BCData() = default; + +inline nscoord BCData::GetIStartEdge(BCBorderOwner& aOwner, + bool& aStart) const { + aOwner = (BCBorderOwner)mIStartOwner; + aStart = IsIStartStart(); + + return (nscoord)mIStartSize; +} + +inline void BCData::SetIStartEdge(BCBorderOwner aOwner, nscoord aSize, + bool aStart) { + mIStartOwner = aOwner; + mIStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize; + SetIStartStart(aStart); +} + +inline nscoord BCData::GetBStartEdge(BCBorderOwner& aOwner, + bool& aStart) const { + aOwner = (BCBorderOwner)mBStartOwner; + aStart = IsBStartStart(); + + return (nscoord)mBStartSize; +} + +inline void BCData::SetBStartEdge(BCBorderOwner aOwner, nscoord aSize, + bool aStart) { + mBStartOwner = aOwner; + mBStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize; + SetBStartStart(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; +} + +#endif diff --git a/layout/tables/crashtests/1027611-1.html b/layout/tables/crashtests/1027611-1.html new file mode 100644 index 0000000000..f21e665709 --- /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 0000000000..b718df845c --- /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 0000000000..1444949606 --- /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/1118168.html b/layout/tables/crashtests/1118168.html new file mode 100644 index 0000000000..bde87826ae --- /dev/null +++ b/layout/tables/crashtests/1118168.html @@ -0,0 +1,1260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ +Mme FOO (BAR) Baz + +

+
+ + + + + + + +
+Impression du lundi 05 janvier 2015 à 17h14
+ +
+SERVICE : +FOOO +— LIT : 1223 P +
+Civilité : Mme FOO (BAR) Baz +(89 ans) +(64.5 kg) (160 cm)
+Né(e) le : 12/06/2025 +
+NDA : 1248744 +
+Motif : foobar +
+Date d'entrée : 26/11/2014 à 15h56 +
+Date de sortie : 23/01/2015 à 10h00 +
+
+
+PRESCRIPTIONS +ADMINISTRATION +
+Date signature + +Libellé médicament
+Posologie
+Commentaires
+
+Commentaires + +D
+04/01 +
+L
+05/01 +
+M
+06/01 +
+M
+07/01 +
MMSNMMSNMMSNMMSN
Perfusions
+30/12/2014
+ +FOO Bar + +
+
Du 30/12/2014 à 18h00
+
+au 24/01/2015 à 18h00 +
+
+
+BAH 40 MG PDR INJ (=BIH) +
+ +40 mg +[mg] + +
+Voie intraveineuse +
+
+
+
+ +
+
+
+
+
+
+
+
+40 +
+
+
+
+
+
+
+
+
+
+
+
+40
+
+
+
+
+
+
+
+
+
+
+
+40
+
+
+
+
+
+
+
+
+
+
+
+40
+
+
+
+
+
+
+
+ +NACL 0,9% SOL INJ PR PERF 500 ML (=CHLORURE de SODIUM) +
+ +50 ml + +
+Voie intraveineuse +
+
+
+ +
+
+
+
+50 +
+
+
+
+
+
+
+
+
+
+
+
+50
+
+
+
+
+
+
+
+
+
+
+
+50
+
+
+
+
+
+
+
+
+
+
+
+50
+
+
+
+
+
+
+
+26/11/2014
+ +BUH Boh + +
+
Du 26/11/2014 à 18h48
+
+au 23/01/2015 à 10h48 +
+
+
+ONDANSETRON 4 MG/2 ML, SOL INJ, AMP (=ZOPHREN) +
+ +8 mg (soit 4 ml) + +
+Voie intraveineuse +
+ +
+
+
+ +

lorem ipsum

+
+
+
+
+
+
+
+
+
+
+
+0 +/ 8
+
+
+0 +/ 8
+
+
+
+
+
+
+
+
+8
+
+
+8
+
+
+
+
+
+
+
+
+8
+
+
+8
+
+
+
+
+
+
+
+
+8
+
+
+
+
Injections
+13/12/2014
+ +FOO Bar + +
+
Du 13/12/2014 à 12h00
+
+à Fin du séjour +
+
+
+ +FUROSEMIDE 20 MG/2 ML, SOL INJ, AMP (=LASILIX) +
+Voie intraveineuse +
+40 mg (soit 4 ml) tous les 12 Heure(s)
+
+
+ +
+
+
+ +

hahaha

+
+
+
+
+
+0 +/ 40 +
+
+
+
+
+
+0 +/ 40 +
+
+
+
+
+
+0 +/ 40 +
+
+
+
+
+
+a/ 40
+
+
+
+
+
+a/ 40
+
+
+
+
+
+a/ 40
+
+
+
+
+
+a/ 40
+
+
+
+
+
+a/ 40
+
+
+
+
+26/11/2014
+ +BUH Boh + +
+
Du 26/11/2014 à 18h50
+
+à Fin du séjour +
+
+
+ +FONDAPARINUX SODIUM 2.5 MG/0.5 ML, SOL INJ, SRG (=ARIXTRA) (A risque) +
+Voie sous-cutanée +
+2.5 mg (soit 0.5 ml) le soir
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+2.5 +
+
+
+
+
+
+
+
+
+
+
+
+2.5 +
+
+
+
+
+
+
+
+
+
+
+
+2.5 +
+
+
+
+
+
+
+
+
+
+
+
+2.5 +
+
+
+
+
Médicaments
+04/01/2015
+ +FOO Bar + +
+
Du 04/01/2015 à 10h00
+
+à Fin du séjour +
+
+
+ +MACROGOL(S) + POTASSIUM + SODIUM SS ARÔME, PDR PR SOL BUV, SACHET (=MOVICOL) +
+Voie orale +
+1 sachets le matin
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+1 +/ -
+
+
+
+
+
+
+
+
+1 +
+
+
+
+
+
+
+
+
+
+
+
+1 +
+
+
+
+
+
+
+
+
+
+
+
+1 +
+
+
+
+
+
+
+
+
+
+
+01/01/2015
+ +FOO Bar + +
+
Du 01/01/2015 à 11h14
+
+à Fin du séjour +
+
+
+ +PREDNISOLONE 5 MG, CPR ORODISPERSIBLE (=SOLUPRED) +
+Voie orale +
+3 mg (soit 0.6 Comprimé orodispersible) le matin
+
+
+
+
+
+ +
+
+
+
+
+3 +
+
+
+
+
+
+
+
+
+
+
+
+3 +
+
+
+
+
+
+
+
+
+
+
+
+3 +
+
+
+
+
+
+
+
+
+
+
+
+3 +
+
+
+
+
+
+
+
+
+
+
+01/01/2015
+ +FOO Bar + +
+
Du 01/01/2015 à 11h12
+
+à Fin du séjour +
+
+
+ +SEROPLEX 5 mg +
+Voie orale +
+10 mg (soit 2 Comprimé pelliculé) le matin
+
+
+ +
+
+
+ +
+
+
+
+
+10 +
+
+
+
+
+
+
+
+
+
+
+
+10 +
+
+
+
+
+
+
+
+
+
+
+
+10 +
+
+
+
+
+
+
+
+
+
+
+
+10 +
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/layout/tables/crashtests/1144641.html b/layout/tables/crashtests/1144641.html new file mode 100644 index 0000000000..0e0b089ea9 --- /dev/null +++ b/layout/tables/crashtests/1144641.html @@ -0,0 +1,65 @@ + + + + + + + + + + diff --git a/layout/tables/crashtests/1183896.html b/layout/tables/crashtests/1183896.html new file mode 100644 index 0000000000..3995ba09f3 --- /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 0000000000..6df062a0b1 --- /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 0000000000..132fdd74d4 --- /dev/null +++ b/layout/tables/crashtests/1223282.html @@ -0,0 +1,11 @@ + + + + + + + +
+ + + diff --git a/layout/tables/crashtests/1232881-1.html b/layout/tables/crashtests/1232881-1.html new file mode 100644 index 0000000000..6dc81f8534 --- /dev/null +++ b/layout/tables/crashtests/1232881-1.html @@ -0,0 +1,18 @@ + + + + +
+
+
+
+ + diff --git a/layout/tables/crashtests/1243623-1.html b/layout/tables/crashtests/1243623-1.html new file mode 100644 index 0000000000..9fd97b3688 --- /dev/null +++ b/layout/tables/crashtests/1243623-1.html @@ -0,0 +1,27 @@ + + + + +
+
+

grilling it up +

+ + + diff --git a/layout/tables/crashtests/1335552-1.html b/layout/tables/crashtests/1335552-1.html new file mode 100644 index 0000000000..95c59f01f9 --- /dev/null +++ b/layout/tables/crashtests/1335552-1.html @@ -0,0 +1,13 @@ + + + + + diff --git a/layout/tables/crashtests/1335552-2.html b/layout/tables/crashtests/1335552-2.html new file mode 100644 index 0000000000..dad49092de --- /dev/null +++ b/layout/tables/crashtests/1335552-2.html @@ -0,0 +1,13 @@ + + + + + diff --git a/layout/tables/crashtests/138725-1.html b/layout/tables/crashtests/138725-1.html new file mode 100644 index 0000000000..8fe5721cca --- /dev/null +++ b/layout/tables/crashtests/138725-1.html @@ -0,0 +1,32 @@ + + + + + + +
+ + + + + + + + + +
+ + + + +
+
+ +
+
+
+
+
+
+ + diff --git a/layout/tables/crashtests/1555757-1.html b/layout/tables/crashtests/1555757-1.html new file mode 100644 index 0000000000..04f2b27a60 --- /dev/null +++ b/layout/tables/crashtests/1555757-1.html @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/layout/tables/crashtests/1555757-2.html b/layout/tables/crashtests/1555757-2.html new file mode 100644 index 0000000000..eff048784a --- /dev/null +++ b/layout/tables/crashtests/1555757-2.html @@ -0,0 +1,27 @@ + + + + + + + + + Change my size + + + diff --git a/layout/tables/crashtests/1555757-3.html b/layout/tables/crashtests/1555757-3.html new file mode 100644 index 0000000000..41d0043e66 --- /dev/null +++ b/layout/tables/crashtests/1555757-3.html @@ -0,0 +1,22 @@ + + + + +
+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 0000000000..26ffb0223b --- /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 0000000000..9302d34031 --- /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 0000000000..e3e2b31c59 --- /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 0000000000..6d1b29f65b --- /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 0000000000..a2e92ae316 --- /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 0000000000..8ad3b51986 --- /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 0000000000..fdfca1658b --- /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 0000000000..1ccc0e1b88 --- /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 0000000000..6c4ae98905 --- /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.xhtml b/layout/tables/crashtests/322779-1.xhtml new file mode 100644 index 0000000000..8af18adcc9 --- /dev/null +++ b/layout/tables/crashtests/322779-1.xhtml @@ -0,0 +1,8 @@ + + + + + + + + \ 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 0000000000..6605b5b222 --- /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 0000000000..b51c917091 --- /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 0000000000..e33a3007b7 --- /dev/null +++ b/layout/tables/crashtests/32447-1.html @@ -0,0 +1,13 @@ + + + + + + + +
Test +
+ + + + diff --git a/layout/tables/crashtests/329891.xhtml b/layout/tables/crashtests/329891.xhtml new file mode 100644 index 0000000000..83fe18472c --- /dev/null +++ b/layout/tables/crashtests/329891.xhtml @@ -0,0 +1,25 @@ + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/layout/tables/crashtests/331344-1.html b/layout/tables/crashtests/331344-1.html new file mode 100644 index 0000000000..0a4b9048af --- /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 0000000000..82b47216cd --- /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 0000000000..708a5ea161 --- /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 0000000000..61cb6ad342 --- /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 0000000000..ddbf1ce854 --- /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 0000000000..2c0419b6de --- /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 0000000000..bca62eefaf --- /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 0000000000..fdde3f9ba7 --- /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 0000000000..66854e5543 --- /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 0000000000..407d47c42f --- /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 0000000000..fedf3b2d12 --- /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 0000000000..4119389a56 --- /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 0000000000..a6b49febbc --- /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 0000000000..f0f00ccd5c --- /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 0000000000..c71757eb99 --- /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 0000000000..4df84ebbeb --- /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 0000000000..7fc72c2763 --- /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 0000000000..a9e381a20a --- /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 0000000000..a7c7e10ca8 --- /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 0000000000..9bbcef7527 --- /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 0000000000..ef3e40b4bb --- /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 0000000000..1964032e7b --- /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 0000000000..636c2e2a62 --- /dev/null +++ b/layout/tables/crashtests/362275.html @@ -0,0 +1,14 @@ + +Testcase bug 362275 - Hang with testcase on print preview, using 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 0000000000..3b8417e31f --- /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 0000000000..16a5a7745d --- /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 0000000000..e7fb6493e3 --- /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 0000000000..ef65e93fcd --- /dev/null +++ b/layout/tables/crashtests/367749.html @@ -0,0 +1,14 @@ + +Testcase bug 362275 - Hang with testcase on print preview, using 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 0000000000..c59e5bf384 --- /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 0000000000..ea6760d4af --- /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 0000000000..7739b20d2c --- /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 0000000000..ffcad72abc --- /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 0000000000..1e4faad51f --- /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 0000000000..42279c8867 --- /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 0000000000..e01e92e60e --- /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 0000000000..8a3fa5cab6 --- /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 0000000000..bd9147876e --- /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 0000000000..46c24c42cb --- /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 0000000000..86a4308860 --- /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 0000000000..6de28cd59f --- /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 0000000000..1479017633 --- /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 0000000000..d332d722c0 --- /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 0000000000..d58c9ba4d5 --- /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 0000000000..bd65fd2bf1 --- /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 0000000000..d26cdfe07b --- /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 0000000000..0f0c92cd5b --- /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 0000000000..3030280809 --- /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 0000000000..e16cc22a69 --- /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 0000000000..d9cda291aa --- /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 0000000000..53753acab1 --- /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 0000000000..593681e08f --- /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 0000000000..b2beec86e7 --- /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 0000000000..f75336e15f --- /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 0000000000..bdb03876a4 --- /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 0000000000..fb13855df9 --- /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 0000000000..b1b259255c --- /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 0000000000..fddf702955 --- /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 0000000000..45f7638577 --- /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 0000000000..932fca8b59 --- /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 0000000000..5f956d2ee3 --- /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 0000000000..6e22332821 --- /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 0000000000..56b5733085 --- /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 0000000000..abdb0dd533 --- /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 0000000000..c9d2f33da6 --- /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 0000000000..1757e695af --- /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 0000000000..35c6d87717 --- /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 0000000000..d9f6732fbc --- /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 0000000000..81807cc96d --- /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 0000000000..d67b611a5e --- /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 0000000000..7cf09a75b1 --- /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 0000000000..6e0ac2c88d --- /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 0000000000..60bf5798de --- /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 0000000000..962c44d106 --- /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 0000000000..b6e3876ded --- /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 0000000000..de3a4af53b --- /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 0000000000..821d390b54 --- /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 0000000000..2b30ea9c19 --- /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 0000000000..6811813c4d --- /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 0000000000..283b3774ae --- /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 0000000000..bf5d14dca4 --- /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 0000000000..41ad1fdf8c --- /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 0000000000..fd818476ec --- /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 0000000000..67923dc6db --- /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 0000000000..23f3959507 --- /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 0000000000..5709eab524 --- /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 0000000000..0f98d15ef1 --- /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 0000000000..5a6d3df5ac --- /dev/null +++ b/layout/tables/crashtests/467141-1.html @@ -0,0 +1,8 @@ + + + + + +
+ + diff --git a/layout/tables/crashtests/481089.html b/layout/tables/crashtests/481089.html new file mode 100644 index 0000000000..ca3f25c1be --- /dev/null +++ b/layout/tables/crashtests/481089.html @@ -0,0 +1,4 @@ + + +
+ diff --git a/layout/tables/crashtests/488388-1.html b/layout/tables/crashtests/488388-1.html new file mode 100644 index 0000000000..e54deb9247 --- /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 0000000000..5c386c2ea2 --- /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 0000000000..ae38c4f7c8 --- /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 0000000000..12829799ee --- /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 0000000000..7ca35ddceb --- /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 0000000000..4bec0ae682 --- /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 0000000000..15a547753c --- /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 0000000000..b80109bf55 --- /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 0000000000..d0e3ce9361 --- /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 0000000000..a39d9e21c9 --- /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 0000000000..2e2caa89d1 --- /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 0000000000..6f8681a175 --- /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 0000000000..300e19f312 --- /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 0000000000..af427ad761 --- /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 0000000000..5a5b606ce9 --- /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 0000000000..32ba0dd541 --- /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 0000000000..6b7102bff6 --- /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 0000000000..e8bfdf6754 --- /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 0000000000..f592366729 --- /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 0000000000..873707eeaf --- /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 0000000000..c6e398491f --- /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 0000000000..d74cf5d632 --- /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 0000000000..2ce86b7474 --- /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 0000000000..13d64dc840 --- /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 0000000000..1496612539 --- /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 0000000000..db885f4949 --- /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 0000000000..4886f8388a --- /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 0000000000..554a069cee --- /dev/null +++ b/layout/tables/crashtests/711864-1.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + +
    + + diff --git a/layout/tables/crashtests/750147.html b/layout/tables/crashtests/750147.html new file mode 100644 index 0000000000..605016414d --- /dev/null +++ b/layout/tables/crashtests/750147.html @@ -0,0 +1,2 @@ + +
    div
    diff --git a/layout/tables/crashtests/759249-1.html b/layout/tables/crashtests/759249-1.html new file mode 100644 index 0000000000..e96b38b947 --- /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 0000000000..57c575eb02 --- /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 0000000000..11ea838b5a --- /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 0000000000..6dd903b804 --- /dev/null +++ b/layout/tables/crashtests/814713.html @@ -0,0 +1,96 @@ + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + diff --git a/layout/tables/crashtests/862624.html b/layout/tables/crashtests/862624.html new file mode 100644 index 0000000000..9a17adb108 --- /dev/null +++ b/layout/tables/crashtests/862624.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + +
    + + diff --git a/layout/tables/crashtests/897883-1.html b/layout/tables/crashtests/897883-1.html new file mode 100644 index 0000000000..b642ae7130 --- /dev/null +++ b/layout/tables/crashtests/897883-1.html @@ -0,0 +1,8 @@ + + + + + +
    +
    c170141183460469231731687303715884105748
    +
    diff --git a/layout/tables/crashtests/980223.html b/layout/tables/crashtests/980223.html new file mode 100644 index 0000000000..b29a9aea5c --- /dev/null +++ b/layout/tables/crashtests/980223.html @@ -0,0 +1,22 @@ + + + +#980223 testcase + + + + + + + + +
    Test table cell
    + + + + + diff --git a/layout/tables/crashtests/crashtests.list b/layout/tables/crashtests/crashtests.list new file mode 100644 index 0000000000..92bca235e3 --- /dev/null +++ b/layout/tables/crashtests/crashtests.list @@ -0,0 +1,182 @@ +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 chrome://reftest/content/crashtests/layout/tables/crashtests/322779-1.xhtml +load 323604-1.html +load 323604-2.xhtml +load 329891.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 +asserts(3) load 444702-1.html # nscoord overflow. +asserts(4) load 448988-1.xhtml # nscoord overflow. +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 481089.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 +skip-if(Android&&browserIsRemote) load 691824-1.xhtml # bug 1507207 +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 +pref(font.size.inflation.minTwips,120) load 750147.html +load 759249-1.html +load 759249-2.html +load 814713.html +load 862624.html +load 897883-1.html +load 980223.html +load 1027611-1.html +load 1031934.html +load 1118168.html +load 1144641.html +load 1183896.html +load 1223282.html +load 1223232.html +load 1232881-1.html +load 1243623-1.html +load 1335552-1.html +load 1335552-2.html +load 1555757-1.html +load 1555757-2.html +load 1555757-3.html +load 1555757-4.html +load 1607045.html +load 1710116-1.html +load 1767364-1.html +load 1767364-2.html +load 1767364-3.html +load 1767364-4.html +load 1795030.html +load 1795051.html +load 1821177.html diff --git a/layout/tables/moz.build b/layout/tables/moz.build new file mode 100644 index 0000000000..f6a1a1f344 --- /dev/null +++ b/layout/tables/moz.build @@ -0,0 +1,51 @@ +# -*- 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.toml"] + +EXPORTS += [ + "celldata.h", # included by nsCellMap.h + "nsCellMap.h", # included by nsTableWrapperFrame.h + "nsITableCellLayout.h", + "nsTableCellFrame.h", # included by dom/base/Selection.cpp + "nsTableFrame.h", # included by nsTableWrapperFrame.h + "nsTableRowFrame.h", # included by nsTableCellFrame.h + "nsTableRowGroupFrame.h", # included by nsTableRowFrame.h + "nsTableWrapperFrame.h", # included by dom/base/Selection.cpp + "TableArea.h", # included by nsCellMap.h +] + +UNIFIED_SOURCES += [ + "BasicTableLayoutStrategy.cpp", + "FixedTableLayoutStrategy.cpp", + "nsCellMap.cpp", + "nsTableCellFrame.cpp", + "nsTableColFrame.cpp", + "nsTableColGroupFrame.cpp", + "nsTableFrame.cpp", + "nsTableRowFrame.cpp", + "nsTableRowGroupFrame.cpp", + "nsTableWrapperFrame.cpp", + "SpanningCellSorter.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "../../intl/unicharutil/util", + "../base", + "../generic", + "../painting", + "../style", + "../xul", + "/dom/base", + "/dom/html", +] diff --git a/layout/tables/nsCellMap.cpp b/layout/tables/nsCellMap.cpp new file mode 100644 index 0000000000..a13a6a1c3a --- /dev/null +++ b/layout/tables/nsCellMap.cpp @@ -0,0 +1,2508 @@ +/* -*- 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 "nsCellMap.h" + +#include "mozilla/PresShell.h" +#include "mozilla/StaticPtr.h" +#include "nsTArray.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 StaticAutoPtr 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.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 { + MOZ_ASSERT(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. + // Bug 1442018: we also need this search for header/footer frames that are + // not marked as _repeatable_ because they have a next-in-flow, as they may + // nevertheless have been _repeated_ from an earlier fragment. + auto isTableHeaderFooterGroup = [](const nsTableRowGroupFrame* aRG) -> bool { + const auto display = aRG->StyleDisplay()->mDisplay; + return display == StyleDisplay::TableHeaderGroup || + display == StyleDisplay::TableFooterGroup; + }; + if (aRowGroup->IsRepeatable() || + (aRowGroup->GetNextInFlow() && isTableHeaderFooterGroup(aRowGroup))) { + auto findOtherRowGroupOfType = + [aRowGroup](nsTableFrame* aTable) -> nsTableRowGroupFrame* { + const auto display = aRowGroup->StyleDisplay()->mDisplay; + auto* table = aTable->FirstContinuation(); + for (; table; table = table->GetNextContinuation()) { + for (auto* child : table->PrincipalChildList()) { + if (child->StyleDisplay()->mDisplay == display && + child != aRowGroup) { + return static_cast(child); + } + } + } + return nullptr; + }; + if (auto* rgOrig = findOtherRowGroupOfType(&mTableFrame)) { + return GetMapFor(rgOrig, aStartHint); + } + MOZ_ASSERT_UNREACHABLE( + "A repeated header/footer should always have an " + "original header/footer it was repeated from"); + } + + return nullptr; +} + +void nsTableCellMap::Synchronize(nsTableFrame* aTableFrame) { + AutoTArray maps; + + nsTableFrame::RowGroupArray orderedRowGroups = + aTableFrame->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) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + maps.AppendElement(map); + } + } + 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(); + } + MOZ_ASSERT_UNREACHABLE("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(); + } + MOZ_ASSERT_UNREACHABLE("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) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mCols.AppendElements(aNumCols); + if (mBCInfo) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mBCInfo->mBEndBorders.AppendElements(aNumCols); + } +} + +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(); + MOZ_ASSERT(lastGoodColIndex >= -1); + for (int32_t colX = numCols - 1; 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) { + MOZ_ASSERT(!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; +} + +// FIXME: The only value callers pass for aSide is eLogicalSideBEnd. +// Consider removing support for the other three values. +void nsTableCellMap::ResetBStartStart(LogicalSide aSide, nsCellMap& aCellMap, + uint32_t aRowGroupStart, + uint32_t aRowIndex, uint32_t aColIndex) { + if (!mBCInfo) ABORT0(); + + BCCellData* cellData; + BCData* bcData = nullptr; + + switch (aSide) { + case eLogicalSideBEnd: + aRowIndex++; + [[fallthrough]]; + case eLogicalSideBStart: + cellData = (BCCellData*)aCellMap.GetDataAt(aRowIndex - aRowGroupStart, + 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++; + [[fallthrough]]; + case eLogicalSideIStart: + cellData = (BCCellData*)aCellMap.GetDataAt(aRowIndex - aRowGroupStart, + 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/iEnd 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++; + [[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++; + [[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(LogicalCorner 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 (eLogicalCornerBStartIEnd == aCorner) { + xPos++; + } else if (eLogicalCornerBEndIEnd == aCorner) { + xPos++; + rgYPos++; + yPos++; + } else if (eLogicalCornerBEndIStart == 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() { 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"); + + // XXX Change the return type of this function to void, or use a fallible + // operation. + mRows.InsertElementsAt(startRowIndex, aNumRows, numCols); + return true; +} + +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 = std::min(zeroRowSpan ? rowSpan - 1 : rowSpan, + GetRowCount() - 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++) { + nsTableRowFrame* rowFrame = aRows.ElementAt(rowX); + for (nsTableCellFrame* cellFrame = rowFrame->GetFirstCell(); cellFrame; + cellFrame = cellFrame->GetNextCell()) { + 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 (nsTableCellFrame* cellFrame = rFrame->GetFirstCell(); cellFrame; + cellFrame = cellFrame->GetNextCell()) { + 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; + } + row.InsertElementsAt(insertionIndex, endColIndex - insertionIndex + 1, + (CellData*)nullptr); + + 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 colIndex; + bool foundOne = false; + for (colIndex = 0; colIndex < maxColIndex; colIndex++) { + CellData* cellData = row[colIndex]; + if (cellData && (cellData->GetCellFrame() || cellData->IsRowSpan())) { + if (foundOne) { + return true; + } + foundOne = 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 + 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); + } + } + + uint32_t 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 = std::move(mRows); + + 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 (nsTableCellFrame* cellFrame = rFrame->GetFirstCell(); cellFrame; + cellFrame = cellFrame->GetNextCell()) { + 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 = std::move(mRows); + + 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 (!spansCauseRebuild) { + 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->DisplayInside()) { + case StyleDisplayInside::TableHeaderGroup: + printf(" thead "); + break; + case StyleDisplayInside::TableFooterGroup: + printf(" tfoot "); + break; + case StyleDisplayInside::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 + 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(); + uint32_t cellFrameColIndex = cellFrame->ColIndex(); + printf("C%d,%d=%p(%u) ", rIndex, colIndex, (void*)cellFrame, + cellFrameColIndex); + } + } + } + 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) { + uint32_t initialColIndex = cellFrame->ColIndex(); + *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->PresShell()->FreeByObjectID(eArenaObjectID_BCCellData, + bcData); + } else { + aData->~CellData(); + mPresContext->PresShell()->FreeByObjectID(eArenaObjectID_CellData, aData); + } +} + +CellData* nsCellMap::AllocCellData(nsTableCellFrame* aOrigCell) { + if (mIsBC) { + BCCellData* data = + (BCCellData*)mPresContext->PresShell()->AllocateByObjectID( + eArenaObjectID_BCCellData, sizeof(BCCellData)); + if (data) { + new (data) BCCellData(aOrigCell); + } + return data; + } + + CellData* data = (CellData*)mPresContext->PresShell()->AllocateByObjectID( + eArenaObjectID_CellData, 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) { + MOZ_ASSERT(aIncrement >= 0, "Bogus increment"); + MOZ_ASSERT(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 (true) { + 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; + } + + MOZ_ASSERT_UNREACHABLE("Can't get here"); + return nullptr; +} diff --git a/layout/tables/nsCellMap.h b/layout/tables/nsCellMap.h new file mode 100644 index 0000000000..9749d327e0 --- /dev/null +++ b/layout/tables/nsCellMap.h @@ -0,0 +1,575 @@ +/* -*- 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 "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); +}; + +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 aRowGroupStart, uint32_t aYPos, + uint32_t aXPos); + + 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(mozilla::LogicalCorner 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; +}; + +/** + * 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), + mCurMapContentRowCount(0), + mCurMapRelevantRowCount(0) { + MOZ_ASSERT(aMap, "Must have map"); + MOZ_ASSERT(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 0000000000..9413911689 --- /dev/null +++ b/layout/tables/nsITableCellLayout.h @@ -0,0 +1,31 @@ +/* -*- 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" + +#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. + +/** + * nsITableCellLayout + * interface for layout objects that act like table cells. + * XXXbz This interface should really go away... + * + * @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; +}; + +#endif diff --git a/layout/tables/nsITableLayoutStrategy.h b/layout/tables/nsITableLayoutStrategy.h new file mode 100644 index 0000000000..e7e4d0e280 --- /dev/null +++ b/layout/tables/nsITableLayoutStrategy.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// 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 gfxContext; +namespace mozilla { +struct ReflowInput; +} // namespace mozilla + +class nsITableLayoutStrategy { + public: + using ReflowInput = mozilla::ReflowInput; + + virtual ~nsITableLayoutStrategy() = default; + + /** Implement nsIFrame::GetMinISize for the table */ + virtual nscoord GetMinISize(gfxContext* aRenderingContext) = 0; + + /** Implement nsIFrame::GetPrefISize for the table */ + virtual nscoord GetPrefISize(gfxContext* 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 0000000000..70556d5e28 --- /dev/null +++ b/layout/tables/nsTableCellFrame.cpp @@ -0,0 +1,1175 @@ +/* -*- 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 "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "nsTableFrame.h" +#include "nsTableColFrame.h" +#include "nsTableRowFrame.h" +#include "nsTableRowGroupFrame.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsCSSRendering.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsIScrollableFrame.h" +#include "nsGenericHTMLElement.h" +#include "nsAttrValueInlines.h" +#include "nsHTMLParts.h" +#include "nsGkAtoms.h" +#include "nsDisplayList.h" +#include "nsLayoutUtils.h" +#include "nsTextFrame.h" +#include + +// TABLECELL SELECTION +#include "nsFrameSelection.h" +#include "mozilla/LookAndFeel.h" + +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +nsTableCellFrame::nsTableCellFrame(ComputedStyle* aStyle, + nsTableFrame* aTableFrame, ClassID aID) + : nsContainerFrame(aStyle, aTableFrame->PresContext(), aID), + mDesiredSize(aTableFrame->GetWritingMode()) { + mColIndex = 0; + mPriorAvailISize = 0; + + SetContentEmpty(false); +} + +nsTableCellFrame::~nsTableCellFrame() = default; + +NS_IMPL_FRAMEARENA_HELPERS(nsTableCellFrame) + +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; + uint32_t colIndex = cellFrame->ColIndex(); + SetColIndex(colIndex); + } else { + // Although the spec doesn't say that writing-mode is not applied to + // table-cells, we still override style value 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. + mWritingMode = GetTableFrame()->GetWritingMode(); + } +} + +void nsTableCellFrame::Destroy(DestroyContext& aContext) { + nsTableFrame::MaybeUnregisterPositionedTablePart(this); + nsContainerFrame::Destroy(aContext); +} + +// 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 input; 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. + LayoutFrameType fType = aReflowInput.mFrame->Type(); + if (fType == LayoutFrameType::Table) { + 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 == LayoutFrameType::TableWrapper); +} + +nsresult nsTableCellFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* 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) { + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_IS_DIRTY); + } + + const nsAtom* colSpanAttribute = + MOZ_UNLIKELY(mContent->AsElement()->IsMathMLElement()) + ? nsGkAtoms::columnspan_ + : nsGkAtoms::colspan; + if (aAttribute == nsGkAtoms::rowspan || aAttribute == colSpanAttribute) { + nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0}, + nsChangeHint_UpdateTableCellSpans); + } + return NS_OK; +} + +/* virtual */ +void nsTableCellFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); + nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle); + + if (!aOldComputedStyle) { + return; // avoid the following on init + } + +#ifdef ACCESSIBILITY + if (nsAccessibilityService* accService = GetAccService()) { + if (StyleBorder()->GetComputedBorder() != + aOldComputedStyle->StyleBorder()->GetComputedBorder()) { + // If a table cell's computed border changes, it can change whether or + // not its parent table is classified as a layout or data table. We + // send a notification here to invalidate the a11y cache on the table + // so the next fetch of IsProbablyLayoutTable() is accurate. + accService->TableLayoutGuessMaybeChanged(PresShell(), mContent); + } + } +#endif + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) { + uint32_t colIndex = ColIndex(); + uint32_t rowIndex = 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(static_cast(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, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) { + MOZ_CRASH("unsupported operation"); +} + +void nsTableCellFrame::RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) { + 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; + displaySelection = DetermineDisplaySelection(); + if (displaySelection) { + RefPtr frameSelection = PresShell()->FrameSelection(); + + if (frameSelection->IsInTableSelectionMode()) { + nscolor bordercolor; + if (displaySelection == nsISelectionController::SELECTION_DISABLED) { + bordercolor = NS_RGB(176, 176, 176); // disabled color + } else { + bordercolor = LookAndFeel::Color(LookAndFeel::ColorID::Highlight, this); + } + nscoord threePx = nsPresContext::CSSPixelsToAppUnits(3); + if ((mRect.width > threePx) && (mRect.height > threePx)) { + // compare bordercolor to background-color + bordercolor = EnsureDifferentColors( + bordercolor, StyleBackground()->BackgroundColor(this)); + + 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); + } + } + } +} + +void nsTableCellFrame::ProcessBorders(nsTableFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + const nsStyleBorder* borderStyle = StyleBorder(); + if (aFrame->IsBorderCollapse() || !borderStyle->HasBorder()) { + return; + } + + if (!GetContentEmpty() || + StyleTableBorder()->mEmptyCells == StyleEmptyCells::Show) { + aLists.BorderBackground()->AppendNewToTop(aBuilder, this); + } +} + +void nsTableCellFrame::InvalidateFrame(uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); + if (GetTableFrame()->IsBorderCollapse()) { + const bool rebuild = StaticPrefs::layout_display_list_retain_sc(); + GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(), + aDisplayItemKey, rebuild); + } +} + +void nsTableCellFrame::InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, + aRebuildDisplayItems); + // 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, + aRebuildDisplayItems); +} + +bool nsTableCellFrame::ShouldPaintBordersAndBackgrounds() const { + // If we're not visible, we don't paint. + if (!StyleVisibility()->IsVisible()) { + return false; + } + + // Consider 'empty-cells', but only in separated borders mode. + if (!GetContentEmpty()) { + return true; + } + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse()) { + return true; + } + + return StyleTableBorder()->mEmptyCells == StyleEmptyCells::Show; +} + +bool nsTableCellFrame::ShouldPaintBackground(nsDisplayListBuilder* aBuilder) { + return ShouldPaintBordersAndBackgrounds(); +} + +LogicalSides nsTableCellFrame::GetLogicalSkipSides() const { + LogicalSides skip(mWritingMode); + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return skip; + } + + if (GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (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 */ + const LogicalMargin border = GetLogicalUsedBorder(GetWritingMode()) + .ApplySkipSides(GetLogicalSkipSides()) + .ConvertTo(aWM, GetWritingMode()); + + nscoord bStartInset = border.BStart(aWM); + nscoord bEndInset = border.BEnd(aWM); + + 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 (GetVerticalAlign()) { + case StyleVerticalAlignKeyword::Baseline: + if (!GetContentEmpty()) { + // 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; + } + // Empty cells don't participate in baseline alignment - + // fallback to start alignment. + [[fallthrough]]; + case StyleVerticalAlignKeyword::Top: + // Align the top of the child frame with the top of the content area, + kidBStart = bStartInset; + break; + + case StyleVerticalAlignKeyword::Bottom: + // Align the bottom of the child frame with the bottom of the content + // area, + kidBStart = bSize - childBSize - bEndInset; + break; + + default: + case StyleVerticalAlignKeyword::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.InkOverflow(), + ReflowChildFlags::Default); + } +} + +bool nsTableCellFrame::ComputeCustomOverflow(OverflowAreas& 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'. +StyleVerticalAlignKeyword nsTableCellFrame::GetVerticalAlign() const { + const StyleVerticalAlign& verticalAlign = StyleDisplay()->mVerticalAlign; + if (verticalAlign.IsKeyword()) { + auto value = verticalAlign.AsKeyword(); + if (value == StyleVerticalAlignKeyword::Top || + value == StyleVerticalAlignKeyword::Middle || + value == StyleVerticalAlignKeyword::Bottom) { + return value; + } + } + return StyleVerticalAlignKeyword::Baseline; +} + +static bool CellHasVisibleContent(nsTableFrame* aTableFrame, + nsTableCellFrame* aCell) { + // see http://www.w3.org/TR/CSS21/tables.html#empty-cells + nsIFrame* content = aCell->CellContentFrame(); + if (content->GetContentRect().Height() > 0) { + return true; + } + if (aTableFrame->IsBorderCollapse()) { + return true; + } + for (nsIFrame* innerFrame : content->PrincipalChildList()) { + LayoutFrameType frameType = innerFrame->Type(); + if (LayoutFrameType::Text == frameType) { + nsTextFrame* textFrame = static_cast(innerFrame); + if (textFrame->HasNoncollapsedCharacters()) { + return true; + } + } else if (LayoutFrameType::Placeholder != frameType) { + return true; + } else if (nsLayoutUtils::GetFloatFromPlaceholder(innerFrame)) { + return true; + } + } + return false; +} + +nsIFrame* nsTableCellFrame::CellContentFrame() const { + nsIFrame* inner = mFrames.FirstChild(); + if (nsIScrollableFrame* sf = do_QueryFrame(inner)) { + return sf->GetScrolledFrame(); + } + return inner; +} + +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(); + const auto wm = GetWritingMode(); + nscoord result; + if (!StyleDisplay()->IsContainLayout() && + nsLayoutUtils::GetFirstLineBaseline(wm, inner, &result)) { + // `result` already includes the padding-start from the inner frame. + return result + GetLogicalUsedBorder(wm).BStart(wm); + } + return CellContentFrame()->ContentBSize(wm) + + GetLogicalUsedBorderAndPadding(wm).BStart(wm); +} + +int32_t nsTableCellFrame::GetRowSpan() { + int32_t rowSpan = 1; + + // Don't look at the content's rowspan if we're a pseudo cell + if (!Style()->IsPseudoOrAnonBox()) { + dom::Element* elem = mContent->AsElement(); + const nsAttrValue* attr = elem->GetParsedAttr(nsGkAtoms::rowspan); + // Note that we don't need to check the tag name, because only table cells + // (including MathML ) 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; + + // Don't look at the content's colspan if we're a pseudo cell + if (!Style()->IsPseudoOrAnonBox()) { + dom::Element* elem = mContent->AsElement(); + const nsAttrValue* attr = elem->GetParsedAttr( + MOZ_UNLIKELY(elem->IsMathMLElement()) ? nsGkAtoms::columnspan_ + : nsGkAtoms::colspan); + // Note that we don't need to check the tag name, because only table cells + // (including MathML ) and table headers parse the "colspan" attribute + // into an integer. + if (attr && attr->Type() == nsAttrValue::eInteger) { + colSpan = attr->GetIntegerValue(); + } + } + return colSpan; +} + +nsIScrollableFrame* nsTableCellFrame::GetScrollTargetFrame() const { + return do_QueryFrame(mFrames.FirstChild()); +} + +/* virtual */ +nscoord nsTableCellFrame::GetMinISize(gfxContext* aRenderingContext) { + nscoord result = 0; + DISPLAY_MIN_INLINE_SIZE(this, result); + + nsIFrame* inner = mFrames.FirstChild(); + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner, + IntrinsicISizeType::MinISize, + nsLayoutUtils::IGNORE_PADDING); + return result; +} + +/* virtual */ +nscoord nsTableCellFrame::GetPrefISize(gfxContext* aRenderingContext) { + nscoord result = 0; + DISPLAY_PREF_INLINE_SIZE(this, result); + + nsIFrame* inner = mFrames.FirstChild(); + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner, + IntrinsicISizeType::PrefISize, + nsLayoutUtils::IGNORE_PADDING); + return result; +} + +/* virtual */ nsIFrame::IntrinsicSizeOffsetData +nsTableCellFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) { + IntrinsicSizeOffsetData result = + nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis); + + result.margin = 0; + + WritingMode wm = GetWritingMode(); + result.border = 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()); + + uint32_t rowIndex = firstCellInFlow->RowIndex(); + int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(*firstCellInFlow); + + nscoord computedBSize = + firstTableInFlow->GetRowSpacing(rowIndex, rowIndex + rowSpan - 1); + computedBSize -= aBlockDirBorderPadding; + uint32_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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + 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); + + WritingMode wm = aReflowInput.GetWritingMode(); + LogicalSize availSize = aReflowInput.AvailableSize(); + + // @note |this| frame applies borders but not any padding. Our anonymous + // inner frame applies the padding (but not borders). + LogicalMargin border = GetBorderWidth(wm); + + ReflowOutput kidSize(wm); + 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 || aPresContext->IsPaginated()) { + // Here, we're changing our own reflow input, so we need to account for our + // padding, even though we don't apply it anywhere else, to get the correct + // percentage resolution on children. + const LogicalMargin bp = border + aReflowInput.ComputedLogicalPadding(wm); + if (aReflowInput.mFlags.mSpecialBSizeReflow) { + const_cast(aReflowInput) + .SetComputedBSize(BSize(wm) - bp.BStartEnd(wm)); + DISPLAY_REFLOW_CHANGE(); + } else { + const nscoord computedUnpaginatedBSize = + CalcUnpaginatedBSize(*this, *tableFrame, bp.BStartEnd(wm)); + if (computedUnpaginatedBSize > 0) { + const_cast(aReflowInput) + .SetComputedBSize(computedUnpaginatedBSize); + DISPLAY_REFLOW_CHANGE(); + } + } + } + + // We need to apply the skip sides for current fragmentainer's border after + // we finish calculating the special block-size or unpaginated block-size to + // prevent the skip sides from affecting the results. + // + // We assume we are the last fragment by using + // PreReflowBlockLevelLogicalSkipSides(), i.e. the block-end border and + // padding is not skipped. + border.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides()); + + availSize.ISize(wm) -= border.IStartEnd(wm); + + // If we have a constrained available block-size, shrink it by subtracting our + // block-direction border and padding for our children. + if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) { + availSize.BSize(wm) -= border.BStart(wm); + + if (aReflowInput.mStyleBorder->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone) { + // We have box-decoration-break:clone. Subtract block-end border from the + // available block-size as well. + availSize.BSize(wm) -= border.BEnd(wm); + } + } + + // Available block-size can became negative after subtracting block-direction + // border and padding. Per spec, to guarantee progress, fragmentainers are + // assumed to have a minimum block size of 1px regardless of their used size. + // https://drafts.csswg.org/css-break/#breaking-rules + availSize.BSize(wm) = + std::max(availSize.BSize(wm), nsPresContext::CSSPixelsToAppUnits(1)); + + WritingMode kidWM = firstKid->GetWritingMode(); + ReflowInput kidReflowInput(aPresContext, aReflowInput, firstKid, + availSize.ConvertTo(kidWM, wm), Nothing(), + ReflowInput::InitFlag::CallerWillInit); + // Override computed padding, in case it's percentage padding + { + const auto padding = aReflowInput.ComputedLogicalPadding(kidWM); + kidReflowInput.Init(aPresContext, Nothing(), Nothing(), Some(padding)); + if (firstKid->IsScrollFrame()) { + // Propagate explicit block sizes to our inner frame, if it's a scroll + // frame. + // Table cells don't respect box-sizing, so we need to remove the + // padding, so that the scroll-frame sizes properly (since the + // scrollbars also add to the padding area). + auto ToScrolledBSize = [&](const nscoord aBSize) { + return std::max(0, aBSize - padding.BStartEnd(kidWM)); + }; + if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) { + kidReflowInput.SetComputedBSize( + ToScrolledBSize(aReflowInput.ComputedBSize())); + } + if (aReflowInput.ComputedMinBSize() > 0) { + kidReflowInput.SetComputedMinBSize( + ToScrolledBSize(aReflowInput.ComputedMinBSize())); + } + if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { + kidReflowInput.SetComputedMaxBSize( + ToScrolledBSize(aReflowInput.ComputedMaxBSize())); + } + } + } + + // 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 input 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(); + + const LogicalPoint kidOrigin = border.StartOffset(wm); + const nsRect origRect = firstKid->GetRect(); + const nsRect origInkOverflow = firstKid->InkOverflowRect(); + const bool firstReflow = firstKid->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + ReflowChild(firstKid, aPresContext, kidSize, kidReflowInput, wm, kidOrigin, + containerSize, ReflowChildFlags::Default, aStatus); + if (aStatus.IsOverflowIncomplete()) { + // 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) + aStatus.SetIncomplete(); + NS_WARNING(nsPrintfCString("Set table cell incomplete %p", this).get()); + } + + // XXXbz is this invalidate actually needed, really? + if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { + InvalidateFrameSubtree(); + } + +#ifdef DEBUG + DebugCheckChildSize(firstKid, kidSize); +#endif + + // Place the child + FinishReflowChild(firstKid, aPresContext, kidSize, &kidReflowInput, wm, + kidOrigin, containerSize, ReflowChildFlags::Default); + + { + nsIFrame* prevInFlow = GetPrevInFlow(); + const bool isEmpty = + prevInFlow + ? static_cast(prevInFlow)->GetContentEmpty() + : !CellHasVisibleContent(tableFrame, this); + SetContentEmpty(isEmpty); + } + + if (tableFrame->IsBorderCollapse()) { + nsTableFrame::InvalidateTableFrame(firstKid, origRect, origInkOverflow, + 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) += border.BStart(wm); + + if (aStatus.IsComplete() || + aReflowInput.mStyleBorder->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone) { + cellSize.BSize(wm) += border.BEnd(wm); + } + } + + // next determine the cell's isize. At this point, we've factored in the + // cell's style attributes. + cellSize.ISize(wm) = kidSize.ISize(wm); + + // factor in border (and disregard padding, which is handled by our child). + if (NS_UNCONSTRAINEDSIZE != cellSize.ISize(wm)) { + cellSize.ISize(wm) += border.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 && + 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 + // nsIFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); +} + +/* ----- 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) { + aRowIndex = RowIndex(); + aColIndex = mColIndex; + return NS_OK; +} + +nsTableCellFrame* NS_NewTableCellFrame(PresShell* aPresShell, + ComputedStyle* aStyle, + nsTableFrame* aTableFrame) { + if (aTableFrame->IsBorderCollapse()) + return new (aPresShell) nsBCTableCellFrame(aStyle, aTableFrame); + else + return new (aPresShell) nsTableCellFrame(aStyle, aTableFrame); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsBCTableCellFrame) + +LogicalMargin nsTableCellFrame::GetBorderWidth(WritingMode aWM) const { + return LogicalMargin(aWM, StyleBorder()->GetComputedBorder()); +} + +void nsTableCellFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray& aResult) { + nsIFrame* kid = mFrames.FirstChild(); + MOZ_ASSERT(kid && !kid->GetNextSibling(), + "Table cells should have just one child"); + aResult.AppendElement(OwnedAnonBox(kid)); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableCellFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"TableCell"_ns, aResult); +} +#endif + +// nsBCTableCellFrame + +nsBCTableCellFrame::nsBCTableCellFrame(ComputedStyle* aStyle, + nsTableFrame* aTableFrame) + : nsTableCellFrame(aStyle, aTableFrame, kClassID) { + mBStartBorder = mIEndBorder = mBEndBorder = mIStartBorder = 0; +} + +nsBCTableCellFrame::~nsBCTableCellFrame() = default; + +/* virtual */ +nsMargin nsBCTableCellFrame::GetUsedBorder() const { + WritingMode wm = GetWritingMode(); + return GetBorderWidth(wm).GetPhysicalMargin(wm); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsBCTableCellFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"BCTableCell"_ns, aResult); +} +#endif + +LogicalMargin nsBCTableCellFrame::GetBorderWidth(WritingMode aWM) const { + int32_t d2a = PresContext()->AppUnitsPerDevPixel(); + return LogicalMargin(aWM, BC_BORDER_END_HALF_COORD(d2a, mBStartBorder), + BC_BORDER_START_HALF_COORD(d2a, mIEndBorder), + BC_BORDER_START_HALF_COORD(d2a, mBEndBorder), + BC_BORDER_END_HALF_COORD(d2a, 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 d2a = PresContext()->AppUnitsPerDevPixel(); + LogicalMargin halfBorder(wm, BC_BORDER_START_HALF_COORD(d2a, mBStartBorder), + BC_BORDER_END_HALF_COORD(d2a, mIEndBorder), + BC_BORDER_END_HALF_COORD(d2a, mBEndBorder), + BC_BORDER_START_HALF_COORD(d2a, mIStartBorder)); + return halfBorder.GetPhysicalMargin(wm); +} + +namespace mozilla { + +class nsDisplayTableCellSelection final : public nsPaintedDisplayItem { + public: + nsDisplayTableCellSelection(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableCellSelection); + } + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTableCellSelection) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + static_cast(mFrame)->DecorateForSelection( + aCtx->GetDrawTarget(), ToReferenceFrame()); + } + NS_DISPLAY_DECL_NAME("TableCellSelection", TYPE_TABLE_CELL_SELECTION) + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override { + RefPtr frameSelection = + mFrame->PresShell()->FrameSelection(); + return !frameSelection->IsInTableSelectionMode(); + } +}; + +} // namespace mozilla + +void nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame"); + if (ShouldPaintBordersAndBackgrounds()) { + // display outset box-shadows if we need to. + bool hasBoxShadow = !StyleEffects()->mBoxShadow.IsEmpty(); + if (hasBoxShadow) { + aLists.BorderBackground()->AppendNewToTop( + aBuilder, this); + } + + nsRect bgRect = GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this); + nsRect bgRectInsideBorder = bgRect; + + // If we're doing collapsed borders, and this element forms a new stacking + // context or has position:relative (which paints as though it did), inset + // the background rect so that we don't overpaint the inset part of our + // borders. + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + (IsStackingContext() || + StyleDisplay()->mPosition == StylePositionProperty::Relative)) { + bgRectInsideBorder.Deflate(GetUsedBorder()); + } + + // display background if we need to. + const AppendedBackgroundType result = + nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + aBuilder, this, bgRectInsideBorder, aLists.BorderBackground(), true, + bgRect); + if (result == AppendedBackgroundType::None) { + aBuilder->BuildCompositorHitTestInfoIfNeeded(this, + aLists.BorderBackground()); + } + + // display inset box-shadows if we need to. + if (hasBoxShadow) { + aLists.BorderBackground()->AppendNewToTop( + 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( + aBuilder, this); + } + + // This can be null if display list building initiated in the middle + // of the table, which can happen with background-clip:text and + // -moz-element. + nsDisplayTableBackgroundSet* backgrounds = + aBuilder->GetTableBackgroundSet(); + if (backgrounds) { + // Compute bgRect relative to reference frame, but using the + // normal (without position:relative offsets) positions for the + // cell, row and row group. + bgRect = GetRectRelativeToSelf() + GetNormalPosition(); + + nsTableRowFrame* row = GetTableRowFrame(); + bgRect += row->GetNormalPosition(); + + nsTableRowGroupFrame* rowGroup = row->GetTableRowGroupFrame(); + bgRect += rowGroup->GetNormalPosition(); + + bgRect += backgrounds->TableToReferenceFrame(); + + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter( + aBuilder); + if (IsStackingContext() || row->IsStackingContext() || + rowGroup->IsStackingContext() || tableFrame->IsStackingContext()) { + // The col/colgroup items we create below will be inserted directly into + // the BorderBackgrounds list of the table frame. That means that + // they'll be moved *outside* of any wrapper items created for any + // frames between this table cell frame and the table wrapper frame, and + // will not participate in those frames's opacity / transform / filter / + // mask effects. If one of those frames is a stacking context, then we + // may have one or more of those wrapper items, and one of them may have + // captured a clip. In order to ensure correct clipping and scrolling of + // the col/colgroup items, restore the clip and ASR that we observed + // when we entered the table frame. If that frame is a stacking context + // but doesn't have any clip capturing wrapper items, then we'll + // double-apply the clip. That's ok. + clipState.SetClipChainForContainingBlockDescendants( + backgrounds->GetTableClipChain()); + asrSetter.SetCurrentActiveScrolledRoot(backgrounds->GetTableASR()); + } + + // Create backgrounds items as needed for the column and column + // group that this cell occupies. + nsTableColFrame* col = backgrounds->GetColForIndex(ColIndex()); + nsTableColGroupFrame* colGroup = col->GetTableColGroupFrame(); + + Maybe buildingForColGroup; + nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + aBuilder, colGroup, bgRect, backgrounds->ColGroupBackgrounds(), false, + colGroup->GetRect() + backgrounds->TableToReferenceFrame(), this, + &buildingForColGroup); + + Maybe buildingForCol; + nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + aBuilder, col, bgRect, backgrounds->ColBackgrounds(), false, + col->GetRect() + colGroup->GetPosition() + + backgrounds->TableToReferenceFrame(), + this, &buildingForCol); + } + } + + // the 'empty-cells' property has no effect on 'outline' + DisplayOutline(aBuilder, aLists); + + 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, aLists); +} diff --git a/layout/tables/nsTableCellFrame.h b/layout/tables/nsTableCellFrame.h new file mode 100644 index 0000000000..f9b6f3ccac --- /dev/null +++ b/layout/tables/nsTableCellFrame.h @@ -0,0 +1,334 @@ +/* -*- 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 "nsITableCellLayout.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "mozilla/ComputedStyle.h" +#include "nsIPercentBSizeObserver.h" +#include "nsTArray.h" +#include "nsTableRowFrame.h" +#include "mozilla/WritingModes.h" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +/** + * 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::ImgDrawResult ImgDrawResult; + + friend nsTableCellFrame* NS_NewTableCellFrame(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle, + nsTableFrame* aTableFrame); + + nsTableCellFrame(ComputedStyle* aStyle, nsTableFrame* aTableFrame) + : nsTableCellFrame(aStyle, aTableFrame, kClassID) {} + + protected: + typedef mozilla::WritingMode WritingMode; + typedef mozilla::LogicalSide LogicalSide; + typedef mozilla::LogicalMargin LogicalMargin; + + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(nsTableCellFrame) + + nsIScrollableFrame* GetScrollTargetFrame() const final; + + nsTableRowFrame* GetTableRowFrame() const { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->IsTableRowFrame()); + return static_cast(parent); + } + + nsTableFrame* GetTableFrame() const { + return GetTableRowFrame()->GetTableFrame(); + } + + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + void Destroy(DestroyContext&) override; + +#ifdef ACCESSIBILITY + mozilla::a11y::AccType AccessibleType() override; +#endif + + nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, + int32_t aModType) override; + + /** @see nsIFrame::DidSetComputedStyle */ + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + +#ifdef DEBUG + // Our anonymous block frame is the content insertion frame so these + // methods should never be called: + void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override; + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override; +#endif + + nsContainerFrame* GetContentInsertionFrame() override { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + + nsIFrame* CellContentFrame() const; + + nsMargin GetUsedMargin() const override; + + void NotifyPercentBSize(const ReflowInput& aReflowInput) override; + + bool NeedsToObserve(const ReflowInput& aReflowInput) override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + virtual void ProcessBorders(nsTableFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists); + + nscoord GetMinISize(gfxContext* aRenderingContext) override; + nscoord GetPrefISize(gfxContext* aRenderingContext) override; + IntrinsicSizeOffsetData IntrinsicISizeOffsets( + nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE) override; + + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override; +#endif + + 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 + * StyleVerticalAlignKeyword::{Top,Middle,Bottom,Baseline}. + */ + virtual mozilla::StyleVerticalAlignKeyword GetVerticalAlign() const; + + bool HasVerticalAlignBaseline() const { + return GetVerticalAlign() == mozilla::StyleVerticalAlignKeyword::Baseline && + !GetContentEmpty(); + } + + /** + * 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 >= 0. + * to get the effective row span (the actual value that applies), use + * GetEffectiveRowSpan() + * @see nsTableFrame::GetEffectiveRowSpan() + */ + int32_t GetRowSpan(); + + // there is no set row index because row index depends on the cell's parent + // row only + + // Return our cell content frame. + void AppendDirectlyOwnedAnonBoxes(nsTArray& aResult) override; + + /*---------------- 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) */ + uint32_t RowIndex() const { + return static_cast(GetParent())->GetRowIndex(); + } + + /** + * 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() + */ + int32_t GetColSpan(); + + /** return the cell's column index (starting at 0 for the first column) */ + uint32_t ColIndex() const { + // NOTE: We copy this from previous continuations, and we don't ever have + // dynamic updates when tables split, so our mColIndex always matches our + // first continuation's. + MOZ_ASSERT(static_cast(FirstContinuation())->mColIndex == + mColIndex, + "mColIndex out of sync with first continuation"); + return mColIndex; + } + + 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() const; + void SetContentEmpty(bool aContentEmpty); + + nsTableCellFrame* GetNextCell() const { + nsIFrame* sibling = GetNextSibling(); + MOZ_ASSERT( + !sibling || static_cast(do_QueryFrame(sibling)), + "How do we have a non-cell sibling?"); + return static_cast(sibling); + } + + virtual LogicalMargin GetBorderWidth(WritingMode aWM) const; + + void DecorateForSelection(DrawTarget* aDrawTarget, nsPoint aPt); + + bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) override; + + void InvalidateFrame(uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + + bool ShouldPaintBordersAndBackgrounds() const; + + bool ShouldPaintBackground(nsDisplayListBuilder* aBuilder); + + protected: + nsTableCellFrame(ComputedStyle* aStyle, nsTableFrame* aTableFrame, + ClassID aID); + ~nsTableCellFrame(); + + LogicalSides GetLogicalSkipSides() 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) { + mDesiredSize = aDesiredSize.Size(GetWritingMode()); +} + +inline bool nsTableCellFrame::GetContentEmpty() const { + 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); + } +} + +// nsBCTableCellFrame +class nsBCTableCellFrame final : public nsTableCellFrame { + typedef mozilla::image::ImgDrawResult ImgDrawResult; + + public: + NS_DECL_FRAMEARENA_HELPERS(nsBCTableCellFrame) + + nsBCTableCellFrame(ComputedStyle* aStyle, nsTableFrame* aTableFrame); + + ~nsBCTableCellFrame(); + + nsMargin GetUsedBorder() const override; + + // Get the *inner half of the border only*, in twips. + 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); + + nsMargin GetBorderOverflow() override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override; +#endif + + private: + // These are the entire width of the border (the cell edge contains only + // the inner half). + BCPixelSize mBStartBorder; + BCPixelSize mIEndBorder; + BCPixelSize mBEndBorder; + BCPixelSize mIStartBorder; +}; + +// Implemented here because that's a sane-ish way to make the includes work out. +inline nsTableCellFrame* nsTableRowFrame::GetFirstCell() const { + nsIFrame* firstChild = mFrames.FirstChild(); + MOZ_ASSERT( + !firstChild || static_cast(do_QueryFrame(firstChild)), + "How do we have a non-cell child?"); + return static_cast(firstChild); +} + +#endif diff --git a/layout/tables/nsTableColFrame.cpp b/layout/tables/nsTableColFrame.cpp new file mode 100644 index 0000000000..16eab74cab --- /dev/null +++ b/layout/tables/nsTableColFrame.cpp @@ -0,0 +1,188 @@ +/* -*- 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 "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsGkAtoms.h" +#include "nsCSSRendering.h" +#include "nsIContent.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.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(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsSplittableFrame(aStyle, aPresContext, kClassID), + mMinCoord(0), + mPrefCoord(0), + mSpanMinCoord(0), + mSpanPrefCoord(0), + mPrefPercent(0.0f), + mSpanPrefPercent(0.0f), + mFinalISize(0), + mColIndex(0), + mIStartBorderWidth(0), + mIEndBorderWidth(0), + mHasSpecifiedCoord(false) { + SetColType(eColContent); + ResetIntrinsics(); + ResetSpanIntrinsics(); + ResetFinalISize(); +} + +nsTableColFrame::~nsTableColFrame() = default; + +nsTableColType nsTableColFrame::GetColType() const { + return (nsTableColType)((GetStateBits() & 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::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + nsSplittableFrame::DidSetComputedStyle(aOldComputedStyle); + + if (!aOldComputedStyle) // avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) { + TableArea damageArea(GetColIndex(), 0, 1, tableFrame->GetRowCount()); + tableFrame->AddBCDamageArea(damageArea); + } +} + +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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + aDesiredSize.ClearSize(); + const nsStyleVisibility* colVis = StyleVisibility(); + bool collapseCol = StyleVisibility::Collapse == colVis->mVisible; + if (collapseCol) { + GetTableFrame()->SetNeedToCollapse(true); + } +} + +void nsTableColFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + // Per https://drafts.csswg.org/css-tables-3/#global-style-overrides: + // "All css properties of table-column and table-column-group boxes are + // ignored, except when explicitly specified by this specification." + // CSS outlines and box-shadows fall into this category, so we skip them + // on these boxes. + MOZ_ASSERT_UNREACHABLE("Cols don't paint themselves"); +} + +int32_t nsTableColFrame::GetSpan() { return StyleTable()->mXSpan; } + +#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(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) nsTableColFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableColFrame) + +nsTableColFrame* nsTableColFrame::GetNextCol() const { + nsIFrame* childFrame = GetNextSibling(); + while (childFrame) { + if (childFrame->IsTableColFrame()) { + return (nsTableColFrame*)childFrame; + } + childFrame = childFrame->GetNextSibling(); + } + return nullptr; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableColFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"TableCol"_ns, aResult); +} +#endif + +void nsTableColFrame::InvalidateFrame(uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); + if (GetTableFrame()->IsBorderCollapse()) { + const bool rebuild = StaticPrefs::layout_display_list_retain_sc(); + GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(), + aDisplayItemKey, rebuild); + } +} + +void nsTableColFrame::InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, + aRebuildDisplayItems); + + // 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, + aRebuildDisplayItems); +} diff --git a/layout/tables/nsTableColFrame.h b/layout/tables/nsTableColFrame.h new file mode 100644 index 0000000000..638347aeeb --- /dev/null +++ b/layout/tables/nsTableColFrame.h @@ -0,0 +1,288 @@ +/* -*- 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" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +class nsTableColFrame final : public nsSplittableFrame { + public: + NS_DECL_FRAMEARENA_HELPERS(nsTableColFrame) + + 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(mozilla::PresShell* aPresShell, + ComputedStyle* aContext); + + // nsIFrame overrides + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override { + nsSplittableFrame::Init(aContent, aParent, aPrevInFlow); + if (!aPrevInFlow) { + mWritingMode = GetTableFrame()->GetWritingMode(); + } + } + + /** @see nsIFrame::DidSetComputedStyle */ + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override; +#endif + + nsTableColGroupFrame* GetTableColGroupFrame() const { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->IsTableColGroupFrame()); + return static_cast(parent); + } + + nsTableFrame* GetTableFrame() const { + return GetTableColGroupFrame()->GetTableFrame(); + } + + int32_t GetColIndex() const; + + void SetColIndex(int32_t aColIndex); + + nsTableColFrame* GetNextCol() const; + + /** return the number of the columns the col represents. always >= 1 */ + int32_t GetSpan(); + + /** convenience method, calls into cellmap */ + int32_t Count() const; + + BCPixelSize GetIStartBorderWidth() const { return mIStartBorderWidth; } + BCPixelSize GetIEndBorderWidth() const { return mIEndBorderWidth; } + void SetIStartBorderWidth(BCPixelSize aWidth) { mIStartBorderWidth = aWidth; } + void SetIEndBorderWidth(BCPixelSize aWidth) { mIEndBorderWidth = aWidth; } + +#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; } + + void InvalidateFrame(uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + + protected: + explicit nsTableColFrame(ComputedStyle* aStyle, nsPresContext* aPresContext); + ~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; + + bool mHasSpecifiedCoord; +}; + +inline int32_t nsTableColFrame::GetColIndex() const { return mColIndex; } + +inline void nsTableColFrame::SetColIndex(int32_t aColIndex) { + mColIndex = aColIndex; +} + +#endif diff --git a/layout/tables/nsTableColGroupFrame.cpp b/layout/tables/nsTableColGroupFrame.cpp new file mode 100644 index 0000000000..54fe53a5c4 --- /dev/null +++ b/layout/tables/nsTableColGroupFrame.cpp @@ -0,0 +1,460 @@ +/* -*- 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 "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" +#include "nsTableColFrame.h" +#include "nsTableFrame.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsHTMLParts.h" +#include "nsGkAtoms.h" +#include "nsCOMPtr.h" +#include "nsCSSRendering.h" + +using namespace mozilla; + +#define COLGROUP_SYNTHETIC_BIT NS_FRAME_STATE_BIT(30) + +bool nsTableColGroupFrame::IsSynthetic() const { + return HasAnyStateBits(COLGROUP_SYNTHETIC_BIT); +} + +void nsTableColGroupFrame::SetIsSynthetic() { + AddStateBits(COLGROUP_SYNTHETIC_BIT); +} + +void nsTableColGroupFrame::ResetColIndices(nsIFrame* aFirstColGroup, + int32_t aFirstColIndex, + nsIFrame* aStartColFrame) { + nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)aFirstColGroup; + int32_t colIndex = aFirstColIndex; + while (colGroupFrame) { + if (colGroupFrame->IsTableColGroupFrame()) { + // 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 (colFrame->IsTableColFrame()) { + ((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; + + // XXX: We cannot use range-based for loop because InsertCol() can destroy the + // nsTableColFrame in the slice we're traversing! Need to check the validity + // of *colIter. + auto colIter = aCols.begin(); + for (auto colIterEnd = aCols.end(); *colIter && colIter != colIterEnd; + ++colIter) { + auto* colFrame = static_cast(*colIter); + colFrame->SetColIndex(colIndex); + mColCount++; + tableFrame->InsertCol(*colFrame, colIndex); + colIndex++; + } + + for (; *colIter; ++colIter) { + auto* colFrame = static_cast(*colIter); + colFrame->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) { + const nsFrameList& colGroups = aTableFrame->GetColGroups(); + + auto lastColGroup = static_cast(colGroups.LastChild()); + if (!lastColGroup) { + return nullptr; + } + + if (!lastColGroup->IsSynthetic()) { + return lastColGroup; + } + + return static_cast(lastColGroup->GetPrevSibling()); +} + +// 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 == FrameChildListID::Principal, "unexpected child list"); +#ifdef DEBUG + for (nsIFrame* f : aChildList) { + MOZ_ASSERT(f->GetParent() == this, "Unexpected parent"); + } +#endif + if (aChildList.IsEmpty()) { + GetTableFrame()->AppendAnonymousColFrames(this, GetSpan(), + eColAnonymousColGroup, false); + return; + } + + mFrames.AppendFrames(this, std::move(aChildList)); +} + +/* virtual */ +void nsTableColGroupFrame::DidSetComputedStyle( + ComputedStyle* aOldComputedStyle) { + nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); + + if (!aOldComputedStyle) // avoid this on init + return; + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) { + 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 == FrameChildListID::Principal, "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(); + DestroyContext context(PresShell()); + RemoveFrame(context, FrameChildListID::Principal, col); + col = nextCol; + } + + // Our next colframe should be an eColContent. We've removed all the + // eColAnonymousColGroup colframes, eColAnonymousCol colframes always follow + // eColContent ones, and eColAnonymousCell colframes only appear in a + // synthetic colgroup, which never gets AppendFrames() called on it. + MOZ_ASSERT(!col || col->GetColType() == eColContent, + "What's going on with our columns?"); + + const nsFrameList::Slice& newFrames = + mFrames.AppendFrames(this, std::move(aFrameList)); + InsertColsReflow(GetStartColumnIndex() + mColCount, newFrames); +} + +void nsTableColGroupFrame::InsertFrames( + ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "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; + } + DestroyContext context(PresShell()); + RemoveFrame(context, FrameChildListID::Principal, col); + col = nextCol; + } + + // Our next colframe should be an eColContent. We've removed all the + // eColAnonymousColGroup colframes, eColAnonymousCol colframes always follow + // eColContent ones, and eColAnonymousCell colframes only appear in a + // synthetic colgroup, which never gets InsertFrames() called on it. + MOZ_ASSERT(!col || col->GetColType() == eColContent, + "What's going on with our columns?"); + + 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, std::move(aFrameList)); + nsIFrame* prevFrame = nsTableFrame::GetFrameAtOrBefore( + this, aPrevFrame, LayoutFrameType::TableCol); + + 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); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void nsTableColGroupFrame::RemoveChild(DestroyContext& aContext, + nsTableColFrame& aChild, + bool aResetSubsequentColIndices) { + int32_t colIndex = 0; + nsIFrame* nextChild = nullptr; + if (aResetSubsequentColIndices) { + colIndex = aChild.GetColIndex(); + nextChild = aChild.GetNextSibling(); + } + mFrames.DestroyFrame(aContext, &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); + } + } + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); +} + +void nsTableColGroupFrame::RemoveFrame(DestroyContext& aContext, + ChildListID aListID, + nsIFrame* aOldFrame) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + + if (!aOldFrame) { + return; + } + bool contentRemoval = false; + + if (aOldFrame->IsTableColFrame()) { + 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) { + nextCol = col->GetNextCol(); + RemoveFrame(aContext, FrameChildListID::Principal, col); + col = nextCol; + } + } + + int32_t colIndex = colFrame->GetColIndex(); + // The RemoveChild call handles calling FrameNeedsReflow on us. + RemoveChild(aContext, *colFrame, true); + + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->RemoveCol(this, colIndex, true, true); + if (mFrames.IsEmpty() && contentRemoval && !IsSynthetic()) { + tableFrame->AppendAnonymousColFrames(this, GetSpan(), + eColAnonymousColGroup, true); + } + } else { + mFrames.DestroyFrame(aContext, aOldFrame); + } +} + +nsIFrame::LogicalSides nsTableColGroupFrame::GetLogicalSkipSides() const { + LogicalSides skip(mWritingMode); + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return skip; + } + + if (GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + NS_ASSERTION(nullptr != mContent, "bad state -- null content for frame"); + + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible; + if (collapseGroup) { + GetTableFrame()->SetNeedToCollapse(true); + } + + const WritingMode wm = GetWritingMode(); + for (nsIFrame* kidFrame : mFrames) { + // 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())); + const LogicalPoint dummyPos(wm); + const nsSize dummyContainerSize; + nsReflowStatus status; + ReflowChild(kidFrame, aPresContext, kidSize, kidReflowInput, wm, dummyPos, + dummyContainerSize, ReflowChildFlags::Default, status); + FinishReflowChild(kidFrame, aPresContext, kidSize, &kidReflowInput, wm, + dummyPos, dummyContainerSize, ReflowChildFlags::Default); + } + + aDesiredSize.ClearSize(); +} + +void nsTableColGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + // Per https://drafts.csswg.org/css-tables-3/#global-style-overrides: + // "All css properties of table-column and table-column-group boxes are + // ignored, except when explicitly specified by this specification." + // CSS outlines and box-shadows fall into this category, so we skip them + // on these boxes. + MOZ_ASSERT_UNREACHABLE("Colgroups don't paint themselves"); +} + +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()->mXSpan; } + +/* ----- global methods ----- */ + +nsTableColGroupFrame* NS_NewTableColGroupFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsTableColGroupFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableColGroupFrame) + +void nsTableColGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); + if (GetTableFrame()->IsBorderCollapse()) { + const bool rebuild = StaticPrefs::layout_display_list_retain_sc(); + GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(), + aDisplayItemKey, rebuild); + } +} + +void nsTableColGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, + aRebuildDisplayItems); + // 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, + aRebuildDisplayItems); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableColGroupFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"TableColGroup"_ns, 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 " + "isSynthetic=%s", + indent, indent, GetStartColumnIndex(), GetColCount(), GetSpan(), + IsSynthetic() ? "true" : "false"); + + // verify the colindices + DebugOnly 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 0000000000..08a07b0acc --- /dev/null +++ b/layout/tables/nsTableColGroupFrame.h @@ -0,0 +1,219 @@ +/* -*- 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; + +namespace mozilla { +class PresShell; +} // namespace mozilla + +/** + * nsTableColGroupFrame + * data structure to maintain information about a single table cell's frame + */ +class nsTableColGroupFrame final : public nsContainerFrame { + public: + NS_DECL_FRAMEARENA_HELPERS(nsTableColGroupFrame) + + /** + * instantiate a new instance of nsTableRowFrame. + * + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableColGroupFrame* NS_NewTableColGroupFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + // nsIFrame overrides + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override { + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + if (!aPrevInFlow) { + mWritingMode = GetTableFrame()->GetWritingMode(); + } + } + + nsTableFrame* GetTableFrame() const { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->IsTableFrame()); + MOZ_ASSERT(!parent->GetPrevInFlow(), + "Col group should always be in a first-in-flow table frame"); + return static_cast(parent); + } + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + 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). + * + * In practice, we don't need to differentiate between cases (1) and (2), + * because they both correspond to table-column-group boxes in the spec and + * hence have observably identical behavior. Case three is flagged as a + * synthetic colgroup, because it may need to have different behavior in some + * cases. + */ + bool IsSynthetic() const; + void SetIsSynthetic(); + + /** 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::DidSetComputedStyle */ + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + void SetInitialChildList(ChildListID aListID, + nsFrameList&& aChildList) override; + void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override; + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) 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(DestroyContext& aContext, 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. + */ + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + /** 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 + 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. + */ + 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); + + void InvalidateFrame(uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + + protected: + nsTableColGroupFrame(ComputedStyle* aStyle, nsPresContext* aPresContext); + + void InsertColsReflow(int32_t aColIndex, const nsFrameList::Slice& aCols); + + LogicalSides GetLogicalSkipSides() const override; + + // data members + int32_t mColCount; + // the starting column index this col group represents. Must be >= 0. + int32_t mStartColIndex; +}; + +inline nsTableColGroupFrame::nsTableColGroupFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsContainerFrame(aStyle, aPresContext, kClassID), + mColCount(0), + mStartColIndex(0) {} + +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 0000000000..9a359192c0 --- /dev/null +++ b/layout/tables/nsTableFrame.cpp @@ -0,0 +1,7276 @@ +/* -*- 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 "nsTableFrame.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/WritingModes.h" + +#include "gfxContext.h" +#include "nsCOMPtr.h" +#include "mozilla/ComputedStyle.h" +#include "nsIFrameInlines.h" +#include "nsFrameList.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 "BasicTableLayoutStrategy.h" +#include "FixedTableLayoutStrategy.h" + +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsCSSRendering.h" +#include "nsGkAtoms.h" +#include "nsCSSAnonBoxes.h" +#include "nsIScriptError.h" +#include "nsFrameManager.h" +#include "nsError.h" +#include "nsCSSFrameConstructor.h" +#include "mozilla/Range.h" +#include "mozilla/RestyleManager.h" +#include "mozilla/ServoStyleSet.h" +#include "nsDisplayList.h" +#include "nsIScrollableFrame.h" +#include "nsCSSProps.h" +#include "nsLayoutUtils.h" +#include "nsStyleChangeList.h" +#include + +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/RenderRootStateManager.h" + +using namespace mozilla; +using namespace mozilla::image; +using namespace mozilla::layout; + +using mozilla::gfx::AutoRestoreTransform; +using mozilla::gfx::DrawTarget; +using mozilla::gfx::Float; +using mozilla::gfx::ToDeviceColor; + +namespace mozilla { + +struct TableReflowInput final { + TableReflowInput(const ReflowInput& aReflowInput, + const LogicalMargin& aBorderPadding, TableReflowMode aMode) + : mReflowInput(aReflowInput), + mWM(aReflowInput.GetWritingMode()), + mAvailSize(mWM) { + MOZ_ASSERT(mReflowInput.mFrame->IsTableFrame(), + "TableReflowInput should only be created for nsTableFrame"); + auto* table = static_cast(mReflowInput.mFrame); + + mICoord = aBorderPadding.IStart(mWM) + table->GetColSpacing(-1); + mAvailSize.ISize(mWM) = + std::max(0, mReflowInput.ComputedISize() - table->GetColSpacing(-1) - + table->GetColSpacing(table->GetColCount())); + + // Bug 1863421 will fix border-spacing issue in the block-axis in printing. + mAvailSize.BSize(mWM) = aMode == TableReflowMode::Measuring + ? NS_UNCONSTRAINEDSIZE + : mReflowInput.AvailableBSize(); + AdvanceBCoord(aBorderPadding.BStart(mWM)); + ReduceAvailableBSizeBy(aBorderPadding.BEnd(mWM) + table->GetRowSpacing(-1) + + table->GetRowSpacing(table->GetRowCount())); + } + + // Advance to the next block-offset and reduce the available block-size. + void AdvanceBCoord(nscoord aAmount) { + mBCoord += aAmount; + ReduceAvailableBSizeBy(aAmount); + } + + const LogicalSize& AvailableSize() const { return mAvailSize; } + + // The real reflow input of the table frame. + const ReflowInput& mReflowInput; + + // Stationary inline-offset, which won't change after the constructor. + nscoord mICoord = 0; + + // Running block-offset, which will be adjusted as we reflow children. + nscoord mBCoord = 0; + + private: + void ReduceAvailableBSizeBy(nscoord aAmount) { + if (mAvailSize.BSize(mWM) == NS_UNCONSTRAINEDSIZE) { + return; + } + mAvailSize.BSize(mWM) -= aAmount; + mAvailSize.BSize(mWM) = std::max(0, mAvailSize.BSize(mWM)); + } + + // mReflowInput's (i.e. table frame's) writing-mode. + WritingMode mWM; + + // The available size for children. The inline-size is stationary after the + // constructor, but the block-size will be adjusted as we reflow children. + LogicalSize mAvailSize; +}; + +struct TableBCData final { + TableArea mDamageArea; + BCPixelSize mBStartBorderWidth = 0; + BCPixelSize mIEndBorderWidth = 0; + BCPixelSize mBEndBorderWidth = 0; + BCPixelSize mIStartBorderWidth = 0; + BCPixelSize mIStartCellBorderWidth = 0; + BCPixelSize mIEndCellBorderWidth = 0; +}; + +} // namespace mozilla + +/******************************************************************************** + ** nsTableFrame ** + ********************************************************************************/ + +ComputedStyle* nsTableFrame::GetParentComputedStyle( + nsIFrame** aProviderFrame) const { + // Since our parent, the table wrapper frame, returned this frame, we + // must return whatever our parent would normally have returned. + + MOZ_ASSERT(GetParent(), "table constructed without table wrapper"); + if (!mContent->GetParent() && !Style()->IsPseudoOrAnonBox()) { + // We're the root. We have no ComputedStyle parent. + *aProviderFrame = nullptr; + return nullptr; + } + + return GetParent()->DoGetParentComputedStyle(aProviderFrame); +} + +nsTableFrame::nsTableFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + ClassID aID) + : nsContainerFrame(aStyle, aPresContext, aID) { + memset(&mBits, 0, sizeof(mBits)); +} + +void nsTableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + MOZ_ASSERT(!mCellMap, "Init called twice"); + MOZ_ASSERT(!mTableLayoutStrategy, "Init called twice"); + MOZ_ASSERT(!aPrevInFlow || aPrevInFlow->IsTableFrame(), + "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 = + (StyleBorderCollapse::Collapse == tableStyle->mBorderCollapse); + SetBorderCollapse(borderCollapse); + if (borderCollapse) { + SetNeedToCalcHasBCBorders(true); + } + + if (!aPrevInFlow) { + // If we're the first-in-flow, we manage the cell map & layout strategy that + // get used by our continuation chain: + mCellMap = MakeUnique(*this, borderCollapse); + if (IsAutoLayout()) { + mTableLayoutStrategy = MakeUnique(this); + } else { + mTableLayoutStrategy = MakeUnique(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))); + } +} + +// Define here (Rather than in the header), even if it's trival, to avoid +// UniquePtr members causing compile errors when their destructors are +// implicitly inserted into this destructor. Destruction requires +// the full definition of types that these UniquePtrs are managing, and +// the header only has forward declarations of them. +nsTableFrame::~nsTableFrame() = default; + +void nsTableFrame::Destroy(DestroyContext& aContext) { + MOZ_ASSERT(!mBits.mIsDestroying); + mBits.mIsDestroying = true; + mColGroups.DestroyFrames(aContext); + nsContainerFrame::Destroy(aContext); +} + +// 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->IsTableRowFrame() || kidFrame->IsTableRowGroupFrame()) && + 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->BreakAfter() || (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->BreakBefore() || + (nextRg && nextRg->HasInternalBreakBefore())) && + !IsRepeatedFrame(aNextFrame)) { + return !IsRepeatedFrame(aSourceFrame); // or after + } + } + return false; +} + +/* static */ +void nsTableFrame::PositionedTablePartMaybeChanged(nsIFrame* aFrame, + ComputedStyle* aOldStyle) { + const bool wasPositioned = + aOldStyle && aOldStyle->IsAbsPosContainingBlock(aFrame); + const bool isPositioned = aFrame->IsAbsPosContainingBlock(); + MOZ_ASSERT(isPositioned == aFrame->Style()->IsAbsPosContainingBlock(aFrame)); + if (wasPositioned == isPositioned) { + return; + } + + nsTableFrame* tableFrame = GetTableFrame(aFrame); + MOZ_ASSERT(tableFrame, "Should have a table frame here"); + tableFrame = static_cast(tableFrame->FirstContinuation()); + + // Retrieve the positioned parts array for this table. + FrameTArray* positionedParts = + tableFrame->GetProperty(PositionedTablePartArray()); + + // Lazily create the array if it doesn't exist yet. + if (!positionedParts) { + positionedParts = new FrameTArray; + tableFrame->SetProperty(PositionedTablePartArray(), positionedParts); + } + + if (isPositioned) { + // Add this frame to the list. + positionedParts->AppendElement(aFrame); + } else { + positionedParts->RemoveElement(aFrame); + } +} + +/* static */ +void nsTableFrame::MaybeUnregisterPositionedTablePart(nsIFrame* aFrame) { + if (!aFrame->IsAbsPosContainingBlock()) { + return; + } + nsTableFrame* tableFrame = GetTableFrame(aFrame); + tableFrame = static_cast(tableFrame->FirstContinuation()); + + if (tableFrame->IsDestroying()) { + return; // We're throwing the table away anyways. + } + + // Retrieve the positioned parts array for this table. + FrameTArray* positionedParts = + tableFrame->GetProperty(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 != FrameChildListID::Principal) { + nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); + return; + } + + MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(), + "unexpected second call to SetInitialChildList"); +#ifdef DEBUG + for (nsIFrame* f : aChildList) { + MOZ_ASSERT(f->GetParent() == this, "Unexpected parent"); + } +#endif + + // 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(childFrame->IsTableColGroupFrame(), + "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::RowOrColSpanChanged(nsTableCellFrame* aCellFrame) { + if (aCellFrame) { + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + // for now just remove the cell from the map and reinsert it + uint32_t rowIndex = aCellFrame->RowIndex(); + uint32_t colIndex = aCellFrame->ColIndex(); + RemoveCell(aCellFrame, rowIndex); + AutoTArray cells; + cells.AppendElement(aCellFrame); + InsertCells(cells, rowIndex, colIndex - 1); + + // XXX Should this use IntrinsicDirty::FrameAncestorsAndDescendants? It + // currently doesn't need to, but it might given more optimization. + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + 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 { + MOZ_ASSERT(!GetPrevInFlow(), "GetColFrame called on next in flow"); + int32_t numCols = mColFrames.Length(); + if ((aColIndex >= 0) && (aColIndex < numCols)) { + MOZ_ASSERT(mColFrames.ElementAt(aColIndex)); + return mColFrames.ElementAt(aColIndex); + } else { + MOZ_ASSERT_UNREACHABLE("invalid col index"); + return nullptr; + } +} + +int32_t nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex, + const nsTableCellFrame& aCell) const { + nsTableCellMap* cellMap = GetCellMap(); + MOZ_ASSERT(nullptr != cellMap, "bad call, cellMap not yet allocated."); + + return cellMap->GetEffectiveRowSpan(aRowIndex, aCell.ColIndex()); +} + +int32_t nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell, + nsCellMap* aCellMap) { + nsTableCellMap* tableCellMap = GetCellMap(); + if (!tableCellMap) ABORT1(1); + + uint32_t colIndex = aCell.ColIndex(); + uint32_t rowIndex = aCell.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); + + uint32_t colIndex = aCell.ColIndex(); + uint32_t rowIndex = aCell.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 = OrderedRowGroups(); + + 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 + mDeletedRowIndexRanges.clear(); + + RowGroupArray rowGroups = OrderedRowGroups(); + + nsTHashSet excludeRowGroups; + for (nsIFrame* excludeRowGroup : aRowGroupsToExclude) { + excludeRowGroups.Insert( + static_cast(excludeRowGroup)); +#ifdef DEBUG + { + // Check to make sure that the row indices of all rows in excluded row + // groups are '0' (i.e. the initial value since they haven't been added + // yet) + const nsFrameList& rowFrames = excludeRowGroup->PrincipalChildList(); + for (nsIFrame* r : rowFrames) { + auto* row = static_cast(r); + MOZ_ASSERT(row->GetRowIndex() == 0, + "exclusions cannot be used for rows that were already added," + "because we'd need to process mDeletedRowIndexRanges"); + } + } +#endif + } + + int32_t rowIndex = 0; + for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx]; + if (!excludeRowGroups.Contains(rgFrame)) { + const nsFrameList& rowFrames = rgFrame->PrincipalChildList(); + for (nsIFrame* r : rowFrames) { + if (mozilla::StyleDisplay::TableRow == r->StyleDisplay()->mDisplay) { + auto* row = static_cast(r); + row->SetRowIndex(rowIndex); + rowIndex++; + } + } + } + } +} + +void nsTableFrame::InsertColGroups(int32_t aStartColIndex, + const nsFrameList::Slice& aColGroups) { + int32_t colIndex = aStartColIndex; + + // XXX: We cannot use range-based for loop because AddColsToTable() can + // destroy the nsTableColGroupFrame in the slice we're traversing! Need to + // check the validity of *colGroupIter. + auto colGroupIter = aColGroups.begin(); + for (auto colGroupIterEnd = aColGroups.end(); + *colGroupIter && colGroupIter != colGroupIterEnd; ++colGroupIter) { + MOZ_ASSERT((*colGroupIter)->IsTableColGroupFrame()); + auto* cgFrame = static_cast(*colGroupIter); + cgFrame->SetStartColumnIndex(colIndex); + cgFrame->AddColsToTable(colIndex, false, cgFrame->PrincipalChildList()); + int32_t numCols = cgFrame->GetColCount(); + colIndex += numCols; + } + + if (*colGroupIter) { + nsTableColGroupFrame::ResetColIndices(*colGroupIter, 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.RemoveLastElement(); + // remove the col from the synthetic col group + nsTableColGroupFrame* lastColGroup = + (nsTableColGroupFrame*)mColGroups.LastChild(); + if (lastColGroup) { + MOZ_ASSERT(lastColGroup->IsSynthetic()); + DestroyContext context(PresShell()); + lastColGroup->RemoveChild(context, *lastCol, false); + + // remove the col group if it is empty + if (lastColGroup->GetColCount() <= 0) { + mColGroups.DestroyFrame(context, (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, GetColCount() - aColIndex, + 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.get(); +} + +nsTableColGroupFrame* nsTableFrame::CreateSyntheticColGroupFrame() { + nsIContent* colGroupContent = GetContent(); + mozilla::PresShell* presShell = PresShell(); + + RefPtr colGroupStyle; + colGroupStyle = presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle( + PseudoStyleType::tableColGroup); + // Create a col group frame + nsTableColGroupFrame* newFrame = + NS_NewTableColGroupFrame(presShell, colGroupStyle); + newFrame->SetIsSynthetic(); + newFrame->Init(colGroupContent, this, nullptr); + return newFrame; +} + +void nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) { + MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_."); + // get the last col group frame + nsTableColGroupFrame* colGroupFrame = + static_cast(mColGroups.LastChild()); + + if (!colGroupFrame || !colGroupFrame->IsSynthetic()) { + int32_t colIndex = (colGroupFrame) ? colGroupFrame->GetStartColumnIndex() + + colGroupFrame->GetColCount() + : 0; + colGroupFrame = CreateSyntheticColGroupFrame(); + 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) { + MOZ_ASSERT(aColGroupFrame, "null frame"); + MOZ_ASSERT(aColType != eColAnonymousCol, "Shouldn't happen"); + MOZ_ASSERT(aNumColsToAdd > 0, "We should be adding _something_."); + + mozilla::PresShell* presShell = 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++) { + // all anonymous cols that we create here use a pseudo ComputedStyle of the + // col group + nsIContent* iContent = aColGroupFrame->GetContent(); + RefPtr computedStyle = + presShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle( + PseudoStyleType::tableCol); + // 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(presShell, computedStyle); + ((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, std::move(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() { + MOZ_ASSERT(!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; + DestroyContext context(PresShell()); + for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) { + nsTableColFrame* colFrame = GetColFrame(colIdx); + if (colFrame && (eColAnonymousCell == colFrame->GetColType())) { + auto* cgFrame = static_cast(colFrame->GetParent()); + // remove the frame from the colgroup + cgFrame->RemoveChild(context, *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( + const nsTableRowGroupFrame* aRowGroupFrame) const { + RowGroupArray orderedRowGroups = 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); + bool shouldRecalculateIndex = !IsDeletedRowIndexRangesEmpty(); + if (shouldRecalculateIndex) { + ResetRowIndices(nsFrameList::Slice(nullptr, nullptr)); + } + int32_t origNumRows = cellMap->GetRowCount(); + int32_t numNewRows = aRowFrames.Length(); + cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans, + damageArea); + MatchCellMapToColCache(cellMap); + + // Perform row index adjustment only if row indices were not + // reset above + if (!shouldRecalculateIndex) { + if (aRowIndex < origNumRows) { + AdjustRowIndices(aRowIndex, numNewRows); + } + + // assign the correct row indices to the new rows. If they were + // recalculated 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; +} + +void nsTableFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) { + if (mDeletedRowIndexRanges.empty()) { + mDeletedRowIndexRanges.insert(std::pair( + aDeletedRowStoredIndex, aDeletedRowStoredIndex)); + return; + } + + // Find the position of the current deleted row's stored index + // among the previous deleted row index ranges and merge ranges if + // they are consecutive, else add a new (disjoint) range to the map. + // Call to mDeletedRowIndexRanges.upper_bound is + // O(log(mDeletedRowIndexRanges.size())) therefore call to + // AddDeletedRowIndex is also ~O(log(mDeletedRowIndexRanges.size())) + + // greaterIter = will point to smallest range in the map with lower value + // greater than the aDeletedRowStoredIndex. + // If no such value exists, point to end of map. + // smallerIter = will point to largest range in the map with higher value + // smaller than the aDeletedRowStoredIndex + // If no such value exists, point to beginning of map. + // i.e. when both values exist below is true: + // smallerIter->second < aDeletedRowStoredIndex < greaterIter->first + auto greaterIter = mDeletedRowIndexRanges.upper_bound(aDeletedRowStoredIndex); + auto smallerIter = greaterIter; + + if (smallerIter != mDeletedRowIndexRanges.begin()) { + smallerIter--; + // While greaterIter might be out-of-bounds (by being equal to end()), + // smallerIter now cannot be, since we returned early above for a 0-size + // map. + } + + // Note: smallerIter can only be equal to greaterIter when both + // of them point to the beginning of the map and in that case smallerIter + // does not "exist" but we clip smallerIter to point to beginning of map + // so that it doesn't point to something unknown or outside the map boundry. + // Note: When greaterIter is not the end (i.e. it "exists") upper_bound() + // ensures aDeletedRowStoredIndex < greaterIter->first so no need to + // assert that. + MOZ_ASSERT(smallerIter == greaterIter || + aDeletedRowStoredIndex > smallerIter->second, + "aDeletedRowIndexRanges already contains aDeletedRowStoredIndex! " + "Trying to delete an already deleted row?"); + + if (smallerIter->second == aDeletedRowStoredIndex - 1) { + if (greaterIter != mDeletedRowIndexRanges.end() && + greaterIter->first == aDeletedRowStoredIndex + 1) { + // merge current index with smaller and greater range as they are + // consecutive + smallerIter->second = greaterIter->second; + mDeletedRowIndexRanges.erase(greaterIter); + } else { + // add aDeletedRowStoredIndex in the smaller range as it is consecutive + smallerIter->second = aDeletedRowStoredIndex; + } + } else if (greaterIter != mDeletedRowIndexRanges.end() && + greaterIter->first == aDeletedRowStoredIndex + 1) { + // add aDeletedRowStoredIndex in the greater range as it is consecutive + mDeletedRowIndexRanges.insert(std::pair( + aDeletedRowStoredIndex, greaterIter->second)); + mDeletedRowIndexRanges.erase(greaterIter); + } else { + // add new range as aDeletedRowStoredIndex is disjoint from existing ranges + mDeletedRowIndexRanges.insert(std::pair( + aDeletedRowStoredIndex, aDeletedRowStoredIndex)); + } +} + +int32_t nsTableFrame::GetAdjustmentForStoredIndex(int32_t aStoredIndex) { + if (mDeletedRowIndexRanges.empty()) return 0; + + int32_t adjustment = 0; + + // O(log(mDeletedRowIndexRanges.size())) + auto endIter = mDeletedRowIndexRanges.upper_bound(aStoredIndex); + for (auto iter = mDeletedRowIndexRanges.begin(); iter != endIter; ++iter) { + adjustment += iter->second - iter->first + 1; + } + + return adjustment; +} + +// 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); + + // Mark rows starting from aFirstRowFrame to the next 'aNumRowsToRemove-1' + // number of rows as deleted. + nsTableRowGroupFrame* parentFrame = aFirstRowFrame.GetTableRowGroupFrame(); + parentFrame->MarkRowsAsDeleted(aFirstRowFrame, aNumRowsToRemove); + + cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans, + damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } + +#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) { + MOZ_ASSERT(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 = 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 OrderedRowGroups. + // 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 (nsIFrame* rowGroup : aRowGroups) { + if (orderedRowGroups[rgIndex] == rowGroup) { + 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 (nsIFrame* rowGroup : aRowGroups) { + if (orderedRowGroups[rgIndex] == rowGroup) { + 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(rowGroup, 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 == FrameChildListID::ColGroup) { + return mColGroups; + } + return nsContainerFrame::GetChildList(aListID); +} + +void nsTableFrame::GetChildLists(nsTArray* aLists) const { + nsContainerFrame::GetChildLists(aLists); + mColGroups.AppendIfNonempty(aLists, FrameChildListID::ColGroup); +} + +static inline bool FrameHasBorder(nsIFrame* f) { + if (!f->StyleVisibility()->IsVisible()) { + return false; + } + + return f->StyleBorder()->HasBorder(); +} + +void nsTableFrame::CalcHasBCBorders() { + if (!IsBorderCollapse()) { + SetHasBCBorders(false); + return; + } + + if (FrameHasBorder(this)) { + SetHasBCBorders(true); + return; + } + + // Check col and col group has borders. + for (nsIFrame* f : this->GetChildList(FrameChildListID::ColGroup)) { + if (FrameHasBorder(f)) { + SetHasBCBorders(true); + return; + } + + nsTableColGroupFrame* colGroup = static_cast(f); + for (nsTableColFrame* col = colGroup->GetFirstColumn(); col; + col = col->GetNextCol()) { + if (FrameHasBorder(col)) { + SetHasBCBorders(true); + return; + } + } + } + + // check row group, row and cell has borders. + RowGroupArray rowGroups = OrderedRowGroups(); + for (nsTableRowGroupFrame* rowGroup : rowGroups) { + if (FrameHasBorder(rowGroup)) { + SetHasBCBorders(true); + return; + } + + for (nsTableRowFrame* row = rowGroup->GetFirstRow(); row; + row = row->GetNextRow()) { + if (FrameHasBorder(row)) { + SetHasBCBorders(true); + return; + } + + for (nsTableCellFrame* cell = row->GetFirstCell(); cell; + cell = cell->GetNextCell()) { + if (FrameHasBorder(cell)) { + SetHasBCBorders(true); + return; + } + } + } + } + + SetHasBCBorders(false); +} + +namespace mozilla { +class nsDisplayTableBorderCollapse; +} + +// table paint code is concerned primarily with borders and bg color +// SEC: TODO: adjust the rect for captions +void nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255, 128, 255)); + + DisplayBorderBackgroundOutline(aBuilder, aLists); + + nsDisplayTableBackgroundSet tableBGs(aBuilder, this); + nsDisplayListCollection lists(aBuilder); + + // 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* colGroup : + FirstContinuation()->GetChildList(FrameChildListID::ColGroup)) { + for (nsIFrame* col : colGroup->PrincipalChildList()) { + tableBGs.AddColumn((nsTableColFrame*)col); + } + } + + for (nsIFrame* kid : PrincipalChildList()) { + BuildDisplayListForChild(aBuilder, kid, lists); + } + + tableBGs.MoveTo(aLists); + lists.MoveTo(aLists); + + if (IsVisibleForPainting()) { + // In the collapsed border model, overlay all collapsed borders. + if (IsBorderCollapse()) { + if (HasBCBorders()) { + aLists.BorderBackground()->AppendNewToTop( + aBuilder, this); + } + } else { + const nsStyleBorder* borderStyle = StyleBorder(); + if (borderStyle->HasBorder()) { + aLists.BorderBackground()->AppendNewToTop(aBuilder, + this); + } + } + } +} + +LogicalSides nsTableFrame::GetLogicalSkipSides() const { + LogicalSides skip(mWritingMode); + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return skip; + } + + // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto + // account for pagination + if (GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (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->IsTableColGroupFrame()); + // first we need to figure out the size of the colgroup + int32_t groupFirstCol = colIdx; + nscoord colGroupISize = 0; + nscoord colSpacing = 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"); + colSpacing = GetColSpacing(colIdx); + colGroupISize += + fif->GetColumnISizeFromFirstInFlow(colIdx) + colSpacing; + ++colIdx; + } + } + if (colGroupISize) { + colGroupISize -= colSpacing; + } + + 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); + colSpacing = GetColSpacing(colIdx); + colOrigin.I(aWM) += colISize + colSpacing; + ++colIdx; + } + } + + colGroupOrigin.I(aWM) += colGroupISize + colSpacing; + } +} + +// 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 + RowGroupArray rowGroups = OrderedRowGroups(); + // 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(gfxContext* aRenderingContext) { + if (NeedToCalcBCBorders()) CalcBCBorders(); + + ReflowColGroups(aRenderingContext); + + return LayoutStrategy()->GetMinISize(aRenderingContext); +} + +/* virtual */ +nscoord nsTableFrame::GetPrefISize(gfxContext* aRenderingContext) { + if (NeedToCalcBCBorders()) CalcBCBorders(); + + ReflowColGroups(aRenderingContext); + + return LayoutStrategy()->GetPrefISize(aRenderingContext, false); +} + +/* virtual */ nsIFrame::IntrinsicSizeOffsetData +nsTableFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) { + IntrinsicSizeOffsetData result = + nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis); + + result.margin = 0; + + if (IsBorderCollapse()) { + result.padding = 0; + + WritingMode wm = GetWritingMode(); + LogicalMargin outerBC = GetIncludedOuterBCBorder(wm); + result.border = outerBC.IStartEnd(wm); + } + + return result; +} + +/* virtual */ +nsIFrame::SizeComputationResult nsTableFrame::ComputeSize( + gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, + nscoord aAvailableISize, const LogicalSize& aMargin, + const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, + ComputeSizeFlags aFlags) { + // Only table wrapper calls this method, and it should use our writing mode. + MOZ_ASSERT(aWM == GetWritingMode(), + "aWM should be the same as our writing mode!"); + + auto result = nsContainerFrame::ComputeSize( + aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding, + aSizeOverrides, aFlags); + + // If our containing block wants to override inner table frame's inline-size + // (e.g. when resolving flex base size), don't enforce the min inline-size + // later in this method. + if (aSizeOverrides.mApplyOverridesVerbatim && aSizeOverrides.mStyleISize && + aSizeOverrides.mStyleISize->IsLengthPercentage()) { + 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.mLogicalSize.ISize(aWM)) { + result.mLogicalSize.ISize(aWM) = minISize; + } + + return result; +} + +nscoord nsTableFrame::TableShrinkISizeToFit(gfxContext* 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 nsIFrame::ShrinkISizeToFit 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( + gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, + nscoord aAvailableISize, const LogicalSize& aMargin, + const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, + ComputeSizeFlags aFlags) { + // Tables always shrink-wrap. + nscoord cbBased = + aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.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) { + LayoutFrameType frameType = rs->mFrame->Type(); + if (LayoutFrameType::TableCell == frameType || + LayoutFrameType::TableRow == frameType || + LayoutFrameType::TableRowGroup == frameType) { + const auto& bsize = rs->mStylePosition->BSize(wm); + // calc() with both lengths and percentages treated like 'auto' on + // internal table elements + if (!bsize.IsAuto() && !bsize.HasLengthAndPercentage()) { + return true; + } + } else if (LayoutFrameType::Table == frameType) { + // we reached the containing table, so always return + return !rs->mStylePosition->BSize(wm).IsAuto(); + } + } + 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(aReflowInput.mFrame->IsTableCellFrame() || + aReflowInput.mFrame->IsTableRowFrame() || + aReflowInput.mFrame->IsTableRowGroupFrame() || + aReflowInput.mFrame->IsTableFrame(), + "unexpected frame type"); + WritingMode wm = aReflowInput.GetWritingMode(); + if (!aReflowInput.mFrame->GetPrevInFlow() && // 1st in flow + (NS_UNCONSTRAINEDSIZE == + aReflowInput.ComputedBSize() || // no computed bsize + 0 == aReflowInput.ComputedBSize()) && + aReflowInput.mStylePosition->BSize(wm) + .ConvertsToPercentage() && // 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) { + LayoutFrameType frameType = rs->mFrame->Type(); + NS_ASSERTION(LayoutFrameType::TableCell == frameType || + LayoutFrameType::TableRow == frameType || + LayoutFrameType::TableRowGroup == frameType || + LayoutFrameType::Table == frameType, + "unexpected frame type"); + + rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + if (LayoutFrameType::Table == 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 input'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 + * input'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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), + "The nsTableWrapperFrame should be the out-of-flow if needed"); + + const WritingMode wm = aReflowInput.GetWritingMode(); + MOZ_ASSERT(aReflowInput.ComputedLogicalMargin(wm).IsAllZero(), + "Only nsTableWrapperFrame can have margins!"); + + bool isPaginated = aPresContext->IsPaginated(); + + 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(); + } + + // Check for an overflow list, and append any row group frames being pushed + MoveOverflowToChildList(); + + bool haveDesiredBSize = false; + SetHaveReflowedColGroups(false); + + // Bug 1863421: We need to call ApplySkipSides() for borderPadding so that it + // is correct in a table continuation. + LogicalMargin borderPadding = aReflowInput.ComputedLogicalBorderPadding(wm); + + // The tentative width is the width we assumed for the table when the child + // frames were positioned (which only matters in vertical-rl mode, because + // they're positioned relative to the right-hand edge). Then, after reflowing + // the kids, we can check whether the table ends up with a different width + // than this tentative value (either because it was unconstrained, so we used + // zero, or because it was enlarged by the child frames), we make the + // necessary positioning adjustments along the x-axis. + nscoord tentativeContainerWidth = 0; + bool mayAdjustXForAllChildren = 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. + if (IsSubtreeDirty() || aReflowInput.ShouldReflowAllKids() || + IsGeometryDirty() || isPaginated || aReflowInput.IsBResize() || + NeedToCollapse()) { + 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 = false; + if (isPaginated) { + // see if an extra reflow will be necessary in pagination mode + // when there is a specified table bsize + if (!GetPrevInFlow() && + NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize()) { + nscoord tableSpecifiedBSize = CalcBorderBoxBSize( + aReflowInput, borderPadding, NS_UNCONSTRAINEDSIZE); + if (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE && + tableSpecifiedBSize > 0) { + needToInitiateSpecialReflow = true; + } + } + } else { + needToInitiateSpecialReflow = + HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + } + nsIFrame* lastChildReflowed = nullptr; + + NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow, + "Shouldn't be in special bsize reflow here!"); + + const TableReflowMode firstReflowMode = needToInitiateSpecialReflow + ? TableReflowMode::Measuring + : TableReflowMode::Final; + ReflowTable(aDesiredSize, aReflowInput, borderPadding, firstReflowMode, + lastChildReflowed, aStatus); + + // When in vertical-rl mode, there may be two kinds of scenarios in which + // the positioning of all the children need to be adjusted along the x-axis + // because the width we assumed for the table when the child frames were + // being positioned(i.e. tentative width) may be different from the final + // width for the table: + // 1. If the computed width for the table is unconstrained, a dummy zero + // width was assumed as the tentative width to begin with. + // 2. If the child frames enlarge the width for the table, the final width + // becomes larger than the tentative one. + // Let's record the tentative width here, if later the final width turns out + // to be different from this tentative one, it means one of the above + // scenarios happens, then we adjust positioning of all the children. + // Note that vertical-lr, unlike vertical-rl, doesn't need to take special + // care of this situation, because they're positioned relative to the + // left-hand edge. + const nsSize containerSize = + aReflowInput.ComputedSizeAsContainerIfConstrained(); + if (wm.IsVerticalRL()) { + tentativeContainerWidth = containerSize.width; + mayAdjustXForAllChildren = true; + } + + // reevaluate special bsize reflow conditions + if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { + needToInitiateSpecialReflow = true; + } + + // XXXldb Are all these conditions correct? + if (needToInitiateSpecialReflow && aStatus.IsComplete()) { + // XXXldb Do we need to set the IsBResize flag on any reflow inputs? + + ReflowInput& mutable_rs = const_cast(aReflowInput); + + // distribute extra block-direction space to rows + aDesiredSize.BSize(wm) = CalcDesiredBSize(aReflowInput, borderPadding); + mutable_rs.mFlags.mSpecialBSizeReflow = true; + + ReflowTable(aDesiredSize, aReflowInput, borderPadding, + TableReflowMode::Final, lastChildReflowed, aStatus); + + if (lastChildReflowed && aStatus.IsIncomplete()) { + // if there is an incomplete child, then set the desired bsize + // to include it but not the next one + aDesiredSize.BSize(wm) = + borderPadding.BEnd(wm) + GetRowSpacing(GetRowCount()) + + lastChildReflowed->GetLogicalNormalRect(wm, containerSize).BEnd(wm); + } + haveDesiredBSize = true; + + mutable_rs.mFlags.mSpecialBSizeReflow = false; + } + } + + aDesiredSize.ISize(wm) = + aReflowInput.ComputedISize() + borderPadding.IStartEnd(wm); + if (!haveDesiredBSize) { + aDesiredSize.BSize(wm) = CalcDesiredBSize(aReflowInput, borderPadding); + } + if (IsRowInserted()) { + ProcessRowInserted(aDesiredSize.BSize(wm)); + } + + // For more information on the reason for what we should do this, refer to the + // code which defines and evaluates the variables xAdjustmentForAllKids and + // tentativeContainerWidth in the previous part in this function. + if (mayAdjustXForAllChildren) { + nscoord xAdjustmentForAllKids = + aDesiredSize.Width() - tentativeContainerWidth; + if (0 != xAdjustmentForAllKids) { + for (nsIFrame* kid : mFrames) { + kid->MovePositionBy(nsPoint(xAdjustmentForAllKids, 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); + } + + SetColumnDimensions(aDesiredSize.BSize(wm), wm, borderPadding, + aDesiredSize.PhysicalSize()); + NS_WARNING_ASSERTION(NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(), + "reflow branch removed unconstrained available isizes"); + if (NeedToCollapse()) { + // This code and the code it depends on assumes that all row groups + // and rows have just been reflowed (i.e., it makes adjustments to + // their rects that are not idempotent). Thus the reflow code + // checks NeedToCollapse() to ensure this is true. + 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(aReflowInput.mStyleDisplay) != + PhysicalAxes::Both) { + // collapsed border may leak out + LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm); + tableRect.Inflate(bcMargin.GetPhysicalMargin(wm)); + } + aDesiredSize.mOverflowAreas.UnionAllWith(tableRect); + + FinishAndStoreOverflow(&aDesiredSize); +} + +void nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput) { + FrameTArray* positionedParts = GetProperty(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. + const WritingMode wm = positionedPart->GetWritingMode(); + const LogicalSize size = positionedPart->GetLogicalSize(wm); + ReflowOutput desiredSize(aReflowInput.GetWritingMode()); + desiredSize.SetSize(wm, size); + desiredSize.mOverflowAreas = + positionedPart->GetOverflowAreasRelativeToSelf(); + + // Construct a dummy reflow input and reflow status. + // XXX(seth): Note that the dummy reflow input doesn't have a correct + // chain of parent reflow inputs. It also doesn't necessarily have a + // correct containing block. + LogicalSize availSize = size; + availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput reflowInput(aPresContext, positionedPart, + aReflowInput.mRenderingContext, availSize, + ReflowInput::InitFlag::DummyParentReflowInput); + nsReflowStatus reflowStatus; + + // 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. + positionedPart->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(OverflowAreas& aOverflowAreas) { + // As above in Reflow, make sure the table overflow area includes the table + // rect, and check for collapsed borders leaking out. + if (ShouldApplyOverflowClipping(StyleDisplay()) != PhysicalAxes::Both) { + 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, + const LogicalMargin& aBorderPadding, + TableReflowMode aReflowMode, + nsIFrame*& aLastChildReflowed, + nsReflowStatus& aStatus) { + aLastChildReflowed = nullptr; + + if (!GetPrevInFlow()) { + mTableLayoutStrategy->ComputeColumnISizes(aReflowInput); + } + + TableReflowInput reflowInput(aReflowInput, aBorderPadding, aReflowMode); + ReflowChildren(reflowInput, aStatus, aLastChildReflowed, + aDesiredSize.mOverflowAreas); + + ReflowColGroups(aReflowInput.mRenderingContext); +} + +void nsTableFrame::PushChildrenToOverflow(const RowGroupArray& aRowGroups, + size_t aPushFrom) { + MOZ_ASSERT(aPushFrom > 0, "pushing first child"); + + // Extract the frames from the array into a frame list. + nsFrameList frames; + for (size_t childX = aPushFrom; childX < aRowGroups.Length(); ++childX) { + nsTableRowGroupFrame* rgFrame = aRowGroups[childX]; + if (!rgFrame->IsRepeatable()) { + mFrames.RemoveFrame(rgFrame); + frames.AppendFrame(nullptr, rgFrame); + } + } + + if (frames.IsEmpty()) { + return; + } + + // Add the frames to our overflow list. + SetOverflowFrames(std::move(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 = OrderedRowGroups(); + + nsTableFrame* firstInFlow = static_cast(FirstInFlow()); + nscoord iSize = firstInFlow->GetCollapsedISize(aWM, aBorderPadding); + nscoord rgISize = iSize - GetColSpacing(-1) - GetColSpacing(GetColCount()); + OverflowAreas 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 = StyleVisibility::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 = StyleVisibility::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::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); + + if (!aOldComputedStyle) // avoid this on init + return; + + if (IsBorderCollapse() && BCRecalcNeeded(aOldComputedStyle, Style())) { + SetFullBCDamageArea(); + } + + // avoid this on init or nextinflow + if (!mTableLayoutStrategy || GetPrevInFlow()) return; + + bool isAuto = IsAutoLayout(); + if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) { + if (isAuto) + mTableLayoutStrategy = MakeUnique(this); + else + mTableLayoutStrategy = MakeUnique(this); + } +} + +void nsTableFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal || + aListID == FrameChildListID::ColGroup, + "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, std::move(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(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(f, nullptr)); + } else { + // Nothing special to do, just add the frame to our child list + MOZ_ASSERT_UNREACHABLE( + "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 + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + SetGeometryDirty(); +} + +void nsTableFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + 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, std::move(aFrameList)); + return; + } + + // Collect ColGroupFrames into a separate list and insert those separately + // from the other frames (bug 759249). + nsFrameList colGroupList; + nsFrameList principalList; + do { + const auto display = aFrameList.FirstChild()->StyleDisplay()->mDisplay; + nsFrameList head = aFrameList.Split([display](nsIFrame* aFrame) { + return aFrame->StyleDisplay()->mDisplay != display; + }); + if (display == mozilla::StyleDisplay::TableColumnGroup) { + colGroupList.AppendFrames(nullptr, std::move(head)); + } else { + principalList.AppendFrames(nullptr, std::move(head)); + } + } while (aFrameList.NotEmpty()); + + // 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 (colGroupList.NotEmpty()) { + HomogenousInsertFrames(FrameChildListID::ColGroup, aPrevFrame, + colGroupList); + } + if (principalList.NotEmpty()) { + HomogenousInsertFrames(FrameChildListID::Principal, aPrevFrame, + principalList); + } +} + +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, std::move(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. + const Maybe newIndex = container->ComputeIndexOf(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 + Maybe lastIndex; + 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(); + } + const Maybe index = container->ComputeIndexOf(content); + // XXX Keep the odd traditional behavior in some indices are nothing + // cases for now. + if ((index.isSome() && + (lastIndex.isNothing() || *index > *lastIndex)) && + (newIndex.isSome() && + (index.isNothing() || *index < *newIndex))) { + lastIndex = index; + aPrevFrame = kidFrame; + } + kidFrame = kidFrame->GetNextSibling(); + } + } + } + } + if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) { + NS_ASSERTION(aListID == FrameChildListID::ColGroup, + "unexpected child list"); + // Insert the column group frames + const nsFrameList::Slice& newColgroups = + mColGroups.InsertFrames(this, aPrevFrame, std::move(aFrameList)); + // find the starting col index for the first new col group + int32_t startColIndex = 0; + if (aPrevFrame) { + nsTableColGroupFrame* prevColGroup = + (nsTableColGroupFrame*)GetFrameAtOrBefore( + this, aPrevFrame, LayoutFrameType::TableColGroup); + if (prevColGroup) { + startColIndex = + prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount(); + } + } + InsertColGroups(startColIndex, newColgroups); + } else if (IsRowGroup(display->mDisplay)) { + NS_ASSERTION(aListID == FrameChildListID::Principal, + "unexpected child list"); + DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames + // Insert the frames in the sibling chain + const nsFrameList::Slice& newRowGroups = + mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); + + InsertRowGroups(newRowGroups); + } else { + NS_ASSERTION(aListID == FrameChildListID::Principal, + "unexpected child list"); + MOZ_ASSERT_UNREACHABLE("How did we even get here?"); + // Just insert the frame and don't worry about reflowing it + mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); + return; + } + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + SetGeometryDirty(); +#ifdef DEBUG_TABLE_CELLMAP + printf("=== TableFrame::InsertFrames\n"); + Dump(true, true, true); +#endif +} + +void nsTableFrame::DoRemoveFrame(DestroyContext& aContext, ChildListID aListID, + nsIFrame* aOldFrame) { + if (aListID == FrameChildListID::ColGroup) { + nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling(); + nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame; + int32_t firstColIndex = colGroup->GetStartColumnIndex(); + int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1; + mColGroups.DestroyFrame(aContext, 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 == FrameChildListID::Principal, + "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(aContext, 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(nullptr, nullptr)); + TableArea damageArea; + cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false, + damageArea); + + static_cast(FirstInFlow()) + ->MatchCellMapToColCache(cellMap); + } + } +} + +void nsTableFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, + nsIFrame* aOldFrame) { + NS_ASSERTION(aListID == FrameChildListID::ColGroup || + mozilla::StyleDisplay::TableColumnGroup != + aOldFrame->StyleDisplay()->mDisplay, + "Wrong list name; use FrameChildListID::ColGroup iff colgroup"); + mozilla::PresShell* presShell = PresShell(); + nsTableFrame* lastParent = nullptr; + while (aOldFrame) { + nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation(); + nsTableFrame* parent = static_cast(aOldFrame->GetParent()); + if (parent != lastParent) { + parent->DrainSelfOverflowList(); + } + parent->DoRemoveFrame(aContext, 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(); + presShell->FrameNeedsReflow(parent, IntrinsicDirty::FrameAndAncestors, + 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); +} + +// This property is only set on the first-in-flow of nsTableFrame. +NS_DECLARE_FRAME_PROPERTY_DELETABLE(TableBCDataProperty, TableBCData) + +TableBCData* nsTableFrame::GetTableBCData() const { + return FirstInFlow()->GetProperty(TableBCDataProperty()); +} + +TableBCData* nsTableFrame::GetOrCreateTableBCData() { + MOZ_ASSERT(!GetPrevInFlow(), + "TableBCProperty should only be set on the first-in-flow!"); + TableBCData* value = GetProperty(TableBCDataProperty()); + if (!value) { + value = new TableBCData(); + SetProperty(TableBCDataProperty(), value); + } + + MOZ_ASSERT(value, "TableBCData must exist!"); + 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 d2a = PresContext()->AppUnitsPerDevPixel(); + TableBCData* propData = GetTableBCData(); + if (propData) { + return LogicalMargin( + aWM, BC_BORDER_START_HALF_COORD(d2a, propData->mBStartBorderWidth), + BC_BORDER_END_HALF_COORD(d2a, propData->mIEndBorderWidth), + BC_BORDER_END_HALF_COORD(d2a, propData->mBEndBorderWidth), + BC_BORDER_START_HALF_COORD(d2a, propData->mIStartBorderWidth)); + } + return LogicalMargin(aWM); +} + +LogicalMargin nsTableFrame::GetIncludedOuterBCBorder( + const WritingMode aWM) const { + if (NeedToCalcBCBorders()) { + const_cast(this)->CalcBCBorders(); + } + + int32_t d2a = PresContext()->AppUnitsPerDevPixel(); + TableBCData* propData = GetTableBCData(); + if (propData) { + return LogicalMargin( + aWM, BC_BORDER_START_HALF_COORD(d2a, propData->mBStartBorderWidth), + BC_BORDER_END_HALF_COORD(d2a, propData->mIEndCellBorderWidth), + BC_BORDER_END_HALF_COORD(d2a, propData->mBEndBorderWidth), + BC_BORDER_START_HALF_COORD(d2a, propData->mIStartCellBorderWidth)); + } + return LogicalMargin(aWM); +} + +LogicalMargin nsTableFrame::GetExcludedOuterBCBorder( + const WritingMode aWM) const { + return GetOuterBCBorder(aWM) - GetIncludedOuterBCBorder(aWM); +} + +void nsTableFrame::GetCollapsedBorderPadding( + Maybe& aBorder, Maybe& aPadding) const { + if (IsBorderCollapse()) { + // Border-collapsed tables don't use any of their padding, and only part of + // their border. + const auto wm = GetWritingMode(); + aBorder.emplace(GetIncludedOuterBCBorder(wm)); + aPadding.emplace(wm); + } +} + +void nsTableFrame::InitChildReflowInput(ReflowInput& aReflowInput) { + const auto childWM = aReflowInput.GetWritingMode(); + LogicalMargin border(childWM); + if (IsBorderCollapse()) { + nsTableRowGroupFrame* rgFrame = + static_cast(aReflowInput.mFrame); + border = rgFrame->GetBCBorderWidth(childWM); + } + const LogicalMargin zeroPadding(childWM); + aReflowInput.Init(PresContext(), Nothing(), Some(border), Some(zeroPadding)); + + 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 input. The origin of +// aKidRect is relative to the upper-left origin of our frame +void nsTableFrame::PlaceChild(TableReflowInput& aReflowInput, + nsIFrame* aKidFrame, + const ReflowInput& aKidReflowInput, + const mozilla::LogicalPoint& aKidPosition, + const nsSize& aContainerSize, + ReflowOutput& aKidDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidInkOverflow) { + WritingMode wm = aReflowInput.mReflowInput.GetWritingMode(); + bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + // Place and size the child + FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, &aKidReflowInput, + wm, aKidPosition, aContainerSize, + ReflowChildFlags::ApplyRelativePositioning); + + InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidInkOverflow, + isFirstReflow); + + aReflowInput.AdvanceBCoord(aKidDesiredSize.BSize(wm)); +} + +nsTableFrame::RowGroupArray nsTableFrame::OrderedRowGroups( + nsTableRowGroupFrame** aHead, nsTableRowGroupFrame** aFoot) const { + RowGroupArray children; + nsTableRowGroupFrame* head = nullptr; + nsTableRowGroupFrame* foot = nullptr; + + nsIFrame* kidFrame = mFrames.FirstChild(); + while (kidFrame) { + const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay(); + auto* rowGroup = static_cast(kidFrame); + + switch (kidDisplay->DisplayInside()) { + case StyleDisplayInside::TableHeaderGroup: + if (head) { // treat additional thead like tbody + children.AppendElement(rowGroup); + } else { + head = rowGroup; + } + break; + case StyleDisplayInside::TableFooterGroup: + if (foot) { // treat additional tfoot like tbody + children.AppendElement(rowGroup); + } else { + foot = rowGroup; + } + break; + case StyleDisplayInside::TableRowGroup: + children.AppendElement(rowGroup); + break; + default: + MOZ_ASSERT_UNREACHABLE("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) { + children.InsertElementAt(0, head); + } + if (aHead) { + *aHead = head; + } + // put the tfoot after the last tbody + if (foot) { + children.AppendElement(foot); + } + if (aFoot) { + *aFoot = foot; + } + + return children; +} + +static bool IsRepeatable(nscoord aFrameBSize, nscoord aPageBSize) { + return aFrameBSize < (aPageBSize / 4); +} + +nscoord nsTableFrame::SetupHeaderFooterChild( + const TableReflowInput& aReflowInput, nsTableRowGroupFrame* aFrame) { + nsPresContext* presContext = PresContext(); + const WritingMode wm = GetWritingMode(); + const nscoord pageBSize = + LogicalSize(wm, presContext->GetPageSize()).BSize(wm); + + // Reflow the child with unconstrained block-size. + LogicalSize availSize = aReflowInput.AvailableSize(); + availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + + const nsSize containerSize = + aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); + ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput, aFrame, + availSize, Nothing(), + ReflowInput::InitFlag::CallerWillInit); + InitChildReflowInput(kidReflowInput); + kidReflowInput.mFlags.mIsTopOfPage = true; + ReflowOutput desiredSize(aReflowInput.mReflowInput); + nsReflowStatus status; + ReflowChild(aFrame, presContext, desiredSize, kidReflowInput, wm, + LogicalPoint(wm, aReflowInput.mICoord, aReflowInput.mBCoord), + containerSize, ReflowChildFlags::Default, status); + // The child will be reflowed again "for real" so no need to place it now + + aFrame->SetRepeatable(IsRepeatable(desiredSize.BSize(wm), pageBSize)); + return desiredSize.BSize(wm); +} + +void nsTableFrame::PlaceRepeatedFooter(TableReflowInput& aReflowInput, + nsTableRowGroupFrame* aTfoot, + nscoord aFooterBSize) { + nsPresContext* presContext = PresContext(); + const WritingMode wm = GetWritingMode(); + LogicalSize kidAvailSize = aReflowInput.AvailableSize(); + kidAvailSize.BSize(wm) = aFooterBSize; + + const nsSize containerSize = + aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); + ReflowInput footerReflowInput(presContext, aReflowInput.mReflowInput, aTfoot, + kidAvailSize, Nothing(), + ReflowInput::InitFlag::CallerWillInit); + InitChildReflowInput(footerReflowInput); + aReflowInput.AdvanceBCoord(GetRowSpacing(GetRowCount())); + + nsRect origTfootRect = aTfoot->GetRect(); + nsRect origTfootInkOverflow = aTfoot->InkOverflowRect(); + + nsReflowStatus footerStatus; + ReflowOutput desiredSize(aReflowInput.mReflowInput); + LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord); + ReflowChild(aTfoot, presContext, desiredSize, footerReflowInput, wm, + kidPosition, containerSize, ReflowChildFlags::Default, + footerStatus); + + PlaceChild(aReflowInput, aTfoot, footerReflowInput, kidPosition, + containerSize, desiredSize, origTfootRect, origTfootInkOverflow); +} + +// Reflow the children based on the avail size and reason in aReflowInput +void nsTableFrame::ReflowChildren(TableReflowInput& aReflowInput, + nsReflowStatus& aStatus, + nsIFrame*& aLastChildReflowed, + OverflowAreas& aOverflowAreas) { + aStatus.Reset(); + aLastChildReflowed = nullptr; + + nsIFrame* prevKidFrame = nullptr; + WritingMode wm = aReflowInput.mReflowInput.GetWritingMode(); + NS_WARNING_ASSERTION( + wm.IsVertical() || + NS_UNCONSTRAINEDSIZE != aReflowInput.mReflowInput.ComputedWidth(), + "shouldn't have unconstrained width in horizontal mode"); + nsSize containerSize = + aReflowInput.mReflowInput.ComputedSizeAsContainerIfConstrained(); + + nsPresContext* presContext = PresContext(); + // nsTableFrame is not able to pull back children from its next-in-flow, per + // bug 1772383. So even under paginated contexts, tables should not fragment + // if they are inside of (i.e. potentially being fragmented by) a column-set + // frame. (This is indicated by the "mTableIsSplittable" flag.) + bool isPaginated = + presContext->IsPaginated() && + aReflowInput.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && + aReflowInput.mReflowInput.mFlags.mTableIsSplittable; + + // Tables currently (though we ought to fix this) only fragment in + // paginated contexts, not in multicolumn contexts. (See bug 888257.) + // This is partly because they don't correctly handle incremental + // layout when paginated. + // + // Since we propagate NS_FRAME_IS_DIRTY from parent to child at the + // start of the parent's reflow (behavior that's new as of bug + // 1308876), we can do things that are effectively incremental reflow + // during paginated layout. Since the table code doesn't handle this + // correctly, we need to set the flag that says to reflow everything + // within the table structure. + if (presContext->IsPaginated()) { + SetGeometryDirty(); + } + + aOverflowAreas.Clear(); + + bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() || + mBits.mResizedColumns || IsGeometryDirty() || + NeedToCollapse(); + + nsTableRowGroupFrame* thead = nullptr; + nsTableRowGroupFrame* tfoot = nullptr; + RowGroupArray rowGroups = OrderedRowGroups(&thead, &tfoot); + bool pageBreak = false; + nscoord footerBSize = 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) { + bool reorder = false; + if (thead && !GetPrevInFlow()) { + reorder = thead->GetNextInFlow(); + SetupHeaderFooterChild(aReflowInput, thead); + } + if (tfoot) { + reorder = reorder || tfoot->GetNextInFlow(); + footerBSize = SetupHeaderFooterChild(aReflowInput, tfoot); + } + if (reorder) { + // Reorder row groups - the reflow may have changed the nextinflows. + rowGroups = OrderedRowGroups(&thead, &tfoot); + } + } + bool allowRepeatedFooter = false; + for (size_t childX = 0; childX < rowGroups.Length(); childX++) { + nsTableRowGroupFrame* kidFrame = rowGroups[childX]; + const nscoord rowSpacing = + GetRowSpacing(kidFrame->GetStartRowIndex() + kidFrame->GetRowCount()); + // See if we should only reflow the dirty child frames + if (reflowAllKids || kidFrame->IsSubtreeDirty() || + (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow && + (isPaginated || + kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { + // A helper to place a repeated footer if allowed, or set it as + // non-repeatable. + auto MaybePlaceRepeatedFooter = [&]() { + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowInput, tfoot, footerBSize); + } else if (tfoot && tfoot->IsRepeatable()) { + tfoot->SetRepeatable(false); + } + }; + + if (pageBreak) { + MaybePlaceRepeatedFooter(); + PushChildrenToOverflow(rowGroups, childX); + aStatus.Reset(); + aStatus.SetIncomplete(); + break; + } + + LogicalSize kidAvailSize = aReflowInput.AvailableSize(); + allowRepeatedFooter = false; + + // If the child is a tbody in paginated mode, reduce the available + // block-size by a repeated footer. + if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.BSize(wm))) { + if (kidFrame != thead && kidFrame != 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 (footerBSize + rowSpacing < kidAvailSize.BSize(wm)) { + allowRepeatedFooter = true; + kidAvailSize.BSize(wm) -= footerBSize + rowSpacing; + } + } + } + + nsRect oldKidRect = kidFrame->GetRect(); + nsRect oldKidInkOverflow = kidFrame->InkOverflowRect(); + + ReflowOutput desiredSize(aReflowInput.mReflowInput); + + // Reflow the child into the available space + ReflowInput kidReflowInput(presContext, aReflowInput.mReflowInput, + kidFrame, kidAvailSize, Nothing(), + ReflowInput::InitFlag::CallerWillInit); + InitChildReflowInput(kidReflowInput); + + // If this isn't the first row group, and the previous row group has a + // nonzero BEnd, 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] + ->GetLogicalNormalRect(wm, containerSize) + .BEnd(wm) > 0)) { + kidReflowInput.mFlags.mIsTopOfPage = false; + } + aReflowInput.AdvanceBCoord(rowSpacing); + // record the presence of a next in flow, it might get destroyed so we + // need to reorder the row group array + const bool reorder = kidFrame->GetNextInFlow(); + + LogicalPoint kidPosition(wm, aReflowInput.mICoord, aReflowInput.mBCoord); + aStatus.Reset(); + ReflowChild(kidFrame, presContext, desiredSize, kidReflowInput, wm, + kidPosition, containerSize, ReflowChildFlags::Default, + aStatus); + + if (reorder) { + // Reorder row groups - the reflow may have changed the nextinflows. + rowGroups = OrderedRowGroups(&thead, &tfoot); + childX = rowGroups.IndexOf(kidFrame); + MOZ_ASSERT(childX != RowGroupArray::NoIndex, + "kidFrame should still be in rowGroups!"); + } + if (isPaginated && !aStatus.IsFullyComplete() && + ShouldAvoidBreakInside(aReflowInput.mReflowInput)) { + aStatus.SetInlineLineBreakBeforeAndReset(); + break; + } + // see if the rowgroup did not fit on this page might be pushed on + // the next page + if (isPaginated && + (aStatus.IsInlineBreakBefore() || + (aStatus.IsComplete() && + (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) && + kidReflowInput.AvailableBSize() < desiredSize.BSize(wm)))) { + if (ShouldAvoidBreakInside(aReflowInput.mReflowInput)) { + aStatus.SetInlineLineBreakBeforeAndReset(); + break; + } + // if we are on top of the page place with dataloss + if (kidReflowInput.mFlags.mIsTopOfPage) { + if (childX + 1 < rowGroups.Length()) { + PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition, + containerSize, desiredSize, oldKidRect, + oldKidInkOverflow); + MaybePlaceRepeatedFooter(); + aStatus.Reset(); + aStatus.SetIncomplete(); + PushChildrenToOverflow(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 + MaybePlaceRepeatedFooter(); + aStatus.Reset(); + aStatus.SetIncomplete(); + PushChildrenToOverflow(rowGroups, childX); + aLastChildReflowed = prevKidFrame; + break; + } else { // we can't push so lets make clear how much space we need + PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition, + containerSize, desiredSize, oldKidRect, + oldKidInkOverflow); + MaybePlaceRepeatedFooter(); + aLastChildReflowed = allowRepeatedFooter ? tfoot : kidFrame; + break; + } + } + } + + aLastChildReflowed = kidFrame; + + pageBreak = false; + // see if there is a page break after this row group or before the next + // one + if (aStatus.IsComplete() && isPaginated && + (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) { + nsIFrame* nextKid = + (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr; + pageBreak = PageBreakAfter(kidFrame, nextKid); + } + + // Place the child + PlaceChild(aReflowInput, kidFrame, kidReflowInput, kidPosition, + containerSize, desiredSize, oldKidRect, oldKidInkOverflow); + + // Remember where we just were in case we end up pushing children + prevKidFrame = kidFrame; + + MOZ_ASSERT(!aStatus.IsIncomplete() || isPaginated, + "Table contents should only fragment in paginated contexts"); + + // Special handling for incomplete children + if (isPaginated && aStatus.IsIncomplete()) { + 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 = + PresShell()->FrameConstructor()->CreateContinuingFrame(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()) { + // OrderedRowGroups 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), + "OrderedRowGroups 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. + MaybePlaceRepeatedFooter(); + if (kidFrame->GetNextSibling()) { + PushChildrenToOverflow(rowGroups, childX + 1); + } + break; + } + } else { // it isn't being reflowed + aReflowInput.AdvanceBCoord(rowSpacing); + const LogicalRect kidRect = + kidFrame->GetLogicalNormalRect(wm, containerSize); + if (kidRect.BStart(wm) != aReflowInput.mBCoord) { + // invalidate the old position + kidFrame->InvalidateFrameSubtree(); + // move to the new position + kidFrame->MovePositionBy( + wm, LogicalPoint(wm, 0, aReflowInput.mBCoord - kidRect.BStart(wm))); + RePositionViews(kidFrame); + // invalidate the new position + kidFrame->InvalidateFrameSubtree(); + } + + aReflowInput.AdvanceBCoord(kidRect.BSize(wm)); + } + } + + // We've now propagated the column resizes and geometry changes to all + // the children. + mBits.mResizedColumns = false; + ClearGeometryDirty(); + + // nsTableFrame does not pull children from its next-in-flow (bug 1772383). + // This is generally fine, since tables only fragment for printing + // (bug 888257) where incremental-reflow is impossible, and so children don't + // usually dynamically move back and forth between continuations. However, + // there are edge cases even with printing where nsTableFrame: + // (1) Generates a continuation and passes children to it, + // (2) Receives another call to Reflow, during which it + // (3) Successfully lays out its remaining children. + // If the completed status flows up as-is, the continuation will be destroyed. + // To avoid that, we return an incomplete status if the continuation contains + // any child that is not a repeated frame. + auto hasNextInFlowThatMustBePreserved = [this, isPaginated]() -> bool { + if (!isPaginated) { + return false; + } + auto* nextInFlow = static_cast(GetNextInFlow()); + if (!nextInFlow) { + return false; + } + for (nsIFrame* kidFrame : nextInFlow->mFrames) { + if (!IsRepeatedFrame(kidFrame)) { + return true; + } + } + return false; + }; + if (aStatus.IsComplete() && hasNextInFlowThatMustBePreserved()) { + aStatus.SetIncomplete(); + } +} + +void nsTableFrame::ReflowColGroups(gfxContext* aRenderingContext) { + if (!GetPrevInFlow() && !HaveReflowedColGroups()) { + const WritingMode wm = GetWritingMode(); + nsPresContext* presContext = PresContext(); + for (nsIFrame* kidFrame : mColGroups) { + if (kidFrame->IsSubtreeDirty()) { + // The column groups don't care about dimensions or reflow inputs. + ReflowOutput kidSize(wm); + ReflowInput kidReflowInput(presContext, kidFrame, aRenderingContext, + LogicalSize(kidFrame->GetWritingMode())); + nsReflowStatus cgStatus; + const LogicalPoint dummyPos(wm); + const nsSize dummyContainerSize; + ReflowChild(kidFrame, presContext, kidSize, kidReflowInput, wm, + dummyPos, dummyContainerSize, ReflowChildFlags::Default, + cgStatus); + FinishReflowChild(kidFrame, presContext, kidSize, &kidReflowInput, wm, + dummyPos, dummyContainerSize, + ReflowChildFlags::Default); + } + } + SetHaveReflowedColGroups(true); + } +} + +nscoord nsTableFrame::CalcDesiredBSize(const ReflowInput& aReflowInput, + const LogicalMargin& aBorderPadding) { + WritingMode wm = aReflowInput.GetWritingMode(); + + // get the natural bsize based on the last child's (row group) rect + RowGroupArray rowGroups = OrderedRowGroups(); + if (rowGroups.IsEmpty()) { + if (eCompatibility_NavQuirks == PresContext()->CompatibilityMode()) { + // empty tables should not have a size in quirks mode + return 0; + } + return CalcBorderBoxBSize(aReflowInput, aBorderPadding, + aBorderPadding.BStartEnd(wm)); + } + + nsTableCellMap* cellMap = GetCellMap(); + MOZ_ASSERT(cellMap); + int32_t rowCount = cellMap->GetRowCount(); + int32_t colCount = cellMap->GetColCount(); + nscoord desiredBSize = aBorderPadding.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 bSize = + CalcBorderBoxBSize(aReflowInput, aBorderPadding, desiredBSize); + if (bSize > desiredBSize) { + // proportionately distribute the excess bsize to unconstrained rows in + // each unconstrained row group. + DistributeBSizeToRows(aReflowInput, bSize - desiredBSize); + return bSize; + } + // Tables don't shrink below their intrinsic size, apparently, even when + // constrained by stuff like flex / grid or what not. + return desiredBSize; + } + + // FIXME(emilio): Is this right? This only affects fragmented tables... + return desiredBSize; +} + +static void ResizeCells(nsTableFrame& aTableFrame) { + nsTableFrame::RowGroupArray rowGroups = aTableFrame.OrderedRowGroups(); + 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 = aReflowInput.ComputedLogicalBorderPadding(wm); + + nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained(); + + RowGroupArray rowGroups = OrderedRowGroups(); + + 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; + const LogicalRect rgNormalRect = + rgFrame->GetLogicalNormalRect(wm, 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; + const LogicalRect rowNormalRect = + rowFrame->GetLogicalNormalRect(wm, dummyContainerSize); + const nscoord rowSpacing = 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 + rowSpacing; + bEndRG += newRowBSize + rowSpacing; + 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) + rowSpacing; + bEndRG += rowNormalRect.BSize(wm) + rowSpacing; + } + rowFrame = rowFrame->GetNextRow(); + } + if (amountUsed > 0) { + if (rgNormalRect.BStart(wm) != bOriginRG) { + rgFrame->InvalidateFrameSubtree(); + } + + nsRect origRgNormalRect = rgFrame->GetRect(); + nsRect origRgInkOverflow = rgFrame->InkOverflowRect(); + + 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, + origRgInkOverflow, 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; + const LogicalRect rgNormalRect = + rgFrame->GetLogicalNormalRect(wm, containerSize); + nsRect rgInkOverflow = rgFrame->InkOverflowRect(); + // 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()) { + const nscoord rowSpacing = 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; + const LogicalRect rowNormalRect = + rowFrame->GetLogicalNormalRect(wm, dummyContainerSize); + nsRect rowInkOverflow = rowFrame->InkOverflowRect(); + // 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 + rowSpacing; + bEndRG += newRowBSize + rowSpacing; + + amountUsed += amountForRow; + amountUsedByRG += amountForRow; + NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation"); + // rowFrame->DidResize(); + nsTableFrame::RePositionViews(rowFrame); + + nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect, + rowInkOverflow, 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) + rowSpacing; + bEndRG += rowNormalRect.BSize(wm) + rowSpacing; + } + } + + 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, + rgInkOverflow, 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); +} + +nscoord nsTableFrame::SynthesizeFallbackBaseline( + mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { + if (aBaselineGroup == BaselineSharingGroup::Last) { + return 0; + } + return BSize(aWM); +} + +/* virtual */ +Maybe nsTableFrame::GetNaturalBaselineBOffset( + WritingMode aWM, BaselineSharingGroup aBaselineGroup, + BaselineExportContext) const { + if (StyleDisplay()->IsContainLayout()) { + return Nothing{}; + } + + RowGroupArray orderedRowGroups = 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) -> Maybe { + const nscoord rgBStart = + aRowGroup->GetLogicalNormalRect(aWM, containerSize).BStart(aWM); + const nscoord rowBStart = + aRow->GetLogicalNormalRect(aWM, aRowGroup->GetSize()).BStart(aWM); + return aRow->GetRowBaseline(aWM).map( + [rgBStart, rowBStart](nscoord aBaseline) { + return rgBStart + rowBStart + aBaseline; + }); + }; + if (aBaselineGroup == BaselineSharingGroup::First) { + for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; + nsTableRowFrame* row = rgFrame->GetFirstRow(); + if (row) { + return TableBaseline(rgFrame, row); + } + } + } else { + for (uint32_t rgIndex = orderedRowGroups.Length(); rgIndex-- > 0;) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; + nsTableRowFrame* row = rgFrame->GetLastRow(); + if (row) { + return TableBaseline(rgFrame, row).map([this, aWM](nscoord aBaseline) { + return BSize(aWM) - aBaseline; + }); + } + } + } + return Nothing{}; +} + +/* ----- global methods ----- */ + +nsTableFrame* NS_NewTableFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + return new (aPresShell) nsTableFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame) + +nsTableFrame* nsTableFrame::GetTableFrame(nsIFrame* aFrame) { + for (nsIFrame* ancestor = aFrame->GetParent(); ancestor; + ancestor = ancestor->GetParent()) { + if (ancestor->IsTableFrame()) { + return static_cast(ancestor); + } + } + MOZ_CRASH("unable to find table parent"); + return nullptr; +} + +bool nsTableFrame::IsAutoBSize(WritingMode aWM) { + const auto& bsize = StylePosition()->BSize(aWM); + if (bsize.IsAuto()) { + return true; + } + return bsize.ConvertsToPercentage() && bsize.ToPercentage() <= 0.0f; +} + +nscoord nsTableFrame::CalcBorderBoxBSize(const ReflowInput& aReflowInput, + const LogicalMargin& aBorderPadding, + nscoord aIntrinsicBorderBoxBSize) { + WritingMode wm = aReflowInput.GetWritingMode(); + nscoord bSize = aReflowInput.ComputedBSize(); + nscoord bp = aBorderPadding.BStartEnd(wm); + if (bSize == NS_UNCONSTRAINEDSIZE) { + if (aIntrinsicBorderBoxBSize == NS_UNCONSTRAINEDSIZE) { + return NS_UNCONSTRAINEDSIZE; + } + bSize = std::max(0, aIntrinsicBorderBoxBSize - bp); + } + return aReflowInput.ApplyMinMaxBSize(bSize) + bp; +} + +bool nsTableFrame::IsAutoLayout() { + if (StyleTable()->mLayoutStrategy == StyleTableLayout::Auto) return true; + // a fixed-layout inline-table must have a inline size + // and tables with inline size set to 'max-content' must be + // auto-layout (at least as long as + // FixedTableLayoutStrategy::GetPrefISize returns nscoord_MAX) + const auto& iSize = StylePosition()->ISize(GetWritingMode()); + return iSize.IsAuto() || iSize.IsMaxContent(); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"Table"_ns, aResult); +} +#endif + +// Find the closet sibling before aPriorChildFrame (including aPriorChildFrame) +// that is of type aChildType +nsIFrame* nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame, + nsIFrame* aPriorChildFrame, + LayoutFrameType aChildType) { + nsIFrame* result = nullptr; + if (!aPriorChildFrame) { + return result; + } + if (aChildType == aPriorChildFrame->Type()) { + 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->Type()) { + 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) { + uint32_t colIndex = cellFrame->ColIndex(); + printf("cell(%u)=%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 (LayoutFrameType::TableColGroup == childFrame->Type()) { + 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 { + if (aColIndex == 0) { + return true; + } + // Since fixed-layout tables should not have their column sizes change + // as they load, we assume that all columns are significant. + auto* fif = static_cast(FirstInFlow()); + if (fif->LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed) { + return true; + } + nsTableCellMap* cellMap = fif->GetCellMap(); + if (!cellMap) { + return false; + } + if (cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0) { + return true; + } + // Check if we have a element with a non-zero definite inline size. + // Note: percentages and calc(%) are intentionally not considered. + if (const auto* col = fif->GetColFrame(aColIndex)) { + const auto& iSize = col->StylePosition()->ISize(GetWritingMode()); + if (iSize.ConvertsToLength() && iSize.ToLength() > 0) { + const auto& maxISize = col->StylePosition()->MaxISize(GetWritingMode()); + if (!maxISize.ConvertsToLength() || maxISize.ToLength() > 0) { + return true; + } + } + const auto& minISize = col->StylePosition()->MinISize(GetWritingMode()); + if (minISize.ConvertsToLength() && minISize.ToLength() > 0) { + return true; + } + } + return false; +} + +/******************************************************************************** + * 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) { + MOZ_ASSERT(IsBorderCollapse(), + "Why call this if we are not border-collapsed?"); +#ifdef DEBUG + VerifyDamageRect(aValue); +#endif + + SetNeedToCalcBCBorders(true); + SetNeedToCalcHasBCBorders(true); + // Get the property + TableBCData* value = GetOrCreateTableBCData(); + +#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() { + MOZ_ASSERT(IsBorderCollapse(), + "Why call this if we are not border-collapsed?"); + + SetNeedToCalcBCBorders(true); + SetNeedToCalcHasBCBorders(true); + + TableBCData* value = GetOrCreateTableBCData(); + 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 !! + StyleBorderStyle style; // border segment style, possible values are defined + // in nsStyleConsts.h as StyleBorderStyle::* + 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 = StyleBorderStyle::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); + + // 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; + nsTableFrame* mTableFirstInFlow; + int32_t mNumTableRows; + int32_t mNumTableCols; + TableBCData* 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), + mTableFirstInFlow(static_cast(aTableFrame->FirstInFlow())), + mNumTableRows(aTableFrame->GetRowCount()), + mNumTableCols(aTableFrame->GetColCount()), + mTableBCData(mTableFirstInFlow->GetTableBCData()), + mTableWM(aTableFrame->Style()), + mCurrentRowFrame(nullptr), + mCurrentColGroupFrame(nullptr), + mCurrentColFrame(nullptr) { + 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; + // We don't necessarily want to traverse all areas + // of the table - mArea(Start|End) specify the area to traverse. + // TODO(dshin): Should be not abuse `nsPoint` for this - See bug 1879847. + nsPoint mAreaStart; + nsPoint mAreaEnd; +}; + +BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame, + const TableArea& aDamageArea) + : mRowGroupStart(0), + mRowGroupEnd(0), + mCellMap(nullptr), + mTableFrame(aTableFrame), + mRowGroups(aTableFrame->OrderedRowGroups()), + mRowGroup(nullptr), + mPrevRow(nullptr), + mIsNewRow(false) { + 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; + + 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 = mTableFirstInFlow->GetColFrame(aColIndex); + if (!mStartCol) ABORT0(); + + mEndCol = mStartCol; + if (mColSpan > 1) { + nsTableColFrame* colFrame = + mTableFirstInFlow->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 + + // 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); +} + +#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, StyleBorderStyle* aStyle, + nscolor* aColor, BCPixelSize* aWidth = nullptr) { + MOZ_ASSERT(aFrame, "null frame"); + MOZ_ASSERT(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 ((StyleBorderStyle::None == *aStyle) || + (StyleBorderStyle::Hidden == *aStyle)) { + return; + } + *aColor = aFrame->Style()->GetVisitedDependentColor( + nsStyleBorder::BorderColorFieldFor(physicalSide)); + + if (aWidth) { + nscoord width = styleData->GetComputedBorderWidth(physicalSide); + *aWidth = aFrame->PresContext()->AppUnitsToDevPixels(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, StyleBorderStyle* aStyle, + nscolor* aColor) { + GetColorAndStyle(aFrame, aTableWM, aSide, aStyle, aColor); + if (StyleBorderStyle::Inset == *aStyle) { + *aStyle = StyleBorderStyle::Ridge; + } else if (StyleBorderStyle::Outset == *aStyle) { + *aStyle = StyleBorderStyle::Groove; + } +} + +class nsDelayedCalcBCBorders : public Runnable { + public: + explicit nsDelayedCalcBCBorders(nsIFrame* aFrame) + : mozilla::Runnable("nsDelayedCalcBCBorders"), mFrame(aFrame) {} + + NS_IMETHOD Run() override { + if (mFrame) { + nsTableFrame* tableFrame = static_cast(mFrame.GetFrame()); + if (tableFrame->NeedToCalcBCBorders()) { + tableFrame->CalcBCBorders(); + } + } + return NS_OK; + } + + private: + WeakFrame mFrame; +}; + +bool nsTableFrame::BCRecalcNeeded(ComputedStyle* aOldComputedStyle, + ComputedStyle* aNewComputedStyle) { + // Attention: the old ComputedStyle is the one we're forgetting, + // and hence possibly completely bogus for GetStyle* purposes. + // We use PeekStyleData instead. + + const nsStyleBorder* oldStyleData = aOldComputedStyle->StyleBorder(); + const nsStyleBorder* newStyleData = aNewComputedStyle->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); + nsresult rv = GetContent()->OwnerDoc()->Dispatch(evt.forget()); + return NS_SUCCEEDED(rv); + } + 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 (StyleBorderStyle::Hidden == aBorder1.style) { + firstDominates = !aIsCorner; + } else if (StyleBorderStyle::Hidden == aBorder2.style) { + firstDominates = aIsCorner; + } else if (aBorder1.width < aBorder2.width) { + firstDominates = false; + } else if (aBorder1.width == aBorder2.width) { + if (static_cast(aBorder1.style) < + static_cast(aBorder2.style)) { + firstDominates = false; + } else if (aBorder1.style == 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 (StyleBorderStyle::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 (StyleBorderStyle::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 (StyleBorderStyle::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 (StyleBorderStyle::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 (StyleBorderStyle::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); +} + +// Initial value indicating that BCCornerInfo's ownerStyle hasn't been set yet. +#define BORDER_STYLE_UNSET static_cast(255) + +struct BCCornerInfo { + BCCornerInfo() { + ownerColor = 0; + ownerWidth = subWidth = ownerElem = subSide = subElem = hasDashDot = + numSegs = bevel = 0; + ownerSide = eLogicalSideBStart; + ownerStyle = BORDER_STYLE_UNSET; + subStyle = StyleBorderStyle::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 + StyleBorderStyle subStyle; // border style of subElem + StyleBorderStyle ownerStyle; // border style of ownerElem + uint16_t ownerSide : 2; // LogicalSide (e.g eLogicalSideBStart, etc) of the + // border owning the corner relative to the corner + uint16_t + ownerElem : 4; // elem type (e.g. eTable, eGroup, etc) owning the corner + uint16_t subSide : 2; // side of border with subWidth relative to the corner + uint16_t subElem : 4; // elem type (e.g. eTable, eGroup, etc) of sub owner + uint16_t hasDashDot : 1; // does a dashed, dotted segment enter the corner, + // they cannot be beveled + uint16_t numSegs : 3; // number of segments entering corner + uint16_t bevel : 1; // is the corner beveled (uses the above two fields + // together with subWidth) + // 7 bits are unused +}; + +// Start a new border at this corner, going in the direction of a given side. +void BCCornerInfo::Set(mozilla::LogicalSide aSide, BCCellBorder aBorder) { + // FIXME bug 1508921: We mask 4-bit BCBorderOwner enum to 3 bits to preserve + // buggy behavior found by the frame_above_rules_all.html mochitest. + ownerElem = aBorder.owner & 0x7; + + ownerStyle = aBorder.style; + ownerWidth = aBorder.width; + ownerColor = aBorder.color; + ownerSide = aSide; + hasDashDot = 0; + numSegs = 0; + if (aBorder.width > 0) { + numSegs++; + hasDashDot = (StyleBorderStyle::Dashed == aBorder.style) || + (StyleBorderStyle::Dotted == aBorder.style); + } + bevel = 0; + subWidth = 0; + // the following will get set later + subSide = IsInline(aSide) ? eLogicalSideBStart : eLogicalSideIStart; + subElem = eTableOwner; + subStyle = StyleBorderStyle::Solid; +} + +// Add a new border going in the direction of a given side, and update the +// dominant border. +void BCCornerInfo::Update(mozilla::LogicalSide aSide, BCCellBorder aBorder) { + if (ownerStyle == BORDER_STYLE_UNSET) { + 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); + + bool existingWins = false; + 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 && ((StyleBorderStyle::Dashed == aBorder.style) || + (StyleBorderStyle::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); + + 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; + UniquePtr corners; +}; + +BCCorners::BCCorners(int32_t aNumCorners, int32_t aStartIndex) { + NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error"); + startIndex = aStartIndex; + endIndex = aStartIndex + aNumCorners - 1; + corners = MakeUnique(aNumCorners); +} + +struct BCCellBorders { + BCCellBorders(int32_t aNumBorders, int32_t aStartIndex); + + 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; + UniquePtr borders; +}; + +BCCellBorders::BCCellBorders(int32_t aNumBorders, int32_t aStartIndex) { + NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error"); + startIndex = aStartIndex; + endIndex = aStartIndex + aNumBorders - 1; + borders = MakeUnique(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 = OrderedRowGroups(); + + // 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; + int32_t iterEndY; + 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; + } else { + // the damage area does not overlap the row group + continue; + } + NS_ASSERTION(iterStartY >= 0 && iterEndY >= 0, + "table index values are expected to be nonnegative"); + 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::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(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(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(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(half, mStartCol->GetIStartBorderWidth())); + } +} + +void BCMapCellInfo::SetTableBEndBorderWidth(BCPixelSize aWidth) { + mTableBCData->mBEndBorderWidth = + std::max(mTableBCData->mBEndBorderWidth, aWidth); +} + +void BCMapCellInfo::SetColumn(int32_t aColX) { + mCurrentColFrame = mTableFirstInFlow->GetColFrame(aColX); + 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); +} + +// Calculate border information for border-collapsed tables. +// Because borders of table/row/cell, etc merge into one, we need to +// determine which border dominates at each cell. In addition, corner-specific +// information, e.g. bevelling, is computed as well. +// +// Here is the order for storing border edges in the cell map as a cell is +// processed. +// +// For each cell, at least 4 edges are processed: +// * There are colspan * N block-start and block-end edges. +// * There are rowspan * N inline-start and inline-end edges. +// +// 1) If the cell being processed is at the block-start of the table, store the +// block-start edge. +// 2) If the cell being processed is at the inline-start of the table, store +// the +// inline-start edge. +// 3) Store the inline-end edge. +// 4) Store the block-end edge. +// +// These steps are traced by calls to `SetBCBorderEdge`. +// +// Corners are indexed by columns only, to avoid allocating a full row * col +// array of `BCCornerInfo`. This trades off memory allocation versus moving +// previous corner information around. +// +// For each cell: +// 1) If the cell is at the block-start of the table, but not at the +// inline-start of the table, store its block-start inline-start corner. +// +// 2) If the cell is at the inline-start of the table, store the block-start +// inline-start corner. +// +// 3) If the cell is at the block-start inline-end of the table, or not at the +// block-start of the table, store the block-start inline-end corner. +// +// 4) If the cell is at the block-end inline-end of the table, store the +// block-end inline-end corner. +// +// 5) If the cell is at the block-end of the table, store the block-end +// inline-start. +// +// Visually, it looks like this: +// +// 2--1--1--1--1--1--3 +// | | | | | | | +// 2--3--3--3--3--3--3 +// | | | | | | | +// 2--3--3--3--3--3--3 +// | | | | | | | +// 5--5--5--5--5--5--4 +// +// For rowspan/colspan cells, the latest border information is propagated +// along its "corners". +// +// These steps are traced by calls to `SetBCBorderCorner`. +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 + TableBCData* propData = GetTableBCData(); + if (!propData) ABORT0(); + + TableArea damageArea(propData->mDamageArea); + // See documentation for why we do this. + ExpandBCDamageArea(damageArea); + + // We accumulate border widths as we process the cells, so we need + // to reset it once in the beginning. + bool tableBorderReset[4]; + for (uint32_t sideX = 0; sideX < ArrayLength(tableBorderReset); sideX++) { + tableBorderReset[sideX] = false; + } + + // Storage for block-direction borders from the previous row, indexed by + // columns. + BCCellBorders lastBlockDirBorders(damageArea.ColCount() + 1, + damageArea.StartCol()); + if (!lastBlockDirBorders.borders) ABORT0(); + // Inline direction border at block start of the table, computed by the + // previous cell. Unused afterwards. + Maybe firstRowBStartEdgeBorder; + BCCellBorder lastBEndBorder; + // Storage for inline-direction borders from previous cells, indexed by + // columns. + // TODO(dshin): Why ColCount + 1? Number of inline segments should match + // column count exactly, unlike block direction segments... + BCCellBorders lastBEndBorders(damageArea.ColCount() + 1, + damageArea.StartCol()); + if (!lastBEndBorders.borders) ABORT0(); + + BCMapCellInfo info(this); + + // Block-start corners of the cell being traversed, indexed by columns. + BCCorners bStartCorners(damageArea.ColCount() + 1, damageArea.StartCol()); + if (!bStartCorners.corners) ABORT0(); + // Block-end corners of the cell being traversed, indexed by columns. + // Note that when a new row starts, they become block-start corners and used + // as such, until cleared with `Set`. + 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 firstRowBStartEdgeBorder, lastBEndBorder need to be reset + if (iter.IsNewRow()) { + if (info.mRowIndex == 0) { + BCCellBorder border; + border.Reset(info.mRowIndex, info.mRowSpan); + firstRowBStartEdgeBorder = Some(border); + } else { + firstRowBStartEdgeBorder = Nothing{}; + } + lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); + } else if (info.mColIndex > damageArea.StartCol()) { + lastBEndBorder = lastBEndBorders[info.mColIndex - 1]; + 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); + BCCellBorder currentBorder = info.GetBStartEdgeBorder(); + BCCornerInfo& bStartIStartCorner = bStartCorners[colIdx]; + // Mark inline-end direction border from this corner. + if (0 == colIdx) { + bStartIStartCorner.Set(eLogicalSideIEnd, currentBorder); + } else { + bStartIStartCorner.Update(eLogicalSideIEnd, currentBorder); + tableCellMap->SetBCBorderCorner( + eLogicalCornerBStartIStart, *iter.mCellMap, 0, 0, colIdx, + LogicalSide(bStartIStartCorner.ownerSide), + bStartIStartCorner.subWidth, bStartIStartCorner.bevel); + } + // Above, we set the corner `colIndex` column as having a border towards + // inline-end, heading towards the next column. Vice versa is also true, + // where the next column has a border heading towards this column. + bStartCorners[colIdx + 1].Set(eLogicalSideIStart, currentBorder); + MOZ_ASSERT(firstRowBStartEdgeBorder, + "Inline start border tracking not set?"); + // update firstRowBStartEdgeBorder and see if a new segment starts + bool startSeg = + firstRowBStartEdgeBorder + ? SetInlineDirBorder(currentBorder, bStartIStartCorner, + firstRowBStartEdgeBorder.ref()) + : true; + // store the border segment in the cell map + tableCellMap->SetBCBorderEdge(eLogicalSideBStart, *iter.mCellMap, 0, 0, + colIdx, 1, currentBorder.owner, + currentBorder.width, startSeg); + + // Set border width at block-start (table-wide and for the cell), but + // only if it's the largest we've encountered. + info.SetTableBStartBorderWidth(currentBorder.width); + info.SetBStartBorderWidths(currentBorder.width); + } + } 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); + BCCellBorder currentBorder = info.GetIStartEdgeBorder(); + BCCornerInfo& bStartIStartCorner = + (0 == rowB) ? bStartCorners[0] : bEndCorners[0]; + bStartIStartCorner.Update(eLogicalSideBEnd, currentBorder); + tableCellMap->SetBCBorderCorner( + eLogicalCornerBStartIStart, *iter.mCellMap, iter.mRowGroupStart, + rowB, 0, LogicalSide(bStartIStartCorner.ownerSide), + bStartIStartCorner.subWidth, bStartIStartCorner.bevel); + bEndCorners[0].Set(eLogicalSideBStart, currentBorder); + + // update lastBlockDirBorders and see if a new segment starts + bool 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); + // Set border width at inline-start (table-wide and for the cell), but + // only if it's the largest we've encountered. + info.SetTableIStartBorderWidth(rowB, currentBorder.width); + info.SetIStartBorderWidths(currentBorder.width); + } + } + + // 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); + BCCellBorder currentBorder = info.GetIEndEdgeBorder(); + // Update/store the bStart-iEnd & bEnd-iEnd corners. Note that we + // overwrite all corner information to the end of the column span. + BCCornerInfo& bStartIEndCorner = + (0 == rowB) ? bStartCorners[info.GetCellEndColIndex() + 1] + : bEndCorners[info.GetCellEndColIndex() + 1]; + bStartIEndCorner.Update(eLogicalSideBEnd, currentBorder); + tableCellMap->SetBCBorderCorner( + eLogicalCornerBStartIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB, + info.GetCellEndColIndex(), LogicalSide(bStartIEndCorner.ownerSide), + bStartIEndCorner.subWidth, bStartIEndCorner.bevel); + BCCornerInfo& bEndIEndCorner = + bEndCorners[info.GetCellEndColIndex() + 1]; + bEndIEndCorner.Set(eLogicalSideBStart, currentBorder); + tableCellMap->SetBCBorderCorner( + eLogicalCornerBEndIEnd, *iter.mCellMap, iter.mRowGroupStart, rowB, + info.GetCellEndColIndex(), LogicalSide(bEndIEndCorner.ownerSide), + bEndIEndCorner.subWidth, bEndIEndCorner.bevel); + // update lastBlockDirBorders and see if a new segment starts + bool 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); + // Set border width at inline-end (table-wide and for the cell), but + // only if it's the largest we've encountered. + info.SetTableIEndBorderWidth(rowB, currentBorder.width); + info.SetIEndBorderWidths(currentBorder.width); + } + } else { + // Cell entries, but not on the block-end side of the entire table. + int32_t segLength = 0; + BCMapCellInfo ajaInfo(this); + BCMapCellInfo priorAjaInfo(this); + for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex(); + rowB += segLength) { + // Grab the cell adjacent to our inline-end. + iter.PeekIEnd(info, rowB, ajaInfo); + BCCellBorder currentBorder = info.GetIEndInternalBorder(); + BCCellBorder 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 + bool 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); + } + // Does the block-start inline-end corner hit the inline-end adjacent + // cell that wouldn't have an inline border? e.g. + // + // o-----------o---------------o + // | | | + // o-----------x Adjacent cell o + // | This Cell | (rowspan) | + // o-----------o---------------o + bool hitsSpanOnIEnd = (rowB > ajaInfo.mRowIndex) && + (rowB < ajaInfo.mRowIndex + ajaInfo.mRowSpan); + BCCornerInfo* bStartIEndCorner = + ((0 == rowB) || hitsSpanOnIEnd) + ? &bStartCorners[info.GetCellEndColIndex() + 1] + : &bEndCorners[info.GetCellEndColIndex() + + 1]; // From previous row. + bStartIEndCorner->Update(eLogicalSideBEnd, currentBorder); + // If this is a rowspan, need to consider if this "corner" is generating + // an inline segment for the adjacent cell. e.g. + // + // o--------------o----o + // | | | + // o x----o + // | (This "row") | | + // o--------------o----o + if (rowB != info.mRowIndex) { + currentBorder = priorAjaInfo.GetBEndInternalBorder(); + BCCellBorder adjacentBorder = ajaInfo.GetBStartInternalBorder(); + currentBorder = CompareBorders(!CELL_CORNER, currentBorder, + adjacentBorder, INLINE_DIR); + bStartIEndCorner->Update(eLogicalSideIEnd, currentBorder); + } + // Check that the spanned area is inside of the invalidation area + if (info.GetCellEndColIndex() < damageArea.EndCol() && + rowB >= damageArea.StartRow()) { + if (0 != rowB) { + // Ok, actually store the information + tableCellMap->SetBCBorderCorner( + eLogicalCornerBStartIEnd, *iter.mCellMap, iter.mRowGroupStart, + rowB, info.GetCellEndColIndex(), + LogicalSide(bStartIEndCorner->ownerSide), + bStartIEndCorner->subWidth, bStartIEndCorner->bevel); + } + // Propagate this segment down the rowspan + for (int32_t rX = rowB + 1; rX < rowB + segLength; rX++) { + tableCellMap->SetBCBorderCorner( + eLogicalCornerBEndIEnd, *iter.mCellMap, iter.mRowGroupStart, rX, + info.GetCellEndColIndex(), + LogicalSide(bStartIEndCorner->ownerSide), + bStartIEndCorner->subWidth, false); + } + } + hitsSpanOnIEnd = + (rowB + segLength < ajaInfo.mRowIndex + ajaInfo.mRowSpan); + BCCornerInfo& bEndIEndCorner = + (hitsSpanOnIEnd) ? bStartCorners[info.GetCellEndColIndex() + 1] + : bEndCorners[info.GetCellEndColIndex() + 1]; + bEndIEndCorner.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); + BCCellBorder currentBorder = info.GetBEndEdgeBorder(); + BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx]; + bEndIStartCorner.Update(eLogicalSideIEnd, currentBorder); + tableCellMap->SetBCBorderCorner( + eLogicalCornerBEndIStart, *iter.mCellMap, iter.mRowGroupStart, + info.GetCellEndRowIndex(), colIdx, + LogicalSide(bEndIStartCorner.ownerSide), bEndIStartCorner.subWidth, + bEndIStartCorner.bevel); + BCCornerInfo& bEndIEndCorner = bEndCorners[colIdx + 1]; + bEndIEndCorner.Update(eLogicalSideIStart, currentBorder); + // Store the block-end inline-end corner if it also is the block-end + // inline-end of the overall table. + if (info.mNumTableCols == colIdx + 1) { + tableCellMap->SetBCBorderCorner( + eLogicalCornerBEndIEnd, *iter.mCellMap, iter.mRowGroupStart, + info.GetCellEndRowIndex(), colIdx, + LogicalSide(bEndIEndCorner.ownerSide), bEndIEndCorner.subWidth, + bEndIEndCorner.bevel, true); + } + // update lastBEndBorder and see if a new segment starts + bool startSeg = + SetInlineDirBorder(currentBorder, bEndIStartCorner, 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; + + // Set border width at block-end (table-wide and for the cell), but + // only if it's the largest we've encountered. + info.SetBEndBorderWidths(currentBorder.width); + info.SetTableBEndBorderWidth(currentBorder.width); + } + } else { + int32_t segLength = 0; + BCMapCellInfo ajaInfo(this); + for (int32_t colIdx = info.mColIndex; colIdx <= info.GetCellEndColIndex(); + colIdx += segLength) { + // Grab the cell adjacent to our block-end. + iter.PeekBEnd(info, colIdx, ajaInfo); + BCCellBorder currentBorder = info.GetBEndInternalBorder(); + BCCellBorder 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); + + BCCornerInfo& bEndIStartCorner = bEndCorners[colIdx]; + // e.g. + // o--o----------o + // | | This col | + // o--x----------o + // | Adjacent | + // o--o----------o + 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] = bEndIStartCorner; + bEndIStartCorner.Set(eLogicalSideIEnd, currentBorder); + update = false; + } + } + if (update) { + bEndIStartCorner.Update(eLogicalSideIEnd, currentBorder); + } + // Check that the spanned area is inside of the invalidation area + if (info.GetCellEndRowIndex() < damageArea.EndRow() && + colIdx >= damageArea.StartCol()) { + if (hitsSpanBelow) { + tableCellMap->SetBCBorderCorner( + eLogicalCornerBEndIStart, *iter.mCellMap, iter.mRowGroupStart, + info.GetCellEndRowIndex(), colIdx, + LogicalSide(bEndIStartCorner.ownerSide), + bEndIStartCorner.subWidth, bEndIStartCorner.bevel); + } + // Propagate this segment down the colspan + for (int32_t c = colIdx + 1; c < colIdx + segLength; c++) { + BCCornerInfo& corner = bEndCorners[c]; + corner.Set(eLogicalSideIEnd, currentBorder); + tableCellMap->SetBCBorderCorner( + eLogicalCornerBEndIStart, *iter.mCellMap, iter.mRowGroupStart, + info.GetCellEndRowIndex(), c, LogicalSide(corner.ownerSide), + corner.subWidth, false); + } + } + // update lastBEndBorders and see if a new segment starts + bool startSeg = + SetInlineDirBorder(currentBorder, bEndIStartCorner, 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& bEndIEndCorner = bEndCorners[colIdx + segLength]; + bEndIEndCorner.Update(eLogicalSideIStart, currentBorder); + } + } + // o------o------o + // | c1 | | + // o------o c2 o + // | c3 | | + // o--e1--o--e2--o + // We normally join edges of successive block-end inline segments by + // consulting the previous segment; however, cell c2's block-end inline + // segment e2 is processed before e1, so we need to process such joins + // out-of-band here, when we're processing c3. + const auto nextColIndex = info.GetCellEndColIndex() + 1; + if ((info.mNumTableCols != nextColIndex) && + (lastBEndBorders[nextColIndex].rowSpan > 1) && + (lastBEndBorders[nextColIndex].rowIndex == + info.GetCellEndRowIndex() + 1)) { + BCCornerInfo& corner = bEndCorners[nextColIndex]; + 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, iter.mRowGroupStart, + info.GetCellEndRowIndex(), nextColIndex); + } + } + } + } + } // 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 BCBorderParameters { + StyleBorderStyle mBorderStyle; + nscolor mBorderColor; + nsRect mBorderRect; + int32_t mAppUnitsPerDevPixel; + mozilla::Side mStartBevelSide; + nscoord mStartBevelOffset; + mozilla::Side mEndBevelSide; + nscoord mEndBevelOffset; + bool mBackfaceIsVisible; + + bool NeedToBevel() const { + if (!mStartBevelOffset && !mEndBevelOffset) { + return false; + } + + if (mBorderStyle == StyleBorderStyle::Dashed || + mBorderStyle == StyleBorderStyle::Dotted) { + return false; + } + + return true; + } +}; + +struct BCBlockDirSeg { + BCBlockDirSeg(); + + void Start(BCPaintBorderIterator& aIter, BCBorderOwner aBorderOwner, + BCPixelSize aBlockSegISize, BCPixelSize aInlineSegBSize, + Maybe aEmptyRowEndSize); + + void Initialize(BCPaintBorderIterator& aIter); + void GetBEndCorner(BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize); + + Maybe BuildBorderParameters(BCPaintBorderIterator& aIter, + BCPixelSize aInlineSegBSize); + void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget, + BCPixelSize aInlineSegBSize); + void CreateWebRenderCommands(BCPaintBorderIterator& aIter, + BCPixelSize aInlineSegBSize, + wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, + const nsPoint& aPt); + 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); + Maybe BuildBorderParameters(BCPaintBorderIterator& aIter); + void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget); + void CreateWebRenderCommands(BCPaintBorderIterator& aIter, + wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, + const nsPoint& aPt); + + 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 +}; + +struct BCPaintData { + explicit BCPaintData(DrawTarget& aDrawTarget) : mDrawTarget(aDrawTarget) {} + + DrawTarget& mDrawTarget; +}; + +struct BCCreateWebRenderCommandsData { + BCCreateWebRenderCommandsData(wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, + const nsPoint& aOffsetToReferenceFrame) + : mBuilder(aBuilder), + mSc(aSc), + mOffsetToReferenceFrame(aOffsetToReferenceFrame) {} + + wr::DisplayListBuilder& mBuilder; + const layers::StackingContextHelper& mSc; + const nsPoint& mOffsetToReferenceFrame; +}; + +struct BCPaintBorderAction { + explicit BCPaintBorderAction(DrawTarget& aDrawTarget) + : mMode(Mode::Paint), mPaintData(aDrawTarget) {} + + BCPaintBorderAction(wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, + const nsPoint& aOffsetToReferenceFrame) + : mMode(Mode::CreateWebRenderCommands), + mCreateWebRenderCommandsData(aBuilder, aSc, aOffsetToReferenceFrame) {} + + ~BCPaintBorderAction() { + // mCreateWebRenderCommandsData is in a union which means the destructor + // wouldn't be called when BCPaintBorderAction get destroyed. So call the + // destructor here explicitly. + if (mMode == Mode::CreateWebRenderCommands) { + mCreateWebRenderCommandsData.~BCCreateWebRenderCommandsData(); + } + } + + enum class Mode { + Paint, + CreateWebRenderCommands, + }; + + Mode mMode; + + union { + BCPaintData mPaintData; + BCCreateWebRenderCommandsData mCreateWebRenderCommandsData; + }; +}; + +// 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); + 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 AccumulateOrDoActionInlineDirSegment(BCPaintBorderAction& aAction); + void AccumulateOrDoActionBlockDirSegment(BCPaintBorderAction& aAction); + void ResetVerInfo(); + void StoreColumnWidth(int32_t aIndex); + bool BlockDirSegmentOwnsCorner(); + + nsTableFrame* mTable; + nsTableFrame* mTableFirstInFlow; + nsTableCellMap* mTableCellMap; + nsCellMap* mCellMap; + WritingMode mTableWM; + 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 + // 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. + UniquePtr mBlockDirInfo; + 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()), + mCellMap(nullptr), + mTableWM(aTable->Style()), + mRowGroups(aTable->OrderedRowGroups()), + mPrevRg(nullptr), + mRg(nullptr), + mIsRepeatedHeader(false), + mIsRepeatedFooter(false), + mStartRg(nullptr), + mRgIndex(0), + mFifRgFirstRowIndex(0), + mRgFirstRowIndex(0), + mRgLastRowIndex(0), + mColIndex(0), + mRowIndex(0), + mIsNewRow(false), + mAtEnd(false), + mPrevRow(nullptr), + mRow(nullptr), + mStartRow(nullptr), + mPrevCell(nullptr), + mCell(nullptr), + mPrevCellData(nullptr), + mCellData(nullptr), + mBCData(nullptr), + mInitialOffsetI(0), + mNextOffsetB(0), + mPrevInlineSegBSize(0) { + MOZ_ASSERT(mTable->IsBorderCollapse(), + "Why are we here if the table is not border-collapsed?"); + + const LogicalMargin bp = mTable->GetIncludedOuterBCBorder(mTableWM); + // block position of first row in damage area + mInitialOffsetB = mTable->GetPrevInFlow() ? 0 : bp.BStart(mTableWM); + mNumTableRows = mTable->GetRowCount(); + mNumTableCols = mTable->GetColCount(); + + // initialize to a non existing index + mRepeatedHeaderRowIndex = -99; +} + +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; + nsPresContext* presContext = mTable->PresContext(); + 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 + : presContext->DevPixelsToAppUnits( + 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 + : presContext->DevPixelsToAppUnits( + 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 bp = mTable->GetIncludedOuterBCBorder(mTableWM); + + // inline position of first col in damage area + mInitialOffsetI = bp.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 = presContext->DevPixelsToAppUnits( + 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 = + presContext->DevPixelsToAppUnits(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 = MakeUnique(mDamageArea.ColCount() + 1); + 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 { + // We should have set mCellMap during SetNewRowGroup, but if we failed to + // find the appropriate map there, let's just give up. + // Bailing out here may leave us with some missing borders, but seems + // preferable to crashing. (Bug 1442018) + if (MOZ_UNLIKELY(!mCellMap)) { + ABORT0(); + } + 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 (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(nsPresContext* aPresContext, + 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 aPresContext->DevPixelsToAppUnits(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(nsPresContext* aPresContext, + 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 aPresContext->DevPixelsToAppUnits(offset); +} + +BCBlockDirSeg::BCBlockDirSeg() + : mFirstRowGroup(nullptr), + mFirstRow(nullptr), + mBEndInlineSegBSize(0), + mBEndOffset(0), + mIsBEndBevel(false) { + 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, + Maybe aEmptyRowEndBSize) { + 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); + nsPresContext* presContext = aIter.mTable->PresContext(); + nscoord offset = CalcVerCornerOffset(presContext, ownerSide, cornerSubWidth, + maxInlineSegBSize, true, bStartBevel); + + mBStartBevelOffset = + bStartBevel ? presContext->DevPixelsToAppUnits(maxInlineSegBSize) : 0; + // XXX this assumes that only corners where 2 segments join can be beveled + mBStartBevelSide = + (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart; + if (aEmptyRowEndBSize && *aEmptyRowEndBSize < offset) { + // This segment is starting from an empty row. This will require the the + // starting segment to overlap with the previously drawn segment, unless the + // empty row's size clears the overlap. + mOffsetB += *aEmptyRowEndBSize; + } else { + 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(aIter.mTable->PresContext(), ownerSide, + cornerSubWidth, mBEndInlineSegBSize, false, + mIsBEndBevel); + mLength += mBEndOffset; +} + +Maybe BCBlockDirSeg::BuildBorderParameters( + BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize) { + BCBorderParameters result; + + // 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) ABORT1(Nothing()); + nsTableCellFrame* cell = mFirstCell; // ??? + nsIFrame* owner = nullptr; + result.mBorderStyle = StyleBorderStyle::Solid; + result.mBorderColor = 0xFFFFFFFF; + result.mBackfaceIsVisible = true; + + // All the tables frames have the same presContext, so we just use any one + // that exists here: + nsPresContext* presContext = aIter.mTable->PresContext(); + result.mAppUnitsPerDevPixel = 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; + } + [[fallthrough]]; + case eColGroupOwner: + if (col) { + owner = col->GetParent(); + } + break; + case eAjaColOwner: + side = eLogicalSideIEnd; + if (!aIter.IsTableIEndMost() && (relColIndex > 0)) { + col = aIter.mBlockDirInfo[relColIndex - 1].mCol; + } + [[fallthrough]]; + case eColOwner: + owner = col; + break; + case eAjaRowGroupOwner: + NS_ERROR("a neighboring rowgroup can never own a vertical border"); + [[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"); + [[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; + [[fallthrough]]; + case eCellOwner: + owner = cell; + break; + } + if (owner) { + ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle, + &result.mBorderColor); + result.mBackfaceIsVisible = !owner->BackfaceIsHidden(); + } + BCPixelSize smallHalf, largeHalf; + DivideBCBorderSize(mWidth, smallHalf, largeHalf); + LogicalRect segRect( + aIter.mTableWM, mOffsetI - presContext->DevPixelsToAppUnits(largeHalf), + mOffsetB, presContext->DevPixelsToAppUnits(mWidth), mLength); + nscoord bEndBevelOffset = + (mIsBEndBevel) ? presContext->DevPixelsToAppUnits(mBEndInlineSegBSize) + : 0; + LogicalSide bEndBevelSide = + (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart; + + // Convert logical to physical sides/coordinates for DrawTableBorderSegment. + + result.mBorderRect = + 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). + + result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mBStartBevelSide); + result.mEndBevelSide = aIter.mTableWM.PhysicalSide(bEndBevelSide); + result.mStartBevelOffset = mBStartBevelOffset; + result.mEndBevelOffset = 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()) { + std::swap(result.mStartBevelSide, result.mEndBevelSide); + std::swap(result.mStartBevelOffset, result.mEndBevelOffset); + } + + return Some(result); +} + +/** + * 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) { + Maybe param = + BuildBorderParameters(aIter, aInlineSegBSize); + if (param.isNothing()) { + return; + } + + nsCSSRendering::DrawTableBorderSegment( + aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect, + param->mAppUnitsPerDevPixel, param->mStartBevelSide, + param->mStartBevelOffset, param->mEndBevelSide, param->mEndBevelOffset); +} + +// Pushes a border bevel triangle and substracts the relevant rectangle from +// aRect, which, after all the bevels, will end up being a solid segment rect. +static void AdjustAndPushBevel(wr::DisplayListBuilder& aBuilder, + wr::LayoutRect& aRect, nscolor aColor, + const nsCSSRendering::Bevel& aBevel, + int32_t aAppUnitsPerDevPixel, + bool aBackfaceIsVisible, bool aIsStart) { + if (!aBevel.mOffset) { + return; + } + + const auto kTransparent = wr::ToColorF(gfx::DeviceColor(0., 0., 0., 0.)); + const bool horizontal = + aBevel.mSide == eSideTop || aBevel.mSide == eSideBottom; + + // Crappy CSS triangle as known by every web developer ever :) + Float offset = NSAppUnitsToFloatPixels(aBevel.mOffset, aAppUnitsPerDevPixel); + wr::LayoutRect bevelRect = aRect; + wr::BorderSide bevelBorder[4]; + for (const auto i : mozilla::AllPhysicalSides()) { + bevelBorder[i] = + wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid); + } + + // We're creating a half-transparent triangle using the border primitive. + // + // Classic web-dev trick, with a gotcha: we use a single corner to avoid + // seams and rounding errors. + // + // Classic web-dev trick :P + auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0); + bevelBorder[aBevel.mSide].color = kTransparent; + if (aIsStart) { + if (horizontal) { + bevelBorder[eSideLeft].color = kTransparent; + borderWidths.left = offset; + } else { + bevelBorder[eSideTop].color = kTransparent; + borderWidths.top = offset; + } + } else { + if (horizontal) { + bevelBorder[eSideRight].color = kTransparent; + borderWidths.right = offset; + } else { + bevelBorder[eSideBottom].color = kTransparent; + borderWidths.bottom = offset; + } + } + + if (horizontal) { + if (aIsStart) { + aRect.min.x += offset; + aRect.max.x += offset; + } else { + bevelRect.min.x += aRect.width() - offset; + bevelRect.max.x += aRect.width() - offset; + } + aRect.max.x -= offset; + bevelRect.max.y = bevelRect.min.y + aRect.height(); + bevelRect.max.x = bevelRect.min.x + offset; + if (aBevel.mSide == eSideTop) { + borderWidths.bottom = aRect.height(); + } else { + borderWidths.top = aRect.height(); + } + } else { + if (aIsStart) { + aRect.min.y += offset; + aRect.max.y += offset; + } else { + bevelRect.min.y += aRect.height() - offset; + bevelRect.max.y += aRect.height() - offset; + } + aRect.max.y -= offset; + bevelRect.max.x = bevelRect.min.x + aRect.width(); + bevelRect.max.y = bevelRect.min.y + offset; + if (aBevel.mSide == eSideLeft) { + borderWidths.right = aRect.width(); + } else { + borderWidths.left = aRect.width(); + } + } + + Range wrsides(bevelBorder, 4); + // It's important to _not_ anti-alias the bevel, because otherwise we wouldn't + // be able bevel to sides of the same color without bleeding in the middle. + aBuilder.PushBorder(bevelRect, bevelRect, aBackfaceIsVisible, borderWidths, + wrsides, wr::EmptyBorderRadius(), + wr::AntialiasBorder::No); +} + +static void CreateWRCommandsForBeveledBorder( + const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, const nsPoint& aOffset) { + MOZ_ASSERT(aBorderParams.NeedToBevel()); + + AutoTArray segments; + nsCSSRendering::GetTableBorderSolidSegments( + segments, aBorderParams.mBorderStyle, aBorderParams.mBorderColor, + aBorderParams.mBorderRect, aBorderParams.mAppUnitsPerDevPixel, + aBorderParams.mStartBevelSide, aBorderParams.mStartBevelOffset, + aBorderParams.mEndBevelSide, aBorderParams.mEndBevelOffset); + + for (const auto& segment : segments) { + auto rect = LayoutDeviceRect::FromUnknownRect(NSRectToRect( + segment.mRect + aOffset, aBorderParams.mAppUnitsPerDevPixel)); + auto r = wr::ToLayoutRect(rect); + auto color = wr::ToColorF(ToDeviceColor(segment.mColor)); + + // Adjust for the start bevel if needed. + AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mStartBevel, + aBorderParams.mAppUnitsPerDevPixel, + aBorderParams.mBackfaceIsVisible, true); + + AdjustAndPushBevel(aBuilder, r, segment.mColor, segment.mEndBevel, + aBorderParams.mAppUnitsPerDevPixel, + aBorderParams.mBackfaceIsVisible, false); + + aBuilder.PushRect(r, r, aBorderParams.mBackfaceIsVisible, false, false, + color); + } +} + +static void CreateWRCommandsForBorderSegment( + const BCBorderParameters& aBorderParams, wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, const nsPoint& aOffset) { + if (aBorderParams.NeedToBevel()) { + CreateWRCommandsForBeveledBorder(aBorderParams, aBuilder, aSc, aOffset); + return; + } + + auto borderRect = LayoutDeviceRect::FromUnknownRect(NSRectToRect( + aBorderParams.mBorderRect + aOffset, aBorderParams.mAppUnitsPerDevPixel)); + + wr::LayoutRect r = wr::ToLayoutRect(borderRect); + wr::BorderSide wrSide[4]; + for (const auto i : mozilla::AllPhysicalSides()) { + wrSide[i] = wr::ToBorderSide(ToDeviceColor(aBorderParams.mBorderColor), + StyleBorderStyle::None); + } + const bool horizontal = aBorderParams.mStartBevelSide == eSideTop || + aBorderParams.mStartBevelSide == eSideBottom; + auto borderWidth = horizontal ? r.height() : r.width(); + + // All border style is set to none except left side. So setting the widths of + // each side to width of rect is fine. + auto borderWidths = wr::ToBorderWidths(0, 0, 0, 0); + + wrSide[horizontal ? eSideTop : eSideLeft] = wr::ToBorderSide( + ToDeviceColor(aBorderParams.mBorderColor), aBorderParams.mBorderStyle); + + if (horizontal) { + borderWidths.top = borderWidth; + } else { + borderWidths.left = borderWidth; + } + + Range wrsides(wrSide, 4); + aBuilder.PushBorder(r, r, aBorderParams.mBackfaceIsVisible, borderWidths, + wrsides, wr::EmptyBorderRadius()); +} + +void BCBlockDirSeg::CreateWebRenderCommands( + BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize, + wr::DisplayListBuilder& aBuilder, const layers::StackingContextHelper& aSc, + const nsPoint& aOffset) { + Maybe param = + BuildBorderParameters(aIter, aInlineSegBSize); + if (param.isNothing()) { + return; + } + + CreateWRCommandsForBorderSegment(*param, aBuilder, aSc, aOffset); +} + +/** + * 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() + : mIsIEndBevel(false), + mIEndBevelOffset(0), + mIEndBevelSide(eLogicalSideBStart), + mEndOffset(0), + mOwner(eTableOwner) { + 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(aIter.mTable->PresContext(), 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); + nsPresContext* presContext = aIter.mTable->PresContext(); + mEndOffset = CalcHorCornerOffset(presContext, ownerSide, cornerSubWidth, + verWidth, false, mIsIEndBevel); + mLength += mEndOffset; + mIEndBevelOffset = + (mIsIEndBevel) ? presContext->DevPixelsToAppUnits(verWidth) : 0; + mIEndBevelSide = + (aIStartSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart; +} + +Maybe BCInlineDirSeg::BuildBorderParameters( + BCPaintBorderIterator& aIter) { + BCBorderParameters result; + + // get the border style, color and paint the segment + LogicalSide side = + aIter.IsDamageAreaBEndMost() ? eLogicalSideBEnd : eLogicalSideBStart; + nsIFrame* rg = aIter.mRg; + if (!rg) ABORT1(Nothing()); + nsIFrame* row = aIter.mRow; + if (!row) ABORT1(Nothing()); + nsIFrame* cell = mFirstCell; + nsIFrame* col; + nsIFrame* owner = nullptr; + result.mBackfaceIsVisible = true; + + // All the tables frames have the same presContext, so we just use any one + // that exists here: + nsPresContext* presContext = aIter.mTable->PresContext(); + result.mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + + result.mBorderStyle = StyleBorderStyle::Solid; + result.mBorderColor = 0xFFFFFFFF; + + switch (mOwner) { + case eTableOwner: + owner = aIter.mTable; + break; + case eAjaColGroupOwner: + NS_ERROR("neighboring colgroups can never own an inline-dir border"); + [[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) ABORT1(Nothing()); + owner = col->GetParent(); + break; + case eAjaColOwner: + NS_ERROR("neighboring column can never own an inline-dir border"); + [[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; + [[fallthrough]]; + case eRowGroupOwner: + owner = rg; + break; + case eAjaRowOwner: + side = eLogicalSideBEnd; + row = (aIter.IsTableBEndMost()) ? aIter.mRow : aIter.mPrevRow; + [[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; + [[fallthrough]]; + case eCellOwner: + owner = cell; + break; + } + if (owner) { + ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &result.mBorderStyle, + &result.mBorderColor); + result.mBackfaceIsVisible = !owner->BackfaceIsHidden(); + } + BCPixelSize smallHalf, largeHalf; + DivideBCBorderSize(mWidth, smallHalf, largeHalf); + LogicalRect segRect(aIter.mTableWM, mOffsetI, + mOffsetB - presContext->DevPixelsToAppUnits(largeHalf), + mLength, presContext->DevPixelsToAppUnits(mWidth)); + + // Convert logical to physical sides/coordinates for DrawTableBorderSegment. + result.mBorderRect = + segRect.GetPhysicalRect(aIter.mTableWM, aIter.mTable->GetSize()); + result.mStartBevelSide = aIter.mTableWM.PhysicalSide(mIStartBevelSide); + result.mEndBevelSide = aIter.mTableWM.PhysicalSide(mIEndBevelSide); + result.mStartBevelOffset = + presContext->DevPixelsToAppUnits(mIStartBevelOffset); + result.mEndBevelOffset = 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.IsBidiRTL()) { + std::swap(result.mStartBevelSide, result.mEndBevelSide); + std::swap(result.mStartBevelOffset, result.mEndBevelOffset); + } + + return Some(result); +} + +/** + * Paint the inline-dir segment + * @param aIter - iterator containing the structural information + * @param aDrawTarget - the draw target + */ +void BCInlineDirSeg::Paint(BCPaintBorderIterator& aIter, + DrawTarget& aDrawTarget) { + Maybe param = BuildBorderParameters(aIter); + if (param.isNothing()) { + return; + } + + nsCSSRendering::DrawTableBorderSegment( + aDrawTarget, param->mBorderStyle, param->mBorderColor, param->mBorderRect, + param->mAppUnitsPerDevPixel, param->mStartBevelSide, + param->mStartBevelOffset, param->mEndBevelSide, param->mEndBevelOffset); +} + +void BCInlineDirSeg::CreateWebRenderCommands( + BCPaintBorderIterator& aIter, wr::DisplayListBuilder& aBuilder, + const layers::StackingContextHelper& aSc, const nsPoint& aPt) { + Maybe param = BuildBorderParameters(aIter); + if (param.isNothing()) { + return; + } + + CreateWRCommandsForBorderSegment(*param, aBuilder, aSc, aPt); +} + +/** + * 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::AccumulateOrDoActionInlineDirSegment( + BCPaintBorderAction& aAction) { + 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) { + if (aAction.mMode == BCPaintBorderAction::Mode::Paint) { + mInlineSeg.Paint(*this, aAction.mPaintData.mDrawTarget); + } else { + MOZ_ASSERT(aAction.mMode == + BCPaintBorderAction::Mode::CreateWebRenderCommands); + mInlineSeg.CreateWebRenderCommands( + *this, aAction.mCreateWebRenderCommandsData.mBuilder, + aAction.mCreateWebRenderCommandsData.mSc, + aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame); + } + } + 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::AccumulateOrDoActionBlockDirSegment( + BCPaintBorderAction& aAction) { + 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, + Nothing{}); + } + + if (!IsDamageAreaBStartMost() && + (isSegStart || IsDamageAreaBEndMost() || IsAfterRepeatedHeader() || + StartRepeatedFooter())) { + Maybe emptyRowEndSize; + // paint the previous seg or the current one if IsDamageAreaBEndMost() + if (blockDirSeg.mLength > 0) { + blockDirSeg.GetBEndCorner(*this, inlineSegBSize); + if (blockDirSeg.mWidth > 0) { + if (aAction.mMode == BCPaintBorderAction::Mode::Paint) { + blockDirSeg.Paint(*this, aAction.mPaintData.mDrawTarget, + inlineSegBSize); + } else { + MOZ_ASSERT(aAction.mMode == + BCPaintBorderAction::Mode::CreateWebRenderCommands); + blockDirSeg.CreateWebRenderCommands( + *this, inlineSegBSize, + aAction.mCreateWebRenderCommandsData.mBuilder, + aAction.mCreateWebRenderCommandsData.mSc, + aAction.mCreateWebRenderCommandsData.mOffsetToReferenceFrame); + } + } + blockDirSeg.AdvanceOffsetB(); + if (mRow->PrincipalChildList().IsEmpty()) { + emptyRowEndSize = Some(mRow->BSize(mTableWM)); + } + } + blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize, + emptyRowEndSize); + } + blockDirSeg.IncludeCurrentBorder(*this); + mPrevInlineSegBSize = inlineSegBSize; +} + +/** + * Reset the block-dir information cache + */ +void BCPaintBorderIterator::ResetVerInfo() { + if (mBlockDirInfo) { + memset(mBlockDirInfo.get(), 0, + mDamageArea.ColCount() * sizeof(BCBlockDirSeg)); + // XXX reinitialize properly + for (auto xIndex : IntegerRange(mDamageArea.ColCount())) { + mBlockDirInfo[xIndex].mColWidth = -1; + } + } +} + +void nsTableFrame::IterateBCBorders(BCPaintBorderAction& aAction, + 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.AccumulateOrDoActionBlockDirSegment(aAction); + } + + // 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.AccumulateOrDoActionInlineDirSegment(aAction); + } +} + +/** + * 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) { + BCPaintBorderAction action(aDrawTarget); + IterateBCBorders(action, aDirtyRect); +} + +void nsTableFrame::CreateWebRenderCommandsForBCBorders( + wr::DisplayListBuilder& aBuilder, + const mozilla::layers::StackingContextHelper& aSc, + const nsRect& aVisibleRect, const nsPoint& aOffsetToReferenceFrame) { + BCPaintBorderAction action(aBuilder, aSc, aOffsetToReferenceFrame); + // We always draw whole table border for webrender. Passing the visible rect + // dirty rect. + IterateBCBorders(action, aVisibleRect - aOffsetToReferenceFrame); +} + +bool nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) { + bool result = false; + nsTableCellMap* cellMap = GetCellMap(); + MOZ_ASSERT(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(); + MOZ_ASSERT(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& aOrigInkOverflow, + 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 inkOverflow = aFrame->InkOverflowRect(); + if (aIsFirstReflow || aOrigRect.TopLeft() != aFrame->GetPosition() || + aOrigInkOverflow.TopLeft() != inkOverflow.TopLeft()) { + // Invalidate the old and new overflow rects. Note that if the + // frame moved, we can't just use aOrigInkOverflow, 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(aOrigInkOverflow + aOrigRect.TopLeft()); + } else if (aOrigRect.Size() != aFrame->GetSize() || + aOrigInkOverflow.Size() != inkOverflow.Size()) { + aFrame->InvalidateFrameWithRect(aOrigInkOverflow); + aFrame->InvalidateFrame(); + } +} + +void nsTableFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray& aResult) { + nsIFrame* wrapper = GetParent(); + MOZ_ASSERT(wrapper->Style()->GetPseudoType() == PseudoStyleType::tableWrapper, + "What happened to our parent?"); + aResult.AppendElement( + OwnedAnonBox(wrapper, &UpdateStyleOfOwnedAnonBoxesForTableWrapper)); +} + +/* static */ +void nsTableFrame::UpdateStyleOfOwnedAnonBoxesForTableWrapper( + nsIFrame* aOwningFrame, nsIFrame* aWrapperFrame, + ServoRestyleState& aRestyleState) { + MOZ_ASSERT( + aWrapperFrame->Style()->GetPseudoType() == PseudoStyleType::tableWrapper, + "What happened to our parent?"); + + RefPtr newStyle = + aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle( + PseudoStyleType::tableWrapper, aOwningFrame->Style()); + + // Figure out whether we have an actual change. It's important that we do + // this, even though all the wrapper's changes are due to properties it + // inherits from us, because it's possible that no one ever asked us for those + // style structs and hence changes to them aren't reflected in + // the handled changes at all. + // + // Also note that extensions can add/remove stylesheets that change the styles + // of anonymous boxes directly, so we need to handle that potential change + // here. + // + // NOTE(emilio): We can't use the ChangesHandledFor optimization (and we + // assert against that), because the table wrapper is up in the frame tree + // compared to the owner frame. + uint32_t equalStructs; // Not used, actually. + nsChangeHint wrapperHint = + aWrapperFrame->Style()->CalcStyleDifference(*newStyle, &equalStructs); + + if (wrapperHint) { + aRestyleState.ChangeList().AppendChange( + aWrapperFrame, aWrapperFrame->GetContent(), wrapperHint); + } + + for (nsIFrame* cur = aWrapperFrame; cur; cur = cur->GetNextContinuation()) { + cur->SetComputedStyle(newStyle); + } + + MOZ_ASSERT(!aWrapperFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES), + "Wrapper frame doesn't have any anon boxes of its own!"); +} + +namespace mozilla { + +nsRect nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +nsDisplayTableBackgroundSet::nsDisplayTableBackgroundSet( + nsDisplayListBuilder* aBuilder, nsIFrame* aTable) + : mBuilder(aBuilder), + mColGroupBackgrounds(aBuilder), + mColBackgrounds(aBuilder), + mCurrentScrollParentId(aBuilder->GetCurrentScrollParentId()) { + mPrevTableBackgroundSet = mBuilder->SetTableBackgroundSet(this); + mozilla::DebugOnly reference = + mBuilder->FindReferenceFrameFor(aTable, &mToReferenceFrame); + MOZ_ASSERT(nsLayoutUtils::FindNearestCommonAncestorFrame(reference, aTable)); + mDirtyRect = mBuilder->GetDirtyRect(); + mCombinedTableClipChain = + mBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); + mTableASR = mBuilder->CurrentActiveScrolledRoot(); +} + +// A display item that draws all collapsed borders for a table. +// At some point, we may want to find a nicer partitioning for dividing +// border-collapse segments into their own display items. +class nsDisplayTableBorderCollapse final : public nsDisplayTableItem { + public: + nsDisplayTableBorderCollapse(nsDisplayListBuilder* aBuilder, + nsTableFrame* aFrame) + : nsDisplayTableItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableBorderCollapse); + } + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTableBorderCollapse) + + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; + bool CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override; + NS_DISPLAY_DECL_NAME("TableBorderCollapse", TYPE_TABLE_BORDER_COLLAPSE) +}; + +void nsDisplayTableBorderCollapse::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint pt = ToReferenceFrame(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + + gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint( + pt, mFrame->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))); + + static_cast(mFrame)->PaintBCBorders( + *drawTarget, GetPaintRect(aBuilder, aCtx) - pt); +} + +bool nsDisplayTableBorderCollapse::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + bool dummy; + static_cast(mFrame)->CreateWebRenderCommandsForBCBorders( + aBuilder, aSc, GetBounds(aDisplayListBuilder, &dummy), + ToReferenceFrame()); + return true; +} + +} // namespace mozilla diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h new file mode 100644 index 0000000000..0204af9834 --- /dev/null +++ b/layout/tables/nsTableFrame.h @@ -0,0 +1,952 @@ +/* -*- 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 "nscore.h" +#include "nsContainerFrame.h" +#include "nsStyleConsts.h" +#include "nsCellMap.h" +#include "nsGkAtoms.h" +#include "nsDisplayList.h" +#include "TableArea.h" + +struct BCPaintBorderAction; +class nsTableCellFrame; +class nsTableCellMap; +class nsTableColFrame; +class nsTableRowGroupFrame; +class nsTableRowFrame; +class nsTableColGroupFrame; +class nsITableLayoutStrategy; + +namespace mozilla { +class LogicalMargin; +class PresShell; +class WritingMode; +struct TableBCData; +struct TableReflowInput; + +namespace layers { +class StackingContextHelper; +} + +// An input to nsTableFrame::ReflowTable() and TableReflowInput. +enum class TableReflowMode : uint8_t { + // A reflow to measure the block-size of the table. We use this value to + // request an unconstrained available block in the first reflow if a second + // special block-size reflow is needed later. + Measuring, + + // A final reflow with the available block-size in the table frame's + // ReflowInput. + Final, +}; + +class nsDisplayTableItem : public nsPaintedDisplayItem { + public: + nsDisplayTableItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) {} + + // 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. + nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; +}; + +class nsDisplayTableBackgroundSet { + public: + nsDisplayList* ColGroupBackgrounds() { return &mColGroupBackgrounds; } + + nsDisplayList* ColBackgrounds() { return &mColBackgrounds; } + + nsDisplayTableBackgroundSet(nsDisplayListBuilder* aBuilder, nsIFrame* aTable); + + ~nsDisplayTableBackgroundSet() { + mozilla::DebugOnly result = + mBuilder->SetTableBackgroundSet(mPrevTableBackgroundSet); + MOZ_ASSERT(result == this); + } + + /** + * Move all display items in our lists to top of the corresponding lists in + * the destination. + */ + void MoveTo(const nsDisplayListSet& aDestination) { + aDestination.BorderBackground()->AppendToTop(ColGroupBackgrounds()); + aDestination.BorderBackground()->AppendToTop(ColBackgrounds()); + } + + void AddColumn(nsTableColFrame* aFrame) { mColumns.AppendElement(aFrame); } + + nsTableColFrame* GetColForIndex(int32_t aIndex) { return mColumns[aIndex]; } + + const nsPoint& TableToReferenceFrame() { return mToReferenceFrame; } + + const nsRect& GetDirtyRect() { return mDirtyRect; } + + const DisplayItemClipChain* GetTableClipChain() { + return mCombinedTableClipChain; + } + + const ActiveScrolledRoot* GetTableASR() { return mTableASR; } + layers::ScrollableLayerGuid::ViewID GetScrollParentId() { + return mCurrentScrollParentId; + } + + private: + // This class is only used on stack, so we don't have to worry about leaking + // it. Don't let us be heap-allocated! + void* operator new(size_t sz) noexcept(true); + + protected: + nsDisplayListBuilder* mBuilder; + nsDisplayTableBackgroundSet* mPrevTableBackgroundSet; + + nsDisplayList mColGroupBackgrounds; + nsDisplayList mColBackgrounds; + + nsTArray mColumns; + nsPoint mToReferenceFrame; + nsRect mDirtyRect; + layers::ScrollableLayerGuid::ViewID mCurrentScrollParentId; + + const DisplayItemClipChain* mCombinedTableClipChain; + const ActiveScrolledRoot* mTableASR; +}; + +} // namespace mozilla + +/* ========================================================================== */ + +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, FrameChildListID::ColGroup, which contains the col + * group frames. + */ +class nsTableFrame : public nsContainerFrame { + typedef mozilla::image::ImgDrawResult ImgDrawResult; + typedef mozilla::WritingMode WritingMode; + typedef mozilla::LogicalMargin LogicalMargin; + + public: + NS_DECL_FRAMEARENA_HELPERS(nsTableFrame) + + 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(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + /** sets defaults for table-specific style. + * @see nsIFrame::Init + */ + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + // 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 or deregister 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 or style recalculation. + // + // @return true if the frame is a registered positioned table part. + static void PositionedTablePartMaybeChanged( + nsIFrame*, mozilla::ComputedStyle* aOldStyle); + + // Unregister a positioned table part with its nsTableFrame, if needed. + static void MaybeUnregisterPositionedTablePart(nsIFrame* aFrame); + + /* + * Notification that rowspan or colspan has changed for content inside a + * table cell + */ + void RowOrColSpanChanged(nsTableCellFrame* aCellFrame); + + /** @see nsIFrame::DestroyFrom */ + void Destroy(DestroyContext&) override; + + /** @see nsIFrame::DidSetComputedStyle */ + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + void SetInitialChildList(ChildListID aListID, + nsFrameList&& aChildList) override; + void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override; + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override; + + nsMargin GetUsedBorder() const override; + nsMargin GetUsedPadding() const override; + nsMargin GetUsedMargin() const override; + + /** helper method to find the table parent of any table frame object */ + static nsTableFrame* GetTableFrame(nsIFrame* aSourceFrame); + + // Return the closest sibling of aPriorChildFrame (including aPriroChildFrame) + // of type aChildType. + static nsIFrame* GetFrameAtOrBefore(nsIFrame* aParentFrame, + nsIFrame* aPriorChildFrame, + mozilla::LayoutFrameType 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; + + const nsFrameList& GetChildList(ChildListID aListID) const override; + void GetChildLists(nsTArray* aLists) const override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + /** 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; + + /** + * Emplace our border and padding in aBorder and aPadding if we are + * border-collapsed. Otherwise, do nothing. + */ + void GetCollapsedBorderPadding( + mozilla::Maybe& aBorder, + mozilla::Maybe& aPadding) const; + + friend class nsDelayedCalcBCBorders; + + void AddBCDamageArea(const mozilla::TableArea& aValue); + bool BCRecalcNeeded(ComputedStyle* aOldComputedStyle, + ComputedStyle* aNewComputedStyle); + void PaintBCBorders(DrawTarget& aDrawTarget, const nsRect& aDirtyRect); + void CreateWebRenderCommandsForBCBorders( + mozilla::wr::DisplayListBuilder& aBuilder, + const mozilla::layers::StackingContextHelper& aSc, + const nsRect& aVisibleRect, const nsPoint& aOffsetToReferenceFrame); + + void MarkIntrinsicISizesDirty() override; + // For border-collapse tables, the caller must not add padding and + // border to the results of these functions. + nscoord GetMinISize(gfxContext* aRenderingContext) override; + nscoord GetPrefISize(gfxContext* aRenderingContext) override; + IntrinsicSizeOffsetData IntrinsicISizeOffsets( + nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE) override; + + SizeComputationResult ComputeSize( + gfxContext* aRenderingContext, mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorderPadding, + const mozilla::StyleSizeOverrides& aSizeOverrides, + mozilla::ComputeSizeFlags aFlags) override; + + mozilla::LogicalSize ComputeAutoSize( + gfxContext* aRenderingContext, mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorderPadding, + const mozilla::StyleSizeOverrides& aSizeOverrides, + mozilla::ComputeSizeFlags aFlags) override; + + /** + * A copy of nsIFrame::ShrinkISizeToFit that calls a different + * GetPrefISize, since tables have two different ones. + */ + nscoord TableShrinkISizeToFit(gfxContext* aRenderingContext, + nscoord aWidthInCB); + + // XXXldb REWRITE THIS COMMENT! + // clang-format off + /** + * 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 + */ + // clang-format on + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void ReflowTable(ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput, + const LogicalMargin& aBorderPadding, + mozilla::TableReflowMode aReflowMode, + nsIFrame*& aLastChildReflowed, nsReflowStatus& aStatus); + + nsFrameList& GetColGroups(); + + ComputedStyle* GetParentComputedStyle( + nsIFrame** aProviderFrame) const override; + +#ifdef DEBUG_FRAME_DUMP + /** @see nsIFrame::GetFrameName */ + 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: + nscoord SynthesizeFallbackBaseline( + mozilla::WritingMode aWM, + BaselineSharingGroup aBaselineGroup) const override; + Maybe GetNaturalBaselineBOffset( + mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup, + BaselineExportContext) 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* CreateSyntheticColGroupFrame(); + + int32_t DestroyAnonymousColFrames(int32_t aNumFrames); + + // Append aNumColsToAdd anonymous col frames of type eColAnonymousCell to our + // last synthetic 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); + + 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 aOrigInkOverflow 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& aOrigInkOverflow, + bool aIsFirstReflow); + + bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) override; + + // Return our wrapper frame. + void AppendDirectlyOwnedAnonBoxes(nsTArray& aResult) override; + + protected: + static void UpdateStyleOfOwnedAnonBoxesForTableWrapper( + nsIFrame* aOwningFrame, nsIFrame* aWrapperFrame, + mozilla::ServoRestyleState& aRestyleState); + + /** protected constructor. + * @see NewFrame + */ + explicit nsTableFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + ClassID aID = kClassID); + + virtual ~nsTableFrame(); + + void InitChildReflowInput(ReflowInput& aReflowInput); + + LogicalSides GetLogicalSkipSides() const override; + + void IterateBCBorders(BCPaintBorderAction& aAction, const nsRect& aDirtyRect); + + public: + bool IsRowInserted() const; + void SetRowInserted(bool aValue); + + protected: + // A helper function to reflow a header or footer with unconstrained + // block-size to see if it should be made repeatable. + // @return the desired block-size for a header or footer. + nscoord SetupHeaderFooterChild(const mozilla::TableReflowInput& aReflowInput, + nsTableRowGroupFrame* aFrame); + + void ReflowChildren(mozilla::TableReflowInput& aReflowInput, + nsReflowStatus& aStatus, nsIFrame*& aLastChildReflowed, + mozilla::OverflowAreas& 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(gfxContext* 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.get(); + } + + // 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); + + protected: + // Calculate the border-box block-size of this table, with the min-block-size, + // max-block-size, and intrinsic border-box block considered. + nscoord CalcBorderBoxBSize(const ReflowInput& aReflowInput, + const LogicalMargin& aBorderPadding, + nscoord aIntrinsicBorderBoxBSize); + + // Calculate the desired block-size of this table. + // + // Note: this method is accurate after the children are reflowed. It might + // distribute extra block-size to table rows if the table has a specified + // block-size larger than the intrinsic block-size. + nscoord CalcDesiredBSize(const ReflowInput& aReflowInput, + const LogicalMargin& aBorderPadding); + + // The following is a helper for CalcDesiredBSize + void DistributeBSizeToRows(const ReflowInput& aReflowInput, nscoord aAmount); + + void PlaceChild(mozilla::TableReflowInput& aReflowInput, nsIFrame* aKidFrame, + const ReflowInput& aKidReflowInput, + const mozilla::LogicalPoint& aKidPosition, + const nsSize& aContainerSize, ReflowOutput& aKidDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidInkOverflow); + void PlaceRepeatedFooter(mozilla::TableReflowInput& aReflowInput, + nsTableRowGroupFrame* aTfoot, nscoord aFooterBSize); + + public: + using RowGroupArray = AutoTArray; + + protected: + // Push all our non-repeatable child frames from the aRowGroups array, in + // order, starting from the frame at aPushFrom to the end of the array. The + // pushed frames are put on our overflow list. This is a table specific + // version that takes into account repeated header and footer frames when + // continuing table frames. + void PushChildrenToOverflow(const RowGroupArray& aRowGroups, + size_t aPushFrom); + + public: + // Return the children frames in the display order (e.g. thead before tbodies + // before tfoot). If there are multiple theads or tfoots, all but the first + // one are treated as tbodies instead. + // + // @param aHead Outparam for the first thead if there is any. + // @param aFoot Outparam for the first tfoot if there is any. + RowGroupArray OrderedRowGroups(nsTableRowGroupFrame** aHead = nullptr, + nsTableRowGroupFrame** aFoot = nullptr) 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); + + bool NeedToCalcHasBCBorders() const; + void SetNeedToCalcHasBCBorders(bool aValue); + + void CalcHasBCBorders(); + bool HasBCBorders(); + void SetHasBCBorders(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(); + + mozilla::TableBCData* GetTableBCData() const; + + protected: + void SetBorderCollapse(bool aValue); + + mozilla::TableBCData* GetOrCreateTableBCData(); + 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(const nsTableRowGroupFrame* aRowGroupFrame) const; + + /** 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: + /* ---------- Row index management methods ------------ */ + + /** Add the given index to the existing ranges of + * deleted row indices and merge ranges if, with the addition of the new + * index, they become consecutive. + * @param aDeletedRowStoredIndex - index of the row that was deleted + * Note - 'stored' index here refers to the index that was assigned to + * the row before any remove row operations were performed i.e. the + * value of mRowIndex and not the value returned by GetRowIndex() + */ + void AddDeletedRowIndex(int32_t aDeletedRowStoredIndex); + + /** Calculate the change that aStoredIndex must be increased/decreased by + * to get new index. + * Note that aStoredIndex is always the index of an undeleted row (since + * rows that have already been deleted can never call this method). + * @param aStoredIndex - The stored index value that must be adjusted + * Note - 'stored' index here refers to the index that was assigned to + * the row before any remove row operations were performed i.e. the + * value of mRowIndex and not the value returned by GetRowIndex() + */ + int32_t GetAdjustmentForStoredIndex(int32_t aStoredIndex); + + /** Returns whether mDeletedRowIndexRanges is empty + */ + bool IsDeletedRowIndexRangesEmpty() const { + return mDeletedRowIndexRanges.empty(); + } + + bool IsDestroying() const { return mBits.mIsDestroying; } + + public: +#ifdef DEBUG + void Dump(bool aDumpRows, bool aDumpCols, bool aDumpCellMap); +#endif + + protected: + /** + * Helper method for RemoveFrame. + */ + void DoRemoveFrame(DestroyContext&, ChildListID, nsIFrame*); +#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 mNeedToCollapse : 1; // rows, cols that have visibility:collapse + // need to be collapsed + uint32_t mResizedColumns : 1; // have we resized columns since last reflow? + uint32_t mNeedToCalcHasBCBorders : 1; + uint32_t mHasBCBorders : 1; + uint32_t mIsDestroying : 1; // Whether we're in the process of destroying + // this table frame. + } mBits; + + std::map mDeletedRowIndexRanges; // maintains ranges of row + // indices of deleted rows + mozilla::UniquePtr mCellMap; // maintains the relationships + // between rows, cols, and cells + // the layout strategy for this frame + mozilla::UniquePtr mTableLayoutStrategy; + 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 bool nsTableFrame::NeedToCalcHasBCBorders() const { + return (bool)mBits.mNeedToCalcHasBCBorders; +} + +inline void nsTableFrame::SetNeedToCalcHasBCBorders(bool aValue) { + mBits.mNeedToCalcHasBCBorders = (unsigned)aValue; +} + +inline bool nsTableFrame::HasBCBorders() { + if (NeedToCalcHasBCBorders()) { + CalcHasBCBorders(); + SetNeedToCalcHasBCBorders(false); + } + return (bool)mBits.mHasBCBorders; +} + +inline void nsTableFrame::SetHasBCBorders(bool aValue) { + mBits.mHasBCBorders = (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/nsTableRowFrame.cpp b/layout/tables/nsTableRowFrame.cpp new file mode 100644 index 0000000000..5d8119522f --- /dev/null +++ b/layout/tables/nsTableRowFrame.cpp @@ -0,0 +1,1344 @@ +/* -*- 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 "nsTableRowFrame.h" + +#include "mozilla/Baseline.h" +#include "mozilla/Maybe.h" +#include "mozilla/PresShell.h" +#include "nsTableRowGroupFrame.h" +#include "nsPresContext.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/StaticPrefs_layout.h" +#include "nsStyleConsts.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.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 + +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +#endif + +using namespace mozilla; + +namespace mozilla { + +struct TableCellReflowInput : public ReflowInput { + TableCellReflowInput(nsPresContext* aPresContext, + const ReflowInput& aParentReflowInput, nsIFrame* aFrame, + const LogicalSize& aAvailableSpace, + ReflowInput::InitFlags aFlags = {}) + : ReflowInput(aPresContext, aParentReflowInput, aFrame, aAvailableSpace, + Nothing(), 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(mWritingMode).IStartEnd(mWritingMode); + computedISize = std::max(0, computedISize); + SetComputedISize(computedISize); + } + if (NS_UNCONSTRAINEDSIZE != ComputedBSize() && + NS_UNCONSTRAINEDSIZE != aAvailSpace.BSize(mWritingMode)) { + nscoord computedBSize = + aAvailSpace.BSize(mWritingMode) - + ComputedLogicalBorderPadding(mWritingMode).BStartEnd(mWritingMode); + computedBSize = std::max(0, computedBSize); + SetComputedBSize(computedBSize); + } +} + +void nsTableRowFrame::InitChildReflowInput(nsPresContext& aPresContext, + const LogicalSize& aAvailSize, + bool aBorderCollapse, + TableCellReflowInput& aReflowInput) { + Maybe collapseBorder; + if (aBorderCollapse) { + // we only reflow cells, so don't need to check frame type + nsBCTableCellFrame* bcCellFrame = (nsBCTableCellFrame*)aReflowInput.mFrame; + if (bcCellFrame) { + collapseBorder.emplace( + bcCellFrame->GetBorderWidth(aReflowInput.GetWritingMode())); + } + } + aReflowInput.Init(&aPresContext, Nothing(), collapseBorder); + 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(ComputedStyle* aStyle, + nsPresContext* aPresContext, ClassID aID) + : nsContainerFrame(aStyle, aPresContext, aID) { + mBits.mRowIndex = 0; + mBits.mHasFixedBSize = 0; + mBits.mHasPctBSize = 0; + mBits.mFirstInserted = 0; + ResetBSize(); +} + +nsTableRowFrame::~nsTableRowFrame() = default; + +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()); + } else { + mWritingMode = GetTableFrame()->GetWritingMode(); + } +} + +void nsTableRowFrame::Destroy(DestroyContext& aContext) { + nsTableFrame::MaybeUnregisterPositionedTablePart(this); + nsContainerFrame::Destroy(aContext); +} + +/* virtual */ +void nsTableRowFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); + nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle); + + if (!aOldComputedStyle) { + return; // avoid the following on init + } + +#ifdef ACCESSIBILITY + if (nsAccessibilityService* accService = GetAccService()) { + // If a table row's background color is now different from + // the background color of its previous row, it is possible our + // table now has alternating row colors. This changes whether or not + // the table is classified as a layout table or data table. + // We invalidate on every background color change to avoid + // walking the tree in search of the nearest row. + if (StyleBackground()->BackgroundColor(this) != + aOldComputedStyle->StyleBackground()->BackgroundColor( + aOldComputedStyle)) { + // We send a notification here to invalidate the a11y cache on the + // table so the next fetch of IsProbablyLayoutTable() is accurate. + accService->TableLayoutGuessMaybeChanged(PresShell(), mContent); + } + } +#endif + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) { + TableArea damageArea(0, GetRowIndex(), tableFrame->GetColCount(), 1); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void nsTableRowFrame::AppendFrames(ChildListID aListID, + nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + + DrainSelfOverflowList(); // ensure the last frame is in mFrames + const nsFrameList::Slice& newCells = + mFrames.AppendFrames(nullptr, std::move(aFrameList)); + + // Add the new cell frames to the table + nsTableFrame* tableFrame = GetTableFrame(); + for (nsIFrame* childFrame : newCells) { + NS_ASSERTION(childFrame->IsTableCellFrame(), + "Not a table cell frame/pseudo frame construction failure"); + tableFrame->AppendCell(static_cast(*childFrame), + GetRowIndex()); + } + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); +} + +void nsTableRowFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, + "inserting after sibling frame with different parent"); + if (mFrames.IsEmpty() || (aPrevFrame && !aPrevFrame->GetNextSibling())) { + // This is actually an append (though our caller didn't figure that out), + // and our append codepath is both simpler/faster _and_ less buggy. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1388898 tracks the bugginess + AppendFrames(aListID, std::move(aFrameList)); + return; + } + + DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames + // Insert Frames in the frame list + const nsFrameList::Slice& newCells = + mFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); + + nsTableCellFrame* prevCellFrame = + static_cast(nsTableFrame::GetFrameAtOrBefore( + this, aPrevFrame, LayoutFrameType::TableCell)); + nsTArray cellChildren; + for (nsIFrame* childFrame : newCells) { + NS_ASSERTION(childFrame->IsTableCellFrame(), + "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) { + colIndex = prevCellFrame->ColIndex(); + } + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->InsertCells(cellChildren, GetRowIndex(), colIndex); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); +} + +void nsTableRowFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, + nsIFrame* aOldFrame) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "unexpected child list"); + MOZ_ASSERT((nsTableCellFrame*)do_QueryFrame(aOldFrame)); + + auto* 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(aContext, aOldFrame); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + 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); +} + +static 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 (nextRow->IsTableRowFrame()) { + bsize += nextRow->BSize(aWM); + rowX++; + } + bsize += aTableFrame.GetRowSpacing(rowX); + nextRow = nextRow->GetNextSibling(); + } + return bsize; +} + +/** + * 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 (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; + cellFrame = cellFrame->GetNextCell()) { + 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 cellInkOverflow = cellFrame->InkOverflowRect(); + + 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->IsRelativelyOrStickyPositioned()) { + // 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); + + if (tableFrame->IsBorderCollapse()) { + nsTableFrame::InvalidateTableFrame(cellFrame, cellOldRect, + cellInkOverflow, 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.InkOverflow(), + ReflowChildFlags::Default); + } + // 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; } + +Maybe nsTableRowFrame::GetRowBaseline(WritingMode aWM) { + if (mMaxCellAscent) { + return Some(mMaxCellAscent); + } + + // If we get here, we don't have a baseline on any of the cells in this row. + if (aWM.IsCentralBaseline()) { + return Nothing{}; + } + nscoord ascent = 0; + for (nsIFrame* childFrame : mFrames) { + MOZ_ASSERT(childFrame->IsTableCellFrame()); + nscoord s = Baseline::SynthesizeBOffsetFromContentBox( + childFrame, aWM, BaselineSharingGroup::First); + ascent = std::max(ascent, s); + } + return Some(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() { + SetHasFixedBSize(false); + SetHasPctBSize(false); + SetFixedBSize(0); + SetPctBSize(0); + SetContentBSize(0); + + 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(); + + ResetBSize(); + const nscoord computedBSize = aReflowInput.ComputedBSize(); + if (computedBSize != NS_UNCONSTRAINEDSIZE && computedBSize > 0) { + SetFixedBSize(computedBSize); + } + + WritingMode wm = aReflowInput.GetWritingMode(); + const nsStylePosition* position = StylePosition(); + const auto& bsizeStyleCoord = position->BSize(wm); + if (bsizeStyleCoord.ConvertsToLength()) { + SetFixedBSize(bsizeStyleCoord.ToLength()); + } else if (bsizeStyleCoord.ConvertsToPercentage()) { + SetPctBSize(bsizeStyleCoord.ToPercentage()); + } + + for (nsTableCellFrame* kidFrame = GetFirstCell(); kidFrame; + kidFrame = kidFrame->GetNextCell()) { + MOZ_ASSERT(kidFrame->GetWritingMode() == wm); + LogicalSize desSize = kidFrame->GetDesiredSize(); + if ((NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) && + !GetPrevInFlow()) { + desSize.BSize(wm) = CalcCellActualBSize(kidFrame, 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 = kidFrame->GetCellBaseline(); + } + nscoord descent = desSize.BSize(wm) - ascent; + UpdateBSize(desSize.BSize(wm), ascent, descent, tableFrame, kidFrame); + } + return GetInitialBSize(); +} + +void nsTableRowFrame::PaintCellBackgroundsForFrame( + nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists, const nsPoint& aOffset) { + // Compute background rect by iterating all cell frame. + const nsPoint toReferenceFrame = aBuilder->ToReferenceFrame(aFrame); + for (nsTableCellFrame* cell = GetFirstCell(); cell; + cell = cell->GetNextCell()) { + if (!cell->ShouldPaintBackground(aBuilder)) { + continue; + } + + auto cellRect = + cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + aOffset; + if (!aBuilder->GetDirtyRect().Intersects(cellRect)) { + continue; + } + cellRect += toReferenceFrame; + nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + aBuilder, aFrame, cellRect, aLists.BorderBackground(), true, + aFrame->GetRectRelativeToSelf() + toReferenceFrame, cell); + } +} + +void nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground()); + + PaintCellBackgroundsForFrame(this, aBuilder, aLists); + + DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground()); + + DisplayOutline(aBuilder, aLists); + + for (nsIFrame* kid : PrincipalChildList()) { + BuildDisplayListForChild(aBuilder, kid, aLists); + } +} + +LogicalSides nsTableRowFrame::GetLogicalSkipSides() const { + LogicalSides skip(mWritingMode); + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return skip; + } + + if (GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +nscoord nsTableRowFrame::CalcCellActualBSize(nsTableCellFrame* aCellFrame, + const 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 auto& bsizeStyleCoord = position->BSize(aWM); + if (bsizeStyleCoord.ConvertsToLength()) { + // 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 = bsizeStyleCoord.ToLength(); + if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks && + position->mBoxSizing == StyleBoxSizing::Content) { + specifiedBSize += + aCellFrame->GetLogicalUsedBorderAndPadding(aWM).BStartEnd(aWM); + } + + if (1 == rowSpan) { + SetFixedBSize(specifiedBSize); + } + } else if (bsizeStyleCoord.ConvertsToPercentage()) { + if (1 == rowSpan) { + SetPctBSize(bsizeStyleCoord.ToPercentage()); + } + } + + // If the specified bsize is greater than the desired bsize, + // then use the specified bsize + return std::max(specifiedBSize, aDesiredBSize); +} + +// 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; + uint32_t colIndex = aCellFrame.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; +} + +static 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 = StyleVisibility::Collapse == colVis->mVisible; + nsIFrame* cgFrame = colFrame->GetParent(); + const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); + bool collapseGroup = StyleVisibility::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.Reset(); + + // 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 (nsTableCellFrame* kidFrame = GetFirstCell(); kidFrame; + kidFrame = kidFrame->GetNextCell()) { + // See if we should only reflow the dirty child frames + bool doReflowChild = true; + if (!aReflowInput.ShouldReflowAllKids() && !aTableFrame.IsGeometryDirty() && + !kidFrame->IsSubtreeDirty()) { + 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(*kidFrame) > 1) { + doReflowChild = false; + } + } + if (aReflowInput.mFlags.mSpecialBSizeReflow) { + if (!isPaginated && + !kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { + continue; + } + } + + uint32_t cellColIndex = kidFrame->ColIndex(); + cellColSpan = aTableFrame.GetEffectiveColSpan(*kidFrame); + + // If the adjacent cell is in a prior row (because of a rowspan) add in the + // space NOTE: prevColIndex can be -1 here. + if (prevColIndex != (static_cast(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); + + nsRect kidInkOverflow = kidFrame->InkOverflowRect(); + 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, *kidFrame); + + 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? + NS_ASSERTION(kidFrame->GetWritingMode() == wm, + "expected consistent writing-mode within table"); + LogicalSize cellDesiredSize = kidFrame->GetDesiredSize(); + if ((availCellISize != kidFrame->GetPriorAvailISize()) || + (cellDesiredSize.ISize(wm) > kidFrame->GetPriorAvailISize()) || + HasAnyStateBits(NS_FRAME_IS_DIRTY) || isPaginated || + kidFrame->IsSubtreeDirty() || + // See if it needs a special reflow, or if it had one that we need to + // undo. + kidFrame->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::InitFlag::CallerWillInit); + InitChildReflowInput(*aPresContext, kidAvailSize, borderCollapse, + *kidReflowInput); + + nsReflowStatus status; + ReflowChild(kidFrame, aPresContext, desiredSize, *kidReflowInput, wm, + kidPosition, containerSize, ReflowChildFlags::Default, + 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 (status.IsIncomplete()) { + aStatus.Reset(); + aStatus.SetIncomplete(); + } + } else { + if (iCoord != origKidNormalPosition.I(wm)) { + kidFrame->InvalidateFrameSubtree(); + } + + desiredSize.SetSize(wm, cellDesiredSize); + desiredSize.mOverflowAreas = kidFrame->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()) { + desiredSize.BSize(wm) = + CalcCellActualBSize(kidFrame, 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 = kidFrame->GetCellBaseline(); + } + nscoord descent = desiredSize.BSize(wm) - ascent; + UpdateBSize(desiredSize.BSize(wm), ascent, descent, &aTableFrame, + kidFrame); + } else { + cellMaxBSize = std::max(cellMaxBSize, desiredSize.BSize(wm)); + int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(*kidFrame); + if (1 == rowSpan) { + SetContentBSize(cellMaxBSize); + } + } + + // Place the child + desiredSize.ISize(wm) = availCellISize; + + ReflowChildFlags flags = ReflowChildFlags::Default; + + if (kidReflowInput) { + // We reflowed. Apply relative positioning in the normal way. + flags = ReflowChildFlags::ApplyRelativePositioning; + } else if (kidFrame->IsRelativelyOrStickyPositioned()) { + // 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->GetProperty(nsIFrame::ComputedOffsetProperty()); + + // On our fist reflow sticky children may not have the property yet (we + // need to reflow the children first to size the scroll frame). + 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, + kidReflowInput.ptrOr(nullptr), wm, kidPosition, + containerSize, flags); + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse()) { + nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidInkOverflow, + 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.Reset(); + aStatus.SetIncomplete(); + } + } + 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 + SetUnpaginatedBSize(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(); + aStatus.SetIncomplete(); + } + 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) { + 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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + WritingMode wm = aReflowInput.GetWritingMode(); + + nsTableFrame* tableFrame = GetTableFrame(); + const nsStyleVisibility* rowVis = StyleVisibility(); + bool collapseRow = StyleVisibility::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() && !aStatus.IsFullyComplete() && + ShouldAvoidBreakInside(aReflowInput)) { + aStatus.SetInlineLineBreakBeforeAndReset(); + } + + // 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 + // nsIFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); +} + +/** + * 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) { + MOZ_ASSERT(aAvailableBSize != NS_UNCONSTRAINEDSIZE, + "Why split cell frame if available bsize is unconstrained?"); + 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 cellInkOverflow = aCellFrame->InkOverflowRect(); + + 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::InitFlag::CallerWillInit); + InitChildReflowInput(*aPresContext, availSize, borderCollapse, + cellReflowInput); + cellReflowInput.mFlags.mIsTopOfPage = aIsTopOfPage; + + ReflowOutput desiredSize(aReflowInput); + + ReflowChild(aCellFrame, aPresContext, desiredSize, cellReflowInput, 0, 0, + ReflowChildFlags::NoMoveFrame, aStatus); + const bool isTruncated = + aAvailableBSize < desiredSize.BSize(wm) && + !aIsTopOfPage; // XXX Is !aIsTopOfPage check really necessary? + const bool isCompleteAndNotTruncated = aStatus.IsComplete() && !isTruncated; + if (isCompleteAndNotTruncated) { + 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 (isCompleteAndNotTruncated) { + aCellFrame->BlockDirAlignChild(wm, mMaxCellAscent); + } + + nsTableFrame::InvalidateTableFrame( + aCellFrame, cellRect.GetPhysicalRect(wm, containerSize), cellInkOverflow, + aCellFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)); + + aCellFrame->DidReflow(aPresContext, nullptr); + + return desiredSize.BSize(wm); +} + +nscoord nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset, + nscoord aISize, + bool aCollapseGroup, + bool& aDidCollapse) { + const nsStyleVisibility* rowVis = StyleVisibility(); + bool collapseRow = StyleVisibility::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 oldInkOverflow = InkOverflowRect(); + + rowRect.BStart(wm) -= aRowOffset; + rowRect.ISize(wm) = aISize; + OverflowAreas overflow; + nscoord shift = 0; + nsSize containerSize = mRect.Size(); + + if (aCollapseGroup || collapseRow) { + aDidCollapse = true; + shift = rowRect.BSize(wm); + nsTableCellFrame* cellFrame = GetFirstCell(); + if (cellFrame) { + uint32_t rowIndex = cellFrame->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 (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; + cellFrame = cellFrame->GetNextCell()) { + uint32_t cellColIndex = cellFrame->ColIndex(); + int32_t cellColSpan = tableFrame->GetEffectiveColSpan(*cellFrame); + + // If the adjacent cell is in a prior row (because of a rowspan) add in + // the space + // NOTE: prevColIndex can be -1 here. + if (prevColIndex != (static_cast(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 = StyleVisibility::Collapse == colVis->mVisible; + nsIFrame* cgFrame = colFrame->GetParent(); + const nsStyleVisibility* groupVis = cgFrame->StyleVisibility(); + bool collapseGroup = StyleVisibility::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 (StyleVisibility::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 = + StyleVisibility::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 oldCellInkOverflow = cellFrame->InkOverflowRect(); + + 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); + OverflowAreas cellOverflow(cellPhysicalBounds, cellPhysicalBounds); + cellFrame->FinishAndStoreOverflow(cellOverflow, + cRect.Size(wm).GetPhysicalSize(wm)); + nsTableFrame::RePositionViews(cellFrame); + ConsiderChildOverflow(overflow, cellFrame); + + if (aRowOffset == 0) { + nsTableFrame::InvalidateTableFrame(cellFrame, oldCellRect, + oldCellInkOverflow, 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, oldInkOverflow, 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 (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; + cellFrame = cellFrame->GetNextCell()) { + uint32_t colIndex = cellFrame->ColIndex(); + // Can aColIndex be -1 here? Let's assume it can for now. + if (static_cast(colIndex) < aColIndex) { + priorCell = cellFrame; + } else { + break; + } + } + mFrames.InsertFrame(this, priorCell, aFrame); +} + +nsTableRowFrame* nsTableRowFrame::GetPrevRow() const { + nsIFrame* prevSibling = GetPrevSibling(); + MOZ_ASSERT( + !prevSibling || static_cast(do_QueryFrame(prevSibling)), + "How do we have a non-row sibling?"); + return static_cast(prevSibling); +} + +nsTableRowFrame* nsTableRowFrame::GetNextRow() const { + nsIFrame* nextSibling = GetNextSibling(); + MOZ_ASSERT( + !nextSibling || static_cast(do_QueryFrame(nextSibling)), + "How do we have a non-row sibling?"); + return static_cast(nextSibling); +} + +// This property is only set on the first-in-flow of nsTableRowFrame. +NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TableRowUnpaginatedBSizeProperty, nscoord) + +void nsTableRowFrame::SetUnpaginatedBSize(nscoord aValue) { + MOZ_ASSERT(!GetPrevInFlow(), + "TableRowUnpaginatedBSizeProperty should only be set on the " + "first-in-flow!"); + AddStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE); + SetProperty(TableRowUnpaginatedBSizeProperty(), aValue); +} + +nscoord nsTableRowFrame::GetUnpaginatedBSize() const { + return GetProperty(TableRowUnpaginatedBSizeProperty()); +} + +#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 (nsTableCellFrame* cellFrame = GetFirstCell(); cellFrame; + cellFrame = cellFrame->GetNextCell()) { + // Ignore row-spanning cells + const auto& cellBSize = cellFrame->StylePosition()->BSize(wm); + if (aTableFrame->GetEffectiveRowSpan(*cellFrame) == 1 && + !cellBSize.IsAuto() && + /* calc() with both percentages and lengths treated like 'auto' */ + (cellBSize.ConvertsToLength() || cellBSize.ConvertsToPercentage())) { + AddStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE); + return; + } + } + RemoveStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE); +} + +void nsTableRowFrame::InvalidateFrame(uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); + if (GetTableFrame()->IsBorderCollapse()) { + const bool rebuild = StaticPrefs::layout_display_list_retain_sc(); + GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(), + aDisplayItemKey, rebuild); + } +} + +void nsTableRowFrame::InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, + aRebuildDisplayItems); + // 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, + aRebuildDisplayItems); +} + +/* ----- global methods ----- */ + +nsTableRowFrame* NS_NewTableRowFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) nsTableRowFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableRowFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableRowFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"TableRow"_ns, aResult); +} +#endif diff --git a/layout/tables/nsTableRowFrame.h b/layout/tables/nsTableRowFrame.h new file mode 100644 index 0000000000..1d68a534c3 --- /dev/null +++ b/layout/tables/nsTableRowFrame.h @@ -0,0 +1,386 @@ +/* -*- 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 "nsTableRowGroupFrame.h" +#include "mozilla/WritingModes.h" + +class nsTableCellFrame; +namespace mozilla { +class PresShell; +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 + NS_DECL_FRAMEARENA_HELPERS(nsTableRowFrame) + + virtual ~nsTableRowFrame(); + + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + void Destroy(DestroyContext&) override; + + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override; + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) 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(mozilla::PresShell* aPresShell, + ComputedStyle* aStyle); + + nsTableRowGroupFrame* GetTableRowGroupFrame() const { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->IsTableRowGroupFrame()); + return static_cast(parent); + } + + nsTableFrame* GetTableFrame() const { + return GetTableRowGroupFrame()->GetTableFrame(); + } + + nsMargin GetUsedMargin() const override; + nsMargin GetUsedBorder() const override; + nsMargin GetUsedPadding() const override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + void PaintCellBackgroundsForFrame(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists, + const nsPoint& aOffset = nsPoint()); + + // Implemented in nsTableCellFrame.h, because it needs to know about the + // nsTableCellFrame class, but we can't include nsTableCellFrame.h here. + inline nsTableCellFrame* GetFirstCell() const; + + /** 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 + */ + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + void DidResize(); + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override; +#endif + + void UpdateBSize(nscoord aBSize, nscoord aAscent, nscoord aDescent, + nsTableFrame* aTableFrame = nullptr, + nsTableCellFrame* aCellFrame = nullptr); + + void ResetBSize(); + + // 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 + */ + Maybe GetRowBaseline(mozilla::WritingMode aWM); + + /** 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); + + // See nsTableFrame.h + int32_t GetAdjustmentForStoredIndex(int32_t aStoredIndex) const; + + // See nsTableFrame.h + void AddDeletedRowIndex(); + + /** 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); + + /** + * Calculate the cell frame's actual block-size given its desired block-size + * (the border-box block-size in the last reflow). This method takes into + * account the specified bsize (in the style). + * + * @return the specified block-size if it is larger than the desired + * block-size. Otherwise, the desired block-size. + */ + nscoord CalcCellActualBSize(nsTableCellFrame* aCellFrame, + const 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* GetPrevRow() const; + nsTableRowFrame* GetNextRow() const; + + bool HasUnpaginatedBSize() const { + return HasAnyStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE); + } + nscoord GetUnpaginatedBSize() const; + void SetUnpaginatedBSize(nscoord aValue); + + BCPixelSize GetBStartBCBorderWidth() const { return mBStartBorderWidth; } + BCPixelSize GetBEndBCBorderWidth() const { return mBEndBorderWidth; } + void SetBStartBCBorderWidth(BCPixelSize aWidth) { + mBStartBorderWidth = aWidth; + } + void SetBEndBCBorderWidth(BCPixelSize aWidth) { mBEndBorderWidth = aWidth; } + mozilla::LogicalMargin GetBCBorderWidth(mozilla::WritingMode aWM); + + void InvalidateFrame(uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + +#ifdef ACCESSIBILITY + mozilla::a11y::AccType AccessibleType() override; +#endif + + protected: + /** protected constructor. + * @see NewFrame + */ + explicit nsTableRowFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + ClassID aID = kClassID); + + void InitChildReflowInput(nsPresContext& aPresContext, + const mozilla::LogicalSize& aAvailSize, + bool aBorderCollapse, + TableCellReflowInput& aReflowInput); + + LogicalSides GetLogicalSkipSides() 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 = 0; + // the bsize based on a style percentage bsize on either the row or any cell + // if mHasPctBSize is set + nscoord mStylePctBSize = 0; + // the bsize based on a style pixel bsize on the row or any + // cell if mHasFixedBSize is set + nscoord mStyleFixedBSize = 0; + + // max-ascent and max-descent amongst all cells that have + // 'vertical-align: baseline' + nscoord mMaxCellAscent = 0; // does include cells with rowspan > 1 + nscoord mMaxCellDescent = 0; // 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 = 0; + BCPixelSize mBEndBorderWidth = 0; + + /** + * 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::GetAdjustmentForStoredIndex( + int32_t aStoredIndex) const { + nsTableRowGroupFrame* parentFrame = GetTableRowGroupFrame(); + return parentFrame->GetAdjustmentForStoredIndex(aStoredIndex); +} + +inline void nsTableRowFrame::AddDeletedRowIndex() { + nsTableRowGroupFrame* parentFrame = GetTableRowGroupFrame(); + parentFrame->AddDeletedRowIndex(int32_t(mBits.mRowIndex)); +} + +inline int32_t nsTableRowFrame::GetRowIndex() const { + int32_t storedRowIndex = int32_t(mBits.mRowIndex); + int32_t rowIndexAdjustment = GetAdjustmentForStoredIndex(storedRowIndex); + return (storedRowIndex - rowIndexAdjustment); +} + +inline void nsTableRowFrame::SetRowIndex(int aRowIndex) { + // Note: Setting the index of a row (as in the case of adding new rows) should + // be preceded by a call to nsTableFrame::RecalculateRowIndices() + // so as to correctly clear mDeletedRowIndexRanges. + MOZ_ASSERT( + GetTableRowGroupFrame()->GetTableFrame()->IsDeletedRowIndexRangesEmpty(), + "mDeletedRowIndexRanges should be empty here!"); + 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 mozilla::LogicalMargin nsTableRowFrame::GetBCBorderWidth( + mozilla::WritingMode aWM) { + nsPresContext* presContext = PresContext(); + return mozilla::LogicalMargin( + aWM, presContext->DevPixelsToAppUnits(mBStartBorderWidth), 0, + presContext->DevPixelsToAppUnits(mBEndBorderWidth), 0); +} + +#endif diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp new file mode 100644 index 0000000000..f8dac61386 --- /dev/null +++ b/layout/tables/nsTableRowGroupFrame.cpp @@ -0,0 +1,1864 @@ +/* -*- 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 "nsTableRowGroupFrame.h" + +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" + +#include "nsCOMPtr.h" +#include "nsTableRowFrame.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsPresContext.h" +#include "nsStyleConsts.h" +#include "nsIContent.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsGkAtoms.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 final { + // Our reflow input + const ReflowInput& mReflowInput; + + // The available size (computed from the parent) + LogicalSize mAvailSize; + + // Running block-offset + nscoord mBCoord = 0; + + explicit TableRowGroupReflowInput(const ReflowInput& aReflowInput) + : mReflowInput(aReflowInput), mAvailSize(aReflowInput.AvailableSize()) {} + + ~TableRowGroupReflowInput() = default; +}; + +} // namespace mozilla + +nsTableRowGroupFrame::nsTableRowGroupFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsContainerFrame(aStyle, aPresContext, kClassID) { + SetRepeatable(false); +} + +nsTableRowGroupFrame::~nsTableRowGroupFrame() = default; + +void nsTableRowGroupFrame::Destroy(DestroyContext& aContext) { + nsTableFrame::MaybeUnregisterPositionedTablePart(this); + nsContainerFrame::Destroy(aContext); +} + +NS_QUERYFRAME_HEAD(nsTableRowGroupFrame) + NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +int32_t nsTableRowGroupFrame::GetRowCount() const { +#ifdef DEBUG + for (nsIFrame* f : mFrames) { + NS_ASSERTION(f->StyleDisplay()->mDisplay == mozilla::StyleDisplay::TableRow, + "Unexpected display"); + NS_ASSERTION(f->IsTableRowFrame(), "Unexpected frame type"); + } +#endif + + return mFrames.GetLength(); +} + +int32_t nsTableRowGroupFrame::GetStartRowIndex() const { + int32_t result = -1; + if (mFrames.NotEmpty()) { + NS_ASSERTION(mFrames.FirstChild()->IsTableRowFrame(), + "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); + } + } +} + +int32_t nsTableRowGroupFrame::GetAdjustmentForStoredIndex( + int32_t aStoredIndex) { + nsTableFrame* tableFrame = GetTableFrame(); + return tableFrame->GetAdjustmentForStoredIndex(aStoredIndex); +} + +void nsTableRowGroupFrame::MarkRowsAsDeleted(nsTableRowFrame& aStartRowFrame, + int32_t aNumRowsToDelete) { + nsTableRowFrame* currentRowFrame = &aStartRowFrame; + for (;;) { + // XXXneerja - Instead of calling AddDeletedRowIndex() per row frame + // it is possible to change AddDeleteRowIndex to instead take + // and as arguments. + // The problem that emerges here is mDeletedRowIndexRanges only stores + // disjoint index ranges and since AddDeletedRowIndex() must operate on + // the "stored" index, in some cases it is possible that the range + // of indices to delete becomes overlapping EG: Deleting rows 9 - 11 and + // then from the remaining rows deleting the *new* rows 7 to 20. + // Handling these overlapping ranges is much more complicated to + // implement and so I opted to add the deleted row index of one row at a + // time and maintain the invariant that the range of deleted row indices + // is always disjoint. + currentRowFrame->AddDeletedRowIndex(); + if (--aNumRowsToDelete == 0) { + break; + } + currentRowFrame = do_QueryFrame(currentRowFrame->GetNextSibling()); + if (!currentRowFrame) { + MOZ_ASSERT_UNREACHABLE("expected another row frame"); + break; + } + } +} + +void nsTableRowGroupFrame::AddDeletedRowIndex(int32_t aDeletedRowStoredIndex) { + nsTableFrame* tableFrame = GetTableFrame(); + return tableFrame->AddDeletedRowIndex(aDeletedRowStoredIndex); +} + +void 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"); + uint32_t colIndex = originalCellFrame->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(); + } +} + +// Handle the child-traversal part of DisplayGenericTablePart +static void DisplayRows(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + 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, true) + ? nullptr + : f->GetFirstRowContaining(aBuilder->GetVisibleRect().y, + &overflowAbove); + + if (kid) { + // have a cursor, use it + while (kid) { + if (kid->GetRect().y - overflowAbove >= + aBuilder->GetVisibleRect().YMost()) { + break; + } + f->BuildDisplayListForChild(aBuilder, kid, 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, aLists); + + if (cursor) { + if (!cursor->AppendFrame(kid)) { + f->ClearRowCursor(); + return; + } + } + + kid = kid->GetNextSibling(); + } + if (cursor) { + cursor->FinishBuildingCursor(); + } +} + +void nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + DisplayOutsetBoxShadow(aBuilder, aLists.BorderBackground()); + + for (nsTableRowFrame* row = GetFirstRow(); row; row = row->GetNextRow()) { + if (!aBuilder->GetDirtyRect().Intersects(row->InkOverflowRect() + + row->GetNormalPosition())) { + continue; + } + row->PaintCellBackgroundsForFrame(this, aBuilder, aLists, + row->GetNormalPosition()); + } + + DisplayInsetBoxShadow(aBuilder, aLists.BorderBackground()); + + DisplayOutline(aBuilder, aLists); + + DisplayRows(aBuilder, this, aLists); +} + +LogicalSides nsTableRowGroupFrame::GetLogicalSkipSides() const { + LogicalSides skip(mWritingMode); + if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone)) { + return skip; + } + + if (GetPrevInFlow()) { + skip |= eLogicalSideBitsBStart; + } + if (GetNextInFlow()) { + skip |= eLogicalSideBitsBEnd; + } + return skip; +} + +// Position and size aKidFrame and update our reflow input. +void nsTableRowGroupFrame::PlaceChild( + nsPresContext* aPresContext, TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame, const ReflowInput& aKidReflowInput, WritingMode aWM, + const LogicalPoint& aKidPosition, const nsSize& aContainerSize, + ReflowOutput& aDesiredSize, const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidInkOverflow) { + bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + + // Place and size the child + FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, &aKidReflowInput, + aWM, aKidPosition, aContainerSize, + ReflowChildFlags::ApplyRelativePositioning); + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse()) { + nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect, + aOriginalKidInkOverflow, isFirstReflow); + } + + // Adjust the running block-offset + aReflowInput.mBCoord += aDesiredSize.BSize(aWM); + + // If our block-size is constrained then update the available bsize + if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(aWM)) { + aReflowInput.mAvailSize.BSize(aWM) -= aDesiredSize.BSize(aWM); + } +} + +void nsTableRowGroupFrame::InitChildReflowInput(nsPresContext* aPresContext, + bool aBorderCollapse, + ReflowInput& aReflowInput) { + const auto childWM = aReflowInput.GetWritingMode(); + LogicalMargin border(childWM); + if (aBorderCollapse) { + auto* rowFrame = static_cast(aReflowInput.mFrame); + border = rowFrame->GetBCBorderWidth(childWM); + } + const LogicalMargin zeroPadding(childWM); + aReflowInput.Init(aPresContext, Nothing(), Some(border), Some(zeroPadding)); +} + +static void CacheRowBSizesForPrinting(nsTableRowFrame* aFirstRow, + WritingMode aWM) { + for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) { + if (!row->GetPrevInFlow()) { + row->SetUnpaginatedBSize(row->BSize(aWM)); + } + } +} + +void nsTableRowGroupFrame::ReflowChildren( + nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + TableRowGroupReflowInput& aReflowInput, nsReflowStatus& aStatus, + bool* aPageBreakBeforeEnd) { + if (aPageBreakBeforeEnd) { + *aPageBreakBeforeEnd = false; + } + + WritingMode wm = aReflowInput.mReflowInput.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.mAvailSize.BSize(wm); + + bool reflowAllKids = aReflowInput.mReflowInput.ShouldReflowAllKids() || + tableFrame->IsGeometryDirty() || + tableFrame->NeedToCollapse(); + + // 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.mReflowInput.ComputedSizeAsContainerIfConstrained(); + + nsIFrame* prevKidFrame = nullptr; + for (nsTableRowFrame* kidFrame = GetFirstRow(); kidFrame; + prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextRow()) { + const nscoord rowSpacing = + tableFrame->GetRowSpacing(kidFrame->GetRowIndex()); + + // Reflow the row frame + if (reflowAllKids || kidFrame->IsSubtreeDirty() || + (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow && + (isPaginated || + kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) { + LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize); + nsRect oldKidInkOverflow = kidFrame->InkOverflowRect(); + + ReflowOutput kidDesiredSize(aReflowInput.mReflowInput); + + // 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.mAvailSize; + kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + ReflowInput kidReflowInput(aPresContext, aReflowInput.mReflowInput, + kidFrame, kidAvailSize, Nothing(), + ReflowInput::InitFlag::CallerWillInit); + InitChildReflowInput(aPresContext, borderCollapse, kidReflowInput); + + // This can indicate that columns were resized. + if (aReflowInput.mReflowInput.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.mBCoord); + ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, wm, + kidPosition, containerSize, ReflowChildFlags::Default, + aStatus); + + // Place the child + PlaceChild(aPresContext, aReflowInput, kidFrame, kidReflowInput, wm, + kidPosition, containerSize, kidDesiredSize, + oldKidRect.GetPhysicalRect(wm, containerSize), + oldKidInkOverflow); + aReflowInput.mBCoord += rowSpacing; + + if (!reflowAllKids) { + if (IsSimpleRowFrame(tableFrame, kidFrame)) { + // Inform the row of its new bsize. + kidFrame->DidResize(); + // the overflow area may have changed inflate the overflow area + const nsStylePosition* stylePos = StylePosition(); + if (tableFrame->IsAutoBSize(wm) && + !stylePos->BSize(wm).ConvertsToLength()) { + // Because other cells in the row may need to be aligned + // differently, repaint the entire row + InvalidateFrame(); + } else if (oldKidRect.BSize(wm) != kidDesiredSize.BSize(wm)) { + needToCalcRowBSizes = true; + } + } else { + needToCalcRowBSizes = true; + } + } + + if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) { + nsTableRowFrame* nextRow = kidFrame->GetNextRow(); + if (nextRow) { + *aPageBreakBeforeEnd = + nsTableFrame::PageBreakAfter(kidFrame, nextRow); + } + } + } else { + // Move a child that was skipped during a reflow. + const LogicalPoint oldPosition = + kidFrame->GetLogicalNormalPosition(wm, containerSize); + if (oldPosition.B(wm) != aReflowInput.mBCoord) { + kidFrame->InvalidateFrameSubtree(); + const LogicalPoint offset(wm, 0, + aReflowInput.mBCoord - oldPosition.B(wm)); + kidFrame->MovePositionBy(wm, offset); + nsTableFrame::RePositionViews(kidFrame); + kidFrame->InvalidateFrameSubtree(); + } + + // Adjust the running b-offset so we know where the next row should be + // placed + nscoord bSize = kidFrame->BSize(wm) + rowSpacing; + aReflowInput.mBCoord += bSize; + + if (NS_UNCONSTRAINEDSIZE != aReflowInput.mAvailSize.BSize(wm)) { + aReflowInput.mAvailSize.BSize(wm) -= bSize; + } + } + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); + } + + if (GetFirstRow()) { + aReflowInput.mBCoord -= + tableFrame->GetRowSpacing(GetStartRowIndex() + GetRowCount()); + } + + // Return our desired rect + aDesiredSize.ISize(wm) = aReflowInput.mReflowInput.AvailableISize(); + aDesiredSize.BSize(wm) = aReflowInput.mBCoord; + + if (aReflowInput.mReflowInput.mFlags.mSpecialBSizeReflow) { + DidResizeRows(aDesiredSize); + if (isPaginated) { + CacheRowBSizesForPrinting(GetFirstRow(), wm); + } + } else if (needToCalcRowBSizes) { + CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.mReflowInput); + if (!reflowAllKids) { + InvalidateFrame(); + } + } +} + +nsTableRowFrame* nsTableRowGroupFrame::GetFirstRow() const { + nsIFrame* firstChild = mFrames.FirstChild(); + MOZ_ASSERT( + !firstChild || static_cast(do_QueryFrame(firstChild)), + "How do we have a non-row child?"); + return static_cast(firstChild); +} + +nsTableRowFrame* nsTableRowGroupFrame::GetLastRow() const { + nsIFrame* lastChild = mFrames.LastChild(); + MOZ_ASSERT( + !lastChild || static_cast(do_QueryFrame(lastChild)), + "How do we have a non-row child?"); + return static_cast(lastChild); +} + +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; + + AutoTArray rowInfo; + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + rowInfo.AppendElements(numRows); + + 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) { + const nscoord rowSpacing = + 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 += rowSpacing; + } + 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(); + cellDesSize.BSize(wm) = rowFrame->CalcCellActualBSize( + 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 rowInkOverflow = rowFrame->InkOverflowRect(); + 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, rowInkOverflow, + 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(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 = StyleVisibility::Collapse == groupVis->mVisible; + if (collapseGroup) { + tableFrame->SetNeedToCollapse(true); + } + + OverflowAreas 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 oldGroupInkOverflow = InkOverflowRect(); + + 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, oldGroupInkOverflow, + false); + + return bGroupOffset; +} + +nsTableRowFrame* nsTableRowGroupFrame::CreateContinuingRowFrame( + nsIFrame* aRowFrame) { + // Create the continuing frame which will create continuing cell frames. + auto* contRowFrame = static_cast( + PresShell()->FrameConstructor()->CreateContinuingFrame(aRowFrame, this)); + + // Add the continuing row frame to the child list. + mFrames.InsertFrame(nullptr, aRowFrame, contRowFrame); + + // Push the continuing row frame and the frames that follow. + // This needs to match `UndoContinuedRow`. + PushChildrenToOverflow(contRowFrame, aRowFrame); + + return contRowFrame; +} + +// 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, + const nsSize& aContainerSize, nsTableRowFrame*& aContRow, + nsTableRowFrame*& aFirstTruncatedRow, nscoord& aDesiredBSize) { + NS_ASSERTION(aSpanningRowBEnd >= 0, "Can't split negative bsizes"); + aFirstTruncatedRow = nullptr; + aDesiredBSize = 0; + + const WritingMode wm = aReflowInput.GetWritingMode(); + 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(); + const LogicalRect rowRect = row->GetLogicalNormalRect(wm, aContainerSize); + // 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 + const nscoord cellAvailBSize = aSpanningRowBEnd - rowRect.BStart(wm); + NS_ASSERTION(cellAvailBSize >= 0, "No space for cell?"); + bool isTopOfPage = (row == aFirstRow) && aFirstRowIsTopOfPage; + + LogicalSize rowAvailSize( + wm, aReflowInput.AvailableISize(), + std::max(aReflowInput.AvailableBSize() - rowRect.BStart(wm), 0)); + // Don't let the available block-size exceed what CalculateRowBSizes set + // for it. + rowAvailSize.BSize(wm) = + std::min(rowAvailSize.BSize(wm), rowRect.BSize(wm)); + ReflowInput rowReflowInput( + aPresContext, aReflowInput, row, + rowAvailSize.ConvertTo(row->GetWritingMode(), wm), Nothing(), + ReflowInput::InitFlag::CallerWillInit); + 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, rowRect.BStart(wm) + cellBSize); + if (status.IsComplete()) { + 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) { + aContRow = CreateContinuingRowFrame(aLastRow); + } + 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( + PresShell()->FrameConstructor()->CreateContinuingFrame( + cell, aLastRow)); + uint32_t colIndex = cell->ColIndex(); + aContRow->InsertCellFrame(contCell, colIndex); + } + } + } + } + } + } + if (!haveRowSpan) { + aDesiredBSize = aLastRow->GetLogicalNormalRect(wm, aContainerSize).BEnd(wm); + } +} + +// 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(); + MOZ_ASSERT(mFrames.ContainsFrame(rowBefore), + "rowBefore not in our frame list?"); + + // Needs to match `CreateContinuingRowFrame` - we're assuming that continued + // frames always go into overflow frames list. + AutoFrameListPtr overflows(aPresContext, StealOverflowFrames()); + if (!rowBefore || !overflows || overflows->IsEmpty() || + overflows->FirstChild() != aRow) { + NS_ERROR("invalid continued row"); + return; + } + + DestroyContext context(aPresContext->PresShell()); + // 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(context, aRow); + + // Put the overflow rows into our child list + if (!overflows->IsEmpty()) { + mFrames.InsertFrames(nullptr, rowBefore, std::move(*overflows)); + } +} + +void nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsTableFrame* aTableFrame, + nsReflowStatus& aStatus, + bool aRowForcedPageBreak) { + MOZ_ASSERT(aPresContext->IsPaginated(), + "SplitRowGroup currently supports only paged media"); + + const WritingMode wm = aReflowInput.GetWritingMode(); + nsTableRowFrame* prevRowFrame = nullptr; + aDesiredSize.BSize(wm) = 0; + aDesiredSize.SetOverflowAreasToDesiredBounds(); + + const nscoord availISize = aReflowInput.AvailableISize(); + const nscoord availBSize = aReflowInput.AvailableBSize(); + const nsSize containerSize = + aReflowInput.ComputedSizeAsContainerIfConstrained(); + const bool borderCollapse = aTableFrame->IsBorderCollapse(); + + const nscoord pageBSize = + LogicalSize(wm, aPresContext->GetPageSize()).BSize(wm); + NS_ASSERTION(pageBSize != 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; + const nscoord rowSpacing = + aTableFrame->GetRowSpacing(rowFrame->GetRowIndex()); + const LogicalRect rowRect = + rowFrame->GetLogicalNormalRect(wm, containerSize); + // See if the row fits on this page + if (rowRect.BEnd(wm) > availBSize) { + 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 || + (availBSize - aDesiredSize.BSize(wm) > pageBSize / 20)) { + LogicalSize availSize(wm, availISize, + std::max(availBSize - rowRect.BStart(wm), 0)); + // Don't let the available block-size exceed what CalculateRowBSizes set + // for it. + availSize.BSize(wm) = std::min(availSize.BSize(wm), rowRect.BSize(wm)); + + ReflowInput rowReflowInput( + aPresContext, aReflowInput, rowFrame, + availSize.ConvertTo(rowFrame->GetWritingMode(), wm), Nothing(), + ReflowInput::InitFlag::CallerWillInit); + + 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 oldRowInkOverflow = rowFrame->InkOverflowRect(); + + // Reflow the cell with the constrained bsize. A cell with rowspan >1 + // will get this reflow later during SplitSpanningCells. + // + // Note: We just pass dummy aPos and aContainerSize since we are not + // moving the row frame. + const LogicalPoint dummyPos(wm); + const nsSize dummyContainerSize; + ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowInput, wm, + dummyPos, dummyContainerSize, ReflowChildFlags::NoMoveFrame, + aStatus); + FinishReflowChild(rowFrame, aPresContext, rowMetrics, &rowReflowInput, + wm, dummyPos, dummyContainerSize, + ReflowChildFlags::NoMoveFrame); + rowFrame->DidResize(); + + if (!aRowForcedPageBreak && !aStatus.IsFullyComplete() && + ShouldAvoidBreakInside(aReflowInput)) { + aStatus.SetInlineLineBreakBeforeAndReset(); + break; + } + + nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect, + oldRowInkOverflow, false); + + if (aStatus.IsIncomplete()) { + // The row frame is incomplete and all of the rowspan 1 cells' block + // frames split + if ((rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize()) || + 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 block-size exceeded the + // avail block-size, then there will be data loss. + NS_ASSERTION( + rowMetrics.BSize(wm) <= rowReflowInput.AvailableBSize(), + "Data loss - incomplete row needed more block-size than " + "available, on top of page!"); + contRow = CreateContinuingRowFrame(rowFrame); + aDesiredSize.BSize(wm) += rowMetrics.BSize(wm); + if (prevRowFrame) { + aDesiredSize.BSize(wm) += rowSpacing; + } + } else { + // Put the row on the next page to give it more block-size. + rowIsOnPage = false; + } + } else { + // The row frame is complete because either (1) its minimum block-size + // is greater than the available block-size we gave it, or (2) it may + // have been given a larger block-size through style than its content, + // or (3) it contains a rowspan >1 cell which hasn't been reflowed + // with a constrained block-size yet (we will find out when + // SplitSpanningCells is called below) + if (rowMetrics.BSize(wm) > availSize.BSize(wm) || + (aStatus.IsInlineBreakBefore() && !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.Reset(); + aStatus.SetIncomplete(); + } + aDesiredSize.BSize(wm) += rowMetrics.BSize(wm); + if (prevRowFrame) { + aDesiredSize.BSize(wm) += rowSpacing; + } + NS_WARNING( + "Data loss - complete row needed more block-size 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 block-size. + rowIsOnPage = false; + } + } + } + } else { + // Put the row on the next page to give it more block-size. + rowIsOnPage = false; + } + + nsTableRowFrame* lastRowThisPage = rowFrame; + nscoord spanningRowBEnd = availBSize; + if (!rowIsOnPage) { + NS_ASSERTION(!contRow, + "We should not have created a continuation if none of " + "this row fits"); + if (!prevRowFrame || + (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowInput))) { + aStatus.SetInlineLineBreakBeforeAndReset(); + break; + } + spanningRowBEnd = + prevRowFrame->GetLogicalNormalRect(wm, containerSize).BEnd(wm); + lastRowThisPage = prevRowFrame; + aStatus.Reset(); + aStatus.SetIncomplete(); + } + + // reflow the cells with rowspan >1 that occur on the page + nsTableRowFrame* firstTruncatedRow; + nscoord bMost; + SplitSpanningCells(aPresContext, aReflowInput, aTableFrame, + firstRowThisPage, lastRowThisPage, + aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd, + containerSize, 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.BSize(wm) = rowRect.BEnd(wm); + aStatus.Reset(); + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } else { + // Try to put firstTruncateRow on the next page + nsTableRowFrame* rowBefore = firstTruncatedRow->GetPrevRow(); + const nscoord oldSpanningRowBEnd = spanningRowBEnd; + spanningRowBEnd = + rowBefore->GetLogicalNormalRect(wm, containerSize).BEnd(wm); + + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + nsTableRowFrame* oldLastRowThisPage = lastRowThisPage; + lastRowThisPage = rowBefore; + aStatus.Reset(); + aStatus.SetIncomplete(); + + // Call SplitSpanningCells again with rowBefore as the last row on the + // page + SplitSpanningCells(aPresContext, aReflowInput, aTableFrame, + firstRowThisPage, rowBefore, + aReflowInput.mFlags.mIsTopOfPage, spanningRowBEnd, + containerSize, contRow, firstTruncatedRow, + aDesiredSize.BSize(wm)); + 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; + spanningRowBEnd = oldSpanningRowBEnd; + SplitSpanningCells(aPresContext, aReflowInput, aTableFrame, + firstRowThisPage, lastRowThisPage, + aReflowInput.mFlags.mIsTopOfPage, + spanningRowBEnd, containerSize, contRow, + firstTruncatedRow, aDesiredSize.BSize(wm)); + NS_WARNING("data loss in a row spanned cell"); + } else { + // Let our parent reflow us again with more space + aDesiredSize.BSize(wm) = rowRect.BEnd(wm); + aStatus.Reset(); + UndoContinuedRow(aPresContext, contRow); + contRow = nullptr; + } + } + } + } else { + aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bMost); + if (contRow) { + aStatus.Reset(); + aStatus.SetIncomplete(); + } + } + if (aStatus.IsIncomplete() && !contRow) { + if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) { + PushChildrenToOverflow(nextRow, lastRowThisPage); + } + } else if (aStatus.IsComplete() && lastRowThisPage) { + // Our size from the unconstrained reflow exceeded the constrained + // available space but our size in the constrained reflow is Complete. + // This can happen when a non-zero block-end margin is suppressed in + // nsBlockFrame::ComputeFinalSize. + if (nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow()) { + aStatus.Reset(); + aStatus.SetIncomplete(); + PushChildrenToOverflow(nextRow, lastRowThisPage); + } + } + break; + } + aDesiredSize.BSize(wm) = rowRect.BEnd(wm); + prevRowFrame = rowFrame; + // see if there is a page break after the row + nsTableRowFrame* nextRow = rowFrame->GetNextRow(); + if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) { + PushChildrenToOverflow(nextRow, rowFrame); + aStatus.Reset(); + aStatus.SetIncomplete(); + break; + } + // After the 1st row that has a block-size, we can't be on top of the page + // anymore. + isTopOfPage = isTopOfPage && rowRect.BEnd(wm) == 0; + } +} + +/** 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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + // 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); + const nsStyleVisibility* groupVis = StyleVisibility(); + bool collapseGroup = StyleVisibility::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. + WritingMode wm = aReflowInput.GetWritingMode(); + if (aReflowInput.mFlags.mTableIsSplittable && + aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && + (aStatus.IsIncomplete() || splitDueToPageBreak || + aDesiredSize.BSize(wm) > aReflowInput.AvailableBSize())) { + // Nope, find a place to split the row group + auto& mutableRIFlags = const_cast(aReflowInput.mFlags); + const bool savedSpecialBSizeReflow = mutableRIFlags.mSpecialBSizeReflow; + mutableRIFlags.mSpecialBSizeReflow = false; + + SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus, + splitDueToPageBreak); + + mutableRIFlags.mSpecialBSizeReflow = savedSpecialBSizeReflow; + } + + // 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()) { + aStatus.SetIncomplete(); + } + + 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. + 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) && + aDesiredSize.Size(wm) != GetLogicalSize(wm)) { + InvalidateFrame(); + } + + FinishAndStoreOverflow(&aDesiredSize); + + // Any absolutely-positioned children will get reflowed in + // nsIFrame::FixupPositionedTableParts in another pass, so propagate our + // dirtiness to them before our parent clears our dirty bits. + PushDirtyBitToAbsoluteFrames(); +} + +bool nsTableRowGroupFrame::ComputeCustomOverflow( + OverflowAreas& aOverflowAreas) { + // Row cursor invariants depend on the ink 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::DidSetComputedStyle( + ComputedStyle* aOldComputedStyle) { + nsContainerFrame::DidSetComputedStyle(aOldComputedStyle); + nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle); + + if (!aOldComputedStyle) { + return; // avoid the following on init + } + + nsTableFrame* tableFrame = GetTableFrame(); + if (tableFrame->IsBorderCollapse() && + tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) { + TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(), + GetRowCount()); + tableFrame->AddBCDamageArea(damageArea); + } +} + +void nsTableRowGroupFrame::AppendFrames(ChildListID aListID, + nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "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 (nsIFrame* f : aFrameList) { + nsTableRowFrame* rowFrame = do_QueryFrame(f); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION( + mozilla::StyleDisplay::TableRow == f->StyleDisplay()->mDisplay, + "wrong display type on rowframe"); + rows.AppendElement(rowFrame); + } + } + + int32_t rowIndex = GetRowCount(); + // Append the frames to the sibling chain + mFrames.AppendFrames(nullptr, std::move(aFrameList)); + + if (rows.Length() > 0) { + nsTableFrame* tableFrame = GetTableFrame(); + tableFrame->AppendRows(this, rowIndex, rows); + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void nsTableRowGroupFrame::InsertFrames( + ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "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 (nsIFrame* f : aFrameList) { + nsTableRowFrame* rowFrame = do_QueryFrame(f); + NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up"); + if (rowFrame) { + NS_ASSERTION( + mozilla::StyleDisplay::TableRow == f->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, std::move(aFrameList)); + + int32_t numRows = rows.Length(); + if (numRows > 0) { + nsTableRowFrame* prevRow = + (nsTableRowFrame*)nsTableFrame::GetFrameAtOrBefore( + this, aPrevFrame, LayoutFrameType::TableRow); + int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex; + tableFrame->InsertRows(this, rows, rowIndex, true); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } +} + +void nsTableRowGroupFrame::RemoveFrame(DestroyContext& aContext, + ChildListID aListID, + nsIFrame* aOldFrame) { + NS_ASSERTION(aListID == FrameChildListID::Principal, "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); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + tableFrame->SetGeometryDirty(); + } + mFrames.DestroyFrame(aContext, 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; +} + +/** find page break before the first row **/ +bool nsTableRowGroupFrame::HasInternalBreakBefore() const { + nsIFrame* firstChild = mFrames.FirstChild(); + if (!firstChild) return false; + return firstChild->StyleDisplay()->BreakBefore(); +} + +/** find page break after the last row **/ +bool nsTableRowGroupFrame::HasInternalBreakAfter() const { + nsIFrame* lastChild = mFrames.LastChild(); + if (!lastChild) return false; + return lastChild->StyleDisplay()->BreakAfter(); +} +/* ----- global methods ----- */ + +nsTableRowGroupFrame* NS_NewTableRowGroupFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsTableRowGroupFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"TableRowGroup"_ns, aResult); +} +#endif + +LogicalMargin nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM) { + LogicalMargin border(aWM); + nsTableRowFrame* firstRowFrame = GetFirstRow(); + if (!firstRowFrame) { + return border; + } + nsTableRowFrame* lastRowFrame = firstRowFrame; + for (nsTableRowFrame* rowFrame = firstRowFrame->GetNextRow(); rowFrame; + rowFrame = rowFrame->GetNextRow()) { + lastRowFrame = rowFrame; + } + border.BStart(aWM) = PresContext()->DevPixelsToAppUnits( + firstRowFrame->GetBStartBCBorderWidth()); + border.BEnd(aWM) = + PresContext()->DevPixelsToAppUnits(lastRowFrame->GetBEndBCBorderWidth()); + return border; +} + +// nsILineIterator methods +int32_t nsTableRowGroupFrame::GetNumLines() const { return GetRowCount(); } + +bool nsTableRowGroupFrame::IsLineIteratorFlowRTL() { + return StyleDirection::Rtl == GetTableFrame()->StyleVisibility()->mDirection; +} + +Result nsTableRowGroupFrame::GetLine( + int32_t aLineNumber) { + if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) { + return Err(NS_ERROR_FAILURE); + } + LineInfo structure; + nsTableFrame* table = GetTableFrame(); + nsTableCellMap* cellMap = table->GetCellMap(); + aLineNumber += GetStartRowIndex(); + + structure.mNumFramesOnLine = + cellMap->GetNumCellsOriginatingInRow(aLineNumber); + if (structure.mNumFramesOnLine == 0) { + return structure; + } + int32_t colCount = table->GetColCount(); + for (int32_t i = 0; i < colCount; i++) { + CellData* data = cellMap->GetDataAt(aLineNumber, i); + if (data && data->IsOrig()) { + structure.mFirstFrameOnLine = (nsIFrame*)data->GetCellFrame(); + nsIFrame* parent = structure.mFirstFrameOnLine->GetParent(); + structure.mLineBounds = parent->GetRect(); + return structure; + } + } + MOZ_ASSERT_UNREACHABLE("cellmap is lying"); + return Err(NS_ERROR_FAILURE); +} + +int32_t nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame, + int32_t aStartLine) { + NS_ENSURE_TRUE(aFrame, -1); + + nsTableRowFrame* rowFrame = do_QueryFrame(aFrame); + if (MOZ_UNLIKELY(!rowFrame)) { + // When we do not have valid table structure in the DOM tree, somebody wants + // to check the line number with an out-of-flow child of this frame because + // its parent frame is set to this frame. Otherwise, the caller must have + // a bug. + MOZ_ASSERT(aFrame->GetParent() == this); + MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); + return -1; + } + + 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(); + + *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 = StyleDirection::Rtl == table->StyleVisibility()->mDirection; + + LineFrameFinder finder(aPos, table->GetSize(), table->GetWritingMode(), + isRTL); + + int32_t n = numCells; + while (n--) { + finder.Scan(frame); + if (finder.IsDone()) { + break; + } + frame = frame->GetNextSibling(); + } + finder.Finish(aFrameFound, aPosIsBeforeFirstFrame, aPosIsAfterLastFrame); + 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); + RemoveProperty(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(); + SetProperty(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 = GetProperty(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->GetRect().YMost() + property->mOverflowBelow > aY) { + --cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + while (cursorIndex + 1 < frameCount && + cursorFrame->GetRect().YMost() + property->mOverflowBelow <= aY) { + ++cursorIndex; + cursorFrame = property->mFrames[cursorIndex]; + } + + property->mCursorIndex = cursorIndex; + *aOverflowAbove = property->mOverflowAbove; + return cursorFrame; +} + +bool nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame) { + // The cursor requires a monotonically increasing sequence in order to + // identify which rows can be skipped, and position:relative can move + // rows around such that the overflow areas don't provide this. + // We take the union of the overflow rect, and the frame's 'normal' position + // (excluding position:relative changes) and record the max difference between + // this combined overflow and the frame's rect. + nsRect positionedOverflowRect = aFrame->InkOverflowRect(); + 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); + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + mFrames.AppendElement(aFrame); + return true; +} + +void nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems); + if (GetTableFrame()->IsBorderCollapse()) { + const bool rebuild = StaticPrefs::layout_display_list_retain_sc(); + GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(), + aDisplayItemKey, rebuild); + } +} + +void nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey, + bool aRebuildDisplayItems) { + nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, + aRebuildDisplayItems); + // 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, + aRebuildDisplayItems); +} diff --git a/layout/tables/nsTableRowGroupFrame.h b/layout/tables/nsTableRowGroupFrame.h new file mode 100644 index 0000000000..cbeea1ff13 --- /dev/null +++ b/layout/tables/nsTableRowGroupFrame.h @@ -0,0 +1,377 @@ +/* -*- 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 "nsAtom.h" +#include "nsILineIterator.h" +#include "nsTArray.h" +#include "nsTableFrame.h" +#include "mozilla/WritingModes.h" + +class nsTableRowFrame; +namespace mozilla { +class PresShell; +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 { + public: + NS_DECL_QUERYFRAME + NS_DECL_FRAMEARENA_HELPERS(nsTableRowGroupFrame) + + /** instantiate a new instance of nsTableRowFrame. + * @param aPresShell the pres shell for this frame + * + * @return the frame that was created + */ + friend nsTableRowGroupFrame* NS_NewTableRowGroupFrame( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + virtual ~nsTableRowGroupFrame(); + + // nsIFrame overrides + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override { + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); + if (!aPrevInFlow) { + mWritingMode = GetTableFrame()->GetWritingMode(); + } + } + + void Destroy(DestroyContext&) override; + + /** @see nsIFrame::DidSetComputedStyle */ + void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override; + + void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override; + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override; + + nsMargin GetUsedMargin() const override; + nsMargin GetUsedBorder() const override; + nsMargin GetUsedPadding() const override; + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + 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 + */ + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + + bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override; +#endif + + nsTableRowFrame* GetFirstRow() const; + nsTableRowFrame* GetLastRow() const; + + nsTableFrame* GetTableFrame() const { + nsIFrame* parent = GetParent(); + MOZ_ASSERT(parent && parent->IsTableFrame()); + return static_cast(parent); + } + + /** return the number of child rows (not necessarily == number of child + * frames) */ + int32_t GetRowCount() const; + + /** return the table-relative row index of the first row in this rowgroup. + * if there are no rows, -1 is returned. + */ + int32_t GetStartRowIndex() const; + + /** 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); + + // See nsTableFrame.h + int32_t GetAdjustmentForStoredIndex(int32_t aStoredIndex); + + /* mark rows starting from aStartRowFrame to the next 'aNumRowsToRemove-1' + * number of rows as deleted + */ + void MarkRowsAsDeleted(nsTableRowFrame& aStartRowFrame, + int32_t aNumRowsToDelete); + + // See nsTableFrame.h + void AddDeletedRowIndex(int32_t aDeletedRowStoredIndex); + + /** + * 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 + */ + void InitRepeatedFrame(nsTableRowGroupFrame* aHeaderFooterFrame); + + /** + * Get the total bsize of all the row rects + */ + nscoord GetBSizeBasis(const ReflowInput& aReflowInput); + + mozilla::LogicalMargin GetBCBorderWidth(mozilla::WritingMode aWM); + + /** + * 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: + // 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 + */ + int32_t GetNumLines() const final; + + /** @see nsILineIterator.h IsLineIteratorFlowRTL */ + bool IsLineIteratorFlowRTL() final; + + /** Return structural information about a line. */ + Result GetLine(int32_t aLineNumber) final; + + /** 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. + */ + int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) final; + + /** 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) final; + + /** 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) final; + + // 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(); + + bool CanProvideLineIterator() const final { return true; } + nsILineIterator* GetLineIterator() final { return this; } + + void InvalidateFrame(uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameWithRect(const nsRect& aRect, + uint32_t aDisplayItemKey = 0, + bool aRebuildDisplayItems = true) override; + void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); } + + protected: + explicit nsTableRowGroupFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext); + + void InitChildReflowInput(nsPresContext* aPresContext, bool aBorderCollapse, + ReflowInput& aReflowInput); + + LogicalSides GetLogicalSkipSides() const override; + + void PlaceChild(nsPresContext* aPresContext, + mozilla::TableRowGroupReflowInput& aReflowInput, + nsIFrame* aKidFrame, const ReflowInput& aKidReflowInput, + mozilla::WritingMode aWM, + const mozilla::LogicalPoint& aKidPosition, + const nsSize& aContainerSize, ReflowOutput& aDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidInkOverflow); + + void CalculateRowBSizes(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput); + + void DidResizeRows(ReflowOutput& aDesiredSize); + + /** + * Reflow the frames we've already created + * + * @param aPresContext presentation context to use + * @param aReflowInput current inline state + */ + void ReflowChildren(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + mozilla::TableRowGroupReflowInput& aReflowInput, + nsReflowStatus& aStatus, + bool* aPageBreakBeforeEnd = nullptr); + + void 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 aSpanningRowBEnd, + const nsSize& aContainerSize, + nsTableRowFrame*& aContRowFrame, + nsTableRowFrame*& aFirstTruncatedRow, + nscoord& aDesiredBSize); + + /** + * Create a continuing table row frame, add it to the child list, and then + * push it and its later siblings to our overflow frames list. + */ + nsTableRowFrame* CreateContinuingRowFrame(nsIFrame* aRowFrame); + + bool IsSimpleRowFrame(nsTableFrame* aTableFrame, nsTableRowFrame* aRowFrame); + + void GetNextRowSibling(nsIFrame** aRowFrame); + + void UndoContinuedRow(nsPresContext* aPresContext, nsTableRowFrame* aRow); + + 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); + } +} + +#endif diff --git a/layout/tables/nsTableWrapperFrame.cpp b/layout/tables/nsTableWrapperFrame.cpp new file mode 100644 index 0000000000..a3e957c4fd --- /dev/null +++ b/layout/tables/nsTableWrapperFrame.cpp @@ -0,0 +1,857 @@ +/* -*- 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 "LayoutConstants.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/PresShell.h" +#include "nsFrameManager.h" +#include "nsGridContainerFrame.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsCSSRendering.h" +#include "nsIContent.h" +#include "prinrval.h" +#include "nsGkAtoms.h" +#include "nsHTMLParts.h" +#include "nsDisplayList.h" +#include "nsLayoutUtils.h" +#include "nsIFrameInlines.h" +#include + +using namespace mozilla; +using namespace mozilla::layout; + +nscoord nsTableWrapperFrame::SynthesizeFallbackBaseline( + mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { + const auto marginBlockEnd = GetLogicalUsedMargin(aWM).BEnd(aWM); + if (aWM.IsCentralBaseline()) { + return (BSize(aWM) + marginBlockEnd) / 2; + } + // Our fallback baseline is the block-end margin-edge, with respect to the + // given writing mode. + if (aBaselineGroup == BaselineSharingGroup::Last) { + return -marginBlockEnd; + } + return BSize(aWM) + marginBlockEnd; +} + +Maybe nsTableWrapperFrame::GetNaturalBaselineBOffset( + WritingMode aWM, BaselineSharingGroup aBaselineGroup, + BaselineExportContext aExportContext) const { + // Baseline is determined by row + // (https://drafts.csswg.org/css-align-3/#baseline-export). If the row + // direction is going to be orthogonal to the parent's writing mode, the + // resulting baseline wouldn't be valid, so we use the fallback baseline + // instead. + if (StyleDisplay()->IsContainLayout() || + GetWritingMode().IsOrthogonalTo(aWM)) { + return Nothing{}; + } + auto* innerTable = InnerTableFrame(); + return innerTable + ->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext) + .map([this, aWM, aBaselineGroup, innerTable](nscoord aBaseline) { + auto bStart = innerTable->BStart(aWM, mRect.Size()); + if (aBaselineGroup == BaselineSharingGroup::First) { + return aBaseline + bStart; + } + auto bEnd = bStart + innerTable->BSize(aWM); + return BSize(aWM) - (bEnd - aBaseline); + }); +} + +nsTableWrapperFrame::nsTableWrapperFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext, + ClassID aID) + : nsContainerFrame(aStyle, aPresContext, aID) {} + +nsTableWrapperFrame::~nsTableWrapperFrame() = default; + +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::Destroy(DestroyContext& aContext) { + DestroyAbsoluteFrames(aContext); + mCaptionFrames.DestroyFrames(aContext); + nsContainerFrame::Destroy(aContext); +} + +const nsFrameList& nsTableWrapperFrame::GetChildList( + ChildListID aListID) const { + if (aListID == FrameChildListID::Caption) { + return mCaptionFrames; + } + + return nsContainerFrame::GetChildList(aListID); +} + +void nsTableWrapperFrame::GetChildLists(nsTArray* aLists) const { + nsContainerFrame::GetChildLists(aLists); + mCaptionFrames.AppendIfNonempty(aLists, FrameChildListID::Caption); +} + +void nsTableWrapperFrame::SetInitialChildList(ChildListID aListID, + nsFrameList&& aChildList) { + if (FrameChildListID::Caption == aListID) { +#ifdef DEBUG + nsIFrame::VerifyDirtyBitSet(aChildList); + for (nsIFrame* f : aChildList) { + MOZ_ASSERT(f->GetParent() == this, "Unexpected parent"); + } +#endif + // the frame constructor already checked for table-caption display type + MOZ_ASSERT(mCaptionFrames.IsEmpty(), + "already have child frames in CaptionList"); + mCaptionFrames = std::move(aChildList); + } else { + MOZ_ASSERT(FrameChildListID::Principal != aListID || + (aChildList.FirstChild() && + aChildList.FirstChild() == aChildList.LastChild() && + aChildList.FirstChild()->IsTableFrame()), + "expected a single table frame in principal child list"); + nsContainerFrame::SetInitialChildList(aListID, std::move(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(FrameChildListID::Caption == aListID, "unexpected child list"); + MOZ_ASSERT(aFrameList.IsEmpty() || aFrameList.FirstChild()->IsTableCaption(), + "appending non-caption frame to captionList"); + mCaptionFrames.AppendFrames(nullptr, std::move(aFrameList)); + + // Reflow the new caption frame. It's already marked dirty, so + // just tell the pres shell. + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + // The presence of caption frames makes us sort our display + // list differently, so mark us as changed for the new + // ordering. + MarkNeedsDisplayItemRebuild(); +} + +void nsTableWrapperFrame::InsertFrames( + ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { + MOZ_ASSERT(FrameChildListID::Caption == 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, std::move(aFrameList)); + + // Reflow the new caption frame. It's already marked dirty, so + // just tell the pres shell. + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + MarkNeedsDisplayItemRebuild(); +} + +void nsTableWrapperFrame::RemoveFrame(DestroyContext& aContext, + 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 + MOZ_ASSERT(FrameChildListID::Caption == aListID, "can't remove inner frame"); + + // Remove the frame and destroy it + mCaptionFrames.DestroyFrame(aContext, aOldFrame); + + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_HAS_DIRTY_CHILDREN); + MarkNeedsDisplayItemRebuild(); +} + +void nsTableWrapperFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + // No border or background is painted because they belong to the inner table. + // The outline belongs to the wrapper frame so it can contain the caption. + + // 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, aLists); + DisplayOutline(aBuilder, aLists); + return; + } + + nsDisplayListCollection set(aBuilder); + BuildDisplayListForInnerTable(aBuilder, set); + + nsDisplayListSet captionSet(set, set.BlockBorderBackgrounds()); + BuildDisplayListForChild(aBuilder, mCaptionFrames.FirstChild(), captionSet); + + // Now we have to sort everything by content order, since the caption + // may be somewhere inside the table. + // We don't sort BlockBorderBackgrounds and BorderBackgrounds because the + // display items in those lists should stay out of content order in order to + // follow the rules in https://www.w3.org/TR/CSS21/zindex.html#painting-order + // and paint the caption background after all of the rest. + set.Floats()->SortByContentOrder(GetContent()); + set.Content()->SortByContentOrder(GetContent()); + set.PositionedDescendants()->SortByContentOrder(GetContent()); + set.Outlines()->SortByContentOrder(GetContent()); + set.MoveTo(aLists); + + DisplayOutline(aBuilder, aLists); +} + +void nsTableWrapperFrame::BuildDisplayListForInnerTable( + nsDisplayListBuilder* aBuilder, 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, aLists); + kid = kid->GetNextSibling(); + } +} + +ComputedStyle* nsTableWrapperFrame::GetParentComputedStyle( + nsIFrame** aProviderFrame) const { + // The table wrapper frame and the (inner) table frame split the style + // data by giving the table frame the ComputedStyle associated with + // the table content node and creating a ComputedStyle for the wrapper + // frame that is a *child* of the table frame's ComputedStyle, + // 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 ComputedStyle is a leaf. + + return (*aProviderFrame = InnerTableFrame())->Style(); +} + +/* virtual */ +nscoord nsTableWrapperFrame::GetMinISize(gfxContext* aRenderingContext) { + nscoord iSize = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, InnerTableFrame(), IntrinsicISizeType::MinISize); + DISPLAY_MIN_INLINE_SIZE(this, iSize); + if (mCaptionFrames.NotEmpty()) { + nscoord capISize = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, mCaptionFrames.FirstChild(), + IntrinsicISizeType::MinISize); + if (capISize > iSize) { + iSize = capISize; + } + } + return iSize; +} + +/* virtual */ +nscoord nsTableWrapperFrame::GetPrefISize(gfxContext* aRenderingContext) { + nscoord maxISize; + DISPLAY_PREF_INLINE_SIZE(this, maxISize); + + maxISize = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, InnerTableFrame(), IntrinsicISizeType::PrefISize); + if (mCaptionFrames.NotEmpty()) { + // Don't let the caption's pref isize expand the table's pref isize. + const nscoord capMinISize = nsLayoutUtils::IntrinsicForContainer( + aRenderingContext, mCaptionFrames.FirstChild(), + IntrinsicISizeType::MinISize); + maxISize = std::max(maxISize, capMinISize); + } + return maxISize; +} + +LogicalSize nsTableWrapperFrame::InnerTableShrinkWrapSize( + gfxContext* aRenderingContext, nsTableFrame* aTableFrame, WritingMode aWM, + const LogicalSize& aCBSize, nscoord aAvailableISize, + const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) const { + MOZ_ASSERT(InnerTableFrame() == aTableFrame); + + AutoMaybeDisableFontInflation an(aTableFrame); + + Maybe collapseBorder; + Maybe collapsePadding; + aTableFrame->GetCollapsedBorderPadding(collapseBorder, collapsePadding); + + SizeComputationInput input(aTableFrame, aRenderingContext, aWM, + aCBSize.ISize(aWM), collapseBorder, + collapsePadding); + LogicalSize marginSize(aWM); // Inner table doesn't have any margin + LogicalSize bpSize = input.ComputedLogicalBorderPadding(aWM).Size(aWM); + + // Note that we pass an empty caption-area here (rather than the caption's + // actual size). This is fine because: + // + // 1) nsTableWrapperFrame::ComputeSize() only uses the size returned by this + // method (indirectly via calling nsTableWrapperFrame::ComputeAutoSize()) + // if it get a aSizeOverrides arg containing any size overrides with + // mApplyOverridesVerbatim=true. The aSizeOverrides arg is passed to this + // method without any modifications. + // + // 2) With 1), that means the aSizeOverrides passing into this method should + // be applied to the inner table directly, so we don't need to subtract + // caption-area when preparing innerOverrides for + // nsTableFrame::ComputeSize(). + StyleSizeOverrides innerOverrides = ComputeSizeOverridesForInnerTable( + aTableFrame, aSizeOverrides, bpSize, /* aBSizeOccupiedByCaption = */ 0); + auto size = + aTableFrame + ->ComputeSize(aRenderingContext, aWM, aCBSize, aAvailableISize, + marginSize, bpSize, innerOverrides, aFlags) + .mLogicalSize; + size.ISize(aWM) += bpSize.ISize(aWM); + if (size.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { + size.BSize(aWM) += bpSize.BSize(aWM); + } + return size; +} + +LogicalSize nsTableWrapperFrame::CaptionShrinkWrapSize( + gfxContext* aRenderingContext, nsIFrame* aCaptionFrame, WritingMode aWM, + const LogicalSize& aCBSize, nscoord aAvailableISize, + ComputeSizeFlags aFlags) const { + MOZ_ASSERT(aCaptionFrame == mCaptionFrames.FirstChild()); + + AutoMaybeDisableFontInflation an(aCaptionFrame); + + SizeComputationInput input(aCaptionFrame, aRenderingContext, aWM, + aCBSize.ISize(aWM)); + LogicalSize marginSize = input.ComputedLogicalMargin(aWM).Size(aWM); + LogicalSize bpSize = input.ComputedLogicalBorderPadding(aWM).Size(aWM); + + auto size = aCaptionFrame + ->ComputeSize(aRenderingContext, aWM, aCBSize, + aAvailableISize, marginSize, bpSize, {}, aFlags) + .mLogicalSize; + size.ISize(aWM) += (marginSize.ISize(aWM) + bpSize.ISize(aWM)); + if (size.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { + size.BSize(aWM) += (marginSize.BSize(aWM) + bpSize.BSize(aWM)); + } + return size; +} + +StyleSize nsTableWrapperFrame::ReduceStyleSizeBy( + const StyleSize& aStyleSize, const nscoord aAmountToReduce) const { + MOZ_ASSERT(aStyleSize.ConvertsToLength(), "Only handles 'Length' StyleSize!"); + const nscoord size = std::max(0, aStyleSize.ToLength() - aAmountToReduce); + return StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(size)); +} + +StyleSizeOverrides nsTableWrapperFrame::ComputeSizeOverridesForInnerTable( + const nsTableFrame* aTableFrame, + const StyleSizeOverrides& aWrapperSizeOverrides, + const LogicalSize& aBorderPadding, nscoord aBSizeOccupiedByCaption) const { + if (aWrapperSizeOverrides.mApplyOverridesVerbatim || + !aWrapperSizeOverrides.HasAnyLengthOverrides()) { + // We are asked to apply the size overrides directly to the inner table, or + // there's no 'Length' size overrides. No need to tweak the size overrides. + return aWrapperSizeOverrides; + } + + const auto wm = aTableFrame->GetWritingMode(); + LogicalSize areaOccupied(wm, 0, aBSizeOccupiedByCaption); + if (aTableFrame->StylePosition()->mBoxSizing == StyleBoxSizing::Content) { + // If the inner table frame has 'box-sizing: content', enlarge the occupied + // area by adding border & padding because they should also be subtracted + // from the size overrides. + areaOccupied += aBorderPadding; + } + + StyleSizeOverrides innerSizeOverrides; + const auto& wrapperISize = aWrapperSizeOverrides.mStyleISize; + if (wrapperISize) { + MOZ_ASSERT(!wrapperISize->HasPercent(), + "Table doesn't support size overrides containing percentages!"); + innerSizeOverrides.mStyleISize.emplace( + wrapperISize->ConvertsToLength() + ? ReduceStyleSizeBy(*wrapperISize, areaOccupied.ISize(wm)) + : *wrapperISize); + } + + const auto& wrapperBSize = aWrapperSizeOverrides.mStyleBSize; + if (wrapperBSize) { + MOZ_ASSERT(!wrapperBSize->HasPercent(), + "Table doesn't support size overrides containing percentages!"); + innerSizeOverrides.mStyleBSize.emplace( + wrapperBSize->ConvertsToLength() + ? ReduceStyleSizeBy(*wrapperBSize, areaOccupied.BSize(wm)) + : *wrapperBSize); + } + + return innerSizeOverrides; +} + +/* virtual */ +nsIFrame::SizeComputationResult nsTableWrapperFrame::ComputeSize( + gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, + nscoord aAvailableISize, const LogicalSize& aMargin, + const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, + ComputeSizeFlags aFlags) { + auto result = nsContainerFrame::ComputeSize( + aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding, + aSizeOverrides, aFlags); + + if (aSizeOverrides.mApplyOverridesVerbatim && + aSizeOverrides.HasAnyOverrides()) { + // We are asked to apply the size overrides directly to the inner table, but + // we still want ourselves to remain 'auto'-sized and shrink-wrapping our + // children's sizes. (Table wrapper frames always have 'auto' inline-size + // and block-size, since we don't inherit those properties from inner table, + // and authors can't target them with styling.) + auto size = + ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, + aMargin, aBorderPadding, aSizeOverrides, aFlags); + result.mLogicalSize = size; + } + + return result; +} + +/* virtual */ +LogicalSize nsTableWrapperFrame::ComputeAutoSize( + gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, + nscoord aAvailableISize, const LogicalSize& aMargin, + const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, + ComputeSizeFlags aFlags) { + nscoord kidAvailableISize = aAvailableISize - aMargin.ISize(aWM); + NS_ASSERTION(aBorderPadding.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. + const ComputeSizeFlags flags = CreateComputeSizeFlagsForChild(); + + // Match the logic in Reflow() that sets aside space for the caption. + Maybe captionSide = GetCaptionSide(); + + const LogicalSize innerTableSize = InnerTableShrinkWrapSize( + aRenderingContext, InnerTableFrame(), aWM, aCBSize, kidAvailableISize, + aSizeOverrides, flags); + if (!captionSide) { + return innerTableSize; + } + const LogicalSize captionSize = + CaptionShrinkWrapSize(aRenderingContext, mCaptionFrames.FirstChild(), aWM, + aCBSize, innerTableSize.ISize(aWM), flags); + const nscoord iSize = + std::max(innerTableSize.ISize(aWM), captionSize.ISize(aWM)); + nscoord bSize = NS_UNCONSTRAINEDSIZE; + if (innerTableSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE && + captionSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { + bSize = innerTableSize.BSize(aWM) + captionSize.BSize(aWM); + } + return LogicalSize(aWM, iSize, bSize); +} + +Maybe nsTableWrapperFrame::GetCaptionSide() const { + if (mCaptionFrames.IsEmpty()) { + return Nothing(); + } + return Some(mCaptionFrames.FirstChild()->StyleTableBorder()->mCaptionSide); +} + +StyleVerticalAlignKeyword nsTableWrapperFrame::GetCaptionVerticalAlign() const { + const auto& va = mCaptionFrames.FirstChild()->StyleDisplay()->mVerticalAlign; + return va.IsKeyword() ? va.AsKeyword() : StyleVerticalAlignKeyword::Top; +} + +nscoord nsTableWrapperFrame::ComputeFinalBSize( + const LogicalSize& aInnerSize, const LogicalSize& aCaptionSize, + const LogicalMargin& aCaptionMargin, const WritingMode aWM) const { + // negative sizes can upset overflow-area code + return std::max(0, aInnerSize.BSize(aWM) + + std::max(0, aCaptionSize.BSize(aWM) + + aCaptionMargin.BStartEnd(aWM))); +} + +void nsTableWrapperFrame::GetCaptionOrigin(StyleCaptionSide aCaptionSide, + const LogicalSize& aInnerSize, + const LogicalSize& aCaptionSize, + LogicalMargin& aCaptionMargin, + LogicalPoint& aOrigin, + WritingMode aWM) const { + 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; + } + if (mCaptionFrames.IsEmpty()) { + return; + } + + NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) && + NS_AUTOMARGIN != aCaptionMargin.BStart(aWM) && + NS_AUTOMARGIN != aCaptionMargin.BEnd(aWM), + "The computed caption margin is auto?"); + + aOrigin.I(aWM) = aCaptionMargin.IStart(aWM); + + // block-dir computation + switch (aCaptionSide) { + case StyleCaptionSide::Bottom: + aOrigin.B(aWM) = aInnerSize.BSize(aWM) + aCaptionMargin.BStart(aWM); + break; + case StyleCaptionSide::Top: + aOrigin.B(aWM) = aCaptionMargin.BStart(aWM); + break; + } +} + +void nsTableWrapperFrame::GetInnerOrigin(const MaybeCaptionSide& aCaptionSide, + const LogicalSize& aCaptionSize, + const LogicalMargin& aCaptionMargin, + const LogicalSize& aInnerSize, + LogicalPoint& aOrigin, + WritingMode aWM) const { + NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) && + NS_AUTOMARGIN != aCaptionMargin.IEnd(aWM), + "The computed caption 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; + } + + // block-dir computation + if (aCaptionSide) { + switch (*aCaptionSide) { + case StyleCaptionSide::Bottom: + // Leave at zero. + break; + case StyleCaptionSide::Top: + aOrigin.B(aWM) = + aCaptionSize.BSize(aWM) + aCaptionMargin.BStartEnd(aWM); + break; + } + } +} + +ComputeSizeFlags nsTableWrapperFrame::CreateComputeSizeFlagsForChild() const { + // Shrink-wrap child frames by default, except if we're a stretched grid item. + if (MOZ_UNLIKELY(IsGridItem())) { + auto* gridContainer = static_cast(GetParent()); + if (gridContainer->GridItemShouldStretch(this, eLogicalAxisInline)) { + return {}; + } + } + return {ComputeSizeFlag::ShrinkWrap}; +} + +void nsTableWrapperFrame::CreateReflowInputForInnerTable( + nsPresContext* aPresContext, nsTableFrame* aTableFrame, + const ReflowInput& aOuterRI, Maybe& aChildRI, + const nscoord aAvailISize, nscoord aBSizeOccupiedByCaption) const { + MOZ_ASSERT(InnerTableFrame() == aTableFrame); + + const WritingMode wm = aTableFrame->GetWritingMode(); + // If we have a caption occupied our content-box area, reduce the available + // block-size by the amount. + nscoord availBSize = aOuterRI.AvailableBSize(); + if (availBSize != NS_UNCONSTRAINEDSIZE) { + availBSize = std::max(0, availBSize - aBSizeOccupiedByCaption); + } + const LogicalSize availSize(wm, aAvailISize, availBSize); + + // For inner table frames, the containing block is the same as for the outer + // table frame. + Maybe cbSize = Some(aOuterRI.mContainingBlockSize); + + // However, if we are a grid item, the CB size needs to subtract our margins + // and the area occupied by the caption. + // + // Note that inner table computed margins are always zero, they're inherited + // by the table wrapper, so we need to get our margin from aOuterRI. + if (IsGridItem()) { + const LogicalMargin margin = aOuterRI.ComputedLogicalMargin(wm); + cbSize->ISize(wm) = std::max(0, cbSize->ISize(wm) - margin.IStartEnd(wm)); + if (cbSize->BSize(wm) != NS_UNCONSTRAINEDSIZE) { + cbSize->BSize(wm) = std::max(0, cbSize->BSize(wm) - margin.BStartEnd(wm) - + aBSizeOccupiedByCaption); + } + } + + ComputeSizeFlags csFlags = CreateComputeSizeFlagsForChild(); + if (!aTableFrame->IsBorderCollapse() && + !aOuterRI.mStyleSizeOverrides.HasAnyOverrides()) { + // We are not border-collapsed and not given any size overrides. It's + // sufficient to call the standard ReflowInput constructor. + aChildRI.emplace(aPresContext, aOuterRI, aTableFrame, availSize, cbSize, + ReflowInput::InitFlags{}, StyleSizeOverrides{}, csFlags); + return; + } + + Maybe borderPadding; + Maybe padding; + { + // Compute inner table frame's border & padding because we may need to + // reduce the size for inner table's size overrides. We won't waste time if + // they are not used, because we can use them directly by passing them into + // ReflowInput::Init(). + Maybe collapseBorder; + Maybe collapsePadding; + aTableFrame->GetCollapsedBorderPadding(collapseBorder, collapsePadding); + SizeComputationInput input(aTableFrame, aOuterRI.mRenderingContext, wm, + cbSize->ISize(wm), collapseBorder, + collapsePadding); + borderPadding.emplace(input.ComputedLogicalBorderPadding(wm)); + padding.emplace(input.ComputedLogicalPadding(wm)); + } + + StyleSizeOverrides innerOverrides = ComputeSizeOverridesForInnerTable( + aTableFrame, aOuterRI.mStyleSizeOverrides, borderPadding->Size(wm), + aBSizeOccupiedByCaption); + + aChildRI.emplace(aPresContext, aOuterRI, aTableFrame, availSize, Nothing(), + ReflowInput::InitFlag::CallerWillInit, innerOverrides, + csFlags); + aChildRI->Init(aPresContext, cbSize, Some(*borderPadding - *padding), + padding); +} + +void nsTableWrapperFrame::CreateReflowInputForCaption( + nsPresContext* aPresContext, nsIFrame* aCaptionFrame, + const ReflowInput& aOuterRI, Maybe& aChildRI, + const nscoord aAvailISize) const { + MOZ_ASSERT(aCaptionFrame == mCaptionFrames.FirstChild()); + + const WritingMode wm = aCaptionFrame->GetWritingMode(); + + // Use unconstrained available block-size so that the caption is always + // fully-complete. + const LogicalSize availSize(wm, aAvailISize, NS_UNCONSTRAINEDSIZE); + aChildRI.emplace(aPresContext, aOuterRI, aCaptionFrame, availSize); + + // See if we need to reset mIsTopOfPage flag. + if (aChildRI->mFlags.mIsTopOfPage) { + if (auto captionSide = GetCaptionSide()) { + if (*captionSide == StyleCaptionSide::Bottom) { + aChildRI->mFlags.mIsTopOfPage = false; + } + } + } +} + +void nsTableWrapperFrame::ReflowChild(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); + ReflowChildFlags flags = ReflowChildFlags::NoMoveFrame; + + // 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 |= ReflowChildFlags::NoDeleteNextInFlowChild; + } + + nsContainerFrame::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); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + // Initialize out parameters + aDesiredSize.ClearSize(); + + 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 origCaptionRect; + nsRect origCaptionInkOverflow; + bool captionFirstReflow = false; + if (mCaptionFrames.NotEmpty()) { + origCaptionRect = mCaptionFrames.FirstChild()->GetRect(); + origCaptionInkOverflow = mCaptionFrames.FirstChild()->InkOverflowRect(); + captionFirstReflow = + mCaptionFrames.FirstChild()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); + } + + // ComputeAutoSize has to match this logic. + WritingMode wm = aOuterRI.GetWritingMode(); + Maybe captionSide = GetCaptionSide(); + const nscoord contentBoxISize = aOuterRI.ComputedSize(wm).ISize(wm); + + MOZ_ASSERT(mCaptionFrames.NotEmpty() == captionSide.isSome()); + + // 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. + CreateReflowInputForInnerTable(aPresContext, InnerTableFrame(), aOuterRI, + innerRI, contentBoxISize); + + // First reflow the caption. + ReflowOutput captionMet(wm); + LogicalSize captionSize(wm); + LogicalMargin captionMargin(wm); + if (captionSide) { + // 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); + CreateReflowInputForCaption(aPresContext, mCaptionFrames.FirstChild(), + aOuterRI, captionRI, innerBorderISize); + + // We intentionally don't merge capStatus into aStatus, since we currently + // can't handle caption continuations, but we probably should. + nsReflowStatus capStatus; + ReflowChild(aPresContext, mCaptionFrames.FirstChild(), *captionRI, + captionMet, capStatus); + captionSize = captionMet.Size(wm); + captionMargin = captionRI->ComputedLogicalMargin(wm); + nscoord bSizeOccupiedByCaption = + captionSize.BSize(wm) + captionMargin.BStartEnd(wm); + if (bSizeOccupiedByCaption) { + // Reset the inner table's ReflowInput to reduce various sizes because of + // the area occupied by caption. + innerRI.reset(); + CreateReflowInputForInnerTable(aPresContext, InnerTableFrame(), aOuterRI, + innerRI, contentBoxISize, + bSizeOccupiedByCaption); + } + } + + // Now we know how much to reduce the block-size for the inner table to + // account for captions. Reflow the inner table. + ReflowOutput innerMet(innerRI->GetWritingMode()); + ReflowChild(aPresContext, InnerTableFrame(), *innerRI, innerMet, aStatus); + LogicalSize innerSize(wm, innerMet.ISize(wm), innerMet.BSize(wm)); + + // Now that we've reflowed both we can place them. + // Compute the desiredSize so that we can use it as the containerSize + // for the FinishReflowChild calls below. + LogicalSize desiredSize(wm); + + // We have zero border and padding, so content-box inline-size is our desired + // border-box inline-size. + desiredSize.ISize(wm) = contentBoxISize; + desiredSize.BSize(wm) = + ComputeFinalBSize(innerSize, captionSize, captionMargin, wm); + + aDesiredSize.SetSize(wm, desiredSize); + nsSize containerSize = aDesiredSize.PhysicalSize(); + + MOZ_ASSERT(mCaptionFrames.NotEmpty() == captionSide.isSome()); + if (mCaptionFrames.NotEmpty()) { + LogicalPoint captionOrigin(wm); + GetCaptionOrigin(*captionSide, innerSize, captionSize, captionMargin, + captionOrigin, wm); + FinishReflowChild(mCaptionFrames.FirstChild(), aPresContext, captionMet, + captionRI.ptr(), wm, captionOrigin, containerSize, + ReflowChildFlags::ApplyRelativePositioning); + captionRI.reset(); + } + // XXX If the bsize is constrained then we need to check whether + // everything still fits... + + LogicalPoint innerOrigin(wm); + GetInnerOrigin(captionSide, captionSize, captionMargin, innerSize, + innerOrigin, wm); + // NOTE: Relative positioning on the table applies to the whole table wrapper. + FinishReflowChild(InnerTableFrame(), aPresContext, innerMet, innerRI.ptr(), + wm, innerOrigin, containerSize, ReflowChildFlags::Default); + innerRI.reset(); + + if (mCaptionFrames.NotEmpty()) { + nsTableFrame::InvalidateTableFrame(mCaptionFrames.FirstChild(), + origCaptionRect, origCaptionInkOverflow, + captionFirstReflow); + } + + UpdateOverflowAreas(aDesiredSize); + + if (GetPrevInFlow()) { + ReflowOverflowContainerChildren(aPresContext, aOuterRI, + aDesiredSize.mOverflowAreas, + ReflowChildFlags::Default, aStatus); + } + + FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aOuterRI, aStatus); +} + +/* ----- 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(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsTableWrapperFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableWrapperFrame) + +#ifdef DEBUG_FRAME_DUMP +nsresult nsTableWrapperFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"TableWrapper"_ns, aResult); +} +#endif diff --git a/layout/tables/nsTableWrapperFrame.h b/layout/tables/nsTableWrapperFrame.h new file mode 100644 index 0000000000..4e4c03e607 --- /dev/null +++ b/layout/tables/nsTableWrapperFrame.h @@ -0,0 +1,282 @@ +/* -*- 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 "LayoutConstants.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "nscore.h" +#include "nsContainerFrame.h" +#include "nsCellMap.h" +#include "nsTableFrame.h" + +namespace mozilla { +class PresShell; +} // namespace mozilla + +/** + * 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(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( + mozilla::PresShell* aPresShell, ComputedStyle* aStyle); + + // nsIFrame overrides - see there for a description + + void Destroy(DestroyContext&) override; + + const nsFrameList& GetChildList(ChildListID aListID) const override; + void GetChildLists(nsTArray* aLists) const override; + + void SetInitialChildList(ChildListID aListID, + nsFrameList&& aChildList) override; + void AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) override; + void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, + nsFrameList&& aFrameList) override; + void RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) override; + + nsContainerFrame* GetContentInsertionFrame() override { + return PrincipalChildList().FirstChild()->GetContentInsertionFrame(); + } + +#ifdef ACCESSIBILITY + mozilla::a11y::AccType AccessibleType() override; +#endif + + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) override; + + void BuildDisplayListForInnerTable(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists); + + nscoord SynthesizeFallbackBaseline( + mozilla::WritingMode aWM, + BaselineSharingGroup aBaselineGroup) const override; + Maybe GetNaturalBaselineBOffset( + mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup, + BaselineExportContext aExportContext) const override; + + nscoord GetMinISize(gfxContext* aRenderingContext) override; + nscoord GetPrefISize(gfxContext* aRenderingContext) override; + + SizeComputationResult ComputeSize( + gfxContext* aRenderingContext, mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorderPadding, + const mozilla::StyleSizeOverrides& aSizeOverrides, + mozilla::ComputeSizeFlags aFlags) override; + + mozilla::LogicalSize ComputeAutoSize( + gfxContext* aRenderingContext, mozilla::WritingMode aWM, + const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, + const mozilla::LogicalSize& aMargin, + const mozilla::LogicalSize& aBorderPadding, + const mozilla::StyleSizeOverrides& aSizeOverrides, + mozilla::ComputeSizeFlags aFlags) override; + + /** process a reflow command for the table. + * This involves reflowing the caption and the inner table. + * @see nsIFrame::Reflow */ + void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) override; + +#ifdef DEBUG_FRAME_DUMP + nsresult GetFrameName(nsAString& aResult) const override; +#endif + + ComputedStyle* GetParentComputedStyle( + 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); + } + + protected: + explicit nsTableWrapperFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext, + ClassID aID = kClassID); + virtual ~nsTableWrapperFrame(); + + using MaybeCaptionSide = Maybe; + + // Get a StyleCaptionSide value, or Nothing if no caption is present. + // + // (Remember that caption-side values are interpreted logically, despite + // having "physical" names.) + MaybeCaptionSide GetCaptionSide() const; + + mozilla::StyleVerticalAlignKeyword GetCaptionVerticalAlign() const; + + nscoord ComputeFinalBSize(const mozilla::LogicalSize& aInnerSize, + const mozilla::LogicalSize& aCaptionSize, + const mozilla::LogicalMargin& aCaptionMargin, + const mozilla::WritingMode aWM) const; + + void GetCaptionOrigin(mozilla::StyleCaptionSide, + const mozilla::LogicalSize& aInnerSize, + const mozilla::LogicalSize& aCaptionSize, + mozilla::LogicalMargin& aCaptionMargin, + mozilla::LogicalPoint& aOrigin, + mozilla::WritingMode aWM) const; + + void GetInnerOrigin(const MaybeCaptionSide&, + const mozilla::LogicalSize& aCaptionSize, + const mozilla::LogicalMargin& aCaptionMargin, + const mozilla::LogicalSize& aInnerSize, + mozilla::LogicalPoint& aOrigin, + mozilla::WritingMode aWM) const; + + // This is a helper for CreateReflowInputForInnerTable() and + // ComputeAutoSize(). It computes whether we need shrink-wrap behavior for + // children. + // + // Note: We don't need to call this in CreateReflowInputForCaption() because + // when we reflow the captions, we want them to stretch their inline-sizes to + // be at least as wide as the inner table frame. + mozilla::ComputeSizeFlags CreateComputeSizeFlagsForChild() const; + + // Create and init the child reflow input, using passed-in aChildRI, so that + // caller can use it after we return. + // + // @param aBSizeOccupiedByCaption the block size occupied by the caption + // within our content box. + void CreateReflowInputForInnerTable( + nsPresContext* aPresContext, nsTableFrame* aTableFrame, + const ReflowInput& aOuterRI, Maybe& aChildRI, + const nscoord aAvailISize, nscoord aBSizeOccupiedByCaption = 0) const; + void CreateReflowInputForCaption(nsPresContext* aPresContext, + nsIFrame* aCaptionFrame, + const ReflowInput& aOuterRI, + Maybe& aChildRI, + const nscoord aAvailISize) const; + + // Reflow the child (caption or inner table frame). + void ReflowChild(nsPresContext* aPresContext, nsIFrame* aChildFrame, + const ReflowInput& aChildRI, ReflowOutput& aMetrics, + nsReflowStatus& aStatus); + + // Set the overflow areas in our reflow metrics + void UpdateOverflowAreas(ReflowOutput& aMet); + + nsTableFrame* InnerTableFrame() const { + return static_cast(mFrames.FirstChild()); + } + + /** + * Helper for ComputeAutoSize. + * Compute the margin-box inline size of the frame given the inputs. + * + * Note: CaptionShrinkWrapISize doesn't need StyleSizeOverrides parameter. + */ + mozilla::LogicalSize InnerTableShrinkWrapSize( + gfxContext* aRenderingContext, nsTableFrame* aTableFrame, + mozilla::WritingMode aWM, const mozilla::LogicalSize& aCBSize, + nscoord aAvailableISize, + const mozilla::StyleSizeOverrides& aSizeOverrides, + mozilla::ComputeSizeFlags aFlag) const; + mozilla::LogicalSize CaptionShrinkWrapSize( + gfxContext* aRenderingContext, nsIFrame* aCaptionFrame, + mozilla::WritingMode aWM, const mozilla::LogicalSize& aCBSize, + nscoord aAvailableISize, mozilla::ComputeSizeFlags aFlag) const; + + /** + * Create a new StyleSize by reducing the size by aAmountToReduce. + * + * @param aStyleSize must be a Length. + */ + mozilla::StyleSize ReduceStyleSizeBy(const mozilla::StyleSize& aStyleSize, + const nscoord aAmountToReduce) const; + + /** + * Compute StyleSizeOverrides for inner table frame given the overrides of the + * table wrapper frame. + */ + mozilla::StyleSizeOverrides ComputeSizeOverridesForInnerTable( + const nsTableFrame* aTableFrame, + const mozilla::StyleSizeOverrides& aWrapperSizeOverrides, + const mozilla::LogicalSize& aBorderPadding, + nscoord aBSizeOccupiedByCaption) const; + + private: + nsFrameList mCaptionFrames; +}; + +#endif diff --git a/layout/tables/reftests/1017137-ref.html b/layout/tables/reftests/1017137-ref.html new file mode 100644 index 0000000000..ae9f89a849 --- /dev/null +++ b/layout/tables/reftests/1017137-ref.html @@ -0,0 +1,27 @@ + + + + Reference: Table fragmentation test + + + + + + + + + + + + +
    +
    +
    +
    +
    HELLO KITTY diff --git a/layout/tables/reftests/1017137.html b/layout/tables/reftests/1017137.html new file mode 100644 index 0000000000..1778d3b566 --- /dev/null +++ b/layout/tables/reftests/1017137.html @@ -0,0 +1,27 @@ + + + + Table fragmentation test + + + + + + + + + + + + +
    +
    +
    +
    +
    HELLO KITTY diff --git a/layout/tables/reftests/1031934-ref.html b/layout/tables/reftests/1031934-ref.html new file mode 100644 index 0000000000..660e00a750 --- /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 0000000000..9477e0e790 --- /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 0000000000..bc9e9006cb --- /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 0000000000..70026618a1 --- /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 0000000000..82ab75544f --- /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 0000000000..3d0949abc9 --- /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 0000000000..cf6291d87b --- /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 0000000000..44e8b94e28 --- /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 0000000000..0b5f9a84e2 --- /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 0000000000..b6a02820b2 --- /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 0000000000..a66768e0fc --- /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 0000000000..379857235b --- /dev/null +++ b/layout/tables/reftests/1220621-2b.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + +
    One
    + diff --git a/layout/tables/reftests/1564308-ref.html b/layout/tables/reftests/1564308-ref.html new file mode 100644 index 0000000000..5dcd33a628 --- /dev/null +++ b/layout/tables/reftests/1564308-ref.html @@ -0,0 +1,22 @@ + + + + Testcase for bug 1564308 + + + + +
    + + + + +
    Hello
    +
    + + + diff --git a/layout/tables/reftests/1564308.html b/layout/tables/reftests/1564308.html new file mode 100644 index 0000000000..1172c33fe9 --- /dev/null +++ b/layout/tables/reftests/1564308.html @@ -0,0 +1,40 @@ + + + + Testcase for bug 1564308 + + + + +
    + + + + + + + + + + + +
    Hello
    +
    + + + + + diff --git a/layout/tables/reftests/dynamic-text-indent-table-cell-ref.html b/layout/tables/reftests/dynamic-text-indent-table-cell-ref.html new file mode 100644 index 0000000000..de749b910a --- /dev/null +++ b/layout/tables/reftests/dynamic-text-indent-table-cell-ref.html @@ -0,0 +1,10 @@ + + + + + + +
    + Some text +
    + diff --git a/layout/tables/reftests/dynamic-text-indent-table-cell.html b/layout/tables/reftests/dynamic-text-indent-table-cell.html new file mode 100644 index 0000000000..c49271a61f --- /dev/null +++ b/layout/tables/reftests/dynamic-text-indent-table-cell.html @@ -0,0 +1,19 @@ + + + + + + +
    + Some text +
    + + diff --git a/layout/tables/reftests/dynamic-text-overflow-table-cell-notref.html b/layout/tables/reftests/dynamic-text-overflow-table-cell-notref.html new file mode 100644 index 0000000000..4cbdc4f813 --- /dev/null +++ b/layout/tables/reftests/dynamic-text-overflow-table-cell-notref.html @@ -0,0 +1,8 @@ + + + + + +
    + Some long text that cannot possibly fit in 130 px, because it just can't. +
    diff --git a/layout/tables/reftests/dynamic-text-overflow-table-cell-ref.html b/layout/tables/reftests/dynamic-text-overflow-table-cell-ref.html new file mode 100644 index 0000000000..c19925ba60 --- /dev/null +++ b/layout/tables/reftests/dynamic-text-overflow-table-cell-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + Some long text that cannot possibly fit in 130 px, because it just can't. +
    diff --git a/layout/tables/reftests/dynamic-text-overflow-table-cell.html b/layout/tables/reftests/dynamic-text-overflow-table-cell.html new file mode 100644 index 0000000000..a572ebc77d --- /dev/null +++ b/layout/tables/reftests/dynamic-text-overflow-table-cell.html @@ -0,0 +1,19 @@ + + + + + + +
    + Some long text that cannot possibly fit in 130 px, because it just can't. +
    + + diff --git a/layout/tables/reftests/reftest.list b/layout/tables/reftests/reftest.list new file mode 100644 index 0000000000..15110c9853 --- /dev/null +++ b/layout/tables/reftests/reftest.list @@ -0,0 +1,14 @@ +== 1017137.html 1017137-ref.html +== 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 +== 1564308.html 1564308-ref.html +== dynamic-text-overflow-table-cell.html dynamic-text-overflow-table-cell-ref.html +!= dynamic-text-overflow-table-cell.html dynamic-text-overflow-table-cell-notref.html +== dynamic-text-indent-table-cell.html dynamic-text-indent-table-cell-ref.html diff --git a/layout/tables/test/mochitest.toml b/layout/tables/test/mochitest.toml new file mode 100644 index 0000000000..21b541cd75 --- /dev/null +++ b/layout/tables/test/mochitest.toml @@ -0,0 +1,7 @@ +[DEFAULT] + +["test_bug337124.html"] + +["test_bug541668_table_event_delivery.html"] + +["test_bug1832110.html"] diff --git a/layout/tables/test/test_bug1832110.html b/layout/tables/test/test_bug1832110.html new file mode 100644 index 0000000000..bc18df2fdb --- /dev/null +++ b/layout/tables/test/test_bug1832110.html @@ -0,0 +1,112 @@ + +Test for Bug 1832110 + + + + + +Mozilla Bug 1832110
    +
    + + + + + + + + +
    +
    + + + + + +
    +
    diff --git a/layout/tables/test/test_bug337124.html b/layout/tables/test/test_bug337124.html new file mode 100644 index 0000000000..d92a7e31f9 --- /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 0000000000..902b4c140d --- /dev/null +++ b/layout/tables/test/test_bug541668_table_event_delivery.html @@ -0,0 +1,48 @@ + + + + + Test for Bug 541668 + + + + + +Mozilla Bug 541668 + + + + + + + + +
    +
    +
    Cell
    Cell
    +
    +
    +
    + + -- cgit v1.2.3