summaryrefslogtreecommitdiffstats
path: root/layout/tables
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /layout/tables
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--layout/tables/BasicTableLayoutStrategy.cpp999
-rw-r--r--layout/tables/BasicTableLayoutStrategy.h74
-rw-r--r--layout/tables/FixedTableLayoutStrategy.cpp402
-rw-r--r--layout/tables/FixedTableLayoutStrategy.h38
-rw-r--r--layout/tables/SpanningCellSorter.cpp144
-rw-r--r--layout/tables/SpanningCellSorter.h91
-rw-r--r--layout/tables/TableArea.h48
-rw-r--r--layout/tables/celldata.h400
-rw-r--r--layout/tables/crashtests/1027611-1.html24
-rw-r--r--layout/tables/crashtests/1031934.html18
-rw-r--r--layout/tables/crashtests/110523-1.html45
-rw-r--r--layout/tables/crashtests/1118168.html1260
-rw-r--r--layout/tables/crashtests/1144641.html65
-rw-r--r--layout/tables/crashtests/1183896.html25
-rw-r--r--layout/tables/crashtests/1223232.html6
-rw-r--r--layout/tables/crashtests/1223282.html11
-rw-r--r--layout/tables/crashtests/1232881-1.html18
-rw-r--r--layout/tables/crashtests/1243623-1.html27
-rw-r--r--layout/tables/crashtests/1335552-1.html13
-rw-r--r--layout/tables/crashtests/1335552-2.html13
-rw-r--r--layout/tables/crashtests/138725-1.html32
-rw-r--r--layout/tables/crashtests/1555757-1.html16
-rw-r--r--layout/tables/crashtests/1555757-2.html27
-rw-r--r--layout/tables/crashtests/1555757-3.html22
-rw-r--r--layout/tables/crashtests/1555757-4.html21
-rw-r--r--layout/tables/crashtests/159359-1.html13
-rw-r--r--layout/tables/crashtests/1607045.html28
-rw-r--r--layout/tables/crashtests/1710116-1.html21
-rw-r--r--layout/tables/crashtests/1767364-1.html16
-rw-r--r--layout/tables/crashtests/1767364-2.html28
-rw-r--r--layout/tables/crashtests/1767364-3.html28
-rw-r--r--layout/tables/crashtests/1767364-4.html28
-rw-r--r--layout/tables/crashtests/1795030.html23
-rw-r--r--layout/tables/crashtests/1795051.html28
-rw-r--r--layout/tables/crashtests/1821177.html43
-rw-r--r--layout/tables/crashtests/187779-1.html19
-rw-r--r--layout/tables/crashtests/189751-1.html3
-rw-r--r--layout/tables/crashtests/197015-1.html10
-rw-r--r--layout/tables/crashtests/220536-1.html93
-rw-r--r--layout/tables/crashtests/223458-1.html25
-rw-r--r--layout/tables/crashtests/237421-1.html16
-rw-r--r--layout/tables/crashtests/237421-2.html47
-rw-r--r--layout/tables/crashtests/238909-1.html8
-rw-r--r--layout/tables/crashtests/239294-1.html38
-rw-r--r--layout/tables/crashtests/240854-1.html40
-rw-r--r--layout/tables/crashtests/266015-1.html18
-rw-r--r--layout/tables/crashtests/267418.html122
-rw-r--r--layout/tables/crashtests/275625.html8
-rw-r--r--layout/tables/crashtests/277062-1.html4
-rw-r--r--layout/tables/crashtests/278385-1.html25
-rw-r--r--layout/tables/crashtests/282175-1.html26
-rw-r--r--layout/tables/crashtests/284844-1.html13
-rw-r--r--layout/tables/crashtests/284852.html130
-rw-r--r--layout/tables/crashtests/28933-1.html10
-rw-r--r--layout/tables/crashtests/29157-1.html28
-rw-r--r--layout/tables/crashtests/300912.html19
-rw-r--r--layout/tables/crashtests/308752-1-inner.html43
-rw-r--r--layout/tables/crashtests/308752-1.html9
-rw-r--r--layout/tables/crashtests/308752-2-inner.html35
-rw-r--r--layout/tables/crashtests/308752-2.html9
-rw-r--r--layout/tables/crashtests/316636-1.html19
-rw-r--r--layout/tables/crashtests/317876.html16
-rw-r--r--layout/tables/crashtests/322779-1.xhtml8
-rw-r--r--layout/tables/crashtests/323604-1.html10
-rw-r--r--layout/tables/crashtests/323604-2.xhtml43
-rw-r--r--layout/tables/crashtests/32447-1.html13
-rw-r--r--layout/tables/crashtests/329891.xhtml25
-rw-r--r--layout/tables/crashtests/331344-1.html11
-rw-r--r--layout/tables/crashtests/331446-1.xhtml42
-rw-r--r--layout/tables/crashtests/331690-1.html38
-rw-r--r--layout/tables/crashtests/339130-1.html37
-rw-r--r--layout/tables/crashtests/339246-1.html32
-rw-r--r--layout/tables/crashtests/339315-1.html38
-rw-r--r--layout/tables/crashtests/341227-1.xhtml30
-rw-r--r--layout/tables/crashtests/343087-1.html40
-rw-r--r--layout/tables/crashtests/343588-1.xhtml35
-rw-r--r--layout/tables/crashtests/344000-1.html45
-rw-r--r--layout/tables/crashtests/347367.html78
-rw-r--r--layout/tables/crashtests/347506-1.xhtml23
-rw-r--r--layout/tables/crashtests/347506-2.xhtml14
-rw-r--r--layout/tables/crashtests/347725-1.xhtml39
-rw-r--r--layout/tables/crashtests/348977-1.xhtml7
-rw-r--r--layout/tables/crashtests/350524-1.xhtml33
-rw-r--r--layout/tables/crashtests/351326-1.xhtml14
-rw-r--r--layout/tables/crashtests/351327-1.xhtml21
-rw-r--r--layout/tables/crashtests/351328-1.xhtml26
-rw-r--r--layout/tables/crashtests/351628-1.xhtml14
-rw-r--r--layout/tables/crashtests/358679-1.xhtml31
-rw-r--r--layout/tables/crashtests/358871-1.xhtml19
-rw-r--r--layout/tables/crashtests/362275.html14
-rw-r--r--layout/tables/crashtests/364512-1.html20
-rw-r--r--layout/tables/crashtests/366556-1.xhtml50
-rw-r--r--layout/tables/crashtests/367673-1.xhtml38
-rw-r--r--layout/tables/crashtests/367749.html14
-rw-r--r--layout/tables/crashtests/367755.xhtml24
-rw-r--r--layout/tables/crashtests/368013.html13
-rw-r--r--layout/tables/crashtests/368166-1.xhtml21
-rw-r--r--layout/tables/crashtests/370360-1.html34
-rw-r--r--layout/tables/crashtests/370710.xhtml39
-rw-r--r--layout/tables/crashtests/370713-1.html13
-rw-r--r--layout/tables/crashtests/370876-1.html41
-rw-r--r--layout/tables/crashtests/370897-1.html45
-rw-r--r--layout/tables/crashtests/371290.html33
-rw-r--r--layout/tables/crashtests/373400-1.html34
-rw-r--r--layout/tables/crashtests/373400-2.html2109
-rw-r--r--layout/tables/crashtests/373400-3.html64
-rw-r--r--layout/tables/crashtests/373611-1.html22
-rw-r--r--layout/tables/crashtests/373946-1.html6
-rw-r--r--layout/tables/crashtests/374356-1.html28
-rw-r--r--layout/tables/crashtests/374819-1.html16
-rw-r--r--layout/tables/crashtests/374819-2.html16
-rw-r--r--layout/tables/crashtests/375058-1.xhtml10
-rw-r--r--layout/tables/crashtests/378240-1.html12
-rw-r--r--layout/tables/crashtests/379687-1.html14
-rw-r--r--layout/tables/crashtests/380200-1.xhtml24
-rw-r--r--layout/tables/crashtests/385132-1.xhtml21
-rw-r--r--layout/tables/crashtests/385132-2.html17
-rw-r--r--layout/tables/crashtests/387051-1.html15
-rw-r--r--layout/tables/crashtests/388700-1.html34
-rw-r--r--layout/tables/crashtests/391898-1.html19
-rw-r--r--layout/tables/crashtests/391901-1.html16
-rw-r--r--layout/tables/crashtests/392132-1.xhtml9
-rw-r--r--layout/tables/crashtests/397448-1.html7
-rw-r--r--layout/tables/crashtests/398157-1.xhtml5
-rw-r--r--layout/tables/crashtests/399209-1.xhtml15
-rw-r--r--layout/tables/crashtests/403249-1.html20
-rw-r--r--layout/tables/crashtests/403579-1.html12
-rw-r--r--layout/tables/crashtests/404301-1.xhtml21
-rw-r--r--layout/tables/crashtests/408753-1.xhtml1
-rw-r--r--layout/tables/crashtests/410426-1.html16
-rw-r--r--layout/tables/crashtests/410428-1.xhtml9
-rw-r--r--layout/tables/crashtests/411582.xhtml6
-rw-r--r--layout/tables/crashtests/413091.xhtml7
-rw-r--r--layout/tables/crashtests/413180-1.html17
-rw-r--r--layout/tables/crashtests/416845-1.xhtml7
-rw-r--r--layout/tables/crashtests/416845-2.xhtml15
-rw-r--r--layout/tables/crashtests/416845-3.html38
-rw-r--r--layout/tables/crashtests/420242-1.xhtml4
-rw-r--r--layout/tables/crashtests/420654-1.xhtml27
-rw-r--r--layout/tables/crashtests/423514-1.xhtml35
-rw-r--r--layout/tables/crashtests/430374.html31
-rw-r--r--layout/tables/crashtests/444431-1.html26
-rw-r--r--layout/tables/crashtests/444702-1.html5
-rw-r--r--layout/tables/crashtests/448988-1.xhtml32
-rw-r--r--layout/tables/crashtests/450311-1.html23
-rw-r--r--layout/tables/crashtests/451170.html21
-rw-r--r--layout/tables/crashtests/451355-1.html5
-rw-r--r--layout/tables/crashtests/456041.html19
-rw-r--r--layout/tables/crashtests/457115.html7
-rw-r--r--layout/tables/crashtests/460637-1.xhtml41
-rw-r--r--layout/tables/crashtests/460637-2.xhtml24
-rw-r--r--layout/tables/crashtests/460637-3.xhtml26
-rw-r--r--layout/tables/crashtests/462849.xhtml20
-rw-r--r--layout/tables/crashtests/467141-1.html8
-rw-r--r--layout/tables/crashtests/481089.html4
-rw-r--r--layout/tables/crashtests/488388-1.html21
-rw-r--r--layout/tables/crashtests/501870-1.html1
-rw-r--r--layout/tables/crashtests/509562-1.xhtml18
-rw-r--r--layout/tables/crashtests/512749-1.html1
-rw-r--r--layout/tables/crashtests/513732-1.html7
-rw-r--r--layout/tables/crashtests/533380-1.xhtml1
-rw-r--r--layout/tables/crashtests/534716-1.html18
-rw-r--r--layout/tables/crashtests/55789-1.html13
-rw-r--r--layout/tables/crashtests/563009-1.html42
-rw-r--r--layout/tables/crashtests/563009-2.html40
-rw-r--r--layout/tables/crashtests/563009-3.html34
-rw-r--r--layout/tables/crashtests/573354-1.xhtml14
-rw-r--r--layout/tables/crashtests/576890-1.html8
-rw-r--r--layout/tables/crashtests/576890-2.html8
-rw-r--r--layout/tables/crashtests/576890-3.html8
-rw-r--r--layout/tables/crashtests/580481-1.xhtml23
-rw-r--r--layout/tables/crashtests/595758-1.xhtml13
-rw-r--r--layout/tables/crashtests/595758-2.xhtml12
-rw-r--r--layout/tables/crashtests/678447-1.html10
-rw-r--r--layout/tables/crashtests/691824-1.xhtml279
-rw-r--r--layout/tables/crashtests/695430-1.html23
-rw-r--r--layout/tables/crashtests/696640-1.html47
-rw-r--r--layout/tables/crashtests/696640-2.html486
-rw-r--r--layout/tables/crashtests/705996-1.html6
-rw-r--r--layout/tables/crashtests/705996-2.html6
-rw-r--r--layout/tables/crashtests/707622-1.html6
-rw-r--r--layout/tables/crashtests/710098-1.html7
-rw-r--r--layout/tables/crashtests/711864-1.html15
-rw-r--r--layout/tables/crashtests/750147.html2
-rw-r--r--layout/tables/crashtests/759249-1.html6
-rw-r--r--layout/tables/crashtests/759249-2.html10
-rw-r--r--layout/tables/crashtests/78623-1.html17
-rw-r--r--layout/tables/crashtests/814713.html96
-rw-r--r--layout/tables/crashtests/862624.html19
-rw-r--r--layout/tables/crashtests/897883-1.html8
-rw-r--r--layout/tables/crashtests/980223.html22
-rw-r--r--layout/tables/crashtests/crashtests.list182
-rw-r--r--layout/tables/moz.build51
-rw-r--r--layout/tables/nsCellMap.cpp2508
-rw-r--r--layout/tables/nsCellMap.h575
-rw-r--r--layout/tables/nsITableCellLayout.h31
-rw-r--r--layout/tables/nsITableLayoutStrategy.h59
-rw-r--r--layout/tables/nsTableCellFrame.cpp1175
-rw-r--r--layout/tables/nsTableCellFrame.h334
-rw-r--r--layout/tables/nsTableColFrame.cpp188
-rw-r--r--layout/tables/nsTableColFrame.h288
-rw-r--r--layout/tables/nsTableColGroupFrame.cpp460
-rw-r--r--layout/tables/nsTableColGroupFrame.h219
-rw-r--r--layout/tables/nsTableFrame.cpp7276
-rw-r--r--layout/tables/nsTableFrame.h952
-rw-r--r--layout/tables/nsTableRowFrame.cpp1344
-rw-r--r--layout/tables/nsTableRowFrame.h386
-rw-r--r--layout/tables/nsTableRowGroupFrame.cpp1864
-rw-r--r--layout/tables/nsTableRowGroupFrame.h377
-rw-r--r--layout/tables/nsTableWrapperFrame.cpp857
-rw-r--r--layout/tables/nsTableWrapperFrame.h282
-rw-r--r--layout/tables/reftests/1017137-ref.html27
-rw-r--r--layout/tables/reftests/1017137.html27
-rw-r--r--layout/tables/reftests/1031934-ref.html27
-rw-r--r--layout/tables/reftests/1031934.html54
-rw-r--r--layout/tables/reftests/1220621-1-ref.html17
-rw-r--r--layout/tables/reftests/1220621-1a.html32
-rw-r--r--layout/tables/reftests/1220621-1b.html31
-rw-r--r--layout/tables/reftests/1220621-1c.html30
-rw-r--r--layout/tables/reftests/1220621-1d.html34
-rw-r--r--layout/tables/reftests/1220621-1e.html34
-rw-r--r--layout/tables/reftests/1220621-1f.html32
-rw-r--r--layout/tables/reftests/1220621-2-ref.html21
-rw-r--r--layout/tables/reftests/1220621-2a.html29
-rw-r--r--layout/tables/reftests/1220621-2b.html32
-rw-r--r--layout/tables/reftests/1564308-ref.html22
-rw-r--r--layout/tables/reftests/1564308.html40
-rw-r--r--layout/tables/reftests/dynamic-text-indent-table-cell-ref.html10
-rw-r--r--layout/tables/reftests/dynamic-text-indent-table-cell.html19
-rw-r--r--layout/tables/reftests/dynamic-text-overflow-table-cell-notref.html8
-rw-r--r--layout/tables/reftests/dynamic-text-overflow-table-cell-ref.html8
-rw-r--r--layout/tables/reftests/dynamic-text-overflow-table-cell.html19
-rw-r--r--layout/tables/reftests/reftest.list14
-rw-r--r--layout/tables/test/mochitest.toml7
-rw-r--r--layout/tables/test/test_bug1832110.html112
-rw-r--r--layout/tables/test/test_bug337124.html32
-rw-r--r--layout/tables/test/test_bug541668_table_event_delivery.html48
237 files changed, 30654 insertions, 0 deletions
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 <algorithm>
+
+#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 <algorithm>
+
+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<nscoord> 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<const HashTableEntry*>(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<HashTableEntry*>(
+ 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<void*>(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<HashTableEntry*>(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<void*>(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<HashTableEntry*> mSortedHashTable;
+
+ /*
+ * operator new is forbidden since we use the pres shell's stack
+ * memory, which much be pushed and popped at points matching a
+ * push/pop on the C++ stack.
+ */
+ void* operator new(size_t sz) 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 <stdint.h>
+
+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 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+ <style>
+ thead {
+ position: relative;
+ }
+ tr {
+ height: 60px;
+ }
+ </style>
+</head>
+<body>
+ <table>
+ <thead>
+ <tr></tr>
+ </thead>
+ <tbody>
+ <tr></tr>
+ <tr></tr>
+ </tbody>
+ </table>
+</body>
+</html>
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 @@
+<html>
+<head>
+</head>
+<body>
+<table>
+<tbody style="visibility: collapse;">
+<tr><td hidden=""></td></tr>
+</tbody>
+</table>
+
+<table>
+<tbody>
+<tr style="visibility: collapse;"><td hidden=""></td></tr>
+</tbody>
+</table>
+
+</body>
+</html>
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 @@
+<html>
+<head>
+<title></title>
+<script type="text/javascript">
+function toggle(b) {
+ var adv = document.getElementById("it_guru");
+ var ns = 'none';
+ if (b) {
+ ns= "table-row";
+ }
+ adv.style.display = ns;
+}
+function boom()
+{
+ toggle(true);
+ document.documentElement.offsetHeight;
+ toggle(false);
+ document.documentElement.offsetHeight;
+ toggle(true);
+ document.documentElement.offsetHeight;
+ toggle(false);
+ document.documentElement.offsetHeight;
+}
+</script></head><body onload="boom();">
+<form action="http://localhost/">
+<table>
+<tbody>
+<tr>
+<td>Without the &lt;tbody&gt; tags Mozilla doesn't crash
+ </td>
+</tr>
+</tbody>
+<tr id="it_guru">
+<td>I disappear
+ </td>
+</tr>
+<tr>
+<td>Without this row Mozilla doesn't crash
+ </td>
+</tr>
+</table>
+</form>
+</body>
+</html>
+
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 @@
+<!DOCTYPE html>
+<html lang="fr" class="reftest-paged">
+<head>
+<style type="text/css">
+html, body {
+width: 100%;
+margin: 0;
+padding: 0;
+font-family: arial, helvetica, sans-serif;
+font-size: 11px;
+}
+
+body {
+background-color: #ddf;
+}
+
+table {
+font-family: inherit;
+font-size: 1em;
+/*border-collapse: collapse;*/
+}
+
+th {
+text-align: center;
+vertical-align: top;
+}
+
+td p,
+th p {
+white-space: normal;
+margin: 0;
+}
+
+p + p {
+margin-top: 0.4em;
+}
+
+img {
+border: none;
+vertical-align: middle;
+}
+
+ul, ol {
+margin: 0;
+padding-left: 1.5em;
+line-height: 1.1;
+}
+
+h1, h2, h3 {
+font-weight: normal;
+}
+
+h1 {
+font-size: 1.6em;
+color: #444;
+margin: 10px 0;
+page-break-before: always;
+}
+
+h1:first-of-type {
+page-break-before: auto;
+}
+
+h1.no-break {
+page-break-before: auto;
+}
+
+h2 {
+font-size: 1.4em;
+color: #444;
+margin: 5px 10px;
+}
+
+h3 {
+font-size: 1.4em;
+color: #666;
+margin: 5px 20px;
+}
+
+hr {
+border: none;
+border-bottom: 1px solid #666;
+}
+
+table.main {
+width: 100%;
+border-spacing: 4px;
+}
+
+table.main td {
+vertical-align: top;
+}
+
+table.main td.button {
+text-align: center;
+}
+
+table.main td.top {
+vertical-align: top;
+}
+
+table.main tbody.viewported td.viewport {
+border: 1px solid #000;
+}
+
+th.title {
+color: #fff;
+font-size: 1.2em;
+font-weight: bold;
+text-align: center;
+background: #888;
+border: 1px solid #ccc;
+}
+
+/* Standard table */
+table.tbl {
+width: 100%;
+border: 1px solid #ccc;
+border-spacing: 0;
+border-collapse: collapse;
+}
+
+
+table.tbl td,
+table.tbl th {
+border: 1px solid #ccc;
+}
+
+table.tbl td.empty,
+table.form td.empty,
+div.empty,
+li.empty,
+span.empty {
+font-style: italic;
+color: #999;
+}
+
+table.tbl td.button {
+text-align: center;
+vertical-align: middle;
+white-space: normal;
+line-height: 140%;
+}
+
+table.tbl tr.clear,
+table.tbl tr.clear a {
+background-color: #ddf;
+color: #000;
+}
+
+table.tbl tr.clear td,
+table.tbl tr.clear th {
+background-color: transparent;
+border: none;
+}
+
+/* Main -- table -- pane */
+#main {
+clear: both;
+width: 100%;
+border-spacing: 0
+}
+
+@font-face {
+font-family: opendyslexic;
+src: url()
+ }
+
+/** Must not apply it to every tag, or it will break textareas behavior */
+td.text,
+th.text,
+legend.text,
+div.text {
+white-space: normal !important;
+/*word-wrap: break-word;*/
+}
+
+
+table.tbl td.left_day {
+border-left: 2px solid grey;
+}
+
+table.tbl td.right_day {
+border-right: 2px solid grey;
+}
+
+table.tbl td.first_perf{background:red;}
+
+tr.in_progress_before, tr.in_progress_after {
+display: none !important;
+}
+
+</style>
+
+</head>
+
+<body class="
+desktop ">
+
+<table class="tbl" >
+<thead>
+<tr class="clear">
+<td colspan="19" class="button">
+<h3>
+<a href="#" onclick="window.print()">
+Mme FOO (BAR) Baz
+</a>
+</h3>
+</td>
+</tr>
+<tr class="clear">
+<td colspan="19">
+<table class="main">
+<tr>
+<td class="text" style="width: 25%; vertical-align: top;">
+Impression du lundi 05 janvier 2015 à 17h14 <br />
+
+<br />
+<strong>SERVICE :</strong>
+FOOO
+&mdash; <strong>LIT : </strong> 1223 P
+</td>
+<td style="width: 25%; vertical-align: top;">
+Civilité : Mme FOO (BAR) Baz
+(89 ans)
+(64.5 kg) (160 cm) <br />
+Né(e) le : 12/06/2025
+</td>
+<td style="vertical-align: top;">
+NDA : 1248744
+<br />
+Motif : foobar
+<br />
+Date d'entrée : 26/11/2014 à 15h56
+<br />
+Date de sortie : 23/01/2015 à 10h00
+</td>
+<td class="text" style="vertical-align: top">
+</td>
+</tr>
+</table>
+</td>
+</tr>
+<tr>
+<th class="title" style="width: 20%" colspan="3">
+PRESCRIPTIONS </th>
+<th class="title" colspan="16">
+ADMINISTRATION
+</th>
+</tr>
+<tr>
+<th class="text" style="width: 7%" rowspan="2">
+Date signature
+</th>
+<th class="text" rowspan="2" style="width: 8%">
+Libellé médicament <br />
+Posologie <br />
+Commentaires <br />
+</th>
+<th style="width: 10%" rowspan="2">
+Commentaires
+</th>
+<th colspan="4">
+D <br />
+04/01
+</th>
+<th colspan="4">
+L <br />
+05/01
+</th>
+<th colspan="4">
+M <br />
+06/01
+</th>
+<th colspan="4">
+M <br />
+07/01
+</th>
+</tr>
+<tr>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">S</th>
+<th style="width: 4.6875%">N</th>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">S</th>
+<th style="width: 4.6875%">N</th>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">S</th>
+<th style="width: 4.6875%">N</th>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">M</th>
+<th style="width: 4.6875%">S</th>
+<th style="width: 4.6875%">N</th>
+</tr>
+</thead>
+
+<tbody class="line_print">
+<tr>
+<th colspan="19" class="section">Perfusions</th>
+</tr>
+</tbody>
+
+<tbody class="line_print">
+<tr>
+<td class="text first_perf " style="vertical-align: top;">
+30/12/2014 <br />
+<span class="mediuser" style="border-left-color: #CC0066;"
+onmouseover="ObjectTooltip.createEx(this, 'CMediusers-608')">
+FOO Bar</span>
+
+<div class="compact" style="white-space: nowrap !important;">
+<div>Du 30/12/2014 à 18h00</div>
+<div>
+au 24/01/2015 à 18h00
+</div>
+</div>
+</td>
+<td class="text first_perf " style="vertical-align: top;">
+BAH 40 MG PDR INJ (=BIH)
+<br />
+<strong>
+40 mg
+[mg]
+</strong>
+<br />
+Voie intraveineuse
+<div>
+</div>
+</td>
+<td style="vertical-align: top;" class="text first_perf ">
+<div class="compact">
+<em>
+<br />
+</em>
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching first_perf ">
+<div class="compact">
+40
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf ">
+<div class="compact">
+40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf ">
+<div class="compact">
+40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf ">
+<div class="compact">
+40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day first_perf ">
+<div class="compact">
+</div>
+</td>
+</tr>
+<tr>
+<td class="text last_perf" style="vertical-align: top;">
+</td>
+<td class="text last_perf" style="vertical-align: top;">
+NACL 0,9% SOL INJ PR PERF 500 ML (=CHLORURE de SODIUM)
+<br />
+<strong>
+50 ml
+</strong>
+<br />
+Voie intraveineuse
+<div>
+</div>
+</td>
+<td style="vertical-align: top;" class="text last_perf">
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching last_perf">
+<div class="compact">
+50
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" last_perf">
+<div class="compact">
+50 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" last_perf">
+<div class="compact">
+50 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" last_perf">
+<div class="compact">
+50 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day last_perf">
+<div class="compact">
+</div>
+</td>
+</tr>
+</tbody>
+
+<tbody class="line_print">
+<tr>
+<td class="text first_perf last_perf" style="vertical-align: top;">
+26/11/2014 <br />
+<span class="mediuser" style="border-left-color: #CC0066;"
+onmouseover="ObjectTooltip.createEx(this, 'CMediusers-2785')">
+BUH Boh</span>
+
+<div class="compact" style="white-space: nowrap !important;">
+<div>Du 26/11/2014 à 18h48</div>
+<div>
+au 23/01/2015 à 10h48
+</div>
+</div>
+</td>
+<td class="text first_perf last_perf" style="vertical-align: top;">
+ONDANSETRON 4 MG/2 ML, SOL INJ, AMP (=ZOPHREN)
+<br />
+<strong>
+8 mg (soit 4 ml)
+</strong>
+<br />
+Voie intraveineuse
+<div>
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAALCAYAAABhwJ3wAAAB4klEQVR4Xq2TzWsTURTFz3wkYCENUmopLmtLNyX/g25bIZsKtBvBjRv3Lt2UAiH7CVLNqlJBBcGdlQKRoJGBIKQBOklEExIkk1pImo+Z48wFHk1CwRYfHN57986bH/dcLnzd9Hzo7CPGE9zje83ic+0zLa3w3/QhefwJx9p9DmbnScMgdV12b2mJvZ0ddlotdjqda8lCge5vl7mnjsCQMYJA+YTddJqjtTWOECWhkQBpmhwkkzw7PLwOSJ0zeggyv40lz46O5OeMRARILVA0Sm91ld3dXZ46zpVA7XabGUOBVEIptK2bStFbXpbKwj6KFhbY397ma+zLm0CTAKVms8l6vS4ViXUToCngW7zgYGODfjwuMOkjDI4SCbE8i490XVcAreD7RqMh51qtRsdx/gk0lvtTKjGPJ2KjAHWB8hyz7G5t8QAH/FGtslKpCKhcLrMUvLEugBRsCqB2FRPf32GPvfV1+rGY6qWMycoKf6bTAioWi7RtewqkYJd7r3xX1tTyeX7FYw4XF6VCQoDs4Sabm5u0X+4LSAsHKvFsDncexWHO6Li4SML3fXieNyYVm7jP5HK4lc0iVihAC2KAhqF5A9+HD4BwasNmXUXWZZL8F77S39DGQ57iNj1EWJ2/++svxcQjnb2QKMgAAAAASUVORK5CYII=" />
+</div>
+</td>
+<td style="vertical-align: top;" class="text first_perf last_perf">
+<div class="compact">
+<em>
+<p>lorem ipsum</p>
+<br />
+</em>
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching first_perf last_perf">
+<div class="compact">
+0
+/ 8 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day first_perf last_perf">
+<div class="compact">
+0
+/ 8 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf last_perf">
+<div class="compact">
+8 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day first_perf last_perf">
+<div class="compact">
+8 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf last_perf">
+<div class="compact">
+8 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day first_perf last_perf">
+<div class="compact">
+8 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" first_perf last_perf">
+<div class="compact">
+8 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day first_perf last_perf">
+<div class="compact">
+</div>
+</td>
+</tr>
+</tbody>
+<tbody class="line_print">
+<tr>
+<th colspan="19" class="section">Injections</th>
+</tr>
+</tbody>
+
+<tbody class="line_print">
+<tr>
+<td class="text" style="vertical-align: top">
+13/12/2014 <br />
+<span class="mediuser" style="border-left-color: #CC0066;"
+onmouseover="ObjectTooltip.createEx(this, 'CMediusers-608')">
+FOO Bar</span>
+
+<div class="compact" style="white-space: nowrap !important;">
+<div>Du 13/12/2014 à 12h00</div>
+<div>
+à Fin du séjour
+</div>
+</div>
+</td>
+<td class="text" style="vertical-align: top">
+<strong>
+FUROSEMIDE 20 MG/2 ML, SOL INJ, AMP (=LASILIX) </strong>
+<br />
+Voie intraveineuse
+<div class="compact">
+40 mg (soit 4 ml) tous les 12 Heure(s)<br />
+</div>
+<div>
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAALCAYAAABhwJ3wAAABGklEQVR4XrWTwUqGQBDHx9UuXQp6he/eE/QQnXqBXqJjDxGgdOtUD9DV0xchG91EIAQDFJfY9RIoHzoxSyqzFBaW8GfGYZzfzl8V96dZHILE0Ps/EQMiIfH56hV1bbBpmj8THd68Gdxe5BYGkS9RK8MaRq0ETXkkCBQ8MYjTvBqktUZahkALQ9mmC5vzel3XWJbl50a+XAK5gC9rxhgblVJYVZXNi6LAPM8tSMDKq+97G9u2HSPLu64DBIAJdNZs4PbwhQHme65hGNyhNHCKVHPz0bpl77nvzBrKsyzDNE3Z80mS4EP8iKGQ6NE3fnx5BJvzAwj2uZOISKcne5ic2rc9u/ce1HUA+m4PgP5aelm/UfgTebNuTrbxB7es8hv2a5NUAAAAAElFTkSuQmCC" />
+</div>
+</td>
+<td style="vertical-align: top" class="text">
+<div class="compact">
+<em>
+<p>hahaha</p>
+<br />
+</em>
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+0
+/ 40
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+0
+/ 40
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+0
+/ 40
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+a/ 40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+a/ 40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+a/ 40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+a/ 40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+a/ 40 </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+</tr>
+</tbody>
+
+<tbody class="line_print">
+<tr>
+<td class="text" style="vertical-align: top">
+26/11/2014 <br />
+<span class="mediuser" style="border-left-color: #CC0066;"
+onmouseover="ObjectTooltip.createEx(this, 'CMediusers-2785')">
+BUH Boh</span>
+
+<div class="compact" style="white-space: nowrap !important;">
+<div>Du 26/11/2014 à 18h50</div>
+<div>
+à Fin du séjour
+</div>
+</div>
+</td>
+<td class="text" style="vertical-align: top">
+<strong>
+FONDAPARINUX SODIUM 2.5 MG/0.5 ML, SOL INJ, SRG (=ARIXTRA) <span style="color: red">(A risque)</span> </strong>
+<br />
+Voie sous-cutanée
+<div class="compact">
+2.5 mg (soit 0.5 ml) le soir<br />
+</div>
+<div>
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAABMElEQVR4XpWOsWrCUBSG/2Nqh0IltLWDQ2YnKxolk/QBHAqlD+DSN3Bw8w2c26G+QOlQXBy7RIQgZOxa6NAqJoOBxCT3ljNcRVKq/eDA+e9/zn8P9Z2+MfoYuc7c0QmEQ6kX637baF+R+WJ6URrpw+shqudVaKThL4QUmH5P0XnroJAv+EQPJO0bG9alBXrMXiDvJb/vaMb+stF6beGIRbPY3DF5gfstWc+8MMFwAHKUw3+I4xgiFSrgMJIkQZqmYKIo4n5/AA+pwfxTHszybokwDCHE7xcoQw1tAha3C9ab31fr1TZgtpiholfYyJzJS0qrQK7JfAIpJaj2XPOCJNAHjQEaZw1oUlODqnZ0EAdwPAfd9y5KxyWfenbPGH+OXddzdexBQqoG5ZOyb51a1R/M4MEXrGRJJgAAAABJRU5ErkJggg==" />
+</div>
+</td>
+<td style="vertical-align: top" class="text">
+<div class="compact">
+<em>
+<br />
+</em>
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+2.5
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+2.5
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+2.5
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+2.5
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+</tr>
+</tbody>
+<tbody class="line_print">
+<tr>
+<th colspan="19" class="section">Médicaments</th>
+</tr>
+</tbody>
+
+<tbody class="line_print">
+<tr>
+<td class="text" style="vertical-align: top">
+04/01/2015 <br />
+<span class="mediuser" style="border-left-color: #CC0066;"
+onmouseover="ObjectTooltip.createEx(this, 'CMediusers-608')">
+FOO Bar</span>
+
+<div class="compact" style="white-space: nowrap !important;">
+<div>Du 04/01/2015 à 10h00</div>
+<div>
+à Fin du séjour
+</div>
+</div>
+</td>
+<td class="text" style="vertical-align: top">
+<strong>
+MACROGOL(S) + POTASSIUM + SODIUM SS ARÔME, PDR PR SOL BUV, SACHET (=MOVICOL) </strong>
+<br />
+Voie orale
+<div class="compact">
+1 sachets le matin<br />
+</div>
+<div>
+</div>
+</td>
+<td style="vertical-align: top" class="text">
+<div class="compact">
+<em>
+<br />
+</em>
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+1
+/ - </div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+1
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+1
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+1
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+</tr>
+</tbody>
+
+<tbody class="line_print">
+<tr>
+<td class="text" style="vertical-align: top">
+01/01/2015 <br />
+<span class="mediuser" style="border-left-color: #CC0066;"
+onmouseover="ObjectTooltip.createEx(this, 'CMediusers-608')">
+FOO Bar</span>
+
+<div class="compact" style="white-space: nowrap !important;">
+<div>Du 01/01/2015 à 11h14</div>
+<div>
+à Fin du séjour
+</div>
+</div>
+</td>
+<td class="text" style="vertical-align: top">
+<strong>
+PREDNISOLONE 5 MG, CPR ORODISPERSIBLE (=SOLUPRED) </strong>
+<br />
+Voie orale
+<div class="compact">
+3 mg (soit 0.6 Comprimé orodispersible) le matin<br />
+</div>
+<div>
+</div>
+</td>
+<td style="vertical-align: top" class="text">
+<div class="compact">
+<em>
+<br />
+</em>
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+3
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+3
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+3
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+3
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+</tr>
+</tbody>
+
+<tbody class="line_print">
+<tr>
+<td class="text" style="vertical-align: top">
+01/01/2015 <br />
+<span class="mediuser" style="border-left-color: #CC0066;"
+onmouseover="ObjectTooltip.createEx(this, 'CMediusers-608')">
+FOO Bar</span>
+
+<div class="compact" style="white-space: nowrap !important;">
+<div>Du 01/01/2015 à 11h12</div>
+<div>
+à Fin du séjour
+</div>
+</div>
+</td>
+<td class="text" style="vertical-align: top">
+<strong>
+SEROPLEX 5 mg </strong>
+<br />
+Voie orale
+<div class="compact">
+10 mg (soit 2 Comprimé pelliculé) le matin<br />
+</div>
+<div>
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAABMElEQVR4XpWOsWrCUBSG/2Nqh0IltLWDQ2YnKxolk/QBHAqlD+DSN3Bw8w2c26G+QOlQXBy7RIQgZOxa6NAqJoOBxCT3ljNcRVKq/eDA+e9/zn8P9Z2+MfoYuc7c0QmEQ6kX637baF+R+WJ6URrpw+shqudVaKThL4QUmH5P0XnroJAv+EQPJO0bG9alBXrMXiDvJb/vaMb+stF6beGIRbPY3DF5gfstWc+8MMFwAHKUw3+I4xgiFSrgMJIkQZqmYKIo4n5/AA+pwfxTHszybokwDCHE7xcoQw1tAha3C9ab31fr1TZgtpiholfYyJzJS0qrQK7JfAIpJaj2XPOCJNAHjQEaZw1oUlODqnZ0EAdwPAfd9y5KxyWfenbPGH+OXddzdexBQqoG5ZOyb51a1R/M4MEXrGRJJgAAAABJRU5ErkJggg==" />
+</div>
+</td>
+<td style="vertical-align: top" class="text">
+<div class="compact">
+<em>
+<br />
+</em>
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+10
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching ">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class="hatching left_day">
+<div class="compact">
+10
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+10
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" left_day">
+<div class="compact">
+10
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+>
+<div class="compact">
+</div>
+</td>
+<td style="vertical-align: top; text-align: center;"
+class=" right_day">
+<div class="compact">
+</div>
+</td>
+</tr>
+</tbody>
+
+</table>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<!-- 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/. -->
+ <head>
+ <style>
+ body { writing-mode:vertical-lr; }
+ #el0 {
+ height: 200px ! important;
+ height: 1em;
+ width: 1em;
+ padding: 5px;
+ display: table;
+ transform:translate3d(0, 80px, 0);
+ }
+ #el0:before {
+ display: -moz-box;
+ content: counter(c, hiragana) attr(id);
+ counter-increment: c 694;
+ }
+ #el0:after {
+ counter-reset: c 694;
+ content: counter(c, cjk-ideographic) attr(id);
+ }
+ #el1 {
+ text-shadow: 0px 20px 0px, 0px -20px 10px;
+ line-height: 4px;
+ transform: translate3d(0px, -300px, 0px);
+ display: table-row-group;
+ border-spacing: 7px;
+ }
+ #el1:after {
+ counter-reset: c;
+ display: -moz-box;
+ content: counter(c, cjk-ideographic) attr(id);
+ counter-increment: c 694;
+ }
+ #el2 {
+ display: table-row-group;
+ transform:translate3d(0, 80px, 0);
+ }
+ #el2:after {
+ content: counter(c, cjk-ideographic) attr(id);
+ }
+ </style>
+ <script>
+ onload = function() {
+ el0=document.createElement('div')
+ el0.setAttribute('id','el0')
+ document.body.appendChild(el0)
+ el1=document.createElement('div')
+ el1.setAttribute('id','el1')
+ el0.appendChild(el1)
+ el2=document.createElement('q')
+ el2.setAttribute('id','el2')
+ el1.appendChild(el2)
+ el0.appendChild(document.createTextNode('A'))
+ setTimeout("location.reload()", 100)
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ row1.style.textAlign = "left";
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+ <table>
+ <tbody>
+ <tr id="row1"></tr>
+ <tr>
+ <td style="position: sticky;"></td>
+ </tr>
+ </tbody>
+ </table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="border-style: dotted; position: sticky; display: table-row;"><input style="width: 400px; position: absolute;"></div>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body>
+
+<div style="display: table-caption; margin-left: 20000%;"></div>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html style="writing-mode: vertical-rl;" class="reftest-wait">
+ <script>
+ function go() {
+ x.style.paddingRight='900%';
+ setTimeout(done, 0);
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <body onload="go()">
+ <div style="transition-delay: 1ms;" id="x"></div>
+ <div style="position: relative; display: table-header-group;">
+ <div style="float: right; padding-top: 153%;"></div>
+ </div>
+ </body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<style>
+p {
+ writing-mode:tb;
+ float:right;
+ font-size:2000px;
+}
+#bb {
+ position:sticky;
+ display:table-footer-group;
+}
+#dd {
+ transition:2s ease-in;
+}
+</style>
+<body>
+<div id="dd"></div>
+<div id="bb">
+ <p>grilling it up
+</div>
+</body>
+<script>
+document.body.offsetTop;
+dd.style.marginTop = "100px";
+</script>
+</html>
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 @@
+<html><head>
+<meta charset=utf-8>
+<script>
+window.onload = function() {
+ let t = document.createElement('tbody');
+ t.style="writing-mode:sideways-lr";
+ document.body.appendChild(t);
+ let c = document.createElement('col');
+ t.appendChild(c);
+};
+</script>
+</head>
+<body>
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 @@
+<html><head>
+<meta charset=utf-8>
+<script>
+window.onload = function() {
+ let t = document.createElement('div');
+ t.style="writing-mode:sideways-lr; display:table-row-group;";
+ document.body.appendChild(t);
+ let c = document.createElement('col');
+ t.appendChild(c);
+};
+</script>
+</head>
+<body>
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 @@
+<html>
+ <body>
+ <table>
+ <tr>
+ <td>
+ <span>
+ <span>
+ <head>
+ <title></title>
+ </head>
+ <table>
+ <tr>
+ <td>
+ <table align="left">
+ <tr>
+ <td>
+ <form>
+ <input type="submit" value="ApplyForThisPosition">
+ </form>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </span>
+ </span>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
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 @@
+<html>
+<head>
+ <style>
+ .class_1 {
+ transition-delay: 2129ms;
+ contain: strict;
+ }
+ </style>
+</head>
+<math>
+ <mover>
+ <ms></ms>
+ <mtable class="class_1"></mtable>
+ </mover>
+</math>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ #testElem {
+ border: 1px solid black;
+ background: yellow;
+ contain: layout size;
+ width: 300px;
+ }
+ </style>
+ <script>
+ function go() {
+ /* trigger an incremental reflow.
+ * Note: elem.style is undefined for MathML elements, so we have
+ * to use setAttribute().
+ */
+ testElem.setAttribute("style", "width:150px");
+ }
+ </script>
+</head>
+<body onload="go()">
+ <math>
+ <mtable id="testElem">Change my size</mtable>
+ </math>
+</body>
+</html>
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 @@
+<style id="a">
+:not(shadow) {
+ contain: strict;
+}
+.z {
+ marker-mid: url(#x);
+ display: inline-table;
+}
+</style>
+<script>
+function go() {
+ b.appendChild(c)
+ f.width
+ a.appendChild(d)
+}
+</script>
+<body onload=go()>
+<button id="b">
+<datalist id="d">
+<dt id="c" class="z" cite="x">
+<base href="x"></base>
+<image id="f">
diff --git a/layout/tables/crashtests/1555757-4.html b/layout/tables/crashtests/1555757-4.html
new file mode 100644
index 0000000000..4b2cfa1cd9
--- /dev/null
+++ b/layout/tables/crashtests/1555757-4.html
@@ -0,0 +1,21 @@
+<style id="a">
+.z {
+ marker-mid: url(#blah);
+ display: inline-table;
+ contain: layout size;
+}
+</style>
+<script>
+function go() {
+ f.width;
+ a.appendChild(d);
+}
+</script>
+<body onload="go()">
+ <base href="x"></base>
+
+ <div class="z">
+ <img id="f">
+ <datalist id="d"></datalist>
+ </div>
+</body>
diff --git a/layout/tables/crashtests/159359-1.html b/layout/tables/crashtests/159359-1.html
new file mode 100644
index 0000000000..9d52d990bf
--- /dev/null
+++ b/layout/tables/crashtests/159359-1.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>Crash test</title>
+</head>
+<body>
+<table style="position: fixed; top: 0px;"> <!-- Crashes Mozilla -->
+<tr>
+ <form>
+ </form>
+</tr>
+</table>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/tables/crashtests/1607045.html b/layout/tables/crashtests/1607045.html
new file mode 100644
index 0000000000..60257e340f
--- /dev/null
+++ b/layout/tables/crashtests/1607045.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+<style>
+
+table tbody td {
+ border-right:1px solid;
+}
+
+table {
+ border-collapse:collapse;
+}
+
+</style>
+</head>
+<body>
+<div style="height:150px"></div>
+
+<table>
+<tbody>
+<tr height=150></tr>
+<tr height=10><td></td></tr>
+</tbody>
+
+<tfoot><tr height=50><td>footer</tfoot>
+</table>
+
+</body>
diff --git a/layout/tables/crashtests/1710116-1.html b/layout/tables/crashtests/1710116-1.html
new file mode 100644
index 0000000000..549a6c9081
--- /dev/null
+++ b/layout/tables/crashtests/1710116-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ #tbody {
+ display: inline-flex;
+ }
+
+ * {
+ inline-size: 3579845520.820976vw;
+ }
+ </style>
+</head>
+<table>
+ <td colspan="149">
+ <tbody id="tbody">
+ <col>
+ </tbody>
+ </td>
+</table>
+</html>
diff --git a/layout/tables/crashtests/1767364-1.html b/layout/tables/crashtests/1767364-1.html
new file mode 100644
index 0000000000..08c336f6d5
--- /dev/null
+++ b/layout/tables/crashtests/1767364-1.html
@@ -0,0 +1,16 @@
+<html class="reftest-paged">
+<head>
+<title>Testcase bug 1767364 - page breaks, empty thead & `position: fixed` div inside tbody causing a crash</title>
+<style>
+* {
+ border-bottom: -moz-default-color solid;
+ break-before: page
+}
+</style>
+</head>
+<table id="a">
+<thead></thead>
+<th>
+<div style="position: fixed">a</div>
+</table>
+<h1>
diff --git a/layout/tables/crashtests/1767364-2.html b/layout/tables/crashtests/1767364-2.html
new file mode 100644
index 0000000000..6104e35e89
--- /dev/null
+++ b/layout/tables/crashtests/1767364-2.html
@@ -0,0 +1,28 @@
+<html class="reftest-paged">
+<head>
+<title>Testcase bug 1767364 - page breaks & empty thead causing data loss and asserts</title>
+<style>
+ .bb {
+ break-before: page;
+ }
+ .red {
+ border-top: 1px red solid;
+ border-bottom: 1px red solid;
+ }
+ .green {
+ border-top: 1px green solid;
+ border-bottom: 1px green solid;
+ }
+ .blue {
+ border-top: 1px blue solid;
+ border-bottom: 1px blue solid;
+ }
+</style>
+</head>
+<body>
+<div class="bb red">
+<table class="bb green">
+ <thead></thead>
+ <tbody><td class="blue">data</td></tbody>
+</table>
+</div>
diff --git a/layout/tables/crashtests/1767364-3.html b/layout/tables/crashtests/1767364-3.html
new file mode 100644
index 0000000000..b183a4c342
--- /dev/null
+++ b/layout/tables/crashtests/1767364-3.html
@@ -0,0 +1,28 @@
+<html class="reftest-paged">
+<head>
+<title>Testcase bug 1767364 - page breaks & empty thead causing data loss and asserts</title>
+<style>
+ .bb {
+ break-before: page;
+ }
+ .red {
+ border-top: 1px red solid;
+ border-bottom: 1px red solid;
+ }
+ .green {
+ border-top: 1px green solid;
+ border-bottom: 1px green solid;
+ }
+ .blue {
+ border-top: 1px blue solid;
+ border-bottom: 1px blue solid;
+ }
+</style>
+</head>
+<body>
+<div class="bb red">
+<table class="bb green">
+ <thead></thead>
+ <tfoot><td class="blue">data</td></tfoot>
+</table>
+</div>
diff --git a/layout/tables/crashtests/1767364-4.html b/layout/tables/crashtests/1767364-4.html
new file mode 100644
index 0000000000..279660ce45
--- /dev/null
+++ b/layout/tables/crashtests/1767364-4.html
@@ -0,0 +1,28 @@
+<html class="reftest-paged">
+<head>
+<title>Testcase bug 1767364 - page breaks & empty thead causing data loss and asserts</title>
+<style>
+ .bb {
+ break-before: page;
+ }
+ .red {
+ border-top: 1px red solid;
+ border-bottom: 1px red solid;
+ }
+ .green {
+ border-top: 1px green solid;
+ border-bottom: 1px green solid;
+ }
+ .blue {
+ border-top: 1px blue solid;
+ border-bottom: 1px blue solid;
+ }
+</style>
+</head>
+<body>
+<div class="bb red">
+<table class="bb green">
+ <tbody></tbody>
+ <tfoot><td class="blue">data</td></tfoot>
+</table>
+</div>
diff --git a/layout/tables/crashtests/1795030.html b/layout/tables/crashtests/1795030.html
new file mode 100644
index 0000000000..2385d45034
--- /dev/null
+++ b/layout/tables/crashtests/1795030.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.addEventListener('load', () => {
+ const colgroup = document.getElementById('id_0')
+ const col = document.getElementById('id_1')
+ colgroup.span = 38
+ col.setAttribute('span', 1174069215)
+ })
+ </script>
+</head>
+<table>
+ <th colspan='177'>
+ <colgroup id='id_0'>
+ </colgroup>
+ <col>
+ <colgroup span='206'>
+ <col id='id_1'>
+ </colgroup>
+ </th>
+</table>
+</html>
diff --git a/layout/tables/crashtests/1795051.html b/layout/tables/crashtests/1795051.html
new file mode 100644
index 0000000000..3d829f5518
--- /dev/null
+++ b/layout/tables/crashtests/1795051.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ max-width: fit-content;
+ }
+ </style>
+ <script>
+ window.addEventListener('load', () => {
+ const col = document.getElementById('id_0')
+ const table = document.getElementById('id_1')
+ const colgroup_1 = document.createElement('colgroup')
+ const colgroup_2 = document.createElement('colgroup')
+ colgroup_2.span = 174
+ table.appendChild(colgroup_1)
+ table.appendChild(col)
+ table.appendChild(colgroup_2)
+ document.documentElement.getAnimations({ })
+ colgroup_1.setAttribute('span', 190)
+ colgroup_2.setAttribute('span', 0)
+ })
+ </script>
+</head>
+<table id='id_1'>
+ <col id='id_0'>
+</table>
+</html>
diff --git a/layout/tables/crashtests/1821177.html b/layout/tables/crashtests/1821177.html
new file mode 100644
index 0000000000..20b79f5987
--- /dev/null
+++ b/layout/tables/crashtests/1821177.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<style>
+#msub {
+ page-break-before: always;
+}
+mtable, mtr, mtd {
+ border: 1px solid;
+}
+</style>
+<br>
+<br>
+<math display="block">
+<mtable style="position: absolute;">
+ <mtr>
+ <mtd>
+ <mrow><mi>1</mi></mrow>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowspan="0" style="font-size: 72px;">
+ <mrow id="msub">
+ <mi>2</mi>
+ </mrow>
+ </mtd>
+ <mtd rowspan="0">
+ <mtext>2</mtext>
+ <mspace height="100px"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowspan="0">
+ <mtext>3</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd style="padding-bottom: 1000px;">
+ <mtext>4</mtext>
+ </mtd>
+ </mtr>
+</mtable>
+</math>
+</html>
diff --git a/layout/tables/crashtests/187779-1.html b/layout/tables/crashtests/187779-1.html
new file mode 100644
index 0000000000..37ef83475d
--- /dev/null
+++ b/layout/tables/crashtests/187779-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<title>bug</title>
+</head>
+<body>
+
+<table>
+ <tr>
+ </tr>
+
+ <tr>
+ <form>
+ <input name="url" size="20" type="text">
+ </form>
+ </tr>
+</table>
+
+</body>
+</html>
diff --git a/layout/tables/crashtests/189751-1.html b/layout/tables/crashtests/189751-1.html
new file mode 100644
index 0000000000..270f4b22d5
--- /dev/null
+++ b/layout/tables/crashtests/189751-1.html
@@ -0,0 +1,3 @@
+<form method="post">
+ <table><tr><td><input type="browse"></td></tr></table>
+</form> \ No newline at end of file
diff --git a/layout/tables/crashtests/197015-1.html b/layout/tables/crashtests/197015-1.html
new file mode 100644
index 0000000000..e913acb395
--- /dev/null
+++ b/layout/tables/crashtests/197015-1.html
@@ -0,0 +1,10 @@
+<html>
+<head><title>bug 197015</title></head>
+<body>
+<table>
+<!-- The "htp" typo is an essential part of this testcase. -->
+<link rel="stylesheet" type="text/css" href="htp://www.mozilla.org/foo.css">
+foo
+</table>
+</body>
+</html>
diff --git a/layout/tables/crashtests/220536-1.html b/layout/tables/crashtests/220536-1.html
new file mode 100644
index 0000000000..e15548cc77
--- /dev/null
+++ b/layout/tables/crashtests/220536-1.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Essai</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+<script type="text/javascript" language="javascript">
+
+function remove_col2() {
+
+var colp = document.getElementById("colp");
+
+var c4= document.getElementById("c4");
+
+
+
+colp.colSpan=0;
+
+colp.style.display="none";
+
+c4.style.display="none";
+
+
+
+
+
+
+
+}
+
+function remove_col() {
+var tr1 = document.getElementById("tr1");
+
+var tr2 = document.getElementById("tr2");
+
+var tr3 = document.getElementById("tr3");
+
+var colp = document.getElementById("colp");
+
+var c1 = document.getElementById("c1");
+
+var c2 = document.getElementById("c2");
+
+var c3 = document.getElementById("c3");
+
+var c4= document.getElementById("c4");
+
+
+
+c1.style.display="none";
+
+colp.colSpan=1;
+
+c3.style.display="none";
+
+c2.style.display="none";
+
+document.documentElement.offsetHeight;
+
+remove_col2();
+
+}
+
+
+
+</script>
+</head>
+<body onload="remove_col()">
+<table border="1" id="t_0">
+ <thead>
+ <tr id="tr1">
+ <th rowspan="2">aaaa</th>
+ <th rowspan="2">bbbb</th>
+ <th colspan="2" id ="colp">cccc</th>
+ <th rowspan="2">dddd</th>
+ </tr>
+ <tr id="tr2">
+ <th id="c1">eeee</th>
+ <th id="c2">ffff</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr id="tr3">
+ <td>1111</td>
+ <td>2222</td>
+ <td id="c3">3333</td>
+ <td id="c4">4444</td>
+ <td>5555</td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
diff --git a/layout/tables/crashtests/223458-1.html b/layout/tables/crashtests/223458-1.html
new file mode 100644
index 0000000000..ecf4a73603
--- /dev/null
+++ b/layout/tables/crashtests/223458-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+ <head>
+ <title>bug 223458</title>
+ <body>
+ <table style="margin-bottom: 1em; border: 1px solid red; border-collapse:collapse;">
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ <tr><td>mod_ssl</td></tr>
+ </table>
+ </body>
+</html>
diff --git a/layout/tables/crashtests/237421-1.html b/layout/tables/crashtests/237421-1.html
new file mode 100644
index 0000000000..77cb1316c3
--- /dev/null
+++ b/layout/tables/crashtests/237421-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en">
+ <head>
+ <title>bug 237421: test1</title>
+ </head>
+ <body>
+ <table>
+ <tr id="foo"><td></td></tr>
+ <tr><td colspan="2"></td></tr>
+ </table>
+ <script>
+ document.getElementById("foo").style.display="table-row";
+ document.getElementById("foo").style.display="none";
+ </script>
+ </body>
+</html>
diff --git a/layout/tables/crashtests/237421-2.html b/layout/tables/crashtests/237421-2.html
new file mode 100644
index 0000000000..a80081946b
--- /dev/null
+++ b/layout/tables/crashtests/237421-2.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en" class="reftest-wait">
+ <head>
+ <title>bug 237421: test2a</title>
+
+ <script type="text/javascript">
+
+
+
+ function remove_second_row() {
+
+ var r2 = document.getElementById("row2");
+
+ r2.style.display ="none";
+
+ setTimeout('remove_first_row()', 10);
+
+ }
+
+
+
+ function remove_first_row() {
+
+ var r1 = document.getElementById("row1");
+
+ r1.style.display ="none";
+
+ document.documentElement.removeAttribute("class");
+
+ }
+
+
+
+ </script>
+
+
+ </head>
+ <body onload="setTimeout( 'remove_second_row()', 10)">
+ <table border>
+ <tr id="row1"><td rowspan="3">a</td><td rowspan="3">b</td></tr>
+ <tr id="row2"></tr>
+ <tr></tr>
+ <tr><td>c</td></tr>
+ </table>
+
+ </body>
+</html>
diff --git a/layout/tables/crashtests/238909-1.html b/layout/tables/crashtests/238909-1.html
new file mode 100644
index 0000000000..3921e1c6b7
--- /dev/null
+++ b/layout/tables/crashtests/238909-1.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>bug 238909</title>
+ </head>
+ <body>
+ <table style="border-collapse: collapse;" width="100" height="100"></table>
+ </body>
+</html>
diff --git a/layout/tables/crashtests/239294-1.html b/layout/tables/crashtests/239294-1.html
new file mode 100644
index 0000000000..7e6f076a56
--- /dev/null
+++ b/layout/tables/crashtests/239294-1.html
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+ <title>Watch Mozilla Go Boom</title>
+</head>
+
+<body>
+
+<!--
+ To make mozilla render this properly, take out the "display: inline"
+ style, or take out the colgroup - getting rid of either makes
+ everything work fine!!!
+-->
+<table style="display: inline">
+ <col></col>
+ <colgroup span="12"></colgroup>
+ <tr>
+ <th>Year</th>
+ <th colspan="12">Month</th>
+ </tr><tr>
+ <td>2001</td>
+ <td colspan="8">&nbsp;</td>
+ <td>Sep</td>
+ <td>Oct</td>
+ <td>Nov</td>
+ <td>Dec</td>
+ </tr><tr>
+ <td>2002</td>
+ <td>Jan</td>
+ <td>Feb</td>
+ <td>Mar</td>
+ <td colspan="9">&nbsp;</td>
+ </tr></table>
+
+</body></html> \ No newline at end of file
diff --git a/layout/tables/crashtests/240854-1.html b/layout/tables/crashtests/240854-1.html
new file mode 100644
index 0000000000..ac98567a3d
--- /dev/null
+++ b/layout/tables/crashtests/240854-1.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en">
+ <head>
+ <title>Table dom Column Handling crash</title>
+
+<SCRIPT>
+function doIt() {
+ var t = document.getElementById('t1');
+ var c1 =document.getElementById('col2');
+ t.removeChild(c1);
+}
+</SCRIPT>
+</HEAD>
+<BODY onload="doIt()">
+The 2 tables should look the same
+<table id="t1" bgcolor=orange border>
+ <col width=100>
+ <col width=200>
+ <col id="col2" width=300>
+ <tr>
+ <td>100</td><td>200</td><td>auto</td>
+ </tr>
+</table>
+<BR>
+<table bgcolor=orange border>
+ <col width=100>
+ <col width=200>
+ <tr>
+ <td>100</td><td>200</td><td>auto</td>
+ </tr>
+</table>
+
+ </BODY>
+</HTML>
+
+
+
+
+
+
diff --git a/layout/tables/crashtests/266015-1.html b/layout/tables/crashtests/266015-1.html
new file mode 100644
index 0000000000..cdfd9f9496
--- /dev/null
+++ b/layout/tables/crashtests/266015-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head>
+<meta http-equiv="Content-type" content="text/html; charset=ISO-8859-1">
+
+<title>col crash 1</title><style type="text/css">
+#table { display: table}
+#col {display: table-column}
+</style></head>
+
+
+<body>
+<div id="table">
+ <div id="col">
+</div>
+ <div>this text should be visible</div>
+
+
+</div></body></html>
diff --git a/layout/tables/crashtests/267418.html b/layout/tables/crashtests/267418.html
new file mode 100644
index 0000000000..cba408e185
--- /dev/null
+++ b/layout/tables/crashtests/267418.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html lang="en-US" class="reftest-wait">
+<head>
+ <title>Testcase, bug 174470</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <style type="text/css">
+
+ table {
+ border-collapse: collapse;
+ margin: 1em;
+ border: thick solid;
+ border-color: #f0f #c0c #909 #606; /* purple */
+ }
+
+ colgroup:first-child + colgroup {
+ border: thick solid;
+ border-color: #f66 #f00 #a00 #600; /* red */
+ }
+
+ colgroup:first-child + colgroup + colgroup > col:first-child + col {
+ border: thick solid;
+ border-color: #ff0 #cc0 #990 #660; /* yellow */
+ }
+
+ colgroup + tbody + tbody {
+ border: thick solid;
+ border-color: #6f6 #0f0 #0a0 #060; /* green */
+ }
+
+ colgroup + tbody > tr:first-child + tr {
+ border: thick solid;
+ border-color: #0ff #0cc #099 #066; /* aqua */
+ }
+
+ colgroup + tbody + tbody + tbody > tr:first-child + tr > td:first-child + td {
+ border: thick solid;
+ border-color: #66f #00f #00a #006; /* blue */
+ }
+
+ </style>
+ <script>
+ function doTest() {
+ var s1 = document.getElementById("screen");
+ s1.remove();
+ document.documentElement.removeAttribute('class');
+ }
+ document.addEventListener("MozReftestInvalidate", doTest);
+ </script>
+</head>
+<body>
+
+<div id="screen" style="position: fixed; left:50px; height:200px; width: 200px; background-color:white; border:1px solid green;"></div>
+
+<p>The following two tables should be mirrors of each other, except that
+(1) the digits should still be normal left-to-right digits and (2) the
+color changes for each of the 6 colors should, in both, be lightest on top
+clockwise to darkest on the left.</p>
+
+<table dir="ltr" >
+ <colgroup>
+ <col>
+ </colgroup>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ </tbody>
+ <tbody>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ </tbody>
+ <tbody>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ </tbody>
+</table>
+
+<table dir="rtl">
+ <colgroup>
+ <col>
+ </colgroup>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ </tbody>
+ <tbody>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ </tbody>
+ <tbody>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ <tr><td>1</td><td>22</td><td>333</td><td>4444</td><td>55555</td><td>666666</td></tr>
+ </tbody>
+</table>
+
+</body>
+</html>
diff --git a/layout/tables/crashtests/275625.html b/layout/tables/crashtests/275625.html
new file mode 100644
index 0000000000..369c5aa518
--- /dev/null
+++ b/layout/tables/crashtests/275625.html
@@ -0,0 +1,8 @@
+<html><head><title>Testcase bug 275625 - Crash with a:hover, a:hover+br{display:table-row;}</title>
+<style>
+a:hover, a:hover+br{display:table-row;}
+</style>
+</head>
+<body>
+<a href="#">Hovering over the link should not crash Mozilla</a><br>
+</body></html> \ No newline at end of file
diff --git a/layout/tables/crashtests/277062-1.html b/layout/tables/crashtests/277062-1.html
new file mode 100644
index 0000000000..59ca0ace57
--- /dev/null
+++ b/layout/tables/crashtests/277062-1.html
@@ -0,0 +1,4 @@
+<html>
+<body>
+
+<TABLE><bla><TD><TD><COLGROUP></COLGROUP><COLGROUP></COLGROUP>
diff --git a/layout/tables/crashtests/278385-1.html b/layout/tables/crashtests/278385-1.html
new file mode 100644
index 0000000000..c82a3a70cd
--- /dev/null
+++ b/layout/tables/crashtests/278385-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1">
+ <title>Firefox Test</title>
+ </head>
+ <body>
+ <table border=1>
+ <tr>
+ <th>
+ <td>
+ <p>TWO</p>
+ </td>
+ <title>Firefox Test</title>
+ </tr>
+ <col>
+ <tr>
+ <td>
+ <p>ONE</p>
+ </td>
+ </tr>
+ <col>
+ </table>
+ </body>
+</html>
diff --git a/layout/tables/crashtests/282175-1.html b/layout/tables/crashtests/282175-1.html
new file mode 100644
index 0000000000..27d4f86e8c
--- /dev/null
+++ b/layout/tables/crashtests/282175-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+ <head>
+ <title>bug 282175</title>
+ <script type="text/javascript">
+ function modifyPosition2()
+ {
+ document.getElementById("table").style.position = "inherit";
+ document.body.offsetHeight;
+ document.getElementById("tr").style.position = "inherit";
+ document.getElementById("table").style.position = "fixed";
+ document.body.offsetHeight;
+ document.getElementById("table").style.position = "inherit";
+ }
+ </script>
+ </head>
+ <body onload="modifyPosition2()">
+ <table style="position: fixed" id="table">
+ <tbody style="position: inherit">
+ <tr style="position: fixed" id="tr">
+ <td>0.2 miles</td>
+ </tr>
+ </tbody>
+ </table>
+ </body>
+</html>
diff --git a/layout/tables/crashtests/284844-1.html b/layout/tables/crashtests/284844-1.html
new file mode 100644
index 0000000000..ad1d72f64f
--- /dev/null
+++ b/layout/tables/crashtests/284844-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html lang="en">
+<head>
+<title>Testcase for bug 284844</title>
+</head>
+<body>
+
+<table style="height:100px; width:100px; background:lime" border="0" cellspacing="0" cellpadding="0">
+<tr><td style="background:red" ></td></tr>
+</table>
+
+</body>
+</html>
diff --git a/layout/tables/crashtests/284852.html b/layout/tables/crashtests/284852.html
new file mode 100644
index 0000000000..5b6a2e5c95
--- /dev/null
+++ b/layout/tables/crashtests/284852.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 284852</title>
+ <style type="text/css">
+
+ table {
+ border-collapse: collapse;
+ border:3px dashed blue;
+ }
+ td {
+ border:3px dotted black;
+ }
+ </style>
+<script>
+ function insertCell(id,rowspan) {
+ var tr = document.getElementById(id);
+ var td = document.createElement('td');
+ td.setAttribute('rowspan',rowspan);
+ tr.insertBefore(td,tr.firstChild)
+ }
+</script>
+</head>
+<body>
+
+<table>
+<tbody>
+ <tr><td>g1row1</td></tr>
+ <tr><td>g1row2</td></tr>
+ <tr><td>g1row3</td></tr>
+</tbody>
+<tbody>
+ <tr><td>g2row1</td></tr>
+ <tr><td>g2row2</td></tr>
+ <tr><td>g2row3</td></tr>
+ <tr id="r1"><td rowspan="0">g2row4</td></tr>
+</tbody>
+</table>
+
+<script>
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+insertCell('r1','0');
+</script>
+
+<button onclick="insertCell('r1','0')">insert cell</button>
+<pre>
+Clicking the button above five times or more gives:
+
+###!!! ASSERTION: invalid BC damage area: 'PR_FALSE', file nsTableFrame.cpp, line 4567
+</pre>
+
+<table>
+<tbody>
+ <tr><td>g1row1</td></tr>
+ <tr><td>g1row2</td></tr>
+ <tr><td>g1row3</td></tr>
+</tbody>
+<tbody>
+ <tr><td>g2row1</td></tr>
+ <tr><td>g2row2</td></tr>
+ <tr><td>g2row3</td></tr>
+ <tr id="r2"><td rowspan="2">g2row4</td></tr>
+</tbody>
+</table>
+
+<script>
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+insertCell('r2','2');
+</script>
+<button onclick="insertCell('r2','2')">insert cell</button>
+<pre>
+Clicking the button gives:
+
+###!!! ASSERTION: invalid BC damage area: 'PR_FALSE', file nsTableFrame.cpp, line 4567
+</pre>
+
+<table>
+<tbody>
+ <tr><td>g1row1</td></tr>
+ <tr><td>g1row2</td></tr>
+ <tr><td>g1row3</td></tr>
+</tbody>
+<tbody>
+ <tr><td>g2row1</td></tr>
+ <tr><td>g2row2</td></tr>
+ <tr><td>g2row3</td></tr>
+ <tr id="r3"></tr>
+</tbody>
+</table>
+
+<script>
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+insertCell('r3','0');
+</script>
+
+<button onclick="insertCell('r3','0')">insert cell</button>
+<pre>
+Clicking the button the first time gives:
+
+###!!! ASSERTION: invalid BC damage area: 'PR_FALSE', file nsTableFrame.cpp, line 4567
+</pre>
+
+
+
+</body>
+</html>
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 @@
+<title>test</title>
+
+<table style='border-collapse:collapse;'>
+ <tr>
+ <td rowspan=2></td>
+ </tr>
+ <tr>
+ <td></td>
+ </tr>
+</table>
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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+<HEAD>
+<TITLE>test</TITLE>
+</HEAD>
+<BODY>
+<TABLE BORDER=1 ID="TABLE1">
+ <TR><TD>cell data</TD><TD>cell data</TD></TR>
+ <TR><TD>cell data</TD><TD>cell data</TD></TR>
+ <TR><TD>cell data</TD><TD>cell data</TD></TR>
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+
+var t=document.getElementById("TABLE1");
+document.write(t);
+
+t.createCaption();
+
+</SCRIPT>
+</BODY>
+</HTML>
+
+
+
+
+
+
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 @@
+<html>
+<head><title>Testcase for assertion</title></head>
+<body>
+
+<table>
+ <tr>
+ <td width="367" colSpan="2">
+ <table width="100%">
+ <tr>
+ <td>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+
+</body>
+</html> \ 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 @@
+<html>
+<head>
+<script>
+function init2() {
+var one = document.getElementById('one');
+var two = document.getElementById('two');
+var three = document.getElementById('three');
+var four = document.getElementById('four');
+
+four.appendChild(two);
+document.getElementsByTagName('tbody')[0].appendChild(three);
+four.appendChild(three);
+one.appendChild(four);
+document.getElementsByTagName('table')[0].appendChild(one);
+setTimeout('clickit()', 0);
+}
+
+function doe(){
+four.remove();
+}
+
+function clickit()
+{
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ document.body.dispatchEvent(evt);
+}
+
+window.addEventListener("load", init2);
+window.addEventListener("click", doe);
+</script>
+</head>
+<body>
+<table style="border-collapse: collapse;"><tbody>
+ <tr> <td rowspan="5">P</td> <td id="three">6</td> <td>O</td> </tr>
+ <tr> <td id="four">5</td> <td>P</td> </tr>
+ <tr id="five"> <td>5</td> <td>U</td> </tr>
+ <tr id="one"> <td>6</td> <td>S</td> </tr>
+ <tr id="two"> <td>S</td> <td>9</td> </tr>
+</tbody></table>
+Mozilla should not crash when clicking in the document
+</body></html>
+
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 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="308752-1-inner.html"></iframe>
+</body>
+</html>
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 @@
+<html>
+<head>
+<script>
+function init2() {
+var one = document.getElementById('one');
+var four = document.getElementById('two');
+document.getElementsByTagName('table')[0].appendChild(one);
+setTimeout('clickit()', 0);
+}
+
+function doe(){
+two.remove();
+}
+function clickit()
+{
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ document.body.dispatchEvent(evt);
+}
+window.addEventListener("load", init2);
+window.addEventListener("click", doe);
+</script>
+</head>
+<body>
+<table style="border-collapse: collapse;">
+ <tbody>
+ <tr><td rowspan="2">r11</td></tr>
+ <tr id="one"><td id="two">r21</td></tr>
+ <tr></tr>
+ </tbody>
+</table>
+Mozilla should not crash when clicking in the document
+</body>
+</html>
+
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 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="308752-2-inner.html"></iframe>
+</body>
+</html>
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 @@
+<!doctype html public "-//w3c//dtd html 3.2//en">
+
+<html>
+
+<head>
+<title>(Type a title for your page here)</title>
+<meta name="GENERATOR" content="Arachnophilia 4.0">
+<meta name="FORMATTER" content="Arachnophilia 4.0">
+</head>
+
+<body bgcolor="#ffffff" text="#000000" link="#0000ff" vlink="#800080" alink="#ff0000">
+
+<table border>
+ <tr><td rowspan="2">foo</td><td>bar</td><td>zap</td></tr>
+ <tr style="visibility:collapse"><td colspan="2">boom</td></tr>
+</table>
+</body>
+
+</html>
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 @@
+<HTML>
+<HEAD>
+<title>Testcase bug 317876 - Crash with evil testcase on hovering after reload with display:table-row, display:inherit</title>
+</HEAD>
+<BODY>
+<div onmouseover="this.removeAttribute('style')" style="width: 20px; height: 30px;">
+ <div style="display: table-row;">
+ <span style="display: table-row;">
+ </span>
+ <span style="display: inherit;">
+ Hovering over this should not crash Mozilla
+ </span>
+ </div>
+</div>
+</BODY>
+</HTML>
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 @@
+<?xml version="1.0"?>
+<window xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <html:table>
+ <scrollbar height="656119391080747862" />
+ </html:table>
+
+</window> \ 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 @@
+<script> function init() {
+ document.getElementById("three").appendChild(document.getElementById("two"))
+
+ setTimeout(function(){
+ document.getElementById("two").appendChild(document.getElementById("one"))
+ }, 200);
+} window.addEventListener("load", init, 0); </script>
+
+<table style="border-collapse: collapse;" border="1"><tr><td id="one">1</td><td id="two">2</td></tr><tr><td id="three">3</td><td>4</td></tr></table>
+
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function foo() {
+ var table = document.getElementById("table");
+ var newRow = document.createElementNS(HTML_NS, 'tr');
+ table.insertBefore(newRow, document.getElementById('lastrow'));
+}
+
+
+doc = document;
+
+</script>
+
+</head>
+
+
+<body onload="setTimeout(foo, 300);">
+
+
+<h3>Tables</h3>
+
+
+<table id="table" border="1" style="border-collapse: collapse">
+
+ <tr>
+ <col></col>
+ <td rowspan="2">TD</td>
+ </tr>
+
+ <tr id="lastrow">
+ <td width="50%">TD</td>
+ </tr>
+
+</table>
+
+
+</body>
+</html>
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 @@
+<html>
+<body>
+
+<TABLE STYLE="position: absolute;">
+<FORM>
+<TR>
+<TD> Test
+</TR>
+</TABLE>
+</FORM>
+
+
+</body>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+
+function init()
+{
+ tbody = document.getElementsByTagName("tbody")[0];
+ comment = document.createComment('foopy');
+ tbody.parentNode.insertBefore(comment, tbody);
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+</head>
+
+<body>
+
+<table><tbody><tr><td></td></tr></tbody></table>
+
+</body>
+</html> \ 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 @@
+<html>
+
+<body>
+ <div style="direction: rtl;">
+ <table> <tr> <td colspan="3">A</td> </tr>
+ <tr style="visibility: collapse;"> <td>B</td> </tr>
+ </table>
+ </div>
+</body>
+
+</html> \ 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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<style id="styles"></style>
+
+<script>
+
+window.addEventListener("load", f1, false);
+
+function f1()
+{
+ document.getElementById("div2").setAttribute("style", "position: absolute;");
+ document.getElementById("table").setAttribute("style", "width: 200%;");
+ setTimeout(f2, 30);
+}
+
+function f2()
+{
+ document.getElementById("styles").textContent = ".thisMatchesNothing { }";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body style="position: relative; column-width: 1px;">
+
+<table id="table">
+ <tr>
+ <td>Table</td>
+ </tr>
+</table>
+
+<div>A</div>
+
+<div id="div2">B</div>
+
+<div style="display: table; width: 200%;">C</div>
+
+</body>
+</html>
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 @@
+<html class="reftest-wait">
+<head>
+
+<style id="s"></style>
+
+<script>
+
+window.addEventListener("load", f1);
+
+function f1()
+{
+ document.getElementsByTagName('head')[0].style.display = "table-row-group";
+ setTimeout(f2, 30);
+}
+
+function f2()
+{
+ document.body.style.display = "table-caption";
+ setTimeout(f3, 30);
+}
+
+function f3()
+{
+ document.body.style.display = "table-column-group";
+ document.getElementById('s').textContent += "body { }";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body>
+
+<span style="position: absolute">f</span>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+
+<head>
+<script>
+
+function boom()
+{
+ var table = document.getElementById("table");
+
+ table.insertBefore(document.createElement("thead"), document.getElementById("A"));
+
+ var tb2 = document.createElement("tfoot");
+ var tr1 = document.createElement("tr");
+ tb2.appendChild(tr1);
+ table.appendChild(tb2);
+
+ var tr = document.createElement("tr");
+ var td = document.createElement("td");
+ td.appendChild(document.createTextNode("td"));
+ td.setAttribute("rowspan", 0);
+ tr.appendChild(td);
+ document.getElementById("B").appendChild(tr);
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<table id="table">
+ <tfoot id="A"><tr><td>td</td></tr></tfoot>
+ <tfoot id="B"><tr><td>td</td></tr></tfoot>
+ <thead></thead>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+
+<head>
+<script>
+
+function boom()
+{
+ var doomed = document.getElementById("doomed");
+ doomed.remove();
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table>
+ <tr style="visibility: collapse;">
+ <td>td</td>
+ </tr>
+ <tr>
+ <td id="doomed">td</td>
+ <td rowspan="0" colspan="0">td</td>
+ </tr>
+ <tr>
+ <td>td</td>
+ </tr>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+
+<head>
+<script>
+
+function boom() {
+ var table = document.getElementById("table");
+
+ var doomed = document.getElementById("doomed");
+ doomed.remove();
+
+ var colgroup1 = document.createElement("colgroup");
+ table.appendChild(colgroup1);
+
+ var thead = document.createElement("thead");
+ var tr = document.createElement("tr");
+ var td = document.createElement("td");
+ td.setAttribute("colspan", 0);
+ tr.appendChild(td);
+ thead.appendChild(tr);
+ table.insertBefore(thead, colgroup1);
+
+ colgroup1.remove();
+
+ var colgroup2 = document.createElement("colgroup");
+ table.appendChild(colgroup2);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table id="table"><tbody><tr id="doomed"><td>x</tr></tbody></table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ var table = document.getElementById('table');
+
+ var newTR = document.createElement('tr');
+ var newTD = document.createElement('td');
+ newTR.appendChild(newTD);
+ table.appendChild(newTR);
+
+ newTD.setAttribute('rowspan', 3);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+
+<body onload="setTimeout(boom, 30);">
+
+<table id="table"><tr><td></td><td></td></tr></table>
+
+</body>
+
+</html>
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 @@
+<!doctype html public "-//w3c//dtd html 3.2//en">
+
+<html>
+
+<head>
+<title>testcase</title>
+<script>
+
+function doit()
+{
+ var doc = window.document;
+ var t1 = doc.getElementById('table1');
+ var tb = doc.createElement('tfoot');
+ var tr = doc.createElement('tr');
+ tb.appendChild(tr);
+ t1.appendChild(tb);
+ var tb = doc.createElement('tfoot');
+ var tr = doc.createElement('tr');
+ tb.appendChild(tr);
+ t1.appendChild(tb);
+ var tb = doc.createElement('tfoot');
+ var td = doc.createElement('td');
+ tr.appendChild(td);
+ tb.appendChild(tr);
+ t1.insertBefore(tb, doc.getElementById("colg1"));
+ tr.remove();
+}
+</script>
+
+</head>
+
+<body onload="doit()">
+
+<table id="table1"><tbody id="tbody1"><tr id="tr1"><td id="td1">x</td></tr></tbody><colgroup id ="colg1"></table>
+
+
+
+</body>
+
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+function boo()
+{
+ document.getElementById("td46").setAttribute("rowspan", 3);
+ document.getElementById("tbody6").appendChild(document.createTextNode('X'));
+}
+
+window.addEventListener("load", boo, false);
+
+</script>
+
+
+</head>
+<body>
+
+<table border="1">
+ <tbody id="tbody6">
+ <tr id="tr7">
+ <td>A</td>
+ <td>B</td>
+ Y
+ </tr>
+ <tr>
+ <td>C</td>
+ <td id="td46">D</td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>dom cellmap crash</title>
+
+
+
+<script>
+function doit()
+{
+ var doc = window.document;
+ var t1= doc.getElementById('table1');
+ var tbA = doc.createElement('tbody');
+ t1.appendChild(tbA);
+ var trA = doc.createElement('tr');
+ var td = doc.createElement('td');
+ td.setAttribute('rowspan', 2);
+ trA.appendChild(td);
+ tbA.appendChild(trA);
+ var trB = doc.createElement('tr');
+ var tdB = doc.createElement('td');
+ tdB.setAttribute('rowspan', 2);
+ trB.appendChild(tdB);
+ tbA.appendChild(trB);
+ tdB.remove();
+ trA.remove();
+ trB.remove();
+ var tb = doc.createElement('tbody');
+ trB.appendChild(tdB);
+ tb.appendChild(trB);
+ t1.appendChild(tb);
+}
+</script>
+
+</head>
+
+<body onload="doit()">
+
+<table id="table1"><tbody id="tbody1"><tr id="tr1"><td id="td1">x</td></tr></tbody></table>
+
+
+
+
+</body>
+
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-paged"><head>
+<title>Testcase bug 347367 - crash when print preview is opened on a certain file styled with meda=print [@ BasicTableLayoutStrategy::CalcPctAdjTableWidth]</title>
+<style>
+div.page
+{
+ float:left;
+ clear:both;
+}
+
+
+table.display td
+{
+ width:50%;
+
+}
+
+
+</style>
+ </head><body>
+ <div id="display_container">
+
+
+ <div class="page"><table class="display" border="0" rules="cols">
+ <tbody><tr><th colspan="2"><h2>Customer Information</h2></th></tr>
+ <tr><td>Company Name</td><td>TenSen.net</td></tr>
+ <tr><td>Address</td><td>208</td></tr>
+ <tr><td>City</td><td>Carthage</td></tr>
+ <tr><td>State</td><td>TX</td></tr>
+ <tr><td>Zipcode</td><td>75633</td></tr>
+ <tr><td>Contact Name</td><td>Dakota</td></tr>
+ <tr><td>Phone Number</td><td>9032353248</td></tr>
+ <tr><td>Email Address</td><td>dakota@tensen.net</td></tr>
+ <tr><td>Customer Information Additional Notes</td><td>This is a rather long "additional notes" field, just a test to see what will happen on the display page.</td></tr>
+ </tbody></table></div>
+
+ <div class="page"><table class="display" border="0" rules="cols">
+ <tbody><tr><th colspan="2"><h2>Customer Information</h2></th></tr>
+ <tr><td>Company Name</td><td>TenSen.net</td></tr>
+ <tr><td>Address</td><td>208</td></tr>
+ <tr><td>City</td><td>Carthage</td></tr>
+ <tr><td>State</td><td>TX</td></tr>
+ <tr><td>Zipcode</td><td>75633</td></tr>
+ <tr><td>Contact Name</td><td>Dakota</td></tr>
+ <tr><td>Phone Number</td><td>9032353248</td></tr>
+ <tr><td>Email Address</td><td>dakota@tensen.net</td></tr>
+ <tr><td>Customer Information Additional Notes</td><td>This is a rather long "additional notes" field, just a test to see what will happen on the display page.</td></tr>
+ </tbody></table></div>
+
+ <div class="page"><table class="display" border="0" rules="cols">
+ <tbody><tr><th colspan="2"><h2>Customer Information</h2></th></tr>
+ <tr><td>Company Name</td><td>TenSen.net</td></tr>
+ <tr><td>Address</td><td>208</td></tr>
+ <tr><td>City</td><td>Carthage</td></tr>
+ <tr><td>State</td><td>TX</td></tr>
+ <tr><td>Zipcode</td><td>75633</td></tr>
+ <tr><td>Contact Name</td><td>Dakota</td></tr>
+ <tr><td>Phone Number</td><td>9032353248</td></tr>
+ <tr><td>Email Address</td><td>dakota@tensen.net</td></tr>
+ <tr><td>Customer Information Additional Notes</td><td>This is a rather long "additional notes" field, just a test to see what will happen on the display page.</td></tr>
+ </tbody></table></div>
+
+ <div class="page"><table class="display" border="0" rules="cols">
+ <tbody><tr><th colspan="2"><h2>Customer Information</h2></th></tr>
+ <tr><td>Company Name</td><td>TenSen.net</td></tr>
+ <tr><td>Address</td><td>208</td></tr>
+ <tr><td>City</td><td>Carthage</td></tr>
+ <tr><td>State</td><td>TX</td></tr>
+ <tr><td>Zipcode</td><td>75633</td></tr>
+ <tr><td>Contact Name</td><td>Dakota</td></tr>
+ <tr><td>Phone Number</td><td>9032353248</td></tr>
+ <tr><td>Email Address</td><td>dakota@tensen.net</td></tr>
+ <tr><td>Customer Information Additional Notes</td><td>This is a rather long "additional notes" field, just a test to see what will happen on the display page.</td></tr>
+ </tbody></table></div>
+
+
+ </div>
+ </body></html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<body>
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+
+ <mtable>
+ <mtr>
+ <mtd><mi>x</mi></mtd>
+ <mtd rowspan="4" columnspan="4"><mi>y</mi></mtd>
+ </mtr>
+ <mtr>
+ <mtd rowspan="0" columnspan="0"><mi>z</mi></mtd>
+ <mtd><mi>w</mi></mtd>
+ </mtr>
+ </mtable>
+
+
+</math></div>
+
+</body>
+
+</html> \ 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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+ <table>
+ <tr>
+ <td>x</td>
+ <td rowspan="4" colspan="4">y</td>
+ </tr>
+ <tr>
+ <td rowspan="0" colspan="0">z</td>
+ <td>w</td>
+ </tr>
+ </table>
+</body>
+</html> \ 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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function foo()
+{
+ var e = document.createElement("td");
+ document.getElementById("h").appendChild(e);
+
+ e.setAttribute("rowspan", 2);
+ e.setAttribute("colspan", 3);
+
+ var tbody = document.getElementById("tbody");
+ tbody.parentNode.removeChild(tbody);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(foo, 30)">
+
+<table id="table1">
+ <thead style="visibility: collapse">
+ <tr id="h"><td rowspan="2">A</td></tr>
+ </thead>
+ <tfoot>
+ <tr><td rowspan="2" colspan="0">B</td></tr>
+ <tr><td>C</td></tr>
+ </tfoot>
+ <tbody id="tbody">
+ <tr><td>D</td></tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <div style="border-collapse: collapse;">
+ <tr style="outline: 5px solid blue;"/>
+ </div>
+ </body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+
+
+</head>
+
+
+<body>
+
+
+<h3>Tables</h3>
+
+<table style="border-collapse: collapse;">
+ <tbody>
+ <tr>
+ <td>td</td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr style="visibility: collapse;">
+ <td colspan="0">td</td>
+ </tr>
+ <tr>
+ <td>td</td>
+ <td colspan="0">td</td>
+ </tr>
+ </tbody>
+</table>
+
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.getElementById('w').setAttribute('rowspan', 0);">
+
+<table>
+ <tbody>
+ <tr></tr>
+ <tr>
+ <td id="w">y</td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<body onload="var doomed = document.getElementById('doomed'); doomed.parentNode.removeChild(doomed); ">
+
+<table id="table1" style="border-collapse: collapse;">
+ <tbody>
+ <tr>
+ </tr>
+ </tbody>
+ <tbody id="doomed">
+ <tr>
+ <td>td</td>
+ </tr>
+ </tbody>
+ <colgroup>
+ </colgroup>
+</table>
+
+</body>
+
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+function boom()
+{
+ document.getElementById('A').setAttribute('colspan', 0);
+ document.getElementById('B').setAttribute('rowspan', 3);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<table>
+ <tr>
+ <td id="A">TD</td>
+ <td id="B" colspan="3">TD</td>
+ </tr>
+</table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+
+<body onload="var yy = document.getElementById('yy'); yy.parentNode.removeChild(yy);">
+
+<table border="1" style="border-collapse: collapse;">
+ <tr>
+ <td> <td id="yy"/> </td>
+ </tr>
+</table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+function foo() {
+ var cell2 = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ cell2.appendChild(document.createTextNode('2'));
+
+ document.getElementById("row2").appendChild(cell2);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(foo, 30);">
+
+<table border="1">
+ <tr>
+ <td>A</td>
+ <td>B</td>
+ </tr>
+ <tr id="row2">
+ <td colspan="0">1</td>
+ </tr>
+</table>
+
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<table border="1">
+ <tbody>
+ <tr>
+ <td>A</td>
+ <td rowspan="3" colspan="0">B</td>
+ </tr>
+ <tr>
+ <td colspan="2">C</td>
+ <td>D</td>
+ </tr>
+ </tbody>
+</table>
+
+
+</body>
+</html>
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 @@
+<html class="reftest-paged"><head>
+<title>Testcase bug 362275 - Hang with testcase on print preview, using column-count and table related stuff</title>
+</head>
+<body>
+This page should not hang Mozilla on print preview
+<div style="column-count: 2;">
+ <span style="display: table-cell;">
+ <span>
+ <textarea style="display: table-cell;"></textarea>
+ </span>
+ </span>
+ <span style="display: table-header-group;"></span>
+</div>
+</body></html>
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 @@
+<html>
+<body>
+
+<table border="1">
+ <tbody style="overflow: hidden;">
+ <tr>
+ <td>
+ Foo
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <p style="height: 200%;">Bar</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
+ document.getElementById("www").appendChild(document.createTextNode('עִבְרִית'));
+ document.getElementById("yyy").appendChild(document.createElementNS(HTML_NS, 'div'));
+ document.documentElement.removeAttribute("class")
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);" style="width: 20em;">
+
+<p>This page should not trigger assertions.</p>
+
+ <table border="1">
+ <tbody>
+ <tr>
+ <td>Foo bar baz zap</td>
+ <td>
+ <table width="100%" height="100%" border="1">
+ <tbody>
+ <tr>
+ <td>
+ <table border="1">
+ <tbody>
+ <tr>
+ <td><span id="www">This text wraps</span></td>
+ <td>Foo</td>
+ </tr>
+ </tbody>
+ </table>
+ <div id="yyy">YYY</div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+</body>
+
+</html> \ 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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("wantstobeatable").style.display = "table";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30)">
+
+<table width="100%" border="1">
+ <tr>
+ <td>
+ <table border="1" width="100%" style="table-layout: fixed;">
+ <tr>
+ <td>Foo</td>
+ </tr>
+ </table>
+ </td>
+ <td width="22">Bar</td>
+ <td id="wantstobeatable">
+ <table border="1" style="table-layout: fixed;">
+ <tr>
+ <td>Baz</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+
+</body>
+
+</html>
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 @@
+<html><head>
+<title>Testcase bug 362275 - Hang with testcase on print preview, using column-count and table related stuff</title>
+</head>
+<body>
+This page should not hang Mozilla on print preview
+<div style="column-count: 2;">
+ <span style="display: table-cell;">
+ <span>
+ <textarea style="display: table-cell;"></textarea>
+ </span>
+ </span>
+ <span style="display: table-header-group;"></span>
+</div>
+</body></html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("tr").style.cssFloat = "right";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 300);">
+
+<table border="1">
+ <tr id="tr">
+ <td>Foo</td>
+ </tr>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head><title>Bug 368013</title></head>
+<body>
+ <table>
+ <thead>
+ <col>1</col>
+ </thead>
+ <tr>
+ <td>1</td>
+ </tr>
+ </table>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script>
+
+function boom()
+{
+ var doomed = document.getElementById("doomed");
+ doomed.parentNode.removeChild(doomed);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+<table><tbody><tr><td id="doomed" rowspan="3"></td></tr></tbody><div/></table>
+
+</body>
+
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<style>
+html {font-size: 70px}
+</style>
+<table>
+<tr>
+<td>test</td>
+<td>a variable-height box within the area defined by the left margin
+and adjacent to the bottom of the top-left-corner.</td>
+<td rowspan="3"> </td>
+<tr>
+<td>test</td>
+<td>a variable-height box within the area defined by the left margin
+and adjacent to the bottom of the top-left-corner.</td>
+<tr>
+<td>test</td>
+<td>a variable-height box within the area defined by the left margin
+and adjacent to the bottom of the top-left-corner.</td>
+
+<tr>
+<td>test</td>
+<td>a variable-height box within the area defined by the left margin
+and adjacent to the bottom of the top-left-corner.</td>
+<td rowspan="3"> </td>
+<tr>
+<td>test</td>
+<td>a variable-height box within the area defined by the left margin
+and adjacent to the bottom of the top-left-corner.</td>
+<tr>
+<td>test</td>
+<td>a variable-height box within the area defined by the left margin
+and adjacent to the bottom of the top-left-corner.</td>
+</table> \ 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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var tr2 = document.getElementById('tr2');
+ var newTD = document.createElement('td');
+ newTD.setAttribute('colspan', 2);
+ newTD.appendChild(document.createTextNode('x'));
+ tr2.insertBefore(newTD, document.getElementById('b'));
+
+ document.getElementById('a').setAttribute('colspan', 1);
+ document.getElementById('b').setAttribute('colspan', 3);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 250);">
+
+<table style="border-collapse: collapse;" border="1">
+ <tbody>
+ <tr>
+ <td id="a" colspan="3">A</td>
+ </tr>
+ <tr id="tr2">
+ <td id="b">B</td>
+ </tr>
+ <tr>
+ <td>C</td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html><head>
+<title>Testcase #2 for bug 370713</title>
+<script>
+function boom() {
+ document.getElementById('tbody').appendChild(document.createElement('tr'));
+}
+
+window.addEventListener("load", boom);
+</script>
+</head>
+<body><table style="border-collapse:collapse"><tbody id="tbody"><tr><td></td></tr><tr></tr></tbody></table></body>
+</html>
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 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var a = document.getElementById("a");
+ var b = document.getElementById("b");
+ var x = document.getElementById("x");
+
+ a.parentNode.insertBefore(x, a);
+ x.remove();
+ b.remove();
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+
+<body onload="setTimeout(boom, 30);">
+
+ <table>
+ <tr>
+ <td id="x">X</td>
+ </tr>
+ </table>
+
+ <table border="1" style="border-collapse: collapse;">
+ <tr>
+ <td id="a">A</td>
+ </tr>
+ <tr>
+ <td id="b" colspan="2">B</td>
+ </tr>
+ </table>
+
+</body>
+
+</html>
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 @@
+<html>
+<head>
+<script type="text/javascript">
+var w_inc = 1;
+var h_inc = 1;
+function resizeContainer(id) {
+ var e = document.getElementById(id);
+ var w = e.clientWidth;
+ if (w > 800 && w_inc==1)
+ w_inc=-1;
+ if (w < 100 && w_inc==-1)
+ w_inc=1;
+ e.style.width=(w+w_inc)+'px';
+
+ var h = e.clientHeight;
+ if (h > 800 && h_inc==1)
+ h_inc=-1;
+ if (h < 100 && h_inc==-1)
+ h_inc=1;
+ e.style.height=(h+h_inc)+'px';
+}
+
+function boom() {
+ var table = document.getElementById('table');
+ var newNode = document.createElement('caption');
+ table.appendChild(newNode);
+ resizeContainer('test');
+
+ newNode = document.createElement('caption');
+ table.appendChild(newNode);
+}
+
+</script>
+<title></title>
+</head>
+
+
+<body onload="boom()">
+
+<div id="test">
+<table id="table" width="100%" height="100%"><tbody><tr><td></td></tr></tbody></table>
+</div>
+
+</body>
+</html>
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 @@
+<html class="reftest-wait">
+<head>
+<title>BC crash</title>
+<script>
+function doit() {
+ var C = document.getElementById('C');
+ var newNode = document.createElement('td');
+ newNode.setAttribute('id', 'D');
+ C.insertBefore(newNode, document.getElementById('B'));
+ var D = document.getElementById('D');
+ D.remove();
+ var A = document.getElementById('A');
+ A.remove();
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+
+<body onload="doit()">
+<table style="border-collapse: collapse">
+ <tbody>
+ <tr id="C">
+ <th id="B"></th>
+ </tr>
+ <tr>
+ <td id="A" colspan="2"></th>
+ </tr>
+ <tbody><tr><th></th></tr></tbody>
+
+</table>
+</body>
+</html>
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 @@
+<html class="reftest-paged"><head>
+<style>
+td {
+height: 670px;
+}
+</style>
+</head>
+<body>
+<table border="1"><tbody>
+<tr><td rowspan="1"></td></tr>
+<tr><td rowspan="3">L!</td></tr>
+<tr><td rowspan="1"></td></tr>
+<tr><td rowspan="9">L-RLk:715KB</td></tr>
+<tr><td rowspan="6"></td></tr>
+<tr><td> jonas</td><td rowspan="7">LZ:13.34MBmZ:2.447MB</td></tr>
+<tr><td> jonas</td></tr>
+<tr><td rowspan="10">LTp:673msTp2:531.5125msTdhtml:1263msTxul:562msTs:1906ms</td><td rowspan="1"></td></tr>
+<tr><td rowspan="9">L</td></tr>
+<tr><td rowspan="5">L TUnit223/0 reftest435/03953/0/455 chrome25/0/0</td></tr>
+<tr><td rowspan="4">L-</td></tr>
+<tr><td rowspan="1"></td><td rowspan="12">LTp:748msTp2:541.15msTdhtml:1294msTxul:774msTs:2302ms</td></tr>
+<tr><td rowspan="3">LZ:13.34MBmZ:2.447MB</td></tr>
+<tr><td rowspan="29"></td><td rowspan="57"></td><td rowspan="34"></td></tr>
+<tr><td> surkov.alexander</td><td rowspan="5">L-</td></tr>
+</tbody></table>
+
+<script>
+function doe() {
+var x=document.getElementsByTagName('table')[0];
+alert(x.offsetHeight);
+}
+//setTimeout(doe, 100);
+</script>
+</body></html>
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 @@
+<html class="reftest-paged"><head><title>tinderbox: Firefox</title></head>
+<body>
+<table bgcolor="#ffffff" border="1" cellpadding="1" cellspacing="1">
+<tbody><tr align="center">
+<th>Build Time</th>
+<th>Guilty</th>
+<td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">Linux argo-vm Dep Nightly</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">Linux bl-bldlnx01 Dep argo-vm test perf</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">Linux bl-bldlnx01 Dep fx-linux-tbox test perf</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">Linux fx-linux-tbox Dep</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">Linux fxdbug-linux-tbox Dep</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">Linux qm-rhel02 dep unit test</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">MacOSX Darwin 8.8.4 bm-xserve08 Dep Universal Nightly</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">MacOSX Darwin 8.8.4 qm-xserve01 dep unit test</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">WINNT 5.1 bl-bldxp01 Dep fx-win32-tbox perf test</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">WINNT 5.1 qm-winxp01 dep unit test</font></td><td rowspan="2" bgcolor="#11dd11"><font face="Helvetica,Arial" size="-1">WINNT 5.2 fx-win32-tbox Dep Nightly</font></td></tr><tr>
+<td rowspan="1"><font size="-1">Click time to <br>see changes <br>since then</font></td><td><font size="-1">Click name to see what they did</font></td></tr>
+<tr align="center"><td align="right"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176559022">
+2007/04/14&nbsp;06:57:02</a></td>
+<td></td><td rowspan="6" bgcolor="#a5a5a5">
+</td>
+<td rowspan="11" bgcolor="#a5a5a5">
+</td>
+<td rowspan="1" bgcolor="#a5a5a5">
+</td>
+<td rowspan="5" bgcolor="#eeff00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176558900.14040.gz" onclick="return log(event,3,'1176558900.14040.gz','Started 06:55, still building..','2 minutes');" title="building">
+L/</a>
+</tt>
+</td><td rowspan="2" bgcolor="#eeff00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176559020.13823.gz" onclick="return log(event,4,'1176559020.13823.gz','Started 06:57, still building..','no time');" title="building">
+L/</a>
+</tt>
+</td><td rowspan="291" bgcolor="#a5a5a5">
+</td>
+<td rowspan="23" bgcolor="#eeff00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176555900.6820.gz" onclick="return log(event,6,'1176555900.6820.gz','Started 06:05, still building..','52 minutes');" title="building">
+L/</a>
+</tt>
+</td><td rowspan="291" bgcolor="#a5a5a5">
+</td>
+<td rowspan="113" bgcolor="#a5a5a5">
+</td>
+<td rowspan="291" bgcolor="#a5a5a5">
+</td>
+<td rowspan="125" bgcolor="#eeff00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176539400.5684.gz" onclick="return log(event,10,'1176539400.5684.gz','Started 01:30, still building..','5 hours, 27 minutes');" title="building">
+L/</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:57:00</td>
+<td></td><td rowspan="18" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176556200.14260.gz" onclick="return log(event,2,'1176556200.14260.gz','Started 06:10, finished 06:57','47 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:08:03:02,842">Tp:842ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:08:07:25,629.6625">Tp2:629.6625ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:08:11:40,1393">Tdhtml:1393ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:08:12:18,814">Txul:814ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:08:15:59,2414">Ts:2414ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:56:02</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+06:55:03</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+06:55:00</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176558180.13823.gz" onclick="return log(event,4,'1176558180.13823.gz','Started 06:43, finished 06:55','12 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:50:53,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:57:13,2016041">Lk:1.92MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:57:13,23353740">MH:22.3MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:57:13,707351">A:690K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:54:01</td>
+<td></td><td rowspan="3" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176558180.14040.gz" onclick="return log(event,3,'1176558180.14040.gz','Started 06:43, finished 06:56','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:44:01</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176558000.13598.gz" onclick="return log(event,0,'1176558000.13598.gz','Started 06:40, finished 06:54','14 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:52:22,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:52:30,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:43:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+06:41:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176557220.12015.gz" onclick="return log(event,3,'1176557220.12015.gz','Started 06:27, finished 06:44','17 minutes');" title="success">
+L</a>
+</tt>
+</td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+06:40:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176557160.11636.gz" onclick="return log(event,4,'1176557160.11636.gz','Started 06:26, finished 06:41','15 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:36:36,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:43:09,2045152">Lk:1.95MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:43:09,22597380">MH:21.6MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:43:09,711564">A:694K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:37:03</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+06:32:02</td>
+<td></td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176555120.10918.gz" onclick="return log(event,1,'1176555120.10918.gz','Started 05:52, finished 06:37','45 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:43:38,744">Tp:744ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:47:41,547.425">Tp2:547.425ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:51:31,1321">Tdhtml:1321ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:52:10,756">Txul:756ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:55:50,2331">Ts:2331ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:28:02</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176556500.10337.gz" onclick="return log(event,0,'1176556500.10337.gz','Started 06:15, finished 06:32','17 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:30:35,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:30:43,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:27:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+06:26:00</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176556200.10081.gz" onclick="return log(event,3,'1176556200.10081.gz','Started 06:10, finished 06:28','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:24:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+06:15:00</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176556140.9760.gz" onclick="return log(event,4,'1176556140.9760.gz','Started 06:09, finished 06:24','15 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:19:40,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:26:04,2079337">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:26:04,22847270">MH:21.8MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:26:04,711250">A:694K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:11:02</td>
+<td></td><td rowspan="5" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+06:10:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+06:09:00</td>
+<td></td><td rowspan="5" bgcolor="#a5a5a5">
+</td>
+<td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176555420.7500.gz" onclick="return log(event,3,'1176555420.7500.gz','Started 05:57, finished 06:11','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:07:01</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+06:06:01</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+06:05:00</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176555120.6750.gz" onclick="return log(event,0,'1176555120.6750.gz','Started 05:52, finished 06:06','14 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:04:14,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:04:23,2497566">mZ:2.382MB</a></tt>
+</td><td rowspan="6" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176555060.6750.gz" onclick="return log(event,4,'1176555060.6750.gz','Started 05:51, finished 06:06','15 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:58:45,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+06:00:02</td>
+<td></td><td rowspan="28" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176550740.6820.gz" onclick="return log(event,6,'1176550740.6820.gz','Started 04:39, finished 06:07','1 hour, 28 minutes');" title="success">
+L</a>
+ <a href="http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/2007-04-14-04-trunk/">D</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:32:38,153">Tp:153ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:52:46,526">Tdhtml:526ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:55:23,197">Txul:197ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:59:03,963">Ts:963ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176555481">
+2007/04/14&nbsp;05:58:01</a></td>
+<td></td><td rowspan="17" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176552960.6279.gz" onclick="return log(event,2,'1176552960.6279.gz','Started 05:16, finished 06:00','44 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:05:53,829">Tp:829ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:10:17,643.4375">Tp2:643.4375ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:14:33,1390">Tdhtml:1390ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:15:16,827">Txul:827ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:07:18:56,2436">Ts:2436ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:57:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:52:00</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176554700.6096.gz" onclick="return log(event,3,'1176554700.6096.gz','Started 05:45, finished 05:58','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:51:00</td>
+<td></td><td rowspan="5" bgcolor="#a5a5a5">
+</td>
+<td rowspan="16" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:49:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:46:02</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176554220.5164.gz" onclick="return log(event,4,'1176554220.5164.gz','Started 05:37, finished 05:49','12 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:44:16,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:50:48,2092960">Lk:2.00MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:50:48,23162409">MH:22.1MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:50:48,705847">A:689K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:45:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:42:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176553980.4711.gz" onclick="return log(event,3,'1176553980.4711.gz','Started 05:33, finished 05:46','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:39:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176553620.4374.gz" onclick="return log(event,0,'1176553620.4374.gz','Started 05:27, finished 05:42','15 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:40:14,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:40:22,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:37:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:34:03</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:34:02</td>
+<td></td><td rowspan="6" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176552960.3003.gz" onclick="return log(event,4,'1176552960.3003.gz','Started 05:16, finished 05:34','18 minutes');" title="testfailed">
+L-</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:33:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:27:00</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176552960.3003.gz" onclick="return log(event,3,'1176552960.3003.gz','Started 05:16, finished 05:34','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:18:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:17:01</td>
+<td></td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176549480.486.gz" onclick="return log(event,0,'1176549480.486.gz','Started 04:18, finished 05:18','1 hour');" title="success">
+L</a>
+ <a href="http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/2007-04-14-04-trunk/">D</a><br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:12:52,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:12:59,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:16:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:14:02</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176552000.3757.gz" onclick="return log(event,2,'1176552000.3757.gz','Started 05:00, finished 05:39','39 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:45:15,863">Tp:863ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:49:42,645.725">Tp2:645.725ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:53:52,1382">Tdhtml:1382ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:54:35,849">Txul:849ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:58:15,2436">Ts:2436ms</a></tt>
+</td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176552000.354.gz" onclick="return log(event,3,'1176552000.354.gz','Started 05:00, finished 05:17','17 minutes');" title="success">
+L</a>
+</tt>
+</td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:09:01</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176551940.32538.gz" onclick="return log(event,4,'1176551940.32538.gz','Started 04:59, finished 05:14','15 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:09:41,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:16:08,2076289">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:16:08,23017733">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:16:08,711868">A:695K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:00:03</td>
+<td></td><td rowspan="21" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176548220.31834.gz" onclick="return log(event,1,'1176548220.31834.gz','Started 03:57, finished 05:09','1 hour, 12 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:15:52,752">Tp:752ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:19:53,546.2875">Tp2:546.2875ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:23:40,1313">Tdhtml:1313ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:24:18,775">Txul:775ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:06:27:59,2339">Ts:2339ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+05:00:00</td>
+<td></td></tr>
+<tr align="center"><td align="right"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176551940">
+2007/04/14&nbsp;04:59:00</a></td>
+<td></td><td rowspan="7" bgcolor="#a5a5a5">
+</td>
+<td rowspan="13" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176549000.8355.gz" onclick="return log(event,3,'1176549000.8355.gz','Started 04:10, finished 05:00','50 minutes');" title="success">
+L</a>
+ <a href="http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/experimental/2007-04-14-04-trunk/">D</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:56:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+04:44:00</td>
+<td></td><td rowspan="1" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176551040.7559.gz" onclick="return log(event,4,'1176551040.7559.gz','Started 04:44, finished 04:56','12 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:51:47,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:58:20,2073225">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:58:20,23007968">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:58:20,714572">A:697K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:41:05</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+04:41:03</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176550020.6378.gz" onclick="return log(event,4,'1176550020.6378.gz','Started 04:27, finished 04:41','14 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:36:53,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:43:22,2083180">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:43:22,23009158">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:43:22,706357">A:689K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:39:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+04:32:02</td>
+<td></td><td rowspan="20" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176547200.6378.gz" onclick="return log(event,6,'1176547200.6378.gz','Started 03:40, finished 04:41','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:12:17,152">Tp:152ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:32:24,527">Tdhtml:527ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:35:01,192">Txul:192ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:38:42,958">Ts:958ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:27:00</td>
+<td></td><td rowspan="11" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176548280.5481.gz" onclick="return log(event,2,'1176548280.5481.gz','Started 03:58, finished 04:32','34 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:38:02,856">Tp:856ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:42:25,624.35">Tp2:624.35ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:46:44,1401">Tdhtml:1401ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:47:22,807">Txul:807ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:51:03,2417">Ts:2417ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:24:03</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+04:18:00</td>
+<td></td><td rowspan="6" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176548820.4763.gz" onclick="return log(event,4,'1176548820.4763.gz','Started 04:07, finished 04:24','17 minutes');" title="testfailed">
+L-</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:12:03</td>
+<td></td><td rowspan="4" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+04:11:05</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+04:10:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+04:08:02</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176548280.3233.gz" onclick="return log(event,3,'1176548280.3233.gz','Started 03:58, finished 04:11','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:07:00</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176548220.2840.gz" onclick="return log(event,0,'1176548220.2840.gz','Started 03:57, finished 04:08','11 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:06:52,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:06:59,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+04:04:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176548281">
+2007/04/14&nbsp;03:58:01</a></td>
+<td></td><td rowspan="5" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176547740.2331.gz" onclick="return log(event,4,'1176547740.2331.gz','Started 03:49, finished 04:04','15 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:56:46,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:58:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:57:00</td>
+<td></td><td rowspan="11" bgcolor="#a5a5a5">
+</td>
+<td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176547620.1626.gz" onclick="return log(event,3,'1176547620.1626.gz','Started 03:47, finished 03:58','11 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:49:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+<td rowspan="9" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176546960.3305.gz" onclick="return log(event,1,'1176546960.3305.gz','Started 03:36, finished 04:12','36 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:18:28,736">Tp:736ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:22:30,548.4">Tp2:548.4ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:26:23,1290">Tdhtml:1290ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:27:00,743">Txul:743ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:05:30:41,2345">Ts:2345ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:49:00</td>
+<td></td><td rowspan="8" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176546960.612.gz" onclick="return log(event,0,'1176546960.612.gz','Started 03:36, finished 03:49','13 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:47:27,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:47:34,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:48:01</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:47:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:47:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176546720.354.gz" onclick="return log(event,4,'1176546720.354.gz','Started 03:32, finished 03:47','15 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:42:38,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:49:10,2073918">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:49:10,22907979">MH:21.8MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:49:10,709122">A:692K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:42:01</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176546960.453.gz" onclick="return log(event,3,'1176546960.453.gz','Started 03:36, finished 03:48','12 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:40:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:37:01</td>
+<td></td><td rowspan="21" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176543660.32376.gz" onclick="return log(event,6,'1176543660.32376.gz','Started 02:41, finished 03:42','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:12:59,153">Tp:153ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:33:06,524">Tdhtml:524ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:35:43,191">Txul:191ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:39:24,965">Ts:965ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:36:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:35:01</td>
+<td></td><td rowspan="4" bgcolor="#a5a5a5">
+</td>
+<td rowspan="7" bgcolor="#a5a5a5">
+</td>
+<td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176546240.26842.gz" onclick="return log(event,3,'1176546240.26842.gz','Started 03:24, finished 03:37','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:32:00</td>
+<td></td><td rowspan="14" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176544320.26486.gz" onclick="return log(event,2,'1176544320.26486.gz','Started 02:52, finished 03:35','43 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:40:42,835">Tp:835ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:45:05,624.8125">Tp2:624.8125ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:49:16,1396">Tdhtml:1396ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:50:03,825">Txul:825ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:53:43,2447">Ts:2447ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:30:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:28:01</td>
+<td></td><td rowspan="5" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176545460.25608.gz" onclick="return log(event,4,'1176545460.25608.gz','Started 03:11, finished 03:30','19 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:22:19,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:25:02</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176545460.25244.gz" onclick="return log(event,0,'1176545460.25244.gz','Started 03:11, finished 03:28','17 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:27:07,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:27:15,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:24:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:15:03</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176545100.24798.gz" onclick="return log(event,3,'1176545100.24798.gz','Started 03:05, finished 03:25','20 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:11:00</td>
+<td></td><td rowspan="19" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176542700.23357.gz" onclick="return log(event,1,'1176542700.23357.gz','Started 02:25, finished 03:15','50 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:21:08,736">Tp:736ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:25:11,535.6125">Tp2:535.6125ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:29:07,1322">Tdhtml:1322ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:29:51,765">Txul:765ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:04:33:31,2349">Ts:2349ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:09:05</td>
+<td></td><td rowspan="4" bgcolor="#a5a5a5">
+</td>
+<td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:06:02</td>
+<td></td><td rowspan="4" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176544440.22424.gz" onclick="return log(event,4,'1176544440.22424.gz','Started 02:54, finished 03:09','15 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:01:28,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:05:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+03:00:01</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176544320.21974.gz" onclick="return log(event,3,'1176544320.21974.gz','Started 02:52, finished 03:06','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176544440">
+2007/04/14&nbsp;02:54:00</a></td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176544200.21305.gz" onclick="return log(event,0,'1176544200.21305.gz','Started 02:50, finished 03:00','10 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:59:00,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:59:08,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:53:02</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+02:52:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+02:51:01</td>
+<td></td><td rowspan="8" bgcolor="#a5a5a5">
+</td>
+<td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176543540.20555.gz" onclick="return log(event,3,'1176543540.20555.gz','Started 02:39, finished 02:53','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:50:00</td>
+<td></td><td rowspan="8" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176543360.20233.gz" onclick="return log(event,4,'1176543360.20233.gz','Started 02:36, finished 02:51','15 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:43:49,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:42:03</td>
+<td></td><td rowspan="4" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+02:41:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+02:40:02</td>
+<td></td><td rowspan="25" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176540060.18717.gz" onclick="return log(event,6,'1176540060.18717.gz','Started 01:41, finished 02:42','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:13:52,151">Tp:151ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:34:00,526">Tdhtml:526ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:36:37,189">Txul:189ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:40:17,961">Ts:961ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:39:01</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+02:39:00</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176542700.18004.gz" onclick="return log(event,0,'1176542700.18004.gz','Started 02:25, finished 02:39','14 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:38:12,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:38:19,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:38:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176542460.18174.gz" onclick="return log(event,3,'1176542460.18174.gz','Started 02:21, finished 02:40','19 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:36:00</td>
+<td></td><td rowspan="18" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176540540.17836.gz" onclick="return log(event,2,'1176540540.17836.gz','Started 01:49, finished 02:38','49 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:43:37,846">Tp:846ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:48:00,616.3125">Tp2:616.3125ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:52:12,1389">Tdhtml:1389ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:52:50,834">Txul:834ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:56:31,2420">Ts:2420ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:34:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+02:25:00</td>
+<td></td><td rowspan="5" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176542220.17419.gz" onclick="return log(event,4,'1176542220.17419.gz','Started 02:17, finished 02:34','17 minutes');" title="testfailed">
+L-</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:22:01</td>
+<td></td><td rowspan="6" bgcolor="#a5a5a5">
+</td>
+<td rowspan="3" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+02:21:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+02:18:01</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176541380.15332.gz" onclick="return log(event,3,'1176541380.15332.gz','Started 02:03, finished 02:22','19 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:17:00</td>
+<td></td><td rowspan="21" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176539760.14620.gz" onclick="return log(event,1,'1176539760.14620.gz','Started 01:36, finished 02:18','42 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:24:15,733">Tp:733ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:28:17,545.85">Tp2:545.85ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:32:09,1317">Tdhtml:1317ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:32:47,740">Txul:740ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:36:28,2332">Ts:2332ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:15:07</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+02:15:03</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176541200.14130.gz" onclick="return log(event,4,'1176541200.14130.gz','Started 02:00, finished 02:15','15 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:10:39,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:17:07,2090389">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:17:07,23012974">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:17:07,715322">A:698K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:04:04</td>
+<td></td><td rowspan="3" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176541200.14130.gz" onclick="return log(event,0,'1176541200.14130.gz','Started 02:00, finished 02:15','15 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:10:39,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:17:07,2090389">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:17:07,23012974">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:17:07,715322">A:698K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+02:03:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+02:00:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176540540.11996.gz" onclick="return log(event,3,'1176540540.11996.gz','Started 01:49, finished 02:04','15 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176541141">
+2007/04/14&nbsp;01:59:01</a></td>
+<td></td><td rowspan="4" bgcolor="#a5a5a5">
+</td>
+<td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:58:04</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:50:02</td>
+<td></td><td rowspan="6" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176540120.10435.gz" onclick="return log(event,4,'1176540120.10435.gz','Started 01:42, finished 01:58','16 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:50:05,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:50:01</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:49:01</td>
+<td></td><td rowspan="11" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176539760.8744.gz" onclick="return log(event,0,'1176539760.8744.gz','Started 01:36, finished 01:50','14 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:48:24,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:48:31,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:49:00</td>
+<td></td><td rowspan="46" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176534300.8586.gz" onclick="return log(event,8,'1176534300.8586.gz','Started 00:05, finished 01:49','1 hour, 44 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:39:09,622">Tp:622ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:42:53,487.45">Tp2:487.45ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:46:13,1262">Tdhtml:1262ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:46:49,579">Txul:579ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:50:33,1922">Ts:1922ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:43:03</td>
+<td></td><td rowspan="19" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176537720.10661.gz" onclick="return log(event,2,'1176537720.10661.gz','Started 01:02, finished 01:59','57 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:04:16,844">Tp:844ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:08:36,620.6625">Tp2:620.6625ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:12:49,1386">Tdhtml:1386ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:13:28,842">Txul:842ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:03:17:09,2441">Ts:2441ms</a></tt>
+</td><td rowspan="8" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176539820.8744.gz" onclick="return log(event,3,'1176539820.8744.gz','Started 01:37, finished 01:50','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:42:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:41:00</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:39:02</td>
+<td></td><td rowspan="26" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536520.7554.gz" onclick="return log(event,6,'1176536520.7554.gz','Started 00:42, finished 01:43','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:14:29,153">Tp:153ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:34:37,524">Tdhtml:524ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:37:14,199">Txul:199ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:40:54,960">Ts:960ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:38:03</td>
+<td></td><td rowspan="9" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176538920.7029.gz" onclick="return log(event,4,'1176538920.7029.gz','Started 01:22, finished 01:39','17 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:34:38,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:41:17,2053187">Lk:1.96MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:41:17,23014331">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:41:17,715001">A:698K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:37:03</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:37:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:37:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:36:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176538800.6885.gz" onclick="return log(event,3,'1176538800.6885.gz','Started 01:20, finished 01:38','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:30:04</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+<td rowspan="22" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536340.6885.gz" onclick="return log(event,1,'1176536340.6885.gz','Started 00:39, finished 01:38','59 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:44:53,749">Tp:749ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:48:55,544.7375">Tp2:544.7375ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:52:48,1306">Tdhtml:1306ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:53:26,762">Txul:762ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:57:07,2314">Ts:2314ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:30:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:25:08</td>
+<td></td><td rowspan="15" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536760.5534.gz" onclick="return log(event,10,'1176536760.5534.gz','Started 00:46, finished 01:30','44 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:22:00</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176537960.5097.gz" onclick="return log(event,0,'1176537960.5097.gz','Started 01:06, finished 01:25','19 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:23:16,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:23:26,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:20:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176537720.6744.gz" onclick="return log(event,4,'1176537720.6744.gz','Started 01:02, finished 01:37','35 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:15:04,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:21:35,2053126">Lk:1.96MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:21:35,23022384">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:21:35,710388">A:693K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:19:08</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176537720.6744.gz" onclick="return log(event,3,'1176537720.6744.gz','Started 01:02, finished 01:37','35 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:15:04,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:21:35,2053126">Lk:1.96MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:21:35,23022384">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:21:35,710388">A:693K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:06:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:05:02</td>
+<td></td><td rowspan="6" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:03:01</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:02:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+01:00:02</td>
+<td></td><td rowspan="14" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536160.4262.gz" onclick="return log(event,2,'1176536160.4262.gz','Started 00:36, finished 01:19','43 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:24:21,831">Tp:831ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:28:47,635.375">Tp2:635.375ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:33:20,1377">Tdhtml:1377ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:34:02,828">Txul:828ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:37:43,2446">Ts:2446ms</a></tt>
+</td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536940.733.gz" onclick="return log(event,3,'1176536940.733.gz','Started 00:49, finished 01:03','14 minutes');" title="success">
+L</a>
+</tt>
+</td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176537482">
+2007/04/14&nbsp;00:58:02</a></td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536760.32767.gz" onclick="return log(event,4,'1176536760.32767.gz','Started 00:46, finished 01:00','14 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:54:52,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:01:30,2083200">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:01:30,22997078">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:01:30,712276">A:695K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:56:01</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:49:02</td>
+<td></td><td rowspan="9" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536340.32424.gz" onclick="return log(event,0,'1176536340.32424.gz','Started 00:39, finished 00:56','17 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:54:46,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:54:57,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:49:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:47:01</td>
+<td></td><td rowspan="9" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176536160.31872.gz" onclick="return log(event,3,'1176536160.31872.gz','Started 00:36, finished 00:49','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:46:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:44:02</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+<td rowspan="19" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176534300.31599.gz" onclick="return log(event,10,'1176534300.31599.gz','Started 00:05, finished 00:47','42 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:44:01</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:42:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176535620.31205.gz" onclick="return log(event,4,'1176535620.31205.gz','Started 00:27, finished 00:44','17 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:39:08,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:45:44,2083855">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:45:44,23009145">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:45:44,713828">A:697K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:39:02</td>
+<td></td><td rowspan="25" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176532980.31205.gz" onclick="return log(event,6,'1176532980.31205.gz','Started 2007/04/13 23:43, finished 2007/04/14 00:44','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:15:33,152">Tp:152ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:35:40,523">Tdhtml:523ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:38:18,191">Txul:191ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:41:58,961">Ts:961ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:39:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:36:02</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+<td rowspan="10" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176534660.32566.gz" onclick="return log(event,1,'1176534660.32566.gz','Started 00:11, finished 00:58','47 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:04:59,713">Tp:713ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:08:59,536.2375">Tp2:536.2375ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:12:50,1328">Tdhtml:1328ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:13:33,761">Txul:761ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:02:17:14,2291">Ts:2291ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:36:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:27:02</td>
+<td></td><td rowspan="19" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176533340.30507.gz" onclick="return log(event,2,'1176533340.30507.gz','Started 2007/04/13 23:49, finished 2007/04/14 00:39','50 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:44:37,845">Tp:845ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:48:57,632.075">Tp2:632.075ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:53:38,1399">Tdhtml:1399ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:54:20,800">Txul:800ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:58:01,2435">Ts:2435ms</a></tt>
+</td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176535140.29855.gz" onclick="return log(event,3,'1176535140.29855.gz','Started 00:19, finished 00:36','17 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:27:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176534660.28224.gz" onclick="return log(event,0,'1176534660.28224.gz','Started 00:11, finished 00:27','16 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:25:54,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:26:01,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:25:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+00:24:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176534480.28124.gz" onclick="return log(event,4,'1176534480.28124.gz','Started 00:08, finished 00:25','17 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:20:25,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:26:58,2078170">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:26:58,23009203">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:26:58,709920">A:693K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:19:03</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:19:01</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:19:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:11:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176534120.27411.gz" onclick="return log(event,3,'1176534120.27411.gz','Started 00:02, finished 00:19','17 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:08:00</td>
+<td></td><td rowspan="7" bgcolor="#a5a5a5">
+</td>
+<td rowspan="13" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176532980.27411.gz" onclick="return log(event,1,'1176532980.27411.gz','Started 2007/04/13 23:43, finished 2007/04/14 00:19','36 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:25:18,741">Tp:741ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:29:18,537.8375">Tp2:537.8375ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:33:08,1307">Tdhtml:1307ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:33:46,750">Txul:750ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:37:26,2349">Ts:2349ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:06:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+00:05:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176533580.25054.gz" onclick="return log(event,4,'1176533580.25054.gz','Started 2007/04/13 23:53, finished 2007/04/14 00:06','13 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:01:37,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:08:11,2080970">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:08:11,23017114">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:08:11,714592">A:697K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:05:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+00:02:02</td>
+<td></td><td rowspan="18" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176531600.1017.gz" onclick="return log(event,8,'1176531600.1017.gz','Started 2007/04/13 23:20, finished 2007/04/14 01:05','1 hour, 45 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:55:31,608">Tp:608ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:59:13,486.0625">Tp2:486.0625ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:02:32,1272">Tdhtml:1272ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:03:08,563">Txul:563ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:06:52,1922">Ts:1922ms</a></tt>
+</td><td rowspan="18" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176531600.24735.gz" onclick="return log(event,10,'1176531600.24735.gz','Started 2007/04/13 23:20, finished 2007/04/14 00:05','45 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+00:02:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176533943">
+2007/04/13&nbsp;23:59:03</a></td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176533340.23948.gz" onclick="return log(event,3,'1176533340.23948.gz','Started 2007/04/13 23:49, finished 2007/04/14 00:02','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:53:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176532980.23354.gz" onclick="return log(event,0,'1176532980.23354.gz','Started 2007/04/13 23:43, finished 2007/04/13 23:59','16 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:57:30,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:57:38,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:51:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:49:02</td>
+<td></td><td rowspan="9" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176532440.22026.gz" onclick="return log(event,4,'1176532440.22026.gz','Started 2007/04/13 23:34, finished 2007/04/13 23:51','17 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:45:46,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:52:35,2080947">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:52:35,23027310">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:52:35,713178">A:696K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:49:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:45:01</td>
+<td></td><td rowspan="13" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176531480.23354.gz" onclick="return log(event,2,'1176531480.23354.gz','Started 2007/04/13 23:18, finished 2007/04/13 23:59','41 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:05:10,860">Tp:860ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:09:37,647.2625">Tp2:647.2625ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:13:52,1403">Tdhtml:1403ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:14:30,833">Txul:833ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:01:18:11,2436">Ts:2436ms</a></tt>
+</td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176532500.21810.gz" onclick="return log(event,3,'1176532500.21810.gz','Started 2007/04/13 23:35, finished 2007/04/13 23:49','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:43:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:40:01</td>
+<td></td><td rowspan="6" bgcolor="#a5a5a5">
+</td>
+<td rowspan="2" bgcolor="#a5a5a5">
+</td>
+<td rowspan="25" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176529320.21421.gz" onclick="return log(event,6,'1176529320.21421.gz','Started 2007/04/13 22:42, finished 2007/04/13 23:45','1 hour, 3 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:16:02,151">Tp:151ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:36:09,524">Tdhtml:524ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:38:47,199">Txul:199ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:42:27,952">Ts:952ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:39:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:36:01</td>
+<td></td><td rowspan="21" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176529680.20693.gz" onclick="return log(event,1,'1176529680.20693.gz','Started 2007/04/13 22:48, finished 2007/04/13 23:39','51 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:45:39,732">Tp:732ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:49:42,543.3125">Tp2:543.3125ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:53:38,1319">Tdhtml:1319ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:54:16,752">Txul:752ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:57:57,2325">Ts:2325ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:35:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:34:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176531480.20266.gz" onclick="return log(event,3,'1176531480.20266.gz','Started 2007/04/13 23:18, finished 2007/04/13 23:36','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:32:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:21:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176531240.19564.gz" onclick="return log(event,0,'1176531240.19564.gz','Started 2007/04/13 23:14, finished 2007/04/13 23:32','18 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:30:18,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:30:27,2497566">mZ:2.382MB</a></tt>
+</td><td rowspan="7" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176531120.19564.gz" onclick="return log(event,4,'1176531120.19564.gz','Started 2007/04/13 23:12, finished 2007/04/13 23:32','20 minutes');" title="testfailed">
+L-</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:20:01</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:20:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:19:01</td>
+<td></td><td rowspan="20" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176529080.28070.gz" onclick="return log(event,8,'1176529080.28070.gz','Started 2007/04/13 22:38, finished 2007/04/14 00:24','1 hour, 46 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:14:20,623">Tp:623ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:18:04,493.825">Tp2:493.825ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:21:23,1245">Tdhtml:1245ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:21:59,579">Txul:579ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:25:43,1906">Ts:1906ms</a></tt>
+</td><td rowspan="20" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176529080.17325.gz" onclick="return log(event,10,'1176529080.17325.gz','Started 2007/04/13 22:38, finished 2007/04/13 23:21','43 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:18:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:14:00</td>
+<td></td><td rowspan="21" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176528900.16996.gz" onclick="return log(event,2,'1176528900.16996.gz','Started 2007/04/13 22:35, finished 2007/04/13 23:20','45 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:25:34,841">Tp:841ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:29:54,617.1">Tp2:617.1ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:34:10,1387">Tdhtml:1387ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:34:53,847">Txul:847ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:38:34,2440">Ts:2440ms</a></tt>
+</td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176530460.16752.gz" onclick="return log(event,3,'1176530460.16752.gz','Started 2007/04/13 23:01, finished 2007/04/13 23:19','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:12:00</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:10:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:02:07</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176530160.15443.gz" onclick="return log(event,4,'1176530160.15443.gz','Started 2007/04/13 22:56, finished 2007/04/13 23:10','14 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:04:53,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:11:30,2060621">Lk:1.97MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:11:30,22692185">MH:21.6MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:11:30,709000">A:692K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:02:01</td>
+<td></td><td rowspan="8" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176529680.14242.gz" onclick="return log(event,0,'1176529680.14242.gz','Started 2007/04/13 22:48, finished 2007/04/13 23:02','14 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:01:06,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:01:13,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:01:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+23:00:01</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176529680.14242.gz" onclick="return log(event,3,'1176529680.14242.gz','Started 2007/04/13 22:48, finished 2007/04/13 23:02','14 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:01:06,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:01:13,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176530221">
+2007/04/13&nbsp;22:57:01</a></td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:56:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:54:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+22:49:01</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176529260.13132.gz" onclick="return log(event,4,'1176529260.13132.gz','Started 2007/04/13 22:41, finished 2007/04/13 22:54','13 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:49:01,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:55:32,2084664">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:55:32,23013762">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:55:32,710387">A:693K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:48:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:44:02</td>
+<td></td><td rowspan="7" bgcolor="#a5a5a5">
+</td>
+<td rowspan="13" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176528000.13825.gz" onclick="return log(event,1,'1176528000.13825.gz','Started 2007/04/13 22:20, finished 2007/04/13 23:00','40 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:06:02,753">Tp:753ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:10:06,558.5">Tp2:558.5ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:14:01,1329">Tdhtml:1329ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:14:45,749">Txul:749ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:14:00:18:25,2341">Ts:2341ms</a></tt>
+</td><td rowspan="9" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176528900.12450.gz" onclick="return log(event,3,'1176528900.12450.gz','Started 2007/04/13 22:35, finished 2007/04/13 22:49','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:42:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:41:00</td>
+<td></td><td rowspan="27" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176525780.11794.gz" onclick="return log(event,6,'1176525780.11794.gz','Started 2007/04/13 21:43, finished 2007/04/13 22:44','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:15:49,157">Tp:157ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:35:57,527">Tdhtml:527ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:38:34,193">Txul:193ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:42:15,964">Ts:964ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:40:02</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+22:39:02</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:38:00</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176528300.11179.gz" onclick="return log(event,4,'1176528300.11179.gz','Started 2007/04/13 22:25, finished 2007/04/13 22:39','14 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:34:30,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:41:08,2091141">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:41:08,23024639">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:41:08,715695">A:698K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:37:02</td>
+<td></td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176526500.20882.gz" onclick="return log(event,8,'1176526500.20882.gz','Started 2007/04/13 21:55, finished 2007/04/13 23:40','1 hour, 45 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:29:56,627">Tp:627ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:33:41,489.875">Tp2:489.875ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:36:58,1241">Tdhtml:1241ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:37:34,563">Txul:563ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:41:18,1922">Ts:1922ms</a></tt>
+</td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176526500.11179.gz" onclick="return log(event,10,'1176526500.11179.gz','Started 2007/04/13 21:55, finished 2007/04/13 22:39','44 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:36:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176528000.10924.gz" onclick="return log(event,0,'1176528000.10924.gz','Started 2007/04/13 22:20, finished 2007/04/13 22:37','17 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:36:11,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:36:19,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:35:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:25:00</td>
+<td></td><td rowspan="17" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176526080.11268.gz" onclick="return log(event,2,'1176526080.11268.gz','Started 2007/04/13 21:48, finished 2007/04/13 22:40','52 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:45:52,837">Tp:837ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:50:16,624.425">Tp2:624.425ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:54:29,1390">Tdhtml:1390ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:55:16,826">Txul:826ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:58:57,2417">Ts:2417ms</a></tt>
+</td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176527940.10772.gz" onclick="return log(event,3,'1176527940.10772.gz','Started 2007/04/13 22:19, finished 2007/04/13 22:36','17 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:22:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+22:20:01</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176527160.8906.gz" onclick="return log(event,4,'1176527160.8906.gz','Started 2007/04/13 22:06, finished 2007/04/13 22:22','16 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:17:54,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:24:19,2086242">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:24:19,23007818">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:24:19,715659">A:698K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:20:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:19:00</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+<td rowspan="22" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176524820.8454.gz" onclick="return log(event,1,'1176524820.8454.gz','Started 2007/04/13 21:27, finished 2007/04/13 22:20','53 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:26:35,719">Tp:719ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:30:37,536.85">Tp2:536.85ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:34:26,1288">Tdhtml:1288ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:35:03,757">Txul:757ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:38:44,2337">Ts:2337ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:09:01</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176526860.8454.gz" onclick="return log(event,3,'1176526860.8454.gz','Started 2007/04/13 22:01, finished 2007/04/13 22:20','19 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:06:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176526500.6342.gz" onclick="return log(event,0,'1176526500.6342.gz','Started 2007/04/13 21:55, finished 2007/04/13 22:09','14 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:07:29,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:07:37,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:04:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+22:02:02</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176526260.5586.gz" onclick="return log(event,4,'1176526260.5586.gz','Started 2007/04/13 21:51, finished 2007/04/13 22:04','13 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:58:29,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:05:26,2085730">Lk:1.99MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:05:26,19491954">MH:18.6MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:05:26,715034">A:698K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+22:01:02</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+22:01:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176526562">
+2007/04/13&nbsp;21:56:02</a></td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176526080.5360.gz" onclick="return log(event,3,'1176526080.5360.gz','Started 2007/04/13 21:48, finished 2007/04/13 22:02','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:55:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:51:01</td>
+<td></td><td rowspan="8" bgcolor="#a5a5a5">
+</td>
+<td rowspan="21" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176524220.13502.gz" onclick="return log(event,8,'1176524220.13502.gz','Started 2007/04/13 21:17, finished 2007/04/13 22:57','1 hour, 40 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:47:27,639">Tp:639ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:51:10,490.125">Tp2:490.125ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:54:26,1236">Tdhtml:1236ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:55:02,562">Txul:562ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:58:46,1906">Ts:1906ms</a></tt>
+</td><td rowspan="21" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176524220.4533.gz" onclick="return log(event,10,'1176524220.4533.gz','Started 2007/04/13 21:17, finished 2007/04/13 21:56','39 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:51:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:49:01</td>
+<td></td><td rowspan="10" bgcolor="#eeff00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176525240.3657.gz" onclick="return log(event,4,'1176525240.3657.gz','Started 2007/04/13 21:34, still building..','9 hours, 23 minutes');" title="building">
+L/</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:48:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:45:01</td>
+<td></td><td rowspan="13" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176524340.5157.gz" onclick="return log(event,2,'1176524340.5157.gz','Started 2007/04/13 21:19, finished 2007/04/13 22:01','42 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:06:42,837">Tp:837ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:11:03,623.4125">Tp2:623.4125ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:15:14,1384">Tdhtml:1384ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:15:53,832">Txul:832ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:23:19:34,2431">Ts:2431ms</a></tt>
+</td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176525360.3205.gz" onclick="return log(event,3,'1176525360.3205.gz','Started 2007/04/13 21:36, finished 2007/04/13 21:49','13 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:44:01</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:43:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:42:02</td>
+<td></td><td rowspan="27" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176522180.2378.gz" onclick="return log(event,6,'1176522180.2378.gz','Started 2007/04/13 20:43, finished 2007/04/13 21:44','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:15:53,153">Tp:153ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:36:00,530">Tdhtml:530ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:38:37,197">Txul:197ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:42:18,958">Ts:958ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:41:02</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176524820.2208.gz" onclick="return log(event,0,'1176524820.2208.gz','Started 2007/04/13 21:27, finished 2007/04/13 21:42','15 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:41:08,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:41:16,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:36:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:36:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:34:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176524340.1550.gz" onclick="return log(event,3,'1176524340.1550.gz','Started 2007/04/13 21:19, finished 2007/04/13 21:36','17 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:27:00</td>
+<td></td><td rowspan="4" bgcolor="#ee0000">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176524400.3657.gz" onclick="return log(event,4,'1176524400.3657.gz','Started 2007/04/13 21:20, finished 2007/04/13 21:51','31 minutes');" title="busted">
+L!</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:21:02</td>
+<td></td><td rowspan="7" bgcolor="#a5a5a5">
+</td>
+<td rowspan="13" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176523140.2145.gz" onclick="return log(event,1,'1176523140.2145.gz','Started 2007/04/13 20:59, finished 2007/04/13 21:41','42 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:47:15,745">Tp:745ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:51:18,543.6375">Tp2:543.6375ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:55:10,1328">Tdhtml:1328ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:55:54,756">Txul:756ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:59:35,2320">Ts:2320ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:20:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:20:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:19:00</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:18:02</td>
+<td></td><td rowspan="22" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176521700.31597.gz" onclick="return log(event,2,'1176521700.31597.gz','Started 2007/04/13 20:35, finished 2007/04/13 21:21','46 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:27:16,838">Tp:838ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:31:34,632.6875">Tp2:632.6875ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:35:49,1404">Tdhtml:1404ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:36:27,844">Txul:844ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:40:08,2434">Ts:2434ms</a></tt>
+</td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176523320.31395.gz" onclick="return log(event,3,'1176523320.31395.gz','Started 2007/04/13 21:02, finished 2007/04/13 21:20','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:18:01</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:17:01</td>
+<td></td><td rowspan="7" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176523140.30993.gz" onclick="return log(event,4,'1176523140.30993.gz','Started 2007/04/13 20:59, finished 2007/04/13 21:18','19 minutes');" title="testfailed">
+L-</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:17:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176523140.30879.gz" onclick="return log(event,0,'1176523140.30879.gz','Started 2007/04/13 20:59, finished 2007/04/13 21:17','18 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:15:21,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:15:29,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:03:01</td>
+<td></td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176522000.2537.gz" onclick="return log(event,8,'1176522000.2537.gz','Started 2007/04/13 20:40, finished 2007/04/13 21:45','1 hour, 5 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:35:24,614">Tp:614ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:39:10,498.65">Tp2:498.65ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:42:28,1262">Tdhtml:1262ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:43:04,578">Txul:578ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:46:48,1937">Ts:1937ms</a></tt>
+</td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176522000.30993.gz" onclick="return log(event,10,'1176522000.30993.gz','Started 2007/04/13 20:40, finished 2007/04/13 21:18','38 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:02:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:01:02</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176522480.28959.gz" onclick="return log(event,3,'1176522480.28959.gz','Started 2007/04/13 20:48, finished 2007/04/13 21:03','15 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+21:01:01</td>
+<td></td></tr>
+<tr align="center"><td align="right"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176523140">
+2007/04/13&nbsp;20:59:00</a></td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:57:01</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+<td rowspan="24" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176519780.28717.gz" onclick="return log(event,1,'1176519780.28717.gz','Started 2007/04/13 20:03, finished 2007/04/13 21:01','58 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:07:53,740">Tp:740ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:11:56,546.875">Tp2:546.875ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:15:48,1291">Tdhtml:1291ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:16:26,746">Txul:746ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:20:06,2342">Ts:2342ms</a></tt>
+</td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+20:49:01</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176522300.27998.gz" onclick="return log(event,4,'1176522300.27998.gz','Started 2007/04/13 20:45, finished 2007/04/13 20:57','12 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:52:39,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:59:13,2017495">Lk:1.92MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:59:13,23023794">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:59:13,714203">A:697K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:48:02</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:48:00</td>
+<td></td><td rowspan="11" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176521340.26283.gz" onclick="return log(event,0,'1176521340.26283.gz','Started 2007/04/13 20:29, finished 2007/04/13 20:48','19 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:46:48,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:46:56,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:45:00</td>
+<td></td><td rowspan="9" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176521700.26437.gz" onclick="return log(event,3,'1176521700.26437.gz','Started 2007/04/13 20:35, finished 2007/04/13 20:49','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:44:03</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+20:43:01</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:43:00</td>
+<td></td><td rowspan="8" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176521280.25410.gz" onclick="return log(event,4,'1176521280.25410.gz','Started 2007/04/13 20:28, finished 2007/04/13 20:43','15 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:38:03,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:44:35,2074728">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:44:35,23014932">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:44:35,710849">A:694K</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:42:02</td>
+<td></td><td rowspan="26" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176518580.25632.gz" onclick="return log(event,6,'1176518580.25632.gz','Started 2007/04/13 19:43, finished 2007/04/13 20:44','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:15:57,151">Tp:151ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:36:04,530">Tdhtml:530ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:38:41,186">Txul:186ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:42:22,954">Ts:954ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:40:01</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:40:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:36:02</td>
+<td></td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176519660.28717.gz" onclick="return log(event,8,'1176519660.28717.gz','Started 2007/04/13 20:01, finished 2007/04/13 21:01','1 hour');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:51:01,623">Tp:623ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:54:46,489.075">Tp2:489.075ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:58:03,1253">Tdhtml:1253ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:58:39,578">Txul:578ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:02:23,1907">Ts:1907ms</a></tt>
+</td><td rowspan="16" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176519660.24977.gz" onclick="return log(event,10,'1176519660.24977.gz','Started 2007/04/13 20:01, finished 2007/04/13 20:40','39 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:35:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:29:00</td>
+<td></td><td rowspan="14" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176519660.25220.gz" onclick="return log(event,2,'1176519660.25220.gz','Started 2007/04/13 20:01, finished 2007/04/13 20:42','41 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:47:44,855">Tp:855ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:52:05,612.7125">Tp2:612.7125ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:56:22,1405">Tdhtml:1405ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:57:08,838">Txul:838ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:22:00:49,2406">Ts:2406ms</a></tt>
+</td><td rowspan="8" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176520680.24586.gz" onclick="return log(event,3,'1176520680.24586.gz','Started 2007/04/13 20:18, finished 2007/04/13 20:36','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:28:00</td>
+<td></td><td rowspan="5" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+20:25:04</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+20:22:02</td>
+<td></td><td rowspan="6" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176520020.23002.gz" onclick="return log(event,4,'1176520020.23002.gz','Started 2007/04/13 20:07, finished 2007/04/13 20:25','18 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:17:52,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:20:03</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:19:02</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:19:01</td>
+<td></td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176519780.21384.gz" onclick="return log(event,0,'1176519780.21384.gz','Started 2007/04/13 20:03, finished 2007/04/13 20:19','16 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:17:19,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:17:28,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:18:00</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:07:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176519660.21384.gz" onclick="return log(event,3,'1176519660.21384.gz','Started 2007/04/13 20:01, finished 2007/04/13 20:19','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:05:02</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right">
+20:03:00</td>
+<td></td><td rowspan="6" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176519000.19227.gz" onclick="return log(event,4,'1176519000.19227.gz','Started 2007/04/13 19:50, finished 2007/04/13 20:05','15 minutes');" title="testfailed">
+L-</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:02:01</td>
+<td></td><td rowspan="4" bgcolor="#a5a5a5">
+</td>
+<td rowspan="14" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176518040.22294.gz" onclick="return log(event,1,'1176518040.22294.gz','Started 2007/04/13 19:34, finished 2007/04/13 20:22','48 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:28:16,747">Tp:747ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:32:16,527.975">Tp2:527.975ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:36:12,1299">Tdhtml:1299ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:36:49,765">Txul:765ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:40:30,2318">Ts:2318ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right">
+20:01:03</td>
+<td></td></tr>
+<tr align="center"><td align="right">
+20:01:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176519002">
+2007/04/13&nbsp;19:50:02</a></td>
+<td></td><td rowspan="19" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176516960.18710.gz" onclick="return log(event,2,'1176516960.18710.gz','Started 2007/04/13 19:16, finished 2007/04/13 20:02','46 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:08:02,844">Tp:844ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:12:23,620.475">Tp2:620.475ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:16:34,1394">Tdhtml:1394ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:17:19,839">Txul:839ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:21:00,2437">Ts:2437ms</a></tt>
+</td><td rowspan="5" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176518820.18478.gz" onclick="return log(event,3,'1176518820.18478.gz','Started 2007/04/13 19:47, finished 2007/04/13 20:01','14 minutes');" title="success">
+L</a>
+</tt>
+</td><td rowspan="25" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176514560.21671.gz" onclick="return log(event,8,'1176514560.21671.gz','Started 2007/04/13 18:36, finished 2007/04/13 20:20','1 hour, 44 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:10:25,629">Tp:629ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:14:11,490.575">Tp2:490.575ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:17:29,1245">Tdhtml:1245ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:18:05,563">Txul:563ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldxp01_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:21:49,1922">Ts:1922ms</a></tt>
+</td><td rowspan="18" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176517020.18710.gz" onclick="return log(event,10,'1176517020.18710.gz','Started 2007/04/13 19:17, finished 2007/04/13 20:02','45 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:50:00</td>
+<td></td><td rowspan="10" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176518040.17162.gz" onclick="return log(event,0,'1176518040.17162.gz','Started 2007/04/13 19:34, finished 2007/04/13 19:50','16 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:48:56,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:49:05,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:48:03</td>
+<td></td><td rowspan="2" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:48:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:47:00</td>
+<td></td><td rowspan="8" bgcolor="#ffaa00">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176517800.16939.gz" onclick="return log(event,4,'1176517800.16939.gz','Started 2007/04/13 19:30, finished 2007/04/13 19:48','18 minutes');" title="testfailed">
+L-</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:40:07,4824">RLk:4.71KB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:45:02</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176518040.16939.gz" onclick="return log(event,3,'1176518040.16939.gz','Started 2007/04/13 19:34, finished 2007/04/13 19:48','14 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:43:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:42:01</td>
+<td></td><td rowspan="18" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176515040.16411.gz" onclick="return log(event,6,'1176515040.16411.gz','Started 2007/04/13 18:44, finished 2007/04/13 19:45','1 hour, 1 minute');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:16:30,153">Tp:153ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:36:38,525">Tdhtml:525ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:39:15,197">Txul:197ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=xserve08.build.mozilla.org_Fx-Trunk&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:42:56,964">Ts:964ms</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:36:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:35:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:34:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:30:00</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+<td rowspan="14" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176514740.15723.gz" onclick="return log(event,1,'1176514740.15723.gz','Started 2007/04/13 18:39, finished 2007/04/13 19:42','1 hour, 3 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:48:39,749">Tp:749ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:52:39,528.725">Tp2:528.725ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:56:32,1318">Tdhtml:1318ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:57:10,764">Txul:764ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:21:00:51,2344">Ts:2344ms</a></tt>
+</td><td rowspan="8" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176516960.14347.gz" onclick="return log(event,3,'1176516960.14347.gz','Started 2007/04/13 19:16, finished 2007/04/13 19:35','19 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:28:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:24:02</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176516660.13081.gz" onclick="return log(event,4,'1176516660.13081.gz','Started 2007/04/13 19:11, finished 2007/04/13 19:28','17 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:23:19,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:29:49,2078589">Lk:1.98MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:29:49,22997262">MH:21.9MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:29:49,703639">A:687K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:23:01</td>
+<td></td><td rowspan="8" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176516360.12577.gz" onclick="return log(event,0,'1176516360.12577.gz','Started 2007/04/13 19:06, finished 2007/04/13 19:24','18 minutes');" title="success">
+L</a>
+<br><a title="Firefox: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:22:44,13637883">Z:13.01MB</a><br><a title="Embed: Code + data size of all shared libs &amp; executables" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=codesize_embed&amp;units=bytes&amp;tbox=argo-vm&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:22:52,2497566">mZ:2.382MB</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:18:02</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:17:01</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:17:00</td>
+<td></td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:16:00</td>
+<td></td><td rowspan="7" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176514560.11370.gz" onclick="return log(event,10,'1176514560.11370.gz','Started 2007/04/13 18:36, finished 2007/04/13 19:18','42 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:11:00</td>
+<td></td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176515160.12295.gz" onclick="return log(event,2,'1176515160.12295.gz','Started 2007/04/13 18:46, finished 2007/04/13 19:23','37 minutes');" title="success">
+L</a>
+<br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:28:27,843">Tp:843ms</a><br><a title="Avg of the median per url pageload time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=pageload2&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:32:55,653.95">Tp2:653.95ms</a><br><a title="DHTML time" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=dhtml&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:37:07,1394">Tdhtml:1394ms</a><br><a title="Best nav open time of 9 runs" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=xulwinopen&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:37:53,823">Txul:823ms</a><br><a title="Best startup time out of 10 startups" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=startup&amp;units=ms&amp;tbox=bl-bldlnx01.office.mozilla.org_HEAD&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:20:41:34,2444">Ts:2444ms</a></tt>
+</td><td rowspan="6" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176515940.11210.gz" onclick="return log(event,3,'1176515940.11210.gz','Started 2007/04/13 18:59, finished 2007/04/13 19:17','18 minutes');" title="success">
+L</a>
+</tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:09:01</td>
+<td></td><td rowspan="1" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:06:00</td>
+<td></td><td rowspan="4" bgcolor="#11dd11">
+<tt>
+
+<a href="http://tinderbox.mozilla.org/showlog.cgi?log=Firefox/1176515700.10235.gz" onclick="return log(event,4,'1176515700.10235.gz','Started 2007/04/13 18:55, finished 2007/04/13 19:09','14 minutes');" title="success">
+L</a>
+<br><a title="refcnt Leaks" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=refcnt_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:04:10,4824">RLk:4.71KB</a><br><a title="Leaks: total bytes 'malloc'ed and not 'free'd" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_leaks&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:10:43,2092043">Lk:2.00MB</a><br><a title="Maximum Heap: max (bytes 'malloc'ed - bytes 'free'd) over run" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_maxheap&amp;units=bytes&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:10:43,23023201">MH:22.0MB</a><br><a title="Allocations: number of calls to 'malloc' and friends" href="http://build-graphs.mozilla.org/graph/query.cgi?testname=trace_malloc_allocs&amp;units=count&amp;tbox=fxdbug-linux-tbox.build.mozilla.org&amp;autoscale=1&amp;days=7&amp;avg=1&amp;showpoint=2007:04:13:19:10:43,705615">A:689K</a></tt>
+</td></tr>
+<tr align="center"><td align="right" bgcolor="#e7e7e7">
+19:02:02</td>
+<td></td><td rowspan="3" bgcolor="#a5a5a5">
+</td>
+</tr>
+<tr align="center"><td align="right"><a href="http://bonsai.mozilla.org/cvsquery.cgi?module=PhoenixTinderbox&amp;date=explicit&amp;mindate=1176515941">
+2007/04/13&nbsp;18:59:01</a></td>
+<td></td></tr>
+<tr align="center"><td align="right">
+18:59:00</td>
+<td></td></tr>
+</tbody></table>
+</html>
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 @@
+<html class="reftest-paged">
+<body>
+<table border>
+ <tr>
+ <td rowspan="2" valign="top">
+<p>Location: </p>
+
+<p>
+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 &#150; they don&#x2019;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.
+</p>
+
+<p>
+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.
+</p>
+
+<p>
+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.
+</p>
+
+<p>
+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.
+</p>
+
+<p>
+From Highway 880: Take Highway 880 to Highway 280 South, and then
+follow directions above from Highway 280 heading southbound to San
+Jose.
+</p>
+ </td>
+ </tr>
+ <tr height="1087">
+ </tr>
+</table>
+</body>
+</html>
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 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("a").style.clear = "right";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="display: table;">
+ <div id="a">a</div>
+ <div id="b" style="height: 18000000px;">b</div>
+ <div id="c">c c c</div>
+</div>
+
+</body>
+</html>
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 @@
+<html><head>
+<title>ASSERTION: no common ancestor at all??? with iframe in display: table-caption</title>
+</head>
+<body style="display: table-caption;">
+<iframe></iframe>
+</body></html> \ 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 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ var tbody = document.getElementById("tbody");
+ tbody.style.overflow = "auto";
+ document.body.offsetWidth;
+ tbody.style.overflow = "";
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<table border="1">
+ <tbody id="tbody">
+ <tr>
+ <td>
+ <p>A</p>
+ <p style="height: 18000000px">B</p>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<table>
+ <tr>
+ <td>A B</td>
+ <td>C</td>
+ </tr>
+ <tr>
+ <td width="100" colspan="3">D</td>
+ </tr>
+</table>
+
+</body>
+</html>
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 @@
+<table cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td>hello</td>
+ <td>
+ <!-- contents of the cell with 0 min width and non-0 pref width -->
+ <table cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td width="5"></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2" width="100"></td>
+ </tr>
+</table>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+td { font-family: monospace; white-space: pre; }
+</style>
+</head>
+<body>
+<table border="1"><tbody><tr><td>&#9;TabIndented</td></tr></tbody></table>
+</body>
+</html>
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 @@
+<html>
+<body onload="document.getElementById('table').style.position = 'fixed';">
+
+<table id="table" border="1">
+ <tr>
+ <td>td</td>
+ <caption>caption</caption>
+ </tr>
+</table>
+
+</body>
+</html> \ 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 @@
+<html style="display: inline-table;" class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ document.documentElement.style.overflow = "auto";
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(boom, 30);">
+Foo
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<body onload="document.getElementById('table1').style.position = 'fixed';">
+
+<table border="1" id="table1" style="border-collapse: collapse; color: red">
+ <tr>
+ <td style="float: left; overflow: auto;">1</td>
+ </tr>
+</table>
+
+<table border="1" style="border-collapse: collapse; color: green">
+ <tr>
+ <td>x</td>
+ </tr>
+ <tr style="display: inline-table">
+ <td>a</td>
+ <td style="display: table-column-group">b</td>
+ <td style="display: inline-table">c</td>
+ </tr>
+</table>
+
+</body>
+
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+.pad { padding: 100%; }
+</style>
+</head>
+
+<body>
+
+ <table>
+ <div class="pad">x</div>
+ <tr>
+ <td>a</td>
+ <td>b</td>
+ <div class="pad">c</div>
+ </tr>
+ </table>
+
+</body>
+
+</html>
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 @@
+<html>
+<head>
+<style>
+.pad { padding: 50%; }
+</style>
+</head>
+
+<body>
+ <table cellspacing=0 cellpadding=0>
+ <tr>
+ <td><div class="pad">a</div></td>
+ <td><div class="pad">b</div></td>
+ </tr>
+ </table>
+</body>
+
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.getElementById('d').style.margin = '60%';">
+ <div style="display: table-caption;">
+ <div id="d" >
+ a
+ b
+ <span style="float: left;">c</span>
+ <span style="float: left;">d</span>
+ </div>
+ </div>
+</body>
+</html>
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 @@
+<html>
+
+<head>
+<script>
+
+function boom()
+{
+ document.body.style.direction = "rtl";
+ tbody = document.getElementById("tbody");
+ tbody.contentEditable = "true";
+ tbody.focus();
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table border="1">
+ <tbody>
+ <tr>
+ <td>
+ <table border="1" style="border-collapse: collapse;"><tbody id="tbody"><tr></tr>
+ <tr>
+ <td></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<html>
+<head>
+</head>
+
+<body style="column-width: 10em;">
+
+<table>
+ <tbody>
+ <tr>
+ <td><img src="about:blank"></td>
+ </tr>
+ <tr>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
+ \ 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 @@
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style>
+tbody, td, span { top: 10%; }
+</style>
+</head>
+<body>
+<div style="width: 1px;">
+<table border="1"><tbody><tr><td>
+ש תות ב<span></span>עית בלעברמון - ד
+</td></tr></tbody></table>
+</div>
+</body>
+</html>
+ \ 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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<span style="border-spacing: 3ch;"><td></td></span>
+
+</body>
+</html>
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 @@
+<html>
+<head>
+</head>
+<body>
+<table><tr><td><div style="margin: 70%;"></td><td></td></tr></table>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<select><table><div style="margin: 0 100%;" /></table></select>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ document.documentElement.style.emptyCells = "show";
+ document.getElementsByTagName("td")[0].style.borderColor = "magenta";
+ document.getElementsByTagName("col")[0].style.position = "absolute";
+}
+</script>
+</head>
+<body onload="boom();">
+<td><col span="3"></col></td>
+</body>
+</html>
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 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var col = document.createElement("col");
+ col.setAttribute('span', 2);
+ document.body.appendChild(col);
+ col.removeAttribute('span');
+ document.body.offsetHeight;
+ document.body.removeChild(col);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
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 @@
+<html>
+<head>
+</head>
+<body>
+<table border="1">
+<tr>
+<td>x<div style="margin: 0 100%;"></div></td>
+<td width="8%">y</td>
+</tr>
+</table>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var b = document.getElementById('b');
+ b.style.counterIncrement = 'chicken';
+ document.body.offsetHeight;
+ b.style.counterIncrement = '';
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<col id="a" span="2"></col><col id="b"></col>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body style="column-width: 1px; position: absolute;"><table style="position: absolute;"><tbody style="bottom: -10px;">foo</tbody></table></body></html>
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 @@
+<html>
+
+<head>
+<script>
+function boom()
+{
+ document.body.appendChild(document.createTextNode("a"));
+ document.body.appendChild(document.createTextNode("b"));
+ document.body.appendChild(document.createTextNode("c"));
+}
+</script>
+</head>
+
+<body onload="boom();" style="display: table-row; text-indent: 17895702px;"></body>
+
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<div style="column-count: 3;"><table style="margin: 17895704px;"></table>x</div>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg"><!-- no body element, intentionally --><div style="display: table-column-group;"/><svg:symbol id="s"/><script>
+
+document.documentElement.offsetHeight;
+document.getElementById("s").style.display = "table-column-group";
+
+</script></html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+<table><colgroup></colgroup></table>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+
+<table>
+ <tr>
+ <td style="width: 9%" colspan="2"></td>
+ </tr>
+ <tr>
+ <td></td>
+ </tr>
+</table>
+
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head></head>
+
+<body><table height="82">x<tbody height="30"></tbody></table></body>
+
+</html>
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 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <style type="text/css">
+
+ html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0;
+ }
+
+ </style>
+</head>
+
+<body><table style="border:6px solid lime" height="82"><tbody>1</tbody><tbody height="30">2</tbody></table></body>
+
+</html>
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 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <style type="text/css">
+
+ html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0;
+ }
+
+ </style>
+
+<script>
+function insertTable() {
+ var table = document.createElement('table');
+ var tbody = document.createElement('tbody');
+ var tbody2 = document.createElement('tbody');
+ var text = document.createTextNode('1');
+ var text2 = document.createTextNode('2');
+ tbody.appendChild(text);
+ tbody2.appendChild(text2);
+ table.appendChild(tbody);
+ table.appendChild(tbody2);
+
+ table.setAttribute('height','82');
+ table.setAttribute('style','border:6px solid lime');
+
+ tbody2.setAttribute('height','30');
+
+ document.body.appendChild(table);
+
+ setTimeout(function() { document.documentElement.className = ""; }, 0);
+}
+</script>
+</head>
+
+<body onload="insertTable()"></body>
+
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body style="width: max-content;"><table><div style="margin: 100%;"></div></table></body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<style type="text/css">
+
+[class="wide"] { width: 100000000px }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("targ").appendChild(document.getElementById("cm").cloneNode(true));
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<table>
+ <tbody>
+ <td><m:mrow class="wide" id="cm"></m:mrow></td>
+ <tr><m:mrow id="targ"></m:mrow></tr>
+ </tbody>
+</table>
+</body>
+
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+window.addEventListener("load", boom, false);
+
+function boom()
+{
+ var d = document.getElementById("d");
+
+ var c = document.createElement("td");
+ c.setAttribute("rowspan", 2);
+ d.parentNode.insertBefore(c, d);
+
+ document.getElementById("d").focus();
+
+ // Wait long enough for the caret to blink at least once.
+ setTimeout(done, 1200);
+}
+
+function done()
+{
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<!-- no <body>, intentionally -->
+
+<div dir="rtl"></div>
+
+<table><tr contenteditable="true" id="d"><td></td></tr></table>
+
+</html>
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 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var odiv = document.createElement("div");
+ odiv.style.height = "30px";
+ var idiv = document.createElement("div");
+ idiv.style.columnWidth = "1px";
+ var table = document.createElement("TABLE");
+ var x = document.createTextNode("x");
+ table.appendChild(x);
+ var tr = document.createElement("TR");
+ var td = document.createElement("TD");
+ tr.appendChild(td);
+ table.appendChild(tr);
+ idiv.appendChild(table);
+ odiv.appendChild(idiv);
+ document.body.appendChild(odiv);
+
+ document.body.offsetHeight;
+
+ td.style.fontFamily = "X";
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var c = document.createElementNS("http://www.w3.org/1999/xhtml", "caption");
+ var m = document.getElementById("m");
+
+ m.insertBefore(c, m.firstChild);
+ m.removeChild(c);
+ document.body.style.visibility = "collapse";
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);">
+ <img usemap="#m" src="data:image/gif,GIF87a%02%00%02%00%B3%00%00%00%00%00%FF%FF%FF%00%00%00%00%00%00%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%2C%00%00%00%00%02%00%02%00%00%04%03%90H%12%00%3B">
+ <map name="m" id="m"><area></map>
+</body>
+
+</html>
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 @@
+<html>
+<body>
+<table height="1061"><td>x<img style="height: 9625100395127px;"></td><td rowspan="3"></td><tr></tr><td height="67108864"></td></table>
+</body>
+</html>
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 @@
+<html style="font-size: 10px;" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+.tall { height: 2891380em; }
+
+</style>
+</head>
+
+<body>
+
+<table>
+ <tbody class="tall">
+ <td>
+ <td class="tall"></td>
+ </td>
+ <tr>
+ <td>
+ <td class="tall"></td>
+ </td>
+ </tr>
+ <tr class="tall"></tr>
+ <tr>
+ <tr class="tall">
+ <td></td>
+ </tr>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head><style type="text/css" id="s"></style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("x").style.cssFloat = "";
+ document.getElementById("s").textContent = "span { margin: -9684px; word-spacing: 31851153225in; }";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table>
+ <tr><td colspan="2"></td></tr>
+ <tr><td><span>1 2 3</span></td><td id="x" style="float: right;"><span>4 5 6</span></td></tr>
+</table>
+
+</body>
+</html>
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 @@
+<html>
+<head>
+<script>
+function doe() {
+document.getElementById('a').style.display = 'table-column-group';
+document.body.offsetHeight;
+}
+</script>
+
+<style>
+div::before { content:"b";}
+div::after { content:"a";}
+</style>
+</head>
+
+<body onload="document.body.offsetHeight; setTimeout(doe,0);">
+<div style="display: table;">
+ <span id="a" style="display: table-header-group; "></span>
+</div>
+</body>
+</html>
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 @@
+<style>table::after { content:"m"; }</style>
+<table>
+<select></select>
+<th></th>
+<colgroup>
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 @@
+<html>
+<head>
+<title> Bug 456041 - Crash [@ nsCellMapColumnIterator::GetNextFrame] with contenteditable, generated content on table and double tbody</title>
+<script>
+//setTimeout(function() {window.location.reload();}, 500);
+</script>
+</head>
+<body>
+<span contenteditable="true"></span>
+
+<style id="e">body table::after { content:"b";}</style>
+
+<table>
+<script>document.body.offsetHeight;</script>
+<tbody></tbody>
+<tbody></tbody>
+</table>
+</body>
+</html>
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 @@
+<html><head></head><body>
+<q style="display: table;">
+<script>document.body.style.display = 'none';</script>
+<object style="display: table-header-group;"></object>
+<object style="display: table;">
+<div style="display: table;"></div>
+<script style="display: table;">
diff --git a/layout/tables/crashtests/460637-1.xhtml b/layout/tables/crashtests/460637-1.xhtml
new file mode 100644
index 0000000000..394c1a613c
--- /dev/null
+++ b/layout/tables/crashtests/460637-1.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head><script type="text/javascript">
+<![CDATA[
+
+ function boom() {
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
+ var r = document.documentElement; while(r.firstChild) { r.firstChild.remove(); }
+ var table = document.createElementNS(HTML_NS, "table");
+ table.setAttribute("border", "1");
+ var text = document.createTextNode("\n ");
+ table.appendChild(text);
+ var tr1 = document.createElementNS(HTML_NS, "tr");
+ table.appendChild(tr1);
+ var tr2 = document.createElementNS(HTML_NS, "tr");
+ var input = document.createElementNS(HTML_NS, "input");
+ tr2.appendChild(input);
+ table.appendChild(tr2);
+ document.documentElement.appendChild(table);
+ var tr3 = document.createElementNS(HTML_NS, 'tr');
+ table.insertBefore(tr3, text);
+ var td = document.createElementNS(HTML_NS, 'td');
+ td.setAttribute('rowspan', 0);
+ tr3.insertBefore(td, null);
+ table.removeAttribute('border');
+ var caption = document.createElementNS(HTML_NS, 'caption');
+ table.insertBefore(caption, tr2);
+ document.documentElement.removeAttribute("class");
+ }
+
+ function ol(e) {
+ window.removeEventListener("load", ol);
+ setTimeout(boom, 400);
+ }
+
+ window.addEventListener("load", ol);
+
+]]></script>
+</head>
+
+<body></body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head><script type="text/javascript">
+<![CDATA[
+
+ function boom() {
+ var tr = document.getElementById('tr');
+ th = document.createElementNS("http://www.w3.org/1999/xhtml", 'th');
+ th.setAttribute('rowspan', 9);
+ tr.appendChild(th);
+ document.documentElement.removeAttribute("class");
+ }
+
+
+ function ol(e) {
+ window.removeEventListener("load", ol);
+ setTimeout(boom, 400);
+ }
+
+ window.addEventListener("load", ol);
+
+]]></script>
+</head>
+<body><table style="border-collapse: collapse;"><tbody><tr id="tr"></tr></tbody></table></body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head><script type="text/javascript">
+<![CDATA[
+
+ function boom() {
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+ var tr4 = document.createElementNS(HTML_NS, 'tr');
+ document.getElementById('tbody1').appendChild(tr4);
+ var span1 = document.createElementNS(HTML_NS, 'td');
+ tr4.insertBefore(span1, null);
+ document.documentElement.removeAttribute("class");
+ }
+
+ function ol(e) {
+ window.removeEventListener("load", ol);
+ setTimeout(boom, 400);
+ }
+
+ window.addEventListener("load", ol);
+
+]]>
+</script>
+</head>
+
+<body><table><tbody id="tbody1"><tr><td rowspan="0"></td></tr></tbody></table></body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<table style="border-collapse: collapse;">
+<colgroup span="2" id="b">
+<col id="a"/>
+</colgroup>
+</table>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+<![CDATA[
+function doe() {
+ document.getElementById('a').remove();
+ document.getElementById('b').style.borderLeft = "6px inset green";
+ }
+ document.documentElement.offsetHeight;
+ setTimeout(doe, 0);
+]]>
+
+</script>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<table width="16211982" cellspacing="0" style="table-layout: fixed;"><tbody><tr><td width="26"></td></tr></tbody></table>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body><div><span style="border-bottom: 1px solid green; position: relative;"><table style="position: absolute;"></table></span></div></body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var table = document.createElement('table');
+ document.body.appendChild(table);
+ var colgroup = document.createElement('colgroup');
+ table.appendChild(colgroup);
+ var col = document.createElement('col');
+ colgroup.appendChild(col);
+}
+
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
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 @@
+<div style="min-width: 4611686018427388000px"><span style="display: table-cell;"></span></div> \ 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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var a = document.getElementById("a");
+ a.parentNode.removeChild(a);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<table style="border-collapse: collapse;">X<td id="a"/><td rowspan="0"/><tbody>Y<tr><td colspan="3"/></tr></tbody></table>
+</body>
+
+</html>
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 @@
+<html style="position:fixed"><table style="position:absolute"></table></html> \ 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 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body>
+<table style="border-collapse: collapse;"><colgroup style="border: 20px solid green;"></colgroup> <tr><td style="height: 43925290cm"></td></tr><tr></tr></table>
+</body>
+</html>
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 @@
+<html class="reftest-wait" xmlns="http://www.w3.org/1999/xhtml"><body onload="setTimeout(function(){document.documentElement.appendChild(document.createTextNode('R')); document.documentElement.removeAttribute('class')},1);"><table style="border-collapse: collapse;"><col /><colgroup style="border: 1px solid green;"></colgroup><tbody>a</tbody>b</table></body></html>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("q").appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "td"));
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<table rules="cols"><thead><tr><th colspan="2"></th></tr></thead><tr></tr><tr id="q" style="border-left: 2px dotted yellow;"></tr></table>
+
+</body>
+</html>
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 @@
+<table><tr><td>
+ 4659
+</td></tr></td>
+
+<CAPTION>
+</caption>
+
+25016
+
+<TH>
+ <colgroup>
+ </COLGROUP>
+</th> \ 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 @@
+<html class="reftest-paged">
+<head>
+<style type="text/css">
+
+ div.room {
+ display: inline-block;
+ float: left;
+ border: 1px solid green;
+ }
+
+
+ </style>
+</head>
+<body>
+
+<!-- adjust height to get tfoot at page boundary" -->
+<div style=" width:430px; height: 880px; border: 1px solid green;">
+
+</div>
+
+
+<div class="room">
+ <table border width="260px" >
+ <tbody>
+ <tr>
+ <td><div style="width:50px; height:28px; border: 1px solid green;"></div>
+ </td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td >
+ <div style="width:50px; height:68px; border: 1px solid green;"></div>
+ </td>
+ </tr>
+ </tfoot>
+</table>
+</div>
+
+<div class="room" style=" width:430px; height: 311px"></div>
+
+<div class="room" style=" width:430px; height: 311px"></div>
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 @@
+<html class="reftest-paged">
+<head>
+<style type="text/css">
+
+ div{
+ border: 1px solid green;
+ }
+
+
+ </style>
+</head>
+<body>
+
+
+<div style=" width:10px; height: 10px; border: 1px solid green;">
+
+</div>
+
+
+
+ <table border width="260px" style="float:left">
+ <tbody>
+ <tr>
+ <td><div style="width:50px; height:910px;"></div>
+ </td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td >
+ <div style="width:50px; height:68px;"></div>
+ </td>
+ </tr>
+ </tfoot>
+</table>
+
+
+<div style="float:left; width:430px; height: 311px"></div>
+
+<div style="float:left; width:430px; height: 311px"></div>
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 @@
+<html class="reftest-paged">
+<head>
+<style type="text/css">
+
+ div{
+ border: 1px solid green;
+ }
+
+
+ </style>
+</head>
+<body>
+
+
+ <table border width="260px" style="float:left">
+ <tbody>
+ <tr>
+ <td><div style="width:50px; height:910px;"></div>
+ </td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td >
+ <div style="width:50px; height:68px;"></div>
+ </td>
+ </tr>
+ </tfoot>
+</table>
+
+
+<div style="float:left; width:430px; height: 311px"></div>
+
+<div style="float:left; width:430px; height: 311px"></div>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("c").setAttribute('span', 2);
+ document.getElementById("cg").setAttribute('span', 2);
+}
+</script>
+</head>
+<body onload="boom();">
+<table><colgroup id="cg"><col span="3" id="c" /></colgroup></table>
+</body>
+</html>
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 @@
+<html class="reftest-paged">
+<head>
+</head>
+<body style=" column-count: 2;">
+<b style="display: table-footer-group; page-break-before: always;"></b>
+<span style="display: table-header-group;"></span>
+</body>
+</html>
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 @@
+<html class="reftest-paged">
+<head>
+</head>
+<body style=" column-count: 2;">
+<b style="display: table-footer-group; page-break-before: always;">footer</b>
+<span style="display: table-header-group;">header</span>
+</body>
+</html>
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 @@
+<html class="reftest-paged">
+<head>
+</head>
+<body style=" column-count: 2;">
+<b style="display: table-footer-group;"></b>
+<span style="display: table-header-group; page-break-after: always;"></span>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+function placeBefore(n, r) { r.parentNode.insertBefore(n, r); }
+
+function boom()
+{
+ placeBefore(document.createElementNS(HTML_NS, 'td'), document.getElementById("td1"));
+ placeBefore(document.createElementNS(HTML_NS, 'tr'), document.getElementById("tr1"));
+}
+
+]]>
+</script>
+</head>
+
+<body onload="boom();">
+<table rules="groups"><tbody><tbody><tr id="tr1" style="border-left: 6px inset green;"><td id="td1"></td></tr></tbody></tbody></table>
+</body>
+
+</html>
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 @@
+<html class="reftest-paged" xmlns="http://www.w3.org/1999/xhtml">
+
+<table contenteditable="true">
+<li/>
+<mtext xmlns="http://www.w3.org/1998/Math/MathML" style="display: table-caption;"/>
+</table>
+
+
+<style>
+mtext::after, table::after { content:url(data:image/gif,GIF89a%01%00%E8%03%80%00%00%00%00%00%FF%FF%FF!%F9%04%00%00%00%00%00%2C%00%00%00%00%01%00%E8%03%00%02%1E%84%8F%A9%CB%ED%0F%A3%9C%B4%DA%8B%B3%DE%BC%FB%0F%86%E2H%96%E6%89%A6%EA%CA%B6%EE%0B%3B%05%00%3B); float:left;}
+
+</style>
+</html>
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 @@
+<html class="reftest-paged" xmlns="http://www.w3.org/1999/xhtml">
+
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <img style ="float: left" src=" data:image/gif,GIF89a%01%00%E8%03%80%00%00%00%00%00%FF%FF%FF!%F9%04%00%00%00%00%00%2C%00%00%00%00%01%00%E8%03%00%02%1E%84%8F%A9%CB%ED%0F%A3%9C%B4%DA%8B%B3%DE%BC%FB%0F%86%E2H%96%E6%89%A6%EA%CA%B6%EE%0B%3B%05%00%3B"/>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</html>
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 @@
+<html class="reftest-paged">
+<head>
+</head>
+<body style=" column-count: 2;">
+<table>
+<tbody><tr><td>rowgroup1</td></tr></tbody>
+<tbody><tr><td>rowgroup2</td></tr></tbody>
+</table>
+</body>
+</html>
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 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>aaa</title>
+ </head>
+ <body onload="boom();boom();">
+ <script>
+ function boom() {
+ if (document.getElementById('root').style.display != '') {
+ document.getElementById('root').style.display = '';
+ } else {
+ document.getElementById('root').style.display = 'none';
+ }
+ document.body.clientWidth;
+ }
+ </script>
+ <form>
+ <div id="root">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <table>
+ <tbody>
+ <tr>
+ <td style="border: 1px inset;">
+ <br>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </form>
+ </body>
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html class="reftest-paged">
+<style>
+div.spacer{height:80px}
+td { border: solid 1px blue}
+table {border: solid 1px green}
+</style>
+<body>
+
+ <div class="spacer"> </div>
+
+ <table>
+ <tbody>
+ <tr>
+ <td height="50" ></td>
+ <td rowspan="2"><img height="120" width="60"></td>
+ </tr>
+ <tr>
+ <td height="500" width="70"></td>
+ </tr>
+ </tbody>
+ </table>
+</body></html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<meta name="flags" content="paged">
+<title> crash at A4 90% generated content + repeatable tfoot</title>
+<link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696640">
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0; height:100%;
+}
+
+.LayoutBreakAfter:after {
+ clear: both;
+ display: block;
+ height: 0;
+ content: "\0020";
+}
+td { width:0.1in; height:0.1in; }
+div.spacer { width:50%; height:1.7in; }
+</style>
+
+</head>
+<body>
+
+<div class="spacer"></div>
+
+<div class="LayoutBreakAfter">
+ <div style="float:left">
+ <table>
+ <tbody >
+ <tr>
+ <td>
+ </td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td >
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+</div>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<meta name="flags" content="paged">
+<link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=696640">
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+html,body {
+ color:black; background-color:white; font-size:16px; padding:0; margin:0; height:100%;
+}
+
+#yui-main { float: left;}
+#bd:after { content: "."; display: block; clear: both; }
+</style>
+</head>
+<body>
+ <div id="bd">
+ <div id="yui-main">
+ <table>
+ <tbody>
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+
+
+ <tr>
+ <td><a href="http://football.fantasysports.yahoo.com/bowl/55789">i</a></td>
+ <td> – </td>
+ <td> – </td>
+ <td></td>
+ <td>vs</td>
+ <td></td>
+ <td><em>vs.</em> – </td>
+ <td></td>
+ <td></td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr >
+ <td colspan="8"><p><b >Total Points</b></p></td>
+ <td colspan="2" >0</td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ </div>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('x').setAttribute('rowspan', '3');">
+<table style="border-collapse: collapse;"><td colspan="3" id="x"></td></table>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body onload="var a = document.getElementById('a'); a.parentNode.removeChild(a);">
+<table style="border-collapse: collapse;"><tr><td colspan="2" id="a"></td><td></td></tr></table>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementsByTagName('td')[0].style.border = '2px solid green';">
+<table rules="all"><tr><td rowspan="2"></td></tr></table>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('b').setAttribute('colspan', '2');">
+<table rules="all"><tr><td rowspan="3"></td><td id="b" rowspan="2"></td></tr>
+</table>
+</body>
+</html>
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 @@
+<html>
+<body onload="document.querySelector('colgroup').style.borderLeft='6px solid green';">
+ <table style="border-collapse: collapse;">
+ <colgroup></colgroup>
+ <tbody>
+ <tr></tr>
+ </tbody>
+ <tbody>
+ <tr></tr>
+ <td></td>
+ <td colspan="2"></td>
+ </tbody>
+ </table>
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html><body><table width="1" height="1"><tr><td width="50" height="100%"><div>div</div></td></tr></table></body></html>
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 @@
+<style>
+table:after {
+ content: counter(bit0);
+ display: table-footer-group;
+</style>
+<table contenteditable><col>><col><tr>>>><tfoot>>><colgroup> \ 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 @@
+<!doctype html>
+<html>
+ <style>
+ table:after { display: table-footer-group; content: "x"; }
+ </style>
+ <table>
+ <script>
+ document.body.offsetWidth;
+ </script>
+ <tbody></tbody><colgroup></colgroup>
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 @@
+<html><head>
+<script>
+function crashMoz() {
+ var div = document.getElementById( "adiv" );
+ var table = document.createElement( "TABLE" );
+ var tr = table.insertRow( 0 );
+ var td = tr.insertCell( 0 );
+ var text = document.createTextNode( "Hello, World." );
+ td.appendChild( text );
+ td.style.backgroundImage = "url( 'any_image_here.gif' )";
+ div.appendChild( table );
+}
+</script>
+</head><body onload="crashMoz();">
+<div id=adiv></div>
+</body></html>
+
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 @@
+<HEAD>
+</SCRIPT>
+</HEAD>
+<BODY >
+<table bgcolor=orange>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <colgroup span=65535></colgroup>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ </tr>
+</table>
+</BODY></HTML>
+
+
+
+
+
+
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 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <table>
+ <tbody>
+ <tr>
+ <td colspan="0"></td>
+ </tr>
+ <tr>
+ <td rowspan="5" colspan="900"></td>
+ </tr>
+ <tr>
+ <td colspan="496"></td>
+ <td></td>
+ </tr>
+ </tbody>
+ </table>
+ </body>
+</html>
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 @@
+<!DOCTYPE HTML PUBLIC "-//W170141183460469231731687303715884105727C//DTD HTML 4.01//EN">
+
+<body >
+<table style="border-collapse:collapse">
+ <tr><td>
+ <tr><td colspan="0">c170141183460469231731687303715884105748</td></tr>
+ <td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
+</table>
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>#980223 testcase</title>
+</head>
+<body>
+<table>
+ <tbody>
+ <tr>
+ <td style="position:relative;">Test table cell</td>
+ </tr>
+ </tbody>
+</table>
+
+<script>
+document.body.offsetHeight;
+document.querySelector('tbody').innerHTML = '';
+document.body.offsetHeight;
+</script>
+
+</body>
+</html>
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 <algorithm>
+
+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<nsCellMap::CellDataArray> sEmptyRow;
+
+// CellData
+
+CellData::CellData(nsTableCellFrame* aOrigCell) {
+ MOZ_COUNT_CTOR(CellData);
+ static_assert(sizeof(mOrigCell) == sizeof(mBits),
+ "mOrigCell and mBits must be the same size");
+ mOrigCell = aOrigCell;
+}
+
+CellData::~CellData() { MOZ_COUNT_DTOR(CellData); }
+
+BCCellData::BCCellData(nsTableCellFrame* aOrigCell) : CellData(aOrigCell) {
+ MOZ_COUNT_CTOR(BCCellData);
+}
+
+BCCellData::~BCCellData() { MOZ_COUNT_DTOR(BCCellData); }
+
+// nsTableCellMap
+
+nsTableCellMap::nsTableCellMap(nsTableFrame& aTableFrame, bool aBorderCollapse)
+ : mTableFrame(aTableFrame), mFirstMap(nullptr), mBCInfo(nullptr) {
+ MOZ_COUNT_CTOR(nsTableCellMap);
+
+ nsTableFrame::RowGroupArray orderedRowGroups = aTableFrame.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<nsTableRowGroupFrame*>(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<nsCellMap*, 8> 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<nsTableRowGroupFrame*>(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<nsTableRowFrame*>& 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<nsTableCellFrame*>& 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<nsTableCellFrame*>* 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<nsTableRowFrame*>* 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<nsTableRowFrame*>& 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<nsTableCellFrame*, 1> 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<nsTableRowFrame*>& 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<nsTableCellFrame*>& 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<nsTableRowFrame*>& 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<nsTableCellFrame*>& 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<nsTableRowFrame*>* 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<CellDataArray> 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<nsTableCellFrame*>* 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<CellDataArray> 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<BCCellData*>(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 <algorithm>
+#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<BCData> mIEndBorders;
+ nsTArray<BCData> 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<nsTableCellFrame*>& 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<nsTableRowFrame*>& 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<nsTableCellFrame*>* 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<nsTableRowFrame*>* 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<nsColInfo, 8> 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<nsTableCellFrame*>& 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<nsTableRowFrame*>& 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<CellData*> 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<nsTableRowFrame*>& aRowFrames,
+ int32_t aStartRowIndex, int32_t aRgFirstRowIndex,
+ TableArea& aDamageArea);
+
+ void ExpandWithCells(nsTableCellMap& aMap,
+ nsTArray<nsTableCellFrame*>& 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<nsTableRowFrame*>* aRowsToInsert,
+ int32_t aNumRowsToRemove);
+
+ void RebuildConsideringCells(nsTableCellMap& aMap, int32_t aNumOrigCols,
+ nsTArray<nsTableCellFrame*>* aCellFrames,
+ int32_t aRowIndex, int32_t aColIndex,
+ bool aInsert);
+
+ bool CellsSpanOut(nsTArray<nsTableRowFrame*>& 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<CellDataArray> 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<nsPresContext> 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 <algorithm>
+
+// 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<uint32_t>(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<nsFrameSelection> 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<nsDisplayBorder>(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<nsTextFrame*>(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 <mtd>) 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 <mtd>) 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<void*>(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<nsTableCellFrame*>(aCellFrame.FirstInFlow());
+ nsTableFrame* firstTableInFlow =
+ static_cast<nsTableFrame*>(aTableFrame.FirstInFlow());
+ nsTableRowFrame* row =
+ static_cast<nsTableRowFrame*>(firstCellInFlow->GetParent());
+ nsTableRowGroupFrame* firstRGInFlow =
+ static_cast<nsTableRowGroupFrame*>(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<ReflowInput&>(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<ReflowInput&>(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<nsTableCellFrame*>(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<OwnedAnonBox>& 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<nsTableCellFrame*>(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<nsFrameSelection> 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<nsDisplayBoxShadowOuter>(
+ 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<nsDisplayBoxShadowInner>(
+ aBuilder, this);
+ }
+
+ // display borders if we need to
+ ProcessBorders(tableFrame, aBuilder, aLists);
+
+ // and display the selection border if we need to
+ if (IsSelected()) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayTableCellSelection>(
+ 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<nsDisplayListBuilder::AutoBuildingDisplayList> buildingForColGroup;
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, colGroup, bgRect, backgrounds->ColGroupBackgrounds(), false,
+ colGroup->GetRect() + backgrounds->TableToReferenceFrame(), this,
+ &buildingForColGroup);
+
+ Maybe<nsDisplayListBuilder::AutoBuildingDisplayList> 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<nsTableRowFrame*>(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<OwnedAnonBox>& 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<nsTableRowFrame*>(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<nsTableCellFrame*>(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<nsTableCellFrame*>(do_QueryFrame(sibling)),
+ "How do we have a non-cell sibling?");
+ return static_cast<nsTableCellFrame*>(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<nsTableCellFrame*>(do_QueryFrame(firstChild)),
+ "How do we have a non-cell child?");
+ return static_cast<nsTableCellFrame*>(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<nsTableColGroupFrame*>(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<nsTableColGroupFrame*>(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<nsTableColFrame*>(*colIter);
+ colFrame->SetColIndex(colIndex);
+ mColCount++;
+ tableFrame->InsertCol(*colFrame, colIndex);
+ colIndex++;
+ }
+
+ for (; *colIter; ++colIter) {
+ auto* colFrame = static_cast<nsTableColFrame*>(*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<nsTableColGroupFrame*>(colGroups.LastChild());
+ if (!lastColGroup) {
+ return nullptr;
+ }
+
+ if (!lastColGroup->IsSynthetic()) {
+ return lastColGroup;
+ }
+
+ return static_cast<nsTableColGroupFrame*>(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 <col> 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<int32_t> j = GetStartColumnIndex();
+ nsTableColFrame* col = GetFirstColumn();
+ while (col) {
+ NS_ASSERTION(j == col->GetColIndex(), "wrong colindex on col frame");
+ col = col->GetNextCol();
+ j++;
+ }
+ NS_ASSERTION((j - GetStartColumnIndex()) == GetColCount(),
+ "number of cols out of sync");
+ printf("\n%s**END COLGROUP DUMP** ", indent);
+ delete[] indent;
+}
+#endif
diff --git a/layout/tables/nsTableColGroupFrame.h b/layout/tables/nsTableColGroupFrame.h
new file mode 100644
index 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<nsTableFrame*>(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 <algorithm>
+
+#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<nsTableFrame*>(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<nsTableCellMap>(*this, borderCollapse);
+ if (IsAutoLayout()) {
+ mTableLayoutStrategy = MakeUnique<BasicTableLayoutStrategy>(this);
+ } else {
+ mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(this);
+ }
+ } else {
+ // Set my isize, because all frames in a table flow are the same isize and
+ // code in nsTableWrapperFrame depends on this being set.
+ WritingMode wm = GetWritingMode();
+ SetSize(LogicalSize(wm, aPrevInFlow->ISize(wm), BSize(wm)));
+ }
+}
+
+// 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<nsTableFrame*>(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<nsTableFrame*>(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<nsTableCellFrame*, 1> 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<nsTableRowGroupFrame*> excludeRowGroups;
+ for (nsIFrame* excludeRowGroup : aRowGroupsToExclude) {
+ excludeRowGroups.Insert(
+ static_cast<nsTableRowGroupFrame*>(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<nsTableRowFrame*>(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<nsTableRowFrame*>(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<nsTableColGroupFrame*>(*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 <col> tags. It's possible
+ // that we still have at least as many <col> 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<nsTableFrame*>(FirstInFlow())->mCellMap.get();
+}
+
+nsTableColGroupFrame* nsTableFrame::CreateSyntheticColGroupFrame() {
+ nsIContent* colGroupContent = GetContent();
+ mozilla::PresShell* presShell = PresShell();
+
+ RefPtr<ComputedStyle> 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<nsTableColGroupFrame*>(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> 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<nsTableColFrame*>(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<nsTableFrame*>(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<nsTableCellFrame*>& 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<nsTableColGroupFrame*>(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<nsTableRowFrame*>& 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<nsTableRowFrame*>& 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<int32_t, int32_t>(
+ 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<int32_t, int32_t>(
+ aDeletedRowStoredIndex, greaterIter->second));
+ mDeletedRowIndexRanges.erase(greaterIter);
+ } else {
+ // add new range as aDeletedRowStoredIndex is disjoint from existing ranges
+ mDeletedRowIndexRanges.insert(std::pair<int32_t, int32_t>(
+ 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<nsTableRowFrame*>& aCollection) {
+ MOZ_ASSERT(aFrame, "null frame");
+ int32_t numRows = 0;
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ aCollection.AppendElement(static_cast<nsTableRowFrame*>(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<nsTableRowFrame*, 8> 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<ChildList>* 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<nsTableColGroupFrame*>(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<nsDisplayTableBorderCollapse>(
+ aBuilder, this);
+ }
+ } else {
+ const nsStyleBorder* borderStyle = StyleBorder();
+ if (borderStyle->HasBorder()) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(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<nsTableFrame*>(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<ReflowInput&>(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<nsTableFrame*>(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<nsTableFrame*>(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<BasicTableLayoutStrategy>(this);
+ else
+ mTableLayoutStrategy = MakeUnique<FixedTableLayoutStrategy>(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<nsTableFrame*>(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<nsTableFrame*>(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<nsIContent> container = content->GetParent();
+ if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823.
+ const Maybe<uint32_t> 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<uint32_t> 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<uint32_t> 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 <col> tags. It's possible
+ // that we still have at least as many <col> 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<nsTableRowGroupFrame*>(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<nsTableFrame*>(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<nsTableFrame*>(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<nsTableFrame*>(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<nsTableFrame*>(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<LogicalMargin>& aBorder, Maybe<LogicalMargin>& 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<nsTableRowGroupFrame*>(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<nsTableRowGroupFrame*>(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<nsTableRowGroupFrame*>(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<nsTableRowGroupFrame*>(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<nsTableFrame*>(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<nscoord> 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<nscoord> {
+ 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<nsTableFrame*>(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<void*>(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<void*>(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<nsTableFrame*>(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<void*>(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<nsTableFrame*>(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 <col> 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<nsTableFrame*>(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<nsBCTableCellFrame*>(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<BCCellData*>(
+ 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<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, mColIndex));
+ if (!cellData) { // add a dead cell data
+ TableArea damageArea;
+ cellData = static_cast<BCCellData*>(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<BCCellData*>(mCellMap->GetDataAt(rgRowIndex, colIndex));
+ if (!cellData) { // add a dead cell data
+ NS_ASSERTION(colIndex < mTableCellMap->GetColCount(), "program error");
+ TableArea damageArea;
+ cellData = static_cast<BCCellData*>(mCellMap->AppendCell(
+ *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
+ if (!cellData) ABORT0();
+ }
+ nsTableRowFrame* row = nullptr;
+ if (cellData->IsRowSpan()) {
+ rgRowIndex -= cellData->GetRowSpanOffset();
+ cellData =
+ static_cast<BCCellData*>(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<BCCellData*>(cellMap->GetDataAt(rgRowIndex, aColIndex));
+ if (!cellData) { // add a dead cell data
+ NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error");
+ TableArea damageArea;
+ cellData = static_cast<BCCellData*>(cellMap->AppendCell(
+ *mTableCellMap, nullptr, rgRowIndex, false, 0, damageArea));
+ if (!cellData) ABORT0();
+ }
+ if (cellData->IsColSpan()) {
+ aColIndex -= cellData->GetColSpanOffset();
+ cellData =
+ static_cast<BCCellData*>(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<nsTableFrame*>(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<nsIRunnable> 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<uint8_t>(aBorder1.style) <
+ static_cast<uint8_t>(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<StyleBorderStyle>(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<BCCornerInfo[]> corners;
+};
+
+BCCorners::BCCorners(int32_t aNumCorners, int32_t aStartIndex) {
+ NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error");
+ startIndex = aStartIndex;
+ endIndex = aStartIndex + aNumCorners - 1;
+ corners = MakeUnique<BCCornerInfo[]>(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<BCCellBorder[]> borders;
+};
+
+BCCellBorders::BCCellBorders(int32_t aNumBorders, int32_t aStartIndex) {
+ NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error");
+ startIndex = aStartIndex;
+ endIndex = aStartIndex + aNumBorders - 1;
+ borders = MakeUnique<BCCellBorder[]>(aNumBorders);
+}
+
+// this function sets the new border properties and returns true if the border
+// segment will start a new segment and not be accumulated into the previous
+// segment.
+static bool SetBorder(const BCCellBorder& aNewBorder, BCCellBorder& aBorder) {
+ bool changed = (aNewBorder.style != aBorder.style) ||
+ (aNewBorder.width != aBorder.width) ||
+ (aNewBorder.color != aBorder.color);
+ aBorder.color = aNewBorder.color;
+ aBorder.width = aNewBorder.width;
+ aBorder.style = aNewBorder.style;
+ aBorder.owner = aNewBorder.owner;
+
+ return changed;
+}
+
+// this function will set the inline-dir border. It will return true if the
+// existing segment will not be continued. Having a block-dir owner of a corner
+// should also start a new segment.
+static bool SetInlineDirBorder(const BCCellBorder& aNewBorder,
+ const BCCornerInfo& aCorner,
+ BCCellBorder& aBorder) {
+ bool startSeg = ::SetBorder(aNewBorder, aBorder);
+ if (!startSeg) {
+ startSeg = !IsInline(LogicalSide(aCorner.ownerSide));
+ }
+ return startSeg;
+}
+
+// Make the damage area larger on the top and bottom by at least one row and on
+// the left and right at least one column. This is done so that adjacent
+// elements are part of the border calculations. The extra segments and borders
+// outside the actual damage area will not be updated in the cell map, because
+// they in turn would need info from adjacent segments outside the damage area
+// to be accurate.
+void nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const {
+ int32_t numRows = GetRowCount();
+ int32_t numCols = GetColCount();
+
+ int32_t dStartX = aArea.StartCol();
+ int32_t dEndX = aArea.EndCol() - 1;
+ int32_t dStartY = aArea.StartRow();
+ int32_t dEndY = aArea.EndRow() - 1;
+
+ // expand the damage area in each direction
+ if (dStartX > 0) {
+ dStartX--;
+ }
+ if (dEndX < (numCols - 1)) {
+ dEndX++;
+ }
+ if (dStartY > 0) {
+ dStartY--;
+ }
+ if (dEndY < (numRows - 1)) {
+ dEndY++;
+ }
+ // Check the damage area so that there are no cells spanning in or out. If
+ // there are any then make the damage area as big as the table, similarly to
+ // the way the cell map decides whether to rebuild versus expand. This could
+ // be optimized to expand to the smallest area that contains no spanners, but
+ // it may not be worth the effort in general, and it would need to be done in
+ // the cell map as well.
+ bool haveSpanner = false;
+ if ((dStartX > 0) || (dEndX < (numCols - 1)) || (dStartY > 0) ||
+ (dEndY < (numRows - 1))) {
+ nsTableCellMap* tableCellMap = GetCellMap();
+ if (!tableCellMap) ABORT0();
+ // Get the ordered row groups
+ RowGroupArray rowGroups = 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<nsTableColGroupFrame*>(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<BCCellBorder> 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<nscoord> aEmptyRowEndSize);
+
+ void Initialize(BCPaintBorderIterator& aIter);
+ void GetBEndCorner(BCPaintBorderIterator& aIter, BCPixelSize aInlineSegBSize);
+
+ Maybe<BCBorderParameters> 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<BCBorderParameters> 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<BCBlockDirSeg[]> 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<nsTableFrame*>(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<nsTableRowFrame*>(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<nsTableRowFrame*>(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<int32_t>(endColIndex - startColIndex),
+ 1 + endRowIndex - startRowIndex);
+
+ Reset();
+ mBlockDirInfo = MakeUnique<BCBlockDirSeg[]>(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<nsTableRowGroupFrame*>(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<nscoord> 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<BCBorderParameters> 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<BCBorderParameters> 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<const wr::BorderSide> 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<nsCSSRendering::SolidBeveledBorderSegment, 3> 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<const wr::BorderSide> 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<BCBorderParameters> 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<BCBorderParameters> 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<BCBorderParameters> 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<BCBorderParameters> 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<nscoord> 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<OwnedAnonBox>& 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<ComputedStyle> 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<const nsIFrame*> 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<nsTableFrame*>(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<nsTableFrame*>(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<nsDisplayTableBackgroundSet*> 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<nsTableColFrame*> 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<nsIFrame*> 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<ChildList>* 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<mozilla::LogicalMargin>& aBorder,
+ mozilla::Maybe<mozilla::LogicalMargin>& 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.
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * @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<nscoord> 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<nsTableCellFrame*>& aCellFrames, int32_t aRowIndex,
+ int32_t aColIndexBefore);
+
+ void RemoveCell(nsTableCellFrame* aCellFrame, int32_t aRowIndex);
+
+ void AppendRows(nsTableRowGroupFrame* aRowGroupFrame, int32_t aRowIndex,
+ nsTArray<nsTableRowFrame*>& aRowFrames);
+
+ int32_t InsertRows(nsTableRowGroupFrame* aRowGroupFrame,
+ nsTArray<nsTableRowFrame*>& 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<OwnedAnonBox>& 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<nsTableFrame*>(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<nsTableRowGroupFrame*, 8>;
+
+ 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<nsTableColFrame*>& 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<nsTableRowFrame*>& 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<nsTableColFrame*, 8> 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<int32_t, int32_t> mDeletedRowIndexRanges; // maintains ranges of row
+ // indices of deleted rows
+ mozilla::UniquePtr<nsTableCellMap> mCellMap; // maintains the relationships
+ // between rows, cols, and cells
+ // the layout strategy for this frame
+ mozilla::UniquePtr<nsITableLayoutStrategy> 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<nsTableFrame*>(FirstInFlow())->mBits.mNeedToCollapse =
+ (unsigned)aValue;
+}
+
+inline bool nsTableFrame::NeedToCollapse() const {
+ return (bool)static_cast<nsTableFrame*>(FirstInFlow())->mBits.mNeedToCollapse;
+}
+
+inline nsFrameList& nsTableFrame::GetColGroups() {
+ return static_cast<nsTableFrame*>(FirstInFlow())->mColGroups;
+}
+
+inline nsTArray<nsTableColFrame*>& 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 <algorithm>
+
+#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<LogicalMargin> 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<nsTableCellFrame&>(*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<nsTableCellFrame*>(nsTableFrame::GetFrameAtOrBefore(
+ this, aPrevFrame, LayoutFrameType::TableCell));
+ nsTArray<nsTableCellFrame*> cellChildren;
+ for (nsIFrame* childFrame : newCells) {
+ NS_ASSERTION(childFrame->IsTableCellFrame(),
+ "Not a table cell frame/pseudo frame construction failure");
+ cellChildren.AppendElement(static_cast<nsTableCellFrame*>(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<nsTableCellFrame*>(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<nscoord> 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<nsTableFrame*>(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<nsTableFrame*>(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<nsTableRowFrame*>(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<int32_t>(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<TableCellReflowInput> 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<nsTableFrame*>(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<nsTableFrame*>(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<int32_t>(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<int32_t>(colIndex) < aColIndex) {
+ priorCell = cellFrame;
+ } else {
+ break;
+ }
+ }
+ mFrames.InsertFrame(this, priorCell, aFrame);
+}
+
+nsTableRowFrame* nsTableRowFrame::GetPrevRow() const {
+ nsIFrame* prevSibling = GetPrevSibling();
+ MOZ_ASSERT(
+ !prevSibling || static_cast<nsTableRowFrame*>(do_QueryFrame(prevSibling)),
+ "How do we have a non-row sibling?");
+ return static_cast<nsTableRowFrame*>(prevSibling);
+}
+
+nsTableRowFrame* nsTableRowFrame::GetNextRow() const {
+ nsIFrame* nextSibling = GetNextSibling();
+ MOZ_ASSERT(
+ !nextSibling || static_cast<nsTableRowFrame*>(do_QueryFrame(nextSibling)),
+ "How do we have a non-row sibling?");
+ return static_cast<nsTableRowFrame*>(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<nsTableRowGroupFrame*>(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<nscoord> 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 <algorithm>
+
+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<nsTableRowFrame*>(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
+ // <start row index> and <num of rows to mark for deletion> 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<nsTableRowGroupFrame*>(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<nsTableRowFrame*>(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<nsTableRowFrame*>(do_QueryFrame(firstChild)),
+ "How do we have a non-row child?");
+ return static_cast<nsTableRowFrame*>(firstChild);
+}
+
+nsTableRowFrame* nsTableRowGroupFrame::GetLastRow() const {
+ nsIFrame* lastChild = mFrames.LastChild();
+ MOZ_ASSERT(
+ !lastChild || static_cast<nsTableRowFrame*>(do_QueryFrame(lastChild)),
+ "How do we have a non-row child?");
+ return static_cast<nsTableRowFrame*>(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, 32> 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<nsTableRowFrame*>(
+ 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<nsTableCellFrame*>(
+ 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<ReflowInput::Flags&>(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<nsTableRowFrame*, 8> 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<nsTableRowFrame*> 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<nsILineIterator::LineInfo, nsresult> 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<nsTableFrame*>(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<LineInfo, nsresult> 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<nsIFrame*> 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 <algorithm>
+
+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<nscoord> 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<ChildList>* 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<LogicalMargin> collapseBorder;
+ Maybe<LogicalMargin> 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<StyleCaptionSide> 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<StyleCaptionSide> 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<nsGridContainerFrame*>(GetParent());
+ if (gridContainer->GridItemShouldStretch(this, eLogicalAxisInline)) {
+ return {};
+ }
+ }
+ return {ComputeSizeFlag::ShrinkWrap};
+}
+
+void nsTableWrapperFrame::CreateReflowInputForInnerTable(
+ nsPresContext* aPresContext, nsTableFrame* aTableFrame,
+ const ReflowInput& aOuterRI, Maybe<ReflowInput>& 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<LogicalSize> 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<LogicalMargin> borderPadding;
+ Maybe<LogicalMargin> 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<LogicalMargin> collapseBorder;
+ Maybe<LogicalMargin> 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<ReflowInput>& 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<ReflowInput> captionRI;
+ Maybe<ReflowInput> 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<StyleCaptionSide> 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<ChildList>* 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<nscoord> 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<mozilla::StyleCaptionSide>;
+
+ // 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<ReflowInput>& aChildRI,
+ const nscoord aAvailISize, nscoord aBSizeOccupiedByCaption = 0) const;
+ void CreateReflowInputForCaption(nsPresContext* aPresContext,
+ nsIFrame* aCaptionFrame,
+ const ReflowInput& aOuterRI,
+ Maybe<ReflowInput>& 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<nsTableFrame*>(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 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+ <title>Reference: Table fragmentation test</title>
+ <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1017137">
+ <meta charset="utf-8">
+ <meta name="flags" content="paged">
+ <style>
+@page { size:5in 3in; margin:0.5in; }
+html,body {
+ color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+table,tr,td { border:0; padding:0; margin:0; }
+</style>
+</head>
+
+<body>
+ <table cellspacing=0 cellpadding=0>
+ <tr id="_TR_1"><td>
+ <div style="height:0.2in"></div>
+ </td></tr>
+
+ <tr id="_TR_2"><td>
+ <table cellspacing=0 cellpadding=0><tr><td><div style="height:0.5in; margin-top:64px"></div></td></tr></table>
+ </td></tr>
+
+ <tr id="_TR_3" style="page-break-before:always"><td>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 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<head>
+ <title>Table fragmentation test</title>
+ <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1017137">
+ <meta charset="utf-8">
+ <meta name="flags" content="paged">
+ <style>
+@page { size:5in 3in; margin:0.5in; }
+html,body {
+ color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+table,tr,td { border:0; padding:0; margin:0; }
+</style>
+</head>
+
+<body>
+ <table cellspacing=0 cellpadding=0>
+ <tr id="_TR_1"><td>
+ <div style="height:0.2in"></div>
+ </td></tr>
+
+ <tr id="_TR_2"><td>
+ <table cellspacing=0 cellpadding=0><tr><td><div style="height:0.5in; margin:64px 0"></div></td></tr></table>
+ </td></tr>
+
+ <tr id="_TR_3"><td>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 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1031934</title>
+</head>
+<body>
+
+<table border="1">
+<tbody style="visibility: collapse;">
+</tbody>
+<tbody>
+<tr><td>Hello</td></tr>
+</tbody>
+<tbody style="visibility: collapse;">
+</tbody>
+</table>
+
+<table border="1">
+<tbody></tbody>
+<tbody>
+<tr><td>Hello</td></tr>
+</tbody>
+<tbody></tbody>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1031934</title>
+ <style type="text/css">
+
+td { display:none; }
+
+ </style>
+</head>
+<body>
+
+<table border="1">
+<tbody style="visibility: collapse;">
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+</tbody>
+<tbody>
+<tr><td></td></tr>
+<tr><td style="display:table-cell">Hello</td></tr>
+<tr><td></td></tr>
+</tbody>
+<tbody style="visibility: collapse;">
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+</tbody>
+</table>
+
+<table border="1">
+<tbody>
+<tr style="visibility: collapse;"><td></td></tr>
+<tr style="visibility: collapse;"><td></td></tr>
+<tr style="visibility: collapse;"><td></td></tr>
+</tbody>
+<tbody>
+<tr><td></td></tr>
+<tr><td style="display:table-cell">Hello</td></tr>
+<tr><td></td></tr>
+</tbody>
+<tbody style="visibility: collapse;">
+<tr style="visibility: collapse;"><td></td></tr>
+<tr style="visibility: collapse;"><td></td></tr>
+<tr style="visibility: collapse;"><td></td></tr>
+</tbody>
+</table>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <td>One</td>
+ <td>Two</td>
+ <td>Three</td>
+</table>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ <td>Two</td>
+ <td>Three</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the first col
+ document.querySelector("col").remove();
+</script>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ <td>Two</td>
+ <td>Three</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the first col
+ document.querySelector("col").remove();
+</script>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ <td>Two</td>
+ <td>Three</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the first col
+ document.querySelector("col").remove();
+</script>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ </colgroup>
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ <td>Two</td>
+ <td>Three</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the first colgroup
+ document.querySelector("colgroup").remove();
+</script>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ <td>Two</td>
+ <td>Three</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the one colgroup
+ document.querySelector("colgroup").remove();
+</script>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ <td>Two</td>
+ <td>Three</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the one colgroup
+ document.querySelector("colgroup").remove();
+</script>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ </tbody>
+</table>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the first col
+ document.querySelector("col").remove();
+</script>
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 @@
+<!DOCTYPE html>
+<style>
+ table {
+ table-layout: fixed;
+ border: 1px solid black;
+ width: 300px;
+ }
+ td {
+ background: yellow;
+ border: 1px solid purple;
+ }
+</style>
+<table>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <tbody>
+ <td>One</td>
+ </tbody>
+</table>
+<script>
+ var t = document.querySelector("table");
+ // Flush layout
+ var width = t.offsetWidth;
+ // Remove the first col
+ document.querySelector("colgroup").remove();
+</script>
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 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1564308</title>
+ <style type="text/css">
+
+html { overflow: scroll } /* suppress resizes for scrollbars */
+
+ </style>
+</head>
+<body style="height: 300px; width: 20em;">
+
+<div style="display:inline">
+<table border="1" style="display:inline-table; vertical-align: top;">
+<tbody>
+<tr><td>Hello</td></tr>
+</tbody>
+</table>
+</div>
+
+</body>
+</html>
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 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1564308</title>
+ <style type="text/css">
+
+html { overflow: scroll } /* suppress resizes for scrollbars */
+
+ </style>
+</head>
+<body style="height: 300px">
+
+<div style="display:inline">
+<table border="1" style="display:inline-table; vertical-align: top;">
+<tbody style="visibility: collapse;">
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+<tr><td></td></tr>
+</tbody>
+<tbody>
+<tr><td>Hello</td></tr>
+</tbody>
+</table>
+</div>
+
+<script>
+
+// This, plus the use of an inline-table wrapped by an inline, is a relatively
+// complicated setup to cause the table to be reflowed while it is not dirty,
+// has no dirty descendants, and is not vertically or horizontally resizing, so
+// that nsTableFrame::Reflow will skip the main part of the function.
+document.body.offsetWidth;
+document.body.style.width = "20em";
+
+</script>
+
+</body>
+</html>
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 @@
+<!DOCTYPE html>
+<html>
+ <table>
+ <tr>
+ <td style="text-indent: 50px">
+ Some text
+ </td>
+ </tr>
+ </table>
+</html>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <table>
+ <tr>
+ <td>
+ Some text
+ </td>
+ </tr>
+ </table>
+ <script>
+ onload = function() {
+ var td = document.querySelector("td");
+ // Make sure layout has happened.
+ var width = td.offsetWidth;
+ td.style.textIndent = "50px";
+ document.documentElement.className = "";
+ }
+ </script>
+</html>
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 @@
+<!DOCTYPE html>
+<table style="table-layout: fixed; width: 130px">
+ <tr>
+ <td style="overflow: hidden; white-space: nowrap;">
+ Some long text that cannot possibly fit in 130 px, because it just can't.
+ </td>
+ </tr>
+</table>
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 @@
+<!DOCTYPE html>
+<table style="table-layout: fixed; width: 130px">
+ <tr>
+ <td style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis">
+ Some long text that cannot possibly fit in 130 px, because it just can't.
+ </td>
+ </tr>
+</table>
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 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <table style="table-layout: fixed; width: 130px">
+ <tr>
+ <td style="overflow: hidden; white-space: nowrap;">
+ Some long text that cannot possibly fit in 130 px, because it just can't.
+ </td>
+ </tr>
+ </table>
+ <script>
+ onload = function() {
+ var td = document.querySelector("td");
+ // Make sure layout has happened.
+ var width = td.offsetWidth;
+ td.style.textOverflow = "ellipsis";
+ document.documentElement.className = "";
+ }
+ </script>
+</html>
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 @@
+<!DOCTYPE HTML>
+<title>Test for Bug 1832110</title>
+<style>
+:root {
+ --bw: 1px;
+}
+
+div {
+ display: inline-block;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+td {
+ border: var(--bw) solid black;
+ line-height: 0;
+ padding: 0;
+}
+
+span {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ background: grey;
+}
+
+.hide {
+ display: none;
+}
+</style>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script class="testbody" type="text/javascript">
+function set_td_border(width) {
+ document.documentElement.style.setProperty("--bw", width + "px");
+}
+
+function raf() {
+ return new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+}
+
+async function show_table(table, other) {
+ // TODO(dshin): Once bug 1825384 resolves, this should not be needed.
+ table.classList.remove("hide");
+ other.classList.add("hide");
+ getComputedStyle(table).getPropertyValue("display");
+ await raf();
+}
+
+async function run_test(relativeZoom, width) {
+ SpecialPowers.setFullZoom(window, relativeZoom);
+ set_td_border(width);
+ show_table(emptyrows, normal);
+ const s1 = await snapshotRect(window, emptyrows.getBoundingClientRect());
+ show_table(normal, emptyrows);
+ const s2 = await snapshotRect(window, normal.getBoundingClientRect());
+ assertSnapshots(s1, s2, true, null, "emptyrows", "normal " + relativeZoom + " " + width);
+}
+
+SimpleTest.waitForExplicitFinish();
+const zoomsToTest = [
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 200,
+ 250,
+ 300,
+];
+const originalZoom = SpecialPowers.getFullZoom(window);
+
+const widthsToTest = [
+ 1,
+ 3,
+ 7,
+ 11,
+ 23,
+];
+for (let i = 0; i < zoomsToTest.length; ++i) {
+ let relativeZoom = originalZoom * zoomsToTest[i] / 100;
+ for (let j = 0; j < widthsToTest.length; ++j) {
+ add_task(async () => { await run_test(relativeZoom, widthsToTest[j]); });
+ }
+}
+add_task(async () => { SpecialPowers.setFullZoom(window, originalZoom); });
+</script>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1832110">Mozilla Bug 1832110</a><br>
+<div id="emptyrows" class="hide">
+<table>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+<tr></tr>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+<tr></tr>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+<tr></tr>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+</table>
+</div><div id="normal" class="hide">
+<table>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+<tr><td><span></span></td><td><span></span></td><td><span></span></td></tr>
+</table>
+</div>
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 @@
+<html><head>
+<title>Test for Bug 337124</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=337124">Mozilla Bug 337124</a>
+
+<span style="display: table-row-group;">
+ <input type="text">
+ </span>
+ <div style="display: table-column-group;">
+ <script>document.body.offsetHeight;</script>
+ </div><span style="display: table-row-group;">
+ <input id="i1" type="text">
+ </span><fieldset id="f1" style="display: table-column-group;">
+ </fieldset>
+</fieldset>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var passed = false;
+if ( document.getElementById("f1").offsetTop > document.getElementById("i1").offsetTop) {
+ passed = true;
+}
+
+ok(passed, "right layout order");
+
+</script>
+</pre>
+</body>
+</html> \ 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 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=541668
+-->
+<head>
+ <title>Test for Bug 541668</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=541668">Mozilla Bug 541668</a>
+<table id="display">
+ <tr>
+ <td rowspan="2">
+ <div id="target" style="background:fuchsia;height:200px;width:200px"></div>
+ </td>
+ <td>Cell</td>
+ </tr>
+ <tr>
+ <td>Cell</td>
+ </tr>
+</table>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 541668 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(run_test);
+
+function run_test()
+{
+ var target = document.getElementById("target");
+
+ var got_mousemove = false;
+ target.addEventListener("mousemove",
+ function(event) { got_mousemove = true });
+ synthesizeMouse(target, 150, 150, { type: "mousemove" });
+ is(got_mousemove, true, "should get mousemove on block");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>