summaryrefslogtreecommitdiff
path: root/layout/tables
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /layout/tables
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/tables')
-rw-r--r--layout/tables/BasicTableLayoutStrategy.cpp1060
-rw-r--r--layout/tables/BasicTableLayoutStrategy.h81
-rw-r--r--layout/tables/FixedTableLayoutStrategy.cpp422
-rw-r--r--layout/tables/FixedTableLayoutStrategy.h40
-rw-r--r--layout/tables/SpanningCellSorter.cpp160
-rw-r--r--layout/tables/SpanningCellSorter.h91
-rw-r--r--layout/tables/TableArea.h41
-rw-r--r--layout/tables/celldata.h459
-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/1183896.html25
-rw-r--r--layout/tables/crashtests/1223232.html6
-rw-r--r--layout/tables/crashtests/1223282.html11
-rw-r--r--layout/tables/crashtests/1243623-1.html27
-rw-r--r--layout/tables/crashtests/138725-1.html32
-rw-r--r--layout/tables/crashtests/159359-1.html13
-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.xul8
-rw-r--r--layout/tables/crashtests/323489-1.html22
-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/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/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/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/crashtests.list159
-rw-r--r--layout/tables/moz.build46
-rw-r--r--layout/tables/nsCellMap.cpp2716
-rw-r--r--layout/tables/nsCellMap.h668
-rw-r--r--layout/tables/nsITableCellLayout.h35
-rw-r--r--layout/tables/nsITableLayoutStrategy.h59
-rw-r--r--layout/tables/nsTableCellFrame.cpp1238
-rw-r--r--layout/tables/nsTableCellFrame.h357
-rw-r--r--layout/tables/nsTableColFrame.cpp213
-rw-r--r--layout/tables/nsTableColFrame.h343
-rw-r--r--layout/tables/nsTableColGroupFrame.cpp524
-rw-r--r--layout/tables/nsTableColGroupFrame.h256
-rw-r--r--layout/tables/nsTableFrame.cpp7536
-rw-r--r--layout/tables/nsTableFrame.h997
-rw-r--r--layout/tables/nsTablePainter.cpp696
-rw-r--r--layout/tables/nsTablePainter.h268
-rw-r--r--layout/tables/nsTableRowFrame.cpp1517
-rw-r--r--layout/tables/nsTableRowFrame.h437
-rw-r--r--layout/tables/nsTableRowGroupFrame.cpp2019
-rw-r--r--layout/tables/nsTableRowGroupFrame.h450
-rw-r--r--layout/tables/nsTableWrapperFrame.cpp1101
-rw-r--r--layout/tables/nsTableWrapperFrame.h305
-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/reftest-stylo.list10
-rw-r--r--layout/tables/reftests/reftest.list9
-rw-r--r--layout/tables/test/mochitest.ini4
-rw-r--r--layout/tables/test/test_bug337124.html32
-rw-r--r--layout/tables/test/test_bug541668_table_event_delivery.html49
207 files changed, 31299 insertions, 0 deletions
diff --git a/layout/tables/BasicTableLayoutStrategy.cpp b/layout/tables/BasicTableLayoutStrategy.cpp
new file mode 100644
index 0000000000..65835e68e8
--- /dev/null
+++ b/layout/tables/BasicTableLayoutStrategy.cpp
@@ -0,0 +1,1060 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Web-compatible algorithms that determine column and table widths,
+ * used for CSS2's 'table-layout: auto'.
+ */
+
+#include "BasicTableLayoutStrategy.h"
+
+#include <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;
+
+namespace css = mozilla::css;
+
+#undef DEBUG_TABLE_STRATEGY
+
+BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame)
+ : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto)
+ , mTableFrame(aTableFrame)
+{
+ MarkIntrinsicISizesDirty();
+}
+
+/* virtual */
+BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
+{
+}
+
+/* virtual */ nscoord
+BasicTableLayoutStrategy::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ DISPLAY_MIN_WIDTH(mTableFrame, mMinISize);
+ if (mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
+ ComputeIntrinsicISizes(aRenderingContext);
+ }
+ return mMinISize;
+}
+
+/* virtual */ nscoord
+BasicTableLayoutStrategy::GetPrefISize(nsRenderingContext* aRenderingContext,
+ bool aComputingSize)
+{
+ DISPLAY_PREF_WIDTH(mTableFrame, mPrefISize);
+ NS_ASSERTION((mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) ==
+ (mPrefISizePctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
+ "dirtyness out of sync");
+ if (mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
+ ComputeIntrinsicISizes(aRenderingContext);
+ }
+ return aComputingSize ? mPrefISizePctExpand : mPrefISize;
+}
+
+struct CellISizeInfo {
+ CellISizeInfo(nscoord aMinCoord, nscoord aPrefCoord,
+ float aPrefPercent, bool aHasSpecifiedISize)
+ : hasSpecifiedISize(aHasSpecifiedISize)
+ , minCoord(aMinCoord)
+ , prefCoord(aPrefCoord)
+ , prefPercent(aPrefPercent)
+ {
+ }
+
+ bool hasSpecifiedISize;
+ nscoord minCoord;
+ nscoord prefCoord;
+ float prefPercent;
+};
+
+// Used for both column and cell calculations. The parts needed only
+// for cells are skipped when aIsCell is false.
+static CellISizeInfo
+GetISizeInfo(nsRenderingContext *aRenderingContext,
+ nsIFrame *aFrame, WritingMode aWM, bool aIsCell)
+{
+ nscoord minCoord, prefCoord;
+ const nsStylePosition *stylePos = aFrame->StylePosition();
+ bool isQuirks = aFrame->PresContext()->CompatibilityMode() ==
+ eCompatibility_NavQuirks;
+ nscoord boxSizingToBorderEdge = 0;
+ if (aIsCell) {
+ // If aFrame is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(aFrame);
+
+ minCoord = aFrame->GetMinISize(aRenderingContext);
+ prefCoord = aFrame->GetPrefISize(aRenderingContext);
+ // Until almost the end of this function, minCoord and prefCoord
+ // represent the box-sizing based isize values (which mean they
+ // should include inline padding and border width when
+ // box-sizing is set to border-box).
+ // Note that this function returns border-box isize, we add the
+ // outer edges near the end of this function.
+
+ // XXX Should we ignore percentage padding?
+ nsIFrame::IntrinsicISizeOffsetData offsets =
+ aFrame->IntrinsicISizeOffsets();
+
+ // In quirks mode, table cell isize should be content-box,
+ // but bsize should be border box.
+ // Because of this historic anomaly, we do not use quirk.css.
+ // (We can't specify one value of box-sizing for isize and another
+ // for bsize).
+ // For this reason, we also do not use box-sizing for just one of
+ // them, as this may be confusing.
+ if (isQuirks || stylePos->mBoxSizing == StyleBoxSizing::Content) {
+ boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder;
+ }
+ else {
+ // StyleBoxSizing::Border and standards-mode
+ minCoord += offsets.hPadding + offsets.hBorder;
+ prefCoord += offsets.hPadding + offsets.hBorder;
+ }
+ } else {
+ minCoord = 0;
+ prefCoord = 0;
+ }
+ float prefPercent = 0.0f;
+ bool hasSpecifiedISize = false;
+
+ const nsStyleCoord& iSize = stylePos->ISize(aWM);
+ nsStyleUnit unit = iSize.GetUnit();
+ // NOTE: We're ignoring calc() units with percentages here, for lack of a
+ // sensible idea for what to do with them. This means calc() with
+ // percentages is basically handled like 'auto' for table cells and
+ // columns.
+ if (iSize.ConvertsToLength()) {
+ hasSpecifiedISize = true;
+ // Note: since ComputeISizeValue was designed to return content-box
+ // isize, it will (in some cases) subtract the box-sizing edges.
+ // We prevent this unwanted behavior by calling it with
+ // aContentEdgeToBoxSizing and aBoxSizingToMarginEdge set to 0.
+ nscoord c = aFrame->ComputeISizeValue(aRenderingContext, 0, 0, 0, iSize);
+ // Quirk: A cell with "nowrap" set and a coord value for the
+ // isize which is bigger than the intrinsic minimum isize uses
+ // that coord value as the minimum isize.
+ // This is kept up-to-date with dynamic changes to nowrap by code in
+ // nsTableCellFrame::AttributeChanged
+ if (aIsCell && c > minCoord && isQuirks &&
+ aFrame->GetContent()->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::nowrap)) {
+ minCoord = c;
+ }
+ prefCoord = std::max(c, minCoord);
+ } else if (unit == eStyleUnit_Percent) {
+ prefPercent = iSize.GetPercentValue();
+ } else if (unit == eStyleUnit_Enumerated && aIsCell) {
+ switch (iSize.GetIntValue()) {
+ case NS_STYLE_WIDTH_MAX_CONTENT:
+ // 'inline-size' only affects pref isize, not min
+ // isize, so don't change anything
+ break;
+ case NS_STYLE_WIDTH_MIN_CONTENT:
+ prefCoord = minCoord;
+ break;
+ case NS_STYLE_WIDTH_FIT_CONTENT:
+ case NS_STYLE_WIDTH_AVAILABLE:
+ // act just like 'inline-size: auto'
+ break;
+ default:
+ NS_NOTREACHED("unexpected enumerated value");
+ }
+ }
+
+ nsStyleCoord maxISize(stylePos->MaxISize(aWM));
+ if (maxISize.GetUnit() == eStyleUnit_Enumerated) {
+ if (!aIsCell || maxISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) {
+ maxISize.SetNoneValue();
+ } else if (maxISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) {
+ // for 'max-inline-size', '-moz-fit-content' is like
+ // '-moz-max-content'
+ maxISize.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT,
+ eStyleUnit_Enumerated);
+ }
+ }
+ unit = maxISize.GetUnit();
+ // XXX To really implement 'max-inline-size' well, we'd need to store
+ // it separately on the columns.
+ if (maxISize.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
+ nscoord c = aFrame->ComputeISizeValue(aRenderingContext,
+ 0, 0, 0, maxISize);
+ minCoord = std::min(c, minCoord);
+ prefCoord = std::min(c, prefCoord);
+ } else if (unit == eStyleUnit_Percent) {
+ float p = stylePos->MaxISize(aWM).GetPercentValue();
+ if (p < prefPercent) {
+ prefPercent = p;
+ }
+ }
+ // treat calc() with percentages on max-inline-size just like 'none'.
+
+ nsStyleCoord minISize(stylePos->MinISize(aWM));
+ if (minISize.GetUnit() == eStyleUnit_Enumerated) {
+ if (!aIsCell || minISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) {
+ minISize.SetCoordValue(0);
+ } else if (minISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) {
+ // for 'min-inline-size', '-moz-fit-content' is like
+ // '-moz-min-content'
+ minISize.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT,
+ eStyleUnit_Enumerated);
+ }
+ }
+ unit = minISize.GetUnit();
+ if (minISize.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
+ nscoord c = aFrame->ComputeISizeValue(aRenderingContext,
+ 0, 0, 0, minISize);
+ minCoord = std::max(c, minCoord);
+ prefCoord = std::max(c, prefCoord);
+ } else if (unit == eStyleUnit_Percent) {
+ float p = stylePos->MinISize(aWM).GetPercentValue();
+ if (p > prefPercent) {
+ prefPercent = p;
+ }
+ }
+ // treat calc() with percentages on min-inline-size just like '0'.
+
+ // XXX Should col frame have border/padding considered?
+ if (aIsCell) {
+ minCoord += boxSizingToBorderEdge;
+ prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge);
+ }
+
+ return CellISizeInfo(minCoord, prefCoord, prefPercent, hasSpecifiedISize);
+}
+
+static inline CellISizeInfo
+GetCellISizeInfo(nsRenderingContext *aRenderingContext,
+ nsTableCellFrame *aCellFrame, WritingMode aWM)
+{
+ return GetISizeInfo(aRenderingContext, aCellFrame, aWM, true);
+}
+
+static inline CellISizeInfo
+GetColISizeInfo(nsRenderingContext *aRenderingContext,
+ nsIFrame *aFrame, WritingMode aWM)
+{
+ return GetISizeInfo(aRenderingContext, aFrame, aWM, false);
+}
+
+
+/**
+ * The algorithm in this function, in addition to meeting the
+ * requirements of Web-compatibility, is also invariant under reordering
+ * of the rows within a table (something that most, but not all, other
+ * browsers are).
+ */
+void
+BasicTableLayoutStrategy::ComputeColumnIntrinsicISizes(nsRenderingContext* aRenderingContext)
+{
+ nsTableFrame *tableFrame = mTableFrame;
+ nsTableCellMap *cellMap = tableFrame->GetCellMap();
+ WritingMode wm = tableFrame->GetWritingMode();
+
+ mozilla::AutoStackArena arena;
+ SpanningCellSorter spanningCells;
+
+ // Loop over the columns to consider the columns and cells *without*
+ // a colspan.
+ int32_t col, col_end;
+ for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
+ nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ colFrame->ResetIntrinsics();
+ colFrame->ResetSpanIntrinsics();
+
+ // Consider the isizes on the column.
+ CellISizeInfo colInfo = GetColISizeInfo(aRenderingContext,
+ colFrame, wm);
+ colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
+ colInfo.hasSpecifiedISize);
+ colFrame->AddPrefPercent(colInfo.prefPercent);
+
+ // Consider the isizes on the column-group. Note that we follow
+ // what the HTML spec says here, and make the isize apply to
+ // each column in the group, not the group as a whole.
+
+ // If column has isize, column-group doesn't override isize.
+ if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 &&
+ colInfo.prefPercent == 0.0f) {
+ NS_ASSERTION(colFrame->GetParent()->GetType() ==
+ nsGkAtoms::tableColGroupFrame,
+ "expected a column-group");
+ colInfo = GetColISizeInfo(aRenderingContext,
+ colFrame->GetParent(), wm);
+ colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
+ colInfo.hasSpecifiedISize);
+ colFrame->AddPrefPercent(colInfo.prefPercent);
+ }
+
+ // Consider the contents of and the isizes on the cells without
+ // colspans.
+ nsCellMapColumnIterator columnIter(cellMap, col);
+ int32_t row, colSpan;
+ nsTableCellFrame* cellFrame;
+ while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) {
+ if (colSpan > 1) {
+ spanningCells.AddCell(colSpan, row, col);
+ continue;
+ }
+
+ CellISizeInfo info = GetCellISizeInfo(aRenderingContext,
+ cellFrame, wm);
+
+ colFrame->AddCoords(info.minCoord, info.prefCoord,
+ info.hasSpecifiedISize);
+ colFrame->AddPrefPercent(info.prefPercent);
+ }
+#ifdef DEBUG_dbaron_off
+ printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n",
+ mTableFrame, col, colFrame->GetMinCoord(),
+ colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
+ colFrame->GetPrefPercent());
+#endif
+ }
+#ifdef DEBUG_TABLE_STRATEGY
+ printf("ComputeColumnIntrinsicISizes single\n");
+ mTableFrame->Dump(false, true, false);
+#endif
+
+ // Consider the cells with a colspan that we saved in the loop above
+ // into the spanning cell sorter. We consider these cells by seeing
+ // if they require adding to the isizes resulting only from cells
+ // with a smaller colspan, and therefore we must process them sorted
+ // in increasing order by colspan. For each colspan group, we
+ // accumulate new values to accumulate in the column frame's Span*
+ // members.
+ //
+ // Considering things only relative to the isizes resulting from
+ // cells with smaller colspans (rather than incrementally including
+ // the results from spanning cells, or doing spanning and
+ // non-spanning cells in a single pass) means that layout remains
+ // row-order-invariant and (except for percentage isizes that add to
+ // more than 100%) column-order invariant.
+ //
+ // Starting with smaller colspans makes it more likely that we
+ // satisfy all the constraints given and don't distribute space to
+ // columns where we don't need it.
+ SpanningCellSorter::Item *item;
+ int32_t colSpan;
+ while ((item = spanningCells.GetNext(&colSpan))) {
+ NS_ASSERTION(colSpan > 1,
+ "cell should not have been put in spanning cell sorter");
+ do {
+ int32_t row = item->row;
+ col = item->col;
+ CellData *cellData = cellMap->GetDataAt(row, col);
+ NS_ASSERTION(cellData && cellData->IsOrig(),
+ "bogus result from spanning cell sorter");
+
+ nsTableCellFrame *cellFrame = cellData->GetCellFrame();
+ NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter");
+
+ CellISizeInfo info =
+ GetCellISizeInfo(aRenderingContext, cellFrame, wm);
+
+ if (info.prefPercent > 0.0f) {
+ DistributePctISizeToColumns(info.prefPercent,
+ col, colSpan);
+ }
+ DistributeISizeToColumns(info.minCoord, col, colSpan,
+ BTLS_MIN_ISIZE, info.hasSpecifiedISize);
+ DistributeISizeToColumns(info.prefCoord, col, colSpan,
+ BTLS_PREF_ISIZE, info.hasSpecifiedISize);
+ } while ((item = item->next));
+
+ // Combine the results of the span analysis into the main results,
+ // for each increment of colspan.
+
+ for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
+ nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+
+ colFrame->AccumulateSpanIntrinsics();
+ colFrame->ResetSpanIntrinsics();
+
+#ifdef DEBUG_dbaron_off
+ printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n",
+ mTableFrame, col, colSpan, colFrame->GetMinCoord(),
+ colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
+ colFrame->GetPrefPercent());
+#endif
+ }
+ }
+
+ // Prevent percentages from adding to more than 100% by (to be
+ // compatible with other browsers) treating any percentages that would
+ // increase the total percentage to more than 100% as the number that
+ // would increase it to only 100% (which is 0% if we've already hit
+ // 100%). This means layout depends on the order of columns.
+ float pct_used = 0.0f;
+ for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
+ nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+
+ colFrame->AdjustPrefPercent(&pct_used);
+ }
+
+#ifdef DEBUG_TABLE_STRATEGY
+ printf("ComputeColumnIntrinsicISizes spanning\n");
+ mTableFrame->Dump(false, true, false);
+#endif
+}
+
+void
+BasicTableLayoutStrategy::ComputeIntrinsicISizes(nsRenderingContext* aRenderingContext)
+{
+ ComputeColumnIntrinsicISizes(aRenderingContext);
+
+ nsTableCellMap *cellMap = mTableFrame->GetCellMap();
+ nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0;
+ float pct_total = 0.0f; // always from 0.0f - 1.0f
+ int32_t colCount = cellMap->GetColCount();
+ // add a total of (colcount + 1) lots of cellSpacingX for columns where a
+ // cell originates
+ nscoord add = mTableFrame->GetColSpacing(colCount);
+
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
+ add += mTableFrame->GetColSpacing(col - 1);
+ }
+ min += colFrame->GetMinCoord();
+ pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord());
+
+ // Percentages are of the table, so we have to reverse them for
+ // intrinsic isizes.
+ float p = colFrame->GetPrefPercent();
+ if (p > 0.0f) {
+ nscoord colPref = colFrame->GetPrefCoord();
+ nscoord new_small_pct_expand =
+ (colPref == nscoord_MAX ?
+ nscoord_MAX : nscoord(float(colPref) / p));
+ if (new_small_pct_expand > max_small_pct_pref) {
+ max_small_pct_pref = new_small_pct_expand;
+ }
+ pct_total += p;
+ } else {
+ nonpct_pref_total = NSCoordSaturatingAdd(nonpct_pref_total,
+ colFrame->GetPrefCoord());
+ }
+ }
+
+ nscoord pref_pct_expand = pref;
+
+ // Account for small percentages expanding the preferred isize of
+ // *other* columns.
+ if (max_small_pct_pref > pref_pct_expand) {
+ pref_pct_expand = max_small_pct_pref;
+ }
+
+ // Account for large percentages expanding the preferred isize of
+ // themselves. There's no need to iterate over the columns multiple
+ // times, since when there is such a need, the small percentage
+ // effect is bigger anyway. (I think!)
+ NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f,
+ "column percentage inline-sizes not adjusted down to 100%");
+ if (pct_total == 1.0f) {
+ if (nonpct_pref_total > 0) {
+ pref_pct_expand = nscoord_MAX;
+ // XXX Or should I use some smaller value? (Test this using
+ // nested tables!)
+ }
+ } else {
+ nscoord large_pct_pref =
+ (nonpct_pref_total == nscoord_MAX ?
+ nscoord_MAX :
+ nscoord(float(nonpct_pref_total) / (1.0f - pct_total)));
+ if (large_pct_pref > pref_pct_expand)
+ pref_pct_expand = large_pct_pref;
+ }
+
+ // border-spacing isn't part of the basis for percentages
+ if (colCount > 0) {
+ min += add;
+ pref = NSCoordSaturatingAdd(pref, add);
+ pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add);
+ }
+
+ mMinISize = min;
+ mPrefISize = pref;
+ mPrefISizePctExpand = pref_pct_expand;
+}
+
+/* virtual */ void
+BasicTableLayoutStrategy::MarkIntrinsicISizesDirty()
+{
+ mMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
+ mPrefISize = NS_INTRINSIC_WIDTH_UNKNOWN;
+ mPrefISizePctExpand = NS_INTRINSIC_WIDTH_UNKNOWN;
+ mLastCalcISize = nscoord_MIN;
+}
+
+/* virtual */ void
+BasicTableLayoutStrategy::ComputeColumnISizes(const ReflowInput& aReflowInput)
+{
+ nscoord iSize = aReflowInput.ComputedISize();
+
+ if (mLastCalcISize == iSize) {
+ return;
+ }
+ mLastCalcISize = iSize;
+
+ NS_ASSERTION((mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) ==
+ (mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN),
+ "dirtyness out of sync");
+ NS_ASSERTION((mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) ==
+ (mPrefISizePctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
+ "dirtyness out of sync");
+ // XXX Is this needed?
+ if (mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
+ ComputeIntrinsicISizes(aReflowInput.mRenderingContext);
+ }
+
+ nsTableCellMap *cellMap = mTableFrame->GetCellMap();
+ int32_t colCount = cellMap->GetColCount();
+ if (colCount <= 0)
+ return; // nothing to do
+
+ DistributeISizeToColumns(iSize, 0, colCount, BTLS_FINAL_ISIZE, false);
+
+#ifdef DEBUG_TABLE_STRATEGY
+ printf("ComputeColumnISizes final\n");
+ mTableFrame->Dump(false, true, false);
+#endif
+}
+
+void
+BasicTableLayoutStrategy::DistributePctISizeToColumns(float aSpanPrefPct,
+ int32_t aFirstCol,
+ int32_t aColCount)
+{
+ // First loop to determine:
+ int32_t nonPctColCount = 0; // number of spanned columns without % isize
+ nscoord nonPctTotalPrefISize = 0; // total pref isize of those columns
+ // and to reduce aSpanPrefPct by columns that already have % isize
+
+ int32_t scol, scol_end;
+ nsTableCellMap *cellMap = mTableFrame->GetCellMap();
+ for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
+ scol < scol_end; ++scol) {
+ nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
+ if (!scolFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ float scolPct = scolFrame->GetPrefPercent();
+ if (scolPct == 0.0f) {
+ nonPctTotalPrefISize += scolFrame->GetPrefCoord();
+ if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
+ ++nonPctColCount;
+ }
+ } else {
+ aSpanPrefPct -= scolPct;
+ }
+ }
+
+ if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) {
+ // There's no %-isize on the colspan left over to distribute,
+ // or there are no columns to which we could distribute %-isize
+ return;
+ }
+
+ // Second loop, to distribute what remains of aSpanPrefPct
+ // between the non-percent-isize spanned columns
+ const bool spanHasNonPctPref = nonPctTotalPrefISize > 0; // Loop invariant
+ for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
+ scol < scol_end; ++scol) {
+ nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
+ if (!scolFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+
+ if (scolFrame->GetPrefPercent() == 0.0f) {
+ NS_ASSERTION((!spanHasNonPctPref ||
+ nonPctTotalPrefISize != 0) &&
+ nonPctColCount != 0,
+ "should not be zero if we haven't allocated "
+ "all pref percent");
+
+ float allocatedPct; // % isize to be given to this column
+ if (spanHasNonPctPref) {
+ // Group so we're multiplying by 1.0f when we need
+ // to use up aSpanPrefPct.
+ allocatedPct = aSpanPrefPct *
+ (float(scolFrame->GetPrefCoord()) /
+ float(nonPctTotalPrefISize));
+ } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
+ // distribute equally when all pref isizes are 0
+ allocatedPct = aSpanPrefPct / float(nonPctColCount);
+ } else {
+ allocatedPct = 0.0f;
+ }
+ // Allocate the percent
+ scolFrame->AddSpanPrefPercent(allocatedPct);
+
+ // To avoid accumulating rounding error from division,
+ // subtract this column's values from the totals.
+ aSpanPrefPct -= allocatedPct;
+ nonPctTotalPrefISize -= scolFrame->GetPrefCoord();
+ if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
+ --nonPctColCount;
+ }
+
+ if (!aSpanPrefPct) {
+ // No more span-percent-isize to distribute --> we're done.
+ NS_ASSERTION(spanHasNonPctPref ?
+ nonPctTotalPrefISize == 0 :
+ nonPctColCount == 0,
+ "No more pct inline-size to distribute, "
+ "but there are still cols that need some.");
+ return;
+ }
+ }
+ }
+}
+
+void
+BasicTableLayoutStrategy::DistributeISizeToColumns(nscoord aISize,
+ int32_t aFirstCol,
+ int32_t aColCount,
+ BtlsISizeType aISizeType,
+ bool aSpanHasSpecifiedISize)
+{
+ NS_ASSERTION(aISizeType != BTLS_FINAL_ISIZE ||
+ (aFirstCol == 0 &&
+ aColCount == mTableFrame->GetCellMap()->GetColCount()),
+ "Computing final column isizes, but didn't get full column range");
+
+ nscoord subtract = 0;
+ // aISize initially includes border-spacing for the boundaries in between
+ // each of the columns. We start at aFirstCol + 1 because the first
+ // in-between boundary would be at the left edge of column aFirstCol + 1
+ for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) {
+ if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
+ // border-spacing isn't part of the basis for percentages.
+ subtract += mTableFrame->GetColSpacing(col - 1);
+ }
+ }
+ if (aISizeType == BTLS_FINAL_ISIZE) {
+ // If we're computing final col-isize, then aISize initially includes
+ // border spacing on the table's far istart + far iend edge, too. Need
+ // to subtract those out, too.
+ subtract += (mTableFrame->GetColSpacing(-1) +
+ mTableFrame->GetColSpacing(aColCount));
+ }
+ aISize = NSCoordSaturatingSubtract(aISize, subtract, nscoord_MAX);
+
+ /*
+ * The goal of this function is to distribute |aISize| between the
+ * columns by making an appropriate AddSpanCoords or SetFinalISize
+ * call for each column. (We call AddSpanCoords if we're
+ * distributing a column-spanning cell's minimum or preferred isize
+ * to its spanned columns. We call SetFinalISize if we're
+ * distributing a table's final isize to its columns.)
+ *
+ * The idea is to either assign one of the following sets of isizes
+ * or a weighted average of two adjacent sets of isizes. It is not
+ * possible to assign values smaller than the smallest set of
+ * isizes. However, see below for handling the case of assigning
+ * values larger than the largest set of isizes. From smallest to
+ * largest, these are:
+ *
+ * 1. [guess_min] Assign all columns their min isize.
+ *
+ * 2. [guess_min_pct] Assign all columns with percentage isizes
+ * their percentage isize, and all other columns their min isize.
+ *
+ * 3. [guess_min_spec] Assign all columns with percentage isizes
+ * their percentage isize, all columns with specified coordinate
+ * isizes their pref isize (since it doesn't matter whether it's the
+ * largest contributor to the pref isize that was the specified
+ * contributor), and all other columns their min isize.
+ *
+ * 4. [guess_pref] Assign all columns with percentage isizes their
+ * specified isize, and all other columns their pref isize.
+ *
+ * If |aISize| is *larger* than what we would assign in (4), then we
+ * expand the columns:
+ *
+ * a. if any columns without a specified coordinate isize or
+ * percent isize have nonzero pref isize, in proportion to pref
+ * isize [total_flex_pref]
+ *
+ * b. otherwise, if any columns without a specified coordinate
+ * isize or percent isize, but with cells originating in them,
+ * have zero pref isize, equally between these
+ * [numNonSpecZeroISizeCols]
+ *
+ * c. otherwise, if any columns without percent isize have nonzero
+ * pref isize, in proportion to pref isize [total_fixed_pref]
+ *
+ * d. otherwise, if any columns have nonzero percentage isizes, in
+ * proportion to the percentage isizes [total_pct]
+ *
+ * e. otherwise, equally.
+ */
+
+ // Loop #1 over the columns, to figure out the four values above so
+ // we know which case we're dealing with.
+
+ nscoord guess_min = 0,
+ guess_min_pct = 0,
+ guess_min_spec = 0,
+ guess_pref = 0,
+ total_flex_pref = 0,
+ total_fixed_pref = 0;
+ float total_pct = 0.0f; // 0.0f to 1.0f
+ int32_t numInfiniteISizeCols = 0;
+ int32_t numNonSpecZeroISizeCols = 0;
+
+ int32_t col;
+ nsTableCellMap *cellMap = mTableFrame->GetCellMap();
+ for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ nscoord min_iSize = colFrame->GetMinCoord();
+ guess_min += min_iSize;
+ if (colFrame->GetPrefPercent() != 0.0f) {
+ float pct = colFrame->GetPrefPercent();
+ total_pct += pct;
+ nscoord val = nscoord(float(aISize) * pct);
+ if (val < min_iSize) {
+ val = min_iSize;
+ }
+ guess_min_pct += val;
+ guess_pref = NSCoordSaturatingAdd(guess_pref, val);
+ } else {
+ nscoord pref_iSize = colFrame->GetPrefCoord();
+ if (pref_iSize == nscoord_MAX) {
+ ++numInfiniteISizeCols;
+ }
+ guess_pref = NSCoordSaturatingAdd(guess_pref, pref_iSize);
+ guess_min_pct += min_iSize;
+ if (colFrame->GetHasSpecifiedCoord()) {
+ // we'll add on the rest of guess_min_spec outside the
+ // loop
+ nscoord delta = NSCoordSaturatingSubtract(pref_iSize,
+ min_iSize, 0);
+ guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta);
+ total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref,
+ pref_iSize);
+ } else if (pref_iSize == 0) {
+ if (cellMap->GetNumCellsOriginatingInCol(col) > 0) {
+ ++numNonSpecZeroISizeCols;
+ }
+ } else {
+ total_flex_pref = NSCoordSaturatingAdd(total_flex_pref,
+ pref_iSize);
+ }
+ }
+ }
+ guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct);
+
+ // Determine what we're flexing:
+ enum Loop2Type {
+ FLEX_PCT_SMALL, // between (1) and (2) above
+ FLEX_FIXED_SMALL, // between (2) and (3) above
+ FLEX_FLEX_SMALL, // between (3) and (4) above
+ FLEX_FLEX_LARGE, // greater than (4) above, case (a)
+ FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b)
+ FLEX_FIXED_LARGE, // greater than (4) above, case (c)
+ FLEX_PCT_LARGE, // greater than (4) above, case (d)
+ FLEX_ALL_LARGE // greater than (4) above, case (e)
+ };
+
+ Loop2Type l2t;
+ // These are constants (over columns) for each case's math. We use
+ // a pair of nscoords rather than a float so that we can subtract
+ // each column's allocation so we avoid accumulating rounding error.
+ nscoord space; // the amount of extra isize to allocate
+ union {
+ nscoord c;
+ float f;
+ } basis; // the sum of the statistic over columns to divide it
+ if (aISize < guess_pref) {
+ if (aISizeType != BTLS_FINAL_ISIZE && aISize <= guess_min) {
+ // Return early -- we don't have any extra space to distribute.
+ return;
+ }
+ NS_ASSERTION(!(aISizeType == BTLS_FINAL_ISIZE && aISize < guess_min),
+ "Table inline-size is less than the "
+ "sum of its columns' min inline-sizes");
+ if (aISize < guess_min_pct) {
+ l2t = FLEX_PCT_SMALL;
+ space = aISize - guess_min;
+ basis.c = guess_min_pct - guess_min;
+ } else if (aISize < guess_min_spec) {
+ l2t = FLEX_FIXED_SMALL;
+ space = aISize - guess_min_pct;
+ basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct,
+ nscoord_MAX);
+ } else {
+ l2t = FLEX_FLEX_SMALL;
+ space = aISize - guess_min_spec;
+ basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec,
+ nscoord_MAX);
+ }
+ } else {
+ space = NSCoordSaturatingSubtract(aISize, guess_pref, nscoord_MAX);
+ if (total_flex_pref > 0) {
+ l2t = FLEX_FLEX_LARGE;
+ basis.c = total_flex_pref;
+ } else if (numNonSpecZeroISizeCols > 0) {
+ l2t = FLEX_FLEX_LARGE_ZERO;
+ basis.c = numNonSpecZeroISizeCols;
+ } else if (total_fixed_pref > 0) {
+ l2t = FLEX_FIXED_LARGE;
+ basis.c = total_fixed_pref;
+ } else if (total_pct > 0.0f) {
+ l2t = FLEX_PCT_LARGE;
+ basis.f = total_pct;
+ } else {
+ l2t = FLEX_ALL_LARGE;
+ basis.c = aColCount;
+ }
+ }
+
+#ifdef DEBUG_dbaron_off
+ printf("ComputeColumnISizes: %d columns in isize %d,\n"
+ " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n"
+ " l2t=%d, space=%d, basis.c=%d\n",
+ aColCount, aISize,
+ guess_min, guess_min_pct, guess_min_spec, guess_pref,
+ total_flex_pref, total_fixed_pref, total_pct,
+ l2t, space, basis.c);
+#endif
+
+ for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ nscoord col_iSize;
+
+ float pct = colFrame->GetPrefPercent();
+ if (pct != 0.0f) {
+ col_iSize = nscoord(float(aISize) * pct);
+ nscoord col_min = colFrame->GetMinCoord();
+ if (col_iSize < col_min) {
+ col_iSize = col_min;
+ }
+ } else {
+ col_iSize = colFrame->GetPrefCoord();
+ }
+
+ nscoord col_iSize_before_adjust = col_iSize;
+
+ switch (l2t) {
+ case FLEX_PCT_SMALL:
+ col_iSize = col_iSize_before_adjust = colFrame->GetMinCoord();
+ if (pct != 0.0f) {
+ nscoord pct_minus_min =
+ nscoord(float(aISize) * pct) - col_iSize;
+ if (pct_minus_min > 0) {
+ float c = float(space) / float(basis.c);
+ basis.c -= pct_minus_min;
+ col_iSize += NSToCoordRound(float(pct_minus_min) * c);
+ }
+ }
+ break;
+ case FLEX_FIXED_SMALL:
+ if (pct == 0.0f) {
+ NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
+ "wrong inline-size assigned");
+ if (colFrame->GetHasSpecifiedCoord()) {
+ nscoord col_min = colFrame->GetMinCoord();
+ nscoord pref_minus_min = col_iSize - col_min;
+ col_iSize = col_iSize_before_adjust = col_min;
+ if (pref_minus_min != 0) {
+ float c = float(space) / float(basis.c);
+ basis.c -= pref_minus_min;
+ col_iSize += NSToCoordRound(
+ float(pref_minus_min) * c);
+ }
+ } else
+ col_iSize = col_iSize_before_adjust =
+ colFrame->GetMinCoord();
+ }
+ break;
+ case FLEX_FLEX_SMALL:
+ if (pct == 0.0f &&
+ !colFrame->GetHasSpecifiedCoord()) {
+ NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
+ "wrong inline-size assigned");
+ nscoord col_min = colFrame->GetMinCoord();
+ nscoord pref_minus_min =
+ NSCoordSaturatingSubtract(col_iSize, col_min, 0);
+ col_iSize = col_iSize_before_adjust = col_min;
+ if (pref_minus_min != 0) {
+ float c = float(space) / float(basis.c);
+ // If we have infinite-isize cols, then the standard
+ // adjustment to col_iSize using 'c' won't work,
+ // because basis.c and pref_minus_min are both
+ // nscoord_MAX and will cancel each other out in the
+ // col_iSize adjustment (making us assign all the
+ // space to the first inf-isize col). To correct for
+ // this, we'll also divide by numInfiniteISizeCols to
+ // spread the space equally among the inf-isize cols.
+ if (numInfiniteISizeCols) {
+ if (colFrame->GetPrefCoord() == nscoord_MAX) {
+ c = c / float(numInfiniteISizeCols);
+ --numInfiniteISizeCols;
+ } else {
+ c = 0.0f;
+ }
+ }
+ basis.c = NSCoordSaturatingSubtract(basis.c,
+ pref_minus_min,
+ nscoord_MAX);
+ col_iSize += NSToCoordRound(
+ float(pref_minus_min) * c);
+ }
+ }
+ break;
+ case FLEX_FLEX_LARGE:
+ if (pct == 0.0f &&
+ !colFrame->GetHasSpecifiedCoord()) {
+ NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
+ "wrong inline-size assigned");
+ if (col_iSize != 0) {
+ if (space == nscoord_MAX) {
+ basis.c -= col_iSize;
+ col_iSize = nscoord_MAX;
+ } else {
+ float c = float(space) / float(basis.c);
+ basis.c -= col_iSize;
+ col_iSize += NSToCoordRound(float(col_iSize) * c);
+ }
+ }
+ }
+ break;
+ case FLEX_FLEX_LARGE_ZERO:
+ if (pct == 0.0f &&
+ !colFrame->GetHasSpecifiedCoord() &&
+ cellMap->GetNumCellsOriginatingInCol(col) > 0) {
+
+ NS_ASSERTION(col_iSize == 0 &&
+ colFrame->GetPrefCoord() == 0,
+ "Since we're in FLEX_FLEX_LARGE_ZERO case, "
+ "all auto-inline-size cols should have zero "
+ "pref inline-size.");
+ float c = float(space) / float(basis.c);
+ col_iSize += NSToCoordRound(c);
+ --basis.c;
+ }
+ break;
+ case FLEX_FIXED_LARGE:
+ if (pct == 0.0f) {
+ NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
+ "wrong inline-size assigned");
+ NS_ASSERTION(colFrame->GetHasSpecifiedCoord() ||
+ colFrame->GetPrefCoord() == 0,
+ "wrong case");
+ if (col_iSize != 0) {
+ float c = float(space) / float(basis.c);
+ basis.c -= col_iSize;
+ col_iSize += NSToCoordRound(float(col_iSize) * c);
+ }
+ }
+ break;
+ case FLEX_PCT_LARGE:
+ NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0,
+ "wrong case");
+ if (pct != 0.0f) {
+ float c = float(space) / basis.f;
+ col_iSize += NSToCoordRound(pct * c);
+ basis.f -= pct;
+ }
+ break;
+ case FLEX_ALL_LARGE:
+ {
+ float c = float(space) / float(basis.c);
+ col_iSize += NSToCoordRound(c);
+ --basis.c;
+ }
+ break;
+ }
+
+ // Only subtract from space if it's a real number.
+ if (space != nscoord_MAX) {
+ NS_ASSERTION(col_iSize != nscoord_MAX,
+ "How is col_iSize nscoord_MAX if space isn't?");
+ NS_ASSERTION(col_iSize_before_adjust != nscoord_MAX,
+ "How is col_iSize_before_adjust nscoord_MAX if space isn't?");
+ space -= col_iSize - col_iSize_before_adjust;
+ }
+
+ NS_ASSERTION(col_iSize >= colFrame->GetMinCoord(),
+ "assigned inline-size smaller than min");
+
+ // Apply the new isize
+ switch (aISizeType) {
+ case BTLS_MIN_ISIZE:
+ {
+ // Note: AddSpanCoords requires both a min and pref isize.
+ // For the pref isize, we'll just pass in our computed
+ // min isize, because the real pref isize will be at least
+ // as big
+ colFrame->AddSpanCoords(col_iSize, col_iSize,
+ aSpanHasSpecifiedISize);
+ }
+ break;
+ case BTLS_PREF_ISIZE:
+ {
+ // Note: AddSpanCoords requires both a min and pref isize.
+ // For the min isize, we'll just pass in 0, because
+ // the real min isize will be at least 0
+ colFrame->AddSpanCoords(0, col_iSize,
+ aSpanHasSpecifiedISize);
+ }
+ break;
+ case BTLS_FINAL_ISIZE:
+ {
+ nscoord old_final = colFrame->GetFinalISize();
+ colFrame->SetFinalISize(col_iSize);
+
+ if (old_final != col_iSize) {
+ mTableFrame->DidResizeColumns();
+ }
+ }
+ break;
+ }
+ }
+ NS_ASSERTION((space == 0 || space == nscoord_MAX) &&
+ ((l2t == FLEX_PCT_LARGE)
+ ? (-0.001f < basis.f && basis.f < 0.001f)
+ : (basis.c == 0 || basis.c == nscoord_MAX)),
+ "didn't subtract all that we added");
+}
diff --git a/layout/tables/BasicTableLayoutStrategy.h b/layout/tables/BasicTableLayoutStrategy.h
new file mode 100644
index 0000000000..082da8fb32
--- /dev/null
+++ b/layout/tables/BasicTableLayoutStrategy.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Web-compatible algorithms that determine column and table isizes,
+ * used for CSS2's 'table-layout: auto'.
+ */
+
+#ifndef BasicTableLayoutStrategy_h_
+#define BasicTableLayoutStrategy_h_
+
+#include "mozilla/Attributes.h"
+#include "nsITableLayoutStrategy.h"
+
+class nsTableFrame;
+
+class BasicTableLayoutStrategy : public nsITableLayoutStrategy
+{
+public:
+ explicit BasicTableLayoutStrategy(nsTableFrame *aTableFrame);
+ virtual ~BasicTableLayoutStrategy();
+
+ // nsITableLayoutStrategy implementation
+ virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext,
+ bool aComputingSize) override;
+ virtual void MarkIntrinsicISizesDirty() override;
+ virtual void ComputeColumnISizes(const ReflowInput& aReflowInput) override;
+
+private:
+ // NOTE: Using prefix "BTLS" to avoid overlapping names with
+ // the values of nsLayoutUtils::IntrinsicISizeType
+ enum BtlsISizeType { BTLS_MIN_ISIZE,
+ BTLS_PREF_ISIZE,
+ BTLS_FINAL_ISIZE };
+
+ // Compute intrinsic isize member variables on the columns.
+ void ComputeColumnIntrinsicISizes(nsRenderingContext* aRenderingContext);
+
+ // Distribute a colspanning cell's percent isize (if any) to its columns.
+ void DistributePctISizeToColumns(float aSpanPrefPct,
+ int32_t aFirstCol,
+ int32_t aColCount);
+
+ // Distribute an isize of some BltsISizeType type to a set of columns.
+ // aISize: The amount of isize to be distributed
+ // aFirstCol: The index (in the table) of the first column to be
+ // considered for receiving isize
+ // aColCount: The number of consecutive columns (starting with aFirstCol)
+ // to be considered for receiving isize
+ // aISizeType: The type of isize being distributed. (BTLS_MIN_ISIZE and
+ // BTLS_PREF_ISIZE are intended to be used for dividing up
+ // colspan's min & pref isize. BTLS_FINAL_ISIZE is intended
+ // to be used for distributing the table's final isize across
+ // all its columns)
+ // aSpanHasSpecifiedISize: Should be true iff:
+ // - We're distributing a colspanning cell's
+ // pref or min isize to its columns
+ // - The colspanning cell has a specified isize.
+ void DistributeISizeToColumns(nscoord aISize,
+ int32_t aFirstCol,
+ int32_t aColCount,
+ BtlsISizeType aISizeType,
+ bool aSpanHasSpecifiedISize);
+
+
+ // Compute the min and pref isizes of the table from the isize
+ // variables on the columns.
+ void ComputeIntrinsicISizes(nsRenderingContext* aRenderingContext);
+
+ nsTableFrame *mTableFrame;
+ nscoord mMinISize;
+ nscoord mPrefISize;
+ nscoord mPrefISizePctExpand;
+ nscoord mLastCalcISize;
+};
+
+#endif /* !defined(BasicTableLayoutStrategy_h_) */
diff --git a/layout/tables/FixedTableLayoutStrategy.cpp b/layout/tables/FixedTableLayoutStrategy.cpp
new file mode 100644
index 0000000000..4277657958
--- /dev/null
+++ b/layout/tables/FixedTableLayoutStrategy.cpp
@@ -0,0 +1,422 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Algorithms that determine column and table inline sizes used for
+ * CSS2's 'table-layout: fixed'.
+ */
+
+#include "FixedTableLayoutStrategy.h"
+#include "nsTableFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableCellFrame.h"
+#include <algorithm>
+
+FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame)
+ : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed)
+ , mTableFrame(aTableFrame)
+{
+ MarkIntrinsicISizesDirty();
+}
+
+/* virtual */
+FixedTableLayoutStrategy::~FixedTableLayoutStrategy()
+{
+}
+
+/* virtual */ nscoord
+FixedTableLayoutStrategy::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+ DISPLAY_MIN_WIDTH(mTableFrame, mMinISize);
+ if (mMinISize != NS_INTRINSIC_WIDTH_UNKNOWN) {
+ return mMinISize;
+ }
+
+ // It's theoretically possible to do something much better here that
+ // depends only on the columns and the first row (where we look at
+ // intrinsic inline sizes inside the first row and then reverse the
+ // algorithm to find the narrowest inline size that would hold all of
+ // those intrinsic inline sizes), but it wouldn't be compatible with
+ // other browsers, or with the use of GetMinISize by
+ // nsTableFrame::ComputeSize to determine the inline size of a fixed
+ // layout table, since CSS2.1 says:
+ // The width of the table is then the greater of the value of the
+ // 'width' property for the table element and the sum of the column
+ // widths (plus cell spacing or borders).
+
+ // XXX Should we really ignore 'min-width' and 'max-width'?
+ // XXX Should we really ignore widths on column groups?
+
+ nsTableCellMap *cellMap = mTableFrame->GetCellMap();
+ int32_t colCount = cellMap->GetColCount();
+
+ nscoord result = 0;
+
+ if (colCount > 0) {
+ result += mTableFrame->GetColSpacing(-1, colCount);
+ }
+
+ WritingMode wm = mTableFrame->GetWritingMode();
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ nscoord spacing = mTableFrame->GetColSpacing(col);
+ const nsStyleCoord *styleISize = &colFrame->StylePosition()->ISize(wm);
+ if (styleISize->ConvertsToLength()) {
+ result += colFrame->ComputeISizeValue(aRenderingContext,
+ 0, 0, 0, *styleISize);
+ } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
+ // do nothing
+ } else {
+ NS_ASSERTION(styleISize->GetUnit() == eStyleUnit_Auto ||
+ styleISize->GetUnit() == eStyleUnit_Enumerated ||
+ (styleISize->IsCalcUnit() && styleISize->CalcHasPercent()),
+ "bad inline size");
+
+ // The 'table-layout: fixed' algorithm considers only cells in the
+ // first row.
+ bool originates;
+ int32_t colSpan;
+ nsTableCellFrame *cellFrame = cellMap->GetCellInfoAt(0, col, &originates,
+ &colSpan);
+ if (cellFrame) {
+ styleISize = &cellFrame->StylePosition()->ISize(wm);
+ if (styleISize->ConvertsToLength() ||
+ (styleISize->GetUnit() == eStyleUnit_Enumerated &&
+ (styleISize->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
+ styleISize->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
+ nscoord cellISize =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext, cellFrame,
+ nsLayoutUtils::MIN_ISIZE);
+ if (colSpan > 1) {
+ // If a column-spanning cell is in the first row, split up
+ // the space evenly. (XXX This isn't quite right if some of
+ // the columns it's in have specified inline sizes. Should
+ // we care?)
+ cellISize = ((cellISize + spacing) / colSpan) - spacing;
+ }
+ result += cellISize;
+ } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
+ if (colSpan > 1) {
+ // XXX Can this force columns to negative inline sizes?
+ result -= spacing * (colSpan - 1);
+ }
+ }
+ // else, for 'auto', '-moz-available', '-moz-fit-content',
+ // and 'calc()' with percentages, do nothing
+ }
+ }
+ }
+
+ return (mMinISize = result);
+}
+
+/* virtual */ nscoord
+FixedTableLayoutStrategy::GetPrefISize(nsRenderingContext* aRenderingContext,
+ bool aComputingSize)
+{
+ // It's theoretically possible to do something much better here that
+ // depends only on the columns and the first row (where we look at
+ // intrinsic inline sizes inside the first row and then reverse the
+ // algorithm to find the narrowest inline size that would hold all of
+ // those intrinsic inline sizes), but it wouldn't be compatible with
+ // other browsers.
+ nscoord result = nscoord_MAX;
+ DISPLAY_PREF_WIDTH(mTableFrame, result);
+ return result;
+}
+
+/* virtual */ void
+FixedTableLayoutStrategy::MarkIntrinsicISizesDirty()
+{
+ mMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
+ mLastCalcISize = nscoord_MIN;
+}
+
+static inline nscoord
+AllocateUnassigned(nscoord aUnassignedSpace, float aShare)
+{
+ if (aShare == 1.0f) {
+ // This happens when the numbers we're dividing to get aShare are
+ // equal. We want to return unassignedSpace exactly, even if it
+ // can't be precisely round-tripped through float.
+ return aUnassignedSpace;
+ }
+ return NSToCoordRound(float(aUnassignedSpace) * aShare);
+}
+
+/* virtual */ void
+FixedTableLayoutStrategy::ComputeColumnISizes(const ReflowInput& aReflowInput)
+{
+ nscoord tableISize = aReflowInput.ComputedISize();
+
+ if (mLastCalcISize == tableISize) {
+ return;
+ }
+ mLastCalcISize = tableISize;
+
+ nsTableCellMap *cellMap = mTableFrame->GetCellMap();
+ int32_t colCount = cellMap->GetColCount();
+
+ if (colCount == 0) {
+ // No Columns - nothing to compute
+ return;
+ }
+
+ // border-spacing isn't part of the basis for percentages.
+ tableISize -= mTableFrame->GetColSpacing(-1, colCount);
+
+ // store the old column inline sizes. We might call SetFinalISize
+ // multiple times on the columns, due to this we can't compare at the
+ // last call that the inline size has changed with respect to the last
+ // call to ComputeColumnISizes. In order to overcome this we store the
+ // old values in this array. A single call to SetFinalISize would make
+ // it possible to call GetFinalISize before and to compare when
+ // setting the final inline size.
+ nsTArray<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 nsStyleCoord *styleISize = &colFrame->StylePosition()->ISize(wm);
+ nscoord colISize;
+ if (styleISize->ConvertsToLength()) {
+ colISize = colFrame->ComputeISizeValue(aReflowInput.mRenderingContext,
+ 0, 0, 0, *styleISize);
+ specTotal += colISize;
+ } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
+ float pct = styleISize->GetPercentValue();
+ colISize = NSToCoordFloor(pct * float(tableISize));
+ colFrame->AddPrefPercent(pct);
+ pctTotal += pct;
+ } else {
+ NS_ASSERTION(styleISize->GetUnit() == eStyleUnit_Auto ||
+ styleISize->GetUnit() == eStyleUnit_Enumerated ||
+ (styleISize->IsCalcUnit() && styleISize->CalcHasPercent()),
+ "bad inline size");
+
+ // The 'table-layout: fixed' algorithm considers only cells in the
+ // first row.
+ bool originates;
+ int32_t colSpan;
+ nsTableCellFrame *cellFrame = cellMap->GetCellInfoAt(0, col, &originates,
+ &colSpan);
+ if (cellFrame) {
+ const nsStylePosition* cellStylePos = cellFrame->StylePosition();
+ styleISize = &cellStylePos->ISize(wm);
+ if (styleISize->ConvertsToLength() ||
+ (styleISize->GetUnit() == eStyleUnit_Enumerated &&
+ (styleISize->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
+ styleISize->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
+ // XXX This should use real percentage padding
+ // Note that the difference between MIN_ISIZE and PREF_ISIZE
+ // shouldn't matter for any of these values of styleISize; use
+ // MIN_ISIZE for symmetry with GetMinISize above, just in case
+ // there is a difference.
+ colISize =
+ nsLayoutUtils::IntrinsicForContainer(aReflowInput.mRenderingContext,
+ cellFrame,
+ nsLayoutUtils::MIN_ISIZE);
+ } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
+ // XXX This should use real percentage padding
+ float pct = styleISize->GetPercentValue();
+ colISize = NSToCoordFloor(pct * float(tableISize));
+
+ if (cellStylePos->mBoxSizing == StyleBoxSizing::Content) {
+ nsIFrame::IntrinsicISizeOffsetData offsets =
+ cellFrame->IntrinsicISizeOffsets();
+ colISize += offsets.hPadding + offsets.hBorder;
+ }
+
+ pct /= float(colSpan);
+ colFrame->AddPrefPercent(pct);
+ pctTotal += pct;
+ } else {
+ // 'auto', '-moz-available', '-moz-fit-content', and 'calc()'
+ // with percentages
+ colISize = unassignedMarker;
+ }
+ if (colISize != unassignedMarker) {
+ if (colSpan > 1) {
+ // If a column-spanning cell is in the first row, split up
+ // the space evenly. (XXX This isn't quite right if some of
+ // the columns it's in have specified iSizes. Should we
+ // care?)
+ nscoord spacing = mTableFrame->GetColSpacing(col);
+ colISize = ((colISize + spacing) / colSpan) - spacing;
+ if (colISize < 0) {
+ colISize = 0;
+ }
+ }
+ if (styleISize->GetUnit() != eStyleUnit_Percent) {
+ specTotal += colISize;
+ }
+ }
+ } else {
+ colISize = unassignedMarker;
+ }
+ }
+
+ colFrame->SetFinalISize(colISize);
+
+ if (colISize == unassignedMarker) {
+ ++unassignedCount;
+ } else {
+ unassignedSpace -= colISize;
+ }
+ }
+
+ if (unassignedSpace < 0) {
+ if (pctTotal > 0) {
+ // If the columns took up too much space, reduce those that had
+ // percentage inline sizes. The spec doesn't say to do this, but
+ // we've always done it in the past, and so does WinIE6.
+ nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableISize));
+ nscoord reduce = std::min(pctUsed, -unassignedSpace);
+ float reduceRatio = float(reduce) / pctTotal;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ nscoord colISize = colFrame->GetFinalISize();
+ colISize -= NSToCoordFloor(colFrame->GetPrefPercent() * reduceRatio);
+ if (colISize < 0) {
+ colISize = 0;
+ }
+ colFrame->SetFinalISize(colISize);
+ }
+ }
+ unassignedSpace = 0;
+ }
+
+ if (unassignedCount > 0) {
+ // The spec says to distribute the remaining space evenly among
+ // the columns.
+ nscoord toAssign = unassignedSpace / unassignedCount;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (colFrame->GetFinalISize() == unassignedMarker) {
+ colFrame->SetFinalISize(toAssign);
+ }
+ }
+ } else if (unassignedSpace > 0) {
+ // The spec doesn't say how to distribute the unassigned space.
+ if (specTotal > 0) {
+ // Distribute proportionally to non-percentage columns.
+ nscoord specUndist = specTotal;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (colFrame->GetPrefPercent() == 0.0f) {
+ NS_ASSERTION(colFrame->GetFinalISize() <= specUndist,
+ "inline sizes don't add up");
+ nscoord toAdd = AllocateUnassigned(unassignedSpace,
+ float(colFrame->GetFinalISize()) /
+ float(specUndist));
+ specUndist -= colFrame->GetFinalISize();
+ colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
+ unassignedSpace -= toAdd;
+ if (specUndist <= 0) {
+ NS_ASSERTION(specUndist == 0, "math should be exact");
+ break;
+ }
+ }
+ }
+ NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
+ } else if (pctTotal > 0) {
+ // Distribute proportionally to percentage columns.
+ float pctUndist = pctTotal;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (pctUndist < colFrame->GetPrefPercent()) {
+ // This can happen with floating-point math.
+ NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist < 0.0001,
+ "inline sizes don't add up");
+ pctUndist = colFrame->GetPrefPercent();
+ }
+ nscoord toAdd = AllocateUnassigned(unassignedSpace,
+ colFrame->GetPrefPercent() /
+ pctUndist);
+ colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
+ unassignedSpace -= toAdd;
+ pctUndist -= colFrame->GetPrefPercent();
+ if (pctUndist <= 0.0f) {
+ break;
+ }
+ }
+ NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
+ } else {
+ // Distribute equally to the zero-iSize columns.
+ int32_t colsRemaining = colCount;
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ NS_ASSERTION(colFrame->GetFinalISize() == 0, "yikes");
+ nscoord toAdd = AllocateUnassigned(unassignedSpace,
+ 1.0f / float(colsRemaining));
+ colFrame->SetFinalISize(toAdd);
+ unassignedSpace -= toAdd;
+ --colsRemaining;
+ }
+ NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
+ }
+ }
+ for (int32_t col = 0; col < colCount; ++col) {
+ nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
+ if (!colFrame) {
+ NS_ERROR("column frames out of sync with cell map");
+ continue;
+ }
+ if (oldColISizes.ElementAt(col) != colFrame->GetFinalISize()) {
+ mTableFrame->DidResizeColumns();
+ break;
+ }
+ }
+}
diff --git a/layout/tables/FixedTableLayoutStrategy.h b/layout/tables/FixedTableLayoutStrategy.h
new file mode 100644
index 0000000000..6d3d20c4e3
--- /dev/null
+++ b/layout/tables/FixedTableLayoutStrategy.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=2:et:sw=2:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Algorithms that determine column and table isizes used for CSS2's
+ * 'table-layout: fixed'.
+ */
+
+#ifndef FixedTableLayoutStrategy_h_
+#define FixedTableLayoutStrategy_h_
+
+#include "mozilla/Attributes.h"
+#include "nsITableLayoutStrategy.h"
+
+class nsTableFrame;
+
+class FixedTableLayoutStrategy : public nsITableLayoutStrategy
+{
+public:
+ explicit FixedTableLayoutStrategy(nsTableFrame *aTableFrame);
+ virtual ~FixedTableLayoutStrategy();
+
+ // nsITableLayoutStrategy implementation
+ virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext,
+ bool aComputingSize) override;
+ virtual void MarkIntrinsicISizesDirty() override;
+ virtual void ComputeColumnISizes(const ReflowInput& aReflowInput)
+ override;
+
+private:
+ nsTableFrame *mTableFrame;
+ nscoord mMinISize;
+ nscoord mLastCalcISize;
+};
+
+#endif /* !defined(FixedTableLayoutStrategy_h_) */
diff --git a/layout/tables/SpanningCellSorter.cpp b/layout/tables/SpanningCellSorter.cpp
new file mode 100644
index 0000000000..c67d784bba
--- /dev/null
+++ b/layout/tables/SpanningCellSorter.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Code to sort cells by their colspan, used by BasicTableLayoutStrategy.
+ */
+
+#include "SpanningCellSorter.h"
+#include "nsQuickSort.h"
+#include "nsIPresShell.h"
+
+//#define DEBUG_SPANNING_CELL_SORTER
+
+SpanningCellSorter::SpanningCellSorter()
+ : mState(ADDING)
+ , mHashTable(&HashTableOps, sizeof(HashTableEntry))
+ , mSortedHashTable(nullptr)
+{
+ memset(mArray, 0, sizeof(mArray));
+}
+
+SpanningCellSorter::~SpanningCellSorter()
+{
+ delete [] mSortedHashTable;
+}
+
+/* static */ const PLDHashTableOps
+SpanningCellSorter::HashTableOps = {
+ HashTableHashKey,
+ HashTableMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+/* static */ PLDHashNumber
+SpanningCellSorter::HashTableHashKey(const void *key)
+{
+ return NS_PTR_TO_INT32(key);
+}
+
+/* static */ bool
+SpanningCellSorter::HashTableMatchEntry(const PLDHashEntryHdr *hdr,
+ const void *key)
+{
+ const HashTableEntry *entry = static_cast<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;
+}
+
+/* static */ int
+SpanningCellSorter::SortArray(const void *a, const void *b, void *closure)
+{
+ int32_t spanA = (*static_cast<HashTableEntry*const*>(a))->mColSpan;
+ int32_t spanB = (*static_cast<HashTableEntry*const*>(b))->mColSpan;
+
+ if (spanA < spanB)
+ return -1;
+ if (spanA == spanB)
+ return 0;
+ return 1;
+}
+
+SpanningCellSorter::Item*
+SpanningCellSorter::GetNext(int32_t *aColSpan)
+{
+ NS_ASSERTION(mState != DONE, "done enumerating, stop calling");
+
+ switch (mState) {
+ case ADDING:
+ /* prepare to enumerate the array */
+ mState = ENUMERATING_ARRAY;
+ mEnumerationIndex = 0;
+ MOZ_FALLTHROUGH;
+ case ENUMERATING_ARRAY:
+ while (mEnumerationIndex < ARRAY_SIZE && !mArray[mEnumerationIndex])
+ ++mEnumerationIndex;
+ if (mEnumerationIndex < ARRAY_SIZE) {
+ Item *result = mArray[mEnumerationIndex];
+ *aColSpan = IndexToSpan(mEnumerationIndex);
+ NS_ASSERTION(result, "logic error");
+#ifdef DEBUG_SPANNING_CELL_SORTER
+ printf("SpanningCellSorter[%p]:"
+ " returning list for colspan=%d from array\n",
+ static_cast<void*>(this), *aColSpan);
+#endif
+ ++mEnumerationIndex;
+ return result;
+ }
+ /* prepare to enumerate the hash */
+ mState = ENUMERATING_HASH;
+ mEnumerationIndex = 0;
+ if (mHashTable.EntryCount() > 0) {
+ HashTableEntry **sh =
+ new HashTableEntry*[mHashTable.EntryCount()];
+ int32_t j = 0;
+ for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) {
+ sh[j++] = static_cast<HashTableEntry*>(iter.Get());
+ }
+ NS_QuickSort(sh, mHashTable.EntryCount(), sizeof(sh[0]),
+ SortArray, nullptr);
+ mSortedHashTable = sh;
+ }
+ MOZ_FALLTHROUGH;
+ case ENUMERATING_HASH:
+ if (mEnumerationIndex < mHashTable.EntryCount()) {
+ Item *result = mSortedHashTable[mEnumerationIndex]->mItems;
+ *aColSpan = mSortedHashTable[mEnumerationIndex]->mColSpan;
+ NS_ASSERTION(result, "holes in hash table");
+#ifdef DEBUG_SPANNING_CELL_SORTER
+ printf("SpanningCellSorter[%p]:"
+ " returning list for colspan=%d from hash\n",
+ static_cast<void*>(this), *aColSpan);
+#endif
+ ++mEnumerationIndex;
+ return result;
+ }
+ mState = DONE;
+ MOZ_FALLTHROUGH;
+ case DONE:
+ ;
+ }
+ return nullptr;
+}
diff --git a/layout/tables/SpanningCellSorter.h b/layout/tables/SpanningCellSorter.h
new file mode 100644
index 0000000000..30139c0e38
--- /dev/null
+++ b/layout/tables/SpanningCellSorter.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SpanningCellSorter_h
+#define SpanningCellSorter_h
+
+/*
+ * Code to sort cells by their colspan, used by BasicTableLayoutStrategy.
+ */
+
+#include "PLDHashTable.h"
+#include "nsDebug.h"
+#include "StackArena.h"
+
+/**
+ * The SpanningCellSorter is responsible for accumulating lists of cells
+ * with colspans so that those cells can later be enumerated, sorted
+ * from lowest number of columns spanned to highest. It does not use a
+ * stable sort (in fact, it currently reverses).
+ */
+class MOZ_STACK_CLASS SpanningCellSorter {
+public:
+ SpanningCellSorter();
+ ~SpanningCellSorter();
+
+ struct Item {
+ int32_t row, col;
+ Item *next;
+ };
+
+ /**
+ * Add a cell to the sorter. Returns false on out of memory.
+ * aColSpan is the number of columns spanned, and aRow/aCol are the
+ * position of the cell in the table (for GetCellInfoAt).
+ */
+ bool AddCell(int32_t aColSpan, int32_t aRow, int32_t aCol);
+
+ /**
+ * Get the next *list* of cells. Each list contains all the cells
+ * for a colspan value, and the lists are given in order from lowest
+ * to highest colspan. The colspan value is filled in to *aColSpan.
+ */
+ Item* GetNext(int32_t *aColSpan);
+private:
+
+ enum State { ADDING, ENUMERATING_ARRAY, ENUMERATING_HASH, DONE };
+ State mState;
+
+ // store small colspans in an array for fast sorting and
+ // enumeration, and large colspans in a hash table
+
+ enum { ARRAY_BASE = 2 };
+ enum { ARRAY_SIZE = 8 };
+ Item *mArray[ARRAY_SIZE];
+ int32_t SpanToIndex(int32_t aSpan) { return aSpan - ARRAY_BASE; }
+ int32_t IndexToSpan(int32_t aIndex) { return aIndex + ARRAY_BASE; }
+ bool UseArrayForSpan(int32_t aSpan) {
+ NS_ASSERTION(SpanToIndex(aSpan) >= 0, "cell without colspan");
+ return SpanToIndex(aSpan) < ARRAY_SIZE;
+ }
+
+ PLDHashTable mHashTable;
+ struct HashTableEntry : public PLDHashEntryHdr {
+ int32_t mColSpan;
+ Item *mItems;
+ };
+
+ static const PLDHashTableOps HashTableOps;
+
+ static PLDHashNumber HashTableHashKey(const void *key);
+ static bool
+ HashTableMatchEntry(const PLDHashEntryHdr *hdr, const void *key);
+
+ static int SortArray(const void *a, const void *b, void *closure);
+
+ /* state used only during enumeration */
+ uint32_t mEnumerationIndex; // into mArray or mSortedHashTable
+ HashTableEntry **mSortedHashTable;
+
+ /*
+ * operator new is forbidden since we use the pres shell's stack
+ * memory, which much be pushed and popped at points matching a
+ * push/pop on the C++ stack.
+ */
+ void* operator new(size_t sz) CPP_THROW_NEW { return nullptr; }
+};
+
+#endif
diff --git a/layout/tables/TableArea.h b/layout/tables/TableArea.h
new file mode 100644
index 0000000000..a94eea6e6c
--- /dev/null
+++ b/layout/tables/TableArea.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_TableArea_h_
+#define mozilla_TableArea_h_
+
+#include "nsRect.h"
+
+namespace mozilla {
+
+struct TableArea
+{
+ TableArea() : mRect() { }
+ TableArea(int32_t aStartCol, int32_t aStartRow,
+ int32_t aColCount, int32_t aRowCount)
+ : mRect(aStartCol, aStartRow, aColCount, aRowCount) { }
+
+ int32_t& StartCol() { return mRect.x; }
+ int32_t& StartRow() { return mRect.y; }
+ int32_t& ColCount() { return mRect.width; }
+ int32_t& RowCount() { return mRect.height; }
+
+ int32_t StartCol() const { return mRect.x; }
+ int32_t StartRow() const { return mRect.y; }
+ int32_t ColCount() const { return mRect.width; }
+ int32_t RowCount() const { return mRect.height; }
+ int32_t EndCol() const { return mRect.XMost(); }
+ int32_t EndRow() const { return mRect.YMost(); }
+
+ void UnionArea(const TableArea& aArea1, const TableArea& aArea2)
+ { mRect.UnionRect(aArea1.mRect, aArea2.mRect); }
+
+private:
+ nsIntRect mRect;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TableArea_h_
diff --git a/layout/tables/celldata.h b/layout/tables/celldata.h
new file mode 100644
index 0000000000..b744b51752
--- /dev/null
+++ b/layout/tables/celldata.h
@@ -0,0 +1,459 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef CellData_h__
+#define CellData_h__
+
+#include "nsISupports.h"
+#include "nsCoord.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/WritingModes.h"
+#include <stdint.h>
+
+class nsTableCellFrame;
+class nsCellMap;
+class BCCellData;
+
+
+#define MAX_ROWSPAN 65534 // the cellmap can not handle more.
+#define MAX_COLSPAN 1000 // limit as IE and opera do. If this ever changes,
+ // change COL_SPAN_OFFSET/COL_SPAN_SHIFT accordingly.
+
+/**
+ * Data stored by nsCellMap to rationalize rowspan and colspan cells.
+ */
+class CellData
+{
+public:
+ /** Initialize the mOrigCell pointer
+ * @param aOrigCell the table cell frame which will be stored in mOrigCell.
+ */
+ void Init(nsTableCellFrame* aCellFrame);
+
+ /** does a cell originate from here
+ * @return is true if a cell corresponds to this cellmap entry
+ */
+ bool IsOrig() const;
+
+ /** is the celldata valid
+ * @return is true if no cell originates and the cell is not spanned by
+ * a row- or colspan. mBits are 0 in this case and mOrigCell is
+ * nullptr
+ */
+ bool IsDead() const;
+
+ /** is the entry spanned by row- or a colspan
+ * @return is true if the entry is spanned by a row- or colspan
+ */
+ bool IsSpan() const;
+
+ /** is the entry spanned by rowspan
+ * @return is true if the entry is spanned by a rowspan
+ */
+ bool IsRowSpan() const;
+
+ /** is the entry spanned by a zero rowspan
+ * zero rowspans span all cells starting from the originating cell down to
+ * the end of the rowgroup or a cell originating in the same column
+ * @return is true if the entry is spanned by a zero rowspan
+ */
+ bool IsZeroRowSpan() const;
+
+ /** mark the current entry as spanned by a zero rowspan
+ * @param aIsZero if true mark the entry as covered by a zero rowspan
+ */
+ void SetZeroRowSpan(bool aIsZero);
+
+ /** get the distance from the current entry to the corresponding origin of the rowspan
+ * @return containing the distance in the column to the originating cell
+ */
+ uint32_t GetRowSpanOffset() const;
+
+ /** set the distance from the current entry to the corresponding origin of the rowspan
+ * @param the distance in the column to the originating cell
+ */
+ void SetRowSpanOffset(uint32_t aSpan);
+
+ /** is the entry spanned by colspan
+ * @return is true if the entry is spanned by a colspan
+ */
+ bool IsColSpan() const;
+
+ /** get the distance from the current entry to the corresponding origin of the colspan
+ * @return containing the distance in the row to the originating cell
+ */
+ uint32_t GetColSpanOffset() const;
+
+ /** set the distance from the current entry to the corresponding origin of the colspan
+ * @param the distance in the column to the originating cell
+ */
+ void SetColSpanOffset(uint32_t aSpan);
+
+ /** is the entry spanned by a row- and a colspan
+ * @return is true if the entry is spanned by a row- and a colspan
+ */
+ bool IsOverlap() const;
+
+ /** mark the current entry as spanned by a row- and a colspan
+ * @param aOverlap if true mark the entry as covered by a row- and a colspan
+ */
+ void SetOverlap(bool aOverlap);
+
+ /** get the table cell frame for this entry
+ * @return a pointer to the cellframe, this will be nullptr when the entry
+ * is only a spanned entry
+ */
+ nsTableCellFrame* GetCellFrame() const;
+
+private:
+ friend class nsCellMap;
+ friend class BCCellData;
+
+ /** constructor.
+ * @param aOrigCell the table cell frame which will be stored in mOrigCell.
+ */
+ explicit CellData(nsTableCellFrame* aOrigCell); // implemented in nsCellMap.cpp
+
+ /** destructor */
+ ~CellData(); // implemented in nsCellMap.cpp
+
+protected:
+
+ // this union relies on the assumption that an object (not primitive type) does
+ // not start on an odd bit boundary. If mSpan is 0 then mOrigCell is in effect
+ // and the data does not represent a span. If mSpan is 1, then mBits is in
+ // effect and the data represents a span.
+ // mBits must match the size of mOrigCell on both 32- and 64-bit platforms.
+ union {
+ nsTableCellFrame* mOrigCell;
+ uintptr_t mBits;
+ };
+};
+
+// Border Collapsing Cell Data
+enum BCBorderOwner
+{
+ eTableOwner = 0,
+ eColGroupOwner = 1,
+ eAjaColGroupOwner = 2, // col group to the left
+ eColOwner = 3,
+ eAjaColOwner = 4, // col to the left
+ eRowGroupOwner = 5,
+ eAjaRowGroupOwner = 6, // row group above
+ eRowOwner = 7,
+ eAjaRowOwner = 8, // row above
+ eCellOwner = 9,
+ eAjaCellOwner = 10 // cell to the top or to the left
+};
+
+typedef uint16_t BCPixelSize;
+
+// These are the max sizes that are stored. If they are exceeded, then the max is stored and
+// the actual value is computed when needed.
+#define MAX_BORDER_WIDTH nscoord((1u << (sizeof(BCPixelSize) * 8)) - 1)
+
+// The half of border on inline/block-axis start side
+static inline BCPixelSize
+BC_BORDER_START_HALF(BCPixelSize px) { return px - px / 2; }
+// The half of border on inline/block-axis end side
+static inline BCPixelSize
+BC_BORDER_END_HALF(BCPixelSize px) { return px / 2; }
+
+static inline nscoord
+BC_BORDER_START_HALF_COORD(int32_t p2t, BCPixelSize px)
+ { return BC_BORDER_START_HALF(px) * p2t; }
+static inline nscoord
+BC_BORDER_END_HALF_COORD(int32_t p2t, BCPixelSize px)
+ { return BC_BORDER_END_HALF(px) * p2t; }
+
+// BCData stores the bstart and istart border info and the corner connecting the two.
+class BCData
+{
+public:
+ BCData();
+
+ ~BCData();
+
+ nscoord GetIStartEdge(BCBorderOwner& aOwner,
+ bool& aStart) const;
+
+ void SetIStartEdge(BCBorderOwner aOwner,
+ nscoord aSize,
+ bool aStart);
+
+ nscoord GetBStartEdge(BCBorderOwner& aOwner,
+ bool& aStart) const;
+
+ void SetBStartEdge(BCBorderOwner aOwner,
+ nscoord aSize,
+ bool aStart);
+
+ BCPixelSize GetCorner(mozilla::LogicalSide& aCornerOwner,
+ bool& aBevel) const;
+
+ void SetCorner(BCPixelSize aSubSize,
+ mozilla::LogicalSide aOwner,
+ bool aBevel);
+
+ bool IsIStartStart() const;
+
+ void SetIStartStart(bool aValue);
+
+ bool IsBStartStart() const;
+
+ void SetBStartStart(bool aValue);
+
+
+protected:
+ BCPixelSize mIStartSize; // size in pixels of iStart border
+ BCPixelSize mBStartSize; // size in pixels of bStart border
+ BCPixelSize mCornerSubSize; // size of the largest border not in the
+ // dominant plane (for example, if corner is
+ // owned by the segment to its bStart or bEnd,
+ // then the size is the max of the border
+ // sizes of the segments to its iStart or iEnd.
+ unsigned mIStartOwner: 4; // owner of iStart border
+ unsigned mBStartOwner: 4; // owner of bStart border
+ unsigned mIStartStart: 1; // set if this is the start of a block-dir border segment
+ unsigned mBStartStart: 1; // set if this is the start of an inline-dir border segment
+ unsigned mCornerSide: 2; // LogicalSide of the owner of the bStart-iStart corner relative to the corner
+ unsigned mCornerBevel: 1; // is the corner beveled (only two segments, perpendicular, not dashed or dotted).
+};
+
+// BCCellData entries replace CellData entries in the cell map if the border collapsing model is in
+// effect. BCData for a row and col entry contains the left and top borders of cell at that row and
+// col and the corner connecting the two. The right borders of the cells in the last col and the bottom
+// borders of the last row are stored in separate BCData entries in the cell map.
+class BCCellData : public CellData
+{
+public:
+ explicit BCCellData(nsTableCellFrame* aOrigCell);
+ ~BCCellData();
+
+ BCData mData;
+};
+
+
+// The layout of a celldata is as follows. The top 10 bits are the colspan
+// offset (which is enough to represent our allowed values 1-1000 for colspan).
+// Then there are two bits of flags.
+// XXXmats Then one unused bit that we should decide how to use in bug 862624.
+// Then 16 bits of rowspan offset (which
+// lets us represent numbers up to 65535. Then another 3 bits of flags.
+
+// num bits to shift right to get right aligned col span
+#define COL_SPAN_SHIFT 22
+// num bits to shift right to get right aligned row span
+#define ROW_SPAN_SHIFT 3
+
+// the col offset to the data containing the original cell.
+#define COL_SPAN_OFFSET (0x3FF << COL_SPAN_SHIFT)
+// the row offset to the data containing the original cell
+#define ROW_SPAN_OFFSET (0xFFFF << ROW_SPAN_SHIFT)
+
+// And the flags
+#define SPAN 0x00000001 // there a row or col span
+#define ROW_SPAN 0x00000002 // there is a row span
+#define ROW_SPAN_0 0x00000004 // the row span is 0
+#define COL_SPAN (1 << (COL_SPAN_SHIFT - 2)) // there is a col span
+#define OVERLAP (1 << (COL_SPAN_SHIFT - 1)) // there is a row span and
+ // col span but not by
+ // same cell
+
+inline nsTableCellFrame* CellData::GetCellFrame() const
+{
+ if (SPAN != (SPAN & mBits)) {
+ return mOrigCell;
+ }
+ return nullptr;
+}
+
+inline void CellData::Init(nsTableCellFrame* aCellFrame)
+{
+ mOrigCell = aCellFrame;
+}
+
+inline bool CellData::IsOrig() const
+{
+ return ((nullptr != mOrigCell) && (SPAN != (SPAN & mBits)));
+}
+
+inline bool CellData::IsDead() const
+{
+ return (0 == mBits);
+}
+
+inline bool CellData::IsSpan() const
+{
+ return (SPAN == (SPAN & mBits));
+}
+
+inline bool CellData::IsRowSpan() const
+{
+ return (SPAN == (SPAN & mBits)) &&
+ (ROW_SPAN == (ROW_SPAN & mBits));
+}
+
+inline bool CellData::IsZeroRowSpan() const
+{
+ return (SPAN == (SPAN & mBits)) &&
+ (ROW_SPAN == (ROW_SPAN & mBits)) &&
+ (ROW_SPAN_0 == (ROW_SPAN_0 & mBits));
+}
+
+inline void CellData::SetZeroRowSpan(bool aIsZeroSpan)
+{
+ if (SPAN == (SPAN & mBits)) {
+ if (aIsZeroSpan) {
+ mBits |= ROW_SPAN_0;
+ }
+ else {
+ mBits &= ~ROW_SPAN_0;
+ }
+ }
+}
+
+inline uint32_t CellData::GetRowSpanOffset() const
+{
+ if ((SPAN == (SPAN & mBits)) && ((ROW_SPAN == (ROW_SPAN & mBits)))) {
+ return (uint32_t)((mBits & ROW_SPAN_OFFSET) >> ROW_SPAN_SHIFT);
+ }
+ return 0;
+}
+
+inline void CellData::SetRowSpanOffset(uint32_t aSpan)
+{
+ mBits &= ~ROW_SPAN_OFFSET;
+ mBits |= (aSpan << ROW_SPAN_SHIFT);
+ mBits |= SPAN;
+ mBits |= ROW_SPAN;
+}
+
+inline bool CellData::IsColSpan() const
+{
+ return (SPAN == (SPAN & mBits)) &&
+ (COL_SPAN == (COL_SPAN & mBits));
+}
+
+inline uint32_t CellData::GetColSpanOffset() const
+{
+ if ((SPAN == (SPAN & mBits)) && ((COL_SPAN == (COL_SPAN & mBits)))) {
+ return (uint32_t)((mBits & COL_SPAN_OFFSET) >> COL_SPAN_SHIFT);
+ }
+ return 0;
+}
+
+inline void CellData::SetColSpanOffset(uint32_t aSpan)
+{
+ mBits &= ~COL_SPAN_OFFSET;
+ mBits |= (aSpan << COL_SPAN_SHIFT);
+
+ mBits |= SPAN;
+ mBits |= COL_SPAN;
+}
+
+inline bool CellData::IsOverlap() const
+{
+ return (SPAN == (SPAN & mBits)) && (OVERLAP == (OVERLAP & mBits));
+}
+
+inline void CellData::SetOverlap(bool aOverlap)
+{
+ if (SPAN == (SPAN & mBits)) {
+ if (aOverlap) {
+ mBits |= OVERLAP;
+ }
+ else {
+ mBits &= ~OVERLAP;
+ }
+ }
+}
+
+inline BCData::BCData()
+{
+ mIStartOwner = mBStartOwner = eCellOwner;
+ mIStartStart = mBStartStart = 1;
+ mIStartSize = mCornerSubSize = mBStartSize = 0;
+ mCornerSide = mozilla::eLogicalSideBStart;
+ mCornerBevel = false;
+}
+
+inline BCData::~BCData()
+{
+}
+
+inline nscoord BCData::GetIStartEdge(BCBorderOwner& aOwner,
+ bool& aStart) const
+{
+ aOwner = (BCBorderOwner)mIStartOwner;
+ aStart = (bool)mIStartStart;
+
+ return (nscoord)mIStartSize;
+}
+
+inline void BCData::SetIStartEdge(BCBorderOwner aOwner,
+ nscoord aSize,
+ bool aStart)
+{
+ mIStartOwner = aOwner;
+ mIStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize;
+ mIStartStart = aStart;
+}
+
+inline nscoord BCData::GetBStartEdge(BCBorderOwner& aOwner,
+ bool& aStart) const
+{
+ aOwner = (BCBorderOwner)mBStartOwner;
+ aStart = (bool)mBStartStart;
+
+ return (nscoord)mBStartSize;
+}
+
+inline void BCData::SetBStartEdge(BCBorderOwner aOwner,
+ nscoord aSize,
+ bool aStart)
+{
+ mBStartOwner = aOwner;
+ mBStartSize = (aSize > MAX_BORDER_WIDTH) ? MAX_BORDER_WIDTH : aSize;
+ mBStartStart = aStart;
+}
+
+inline BCPixelSize BCData::GetCorner(mozilla::LogicalSide& aOwnerSide,
+ bool& aBevel) const
+{
+ aOwnerSide = mozilla::LogicalSide(mCornerSide);
+ aBevel = (bool)mCornerBevel;
+ return mCornerSubSize;
+}
+
+inline void BCData::SetCorner(BCPixelSize aSubSize,
+ mozilla::LogicalSide aOwnerSide,
+ bool aBevel)
+{
+ mCornerSubSize = aSubSize;
+ mCornerSide = aOwnerSide;
+ mCornerBevel = aBevel;
+}
+
+inline bool BCData::IsIStartStart() const
+{
+ return (bool)mIStartStart;
+}
+
+inline void BCData::SetIStartStart(bool aValue)
+{
+ mIStartStart = aValue;
+}
+
+inline bool BCData::IsBStartStart() const
+{
+ return (bool)mBStartStart;
+}
+
+inline void BCData::SetBStartStart(bool aValue)
+{
+ mBStartStart = aValue;
+}
+
+#endif
diff --git a/layout/tables/crashtests/1027611-1.html b/layout/tables/crashtests/1027611-1.html
new file mode 100644
index 0000000000..6083a63356
--- /dev/null
+++ b/layout/tables/crashtests/1027611-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<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/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/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/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/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/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..d8f8041204
--- /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.parentNode.removeChild(s1);
+ document.documentElement.removeAttribute('class');
+ }
+ document.addEventListener("MozReftestInvalidate", doTest, false);
+ </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..4d2155f169
--- /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.parentNode.removeChild(four);
+}
+
+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, 0);
+window.addEventListener("click", doe, 0);
+</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..ebb8df7117
--- /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.parentNode.removeChild(two);
+}
+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, 0);
+window.addEventListener("click", doe, 0);
+</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.xul b/layout/tables/crashtests/322779-1.xul
new file mode 100644
index 0000000000..8af18adcc9
--- /dev/null
+++ b/layout/tables/crashtests/322779-1.xul
@@ -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/323489-1.html b/layout/tables/crashtests/323489-1.html
new file mode 100644
index 0000000000..f092215993
--- /dev/null
+++ b/layout/tables/crashtests/323489-1.html
@@ -0,0 +1,22 @@
+<html>
+
+<head>
+
+<style type="text/css">
+body {
+ overflow: auto;
+ display: -moz-stack;
+}
+</style>
+
+
+</head>
+
+<body>
+
+
+<table><tbody><tr><td>Cell</td></tr></tbody></table>
+
+</body>
+
+</html> \ 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/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..6aab87c8e2
--- /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; -moz-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..d0e84e3dc7
--- /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, false);
+
+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..5366e263ae
--- /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.parentNode.removeChild(doomed);
+}
+
+</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..54e20dbf60
--- /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.parentNode.removeChild(doomed);
+
+ 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.parentNode.removeChild(colgroup1);
+
+ 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..139e7898ed
--- /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.parentNode.removeChild(tr);
+}
+</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..0dbfac857f
--- /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.parentNode.removeChild(tdB);
+ trA.parentNode.removeChild(trA);
+ trB.parentNode.removeChild(trB);
+ 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..4fe2521080
--- /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-print"><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..45edb6311b
--- /dev/null
+++ b/layout/tables/crashtests/362275.html
@@ -0,0 +1,14 @@
+<html class="reftest-print"><head>
+<title>Testcase bug 362275 - Hang with testcase on print preview, using -moz-column-count and table related stuff</title>
+</head>
+<body>
+This page should not hang Mozilla on print preview
+<div style="-moz-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..7fbb23faef
--- /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 -moz-column-count and table related stuff</title>
+</head>
+<body>
+This page should not hang Mozilla on print preview
+<div style="-moz-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..f2b8cdca79
--- /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, false);
+</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..d6ca97e181
--- /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.parentNode.removeChild(x);
+ b.parentNode.removeChild(b);
+
+ 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..0c19fdaa54
--- /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.parentNode.removeChild(D);
+ var A = document.getElementById('A');
+ A.parentNode.removeChild(A);
+ 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..bce7e5e599
--- /dev/null
+++ b/layout/tables/crashtests/373400-1.html
@@ -0,0 +1,34 @@
+<html class="reftest-print"><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..5e224d8326
--- /dev/null
+++ b/layout/tables/crashtests/373400-2.html
@@ -0,0 +1,2109 @@
+<html class="reftest-print"><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..ad32a18d09
--- /dev/null
+++ b/layout/tables/crashtests/373400-3.html
@@ -0,0 +1,64 @@
+<html class="reftest-print">
+<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..ff241691bb
--- /dev/null
+++ b/layout/tables/crashtests/391898-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+</head>
+
+<body style="-moz-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..760d7e5327
--- /dev/null
+++ b/layout/tables/crashtests/408753-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body style="-moz-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..596422d38f
--- /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="-moz-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..286bb4f7aa
--- /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: -moz-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..d5ec8101a1
--- /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.MozColumnWidth = "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..1063730e32
--- /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.removeChild(r.firstChild); }
+ 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, false);
+ setTimeout(boom, 400);
+ }
+
+ window.addEventListener("load", ol, false);
+
+]]></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..2bdaee93d1
--- /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, false);
+ setTimeout(boom, 400);
+ }
+
+ window.addEventListener("load", ol, false);
+
+]]></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..2d73a1e718
--- /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, false);
+ setTimeout(boom, 400);
+ }
+
+ window.addEventListener("load", ol, false);
+
+]]>
+</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..84df33eee3
--- /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').parentNode.removeChild(document.getElementById('a'));
+ 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/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..c76f3e0504
--- /dev/null
+++ b/layout/tables/crashtests/563009-1.html
@@ -0,0 +1,42 @@
+<html class="reftest-print">
+<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..daa0c8fa51
--- /dev/null
+++ b/layout/tables/crashtests/563009-2.html
@@ -0,0 +1,40 @@
+<html class="reftest-print">
+<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..8db6203ae7
--- /dev/null
+++ b/layout/tables/crashtests/563009-3.html
@@ -0,0 +1,34 @@
+<html class="reftest-print">
+<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..aa97e68159
--- /dev/null
+++ b/layout/tables/crashtests/576890-1.html
@@ -0,0 +1,8 @@
+<html class="reftest-print">
+<head>
+</head>
+<body style=" -moz-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..5ffea0d2fb
--- /dev/null
+++ b/layout/tables/crashtests/576890-2.html
@@ -0,0 +1,8 @@
+<html class="reftest-print">
+<head>
+</head>
+<body style=" -moz-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..b5717a96d5
--- /dev/null
+++ b/layout/tables/crashtests/576890-3.html
@@ -0,0 +1,8 @@
+<html class="reftest-print">
+<head>
+</head>
+<body style=" -moz-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..767a43650f
--- /dev/null
+++ b/layout/tables/crashtests/595758-1.xhtml
@@ -0,0 +1,13 @@
+<html class="reftest-print" 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..644266ef83
--- /dev/null
+++ b/layout/tables/crashtests/595758-2.xhtml
@@ -0,0 +1,12 @@
+<html class="reftest-print" 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..654b47dc47
--- /dev/null
+++ b/layout/tables/crashtests/678447-1.html
@@ -0,0 +1,10 @@
+<html class="reftest-print">
+<head>
+</head>
+<body style=" -moz-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..696fa731e9
--- /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-print">
+<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..c0f85ec80c
--- /dev/null
+++ b/layout/tables/crashtests/696640-1.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<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..52d64c7ada
--- /dev/null
+++ b/layout/tables/crashtests/696640-2.html
@@ -0,0 +1,486 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<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/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/crashtests.list b/layout/tables/crashtests/crashtests.list
new file mode 100644
index 0000000000..f110053f16
--- /dev/null
+++ b/layout/tables/crashtests/crashtests.list
@@ -0,0 +1,159 @@
+load 28933-1.html
+load 29157-1.html
+load 32447-1.html
+load 55789-1.html
+load 78623-1.html
+load 110523-1.html
+load 138725-1.html
+load 159359-1.html
+load 187779-1.html
+load 189751-1.html
+load 197015-1.html
+load 220536-1.html
+load 223458-1.html
+load 237421-1.html
+load 237421-2.html
+load 238909-1.html
+load 239294-1.html
+load 240854-1.html
+load 266015-1.html
+load 267418.html
+load 275625.html
+load 277062-1.html
+load 278385-1.html
+load 282175-1.html
+load 284844-1.html
+load 284844-1.html
+load 284852.html
+load 300912.html
+load 308752-1.html
+load 308752-2.html
+load 316636-1.html
+load 317876.html
+load 322779-1.xul
+load 323489-1.html
+load 323604-1.html
+load 323604-2.xhtml
+load 331344-1.html
+load 331446-1.xhtml
+load 331690-1.html
+load 339130-1.html
+load 339246-1.html
+load 339315-1.html
+load 341227-1.xhtml
+load 343087-1.html
+load 343588-1.xhtml
+load 344000-1.html
+load 347367.html
+load 347506-1.xhtml
+load 347506-2.xhtml
+load 347725-1.xhtml
+load 348977-1.xhtml
+load 350524-1.xhtml
+load 351326-1.xhtml
+load 351327-1.xhtml
+load 351328-1.xhtml
+load 351628-1.xhtml
+load 358679-1.xhtml
+load 358871-1.xhtml
+load 362275.html
+load 364512-1.html
+load 366556-1.xhtml
+load 367673-1.xhtml
+load 367749.html
+load 367755.xhtml
+load 368013.html
+load 368166-1.xhtml
+load 370360-1.html
+load 370710.xhtml
+load 370713-1.html
+load 370876-1.html
+load 370897-1.html
+load 371290.html
+load 373400-1.html
+load 373400-2.html
+load 373400-3.html
+load 373611-1.html
+load 373946-1.html
+load 374356-1.html
+load 374819-1.html
+load 374819-2.html
+load 375058-1.xhtml
+load 378240-1.html
+load 379687-1.html
+load 380200-1.xhtml
+load 385132-1.xhtml
+load 385132-2.html
+load 387051-1.html
+load 388700-1.html
+load 391898-1.html
+load 391901-1.html
+load 392132-1.xhtml
+load 397448-1.html
+load 398157-1.xhtml
+load 399209-1.xhtml
+load 403249-1.html
+load 403579-1.html
+load 404301-1.xhtml
+load 408753-1.xhtml
+load 410426-1.html
+load 410428-1.xhtml
+load 411582.xhtml
+load 413091.xhtml
+load 413180-1.html
+load 416845-1.xhtml
+load 416845-2.xhtml
+load 416845-3.html
+load 420242-1.xhtml
+asserts(8) load 420654-1.xhtml # bug 458238, bug 436123, bug 457397
+load 423514-1.xhtml
+load 430374.html
+load 444431-1.html
+load 444702-1.html
+load 448988-1.xhtml
+load 450311-1.html
+load 451170.html
+load 451355-1.html
+load 456041.html
+load 457115.html
+load 460637-1.xhtml
+load 460637-2.xhtml
+load 460637-3.xhtml
+load 462849.xhtml
+load 467141-1.html
+load 488388-1.html
+load 501870-1.html
+load 509562-1.xhtml
+load 512749-1.html
+load 513732-1.html
+load 533380-1.xhtml
+load 534716-1.html
+load 563009-1.html
+load 563009-2.html
+load 563009-3.html
+load 573354-1.xhtml
+load 576890-1.html
+load 576890-2.html
+load 576890-3.html
+load 580481-1.xhtml
+asserts(1) load 595758-1.xhtml # Bug 714667
+load 595758-2.xhtml
+load 678447-1.html
+load 691824-1.xhtml
+load 695430-1.html
+load 696640-1.html
+load 696640-2.html
+load 705996-1.html
+load 705996-2.html
+load 707622-1.html
+load 710098-1.html
+load 711864-1.html
+asserts-if(gtkWidget&&browserIsRemote,5) load 759249-1.html # Bug 1195474
+load 759249-2.html
+load 814713.html
+load 1027611-1.html
+load 1031934.html
+load 1183896.html
+load 1223282.html
+load 1223232.html
+load 1243623-1.html
diff --git a/layout/tables/moz.build b/layout/tables/moz.build
new file mode 100644
index 0000000000..b777763202
--- /dev/null
+++ b/layout/tables/moz.build
@@ -0,0 +1,46 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'Layout: Tables')
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+
+EXPORTS += [
+ 'nsITableCellLayout.h',
+]
+
+UNIFIED_SOURCES += [
+ 'BasicTableLayoutStrategy.cpp',
+ 'FixedTableLayoutStrategy.cpp',
+ 'nsCellMap.cpp',
+ 'nsTableCellFrame.cpp',
+ 'nsTableColFrame.cpp',
+ 'nsTableColGroupFrame.cpp',
+ 'nsTableFrame.cpp',
+ 'nsTablePainter.cpp',
+ 'nsTableRowFrame.cpp',
+ 'nsTableRowGroupFrame.cpp',
+ 'nsTableWrapperFrame.cpp',
+ 'SpanningCellSorter.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '../../intl/unicharutil/util',
+ '../base',
+ '../generic',
+ '../style',
+ '../xul',
+ '/dom/base',
+ '/dom/html',
+]
+
+DEFINES['DEBUG_TABLE_STRATEGY_off'] = True
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/layout/tables/nsCellMap.cpp b/layout/tables/nsCellMap.cpp
new file mode 100644
index 0000000000..bdd12cf70b
--- /dev/null
+++ b/layout/tables/nsCellMap.cpp
@@ -0,0 +1,2716 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTArray.h"
+#include "nsCellMap.h"
+#include "nsTableFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include <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 nsCellMap::CellDataArray * sEmptyRow;
+
+// CellData
+
+CellData::CellData(nsTableCellFrame* aOrigCell)
+{
+ MOZ_COUNT_CTOR(CellData);
+ static_assert(sizeof(mOrigCell) == sizeof(mBits),
+ "mOrigCell and mBits must be the same size");
+ mOrigCell = aOrigCell;
+}
+
+CellData::~CellData()
+{
+ MOZ_COUNT_DTOR(CellData);
+}
+
+BCCellData::BCCellData(nsTableCellFrame* aOrigCell)
+:CellData(aOrigCell)
+{
+ MOZ_COUNT_CTOR(BCCellData);
+}
+
+BCCellData::~BCCellData()
+{
+ MOZ_COUNT_DTOR(BCCellData);
+}
+
+// nsTableCellMap
+
+nsTableCellMap::nsTableCellMap(nsTableFrame& aTableFrame,
+ bool aBorderCollapse)
+:mTableFrame(aTableFrame), mFirstMap(nullptr), mBCInfo(nullptr)
+{
+ MOZ_COUNT_CTOR(nsTableCellMap);
+
+ nsTableFrame::RowGroupArray orderedRowGroups;
+ aTableFrame.OrderRowGroups(orderedRowGroups);
+
+ nsTableRowGroupFrame* prior = nullptr;
+ for (uint32_t rgX = 0; rgX < orderedRowGroups.Length(); rgX++) {
+ nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgX];
+ InsertGroupCellMap(rgFrame, prior);
+ prior = rgFrame;
+ }
+ if (aBorderCollapse) {
+ mBCInfo = new BCInfo();
+ }
+}
+
+nsTableCellMap::~nsTableCellMap()
+{
+ MOZ_COUNT_DTOR(nsTableCellMap);
+
+ nsCellMap* cellMap = mFirstMap;
+ while (cellMap) {
+ nsCellMap* next = cellMap->GetNextSibling();
+ delete cellMap;
+ cellMap = next;
+ }
+
+ if (mBCInfo) {
+ DeleteIEndBEndBorders();
+ delete mBCInfo;
+ }
+}
+
+// Get the bcData holding the border segments of the iEnd edge of the table
+BCData*
+nsTableCellMap::GetIEndMostBorder(int32_t aRowIndex)
+{
+ if (!mBCInfo) ABORT1(nullptr);
+
+ int32_t numRows = mBCInfo->mIEndBorders.Length();
+ if (aRowIndex < numRows) {
+ return &mBCInfo->mIEndBorders.ElementAt(aRowIndex);
+ }
+
+ mBCInfo->mIEndBorders.SetLength(aRowIndex+1);
+ return &mBCInfo->mIEndBorders.ElementAt(aRowIndex);
+}
+
+// Get the bcData holding the border segments of the bEnd edge of the table
+BCData*
+nsTableCellMap::GetBEndMostBorder(int32_t aColIndex)
+{
+ if (!mBCInfo) ABORT1(nullptr);
+
+ int32_t numCols = mBCInfo->mBEndBorders.Length();
+ if (aColIndex < numCols) {
+ return &mBCInfo->mBEndBorders.ElementAt(aColIndex);
+ }
+
+ mBCInfo->mBEndBorders.SetLength(aColIndex+1);
+ return &mBCInfo->mBEndBorders.ElementAt(aColIndex);
+}
+
+// delete the borders corresponding to the iEnd and bEnd edges of the table
+void
+nsTableCellMap::DeleteIEndBEndBorders()
+{
+ if (mBCInfo) {
+ mBCInfo->mBEndBorders.Clear();
+ mBCInfo->mIEndBorders.Clear();
+ }
+}
+
+void
+nsTableCellMap::InsertGroupCellMap(nsCellMap* aPrevMap,
+ nsCellMap& aNewMap)
+{
+ nsCellMap* next;
+ if (aPrevMap) {
+ next = aPrevMap->GetNextSibling();
+ aPrevMap->SetNextSibling(&aNewMap);
+ }
+ else {
+ next = mFirstMap;
+ mFirstMap = &aNewMap;
+ }
+ aNewMap.SetNextSibling(next);
+}
+
+void nsTableCellMap::InsertGroupCellMap(nsTableRowGroupFrame* aNewGroup,
+ nsTableRowGroupFrame*& aPrevGroup)
+{
+ nsCellMap* newMap = new nsCellMap(aNewGroup, mBCInfo != nullptr);
+ nsCellMap* prevMap = nullptr;
+ nsCellMap* lastMap = mFirstMap;
+ if (aPrevGroup) {
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ lastMap = map;
+ if (map->GetRowGroup() == aPrevGroup) {
+ prevMap = map;
+ break;
+ }
+ map = map->GetNextSibling();
+ }
+ }
+ if (!prevMap) {
+ if (aPrevGroup) {
+ prevMap = lastMap;
+ aPrevGroup = (prevMap) ? prevMap->GetRowGroup() : nullptr;
+ }
+ else {
+ aPrevGroup = nullptr;
+ }
+ }
+ InsertGroupCellMap(prevMap, *newMap);
+}
+
+void nsTableCellMap::RemoveGroupCellMap(nsTableRowGroupFrame* aGroup)
+{
+ nsCellMap* map = mFirstMap;
+ nsCellMap* prior = nullptr;
+ while (map) {
+ if (map->GetRowGroup() == aGroup) {
+ nsCellMap* next = map->GetNextSibling();
+ if (mFirstMap == map) {
+ mFirstMap = next;
+ }
+ else {
+ prior->SetNextSibling(next);
+ }
+ delete map;
+ break;
+ }
+ prior = map;
+ map = map->GetNextSibling();
+ }
+}
+
+static nsCellMap*
+FindMapFor(const nsTableRowGroupFrame* aRowGroup,
+ nsCellMap* aStart,
+ const nsCellMap* aEnd)
+{
+ for (nsCellMap* map = aStart; map != aEnd; map = map->GetNextSibling()) {
+ if (aRowGroup == map->GetRowGroup()) {
+ return map;
+ }
+ }
+
+ return nullptr;
+}
+
+nsCellMap*
+nsTableCellMap::GetMapFor(const nsTableRowGroupFrame* aRowGroup,
+ nsCellMap* aStartHint) const
+{
+ NS_PRECONDITION(aRowGroup, "Must have a rowgroup");
+ NS_ASSERTION(!aRowGroup->GetPrevInFlow(), "GetMapFor called with continuation");
+ if (aStartHint) {
+ nsCellMap* map = FindMapFor(aRowGroup, aStartHint, nullptr);
+ if (map) {
+ return map;
+ }
+ }
+
+ nsCellMap* map = FindMapFor(aRowGroup, mFirstMap, aStartHint);
+ if (map) {
+ return map;
+ }
+
+ // if aRowGroup is a repeated header or footer find the header or footer it was repeated from
+ if (aRowGroup->IsRepeatable()) {
+ nsTableFrame* fifTable = static_cast<nsTableFrame*>(mTableFrame.FirstInFlow());
+
+ const nsStyleDisplay* display = aRowGroup->StyleDisplay();
+ nsTableRowGroupFrame* rgOrig =
+ (StyleDisplay::TableHeaderGroup == display->mDisplay) ?
+ fifTable->GetTHead() : fifTable->GetTFoot();
+ // find the row group cell map using the original header/footer
+ if (rgOrig && rgOrig != aRowGroup) {
+ return GetMapFor(rgOrig, aStartHint);
+ }
+ }
+
+ return nullptr;
+}
+
+void
+nsTableCellMap::Synchronize(nsTableFrame* aTableFrame)
+{
+ nsTableFrame::RowGroupArray orderedRowGroups;
+ AutoTArray<nsCellMap*, 8> maps;
+
+ aTableFrame->OrderRowGroups(orderedRowGroups);
+ if (!orderedRowGroups.Length()) {
+ return;
+ }
+
+ // XXXbz this fails if orderedRowGroups is missing some row groups
+ // (due to OOM when appending to the array, e.g. -- we leak maps in
+ // that case).
+
+ // Scope |map| outside the loop so we can use it as a hint.
+ nsCellMap* map = nullptr;
+ for (uint32_t rgX = 0; rgX < orderedRowGroups.Length(); rgX++) {
+ nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgX];
+ map = GetMapFor(static_cast<nsTableRowGroupFrame*>(rgFrame->FirstInFlow()),
+ map);
+ if (map) {
+ if (!maps.AppendElement(map)) {
+ delete map;
+ map = nullptr;
+ NS_WARNING("Could not AppendElement");
+ break;
+ }
+ }
+ }
+ if (maps.IsEmpty()) {
+ MOZ_ASSERT(!mFirstMap);
+ return;
+ }
+
+ int32_t mapIndex = maps.Length() - 1; // Might end up -1
+ nsCellMap* nextMap = maps.ElementAt(mapIndex);
+ nextMap->SetNextSibling(nullptr);
+ for (mapIndex-- ; mapIndex >= 0; mapIndex--) {
+ nsCellMap* map = maps.ElementAt(mapIndex);
+ map->SetNextSibling(nextMap);
+ nextMap = map;
+ }
+ mFirstMap = nextMap;
+}
+
+bool
+nsTableCellMap::HasMoreThanOneCell(int32_t aRowIndex) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ if (map->GetRowCount() > rowIndex) {
+ return map->HasMoreThanOneCell(rowIndex);
+ }
+ rowIndex -= map->GetRowCount();
+ map = map->GetNextSibling();
+ }
+ return false;
+}
+
+int32_t
+nsTableCellMap::GetNumCellsOriginatingInRow(int32_t aRowIndex) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ if (map->GetRowCount() > rowIndex) {
+ return map->GetNumCellsOriginatingInRow(rowIndex);
+ }
+ rowIndex -= map->GetRowCount();
+ map = map->GetNextSibling();
+ }
+ return 0;
+}
+int32_t
+nsTableCellMap::GetEffectiveRowSpan(int32_t aRowIndex,
+ int32_t aColIndex) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ if (map->GetRowCount() > rowIndex) {
+ return map->GetRowSpan(rowIndex, aColIndex, true);
+ }
+ rowIndex -= map->GetRowCount();
+ map = map->GetNextSibling();
+ }
+ NS_NOTREACHED("Bogus row index?");
+ return 0;
+}
+
+int32_t
+nsTableCellMap::GetEffectiveColSpan(int32_t aRowIndex,
+ int32_t aColIndex) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ if (map->GetRowCount() > rowIndex) {
+ return map->GetEffectiveColSpan(*this, rowIndex, aColIndex);
+ }
+ rowIndex -= map->GetRowCount();
+ map = map->GetNextSibling();
+ }
+ NS_NOTREACHED("Bogus row index?");
+ return 0;
+}
+
+nsTableCellFrame*
+nsTableCellMap::GetCellFrame(int32_t aRowIndex,
+ int32_t aColIndex,
+ CellData& aData,
+ bool aUseRowIfOverlap) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ if (map->GetRowCount() > rowIndex) {
+ return map->GetCellFrame(rowIndex, aColIndex, aData, aUseRowIfOverlap);
+ }
+ rowIndex -= map->GetRowCount();
+ map = map->GetNextSibling();
+ }
+ return nullptr;
+}
+
+nsColInfo*
+nsTableCellMap::GetColInfoAt(int32_t aColIndex)
+{
+ int32_t numColsToAdd = aColIndex + 1 - mCols.Length();
+ if (numColsToAdd > 0) {
+ AddColsAtEnd(numColsToAdd); // XXX this could fail to add cols in theory
+ }
+ return &mCols.ElementAt(aColIndex);
+}
+
+int32_t
+nsTableCellMap::GetRowCount() const
+{
+ int32_t numRows = 0;
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ numRows += map->GetRowCount();
+ map = map->GetNextSibling();
+ }
+ return numRows;
+}
+
+CellData*
+nsTableCellMap::GetDataAt(int32_t aRowIndex,
+ int32_t aColIndex) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* map = mFirstMap;
+ while (map) {
+ if (map->GetRowCount() > rowIndex) {
+ return map->GetDataAt(rowIndex, aColIndex);
+ }
+ rowIndex -= map->GetRowCount();
+ map = map->GetNextSibling();
+ }
+ return nullptr;
+}
+
+void
+nsTableCellMap::AddColsAtEnd(uint32_t aNumCols)
+{
+ if (!mCols.AppendElements(aNumCols)) {
+ NS_WARNING("Could not AppendElement");
+ }
+ if (mBCInfo) {
+ if (!mBCInfo->mBEndBorders.AppendElements(aNumCols)) {
+ NS_WARNING("Could not AppendElement");
+ }
+ }
+}
+
+void
+nsTableCellMap::RemoveColsAtEnd()
+{
+ // Remove the cols at the end which don't have originating cells or cells spanning
+ // into them. Only do this if the col was created as eColAnonymousCell
+ int32_t numCols = GetColCount();
+ int32_t lastGoodColIndex = mTableFrame.GetIndexOfLastRealCol();
+ for (int32_t colX = numCols - 1; (colX >= 0) && (colX > lastGoodColIndex); colX--) {
+ nsColInfo& colInfo = mCols.ElementAt(colX);
+ if ((colInfo.mNumCellsOrig <= 0) && (colInfo.mNumCellsSpan <= 0)) {
+ mCols.RemoveElementAt(colX);
+
+ if (mBCInfo) {
+ int32_t count = mBCInfo->mBEndBorders.Length();
+ if (colX < count) {
+ mBCInfo->mBEndBorders.RemoveElementAt(colX);
+ }
+ }
+ }
+ else break; // only remove until we encounter the 1st valid one
+ }
+}
+
+void
+nsTableCellMap::ClearCols()
+{
+ mCols.Clear();
+ if (mBCInfo)
+ mBCInfo->mBEndBorders.Clear();
+}
+void
+nsTableCellMap::InsertRows(nsTableRowGroupFrame* aParent,
+ nsTArray<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)
+{
+ NS_PRECONDITION(!aRowsToInsert || aNumRowsToRemove == 0,
+ "Can't handle both removing and inserting rows at once");
+
+ int32_t numOrigCols = GetColCount();
+ ClearCols();
+ nsCellMap* cellMap = mFirstMap;
+ int32_t rowCount = 0;
+ while (cellMap) {
+ if (cellMap == aCellMap) {
+ cellMap->RebuildConsideringRows(*this, aStartRowIndex, aRowsToInsert,
+ aNumRowsToRemove);
+ }
+ else {
+ cellMap->RebuildConsideringCells(*this, numOrigCols, nullptr, -1, 0,
+ false);
+ }
+ rowCount += cellMap->GetRowCount();
+ cellMap = cellMap->GetNextSibling();
+ }
+ SetDamageArea(0, 0, GetColCount(), rowCount, aDamageArea);
+}
+
+int32_t
+nsTableCellMap::GetNumCellsOriginatingInCol(int32_t aColIndex) const
+{
+ int32_t colCount = mCols.Length();
+ if ((aColIndex >= 0) && (aColIndex < colCount)) {
+ return mCols.ElementAt(aColIndex).mNumCellsOrig;
+ }
+ else {
+ NS_ERROR("nsCellMap::GetNumCellsOriginatingInCol - bad col index");
+ return 0;
+ }
+}
+
+#ifdef DEBUG
+void
+nsTableCellMap::Dump(char* aString) const
+{
+ if (aString)
+ printf("%s \n", aString);
+ printf("***** START TABLE CELL MAP DUMP ***** %p\n", (void*)this);
+ // output col info
+ int32_t colCount = mCols.Length();
+ printf ("cols array orig/span-> %p", (void*)this);
+ for (int32_t colX = 0; colX < colCount; colX++) {
+ const nsColInfo& colInfo = mCols.ElementAt(colX);
+ printf ("%d=%d/%d ", colX, colInfo.mNumCellsOrig, colInfo.mNumCellsSpan);
+ }
+ printf(" cols in cache %d\n", int(mTableFrame.GetColCache().Length()));
+ nsCellMap* cellMap = mFirstMap;
+ while (cellMap) {
+ cellMap->Dump(nullptr != mBCInfo);
+ cellMap = cellMap->GetNextSibling();
+ }
+ if (nullptr != mBCInfo) {
+ printf("***** block-end borders *****\n");
+ nscoord size;
+ BCBorderOwner owner;
+ LogicalSide side;
+ bool segStart;
+ bool bevel;
+ int32_t colIndex;
+ int32_t numCols = mBCInfo->mBEndBorders.Length();
+ for (int32_t i = 0; i <= 2; i++) {
+
+ printf("\n ");
+ for (colIndex = 0; colIndex < numCols; colIndex++) {
+ BCData& cd = mBCInfo->mBEndBorders.ElementAt(colIndex);
+ if (0 == i) {
+ size = cd.GetBStartEdge(owner, segStart);
+ printf("t=%d%X%d ", int32_t(size), owner, segStart);
+ }
+ else if (1 == i) {
+ size = cd.GetIStartEdge(owner, segStart);
+ printf("l=%d%X%d ", int32_t(size), owner, segStart);
+ }
+ else {
+ size = cd.GetCorner(side, bevel);
+ printf("c=%d%X%d ", int32_t(size), side, bevel);
+ }
+ }
+ BCData& cd = mBCInfo->mBEndIEndCorner;
+ if (0 == i) {
+ size = cd.GetBStartEdge(owner, segStart);
+ printf("t=%d%X%d ", int32_t(size), owner, segStart);
+ }
+ else if (1 == i) {
+ size = cd.GetIStartEdge(owner, segStart);
+ printf("l=%d%X%d ", int32_t(size), owner, segStart);
+ }
+ else {
+ size = cd.GetCorner(side, bevel);
+ printf("c=%d%X%d ", int32_t(size), side, bevel);
+ }
+ }
+ printf("\n");
+ }
+ printf("***** END TABLE CELL MAP DUMP *****\n");
+}
+#endif
+
+nsTableCellFrame*
+nsTableCellMap::GetCellInfoAt(int32_t aRowIndex,
+ int32_t aColIndex,
+ bool* aOriginates,
+ int32_t* aColSpan) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* cellMap = mFirstMap;
+ while (cellMap) {
+ if (cellMap->GetRowCount() > rowIndex) {
+ return cellMap->GetCellInfoAt(*this, rowIndex, aColIndex, aOriginates, aColSpan);
+ }
+ rowIndex -= cellMap->GetRowCount();
+ cellMap = cellMap->GetNextSibling();
+ }
+ return nullptr;
+}
+
+int32_t
+nsTableCellMap::GetIndexByRowAndColumn(int32_t aRow, int32_t aColumn) const
+{
+ int32_t index = 0;
+
+ int32_t colCount = mCols.Length();
+ int32_t rowIndex = aRow;
+
+ nsCellMap* cellMap = mFirstMap;
+ while (cellMap) {
+ int32_t rowCount = cellMap->GetRowCount();
+ if (rowIndex >= rowCount) {
+ // If the rowCount is less than the rowIndex, this means that the index is
+ // not within the current map. If so, get the index of the last cell in
+ // the last row.
+ rowIndex -= rowCount;
+
+ int32_t cellMapIdx = cellMap->GetHighestIndex(colCount);
+ if (cellMapIdx != -1)
+ index += cellMapIdx + 1;
+
+ } else {
+ // Index is in valid range for this cellmap, so get the index of rowIndex
+ // and aColumn.
+ int32_t cellMapIdx = cellMap->GetIndexByRowAndColumn(colCount, rowIndex,
+ aColumn);
+ if (cellMapIdx == -1)
+ return -1; // no cell at the given row and column.
+
+ index += cellMapIdx;
+ return index; // no need to look through further maps here
+ }
+
+ cellMap = cellMap->GetNextSibling();
+ }
+
+ return -1;
+}
+
+void
+nsTableCellMap::GetRowAndColumnByIndex(int32_t aIndex,
+ int32_t *aRow, int32_t *aColumn) const
+{
+ *aRow = -1;
+ *aColumn = -1;
+
+ int32_t colCount = mCols.Length();
+
+ int32_t previousRows = 0;
+ int32_t index = aIndex;
+
+ nsCellMap* cellMap = mFirstMap;
+ while (cellMap) {
+ int32_t rowCount = cellMap->GetRowCount();
+ // Determine the highest possible index in this map to see
+ // if wanted index is in here.
+ int32_t cellMapIdx = cellMap->GetHighestIndex(colCount);
+ if (cellMapIdx == -1) {
+ // The index is not within this map, increase the total row index
+ // accordingly.
+ previousRows += rowCount;
+ } else {
+ if (index > cellMapIdx) {
+ // The index is not within this map, so decrease it by the cellMapIdx
+ // determined index and increase the total row index accordingly.
+ index -= cellMapIdx + 1;
+ previousRows += rowCount;
+ } else {
+ cellMap->GetRowAndColumnByIndex(colCount, index, aRow, aColumn);
+ // If there were previous indexes, take them into account.
+ *aRow += previousRows;
+ return; // no need to look any further.
+ }
+ }
+
+ cellMap = cellMap->GetNextSibling();
+ }
+}
+
+bool nsTableCellMap::RowIsSpannedInto(int32_t aRowIndex,
+ int32_t aNumEffCols) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* cellMap = mFirstMap;
+ while (cellMap) {
+ if (cellMap->GetRowCount() > rowIndex) {
+ return cellMap->RowIsSpannedInto(rowIndex, aNumEffCols);
+ }
+ rowIndex -= cellMap->GetRowCount();
+ cellMap = cellMap->GetNextSibling();
+ }
+ return false;
+}
+
+bool nsTableCellMap::RowHasSpanningCells(int32_t aRowIndex,
+ int32_t aNumEffCols) const
+{
+ int32_t rowIndex = aRowIndex;
+ nsCellMap* cellMap = mFirstMap;
+ while (cellMap) {
+ if (cellMap->GetRowCount() > rowIndex) {
+ return cellMap->RowHasSpanningCells(rowIndex, aNumEffCols);
+ }
+ rowIndex -= cellMap->GetRowCount();
+ cellMap = cellMap->GetNextSibling();
+ }
+ return false;
+}
+
+void
+nsTableCellMap::ResetBStartStart(LogicalSide aSide,
+ nsCellMap& aCellMap,
+ uint32_t aRowIndex,
+ uint32_t aColIndex,
+ bool aIsBEndIEnd)
+{
+ if (!mBCInfo || aIsBEndIEnd) ABORT0();
+
+ BCCellData* cellData;
+ BCData* bcData = nullptr;
+
+ switch(aSide) {
+ case eLogicalSideBEnd:
+ aRowIndex++;
+ MOZ_FALLTHROUGH;
+ case eLogicalSideBStart:
+ cellData = (BCCellData*)aCellMap.GetDataAt(aRowIndex, aColIndex);
+ if (cellData) {
+ bcData = &cellData->mData;
+ }
+ else {
+ NS_ASSERTION(aSide == eLogicalSideBEnd, "program error");
+ // try the next row group
+ nsCellMap* cellMap = aCellMap.GetNextSibling();
+ if (cellMap) {
+ cellData = (BCCellData*)cellMap->GetDataAt(0, aColIndex);
+ if (cellData) {
+ bcData = &cellData->mData;
+ }
+ else {
+ bcData = GetBEndMostBorder(aColIndex);
+ }
+ }
+ }
+ break;
+ case eLogicalSideIEnd:
+ aColIndex++;
+ MOZ_FALLTHROUGH;
+ case eLogicalSideIStart:
+ cellData = (BCCellData*)aCellMap.GetDataAt(aRowIndex, aColIndex);
+ if (cellData) {
+ bcData = &cellData->mData;
+ }
+ else {
+ NS_ASSERTION(aSide == eLogicalSideIEnd, "program error");
+ bcData = GetIEndMostBorder(aRowIndex);
+ }
+ break;
+ }
+ if (bcData) {
+ bcData->SetBStartStart(false);
+ }
+}
+
+// store the aSide border segment at coord = (aRowIndex, aColIndex). For bStart/iStart, store
+// the info at coord. For bEnd/iStart store it at the adjacent location so that it is
+// bStart/iStart at that location. If the new location is at the iEnd or bEnd edge of the
+// table, then store it one of the special arrays (iEnd-most borders, bEnd-most borders).
+void
+nsTableCellMap::SetBCBorderEdge(LogicalSide aSide,
+ nsCellMap& aCellMap,
+ uint32_t aCellMapStart,
+ uint32_t aRowIndex,
+ uint32_t aColIndex,
+ uint32_t aLength,
+ BCBorderOwner aOwner,
+ nscoord aSize,
+ bool aChanged)
+{
+ if (!mBCInfo) ABORT0();
+
+ BCCellData* cellData;
+ int32_t lastIndex, xIndex, yIndex;
+ int32_t xPos = aColIndex;
+ int32_t yPos = aRowIndex;
+ int32_t rgYPos = aRowIndex - aCellMapStart;
+ bool changed;
+
+ switch(aSide) {
+ case eLogicalSideBEnd:
+ rgYPos++;
+ yPos++;
+ MOZ_FALLTHROUGH;
+ case eLogicalSideBStart:
+ lastIndex = xPos + aLength - 1;
+ for (xIndex = xPos; xIndex <= lastIndex; xIndex++) {
+ changed = aChanged && (xIndex == xPos);
+ BCData* bcData = nullptr;
+ cellData = (BCCellData*)aCellMap.GetDataAt(rgYPos, xIndex);
+ if (!cellData) {
+ int32_t numRgRows = aCellMap.GetRowCount();
+ if (yPos < numRgRows) { // add a dead cell data
+ TableArea damageArea;
+ cellData = (BCCellData*)aCellMap.AppendCell(*this, nullptr, rgYPos,
+ false, 0, damageArea);
+ if (!cellData) ABORT0();
+ }
+ else {
+ NS_ASSERTION(aSide == eLogicalSideBEnd, "program error");
+ // try the next non empty row group
+ nsCellMap* cellMap = aCellMap.GetNextSibling();
+ while (cellMap && (0 == cellMap->GetRowCount())) {
+ cellMap = cellMap->GetNextSibling();
+ }
+ if (cellMap) {
+ cellData = (BCCellData*)cellMap->GetDataAt(0, xIndex);
+ if (!cellData) { // add a dead cell
+ TableArea damageArea;
+ cellData = (BCCellData*)cellMap->AppendCell(*this, nullptr, 0,
+ false, 0,
+ damageArea);
+ }
+ }
+ else { // must be at the end of the table
+ bcData = GetBEndMostBorder(xIndex);
+ }
+ }
+ }
+ if (!bcData && cellData) {
+ bcData = &cellData->mData;
+ }
+ if (bcData) {
+ bcData->SetBStartEdge(aOwner, aSize, changed);
+ }
+ else NS_ERROR("Cellmap: BStart edge not found");
+ }
+ break;
+ case eLogicalSideIEnd:
+ xPos++;
+ MOZ_FALLTHROUGH;
+ case eLogicalSideIStart:
+ // since bStart, bEnd borders were set, there should already be a cellData entry
+ lastIndex = rgYPos + aLength - 1;
+ for (yIndex = rgYPos; yIndex <= lastIndex; yIndex++) {
+ changed = aChanged && (yIndex == rgYPos);
+ cellData = (BCCellData*)aCellMap.GetDataAt(yIndex, xPos);
+ if (cellData) {
+ cellData->mData.SetIStartEdge(aOwner, aSize, changed);
+ }
+ else {
+ NS_ASSERTION(aSide == eLogicalSideIEnd, "program error");
+ BCData* bcData = GetIEndMostBorder(yIndex + aCellMapStart);
+ if (bcData) {
+ bcData->SetIStartEdge(aOwner, aSize, changed);
+ }
+ else NS_ERROR("Cellmap: IStart edge not found");
+ }
+ }
+ break;
+ }
+}
+
+// store corner info (aOwner, aSubSize, aBevel). For aCorner = eBStartIStart, store the info at
+// (aRowIndex, aColIndex). For eBStartIEnd, store it in the entry to the iEnd-wards where
+// it would be BStartIStart. For eBEndIEnd, store it in the entry to the bEnd-wards. etc.
+void
+nsTableCellMap::SetBCBorderCorner(Corner aCorner,
+ nsCellMap& aCellMap,
+ uint32_t aCellMapStart,
+ uint32_t aRowIndex,
+ uint32_t aColIndex,
+ LogicalSide aOwner,
+ nscoord aSubSize,
+ bool aBevel,
+ bool aIsBEndIEnd)
+{
+ if (!mBCInfo) ABORT0();
+
+ if (aIsBEndIEnd) {
+ mBCInfo->mBEndIEndCorner.SetCorner(aSubSize, aOwner, aBevel);
+ return;
+ }
+
+ int32_t xPos = aColIndex;
+ int32_t yPos = aRowIndex;
+ int32_t rgYPos = aRowIndex - aCellMapStart;
+
+ if (eBStartIEnd == aCorner) {
+ xPos++;
+ }
+ else if (eBEndIEnd == aCorner) {
+ xPos++;
+ rgYPos++;
+ yPos++;
+ }
+ else if (eBEndIStart == aCorner) {
+ rgYPos++;
+ yPos++;
+ }
+
+ BCCellData* cellData = nullptr;
+ BCData* bcData = nullptr;
+ if (GetColCount() <= xPos) {
+ NS_ASSERTION(xPos == GetColCount(), "program error");
+ // at the iEnd edge of the table as we checked the corner before
+ NS_ASSERTION(!aIsBEndIEnd, "should be handled before");
+ bcData = GetIEndMostBorder(yPos);
+ }
+ else {
+ cellData = (BCCellData*)aCellMap.GetDataAt(rgYPos, xPos);
+ if (!cellData) {
+ int32_t numRgRows = aCellMap.GetRowCount();
+ if (yPos < numRgRows) { // add a dead cell data
+ TableArea damageArea;
+ cellData = (BCCellData*)aCellMap.AppendCell(*this, nullptr, rgYPos,
+ false, 0, damageArea);
+ }
+ else {
+ // try the next non empty row group
+ nsCellMap* cellMap = aCellMap.GetNextSibling();
+ while (cellMap && (0 == cellMap->GetRowCount())) {
+ cellMap = cellMap->GetNextSibling();
+ }
+ if (cellMap) {
+ cellData = (BCCellData*)cellMap->GetDataAt(0, xPos);
+ if (!cellData) { // add a dead cell
+ TableArea damageArea;
+ cellData = (BCCellData*)cellMap->AppendCell(*this, nullptr, 0,
+ false, 0, damageArea);
+ }
+ }
+ else { // must be at the bEnd of the table
+ bcData = GetBEndMostBorder(xPos);
+ }
+ }
+ }
+ }
+ if (!bcData && cellData) {
+ bcData = &cellData->mData;
+ }
+ if (bcData) {
+ bcData->SetCorner(aSubSize, aOwner, aBevel);
+ }
+ else NS_ERROR("program error: Corner not found");
+}
+
+nsCellMap::nsCellMap(nsTableRowGroupFrame* aRowGroup, bool aIsBC)
+ : mRows(8), mContentRowCount(0), mRowGroupFrame(aRowGroup),
+ mNextSibling(nullptr), mIsBC(aIsBC),
+ mPresContext(aRowGroup->PresContext())
+{
+ MOZ_COUNT_CTOR(nsCellMap);
+ NS_ASSERTION(mPresContext, "Must have prescontext");
+}
+
+nsCellMap::~nsCellMap()
+{
+ MOZ_COUNT_DTOR(nsCellMap);
+
+ uint32_t mapRowCount = mRows.Length();
+ for (uint32_t rowX = 0; rowX < mapRowCount; rowX++) {
+ CellDataArray &row = mRows[rowX];
+ uint32_t colCount = row.Length();
+ for (uint32_t colX = 0; colX < colCount; colX++) {
+ DestroyCellData(row[colX]);
+ }
+ }
+}
+
+/* static */
+void
+nsCellMap::Init()
+{
+ MOZ_ASSERT(!sEmptyRow, "How did that happen?");
+ sEmptyRow = new nsCellMap::CellDataArray();
+}
+
+/* static */
+void
+nsCellMap::Shutdown()
+{
+ delete sEmptyRow;
+ sEmptyRow = nullptr;
+}
+
+nsTableCellFrame*
+nsCellMap::GetCellFrame(int32_t aRowIndexIn,
+ int32_t aColIndexIn,
+ CellData& aData,
+ bool aUseRowIfOverlap) const
+{
+ int32_t rowIndex = aRowIndexIn - aData.GetRowSpanOffset();
+ int32_t colIndex = aColIndexIn - aData.GetColSpanOffset();
+ if (aData.IsOverlap()) {
+ if (aUseRowIfOverlap) {
+ colIndex = aColIndexIn;
+ }
+ else {
+ rowIndex = aRowIndexIn;
+ }
+ }
+
+ CellData* data =
+ mRows.SafeElementAt(rowIndex, *sEmptyRow).SafeElementAt(colIndex);
+ if (data) {
+ return data->GetCellFrame();
+ }
+ return nullptr;
+}
+
+int32_t
+nsCellMap::GetHighestIndex(int32_t aColCount)
+{
+ int32_t index = -1;
+ int32_t rowCount = mRows.Length();
+ for (int32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ const CellDataArray& row = mRows[rowIdx];
+
+ for (int32_t colIdx = 0; colIdx < aColCount; colIdx++) {
+ CellData* data = row.SafeElementAt(colIdx);
+ // No data means row doesn't have more cells.
+ if (!data)
+ break;
+
+ if (data->IsOrig())
+ index++;
+ }
+ }
+
+ return index;
+}
+
+int32_t
+nsCellMap::GetIndexByRowAndColumn(int32_t aColCount,
+ int32_t aRow, int32_t aColumn) const
+{
+ if (uint32_t(aRow) >= mRows.Length())
+ return -1;
+
+ int32_t index = -1;
+ int32_t lastColsIdx = aColCount - 1;
+
+ // Find row index of the cell where row span is started.
+ const CellDataArray& row = mRows[aRow];
+ CellData* data = row.SafeElementAt(aColumn);
+ int32_t origRow = data ? aRow - data->GetRowSpanOffset() : aRow;
+
+ // Calculate cell index.
+ for (int32_t rowIdx = 0; rowIdx <= origRow; rowIdx++) {
+ const CellDataArray& row = mRows[rowIdx];
+ int32_t colCount = (rowIdx == origRow) ? aColumn : lastColsIdx;
+
+ for (int32_t colIdx = 0; colIdx <= colCount; colIdx++) {
+ data = row.SafeElementAt(colIdx);
+ // No data means row doesn't have more cells.
+ if (!data)
+ break;
+
+ if (data->IsOrig())
+ index++;
+ }
+ }
+
+ // Given row and column don't point to the cell.
+ if (!data)
+ return -1;
+
+ return index;
+}
+
+void
+nsCellMap::GetRowAndColumnByIndex(int32_t aColCount, int32_t aIndex,
+ int32_t *aRow, int32_t *aColumn) const
+{
+ *aRow = -1;
+ *aColumn = -1;
+
+ int32_t index = aIndex;
+ int32_t rowCount = mRows.Length();
+
+ for (int32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ const CellDataArray& row = mRows[rowIdx];
+
+ for (int32_t colIdx = 0; colIdx < aColCount; colIdx++) {
+ CellData* data = row.SafeElementAt(colIdx);
+
+ // The row doesn't have more cells.
+ if (!data)
+ break;
+
+ if (data->IsOrig())
+ index--;
+
+ if (index < 0) {
+ *aRow = rowIdx;
+ *aColumn = colIdx;
+ return;
+ }
+ }
+ }
+}
+
+bool nsCellMap::Grow(nsTableCellMap& aMap,
+ int32_t aNumRows,
+ int32_t aRowIndex)
+{
+ NS_ASSERTION(aNumRows >= 1, "Why are we calling this?");
+
+ // Get the number of cols we want to use for preallocating the row arrays.
+ int32_t numCols = aMap.GetColCount();
+ if (numCols == 0) {
+ numCols = 4;
+ }
+ uint32_t startRowIndex = (aRowIndex >= 0) ? aRowIndex : mRows.Length();
+ NS_ASSERTION(startRowIndex <= mRows.Length(), "Missing grow call inbetween");
+
+ return mRows.InsertElementsAt(startRowIndex, aNumRows, numCols) != nullptr;
+}
+
+void nsCellMap::GrowRow(CellDataArray& aRow,
+ int32_t aNumCols)
+
+{
+ // Have to have the cast to get the template to do the right thing.
+ aRow.InsertElementsAt(aRow.Length(), aNumCols, (CellData*)nullptr);
+}
+
+void
+nsCellMap::InsertRows(nsTableCellMap& aMap,
+ nsTArray<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 = zeroRowSpan ? endRowIndex - aRowIndex :
+ 1 + endRowIndex - aRowIndex;
+ SetDamageArea(startColIndex, aRgFirstRowIndex + aRowIndex,
+ 1 + endColIndex - startColIndex, height, aDamageArea);
+ }
+
+ if (!aCellFrame) {
+ return origData;
+ }
+
+ // initialize the cell frame
+ aCellFrame->SetColIndex(startColIndex);
+
+ // Create CellData objects for the rows that this cell spans. Set
+ // their mOrigCell to nullptr and their mSpanData to point to data.
+ for (int32_t rowX = aRowIndex; rowX <= endRowIndex; rowX++) {
+ // The row at rowX will need to have at least endColIndex columns
+ mRows[rowX].SetCapacity(endColIndex);
+ for (int32_t colX = startColIndex; colX <= endColIndex; colX++) {
+ if ((rowX != aRowIndex) || (colX != startColIndex)) { // skip orig cell data done above
+ CellData* cellData = GetDataAt(rowX, colX);
+ if (cellData) {
+ if (cellData->IsOrig()) {
+ NS_ERROR("cannot overlap originating cell");
+ continue;
+ }
+ if (rowX > aRowIndex) { // row spanning into cell
+ if (cellData->IsRowSpan()) {
+ // do nothing, this can be caused by rowspan which is overlapped
+ // by a another cell with a rowspan and a colspan
+ }
+ else {
+ cellData->SetRowSpanOffset(rowX - aRowIndex);
+ if (zeroRowSpan) {
+ cellData->SetZeroRowSpan(true);
+ }
+ }
+ }
+ if (colX > startColIndex) { // col spanning into cell
+ if (!cellData->IsColSpan()) {
+ if (cellData->IsRowSpan()) {
+ cellData->SetOverlap(true);
+ }
+ cellData->SetColSpanOffset(colX - startColIndex);
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ colInfo->mNumCellsSpan++;
+ }
+ }
+ }
+ else {
+ cellData = AllocCellData(nullptr);
+ if (!cellData) return origData;
+ if (rowX > aRowIndex) {
+ cellData->SetRowSpanOffset(rowX - aRowIndex);
+ if (zeroRowSpan) {
+ cellData->SetZeroRowSpan(true);
+ }
+ }
+ if (colX > startColIndex) {
+ cellData->SetColSpanOffset(colX - startColIndex);
+ }
+ SetDataAt(aMap, *cellData, rowX, colX);
+ }
+ }
+ }
+ }
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("appended cell=%p row=%d \n", aCellFrame, aRowIndex);
+ aMap.Dump();
+#endif
+ return origData;
+}
+
+bool nsCellMap::CellsSpanOut(nsTArray<nsTableRowFrame*>& aRows) const
+{
+ int32_t numNewRows = aRows.Length();
+ for (int32_t rowX = 0; rowX < numNewRows; rowX++) {
+ nsIFrame* rowFrame = (nsIFrame *) aRows.ElementAt(rowX);
+ for (nsIFrame* childFrame : rowFrame->PrincipalChildList()) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
+ if (cellFrame) {
+ bool zeroSpan;
+ int32_t rowSpan = GetRowSpanForNewCell(cellFrame, rowX, zeroSpan);
+ if (zeroSpan || rowX + rowSpan > numNewRows) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// return true if any cells have rows spans into or out of the region
+// defined by the row and col indices or any cells have colspans into the region
+bool nsCellMap::CellsSpanInOrOut(int32_t aStartRowIndex,
+ int32_t aEndRowIndex,
+ int32_t aStartColIndex,
+ int32_t aEndColIndex) const
+{
+ /*
+ * this routine will watch the cells adjacent to the region or at the edge
+ * they are marked with *. The routine will verify whether they span in or
+ * are spanned out.
+ *
+ * startCol endCol
+ * r1c1 r1c2 r1c3 r1c4 r1c5 r1rc6 r1c7
+ * startrow r2c1 r2c2 *r2c3 *r2c4 *r2c5 *r2rc6 r2c7
+ * endrow r3c1 r3c2 *r3c3 r3c4 r3c5 *r3rc6 r3c7
+ * r4c1 r4c2 *r4c3 *r4c4 *r4c5 r4rc6 r4c7
+ * r5c1 r5c2 r5c3 r5c4 r5c5 r5rc6 r5c7
+ */
+
+ int32_t numRows = mRows.Length(); // use the cellmap rows to determine the
+ // current cellmap extent.
+ for (int32_t colX = aStartColIndex; colX <= aEndColIndex; colX++) {
+ CellData* cellData;
+ if (aStartRowIndex > 0) {
+ cellData = GetDataAt(aStartRowIndex, colX);
+ if (cellData && (cellData->IsRowSpan())) {
+ return true; // there is a row span into the region
+ }
+ if ((aStartRowIndex >= mContentRowCount) && (mContentRowCount > 0)) {
+ cellData = GetDataAt(mContentRowCount - 1, colX);
+ if (cellData && cellData->IsZeroRowSpan()) {
+ return true; // When we expand the zerospan it'll span into our row
+ }
+ }
+ }
+ if (aEndRowIndex < numRows - 1) { // is there anything below aEndRowIndex
+ cellData = GetDataAt(aEndRowIndex + 1, colX);
+ if ((cellData) && (cellData->IsRowSpan())) {
+ return true; // there is a row span out of the region
+ }
+ }
+ else {
+ cellData = GetDataAt(aEndRowIndex, colX);
+ if ((cellData) && (cellData->IsRowSpan()) && (mContentRowCount < numRows)) {
+ return true; // this cell might be the cause of a dead row
+ }
+ }
+ }
+ if (aStartColIndex > 0) {
+ for (int32_t rowX = aStartRowIndex; rowX <= aEndRowIndex; rowX++) {
+ CellData* cellData = GetDataAt(rowX, aStartColIndex);
+ if (cellData && (cellData->IsColSpan())) {
+ return true; // there is a col span into the region
+ }
+ cellData = GetDataAt(rowX, aEndColIndex + 1);
+ if (cellData && (cellData->IsColSpan())) {
+ return true; // there is a col span out of the region
+ }
+ }
+ }
+ return false;
+}
+
+void nsCellMap::InsertCells(nsTableCellMap& aMap,
+ nsTArray<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 (nsIFrame* cFrame : rFrame->PrincipalChildList()) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(cFrame);
+ if (cellFrame) {
+ AppendCell(aMap, cellFrame, rowX, false, aRgFirstRowIndex, aDamageArea,
+ &colIndex);
+ }
+ }
+ newRowIndex++;
+ }
+ // mark all following rows damaged, they might contain a previously set
+ // damage area which we can not shift.
+ int32_t firstDamagedRow = aRgFirstRowIndex + startRowIndex;
+ SetDamageArea(0, firstDamagedRow, aMap.GetColCount(),
+ aMap.GetRowCount() - firstDamagedRow, aDamageArea);
+}
+
+void nsCellMap::ExpandWithCells(nsTableCellMap& aMap,
+ nsTArray<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;
+ }
+ if (!row.InsertElementsAt(insertionIndex, endColIndex - insertionIndex + 1,
+ (CellData*)nullptr) &&
+ rowX == aRowIndex) {
+ // Failed to insert the slots, and this is the very first row. That
+ // means that we need to clean up |origData| before returning, since
+ // the cellmap doesn't own it yet.
+ DestroyCellData(origData);
+ return;
+ }
+
+ for (int32_t colX = startColIndex; colX <= endColIndex; colX++) {
+ CellData* data = origData;
+ if ((rowX != aRowIndex) || (colX != startColIndex)) {
+ data = AllocCellData(nullptr);
+ if (!data) return;
+ if (rowX > aRowIndex) {
+ data->SetRowSpanOffset(rowX - aRowIndex);
+ if (aRowSpanIsZero) {
+ data->SetZeroRowSpan(true);
+ }
+ }
+ if (colX > startColIndex) {
+ data->SetColSpanOffset(colX - startColIndex);
+ }
+ }
+ SetDataAt(aMap, *data, rowX, colX);
+ }
+ }
+ cellFrame->SetColIndex(startColIndex);
+ }
+ int32_t damageHeight = std::min(GetRowGroup()->GetRowCount() - aRowIndex,
+ aRowSpan);
+ SetDamageArea(aColIndex, aRgFirstRowIndex + aRowIndex,
+ 1 + endColIndex - aColIndex, damageHeight, aDamageArea);
+
+ int32_t rowX;
+
+ // update the row and col info due to shifting
+ for (rowX = aRowIndex; rowX <= endRowIndex; rowX++) {
+ CellDataArray& row = mRows[rowX];
+ uint32_t numCols = row.Length();
+ uint32_t colX;
+ for (colX = aColIndex + totalColSpan; colX < numCols; colX++) {
+ CellData* data = row[colX];
+ if (data) {
+ // increase the origin and span counts beyond the spanned cols
+ if (data->IsOrig()) {
+ // a cell that gets moved needs adjustment as well as it new orignating col
+ data->GetCellFrame()->SetColIndex(colX);
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ colInfo->mNumCellsOrig++;
+ }
+ if (data->IsColSpan()) {
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ colInfo->mNumCellsSpan++;
+ }
+
+ // decrease the origin and span counts within the spanned cols
+ int32_t colX2 = colX - totalColSpan;
+ nsColInfo* colInfo2 = aMap.GetColInfoAt(colX2);
+ if (data->IsOrig()) {
+ // the old originating col of a moved cell needs adjustment
+ colInfo2->mNumCellsOrig--;
+ }
+ if (data->IsColSpan()) {
+ colInfo2->mNumCellsSpan--;
+ }
+ }
+ }
+ }
+}
+
+void nsCellMap::ShrinkWithoutRows(nsTableCellMap& aMap,
+ int32_t aStartRowIndex,
+ int32_t aNumRowsToRemove,
+ int32_t aRgFirstRowIndex,
+ TableArea& aDamageArea)
+{
+ NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch");
+ int32_t endRowIndex = aStartRowIndex + aNumRowsToRemove - 1;
+ uint32_t colCount = aMap.GetColCount();
+ for (int32_t rowX = endRowIndex; rowX >= aStartRowIndex; --rowX) {
+ CellDataArray& row = mRows[rowX];
+ uint32_t colX;
+ for (colX = 0; colX < colCount; colX++) {
+ CellData* data = row.SafeElementAt(colX);
+ if (data) {
+ // Adjust the column counts.
+ if (data->IsOrig()) {
+ // Decrement the column count.
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ colInfo->mNumCellsOrig--;
+ }
+ // colspan=0 is only counted as a spanned cell in the 1st col it spans
+ else if (data->IsColSpan()) {
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ colInfo->mNumCellsSpan--;
+ }
+ }
+ }
+
+ uint32_t rowLength = row.Length();
+ // Delete our row information.
+ for (colX = 0; colX < rowLength; colX++) {
+ DestroyCellData(row[colX]);
+ }
+
+ mRows.RemoveElementAt(rowX);
+
+ // Decrement our row and next available index counts.
+ mContentRowCount--;
+ }
+ aMap.RemoveColsAtEnd();
+ // mark all following rows damaged, they might contain a previously set
+ // damage area which we can not shift.
+ int32_t firstDamagedRow = aRgFirstRowIndex + aStartRowIndex;
+ SetDamageArea(0, firstDamagedRow, aMap.GetColCount(),
+ aMap.GetRowCount() - firstDamagedRow, aDamageArea);
+}
+
+int32_t nsCellMap::GetEffectiveColSpan(const nsTableCellMap& aMap,
+ int32_t aRowIndex,
+ int32_t aColIndex) const
+{
+ int32_t numColsInTable = aMap.GetColCount();
+ int32_t colSpan = 1;
+ if (uint32_t(aRowIndex) >= mRows.Length()) {
+ return colSpan;
+ }
+
+ const CellDataArray& row = mRows[aRowIndex];
+ int32_t colX;
+ CellData* data;
+ int32_t maxCols = numColsInTable;
+ bool hitOverlap = false; // XXX this is not ever being set to true
+ for (colX = aColIndex + 1; colX < maxCols; colX++) {
+ data = row.SafeElementAt(colX);
+ if (data) {
+ // for an overlapping situation get the colspan from the originating cell and
+ // use that as the max number of cols to iterate. Since this is rare, only
+ // pay the price of looking up the cell's colspan here.
+ if (!hitOverlap && data->IsOverlap()) {
+ CellData* origData = row.SafeElementAt(aColIndex);
+ if (origData && origData->IsOrig()) {
+ nsTableCellFrame* cellFrame = origData->GetCellFrame();
+ if (cellFrame) {
+ // possible change the number of colums to iterate
+ maxCols = std::min(aColIndex + cellFrame->GetColSpan(), maxCols);
+ if (colX >= maxCols)
+ break;
+ }
+ }
+ }
+ if (data->IsColSpan()) {
+ colSpan++;
+ }
+ else {
+ break;
+ }
+ }
+ else break;
+ }
+ return colSpan;
+}
+
+int32_t
+nsCellMap::GetRowSpanForNewCell(nsTableCellFrame* aCellFrameToAdd,
+ int32_t aRowIndex,
+ bool& aIsZeroRowSpan) const
+{
+ aIsZeroRowSpan = false;
+ int32_t rowSpan = aCellFrameToAdd->GetRowSpan();
+ if (0 == rowSpan) {
+ // Use a min value of 2 for a zero rowspan to make computations easier
+ // elsewhere. Zero rowspans are only content dependent!
+ rowSpan = std::max(2, mContentRowCount - aRowIndex);
+ aIsZeroRowSpan = true;
+ }
+ return rowSpan;
+}
+
+bool nsCellMap::HasMoreThanOneCell(int32_t aRowIndex) const
+{
+ const CellDataArray& row = mRows.SafeElementAt(aRowIndex, *sEmptyRow);
+ uint32_t maxColIndex = row.Length();
+ uint32_t count = 0;
+ uint32_t colIndex;
+ for (colIndex = 0; colIndex < maxColIndex; colIndex++) {
+ CellData* cellData = row[colIndex];
+ if (cellData && (cellData->GetCellFrame() || cellData->IsRowSpan()))
+ count++;
+ if (count > 1)
+ return true;
+ }
+ return false;
+}
+
+int32_t
+nsCellMap::GetNumCellsOriginatingInRow(int32_t aRowIndex) const
+{
+ const CellDataArray& row = mRows.SafeElementAt(aRowIndex, *sEmptyRow);
+ uint32_t count = 0;
+ uint32_t maxColIndex = row.Length();
+ uint32_t colIndex;
+ for (colIndex = 0; colIndex < maxColIndex; colIndex++) {
+ CellData* cellData = row[colIndex];
+ if (cellData && cellData->IsOrig())
+ count++;
+ }
+ return count;
+}
+
+int32_t nsCellMap::GetRowSpan(int32_t aRowIndex,
+ int32_t aColIndex,
+ bool aGetEffective) const
+{
+ int32_t rowSpan = 1;
+ int32_t rowCount = (aGetEffective) ? mContentRowCount : mRows.Length();
+ int32_t rowX;
+ for (rowX = aRowIndex + 1; rowX < rowCount; rowX++) {
+ CellData* data = GetDataAt(rowX, aColIndex);
+ if (data) {
+ if (data->IsRowSpan()) {
+ rowSpan++;
+ }
+ else {
+ break;
+ }
+ }
+ else break;
+ }
+ return rowSpan;
+}
+
+void nsCellMap::ShrinkWithoutCell(nsTableCellMap& aMap,
+ nsTableCellFrame& aCellFrame,
+ int32_t aRowIndex,
+ int32_t aColIndex,
+ int32_t aRgFirstRowIndex,
+ TableArea& aDamageArea)
+{
+ NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch");
+ uint32_t colX, rowX;
+
+ // get the rowspan and colspan from the cell map since the content may have changed
+ uint32_t numCols = aMap.GetColCount();
+ int32_t rowSpan = GetRowSpan(aRowIndex, aColIndex, true);
+ uint32_t colSpan = GetEffectiveColSpan(aMap, aRowIndex, aColIndex);
+ uint32_t endRowIndex = aRowIndex + rowSpan - 1;
+ uint32_t endColIndex = aColIndex + colSpan - 1;
+
+ // adjust the col counts due to the deleted cell before removing it
+ for (colX = aColIndex; colX <= endColIndex; colX++) {
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ if (colX == uint32_t(aColIndex)) {
+ colInfo->mNumCellsOrig--;
+ }
+ else {
+ colInfo->mNumCellsSpan--;
+ }
+ }
+
+ // remove the deleted cell and cellData entries for it
+ for (rowX = aRowIndex; rowX <= endRowIndex; rowX++) {
+ CellDataArray& row = mRows[rowX];
+
+ // endIndexForRow points at the first slot we don't want to clean up. This
+ // makes the aColIndex == 0 case work right with our unsigned int colX.
+ NS_ASSERTION(endColIndex + 1 <= row.Length(), "span beyond the row size!");
+ uint32_t endIndexForRow = std::min(endColIndex + 1, uint32_t(row.Length()));
+
+ // Since endIndexForRow <= row.Length(), enough to compare aColIndex to it.
+ if (uint32_t(aColIndex) < endIndexForRow) {
+ for (colX = endIndexForRow; colX > uint32_t(aColIndex); colX--) {
+ DestroyCellData(row[colX-1]);
+ }
+ row.RemoveElementsAt(aColIndex, endIndexForRow - aColIndex);
+ }
+ }
+
+ numCols = aMap.GetColCount();
+
+ // update the row and col info due to shifting
+ for (rowX = aRowIndex; rowX <= endRowIndex; rowX++) {
+ CellDataArray& row = mRows[rowX];
+ for (colX = aColIndex; colX < numCols - colSpan; colX++) {
+ CellData* data = row.SafeElementAt(colX);
+ if (data) {
+ if (data->IsOrig()) {
+ // a cell that gets moved to the left needs adjustment in its new location
+ data->GetCellFrame()->SetColIndex(colX);
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ colInfo->mNumCellsOrig++;
+ // a cell that gets moved to the left needs adjustment in its old location
+ colInfo = aMap.GetColInfoAt(colX + colSpan);
+ if (colInfo) {
+ colInfo->mNumCellsOrig--;
+ }
+ }
+
+ else if (data->IsColSpan()) {
+ // a cell that gets moved to the left needs adjustment
+ // in its new location
+ nsColInfo* colInfo = aMap.GetColInfoAt(colX);
+ colInfo->mNumCellsSpan++;
+ // a cell that gets moved to the left needs adjustment
+ // in its old location
+ colInfo = aMap.GetColInfoAt(colX + colSpan);
+ if (colInfo) {
+ colInfo->mNumCellsSpan--;
+ }
+ }
+ }
+ }
+ }
+ aMap.RemoveColsAtEnd();
+ SetDamageArea(aColIndex, aRgFirstRowIndex + aRowIndex,
+ std::max(0, aMap.GetColCount() - aColIndex - 1),
+ 1 + endRowIndex - aRowIndex, aDamageArea);
+}
+
+void
+nsCellMap::RebuildConsideringRows(nsTableCellMap& aMap,
+ int32_t aStartRowIndex,
+ nsTArray<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;
+ mRows.SwapElements(origRows);
+
+ int32_t rowNumberChange;
+ if (aRowsToInsert) {
+ rowNumberChange = aRowsToInsert->Length();
+ } else {
+ rowNumberChange = -aNumRowsToRemove;
+ }
+
+ // adjust mContentRowCount based on the function arguments as they are known to
+ // be real rows.
+ mContentRowCount += rowNumberChange;
+ NS_ASSERTION(mContentRowCount >= 0, "previous mContentRowCount was wrong");
+ // mRows is empty now. Grow it to the size we expect it to have.
+ if (mContentRowCount) {
+ if (!Grow(aMap, mContentRowCount)) {
+ // Bail, I guess... Not sure what else we can do here.
+ return;
+ }
+ }
+
+ // aStartRowIndex might be after all existing rows so we should limit the
+ // copy to the amount of exisiting rows
+ uint32_t copyEndRowIndex = std::min(numOrigRows, uint32_t(aStartRowIndex));
+
+ // rowX keeps track of where we are in mRows while setting up the
+ // new cellmap.
+ uint32_t rowX = 0;
+ TableArea damageArea;
+ // put back the rows before the affected ones just as before. Note that we
+ // can't just copy the old rows in bit-for-bit, because they might be
+ // spanning out into the rows we're adding/removing.
+ for ( ; rowX < copyEndRowIndex; rowX++) {
+ const CellDataArray& row = origRows[rowX];
+ uint32_t numCols = row.Length();
+ for (uint32_t colX = 0; colX < numCols; colX++) {
+ // put in the original cell from the cell map
+ const CellData* data = row.ElementAt(colX);
+ if (data && data->IsOrig()) {
+ AppendCell(aMap, data->GetCellFrame(), rowX, false, 0, damageArea);
+ }
+ }
+ }
+
+ // Now handle the new rows being inserted, if any.
+ uint32_t copyStartRowIndex;
+ rowX = aStartRowIndex;
+ if (aRowsToInsert) {
+ // add in the new cells and create rows if necessary
+ int32_t numNewRows = aRowsToInsert->Length();
+ for (int32_t newRowX = 0; newRowX < numNewRows; newRowX++) {
+ nsTableRowFrame* rFrame = aRowsToInsert->ElementAt(newRowX);
+ for (nsIFrame* cFrame : rFrame->PrincipalChildList()) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(cFrame);
+ if (cellFrame) {
+ AppendCell(aMap, cellFrame, rowX, false, 0, damageArea);
+ }
+ }
+ rowX++;
+ }
+ copyStartRowIndex = aStartRowIndex;
+ }
+ else {
+ copyStartRowIndex = aStartRowIndex + aNumRowsToRemove;
+ }
+
+ // put back the rows after the affected ones just as before. Again, we can't
+ // just copy the old bits because that would not handle the new rows spanning
+ // out or our earlier old rows spanning through the damaged area.
+ for (uint32_t copyRowX = copyStartRowIndex; copyRowX < numOrigRows;
+ copyRowX++) {
+ const CellDataArray& row = origRows[copyRowX];
+ uint32_t numCols = row.Length();
+ for (uint32_t colX = 0; colX < numCols; colX++) {
+ // put in the original cell from the cell map
+ CellData* data = row.ElementAt(colX);
+ if (data && data->IsOrig()) {
+ AppendCell(aMap, data->GetCellFrame(), rowX, false, 0, damageArea);
+ }
+ }
+ rowX++;
+ }
+
+ // delete the old cell map. Now rowX no longer has anything to do with mRows
+ for (rowX = 0; rowX < numOrigRows; rowX++) {
+ CellDataArray& row = origRows[rowX];
+ uint32_t len = row.Length();
+ for (uint32_t colX = 0; colX < len; colX++) {
+ DestroyCellData(row[colX]);
+ }
+ }
+}
+
+void
+nsCellMap::RebuildConsideringCells(nsTableCellMap& aMap,
+ int32_t aNumOrigCols,
+ nsTArray<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;
+ mRows.SwapElements(origRows);
+
+ int32_t numNewCells = (aCellFrames) ? aCellFrames->Length() : 0;
+
+ // the new cells might extend the previous column number
+ NS_ASSERTION(aNumOrigCols >= aColIndex, "Appending cells far beyond cellmap data?!");
+ int32_t numCols = aInsert ? std::max(aNumOrigCols, aColIndex + 1) : aNumOrigCols;
+
+ // build the new cell map. Hard to say what, if anything, we can preallocate
+ // here... Should come back to that sometime, perhaps.
+ int32_t rowX;
+ TableArea damageArea;
+ for (rowX = 0; rowX < numOrigRows; rowX++) {
+ const CellDataArray& row = origRows[rowX];
+ for (int32_t colX = 0; colX < numCols; colX++) {
+ if ((rowX == aRowIndex) && (colX == aColIndex)) {
+ if (aInsert) { // put in the new cells
+ for (int32_t cellX = 0; cellX < numNewCells; cellX++) {
+ nsTableCellFrame* cell = aCellFrames->ElementAt(cellX);
+ if (cell) {
+ AppendCell(aMap, cell, rowX, false, 0, damageArea);
+ }
+ }
+ }
+ else {
+ continue; // do not put the deleted cell back
+ }
+ }
+ // put in the original cell from the cell map
+ CellData* data = row.SafeElementAt(colX);
+ if (data && data->IsOrig()) {
+ AppendCell(aMap, data->GetCellFrame(), rowX, false, 0, damageArea);
+ }
+ }
+ }
+ if (aInsert && numOrigRows <= aRowIndex) { // append the new cells below the last original row
+ NS_ASSERTION (numOrigRows == aRowIndex, "Appending cells far beyond the last row");
+ for (int32_t cellX = 0; cellX < numNewCells; cellX++) {
+ nsTableCellFrame* cell = aCellFrames->ElementAt(cellX);
+ if (cell) {
+ AppendCell(aMap, cell, aRowIndex, false, 0, damageArea);
+ }
+ }
+ }
+
+ // delete the old cell map
+ for (rowX = 0; rowX < numOrigRows; rowX++) {
+ CellDataArray& row = origRows[rowX];
+ uint32_t len = row.Length();
+ for (uint32_t colX = 0; colX < len; colX++) {
+ DestroyCellData(row.SafeElementAt(colX));
+ }
+ }
+ // expand the cellmap to cover empty content rows
+ if (mRows.Length() < uint32_t(mContentRowCount)) {
+ Grow(aMap, mContentRowCount - mRows.Length());
+ }
+
+}
+
+void nsCellMap::RemoveCell(nsTableCellMap& aMap,
+ nsTableCellFrame* aCellFrame,
+ int32_t aRowIndex,
+ int32_t aRgFirstRowIndex,
+ TableArea& aDamageArea)
+{
+ uint32_t numRows = mRows.Length();
+ if (uint32_t(aRowIndex) >= numRows) {
+ NS_ERROR("bad arg in nsCellMap::RemoveCell");
+ return;
+ }
+ int32_t numCols = aMap.GetColCount();
+
+ // Now aRowIndex is guaranteed OK.
+
+ // get the starting col index of the cell to remove
+ int32_t startColIndex;
+ for (startColIndex = 0; startColIndex < numCols; startColIndex++) {
+ CellData* data = mRows[aRowIndex].SafeElementAt(startColIndex);
+ if (data && (data->IsOrig()) && (aCellFrame == data->GetCellFrame())) {
+ break; // we found the col index
+ }
+ }
+
+ int32_t rowSpan = GetRowSpan(aRowIndex, startColIndex, false);
+ // record whether removing the cells is going to cause complications due
+ // to existing row spans, col spans or table sizing.
+ bool spansCauseRebuild = CellsSpanInOrOut(aRowIndex,
+ aRowIndex + rowSpan - 1,
+ startColIndex, numCols - 1);
+ // XXX if the cell has a col span to the end of the map, and the end has no originating
+ // cells, we need to assume that this the only such cell, and rebuild so that there are
+ // no extraneous cols at the end. The same is true for removing rows.
+ if (!aCellFrame->GetRowSpan() || !aCellFrame->GetColSpan())
+ spansCauseRebuild = true;
+
+ if (spansCauseRebuild) {
+ aMap.RebuildConsideringCells(this, nullptr, aRowIndex, startColIndex, false,
+ aDamageArea);
+ }
+ else {
+ ShrinkWithoutCell(aMap, *aCellFrame, aRowIndex, startColIndex,
+ aRgFirstRowIndex, aDamageArea);
+ }
+}
+
+#ifdef DEBUG
+void nsCellMap::Dump(bool aIsBorderCollapse) const
+{
+ printf("\n ***** START GROUP CELL MAP DUMP ***** %p\n", (void*)this);
+ nsTableRowGroupFrame* rg = GetRowGroup();
+ const nsStyleDisplay* display = rg->StyleDisplay();
+ switch (display->mDisplay) {
+ case StyleDisplay::TableHeaderGroup:
+ printf(" thead ");
+ break;
+ case StyleDisplay::TableFooterGroup:
+ printf(" tfoot ");
+ break;
+ case StyleDisplay::TableRowGroup:
+ printf(" tbody ");
+ break;
+ default:
+ printf("HUH? wrong display type on rowgroup");
+ }
+ uint32_t mapRowCount = mRows.Length();
+ printf("mapRowCount=%u tableRowCount=%d\n", mapRowCount, mContentRowCount);
+
+
+ uint32_t rowIndex, colIndex;
+ for (rowIndex = 0; rowIndex < mapRowCount; rowIndex++) {
+ const CellDataArray& row = mRows[rowIndex];
+ printf(" row %d : ", rowIndex);
+ uint32_t colCount = row.Length();
+ for (colIndex = 0; colIndex < colCount; colIndex++) {
+ CellData* cd = row[colIndex];
+ if (cd) {
+ if (cd->IsOrig()) {
+ printf("C%d,%d ", rowIndex, colIndex);
+ } else {
+ if (cd->IsRowSpan()) {
+ printf("R ");
+ }
+ if (cd->IsColSpan()) {
+ printf("C ");
+ }
+ if (!(cd->IsRowSpan() && cd->IsColSpan())) {
+ printf(" ");
+ }
+ printf(" ");
+ }
+ } else {
+ printf("---- ");
+ }
+ }
+ if (aIsBorderCollapse) {
+ nscoord size;
+ BCBorderOwner owner;
+ LogicalSide side;
+ bool segStart;
+ bool bevel;
+ for (int32_t i = 0; i <= 2; i++) {
+ printf("\n ");
+ for (colIndex = 0; colIndex < colCount; colIndex++) {
+ BCCellData* cd = (BCCellData *)row[colIndex];
+ if (cd) {
+ if (0 == i) {
+ size = cd->mData.GetBStartEdge(owner, segStart);
+ printf("t=%d%d%d ", int32_t(size), owner, segStart);
+ }
+ else if (1 == i) {
+ size = cd->mData.GetIStartEdge(owner, segStart);
+ printf("l=%d%d%d ", int32_t(size), owner, segStart);
+ }
+ else {
+ size = cd->mData.GetCorner(side, bevel);
+ printf("c=%d%d%d ", int32_t(size), side, bevel);
+ }
+ }
+ }
+ }
+ }
+ printf("\n");
+ }
+
+ // output info mapping Ci,j to cell address
+ uint32_t cellCount = 0;
+ for (uint32_t rIndex = 0; rIndex < mapRowCount; rIndex++) {
+ const CellDataArray& row = mRows[rIndex];
+ uint32_t colCount = row.Length();
+ printf(" ");
+ for (colIndex = 0; colIndex < colCount; colIndex++) {
+ CellData* cd = row[colIndex];
+ if (cd) {
+ if (cd->IsOrig()) {
+ nsTableCellFrame* cellFrame = cd->GetCellFrame();
+ int32_t cellFrameColIndex;
+ cellFrame->GetColIndex(cellFrameColIndex);
+ printf("C%d,%d=%p(%d) ", rIndex, colIndex, (void*)cellFrame,
+ cellFrameColIndex);
+ cellCount++;
+ }
+ }
+ }
+ printf("\n");
+ }
+
+ printf(" ***** END GROUP CELL MAP DUMP *****\n");
+}
+#endif
+
+CellData*
+nsCellMap::GetDataAt(int32_t aMapRowIndex,
+ int32_t aColIndex) const
+{
+ return
+ mRows.SafeElementAt(aMapRowIndex, *sEmptyRow).SafeElementAt(aColIndex);
+}
+
+// only called if the cell at aMapRowIndex, aColIndex is null or dead
+// (the latter from ExpandZeroColSpans (XXXmats which has now been removed -
+// are there other ways cells may be dead?)).
+void nsCellMap::SetDataAt(nsTableCellMap& aMap,
+ CellData& aNewCell,
+ int32_t aMapRowIndex,
+ int32_t aColIndex)
+{
+ NS_ASSERTION(!!aMap.mBCInfo == mIsBC, "BC state mismatch");
+ if (uint32_t(aMapRowIndex) >= mRows.Length()) {
+ NS_ERROR("SetDataAt called with row index > num rows");
+ return;
+ }
+
+ CellDataArray& row = mRows[aMapRowIndex];
+
+ // the table map may need cols added
+ int32_t numColsToAdd = aColIndex + 1 - aMap.GetColCount();
+ if (numColsToAdd > 0) {
+ aMap.AddColsAtEnd(numColsToAdd);
+ }
+ // the row may need cols added
+ numColsToAdd = aColIndex + 1 - row.Length();
+ if (numColsToAdd > 0) {
+ // XXXbz need to handle allocation failures.
+ GrowRow(row, numColsToAdd);
+ }
+
+ DestroyCellData(row[aColIndex]);
+
+ row.ReplaceElementsAt(aColIndex, 1, &aNewCell);
+ // update the originating cell counts if cell originates in this row, col
+ nsColInfo* colInfo = aMap.GetColInfoAt(aColIndex);
+ if (colInfo) {
+ if (aNewCell.IsOrig()) {
+ colInfo->mNumCellsOrig++;
+ }
+ else if (aNewCell.IsColSpan()) {
+ colInfo->mNumCellsSpan++;
+ }
+ }
+ else NS_ERROR("SetDataAt called with col index > table map num cols");
+}
+
+nsTableCellFrame*
+nsCellMap::GetCellInfoAt(const nsTableCellMap& aMap,
+ int32_t aRowX,
+ int32_t aColX,
+ bool* aOriginates,
+ int32_t* aColSpan) const
+{
+ if (aOriginates) {
+ *aOriginates = false;
+ }
+ CellData* data = GetDataAt(aRowX, aColX);
+ nsTableCellFrame* cellFrame = nullptr;
+ if (data) {
+ if (data->IsOrig()) {
+ cellFrame = data->GetCellFrame();
+ if (aOriginates)
+ *aOriginates = true;
+ }
+ else {
+ cellFrame = GetCellFrame(aRowX, aColX, *data, true);
+ }
+ if (cellFrame && aColSpan) {
+ int32_t initialColIndex;
+ cellFrame->GetColIndex(initialColIndex);
+ *aColSpan = GetEffectiveColSpan(aMap, aRowX, initialColIndex);
+ }
+ }
+ return cellFrame;
+}
+
+
+bool nsCellMap::RowIsSpannedInto(int32_t aRowIndex,
+ int32_t aNumEffCols) const
+{
+ if ((0 > aRowIndex) || (aRowIndex >= mContentRowCount)) {
+ return false;
+ }
+ for (int32_t colIndex = 0; colIndex < aNumEffCols; colIndex++) {
+ CellData* cd = GetDataAt(aRowIndex, colIndex);
+ if (cd) { // there's really a cell at (aRowIndex, colIndex)
+ if (cd->IsSpan()) { // the cell at (aRowIndex, colIndex) is the result of a span
+ if (cd->IsRowSpan() && GetCellFrame(aRowIndex, colIndex, *cd, true)) { // XXX why the last check
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool nsCellMap::RowHasSpanningCells(int32_t aRowIndex,
+ int32_t aNumEffCols) const
+{
+ if ((0 > aRowIndex) || (aRowIndex >= mContentRowCount)) {
+ return false;
+ }
+ if (aRowIndex != mContentRowCount - 1) {
+ // aRowIndex is not the last row, so we check the next row after aRowIndex for spanners
+ for (int32_t colIndex = 0; colIndex < aNumEffCols; colIndex++) {
+ CellData* cd = GetDataAt(aRowIndex, colIndex);
+ if (cd && (cd->IsOrig())) { // cell originates
+ CellData* cd2 = GetDataAt(aRowIndex + 1, colIndex);
+ if (cd2 && cd2->IsRowSpan()) { // cd2 is spanned by a row
+ if (cd->GetCellFrame() == GetCellFrame(aRowIndex + 1, colIndex, *cd2, true)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+void nsCellMap::DestroyCellData(CellData* aData)
+{
+ if (!aData) {
+ return;
+ }
+
+ if (mIsBC) {
+ BCCellData* bcData = static_cast<BCCellData*>(aData);
+ bcData->~BCCellData();
+ mPresContext->FreeToShell(sizeof(BCCellData), bcData);
+ } else {
+ aData->~CellData();
+ mPresContext->FreeToShell(sizeof(CellData), aData);
+ }
+}
+
+CellData* nsCellMap::AllocCellData(nsTableCellFrame* aOrigCell)
+{
+ if (mIsBC) {
+ BCCellData* data = (BCCellData*)
+ mPresContext->AllocateFromShell(sizeof(BCCellData));
+ if (data) {
+ new (data) BCCellData(aOrigCell);
+ }
+ return data;
+ }
+
+ CellData* data = (CellData*)
+ mPresContext->AllocateFromShell(sizeof(CellData));
+ if (data) {
+ new (data) CellData(aOrigCell);
+ }
+ return data;
+}
+
+void
+nsCellMapColumnIterator::AdvanceRowGroup()
+{
+ do {
+ mCurMapStart += mCurMapContentRowCount;
+ mCurMap = mCurMap->GetNextSibling();
+ if (!mCurMap) {
+ // Set mCurMapContentRowCount and mCurMapRelevantRowCount to 0 in case
+ // mCurMap has no next sibling. This can happen if we just handled the
+ // last originating cell. Future calls will end up with mFoundCells ==
+ // mOrigCells, but for this one mFoundCells was definitely not big enough
+ // if we got here.
+ mCurMapContentRowCount = 0;
+ mCurMapRelevantRowCount = 0;
+ break;
+ }
+
+ mCurMapContentRowCount = mCurMap->GetRowCount();
+ uint32_t rowArrayLength = mCurMap->mRows.Length();
+ mCurMapRelevantRowCount = std::min(mCurMapContentRowCount, rowArrayLength);
+ } while (0 == mCurMapRelevantRowCount);
+
+ NS_ASSERTION(mCurMapRelevantRowCount != 0 || !mCurMap,
+ "How did that happen?");
+
+ // Set mCurMapRow to 0, since cells can't span across table row groups.
+ mCurMapRow = 0;
+}
+
+void
+nsCellMapColumnIterator::IncrementRow(int32_t aIncrement)
+{
+ NS_PRECONDITION(aIncrement >= 0, "Bogus increment");
+ NS_PRECONDITION(mCurMap, "Bogus mOrigCells?");
+ if (aIncrement == 0) {
+ AdvanceRowGroup();
+ }
+ else {
+ mCurMapRow += aIncrement;
+ if (mCurMapRow >= mCurMapRelevantRowCount) {
+ AdvanceRowGroup();
+ }
+ }
+}
+
+nsTableCellFrame*
+nsCellMapColumnIterator::GetNextFrame(int32_t* aRow, int32_t* aColSpan)
+{
+ // Fast-path for the case when we don't have anything left in the column and
+ // we know it.
+ if (mFoundCells == mOrigCells) {
+ *aRow = 0;
+ *aColSpan = 1;
+ return nullptr;
+ }
+
+ while (1) {
+ NS_ASSERTION(mCurMapRow < mCurMapRelevantRowCount, "Bogus mOrigCells?");
+ // Safe to just get the row (which is faster than calling GetDataAt(), but
+ // there may not be that many cells in it, so have to use SafeElementAt for
+ // the mCol.
+ const nsCellMap::CellDataArray& row = mCurMap->mRows[mCurMapRow];
+ CellData* cellData = row.SafeElementAt(mCol);
+ if (!cellData || cellData->IsDead()) {
+ // Could hit this if there are fewer cells in this row than others, for
+ // example.
+ IncrementRow(1);
+ continue;
+ }
+
+ if (cellData->IsColSpan()) {
+ // Look up the originating data for this cell, advance by its relative rowspan.
+ int32_t rowspanOffset = cellData->GetRowSpanOffset();
+ nsTableCellFrame* cellFrame = mCurMap->GetCellFrame(mCurMapRow, mCol, *cellData, false);
+ NS_ASSERTION(cellFrame,"Must have usable originating data here");
+ int32_t rowSpan = cellFrame->GetRowSpan();
+ if (rowSpan == 0) {
+ AdvanceRowGroup();
+ }
+ else {
+ IncrementRow(rowSpan - rowspanOffset);
+ }
+ continue;
+ }
+
+ NS_ASSERTION(cellData->IsOrig(),
+ "Must have originating cellData by this point. "
+ "See comment on mCurMapRow in header.");
+
+ nsTableCellFrame* cellFrame = cellData->GetCellFrame();
+ NS_ASSERTION(cellFrame, "Orig data without cellframe?");
+
+ *aRow = mCurMapStart + mCurMapRow;
+ *aColSpan = mCurMap->GetEffectiveColSpan(*mMap, mCurMapRow, mCol);
+
+ IncrementRow(cellFrame->GetRowSpan());
+
+ ++mFoundCells;
+
+ MOZ_ASSERT(cellData == mMap->GetDataAt(*aRow, mCol),
+ "Giving caller bogus row?");
+
+ return cellFrame;
+ }
+
+ NS_NOTREACHED("Can't get here");
+ return nullptr;
+}
diff --git a/layout/tables/nsCellMap.h b/layout/tables/nsCellMap.h
new file mode 100644
index 0000000000..991343aa71
--- /dev/null
+++ b/layout/tables/nsCellMap.h
@@ -0,0 +1,668 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsCellMap_h__
+#define nsCellMap_h__
+
+#include "nscore.h"
+#include "celldata.h"
+#include "nsTArray.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsAlgorithm.h"
+#include "nsRect.h"
+#include <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);
+};
+
+enum Corner
+{
+ eBStartIStart = 0,
+ eBStartIEnd = 1,
+ eBEndIEnd = 2,
+ eBEndIStart = 3
+};
+
+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 aYPos,
+ uint32_t aXPos,
+ bool aIsBEndIEnd = false);
+
+ void SetBCBorderEdge(mozilla::LogicalSide aEdge,
+ nsCellMap& aCellMap,
+ uint32_t aCellMapStart,
+ uint32_t aYPos,
+ uint32_t aXPos,
+ uint32_t aLength,
+ BCBorderOwner aOwner,
+ nscoord aSize,
+ bool aChanged);
+
+ void SetBCBorderCorner(::Corner aCorner,
+ nsCellMap& aCellMap,
+ uint32_t aCellMapStart,
+ uint32_t aYPos,
+ uint32_t aXPos,
+ mozilla::LogicalSide aOwner,
+ nscoord aSubSize,
+ bool aBevel,
+ bool aIsBottomRight = false);
+
+ /** dump a representation of the cell map to stdout for debugging */
+#ifdef DEBUG
+ void Dump(char* aString = nullptr) const;
+#endif
+
+protected:
+ BCData* GetIEndMostBorder(int32_t aRowIndex);
+ BCData* GetBEndMostBorder(int32_t aColIndex);
+
+ friend class nsCellMap;
+ friend class BCMapCellIterator;
+ friend class BCPaintBorderIterator;
+ friend class nsCellMapColumnIterator;
+
+/** Insert a row group cellmap after aPrevMap, if aPrefMap is null insert it
+ * at the beginning, the ordering of the cellmap corresponds to the ordering of
+ * rowgroups once OrderRowGroups has been called
+ */
+ void InsertGroupCellMap(nsCellMap* aPrevMap,
+ nsCellMap& aNewMap);
+ void DeleteIEndBEndBorders();
+
+ nsTableFrame& mTableFrame;
+ AutoTArray<nsColInfo, 8> mCols;
+ nsCellMap* mFirstMap;
+ // border collapsing info
+ BCInfo* mBCInfo;
+};
+
+/** nsCellMap is a support class for nsTablePart.
+ * It maintains an Rows x Columns grid onto which the cells of the table are mapped.
+ * This makes processing of rowspan and colspan attributes much easier.
+ * Each cell is represented by a CellData object.
+ *
+ * @see CellData
+ * @see nsTableFrame::AddCellToMap
+ * @see nsTableFrame::GrowCellMap
+ * @see nsTableFrame::BuildCellIntoMap
+ *
+ * mRows is an array of rows. Each row is an array of cells. a cell
+ * can be null.
+ */
+class nsCellMap
+{
+ typedef mozilla::TableArea TableArea;
+
+public:
+ /** constructor
+ * @param aRowGroupFrame the row group frame this is a cellmap for
+ * @param aIsBC whether the table is doing border-collapse
+ */
+ nsCellMap(nsTableRowGroupFrame* aRowGroupFrame, bool aIsBC);
+
+ /** destructor
+ * NOT VIRTUAL BECAUSE THIS CLASS SHOULD **NEVER** BE SUBCLASSED
+ */
+ ~nsCellMap();
+
+ static void Init();
+ static void Shutdown();
+
+ nsCellMap* GetNextSibling() const;
+ void SetNextSibling(nsCellMap* aSibling);
+
+ nsTableRowGroupFrame* GetRowGroup() const;
+
+ nsTableCellFrame* GetCellFrame(int32_t aRowIndex,
+ int32_t aColIndex,
+ CellData& aData,
+ bool aUseRowSpanIfOverlap) const;
+
+ /**
+ * Returns highest cell index within the cell map.
+ *
+ * @param aColCount [in] the number of columns in the table
+ */
+ int32_t GetHighestIndex(int32_t aColCount);
+
+ /**
+ * Returns the index of the given row and column coordinates.
+ *
+ * @see nsITableLayout::GetIndexByRowAndColumn()
+ *
+ * @param aColCount [in] the number of columns in the table
+ * @param aRow [in] the row coordinate
+ * @param aColumn [in] the column coordinate
+ */
+ int32_t GetIndexByRowAndColumn(int32_t aColCount,
+ int32_t aRow, int32_t aColumn) const;
+
+ /**
+ * Get the row and column coordinates at the given index.
+ *
+ * @see nsITableLayout::GetRowAndColumnByIndex()
+ *
+ * @param aColCount [in] the number of columns in the table
+ * @param aIndex [in] the index for which coordinates are to be retrieved
+ * @param aRow [out] the row coordinate to be returned
+ * @param aColumn [out] the column coordinate to be returned
+ */
+ void GetRowAndColumnByIndex(int32_t aColCount, int32_t aIndex,
+ int32_t *aRow, int32_t *aColumn) const;
+
+ /** append the cellFrame at an empty or dead cell or finally at the end of
+ * the row at aRowIndex and return a pointer to the celldata entry in the
+ * cellmap
+ *
+ * @param aMap - reference to the table cell map
+ * @param aCellFrame - a pointer to the cellframe which will be appended
+ * to the row
+ * @param aRowIndex - to this row the celldata entry will be added
+ * @param aRebuildIfNecessay - if a cell spans into a row below it might be
+ * necesserary to rebuild the cellmap as this rowspan
+ * might overlap another cell.
+ * @param aDamageArea - area in cellmap coordinates which have been updated.
+ * @param aColToBeginSearch - if not null contains the column number where
+ * the search for a empty or dead cell in the
+ * row should start
+ * @return - a pointer to the celldata entry inserted into
+ * the cellmap
+ */
+ CellData* AppendCell(nsTableCellMap& aMap,
+ nsTableCellFrame* aCellFrame,
+ int32_t aRowIndex,
+ bool aRebuildIfNecessary,
+ int32_t aRgFirstRowIndex,
+ TableArea& aDamageArea,
+ int32_t* aBeginSearchAtCol = nullptr);
+
+ void InsertCells(nsTableCellMap& aMap,
+ nsTArray<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)
+ {
+ NS_PRECONDITION(aMap, "Must have map");
+ NS_PRECONDITION(mCol < aMap->GetColCount(), "Invalid column");
+ mOrigCells = aMap->GetNumCellsOriginatingInCol(mCol);
+ if (mCurMap) {
+ mCurMapContentRowCount = mCurMap->GetRowCount();
+ uint32_t rowArrayLength = mCurMap->mRows.Length();
+ mCurMapRelevantRowCount = std::min(mCurMapContentRowCount, rowArrayLength);
+ if (mCurMapRelevantRowCount == 0 && mOrigCells > 0) {
+ // This row group is useless; advance!
+ AdvanceRowGroup();
+ }
+ }
+#ifdef DEBUG
+ else {
+ NS_ASSERTION(mOrigCells == 0, "Why no rowgroups?");
+ }
+#endif
+ }
+
+ nsTableCellFrame* GetNextFrame(int32_t* aRow, int32_t* aColSpan);
+
+private:
+ void AdvanceRowGroup();
+
+ // Advance the row; aIncrement is considered to be a cell's rowspan,
+ // so if 0 is passed in we'll advance to the next rowgroup.
+ void IncrementRow(int32_t aIncrement);
+
+ const nsTableCellMap* mMap;
+ const nsCellMap* mCurMap;
+
+ // mCurMapStart is the row in the entire nsTableCellMap where
+ // mCurMap starts. This is used to compute row indices to pass to
+ // nsTableCellMap::GetDataAt, so must be a _content_ row index.
+ uint32_t mCurMapStart;
+
+ // In steady-state mCurMapRow is the row in our current nsCellMap
+ // that we'll use the next time GetNextFrame() is called. Due to
+ // the way we skip over rowspans, the entry in mCurMapRow and mCol
+ // is either null, dead, originating, or a colspan. In particular,
+ // it cannot be a rowspan or overlap entry.
+ uint32_t mCurMapRow;
+ const int32_t mCol;
+ uint32_t mOrigCells;
+ uint32_t mFoundCells;
+
+ // The number of content rows in mCurMap. This may be bigger than the number
+ // of "relevant" rows, or it might be smaller.
+ uint32_t mCurMapContentRowCount;
+
+ // The number of "relevant" rows in mCurMap. That is, the number of rows
+ // which might have an originating cell in them. Once mCurMapRow reaches
+ // mCurMapRelevantRowCount, we should move to the next map.
+ uint32_t mCurMapRelevantRowCount;
+};
+
+
+/* ----- inline methods ----- */
+inline int32_t nsTableCellMap::GetColCount() const
+{
+ return mCols.Length();
+}
+
+inline nsCellMap* nsCellMap::GetNextSibling() const
+{
+ return mNextSibling;
+}
+
+inline void nsCellMap::SetNextSibling(nsCellMap* aSibling)
+{
+ mNextSibling = aSibling;
+}
+
+inline nsTableRowGroupFrame* nsCellMap::GetRowGroup() const
+{
+ return mRowGroupFrame;
+}
+
+inline int32_t nsCellMap::GetRowCount(bool aConsiderDeadRowSpanRows) const
+{
+ int32_t rowCount = (aConsiderDeadRowSpanRows) ? mRows.Length() : mContentRowCount;
+ return rowCount;
+}
+
+// nsColInfo
+
+inline nsColInfo::nsColInfo()
+ :mNumCellsOrig(0), mNumCellsSpan(0)
+{}
+
+inline nsColInfo::nsColInfo(int32_t aNumCellsOrig,
+ int32_t aNumCellsSpan)
+ :mNumCellsOrig(aNumCellsOrig), mNumCellsSpan(aNumCellsSpan)
+{}
+
+
+#endif
diff --git a/layout/tables/nsITableCellLayout.h b/layout/tables/nsITableCellLayout.h
new file mode 100644
index 0000000000..e761d76be1
--- /dev/null
+++ b/layout/tables/nsITableCellLayout.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsITableCellLayout_h__
+#define nsITableCellLayout_h__
+
+#include "nsQueryFrame.h"
+
+/**
+ * nsITableCellLayout
+ * interface for layout objects that act like table cells.
+ *
+ * @author sclark
+ */
+class nsITableCellLayout
+{
+public:
+
+ NS_DECL_QUERYFRAME_TARGET(nsITableCellLayout)
+
+ /** return the mapped cell's row and column indexes (starting at 0 for each) */
+ NS_IMETHOD GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex)=0;
+
+ /** return the mapped cell's row index (starting at 0 for the first row) */
+ virtual nsresult GetRowIndex(int32_t &aRowIndex) const = 0;
+
+ /** return the mapped cell's column index (starting at 0 for the first column) */
+ virtual nsresult GetColIndex(int32_t &aColIndex) const = 0;
+};
+
+#endif
+
+
+
diff --git a/layout/tables/nsITableLayoutStrategy.h b/layout/tables/nsITableLayoutStrategy.h
new file mode 100644
index 0000000000..d7d694096a
--- /dev/null
+++ b/layout/tables/nsITableLayoutStrategy.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * interface for the set of algorithms that determine column and table
+ * isizes
+ */
+
+#ifndef nsITableLayoutStrategy_h_
+#define nsITableLayoutStrategy_h_
+
+#include "nscore.h"
+#include "nsCoord.h"
+
+class nsRenderingContext;
+namespace mozilla {
+struct ReflowInput;
+} // namespace mozilla
+
+class nsITableLayoutStrategy
+{
+public:
+ using ReflowInput = mozilla::ReflowInput;
+
+ virtual ~nsITableLayoutStrategy() {}
+
+ /** Implement nsIFrame::GetMinISize for the table */
+ virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) = 0;
+
+ /** Implement nsIFrame::GetPrefISize for the table */
+ virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext,
+ bool aComputingSize) = 0;
+
+ /** Implement nsIFrame::MarkIntrinsicISizesDirty for the table */
+ virtual void MarkIntrinsicISizesDirty() = 0;
+
+ /**
+ * Compute final column isizes based on the intrinsic isize data and
+ * the available isize.
+ */
+ virtual void ComputeColumnISizes(const ReflowInput& aReflowInput) = 0;
+
+ /**
+ * Return the type of table layout strategy, without the cost of
+ * a virtual function call
+ */
+ enum Type { Auto, Fixed };
+ Type GetType() const { return mType; }
+
+protected:
+ explicit nsITableLayoutStrategy(Type aType) : mType(aType) {}
+private:
+ Type mType;
+};
+
+#endif /* !defined(nsITableLayoutStrategy_h_) */
diff --git a/layout/tables/nsTableCellFrame.cpp b/layout/tables/nsTableCellFrame.cpp
new file mode 100644
index 0000000000..316a966134
--- /dev/null
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -0,0 +1,1238 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTableCellFrame.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "nsTableFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsTablePainter.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsRenderingContext.h"
+#include "nsCSSRendering.h"
+#include "nsIContent.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsHTMLParts.h"
+#include "nsGkAtoms.h"
+#include "nsIPresShell.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMNode.h"
+#include "nsNameSpaceManager.h"
+#include "nsDisplayList.h"
+#include "nsLayoutUtils.h"
+#include "nsTextFrame.h"
+#include "FrameLayerBuilder.h"
+#include <algorithm>
+
+//TABLECELL SELECTION
+#include "nsFrameSelection.h"
+#include "mozilla/LookAndFeel.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+nsTableCellFrame::nsTableCellFrame(nsStyleContext* aContext,
+ nsTableFrame* aTableFrame)
+ : nsContainerFrame(aContext)
+ , mDesiredSize(aTableFrame->GetWritingMode())
+{
+ mColIndex = 0;
+ mPriorAvailISize = 0;
+
+ SetContentEmpty(false);
+ SetHasPctOverBSize(false);
+}
+
+nsTableCellFrame::~nsTableCellFrame()
+{
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableCellFrame)
+
+nsTableCellFrame*
+nsTableCellFrame::GetNextCell() const
+{
+ nsIFrame* childFrame = GetNextSibling();
+ while (childFrame) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
+ if (cellFrame) {
+ return cellFrame;
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ return nullptr;
+}
+
+void
+nsTableCellFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ // Let the base class do its initialization
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+
+ if (aPrevInFlow) {
+ // Set the column index
+ nsTableCellFrame* cellFrame = (nsTableCellFrame*)aPrevInFlow;
+ int32_t colIndex;
+ cellFrame->GetColIndex(colIndex);
+ SetColIndex(colIndex);
+ }
+}
+
+void
+nsTableCellFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
+ nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot);
+ }
+
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+// nsIPercentBSizeObserver methods
+
+void
+nsTableCellFrame::NotifyPercentBSize(const ReflowInput& aReflowInput)
+{
+ // ReflowInput ensures the mCBReflowInput of blocks inside a
+ // cell is the cell frame, not the inner-cell block, and that the
+ // containing block of an inner table is the containing block of its
+ // table wrapper.
+ // XXXldb Given the now-stricter |NeedsToObserve|, many if not all of
+ // these tests are probably unnecessary.
+
+ // Maybe the cell reflow state; we sure if we're inside the |if|.
+ const ReflowInput *cellRI = aReflowInput.mCBReflowInput;
+
+ if (cellRI && cellRI->mFrame == this &&
+ (cellRI->ComputedBSize() == NS_UNCONSTRAINEDSIZE ||
+ cellRI->ComputedBSize() == 0)) { // XXXldb Why 0?
+ // This is a percentage bsize on a frame whose percentage bsizes
+ // are based on the bsize of the cell, since its containing block
+ // is the inner cell frame.
+
+ // We'll only honor the percent bsize if sibling-cells/ancestors
+ // have specified/pct bsize. (Also, siblings only count for this if
+ // both this cell and the sibling cell span exactly 1 row.)
+
+ if (nsTableFrame::AncestorsHaveStyleBSize(*cellRI) ||
+ (GetTableFrame()->GetEffectiveRowSpan(*this) == 1 &&
+ cellRI->mParentReflowInput->mFrame->
+ HasAnyStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE))) {
+
+ for (const ReflowInput *rs = aReflowInput.mParentReflowInput;
+ rs != cellRI;
+ rs = rs->mParentReflowInput) {
+ rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+
+ nsTableFrame::RequestSpecialBSizeReflow(*cellRI);
+ }
+ }
+}
+
+// The cell needs to observe its block and things inside its block but nothing below that
+bool
+nsTableCellFrame::NeedsToObserve(const ReflowInput& aReflowInput)
+{
+ const ReflowInput *rs = aReflowInput.mParentReflowInput;
+ if (!rs)
+ return false;
+ if (rs->mFrame == this) {
+ // We always observe the child block. It will never send any
+ // notifications, but we need this so that the observer gets
+ // propagated to its kids.
+ return true;
+ }
+ rs = rs->mParentReflowInput;
+ if (!rs) {
+ return false;
+ }
+
+ // We always need to let the percent bsize observer be propagated
+ // from a table wrapper frame to an inner table frame.
+ nsIAtom *fType = aReflowInput.mFrame->GetType();
+ if (fType == nsGkAtoms::tableFrame) {
+ return true;
+ }
+
+ // We need the observer to be propagated to all children of the cell
+ // (i.e., children of the child block) in quirks mode, but only to
+ // tables in standards mode.
+ // XXX This may not be true in the case of orthogonal flows within
+ // the cell (bug 1174711 comment 8); we may need to observe isizes
+ // instead of bsizes for orthogonal children.
+ return rs->mFrame == this &&
+ (PresContext()->CompatibilityMode() == eCompatibility_NavQuirks ||
+ fType == nsGkAtoms::tableWrapperFrame);
+}
+
+nsresult
+nsTableCellFrame::GetRowIndex(int32_t &aRowIndex) const
+{
+ nsresult result;
+ nsTableRowFrame* row = static_cast<nsTableRowFrame*>(GetParent());
+ if (row) {
+ aRowIndex = row->GetRowIndex();
+ result = NS_OK;
+ }
+ else {
+ aRowIndex = 0;
+ result = NS_ERROR_NOT_INITIALIZED;
+ }
+ return result;
+}
+
+nsresult
+nsTableCellFrame::GetColIndex(int32_t &aColIndex) const
+{
+ if (GetPrevInFlow()) {
+ return static_cast<nsTableCellFrame*>(FirstInFlow())->GetColIndex(aColIndex);
+ }
+ else {
+ aColIndex = mColIndex;
+ return NS_OK;
+ }
+}
+
+nsresult
+nsTableCellFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // We need to recalculate in this case because of the nowrap quirk in
+ // BasicTableLayoutStrategy
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::nowrap &&
+ PresContext()->CompatibilityMode() == eCompatibility_NavQuirks) {
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
+ }
+ // let the table frame decide what to do
+ GetTableFrame()->AttributeChangedFor(this, mContent, aAttribute);
+ return NS_OK;
+}
+
+/* virtual */ void
+nsTableCellFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsContainerFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (!aOldStyleContext) //avoid this on init
+ return;
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse() &&
+ tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) {
+ int32_t colIndex, rowIndex;
+ GetColIndex(colIndex);
+ GetRowIndex(rowIndex);
+ // row span needs to be clamped as we do not create rows in the cellmap
+ // which do not have cells originating in them
+ TableArea damageArea(colIndex, rowIndex, GetColSpan(),
+ std::min(GetRowSpan(), tableFrame->GetRowCount() - rowIndex));
+ tableFrame->AddBCDamageArea(damageArea);
+ }
+}
+
+#ifdef DEBUG
+void
+nsTableCellFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ MOZ_CRASH("unsupported operation");
+}
+
+void
+nsTableCellFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ MOZ_CRASH("unsupported operation");
+}
+
+void
+nsTableCellFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ MOZ_CRASH("unsupported operation");
+}
+#endif
+
+void nsTableCellFrame::SetColIndex(int32_t aColIndex)
+{
+ mColIndex = aColIndex;
+}
+
+/* virtual */ nsMargin
+nsTableCellFrame::GetUsedMargin() const
+{
+ return nsMargin(0,0,0,0);
+}
+
+//ASSURE DIFFERENT COLORS for selection
+inline nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB)
+{
+ if (colorA == colorB)
+ {
+ nscolor res;
+ res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
+ NS_GET_G(colorA) ^ 0xff,
+ NS_GET_B(colorA) ^ 0xff);
+ return res;
+ }
+ return colorA;
+}
+
+void
+nsTableCellFrame::DecorateForSelection(DrawTarget* aDrawTarget, nsPoint aPt)
+{
+ NS_ASSERTION(IsSelected(), "Should only be called for selected cells");
+ int16_t displaySelection;
+ nsPresContext* presContext = PresContext();
+ displaySelection = DisplaySelection(presContext);
+ if (displaySelection) {
+ RefPtr<nsFrameSelection> frameSelection =
+ presContext->PresShell()->FrameSelection();
+
+ if (frameSelection->GetTableCellSelection()) {
+ nscolor bordercolor;
+ if (displaySelection == nsISelectionController::SELECTION_DISABLED) {
+ bordercolor = NS_RGB(176,176,176);// disabled color
+ }
+ else {
+ bordercolor =
+ LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
+ }
+ nscoord threePx = nsPresContext::CSSPixelsToAppUnits(3);
+ if ((mRect.width > threePx) && (mRect.height > threePx))
+ {
+ //compare bordercolor to ((nsStyleColor *)myColor)->mBackgroundColor)
+ bordercolor = EnsureDifferentColors(bordercolor,
+ StyleBackground()->mBackgroundColor);
+
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ Point devPixelOffset = NSPointToPoint(aPt, appUnitsPerDevPixel);
+
+ AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ aDrawTarget->SetTransform(
+ aDrawTarget->GetTransform().PreTranslate(devPixelOffset));
+
+ ColorPattern color(ToDeviceColor(bordercolor));
+
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ StrokeLineWithSnapping(nsPoint(onePixel, 0), nsPoint(mRect.width, 0),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(nsPoint(0, onePixel), nsPoint(0, mRect.height),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(nsPoint(onePixel, mRect.height),
+ nsPoint(mRect.width, mRect.height),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(nsPoint(mRect.width, onePixel),
+ nsPoint(mRect.width, mRect.height),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ //middle
+ nsRect r(onePixel, onePixel,
+ mRect.width - onePixel, mRect.height - onePixel);
+ Rect devPixelRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *aDrawTarget);
+ aDrawTarget->StrokeRect(devPixelRect, color);
+ //shading
+ StrokeLineWithSnapping(nsPoint(2*onePixel, mRect.height-2*onePixel),
+ nsPoint(mRect.width-onePixel, mRect.height- (2*onePixel)),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(nsPoint(mRect.width - (2*onePixel), 2*onePixel),
+ nsPoint(mRect.width - (2*onePixel), mRect.height-onePixel),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ }
+ }
+ }
+}
+
+DrawResult
+nsTableCellFrame::PaintBackground(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt,
+ uint32_t aFlags)
+{
+ nsRect rect(aPt, GetSize());
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*PresContext(),
+ aRenderingContext,
+ aDirtyRect, rect,
+ this, aFlags);
+ return nsCSSRendering::PaintBackground(params);
+}
+
+// Called by nsTablePainter
+DrawResult
+nsTableCellFrame::PaintCellBackground(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt,
+ uint32_t aFlags)
+{
+ if (!StyleVisibility()->IsVisible()) {
+ return DrawResult::SUCCESS;
+ }
+
+ return PaintBackground(aRenderingContext, aDirtyRect, aPt, aFlags);
+}
+
+nsresult
+nsTableCellFrame::ProcessBorders(nsTableFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists)
+{
+ const nsStyleBorder* borderStyle = StyleBorder();
+ if (aFrame->IsBorderCollapse() || !borderStyle->HasBorder())
+ return NS_OK;
+
+ if (!GetContentEmpty() ||
+ StyleTableBorder()->mEmptyCells == NS_STYLE_TABLE_EMPTY_CELLS_SHOW) {
+ aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplayBorder(aBuilder, this));
+ }
+
+ return NS_OK;
+}
+
+class nsDisplayTableCellBackground : public nsDisplayTableItem {
+public:
+ nsDisplayTableCellBackground(nsDisplayListBuilder* aBuilder,
+ nsTableCellFrame* aFrame) :
+ nsDisplayTableItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayTableCellBackground);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayTableCellBackground() {
+ MOZ_COUNT_DTOR(nsDisplayTableCellBackground);
+ }
+#endif
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override {
+ aOutFrames->AppendElement(mFrame);
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) override;
+ NS_DISPLAY_DECL_NAME("TableCellBackground", TYPE_TABLE_CELL_BACKGROUND)
+};
+
+void nsDisplayTableCellBackground::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawResult result = static_cast<nsTableCellFrame*>(mFrame)->
+ PaintBackground(*aCtx, mVisibleRect, ToReferenceFrame(),
+ aBuilder->GetBackgroundPaintFlags());
+
+ nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
+}
+
+nsRect
+nsDisplayTableCellBackground::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap)
+{
+ // revert from nsDisplayTableItem's implementation ... cell backgrounds
+ // don't overflow the cell
+ return nsDisplayItem::GetBounds(aBuilder, aSnap);
+}
+
+void nsTableCellFrame::InvalidateFrame(uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrame(aDisplayItemKey);
+ GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey);
+}
+
+void nsTableCellFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey);
+ // If we have filters applied that would affects our bounds, then
+ // we get an inactive layer created and this is computed
+ // within FrameLayerBuilder
+ GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey);
+}
+
+static void
+PaintTableCellSelection(nsIFrame* aFrame, DrawTarget* aDrawTarget,
+ const nsRect& aRect, nsPoint aPt)
+{
+ static_cast<nsTableCellFrame*>(aFrame)->DecorateForSelection(aDrawTarget,
+ aPt);
+}
+
+void
+nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame");
+ if (IsVisibleInSelection(aBuilder)) {
+ nsTableFrame* tableFrame = GetTableFrame();
+ int32_t emptyCellStyle = GetContentEmpty() && !tableFrame->IsBorderCollapse() ?
+ StyleTableBorder()->mEmptyCells
+ : NS_STYLE_TABLE_EMPTY_CELLS_SHOW;
+ // take account of 'empty-cells'
+ if (StyleVisibility()->IsVisible() &&
+ (NS_STYLE_TABLE_EMPTY_CELLS_HIDE != emptyCellStyle)) {
+ // display outset box-shadows if we need to.
+ bool hasBoxShadow = !!StyleEffects()->mBoxShadow;
+ if (hasBoxShadow) {
+ aLists.BorderBackground()->AppendNewToTop(
+ new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, this));
+ }
+
+ // display background if we need to.
+ if (aBuilder->IsForEventDelivery() ||
+ !StyleBackground()->IsTransparent() || StyleDisplay()->mAppearance) {
+ if (!tableFrame->IsBorderCollapse()) {
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder,
+ this,
+ GetRectRelativeToSelf(),
+ aLists.BorderBackground());
+ } else if (aBuilder->IsAtRootOfPseudoStackingContext() ||
+ aBuilder->IsForEventDelivery()) {
+ // The cell background was not painted by the nsTablePainter,
+ // so we need to do it. We have special background processing here
+ // so we need to duplicate some code from nsFrame::DisplayBorderBackgroundOutline
+ nsDisplayTableItem* item =
+ new (aBuilder) nsDisplayTableCellBackground(aBuilder, this);
+ aLists.BorderBackground()->AppendNewToTop(item);
+ item->UpdateForFrameBackground(this);
+ } else {
+ // The nsTablePainter will paint our background. Make sure it
+ // knows if we're background-attachment:fixed.
+ nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem();
+ if (currentItem) {
+ currentItem->UpdateForFrameBackground(this);
+ }
+ }
+ }
+
+ // display inset box-shadows if we need to.
+ if (hasBoxShadow) {
+ aLists.BorderBackground()->AppendNewToTop(
+ new (aBuilder) nsDisplayBoxShadowInner(aBuilder, this));
+ }
+
+ // display borders if we need to
+ ProcessBorders(tableFrame, aBuilder, aLists);
+
+ // and display the selection border if we need to
+ if (IsSelected()) {
+ aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this, ::PaintTableCellSelection,
+ "TableCellSelection",
+ nsDisplayItem::TYPE_TABLE_CELL_SELECTION));
+ }
+ }
+
+ // the 'empty-cells' property has no effect on 'outline'
+ DisplayOutline(aBuilder, aLists);
+ }
+
+ // Push a null 'current table item' so that descendant tables can't
+ // accidentally mess with our table
+ nsAutoPushCurrentTableItem pushTableItem;
+ pushTableItem.Push(aBuilder, nullptr);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ NS_ASSERTION(kid && !kid->GetNextSibling(), "Table cells should have just one child");
+ // The child's background will go in our BorderBackground() list.
+ // This isn't a problem since it won't have a real background except for
+ // event handling. We do not call BuildDisplayListForNonBlockChildren
+ // because that/ would put the child's background in the Content() list
+ // which isn't right (e.g., would end up on top of our child floats for
+ // event handling).
+ BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
+}
+
+nsIFrame::LogicalSides
+nsTableCellFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
+{
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return LogicalSides();
+ }
+
+ LogicalSides skip;
+ if (nullptr != GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (nullptr != GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+
+/* virtual */ nsMargin
+nsTableCellFrame::GetBorderOverflow()
+{
+ return nsMargin(0, 0, 0, 0);
+}
+
+// Align the cell's child frame within the cell
+
+void nsTableCellFrame::BlockDirAlignChild(WritingMode aWM, nscoord aMaxAscent)
+{
+ /* It's the 'border-collapse' on the table that matters */
+ LogicalMargin borderPadding = GetLogicalUsedBorderAndPadding(aWM);
+
+ nscoord bStartInset = borderPadding.BStart(aWM);
+ nscoord bEndInset = borderPadding.BEnd(aWM);
+
+ uint8_t verticalAlignFlags = GetVerticalAlign();
+
+ nscoord bSize = BSize(aWM);
+ nsIFrame* firstKid = mFrames.FirstChild();
+ nsSize containerSize = mRect.Size();
+ NS_ASSERTION(firstKid, "Frame construction error, a table cell always has "
+ "an inner cell frame");
+ LogicalRect kidRect = firstKid->GetLogicalRect(aWM, containerSize);
+ nscoord childBSize = kidRect.BSize(aWM);
+
+ // Vertically align the child
+ nscoord kidBStart = 0;
+ switch (verticalAlignFlags)
+ {
+ case NS_STYLE_VERTICAL_ALIGN_BASELINE:
+ // Align the baselines of the child frame with the baselines of
+ // other children in the same row which have 'vertical-align: baseline'
+ kidBStart = bStartInset + aMaxAscent - GetCellBaseline();
+ break;
+
+ case NS_STYLE_VERTICAL_ALIGN_TOP:
+ // Align the top of the child frame with the top of the content area,
+ kidBStart = bStartInset;
+ break;
+
+ case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
+ // Align the bottom of the child frame with the bottom of the content area,
+ kidBStart = bSize - childBSize - bEndInset;
+ break;
+
+ default:
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
+ // Align the middle of the child frame with the middle of the content area,
+ kidBStart = (bSize - childBSize - bEndInset + bStartInset) / 2;
+ }
+ // If the content is larger than the cell bsize, align from bStartInset
+ // (cell's content-box bstart edge).
+ kidBStart = std::max(bStartInset, kidBStart);
+
+ if (kidBStart != kidRect.BStart(aWM)) {
+ // Invalidate at the old position first
+ firstKid->InvalidateFrameSubtree();
+ }
+
+ firstKid->SetPosition(aWM, LogicalPoint(aWM, kidRect.IStart(aWM),
+ kidBStart), containerSize);
+ ReflowOutput desiredSize(aWM);
+ desiredSize.SetSize(aWM, GetLogicalSize(aWM));
+
+ nsRect overflow(nsPoint(0,0), GetSize());
+ overflow.Inflate(GetBorderOverflow());
+ desiredSize.mOverflowAreas.SetAllTo(overflow);
+ ConsiderChildOverflow(desiredSize.mOverflowAreas, firstKid);
+ FinishAndStoreOverflow(&desiredSize);
+ if (kidBStart != kidRect.BStart(aWM)) {
+ // Make sure any child views are correctly positioned. We know the inner table
+ // cell won't have a view
+ nsContainerFrame::PositionChildViews(firstKid);
+
+ // Invalidate new overflow rect
+ firstKid->InvalidateFrameSubtree();
+ }
+ if (HasView()) {
+ nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this,
+ GetView(),
+ desiredSize.VisualOverflow(), 0);
+ }
+}
+
+bool
+nsTableCellFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
+{
+ nsRect bounds(nsPoint(0,0), GetSize());
+ bounds.Inflate(GetBorderOverflow());
+
+ aOverflowAreas.UnionAllWith(bounds);
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+// Per CSS 2.1, we map 'sub', 'super', 'text-top', 'text-bottom',
+// length, percentage, and calc() values to 'baseline'.
+uint8_t
+nsTableCellFrame::GetVerticalAlign() const
+{
+ const nsStyleCoord& verticalAlign = StyleDisplay()->mVerticalAlign;
+ if (verticalAlign.GetUnit() == eStyleUnit_Enumerated) {
+ uint8_t value = verticalAlign.GetIntValue();
+ if (value == NS_STYLE_VERTICAL_ALIGN_TOP ||
+ value == NS_STYLE_VERTICAL_ALIGN_MIDDLE ||
+ value == NS_STYLE_VERTICAL_ALIGN_BOTTOM) {
+ return value;
+ }
+ }
+ return NS_STYLE_VERTICAL_ALIGN_BASELINE;
+}
+
+bool
+nsTableCellFrame::CellHasVisibleContent(nscoord height,
+ nsTableFrame* tableFrame,
+ nsIFrame* kidFrame)
+{
+ // see http://www.w3.org/TR/CSS21/tables.html#empty-cells
+ if (height > 0)
+ return true;
+ if (tableFrame->IsBorderCollapse())
+ return true;
+ for (nsIFrame* innerFrame : kidFrame->PrincipalChildList()) {
+ nsIAtom* frameType = innerFrame->GetType();
+ if (nsGkAtoms::textFrame == frameType) {
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(innerFrame);
+ if (textFrame->HasNoncollapsedCharacters())
+ return true;
+ }
+ else if (nsGkAtoms::placeholderFrame != frameType) {
+ return true;
+ }
+ else {
+ nsIFrame *floatFrame = nsLayoutUtils::GetFloatFromPlaceholder(innerFrame);
+ if (floatFrame)
+ return true;
+ }
+ }
+ return false;
+}
+
+nscoord
+nsTableCellFrame::GetCellBaseline() const
+{
+ // Ignore the position of the inner frame relative to the cell frame
+ // since we want the position as though the inner were top-aligned.
+ nsIFrame *inner = mFrames.FirstChild();
+ nscoord borderPadding = GetUsedBorderAndPadding().top;
+ nscoord result;
+ if (nsLayoutUtils::GetFirstLineBaseline(GetWritingMode(), inner, &result))
+ return result + borderPadding;
+ return inner->GetContentRectRelativeToSelf().YMost() +
+ borderPadding;
+}
+
+int32_t nsTableCellFrame::GetRowSpan()
+{
+ int32_t rowSpan=1;
+ nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
+
+ // Don't look at the content's rowspan if we're a pseudo cell
+ if (hc && !StyleContext()->GetPseudo()) {
+ const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::rowspan);
+ // Note that we don't need to check the tag name, because only table cells
+ // and table headers parse the "rowspan" attribute into an integer.
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ rowSpan = attr->GetIntegerValue();
+ }
+ }
+ return rowSpan;
+}
+
+int32_t nsTableCellFrame::GetColSpan()
+{
+ int32_t colSpan=1;
+ nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
+
+ // Don't look at the content's colspan if we're a pseudo cell
+ if (hc && !StyleContext()->GetPseudo()) {
+ const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::colspan);
+ // Note that we don't need to check the tag name, because only table cells
+ // and table headers parse the "colspan" attribute into an integer.
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ colSpan = attr->GetIntegerValue();
+ }
+ }
+ return colSpan;
+}
+
+/* virtual */ nscoord
+nsTableCellFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result = 0;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ nsIFrame *inner = mFrames.FirstChild();
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner,
+ nsLayoutUtils::MIN_ISIZE);
+ return result;
+}
+
+/* virtual */ nscoord
+nsTableCellFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result = 0;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ nsIFrame *inner = mFrames.FirstChild();
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner,
+ nsLayoutUtils::PREF_ISIZE);
+ return result;
+}
+
+/* virtual */ nsIFrame::IntrinsicISizeOffsetData
+nsTableCellFrame::IntrinsicISizeOffsets()
+{
+ IntrinsicISizeOffsetData result = nsContainerFrame::IntrinsicISizeOffsets();
+
+ result.hMargin = 0;
+ result.hPctMargin = 0;
+
+ WritingMode wm = GetWritingMode();
+ result.hBorder = GetBorderWidth(wm).IStartEnd(wm);
+
+ return result;
+}
+
+#ifdef DEBUG
+#define PROBABLY_TOO_LARGE 1000000
+static
+void DebugCheckChildSize(nsIFrame* aChild,
+ ReflowOutput& aMet)
+{
+ WritingMode wm = aMet.GetWritingMode();
+ if ((aMet.ISize(wm) < 0) || (aMet.ISize(wm) > PROBABLY_TOO_LARGE)) {
+ printf("WARNING: cell content %p has large inline size %d \n",
+ static_cast<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());
+
+ int32_t rowIndex;
+ firstCellInFlow->GetRowIndex(rowIndex);
+ int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(*firstCellInFlow);
+
+ nscoord computedBSize = firstTableInFlow->GetRowSpacing(rowIndex,
+ rowIndex + rowSpan - 1);
+ computedBSize -= aBlockDirBorderPadding;
+ int32_t rowX;
+ for (row = firstRGInFlow->GetFirstRow(), rowX = 0; row; row = row->GetNextRow(), rowX++) {
+ if (rowX > rowIndex + rowSpan - 1) {
+ break;
+ }
+ else if (rowX >= rowIndex) {
+ computedBSize += row->GetUnpaginatedBSize();
+ }
+ }
+ return computedBSize;
+}
+
+void
+nsTableCellFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableCellFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) {
+ FirstInFlow()->AddStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW);
+ }
+
+ // see if a special bsize reflow needs to occur due to having a pct height
+ nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
+
+ aStatus = NS_FRAME_COMPLETE;
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize availSize(wm, aReflowInput.AvailableISize(),
+ aReflowInput.AvailableBSize());
+
+ LogicalMargin borderPadding = aReflowInput.ComputedLogicalPadding();
+ LogicalMargin border = GetBorderWidth(wm);
+ borderPadding += border;
+
+ // reduce available space by insets, if we're in a constrained situation
+ availSize.ISize(wm) -= borderPadding.IStartEnd(wm);
+ if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
+ availSize.BSize(wm) -= borderPadding.BStartEnd(wm);
+ }
+
+ // Try to reflow the child into the available space. It might not
+ // fit or might need continuing.
+ if (availSize.BSize(wm) < 0) {
+ availSize.BSize(wm) = 1;
+ }
+
+ ReflowOutput kidSize(wm, aDesiredSize.mFlags);
+ kidSize.ClearSize();
+ SetPriorAvailISize(aReflowInput.AvailableISize());
+ nsIFrame* firstKid = mFrames.FirstChild();
+ NS_ASSERTION(firstKid, "Frame construction error, a table cell always has an inner cell frame");
+ nsTableFrame* tableFrame = GetTableFrame();
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) {
+ const_cast<ReflowInput&>(aReflowInput).
+ SetComputedBSize(BSize(wm) - borderPadding.BStartEnd(wm));
+ DISPLAY_REFLOW_CHANGE();
+ }
+ else if (aPresContext->IsPaginated()) {
+ nscoord computedUnpaginatedBSize =
+ CalcUnpaginatedBSize((nsTableCellFrame&)*this,
+ *tableFrame, borderPadding.BStartEnd(wm));
+ if (computedUnpaginatedBSize > 0) {
+ const_cast<ReflowInput&>(aReflowInput).SetComputedBSize(computedUnpaginatedBSize);
+ DISPLAY_REFLOW_CHANGE();
+ }
+ }
+ else {
+ SetHasPctOverBSize(false);
+ }
+
+ WritingMode kidWM = firstKid->GetWritingMode();
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, firstKid,
+ availSize.ConvertTo(kidWM, wm));
+
+ // Don't be a percent height observer if we're in the middle of
+ // special-bsize reflow, in case we get an accidental NotifyPercentBSize()
+ // call (which we shouldn't honor during special-bsize reflow)
+ if (!aReflowInput.mFlags.mSpecialBSizeReflow) {
+ // mPercentBSizeObserver is for children of cells in quirks mode,
+ // but only those than are tables in standards mode. NeedsToObserve
+ // will determine how far this is propagated to descendants.
+ kidReflowInput.mPercentBSizeObserver = this;
+ }
+ // Don't propagate special bsize reflow state to our kids
+ kidReflowInput.mFlags.mSpecialBSizeReflow = false;
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow ||
+ FirstInFlow()->HasAnyStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) {
+ // We need to force the kid to have mBResize set if we've had a
+ // special reflow in the past, since the non-special reflow needs to
+ // resize back to what it was without the special bsize reflow.
+ kidReflowInput.SetBResize(true);
+ }
+
+ nsSize containerSize =
+ aReflowInput.ComputedSizeAsContainerIfConstrained();
+
+ LogicalPoint kidOrigin(wm, borderPadding.IStart(wm),
+ borderPadding.BStart(wm));
+ nsRect origRect = firstKid->GetRect();
+ nsRect origVisualOverflow = firstKid->GetVisualOverflowRect();
+ bool firstReflow = firstKid->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+
+ ReflowChild(firstKid, aPresContext, kidSize, kidReflowInput,
+ wm, kidOrigin, containerSize, 0, aStatus);
+ if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) {
+ // Don't pass OVERFLOW_INCOMPLETE through tables until they can actually handle it
+ //XXX should paginate overflow as overflow, but not in this patch (bug 379349)
+ NS_FRAME_SET_INCOMPLETE(aStatus);
+ printf("Set table cell incomplete %p\n", static_cast<void*>(this));
+ }
+
+ // XXXbz is this invalidate actually needed, really?
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ InvalidateFrameSubtree();
+ }
+
+#ifdef DEBUG
+ DebugCheckChildSize(firstKid, kidSize);
+#endif
+
+ // 0 dimensioned cells need to be treated specially in Standard/NavQuirks mode
+ // see testcase "emptyCells.html"
+ nsIFrame* prevInFlow = GetPrevInFlow();
+ bool isEmpty;
+ if (prevInFlow) {
+ isEmpty = static_cast<nsTableCellFrame*>(prevInFlow)->GetContentEmpty();
+ } else {
+ isEmpty = !CellHasVisibleContent(kidSize.Height(), tableFrame, firstKid);
+ }
+ SetContentEmpty(isEmpty);
+
+ // Place the child
+ FinishReflowChild(firstKid, aPresContext, kidSize, &kidReflowInput,
+ wm, kidOrigin, containerSize, 0);
+
+ nsTableFrame::InvalidateTableFrame(firstKid, origRect, origVisualOverflow,
+ firstReflow);
+
+ // first, compute the bsize which can be set w/o being restricted by
+ // available bsize
+ LogicalSize cellSize(wm);
+ cellSize.BSize(wm) = kidSize.BSize(wm);
+
+ if (NS_UNCONSTRAINEDSIZE != cellSize.BSize(wm)) {
+ cellSize.BSize(wm) += borderPadding.BStartEnd(wm);
+ }
+
+ // next determine the cell's isize
+ cellSize.ISize(wm) = kidSize.ISize(wm); // at this point, we've factored in the cell's style attributes
+
+ // factor in border and padding
+ if (NS_UNCONSTRAINEDSIZE != cellSize.ISize(wm)) {
+ cellSize.ISize(wm) += borderPadding.IStartEnd(wm);
+ }
+
+ // set the cell's desired size and max element size
+ aDesiredSize.SetSize(wm, cellSize);
+
+ // the overflow area will be computed when BlockDirAlignChild() gets called
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) {
+ if (aDesiredSize.BSize(wm) > BSize(wm)) {
+ // set a bit indicating that the pct bsize contents exceeded
+ // the height that they could honor in the pass 2 reflow
+ SetHasPctOverBSize(true);
+ }
+ if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) {
+ aDesiredSize.BSize(wm) = BSize(wm);
+ }
+ }
+
+ // If our parent is in initial reflow, it'll handle invalidating our
+ // entire overflow rect.
+ if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
+ nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) {
+ InvalidateFrame();
+ }
+
+ // remember the desired size for this reflow
+ SetDesiredSize(aDesiredSize);
+
+ // Any absolutely-positioned children will get reflowed in
+ // nsFrame::FixupPositionedTableParts in another pass, so propagate our
+ // dirtiness to them before our parent clears our dirty bits.
+ PushDirtyBitToAbsoluteFrames();
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+/* ----- global methods ----- */
+
+NS_QUERYFRAME_HEAD(nsTableCellFrame)
+ NS_QUERYFRAME_ENTRY(nsTableCellFrame)
+ NS_QUERYFRAME_ENTRY(nsITableCellLayout)
+ NS_QUERYFRAME_ENTRY(nsIPercentBSizeObserver)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsTableCellFrame::AccessibleType()
+{
+ return a11y::eHTMLTableCellType;
+}
+#endif
+
+/* This is primarily for editor access via nsITableLayout */
+NS_IMETHODIMP
+nsTableCellFrame::GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex)
+{
+ nsresult res = GetRowIndex(aRowIndex);
+ if (NS_FAILED(res))
+ {
+ aColIndex = 0;
+ return res;
+ }
+ aColIndex = mColIndex;
+ return NS_OK;
+}
+
+nsTableCellFrame*
+NS_NewTableCellFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext,
+ nsTableFrame* aTableFrame)
+{
+ if (aTableFrame->IsBorderCollapse())
+ return new (aPresShell) nsBCTableCellFrame(aContext, aTableFrame);
+ else
+ return new (aPresShell) nsTableCellFrame(aContext, aTableFrame);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsBCTableCellFrame)
+
+LogicalMargin
+nsTableCellFrame::GetBorderWidth(WritingMode aWM) const
+{
+ return LogicalMargin(aWM, StyleBorder()->GetComputedBorder());
+}
+
+nsIAtom*
+nsTableCellFrame::GetType() const
+{
+ return nsGkAtoms::tableCellFrame;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTableCellFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("TableCell"), aResult);
+}
+#endif
+
+// nsBCTableCellFrame
+
+nsBCTableCellFrame::nsBCTableCellFrame(nsStyleContext* aContext,
+ nsTableFrame* aTableFrame)
+ : nsTableCellFrame(aContext, aTableFrame)
+{
+ mBStartBorder = mIEndBorder = mBEndBorder = mIStartBorder = 0;
+}
+
+nsBCTableCellFrame::~nsBCTableCellFrame()
+{
+}
+
+nsIAtom*
+nsBCTableCellFrame::GetType() const
+{
+ return nsGkAtoms::bcTableCellFrame;
+}
+
+/* virtual */ nsMargin
+nsBCTableCellFrame::GetUsedBorder() const
+{
+ WritingMode wm = GetWritingMode();
+ return GetBorderWidth(wm).GetPhysicalMargin(wm);
+}
+
+/* virtual */ bool
+nsBCTableCellFrame::GetBorderRadii(const nsSize& aFrameSize,
+ const nsSize& aBorderArea,
+ Sides aSkipSides,
+ nscoord aRadii[8]) const
+{
+ NS_FOR_CSS_HALF_CORNERS(corner) {
+ aRadii[corner] = 0;
+ }
+ return false;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsBCTableCellFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("BCTableCell"), aResult);
+}
+#endif
+
+LogicalMargin
+nsBCTableCellFrame::GetBorderWidth(WritingMode aWM) const
+{
+ int32_t pixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
+ return LogicalMargin(aWM,
+ BC_BORDER_END_HALF_COORD(pixelsToTwips, mBStartBorder),
+ BC_BORDER_START_HALF_COORD(pixelsToTwips, mIEndBorder),
+ BC_BORDER_START_HALF_COORD(pixelsToTwips, mBEndBorder),
+ BC_BORDER_END_HALF_COORD(pixelsToTwips, mIStartBorder));
+}
+
+BCPixelSize
+nsBCTableCellFrame::GetBorderWidth(LogicalSide aSide) const
+{
+ switch(aSide) {
+ case eLogicalSideBStart:
+ return BC_BORDER_END_HALF(mBStartBorder);
+ case eLogicalSideIEnd:
+ return BC_BORDER_START_HALF(mIEndBorder);
+ case eLogicalSideBEnd:
+ return BC_BORDER_START_HALF(mBEndBorder);
+ default:
+ return BC_BORDER_END_HALF(mIStartBorder);
+ }
+}
+
+void
+nsBCTableCellFrame::SetBorderWidth(LogicalSide aSide, BCPixelSize aValue)
+{
+ switch(aSide) {
+ case eLogicalSideBStart:
+ mBStartBorder = aValue;
+ break;
+ case eLogicalSideIEnd:
+ mIEndBorder = aValue;
+ break;
+ case eLogicalSideBEnd:
+ mBEndBorder = aValue;
+ break;
+ default:
+ mIStartBorder = aValue;
+ }
+}
+
+/* virtual */ nsMargin
+nsBCTableCellFrame::GetBorderOverflow()
+{
+ WritingMode wm = GetWritingMode();
+ int32_t p2t = nsPresContext::AppUnitsPerCSSPixel();
+ LogicalMargin halfBorder(wm,
+ BC_BORDER_START_HALF_COORD(p2t, mBStartBorder),
+ BC_BORDER_END_HALF_COORD(p2t, mIEndBorder),
+ BC_BORDER_END_HALF_COORD(p2t, mBEndBorder),
+ BC_BORDER_START_HALF_COORD(p2t, mIStartBorder));
+ return halfBorder.GetPhysicalMargin(wm);
+}
+
+
+DrawResult
+nsBCTableCellFrame::PaintBackground(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt,
+ uint32_t aFlags)
+{
+ // make border-width reflect the half of the border-collapse
+ // assigned border that's inside the cell
+ WritingMode wm = GetWritingMode();
+ nsMargin borderWidth = GetBorderWidth(wm).GetPhysicalMargin(wm);
+
+ nsStyleBorder myBorder(*StyleBorder());
+
+ NS_FOR_CSS_SIDES(side) {
+ myBorder.SetBorderWidth(side, borderWidth.Side(side));
+ }
+
+ // bypassing nsCSSRendering::PaintBackground is safe because this kind
+ // of frame cannot be used for the root element
+ nsRect rect(aPt, GetSize());
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*PresContext(),
+ aRenderingContext, aDirtyRect,
+ rect, this,
+ aFlags);
+ return nsCSSRendering::PaintBackgroundWithSC(params, StyleContext(), myBorder);
+}
diff --git a/layout/tables/nsTableCellFrame.h b/layout/tables/nsTableCellFrame.h
new file mode 100644
index 0000000000..6717e1b70d
--- /dev/null
+++ b/layout/tables/nsTableCellFrame.h
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTableCellFrame_h__
+#define nsTableCellFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "celldata.h"
+#include "imgIContainer.h"
+#include "nsITableCellLayout.h"
+#include "nscore.h"
+#include "nsContainerFrame.h"
+#include "nsStyleContext.h"
+#include "nsIPercentBSizeObserver.h"
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsTArray.h"
+#include "nsTableRowFrame.h"
+#include "mozilla/WritingModes.h"
+
+/**
+ * nsTableCellFrame
+ * data structure to maintain information about a single table cell's frame
+ *
+ * NOTE: frames are not ref counted. We expose addref and release here
+ * so we can change that decsion in the future. Users of nsITableCellLayout
+ * should refcount correctly as if this object is being ref counted, though
+ * no actual support is under the hood.
+ *
+ * @author sclark
+ */
+class nsTableCellFrame : public nsContainerFrame,
+ public nsITableCellLayout,
+ public nsIPercentBSizeObserver
+{
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::image::DrawResult DrawResult;
+
+protected:
+ typedef mozilla::WritingMode WritingMode;
+ typedef mozilla::LogicalSide LogicalSide;
+ typedef mozilla::LogicalMargin LogicalMargin;
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsTableCellFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // default constructor supplied by the compiler
+
+ nsTableCellFrame(nsStyleContext* aContext, nsTableFrame* aTableFrame);
+ ~nsTableCellFrame();
+
+ nsTableRowFrame* GetTableRowFrame() const
+ {
+ nsIFrame* parent = GetParent();
+ MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableRowFrame);
+ return static_cast<nsTableRowFrame*>(parent);
+ }
+
+ nsTableFrame* GetTableFrame() const
+ {
+ return GetTableRowFrame()->GetTableFrame();
+ }
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ /** @see nsIFrame::DidSetStyleContext */
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+#ifdef DEBUG
+ // Our anonymous block frame is the content insertion frame so these
+ // methods should never be called:
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+#endif
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ virtual nsMargin GetUsedMargin() const override;
+
+ virtual void NotifyPercentBSize(const ReflowInput& aReflowInput) override;
+
+ virtual bool NeedsToObserve(const ReflowInput& aReflowInput) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ DrawResult PaintCellBackground(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect, nsPoint aPt,
+ uint32_t aFlags);
+
+
+ virtual nsresult ProcessBorders(nsTableFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists);
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+ virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsLayoutAtoms::tableCellFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ // Although the spec doesn't say that writing-mode is not applied to
+ // table-cells, we still override this method here because we want to
+ // make effective writing mode of table structure frames consistent
+ // within a table. The content inside table cells is reflowed by an
+ // anonymous block, hence their writing mode is not affected.
+ virtual mozilla::WritingMode GetWritingMode() const override
+ { return GetTableFrame()->GetWritingMode(); }
+
+ void BlockDirAlignChild(mozilla::WritingMode aWM, nscoord aMaxAscent);
+
+ /*
+ * Get the value of vertical-align adjusted for CSS 2's rules for a
+ * table cell, which means the result is always
+ * NS_STYLE_VERTICAL_ALIGN_{TOP,MIDDLE,BOTTOM,BASELINE}.
+ */
+ virtual uint8_t GetVerticalAlign() const;
+
+ bool HasVerticalAlignBaseline() const {
+ return GetVerticalAlign() == NS_STYLE_VERTICAL_ALIGN_BASELINE;
+ }
+
+ bool CellHasVisibleContent(nscoord aBSize,
+ nsTableFrame* tableFrame,
+ nsIFrame* kidFrame);
+
+ /**
+ * Get the first-line baseline of the cell relative to its block-start border
+ * edge, as if the cell were vertically aligned to the top of the row.
+ */
+ nscoord GetCellBaseline() const;
+
+ /**
+ * return the cell's specified row span. this is what was specified in the
+ * content model or in the style info, and is always >= 1.
+ * to get the effective row span (the actual value that applies), use GetEffectiveRowSpan()
+ * @see nsTableFrame::GetEffectiveRowSpan()
+ */
+ virtual int32_t GetRowSpan();
+
+ // there is no set row index because row index depends on the cell's parent row only
+
+ /*---------------- nsITableCellLayout methods ------------------------*/
+
+ /**
+ * return the cell's starting row index (starting at 0 for the first row).
+ * for continued cell frames the row index is that of the cell's first-in-flow
+ * and the column index (starting at 0 for the first column
+ */
+ NS_IMETHOD GetCellIndexes(int32_t &aRowIndex, int32_t &aColIndex) override;
+
+ /** return the mapped cell's row index (starting at 0 for the first row) */
+ virtual nsresult GetRowIndex(int32_t &aRowIndex) const override;
+
+ /**
+ * return the cell's specified col span. this is what was specified in the
+ * content model or in the style info, and is always >= 1.
+ * to get the effective col span (the actual value that applies), use GetEffectiveColSpan()
+ * @see nsTableFrame::GetEffectiveColSpan()
+ */
+ virtual int32_t GetColSpan();
+
+ /** return the cell's column index (starting at 0 for the first column) */
+ virtual nsresult GetColIndex(int32_t &aColIndex) const override;
+ void SetColIndex(int32_t aColIndex);
+
+ /** return the available isize given to this frame during its last reflow */
+ inline nscoord GetPriorAvailISize();
+
+ /** set the available isize given to this frame during its last reflow */
+ inline void SetPriorAvailISize(nscoord aPriorAvailISize);
+
+ /** return the desired size returned by this frame during its last reflow */
+ inline mozilla::LogicalSize GetDesiredSize();
+
+ /** set the desired size returned by this frame during its last reflow */
+ inline void SetDesiredSize(const ReflowOutput & aDesiredSize);
+
+ bool GetContentEmpty();
+ void SetContentEmpty(bool aContentEmpty);
+
+ bool HasPctOverBSize();
+ void SetHasPctOverBSize(bool aValue);
+
+ nsTableCellFrame* GetNextCell() const;
+
+ virtual LogicalMargin GetBorderWidth(WritingMode aWM) const;
+
+ virtual DrawResult PaintBackground(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt,
+ uint32_t aFlags);
+
+ void DecorateForSelection(DrawTarget* aDrawTarget, nsPoint aPt);
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart));
+ }
+
+ virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); }
+
+protected:
+ virtual LogicalSides
+ GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override;
+
+ /**
+ * GetBorderOverflow says how far the cell's own borders extend
+ * outside its own bounds. In the separated borders model this should
+ * just be zero (as it is for most frames), but in the collapsed
+ * borders model (for which nsBCTableCellFrame overrides this virtual
+ * method), it considers the extents of the collapsed border.
+ */
+ virtual nsMargin GetBorderOverflow();
+
+ friend class nsTableRowFrame;
+
+ uint32_t mColIndex; // the starting column for this cell
+
+ nscoord mPriorAvailISize; // the avail isize during the last reflow
+ mozilla::LogicalSize mDesiredSize; // the last desired inline and block size
+};
+
+inline nscoord nsTableCellFrame::GetPriorAvailISize()
+{ return mPriorAvailISize; }
+
+inline void nsTableCellFrame::SetPriorAvailISize(nscoord aPriorAvailISize)
+{ mPriorAvailISize = aPriorAvailISize; }
+
+inline mozilla::LogicalSize nsTableCellFrame::GetDesiredSize()
+{ return mDesiredSize; }
+
+inline void nsTableCellFrame::SetDesiredSize(const ReflowOutput & aDesiredSize)
+{
+ mozilla::WritingMode wm = aDesiredSize.GetWritingMode();
+ mDesiredSize = aDesiredSize.Size(wm).ConvertTo(GetWritingMode(), wm);
+}
+
+inline bool nsTableCellFrame::GetContentEmpty()
+{
+ return HasAnyStateBits(NS_TABLE_CELL_CONTENT_EMPTY);
+}
+
+inline void nsTableCellFrame::SetContentEmpty(bool aContentEmpty)
+{
+ if (aContentEmpty) {
+ AddStateBits(NS_TABLE_CELL_CONTENT_EMPTY);
+ } else {
+ RemoveStateBits(NS_TABLE_CELL_CONTENT_EMPTY);
+ }
+}
+
+inline bool nsTableCellFrame::HasPctOverBSize()
+{
+ return HasAnyStateBits(NS_TABLE_CELL_HAS_PCT_OVER_BSIZE);
+}
+
+inline void nsTableCellFrame::SetHasPctOverBSize(bool aValue)
+{
+ if (aValue) {
+ AddStateBits(NS_TABLE_CELL_HAS_PCT_OVER_BSIZE);
+ } else {
+ RemoveStateBits(NS_TABLE_CELL_HAS_PCT_OVER_BSIZE);
+ }
+}
+
+// nsBCTableCellFrame
+class nsBCTableCellFrame final : public nsTableCellFrame
+{
+ typedef mozilla::image::DrawResult DrawResult;
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ nsBCTableCellFrame(nsStyleContext* aContext, nsTableFrame* aTableFrame);
+
+ ~nsBCTableCellFrame();
+
+ virtual nsIAtom* GetType() const override;
+
+ virtual nsMargin GetUsedBorder() const override;
+ virtual bool GetBorderRadii(const nsSize& aFrameSize,
+ const nsSize& aBorderArea,
+ Sides aSkipSides,
+ nscoord aRadii[8]) const override;
+
+ // Get the *inner half of the border only*, in twips.
+ virtual LogicalMargin GetBorderWidth(WritingMode aWM) const override;
+
+ // Get the *inner half of the border only*, in pixels.
+ BCPixelSize GetBorderWidth(LogicalSide aSide) const;
+
+ // Set the full (both halves) width of the border
+ void SetBorderWidth(LogicalSide aSide, BCPixelSize aPixelValue);
+
+ virtual nsMargin GetBorderOverflow() override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual DrawResult PaintBackground(nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt,
+ uint32_t aFlags) override;
+
+private:
+
+ // These are the entire width of the border (the cell edge contains only
+ // the inner half, per the macros in nsTablePainter.h).
+ BCPixelSize mBStartBorder;
+ BCPixelSize mIEndBorder;
+ BCPixelSize mBEndBorder;
+ BCPixelSize mIStartBorder;
+};
+
+#endif
diff --git a/layout/tables/nsTableColFrame.cpp b/layout/tables/nsTableColFrame.cpp
new file mode 100644
index 0000000000..8f449c3d9c
--- /dev/null
+++ b/layout/tables/nsTableColFrame.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "nsTableColFrame.h"
+#include "nsTableFrame.h"
+#include "nsContainerFrame.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsCSSRendering.h"
+#include "nsIContent.h"
+
+using namespace mozilla;
+
+#define COL_TYPE_BITS (NS_FRAME_STATE_BIT(28) | \
+ NS_FRAME_STATE_BIT(29) | \
+ NS_FRAME_STATE_BIT(30) | \
+ NS_FRAME_STATE_BIT(31))
+#define COL_TYPE_OFFSET 28
+
+using namespace mozilla;
+
+nsTableColFrame::nsTableColFrame(nsStyleContext* aContext) :
+ nsSplittableFrame(aContext)
+{
+ SetColType(eColContent);
+ ResetIntrinsics();
+ ResetSpanIntrinsics();
+ ResetFinalISize();
+}
+
+nsTableColFrame::~nsTableColFrame()
+{
+}
+
+nsTableColType
+nsTableColFrame::GetColType() const
+{
+ return (nsTableColType)((mState & COL_TYPE_BITS) >> COL_TYPE_OFFSET);
+}
+
+void
+nsTableColFrame::SetColType(nsTableColType aType)
+{
+ NS_ASSERTION(aType != eColAnonymousCol ||
+ (GetPrevContinuation() &&
+ GetPrevContinuation()->GetNextContinuation() == this &&
+ GetPrevContinuation()->GetNextSibling() == this),
+ "spanned content cols must be continuations");
+ uint32_t type = aType - eColContent;
+ RemoveStateBits(COL_TYPE_BITS);
+ AddStateBits(nsFrameState(type << COL_TYPE_OFFSET));
+}
+
+/* virtual */ void
+nsTableColFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsSplittableFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (!aOldStyleContext) //avoid this on init
+ return;
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse() &&
+ tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) {
+ TableArea damageArea(GetColIndex(), 0, 1, tableFrame->GetRowCount());
+ tableFrame->AddBCDamageArea(damageArea);
+ }
+}
+
+void nsTableColFrame::SetContinuousBCBorderWidth(LogicalSide aForSide,
+ BCPixelSize aPixelValue)
+{
+ switch (aForSide) {
+ case eLogicalSideBStart:
+ mBStartContBorderWidth = aPixelValue;
+ return;
+ case eLogicalSideIEnd:
+ mIEndContBorderWidth = aPixelValue;
+ return;
+ case eLogicalSideBEnd:
+ mBEndContBorderWidth = aPixelValue;
+ return;
+ default:
+ NS_ERROR("invalid side arg");
+ }
+}
+
+void
+nsTableColFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableColFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ aDesiredSize.ClearSize();
+ const nsStyleVisibility* colVis = StyleVisibility();
+ bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible);
+ if (collapseCol) {
+ GetTableFrame()->SetNeedToCollapse(true);
+ }
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+int32_t nsTableColFrame::GetSpan()
+{
+ return StyleTable()->mSpan;
+}
+
+#ifdef DEBUG
+void nsTableColFrame::Dump(int32_t aIndent)
+{
+ char* indent = new char[aIndent + 1];
+ if (!indent) return;
+ for (int32_t i = 0; i < aIndent + 1; i++) {
+ indent[i] = ' ';
+ }
+ indent[aIndent] = 0;
+
+ printf("%s**START COL DUMP**\n%s colIndex=%d coltype=",
+ indent, indent, mColIndex);
+ nsTableColType colType = GetColType();
+ switch (colType) {
+ case eColContent:
+ printf(" content ");
+ break;
+ case eColAnonymousCol:
+ printf(" anonymous-column ");
+ break;
+ case eColAnonymousColGroup:
+ printf(" anonymous-colgroup ");
+ break;
+ case eColAnonymousCell:
+ printf(" anonymous-cell ");
+ break;
+ }
+ printf("\nm:%d c:%d(%c) p:%f sm:%d sc:%d sp:%f f:%d",
+ int32_t(mMinCoord), int32_t(mPrefCoord),
+ mHasSpecifiedCoord ? 's' : 'u', mPrefPercent,
+ int32_t(mSpanMinCoord), int32_t(mSpanPrefCoord),
+ mSpanPrefPercent,
+ int32_t(GetFinalISize()));
+ printf("\n%s**END COL DUMP** ", indent);
+ delete [] indent;
+}
+#endif
+/* ----- global methods ----- */
+
+nsTableColFrame*
+NS_NewTableColFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTableColFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableColFrame)
+
+nsTableColFrame*
+nsTableColFrame::GetNextCol() const
+{
+ nsIFrame* childFrame = GetNextSibling();
+ while (childFrame) {
+ if (nsGkAtoms::tableColFrame == childFrame->GetType()) {
+ return (nsTableColFrame*)childFrame;
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ return nullptr;
+}
+
+nsIAtom*
+nsTableColFrame::GetType() const
+{
+ return nsGkAtoms::tableColFrame;
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTableColFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("TableCol"), aResult);
+}
+#endif
+
+nsSplittableType
+nsTableColFrame::GetSplittableType() const
+{
+ return NS_FRAME_NOT_SPLITTABLE;
+}
+
+void
+nsTableColFrame::InvalidateFrame(uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrame(aDisplayItemKey);
+ GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey);
+}
+
+void
+nsTableColFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey);
+
+ // If we have filters applied that would affects our bounds, then
+ // we get an inactive layer created and this is computed
+ // within FrameLayerBuilder
+ GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey);
+}
+
diff --git a/layout/tables/nsTableColFrame.h b/layout/tables/nsTableColFrame.h
new file mode 100644
index 0000000000..e95fe76b1d
--- /dev/null
+++ b/layout/tables/nsTableColFrame.h
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTableColFrame_h__
+#define nsTableColFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "celldata.h"
+#include "nscore.h"
+#include "nsContainerFrame.h"
+#include "nsTArray.h"
+#include "nsTableColGroupFrame.h"
+#include "mozilla/WritingModes.h"
+
+class nsTableColFrame : public nsSplittableFrame {
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ enum {eWIDTH_SOURCE_NONE =0, // no cell has contributed to the width style
+ eWIDTH_SOURCE_CELL =1, // a cell specified a width
+ eWIDTH_SOURCE_CELL_WITH_SPAN=2 // a cell implicitly specified a width via colspan
+ };
+
+ nsTableColType GetColType() const;
+ void SetColType(nsTableColType aType);
+
+ /** instantiate a new instance of nsTableRowFrame.
+ * @param aPresShell the pres shell for this frame
+ *
+ * @return the frame that was created
+ */
+ friend nsTableColFrame* NS_NewTableColFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ nsTableColGroupFrame* GetTableColGroupFrame() const
+ {
+ nsIFrame* parent = GetParent();
+ MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableColGroupFrame);
+ return static_cast<nsTableColGroupFrame*>(parent);
+ }
+
+ nsTableFrame* GetTableFrame() const
+ {
+ return GetTableColGroupFrame()->GetTableFrame();
+ }
+
+ /** @see nsIFrame::DidSetStyleContext */
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ int32_t GetColIndex() const;
+
+ void SetColIndex (int32_t aColIndex);
+
+ nsTableColFrame* GetNextCol() const;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ /**
+ * Table columns never paint anything, nor receive events.
+ */
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::tableColFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual nsSplittableType GetSplittableType() const override;
+
+ virtual mozilla::WritingMode GetWritingMode() const override
+ { return GetTableFrame()->GetWritingMode(); }
+
+ /** return the number of the columns the col represents. always >= 1 */
+ int32_t GetSpan();
+
+ /** convenience method, calls into cellmap */
+ int32_t Count() const;
+
+ nscoord GetIStartBorderWidth() const { return mIStartBorderWidth; }
+ nscoord GetIEndBorderWidth() const { return mIEndBorderWidth; }
+ void SetIStartBorderWidth(BCPixelSize aWidth) { mIStartBorderWidth = aWidth; }
+ void SetIEndBorderWidth(BCPixelSize aWidth) { mIEndBorderWidth = aWidth; }
+
+ /**
+ * Gets inner border widths before collapsing with cell borders
+ * Caller must get istart border from previous column or from table
+ * GetContinuousBCBorderWidth will not overwrite aBorder.IStart
+ * see nsTablePainter about continuous borders
+ *
+ * @return outer iend border width (istart inner for next column)
+ */
+ nscoord GetContinuousBCBorderWidth(mozilla::WritingMode aWM,
+ mozilla::LogicalMargin& aBorder);
+ /**
+ * Set full border widths before collapsing with cell borders
+ * @param aForSide - side to set; only valid for bstart, iend, and bend
+ */
+ void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide,
+ BCPixelSize aPixelValue);
+#ifdef DEBUG
+ void Dump(int32_t aIndent);
+#endif
+
+ /**
+ * Restore the default values of the intrinsic widths, so that we can
+ * re-accumulate intrinsic widths from the cells in the column.
+ */
+ void ResetIntrinsics() {
+ mMinCoord = 0;
+ mPrefCoord = 0;
+ mPrefPercent = 0.0f;
+ mHasSpecifiedCoord = false;
+ }
+
+ /**
+ * Restore the default value of the preferred percentage width (the
+ * only intrinsic width used by FixedTableLayoutStrategy.
+ */
+ void ResetPrefPercent() {
+ mPrefPercent = 0.0f;
+ }
+
+ /**
+ * Restore the default values of the temporary buffer for
+ * spanning-cell intrinsic widths (as we process spanning cells).
+ */
+ void ResetSpanIntrinsics() {
+ mSpanMinCoord = 0;
+ mSpanPrefCoord = 0;
+ mSpanPrefPercent = 0.0f;
+ }
+
+ /**
+ * Add the widths for a cell or column element, or the contribution of
+ * the widths from a column-spanning cell:
+ * @param aMinCoord The minimum intrinsic width
+ * @param aPrefCoord The preferred intrinsic width or, if there is a
+ * specified non-percentage width, max(specified width, minimum intrinsic
+ * width).
+ * @param aHasSpecifiedCoord Whether there is a specified
+ * non-percentage width.
+ *
+ * Note that the implementation of this functions is a bit tricky
+ * since mPrefCoord means different things depending on
+ * whether mHasSpecifiedCoord is true (and likewise for aPrefCoord and
+ * aHasSpecifiedCoord). If mHasSpecifiedCoord is false, then
+ * all widths added had aHasSpecifiedCoord false and mPrefCoord is the
+ * largest of the pref widths. But if mHasSpecifiedCoord is true,
+ * then mPrefCoord is the largest of (1) the pref widths for cells
+ * with aHasSpecifiedCoord true and (2) the min widths for cells with
+ * aHasSpecifiedCoord false.
+ */
+ void AddCoords(nscoord aMinCoord, nscoord aPrefCoord,
+ bool aHasSpecifiedCoord) {
+ NS_ASSERTION(aMinCoord <= aPrefCoord, "intrinsic widths out of order");
+
+ if (aHasSpecifiedCoord && !mHasSpecifiedCoord) {
+ mPrefCoord = mMinCoord;
+ mHasSpecifiedCoord = true;
+ }
+ if (!aHasSpecifiedCoord && mHasSpecifiedCoord) {
+ aPrefCoord = aMinCoord; // NOTE: modifying argument
+ }
+
+ if (aMinCoord > mMinCoord)
+ mMinCoord = aMinCoord;
+ if (aPrefCoord > mPrefCoord)
+ mPrefCoord = aPrefCoord;
+
+ NS_ASSERTION(mMinCoord <= mPrefCoord, "min larger than pref");
+ }
+
+ /**
+ * Add a percentage width specified on a cell or column element or the
+ * contribution to this column of a percentage width specified on a
+ * column-spanning cell.
+ */
+ void AddPrefPercent(float aPrefPercent) {
+ if (aPrefPercent > mPrefPercent)
+ mPrefPercent = aPrefPercent;
+ }
+
+ /**
+ * Get the largest minimum intrinsic width for this column.
+ */
+ nscoord GetMinCoord() const { return mMinCoord; }
+ /**
+ * Get the largest preferred width for this column, or, if there were
+ * any specified non-percentage widths (see GetHasSpecifiedCoord), the
+ * largest minimum intrinsic width or specified width.
+ */
+ nscoord GetPrefCoord() const { return mPrefCoord; }
+ /**
+ * Get whether there were any specified widths contributing to this
+ * column.
+ */
+ bool GetHasSpecifiedCoord() const { return mHasSpecifiedCoord; }
+
+ /**
+ * Get the largest specified percentage width contributing to this
+ * column (returns 0 if there were none).
+ */
+ float GetPrefPercent() const { return mPrefPercent; }
+
+ /**
+ * Like AddCoords, but into a temporary buffer used for groups of
+ * column-spanning cells.
+ */
+ void AddSpanCoords(nscoord aSpanMinCoord, nscoord aSpanPrefCoord,
+ bool aSpanHasSpecifiedCoord) {
+ NS_ASSERTION(aSpanMinCoord <= aSpanPrefCoord,
+ "intrinsic widths out of order");
+
+ if (!aSpanHasSpecifiedCoord && mHasSpecifiedCoord) {
+ aSpanPrefCoord = aSpanMinCoord; // NOTE: modifying argument
+ }
+
+ if (aSpanMinCoord > mSpanMinCoord)
+ mSpanMinCoord = aSpanMinCoord;
+ if (aSpanPrefCoord > mSpanPrefCoord)
+ mSpanPrefCoord = aSpanPrefCoord;
+
+ NS_ASSERTION(mSpanMinCoord <= mSpanPrefCoord, "min larger than pref");
+ }
+
+ /*
+ * Accumulate percentage widths on column spanning cells into
+ * temporary variables.
+ */
+ void AddSpanPrefPercent(float aSpanPrefPercent) {
+ if (aSpanPrefPercent > mSpanPrefPercent)
+ mSpanPrefPercent = aSpanPrefPercent;
+ }
+
+ /*
+ * Accumulate the temporary variables for column spanning cells into
+ * the primary variables.
+ */
+ void AccumulateSpanIntrinsics() {
+ AddCoords(mSpanMinCoord, mSpanPrefCoord, mHasSpecifiedCoord);
+ AddPrefPercent(mSpanPrefPercent);
+ }
+
+ // Used to adjust a column's pref percent so that the table's total
+ // never exceeeds 100% (by only allowing percentages to be used,
+ // starting at the first column, until they reach 100%).
+ void AdjustPrefPercent(float *aTableTotalPercent) {
+ float allowed = 1.0f - *aTableTotalPercent;
+ if (mPrefPercent > allowed)
+ mPrefPercent = allowed;
+ *aTableTotalPercent += mPrefPercent;
+ }
+
+ // The final width of the column.
+ void ResetFinalISize() {
+ mFinalISize = nscoord_MIN; // so we detect that it changed
+ }
+ void SetFinalISize(nscoord aFinalISize) {
+ mFinalISize = aFinalISize;
+ }
+ nscoord GetFinalISize() {
+ return mFinalISize;
+ }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsSplittableFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart));
+ }
+
+ virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); }
+
+protected:
+
+ explicit nsTableColFrame(nsStyleContext* aContext);
+ ~nsTableColFrame();
+
+ nscoord mMinCoord;
+ nscoord mPrefCoord;
+ nscoord mSpanMinCoord; // XXX...
+ nscoord mSpanPrefCoord; // XXX...
+ float mPrefPercent;
+ float mSpanPrefPercent; // XXX...
+ // ...XXX the four members marked above could be allocated as part of
+ // a separate array allocated only during
+ // BasicTableLayoutStrategy::ComputeColumnIntrinsicISizes (and only
+ // when colspans were present).
+ nscoord mFinalISize;
+
+ // the index of the column with respect to the whole table (starting at 0)
+ // it should never be smaller then the start column index of the parent
+ // colgroup
+ uint32_t mColIndex;
+
+ // border width in pixels of the inner half of the border only
+ BCPixelSize mIStartBorderWidth;
+ BCPixelSize mIEndBorderWidth;
+ BCPixelSize mBStartContBorderWidth;
+ BCPixelSize mIEndContBorderWidth;
+ BCPixelSize mBEndContBorderWidth;
+
+ bool mHasSpecifiedCoord;
+};
+
+inline int32_t nsTableColFrame::GetColIndex() const
+{
+ return mColIndex;
+}
+
+inline void nsTableColFrame::SetColIndex (int32_t aColIndex)
+{
+ mColIndex = aColIndex;
+}
+
+inline nscoord
+nsTableColFrame::GetContinuousBCBorderWidth(mozilla::WritingMode aWM,
+ mozilla::LogicalMargin& aBorder)
+{
+ int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
+ aBorder.BStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips,
+ mBStartContBorderWidth);
+ aBorder.IEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips,
+ mIEndContBorderWidth);
+ aBorder.BEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips,
+ mBEndContBorderWidth);
+ return BC_BORDER_END_HALF_COORD(aPixelsToTwips, mIEndContBorderWidth);
+}
+
+#endif
+
diff --git a/layout/tables/nsTableColGroupFrame.cpp b/layout/tables/nsTableColGroupFrame.cpp
new file mode 100644
index 0000000000..ff8879a0b0
--- /dev/null
+++ b/layout/tables/nsTableColGroupFrame.cpp
@@ -0,0 +1,524 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsTableColGroupFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableFrame.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsHTMLParts.h"
+#include "nsGkAtoms.h"
+#include "nsCOMPtr.h"
+#include "nsCSSRendering.h"
+#include "nsIPresShell.h"
+
+using namespace mozilla;
+
+#define COL_GROUP_TYPE_BITS (NS_FRAME_STATE_BIT(30) | \
+ NS_FRAME_STATE_BIT(31))
+#define COL_GROUP_TYPE_OFFSET 30
+
+nsTableColGroupType
+nsTableColGroupFrame::GetColType() const
+{
+ return (nsTableColGroupType)((mState & COL_GROUP_TYPE_BITS) >> COL_GROUP_TYPE_OFFSET);
+}
+
+void nsTableColGroupFrame::SetColType(nsTableColGroupType aType)
+{
+ NS_ASSERTION(GetColType() == eColGroupContent,
+ "should only call nsTableColGroupFrame::SetColType with aType "
+ "!= eColGroupContent once");
+ uint32_t type = aType - eColGroupContent;
+ RemoveStateBits(COL_GROUP_TYPE_BITS);
+ AddStateBits(nsFrameState(type << COL_GROUP_TYPE_OFFSET));
+}
+
+void nsTableColGroupFrame::ResetColIndices(nsIFrame* aFirstColGroup,
+ int32_t aFirstColIndex,
+ nsIFrame* aStartColFrame)
+{
+ nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)aFirstColGroup;
+ int32_t colIndex = aFirstColIndex;
+ while (colGroupFrame) {
+ if (nsGkAtoms::tableColGroupFrame == colGroupFrame->GetType()) {
+ // reset the starting col index for the first cg only if we should reset
+ // the whole colgroup (aStartColFrame defaults to nullptr) or if
+ // aFirstColIndex is smaller than the existing starting col index
+ if ((colIndex != aFirstColIndex) ||
+ (colIndex < colGroupFrame->GetStartColumnIndex()) ||
+ !aStartColFrame) {
+ colGroupFrame->SetStartColumnIndex(colIndex);
+ }
+ nsIFrame* colFrame = aStartColFrame;
+ if (!colFrame || (colIndex != aFirstColIndex)) {
+ colFrame = colGroupFrame->PrincipalChildList().FirstChild();
+ }
+ while (colFrame) {
+ if (nsGkAtoms::tableColFrame == colFrame->GetType()) {
+ ((nsTableColFrame*)colFrame)->SetColIndex(colIndex);
+ colIndex++;
+ }
+ colFrame = colFrame->GetNextSibling();
+ }
+ }
+ colGroupFrame = static_cast<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;
+ nsFrameList::Enumerator e(aCols);
+ for (; !e.AtEnd(); e.Next()) {
+ ((nsTableColFrame*)e.get())->SetColIndex(colIndex);
+ mColCount++;
+ tableFrame->InsertCol((nsTableColFrame &)*e.get(), colIndex);
+ colIndex++;
+ }
+
+ for (nsFrameList::Enumerator eTail = e.GetUnlimitedEnumerator();
+ !eTail.AtEnd();
+ eTail.Next()) {
+ ((nsTableColFrame*)eTail.get())->SetColIndex(colIndex);
+ colIndex++;
+ }
+
+ // We have already set the colindex for all the colframes in this
+ // colgroup that come after the first inserted colframe, but there could
+ // be other colgroups following this one and their colframes need
+ // correct colindices too.
+ if (aResetSubsequentColIndices && GetNextSibling()) {
+ ResetColIndices(GetNextSibling(), colIndex);
+ }
+
+ return NS_OK;
+}
+
+
+nsTableColGroupFrame*
+nsTableColGroupFrame::GetLastRealColGroup(nsTableFrame* aTableFrame)
+{
+ nsFrameList colGroups = aTableFrame->GetColGroups();
+
+ nsIFrame* nextToLastColGroup = nullptr;
+ nsFrameList::FrameLinkEnumerator link(colGroups);
+ for ( ; !link.AtEnd(); link.Next()) {
+ nextToLastColGroup = link.PrevFrame();
+ }
+
+ if (!link.PrevFrame()) {
+ return nullptr; // there are no col group frames
+ }
+
+ nsTableColGroupType lastColGroupType =
+ static_cast<nsTableColGroupFrame*>(link.PrevFrame())->GetColType();
+ if (eColGroupAnonymousCell == lastColGroupType) {
+ return static_cast<nsTableColGroupFrame*>(nextToLastColGroup);
+ }
+
+ return static_cast<nsTableColGroupFrame*>(link.PrevFrame());
+}
+
+// don't set mColCount here, it is done in AddColsToTable
+void
+nsTableColGroupFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ MOZ_ASSERT(mFrames.IsEmpty(),
+ "unexpected second call to SetInitialChildList");
+ MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
+ if (aChildList.IsEmpty()) {
+ GetTableFrame()->AppendAnonymousColFrames(this, GetSpan(),
+ eColAnonymousColGroup, false);
+ return;
+ }
+
+ mFrames.AppendFrames(this, aChildList);
+}
+
+/* virtual */ void
+nsTableColGroupFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsContainerFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (!aOldStyleContext) //avoid this on init
+ return;
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse() &&
+ tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) {
+ int32_t colCount = GetColCount();
+ if (!colCount)
+ return; // this is a degenerated colgroup
+ TableArea damageArea(GetFirstColumn()->GetColIndex(), 0, colCount,
+ tableFrame->GetRowCount());
+ tableFrame->AddBCDamageArea(damageArea);
+ }
+}
+
+void
+nsTableColGroupFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+
+ nsTableColFrame* col = GetFirstColumn();
+ nsTableColFrame* nextCol;
+ while (col && col->GetColType() == eColAnonymousColGroup) {
+ // this colgroup spans one or more columns but now that there is a
+ // real column below, spanned anonymous columns should be removed,
+ // since the HTML spec says to ignore the span of a colgroup if it
+ // has content columns in it.
+ nextCol = col->GetNextCol();
+ RemoveFrame(kPrincipalList, col);
+ col = nextCol;
+ }
+
+ const nsFrameList::Slice& newFrames =
+ mFrames.AppendFrames(this, aFrameList);
+ InsertColsReflow(GetStartColumnIndex() + mColCount, newFrames);
+}
+
+void
+nsTableColGroupFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+
+ nsTableColFrame* col = GetFirstColumn();
+ nsTableColFrame* nextCol;
+ while (col && col->GetColType() == eColAnonymousColGroup) {
+ // this colgroup spans one or more columns but now that there is a
+ // real column below, spanned anonymous columns should be removed,
+ // since the HTML spec says to ignore the span of a colgroup if it
+ // has content columns in it.
+ nextCol = col->GetNextCol();
+ if (col == aPrevFrame) {
+ // This can happen when we're being appended to
+ NS_ASSERTION(!nextCol || nextCol->GetColType() != eColAnonymousColGroup,
+ "Inserting in the middle of our anonymous cols?");
+ // We'll want to insert at the beginning
+ aPrevFrame = nullptr;
+ }
+ RemoveFrame(kPrincipalList, col);
+ col = nextCol;
+ }
+
+ NS_ASSERTION(!aPrevFrame || aPrevFrame == aPrevFrame->LastContinuation(),
+ "Prev frame should be last in continuation chain");
+ NS_ASSERTION(!aPrevFrame || !GetNextColumn(aPrevFrame) ||
+ GetNextColumn(aPrevFrame)->GetColType() != eColAnonymousCol,
+ "Shouldn't be inserting before a spanned colframe");
+
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(this, aPrevFrame, aFrameList);
+ nsIFrame* prevFrame = nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame,
+ nsGkAtoms::tableColFrame);
+
+ int32_t colIndex = (prevFrame) ? ((nsTableColFrame*)prevFrame)->GetColIndex() + 1 : GetStartColumnIndex();
+ InsertColsReflow(colIndex, newFrames);
+}
+
+void
+nsTableColGroupFrame::InsertColsReflow(int32_t aColIndex,
+ const nsFrameList::Slice& aCols)
+{
+ AddColsToTable(aColIndex, true, aCols);
+
+ PresContext()->PresShell()->FrameNeedsReflow(this,
+ nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsTableColGroupFrame::RemoveChild(nsTableColFrame& aChild,
+ bool aResetSubsequentColIndices)
+{
+ int32_t colIndex = 0;
+ nsIFrame* nextChild = nullptr;
+ if (aResetSubsequentColIndices) {
+ colIndex = aChild.GetColIndex();
+ nextChild = aChild.GetNextSibling();
+ }
+ mFrames.DestroyFrame(&aChild);
+ mColCount--;
+ if (aResetSubsequentColIndices) {
+ if (nextChild) { // reset inside this and all following colgroups
+ ResetColIndices(this, colIndex, nextChild);
+ }
+ else {
+ nsIFrame* nextGroup = GetNextSibling();
+ if (nextGroup) // reset next and all following colgroups
+ ResetColIndices(nextGroup, colIndex);
+ }
+ }
+
+ PresContext()->PresShell()->FrameNeedsReflow(this,
+ nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsTableColGroupFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+
+ if (!aOldFrame) {
+ return;
+ }
+ bool contentRemoval = false;
+
+ if (nsGkAtoms::tableColFrame == aOldFrame->GetType()) {
+ nsTableColFrame* colFrame = (nsTableColFrame*)aOldFrame;
+ if (colFrame->GetColType() == eColContent) {
+ contentRemoval = true;
+ // Remove any anonymous column frames this <col> produced via a colspan
+ nsTableColFrame* col = colFrame->GetNextCol();
+ nsTableColFrame* nextCol;
+ while (col && col->GetColType() == eColAnonymousCol) {
+#ifdef DEBUG
+ nsIFrame* providerFrame;
+ nsStyleContext* psc = colFrame->GetParentStyleContext(&providerFrame);
+ if (colFrame->StyleContext()->GetParent() == psc) {
+ NS_ASSERTION(col->StyleContext() == colFrame->StyleContext() &&
+ col->GetContent() == colFrame->GetContent(),
+ "How did that happen??");
+ }
+ // else colFrame is being removed because of a frame
+ // reconstruct on it, and its style context is still the old
+ // one, so we can't assert anything about how it compares to
+ // col's style context.
+#endif
+ nextCol = col->GetNextCol();
+ RemoveFrame(kPrincipalList, col);
+ col = nextCol;
+ }
+ }
+
+ int32_t colIndex = colFrame->GetColIndex();
+ // The RemoveChild call handles calling FrameNeedsReflow on us.
+ RemoveChild(*colFrame, true);
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ tableFrame->RemoveCol(this, colIndex, true, true);
+ if (mFrames.IsEmpty() && contentRemoval &&
+ GetColType() == eColGroupContent) {
+ tableFrame->AppendAnonymousColFrames(this, GetSpan(),
+ eColAnonymousColGroup, true);
+ }
+ }
+ else {
+ mFrames.DestroyFrame(aOldFrame);
+ }
+}
+
+nsIFrame::LogicalSides
+nsTableColGroupFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
+{
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return LogicalSides();
+ }
+
+ LogicalSides skip;
+ if (nullptr != GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (nullptr != GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+
+void
+nsTableColGroupFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableColGroupFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ NS_ASSERTION(nullptr!=mContent, "bad state -- null content for frame");
+
+ const nsStyleVisibility* groupVis = StyleVisibility();
+ bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible);
+ if (collapseGroup) {
+ GetTableFrame()->SetNeedToCollapse(true);
+ }
+ // for every content child that (is a column thingy and does not already have a frame)
+ // create a frame and adjust it's style
+
+ for (nsIFrame *kidFrame = mFrames.FirstChild(); kidFrame;
+ kidFrame = kidFrame->GetNextSibling()) {
+ // Give the child frame a chance to reflow, even though we know it'll have 0 size
+ ReflowOutput kidSize(aReflowInput);
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame,
+ LogicalSize(kidFrame->GetWritingMode()));
+
+ nsReflowStatus status;
+ ReflowChild(kidFrame, aPresContext, kidSize, kidReflowInput, 0, 0, 0, status);
+ FinishReflowChild(kidFrame, aPresContext, kidSize, nullptr, 0, 0, 0);
+ }
+
+ aDesiredSize.ClearSize();
+ aStatus = NS_FRAME_COMPLETE;
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+nsTableColFrame * nsTableColGroupFrame::GetFirstColumn()
+{
+ return GetNextColumn(nullptr);
+}
+
+nsTableColFrame * nsTableColGroupFrame::GetNextColumn(nsIFrame *aChildFrame)
+{
+ nsTableColFrame *result = nullptr;
+ nsIFrame *childFrame = aChildFrame;
+ if (!childFrame) {
+ childFrame = mFrames.FirstChild();
+ }
+ else {
+ childFrame = childFrame->GetNextSibling();
+ }
+ while (childFrame)
+ {
+ if (mozilla::StyleDisplay::TableColumn ==
+ childFrame->StyleDisplay()->mDisplay)
+ {
+ result = (nsTableColFrame *)childFrame;
+ break;
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ return result;
+}
+
+int32_t nsTableColGroupFrame::GetSpan()
+{
+ return StyleTable()->mSpan;
+}
+
+void nsTableColGroupFrame::SetContinuousBCBorderWidth(LogicalSide aForSide,
+ BCPixelSize aPixelValue)
+{
+ switch (aForSide) {
+ case eLogicalSideBStart:
+ mBStartContBorderWidth = aPixelValue;
+ return;
+ case eLogicalSideBEnd:
+ mBEndContBorderWidth = aPixelValue;
+ return;
+ default:
+ NS_ERROR("invalid side arg");
+ }
+}
+
+void nsTableColGroupFrame::GetContinuousBCBorderWidth(WritingMode aWM,
+ LogicalMargin& aBorder)
+{
+ int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
+ nsTableColFrame* col = GetTableFrame()->
+ GetColFrame(mStartColIndex + mColCount - 1);
+ col->GetContinuousBCBorderWidth(aWM, aBorder);
+ aBorder.BStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips,
+ mBStartContBorderWidth);
+ aBorder.BEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips,
+ mBEndContBorderWidth);
+}
+
+/* ----- global methods ----- */
+
+nsTableColGroupFrame*
+NS_NewTableColGroupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTableColGroupFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableColGroupFrame)
+
+nsIAtom*
+nsTableColGroupFrame::GetType() const
+{
+ return nsGkAtoms::tableColGroupFrame;
+}
+
+void
+nsTableColGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrame(aDisplayItemKey);
+ GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey);
+}
+
+void
+nsTableColGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey);
+ // If we have filters applied that would affects our bounds, then
+ // we get an inactive layer created and this is computed
+ // within FrameLayerBuilder
+ GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTableColGroupFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("TableColGroup"), aResult);
+}
+
+void nsTableColGroupFrame::Dump(int32_t aIndent)
+{
+ char* indent = new char[aIndent + 1];
+ if (!indent) return;
+ for (int32_t i = 0; i < aIndent + 1; i++) {
+ indent[i] = ' ';
+ }
+ indent[aIndent] = 0;
+
+ printf("%s**START COLGROUP DUMP**\n%s startcolIndex=%d colcount=%d span=%d coltype=",
+ indent, indent, GetStartColumnIndex(), GetColCount(), GetSpan());
+ nsTableColGroupType colType = GetColType();
+ switch (colType) {
+ case eColGroupContent:
+ printf(" content ");
+ break;
+ case eColGroupAnonymousCol:
+ printf(" anonymous-column ");
+ break;
+ case eColGroupAnonymousCell:
+ printf(" anonymous-cell ");
+ break;
+ }
+ // verify the colindices
+ int32_t j = GetStartColumnIndex();
+ nsTableColFrame* col = GetFirstColumn();
+ while (col) {
+ NS_ASSERTION(j == col->GetColIndex(), "wrong colindex on col frame");
+ col = col->GetNextCol();
+ j++;
+ }
+ NS_ASSERTION((j - GetStartColumnIndex()) == GetColCount(),
+ "number of cols out of sync");
+ printf("\n%s**END COLGROUP DUMP** ", indent);
+ delete [] indent;
+}
+#endif
+
diff --git a/layout/tables/nsTableColGroupFrame.h b/layout/tables/nsTableColGroupFrame.h
new file mode 100644
index 0000000000..2a25fdc444
--- /dev/null
+++ b/layout/tables/nsTableColGroupFrame.h
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTableColGroupFrame_h__
+#define nsTableColGroupFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsContainerFrame.h"
+#include "nsTableFrame.h"
+#include "mozilla/WritingModes.h"
+
+class nsTableColFrame;
+
+/**
+ * nsTableColGroupFrame
+ * data structure to maintain information about a single table cell's frame
+ *
+ * @author sclark
+ */
+class nsTableColGroupFrame final : public nsContainerFrame
+{
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // default constructor supplied by the compiler
+
+ /** instantiate a new instance of nsTableRowFrame.
+ * @param aPresShell the pres shell for this frame
+ *
+ * @return the frame that was created
+ */
+ friend nsTableColGroupFrame* NS_NewTableColGroupFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ nsTableFrame* GetTableFrame() const
+ {
+ nsIFrame* parent = GetParent();
+ MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableFrame);
+ MOZ_ASSERT(!parent->GetPrevInFlow(),
+ "Col group should always be in a first-in-flow table frame");
+ return static_cast<nsTableFrame*>(parent);
+ }
+
+ /**
+ * ColGroups never paint anything, nor receive events.
+ */
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ /** A colgroup can be caused by three things:
+ * 1) An element with table-column-group display
+ * 2) An element with a table-column display without a
+ * table-column-group parent
+ * 3) Cells that are not in a column (and hence get an anonymous
+ * column and colgroup).
+ * @return colgroup type
+ */
+ nsTableColGroupType GetColType() const;
+
+ /** Set the colgroup type based on the creation cause
+ * @param aType - the reason why this colgroup is needed
+ */
+ void SetColType(nsTableColGroupType aType);
+
+ /** Real in this context are colgroups that come from an element
+ * with table-column-group display or wrap around columns that
+ * come from an element with table-column display. Colgroups
+ * that are the result of wrapping cells in an anonymous
+ * column and colgroup are not considered real here.
+ * @param aTableFrame - the table parent of the colgroups
+ * @return the last real colgroup
+ */
+ static nsTableColGroupFrame* GetLastRealColGroup(nsTableFrame* aTableFrame);
+
+ /** @see nsIFrame::DidSetStyleContext */
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ /** remove the column aChild from the column group, if requested renumber
+ * the subsequent columns in this column group and all following column
+ * groups. see also ResetColIndices for this
+ * @param aChild - the column frame that needs to be removed
+ * @param aResetSubsequentColIndices - if true the columns that follow
+ * after aChild will be reenumerated
+ */
+ void RemoveChild(nsTableColFrame& aChild,
+ bool aResetSubsequentColIndices);
+
+ /** reflow of a column group is a trivial matter of reflowing
+ * the col group's children (columns), and setting this frame
+ * to 0-size. Since tables are row-centric, column group frames
+ * don't play directly in the rendering game. They do however
+ * maintain important state that effects table and cell layout.
+ */
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::tableColGroupFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual mozilla::WritingMode GetWritingMode() const override
+ { return GetTableFrame()->GetWritingMode(); }
+
+ /** Add column frames to the table storages: colframe cache and cellmap
+ * this doesn't change the mFrames of the colgroup frame.
+ * @param aFirstColIndex - the index at which aFirstFrame should be inserted
+ * into the colframe cache.
+ * @param aResetSubsequentColIndices - the indices of the col frames
+ * after the insertion might need
+ * an update
+ * @param aCols - an iterator that can be used to iterate over the col
+ * frames to be added. Once this is done, the frames on the
+ * sbling chain of its .get() at that point will still need
+ * their col indices updated.
+ * @result - if there is no table frame or the table frame is not
+ * the first in flow it will return an error
+ */
+ nsresult AddColsToTable(int32_t aFirstColIndex,
+ bool aResetSubsequentColIndices,
+ const nsFrameList::Slice& aCols);
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+ void Dump(int32_t aIndent);
+#endif
+
+ /** returns the number of columns represented by this group.
+ * if there are col children, count them (taking into account the span of each)
+ * else, check my own span attribute.
+ */
+ virtual int32_t GetColCount() const;
+
+ /** first column on the child list */
+ nsTableColFrame * GetFirstColumn();
+ /** next sibling to aChildFrame that is a column frame, first column frame
+ * in the column group if aChildFrame is null
+ */
+ nsTableColFrame * GetNextColumn(nsIFrame *aChildFrame);
+
+ /** @return - the position of the first column in this colgroup in the table
+ * colframe cache.
+ */
+ int32_t GetStartColumnIndex();
+
+ /** set the position of the first column in this colgroup in the table
+ * colframe cache.
+ */
+ void SetStartColumnIndex(int32_t aIndex);
+
+ /** helper method to get the span attribute for this colgroup */
+ int32_t GetSpan();
+
+ /** provide access to the mFrames list
+ */
+ nsFrameList& GetWritableChildList();
+
+ /** set the column index for all frames starting at aStartColFrame, it
+ * will also reset the column indices in all subsequent colgroups
+ * @param aFirstColGroup - start the reset operation inside this colgroup
+ * @param aFirstColIndex - first column that is reset should get this index
+ * @param aStartColFrame - if specified the reset starts with this column
+ * inside the colgroup; if not specified, the reset
+ * starts with the first column
+ */
+ static void ResetColIndices(nsIFrame* aFirstColGroup,
+ int32_t aFirstColIndex,
+ nsIFrame* aStartColFrame = nullptr);
+
+ /**
+ * Gets inner border widths before collapsing with cell borders
+ * Caller must get istart border from previous column
+ * GetContinuousBCBorderWidth will not overwrite aBorder.IStart
+ * see nsTablePainter about continuous borders
+ */
+ void GetContinuousBCBorderWidth(mozilla::WritingMode aWM,
+ mozilla::LogicalMargin& aBorder);
+ /**
+ * Set full border widths before collapsing with cell borders
+ * @param aForSide - side to set; only accepts bstart and bend
+ */
+ void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide,
+ BCPixelSize aPixelValue);
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart));
+ }
+
+ virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); }
+
+protected:
+ explicit nsTableColGroupFrame(nsStyleContext* aContext);
+
+ void InsertColsReflow(int32_t aColIndex,
+ const nsFrameList::Slice& aCols);
+
+ virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override;
+
+ // data members
+ int32_t mColCount;
+ // the starting column index this col group represents. Must be >= 0.
+ int32_t mStartColIndex;
+
+ // border width in pixels
+ BCPixelSize mBStartContBorderWidth;
+ BCPixelSize mBEndContBorderWidth;
+};
+
+inline nsTableColGroupFrame::nsTableColGroupFrame(nsStyleContext *aContext)
+: nsContainerFrame(aContext), mColCount(0), mStartColIndex(0)
+{
+ SetColType(eColGroupContent);
+}
+
+inline int32_t nsTableColGroupFrame::GetStartColumnIndex()
+{
+ return mStartColIndex;
+}
+
+inline void nsTableColGroupFrame::SetStartColumnIndex (int32_t aIndex)
+{
+ mStartColIndex = aIndex;
+}
+
+inline int32_t nsTableColGroupFrame::GetColCount() const
+{
+ return mColCount;
+}
+
+inline nsFrameList& nsTableColGroupFrame::GetWritableChildList()
+{
+ return mFrames;
+}
+
+#endif
+
diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp
new file mode 100644
index 0000000000..5030804ed2
--- /dev/null
+++ b/layout/tables/nsTableFrame.cpp
@@ -0,0 +1,7536 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/WritingModes.h"
+
+#include "nsCOMPtr.h"
+#include "nsTableFrame.h"
+#include "nsRenderingContext.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsIContent.h"
+#include "nsCellMap.h"
+#include "nsTableCellFrame.h"
+#include "nsHTMLParts.h"
+#include "nsTableColFrame.h"
+#include "nsTableColGroupFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsTableWrapperFrame.h"
+#include "nsTablePainter.h"
+
+#include "BasicTableLayoutStrategy.h"
+#include "FixedTableLayoutStrategy.h"
+
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+#include "nsCSSRendering.h"
+#include "nsGkAtoms.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsIPresShell.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIScriptError.h"
+#include "nsFrameManager.h"
+#include "nsError.h"
+#include "nsCSSFrameConstructor.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "nsDisplayList.h"
+#include "nsIScrollableFrame.h"
+#include "nsCSSProps.h"
+#include "RestyleTracker.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::image;
+using namespace mozilla::layout;
+
+/********************************************************************************
+ ** TableReflowInput **
+ ********************************************************************************/
+
+namespace mozilla {
+
+struct TableReflowInput {
+
+ // the real reflow state
+ const ReflowInput& reflowInput;
+
+ // The table's available size (in reflowInput's writing mode)
+ LogicalSize availSize;
+
+ // Stationary inline-offset
+ nscoord iCoord;
+
+ // Running block-offset
+ nscoord bCoord;
+
+ TableReflowInput(const ReflowInput& aReflowInput,
+ const LogicalSize& aAvailSize)
+ : reflowInput(aReflowInput)
+ , availSize(aAvailSize)
+ {
+ MOZ_ASSERT(reflowInput.mFrame->GetType() == nsGkAtoms::tableFrame,
+ "TableReflowInput should only be created for nsTableFrame");
+ nsTableFrame* table =
+ static_cast<nsTableFrame*>(reflowInput.mFrame->FirstInFlow());
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalMargin borderPadding = table->GetChildAreaOffset(wm, &reflowInput);
+
+ iCoord = borderPadding.IStart(wm) + table->GetColSpacing(-1);
+ bCoord = borderPadding.BStart(wm); //cellspacing added during reflow
+
+ // XXX do we actually need to check for unconstrained inline-size here?
+ if (NS_UNCONSTRAINEDSIZE != availSize.ISize(wm)) {
+ int32_t colCount = table->GetColCount();
+ availSize.ISize(wm) -= borderPadding.IStartEnd(wm) +
+ table->GetColSpacing(-1) +
+ table->GetColSpacing(colCount);
+ availSize.ISize(wm) = std::max(0, availSize.ISize(wm));
+ }
+
+ if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
+ availSize.BSize(wm) -= borderPadding.BStartEnd(wm) +
+ table->GetRowSpacing(-1) +
+ table->GetRowSpacing(table->GetRowCount());
+ availSize.BSize(wm) = std::max(0, availSize.BSize(wm));
+ }
+ }
+};
+
+} // namespace mozilla
+
+/********************************************************************************
+ ** nsTableFrame **
+ ********************************************************************************/
+
+struct BCPropertyData
+{
+ BCPropertyData() : mBStartBorderWidth(0), mIEndBorderWidth(0),
+ mBEndBorderWidth(0), mIStartBorderWidth(0),
+ mIStartCellBorderWidth(0), mIEndCellBorderWidth(0) {}
+ TableArea mDamageArea;
+ BCPixelSize mBStartBorderWidth;
+ BCPixelSize mIEndBorderWidth;
+ BCPixelSize mBEndBorderWidth;
+ BCPixelSize mIStartBorderWidth;
+ BCPixelSize mIStartCellBorderWidth;
+ BCPixelSize mIEndCellBorderWidth;
+};
+
+nsStyleContext*
+nsTableFrame::GetParentStyleContext(nsIFrame** aProviderFrame) const
+{
+ // Since our parent, the table wrapper frame, returned this frame, we
+ // must return whatever our parent would normally have returned.
+
+ NS_PRECONDITION(GetParent(), "table constructed without table wrapper");
+ if (!mContent->GetParent() && !StyleContext()->GetPseudo()) {
+ // We're the root. We have no style context parent.
+ *aProviderFrame = nullptr;
+ return nullptr;
+ }
+
+ return GetParent()->DoGetParentStyleContext(aProviderFrame);
+}
+
+
+nsIAtom*
+nsTableFrame::GetType() const
+{
+ return nsGkAtoms::tableFrame;
+}
+
+
+nsTableFrame::nsTableFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext),
+ mCellMap(nullptr),
+ mTableLayoutStrategy(nullptr)
+{
+ memset(&mBits, 0, sizeof(mBits));
+}
+
+void
+nsTableFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_PRECONDITION(!mCellMap, "Init called twice");
+ NS_PRECONDITION(!mTableLayoutStrategy, "Init called twice");
+ NS_PRECONDITION(!aPrevInFlow ||
+ aPrevInFlow->GetType() == nsGkAtoms::tableFrame,
+ "prev-in-flow must be of same type");
+
+ // Let the base class do its processing
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // see if border collapse is on, if so set it
+ const nsStyleTableBorder* tableStyle = StyleTableBorder();
+ bool borderCollapse = (NS_STYLE_BORDER_COLLAPSE == tableStyle->mBorderCollapse);
+ SetBorderCollapse(borderCollapse);
+
+ if (!aPrevInFlow) {
+ // If we're the first-in-flow, we manage the cell map & layout strategy that
+ // get used by our continuation chain:
+ mCellMap = new nsTableCellMap(*this, borderCollapse);
+ if (IsAutoLayout()) {
+ mTableLayoutStrategy = new BasicTableLayoutStrategy(this);
+ } else {
+ mTableLayoutStrategy = new FixedTableLayoutStrategy(this);
+ }
+ } else {
+ // Set my isize, because all frames in a table flow are the same isize and
+ // code in nsTableWrapperFrame depends on this being set.
+ WritingMode wm = GetWritingMode();
+ SetSize(LogicalSize(wm, aPrevInFlow->ISize(wm), BSize(wm)));
+ }
+}
+
+nsTableFrame::~nsTableFrame()
+{
+ delete mCellMap;
+ delete mTableLayoutStrategy;
+}
+
+void
+nsTableFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ mColGroups.DestroyFramesFrom(aDestructRoot);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+// Make sure any views are positioned properly
+void
+nsTableFrame::RePositionViews(nsIFrame* aFrame)
+{
+ nsContainerFrame::PositionFrameView(aFrame);
+ nsContainerFrame::PositionChildViews(aFrame);
+}
+
+static bool
+IsRepeatedFrame(nsIFrame* kidFrame)
+{
+ return (kidFrame->GetType() == nsGkAtoms::tableRowFrame ||
+ kidFrame->GetType() == nsGkAtoms::tableRowGroupFrame) &&
+ kidFrame->HasAnyStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
+}
+
+bool
+nsTableFrame::PageBreakAfter(nsIFrame* aSourceFrame,
+ nsIFrame* aNextFrame)
+{
+ const nsStyleDisplay* display = aSourceFrame->StyleDisplay();
+ nsTableRowGroupFrame* prevRg = do_QueryFrame(aSourceFrame);
+ // don't allow a page break after a repeated element ...
+ if ((display->mBreakAfter || (prevRg && prevRg->HasInternalBreakAfter())) &&
+ !IsRepeatedFrame(aSourceFrame)) {
+ return !(aNextFrame && IsRepeatedFrame(aNextFrame)); // or before
+ }
+
+ if (aNextFrame) {
+ display = aNextFrame->StyleDisplay();
+ // don't allow a page break before a repeated element ...
+ nsTableRowGroupFrame* nextRg = do_QueryFrame(aNextFrame);
+ if ((display->mBreakBefore ||
+ (nextRg && nextRg->HasInternalBreakBefore())) &&
+ !IsRepeatedFrame(aNextFrame)) {
+ return !IsRepeatedFrame(aSourceFrame); // or after
+ }
+ }
+ return false;
+}
+
+/* static */ void
+nsTableFrame::RegisterPositionedTablePart(nsIFrame* aFrame)
+{
+ // Supporting relative positioning for table parts other than table cells has
+ // the potential to break sites that apply 'position: relative' to those
+ // parts, expecting nothing to happen. We warn at the console to make tracking
+ // down the issue easy.
+ if (!IS_TABLE_CELL(aFrame->GetType())) {
+ nsIContent* content = aFrame->GetContent();
+ nsPresContext* presContext = aFrame->PresContext();
+ if (content && !presContext->HasWarnedAboutPositionedTableParts()) {
+ presContext->SetHasWarnedAboutPositionedTableParts();
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Layout: Tables"),
+ content->OwnerDoc(),
+ nsContentUtils::eLAYOUT_PROPERTIES,
+ "TablePartRelPosWarning");
+ }
+ }
+
+ nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(aFrame);
+ MOZ_ASSERT(tableFrame, "Should have a table frame here");
+ tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());
+
+ // Retrieve the positioned parts array for this table.
+ FrameProperties props = tableFrame->Properties();
+ FrameTArray* positionedParts = props.Get(PositionedTablePartArray());
+
+ // Lazily create the array if it doesn't exist yet.
+ if (!positionedParts) {
+ positionedParts = new FrameTArray;
+ props.Set(PositionedTablePartArray(), positionedParts);
+ }
+
+ // Add this frame to the list.
+ positionedParts->AppendElement(aFrame);
+}
+
+/* static */ void
+nsTableFrame::UnregisterPositionedTablePart(nsIFrame* aFrame,
+ nsIFrame* aDestructRoot)
+{
+ // Retrieve the table frame, and check if we hit aDestructRoot on the way.
+ bool didPassThrough;
+ nsTableFrame* tableFrame = GetTableFramePassingThrough(aDestructRoot, aFrame,
+ &didPassThrough);
+ if (!didPassThrough && !tableFrame->GetPrevContinuation()) {
+ // The table frame will be destroyed, and it's the first im flow (and thus
+ // owning the PositionedTablePartArray), so we don't need to do
+ // anything.
+ return;
+ }
+ tableFrame = static_cast<nsTableFrame*>(tableFrame->FirstContinuation());
+
+ // Retrieve the positioned parts array for this table.
+ FrameProperties props = tableFrame->Properties();
+ FrameTArray* positionedParts = props.Get(PositionedTablePartArray());
+
+ // Remove the frame.
+ MOZ_ASSERT(positionedParts && positionedParts->Contains(aFrame),
+ "Asked to unregister a positioned table part that wasn't registered");
+ if (positionedParts) {
+ positionedParts->RemoveElement(aFrame);
+ }
+}
+
+// XXX this needs to be cleaned up so that the frame constructor breaks out col group
+// frames into a separate child list, bug 343048.
+void
+nsTableFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ if (aListID != kPrincipalList) {
+ nsContainerFrame::SetInitialChildList(aListID, aChildList);
+ return;
+ }
+
+ MOZ_ASSERT(mFrames.IsEmpty() && mColGroups.IsEmpty(),
+ "unexpected second call to SetInitialChildList");
+
+ // XXXbz the below code is an icky cesspit that's only needed in its current
+ // form for two reasons:
+ // 1) Both rowgroups and column groups come in on the principal child list.
+ while (aChildList.NotEmpty()) {
+ nsIFrame* childFrame = aChildList.FirstChild();
+ aChildList.RemoveFirstChild();
+ const nsStyleDisplay* childDisplay = childFrame->StyleDisplay();
+
+ if (mozilla::StyleDisplay::TableColumnGroup == childDisplay->mDisplay) {
+ NS_ASSERTION(nsGkAtoms::tableColGroupFrame == childFrame->GetType(),
+ "This is not a colgroup");
+ mColGroups.AppendFrame(nullptr, childFrame);
+ }
+ else { // row groups and unknown frames go on the main list for now
+ mFrames.AppendFrame(nullptr, childFrame);
+ }
+ }
+
+ // If we have a prev-in-flow, then we're a table that has been split and
+ // so don't treat this like an append
+ if (!GetPrevInFlow()) {
+ // process col groups first so that real cols get constructed before
+ // anonymous ones due to cells in rows.
+ InsertColGroups(0, mColGroups);
+ InsertRowGroups(mFrames);
+ // calc collapsing borders
+ if (IsBorderCollapse()) {
+ SetFullBCDamageArea();
+ }
+ }
+}
+
+void
+nsTableFrame::AttributeChangedFor(nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsIAtom* aAttribute)
+{
+ nsTableCellFrame *cellFrame = do_QueryFrame(aFrame);
+ if (cellFrame) {
+ if ((nsGkAtoms::rowspan == aAttribute) ||
+ (nsGkAtoms::colspan == aAttribute)) {
+ nsTableCellMap* cellMap = GetCellMap();
+ if (cellMap) {
+ // for now just remove the cell from the map and reinsert it
+ int32_t rowIndex, colIndex;
+ cellFrame->GetRowIndex(rowIndex);
+ cellFrame->GetColIndex(colIndex);
+ RemoveCell(cellFrame, rowIndex);
+ AutoTArray<nsTableCellFrame*, 1> cells;
+ cells.AppendElement(cellFrame);
+ InsertCells(cells, rowIndex, colIndex - 1);
+
+ // XXX Should this use eStyleChange? It currently doesn't need
+ // to, but it might given more optimization.
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+}
+
+
+/* ****** CellMap methods ******* */
+
+/* return the effective col count */
+int32_t
+nsTableFrame::GetEffectiveColCount() const
+{
+ int32_t colCount = GetColCount();
+ if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto) {
+ nsTableCellMap* cellMap = GetCellMap();
+ if (!cellMap) {
+ return 0;
+ }
+ // don't count cols at the end that don't have originating cells
+ for (int32_t colIdx = colCount - 1; colIdx >= 0; colIdx--) {
+ if (cellMap->GetNumCellsOriginatingInCol(colIdx) > 0) {
+ break;
+ }
+ colCount--;
+ }
+ }
+ return colCount;
+}
+
+int32_t
+nsTableFrame::GetIndexOfLastRealCol()
+{
+ int32_t numCols = mColFrames.Length();
+ if (numCols > 0) {
+ for (int32_t colIdx = numCols - 1; colIdx >= 0; colIdx--) {
+ nsTableColFrame* colFrame = GetColFrame(colIdx);
+ if (colFrame) {
+ if (eColAnonymousCell != colFrame->GetColType()) {
+ return colIdx;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+nsTableColFrame*
+nsTableFrame::GetColFrame(int32_t aColIndex) const
+{
+ NS_ASSERTION(!GetPrevInFlow(), "GetColFrame called on next in flow");
+ int32_t numCols = mColFrames.Length();
+ if ((aColIndex >= 0) && (aColIndex < numCols)) {
+ return mColFrames.ElementAt(aColIndex);
+ }
+ else {
+ NS_ERROR("invalid col index");
+ return nullptr;
+ }
+}
+
+int32_t
+nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex,
+ const nsTableCellFrame& aCell) const
+{
+ nsTableCellMap* cellMap = GetCellMap();
+ NS_PRECONDITION (nullptr != cellMap, "bad call, cellMap not yet allocated.");
+
+ int32_t colIndex;
+ aCell.GetColIndex(colIndex);
+ return cellMap->GetEffectiveRowSpan(aRowIndex, colIndex);
+}
+
+int32_t
+nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell,
+ nsCellMap* aCellMap)
+{
+ nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1);
+
+ int32_t colIndex, rowIndex;
+ aCell.GetColIndex(colIndex);
+ aCell.GetRowIndex(rowIndex);
+
+ if (aCellMap)
+ return aCellMap->GetRowSpan(rowIndex, colIndex, true);
+ else
+ return tableCellMap->GetEffectiveRowSpan(rowIndex, colIndex);
+}
+
+int32_t
+nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell,
+ nsCellMap* aCellMap) const
+{
+ nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1);
+
+ int32_t colIndex, rowIndex;
+ aCell.GetColIndex(colIndex);
+ aCell.GetRowIndex(rowIndex);
+
+ if (aCellMap)
+ return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex);
+ else
+ return tableCellMap->GetEffectiveColSpan(rowIndex, colIndex);
+}
+
+bool
+nsTableFrame::HasMoreThanOneCell(int32_t aRowIndex) const
+{
+ nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1);
+ return tableCellMap->HasMoreThanOneCell(aRowIndex);
+}
+
+void
+nsTableFrame::AdjustRowIndices(int32_t aRowIndex,
+ int32_t aAdjustment)
+{
+ // Iterate over the row groups and adjust the row indices of all rows
+ // whose index is >= aRowIndex.
+ RowGroupArray rowGroups;
+ OrderRowGroups(rowGroups);
+
+ for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ rowGroups[rgIdx]->AdjustRowIndices(aRowIndex, aAdjustment);
+ }
+}
+
+
+void
+nsTableFrame::ResetRowIndices(const nsFrameList::Slice& aRowGroupsToExclude)
+{
+ // Iterate over the row groups and adjust the row indices of all rows
+ // omit the rowgroups that will be inserted later
+ RowGroupArray rowGroups;
+ OrderRowGroups(rowGroups);
+
+ int32_t rowIndex = 0;
+ nsTHashtable<nsPtrHashKey<nsTableRowGroupFrame> > excludeRowGroups;
+ nsFrameList::Enumerator excludeRowGroupsEnumerator(aRowGroupsToExclude);
+ while (!excludeRowGroupsEnumerator.AtEnd()) {
+ excludeRowGroups.PutEntry(static_cast<nsTableRowGroupFrame*>(excludeRowGroupsEnumerator.get()));
+ excludeRowGroupsEnumerator.Next();
+ }
+
+ for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+ if (!excludeRowGroups.GetEntry(rgFrame)) {
+ const nsFrameList& rowFrames = rgFrame->PrincipalChildList();
+ for (nsFrameList::Enumerator rows(rowFrames); !rows.AtEnd(); rows.Next()) {
+ if (mozilla::StyleDisplay::TableRow == rows.get()->StyleDisplay()->mDisplay) {
+ ((nsTableRowFrame *)rows.get())->SetRowIndex(rowIndex);
+ rowIndex++;
+ }
+ }
+ }
+ }
+}
+void
+nsTableFrame::InsertColGroups(int32_t aStartColIndex,
+ const nsFrameList::Slice& aColGroups)
+{
+ int32_t colIndex = aStartColIndex;
+ nsFrameList::Enumerator colGroups(aColGroups);
+ for (; !colGroups.AtEnd(); colGroups.Next()) {
+ MOZ_ASSERT(colGroups.get()->GetType() == nsGkAtoms::tableColGroupFrame);
+ nsTableColGroupFrame* cgFrame =
+ static_cast<nsTableColGroupFrame*>(colGroups.get());
+ cgFrame->SetStartColumnIndex(colIndex);
+ // XXXbz this sucks. AddColsToTable will actually remove colgroups from
+ // the list we're traversing! Need to fix things here. :( I guess this is
+ // why the old code used pointer-to-last-frame as opposed to
+ // pointer-to-frame-after-last....
+
+ // How about dealing with this by storing a const reference to the
+ // mNextSibling of the framelist's last frame, instead of storing a pointer
+ // to the first-after-next frame? Will involve making nsFrameList friend
+ // of nsIFrame, but it's time for that anyway.
+ cgFrame->AddColsToTable(colIndex, false,
+ colGroups.get()->PrincipalChildList());
+ int32_t numCols = cgFrame->GetColCount();
+ colIndex += numCols;
+ }
+
+ nsFrameList::Enumerator remainingColgroups = colGroups.GetUnlimitedEnumerator();
+ if (!remainingColgroups.AtEnd()) {
+ nsTableColGroupFrame::ResetColIndices(
+ static_cast<nsTableColGroupFrame*>(remainingColgroups.get()), colIndex);
+ }
+}
+
+void
+nsTableFrame::InsertCol(nsTableColFrame& aColFrame,
+ int32_t aColIndex)
+{
+ mColFrames.InsertElementAt(aColIndex, &aColFrame);
+ nsTableColType insertedColType = aColFrame.GetColType();
+ int32_t numCacheCols = mColFrames.Length();
+ nsTableCellMap* cellMap = GetCellMap();
+ if (cellMap) {
+ int32_t numMapCols = cellMap->GetColCount();
+ if (numCacheCols > numMapCols) {
+ bool removedFromCache = false;
+ if (eColAnonymousCell != insertedColType) {
+ nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1);
+ if (lastCol) {
+ nsTableColType lastColType = lastCol->GetColType();
+ if (eColAnonymousCell == lastColType) {
+ // remove the col from the cache
+ mColFrames.RemoveElementAt(numCacheCols - 1);
+ // remove the col from the eColGroupAnonymousCell col group
+ nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame *)mColGroups.LastChild();
+ if (lastColGroup) {
+ lastColGroup->RemoveChild(*lastCol, false);
+
+ // remove the col group if it is empty
+ if (lastColGroup->GetColCount() <= 0) {
+ mColGroups.DestroyFrame((nsIFrame*)lastColGroup);
+ }
+ }
+ removedFromCache = true;
+ }
+ }
+ }
+ if (!removedFromCache) {
+ cellMap->AddColsAtEnd(1);
+ }
+ }
+ }
+ // for now, just bail and recalc all of the collapsing borders
+ if (IsBorderCollapse()) {
+ TableArea damageArea(aColIndex, 0, 1, GetRowCount());
+ AddBCDamageArea(damageArea);
+ }
+}
+
+void
+nsTableFrame::RemoveCol(nsTableColGroupFrame* aColGroupFrame,
+ int32_t aColIndex,
+ bool aRemoveFromCache,
+ bool aRemoveFromCellMap)
+{
+ if (aRemoveFromCache) {
+ mColFrames.RemoveElementAt(aColIndex);
+ }
+ if (aRemoveFromCellMap) {
+ nsTableCellMap* cellMap = GetCellMap();
+ if (cellMap) {
+ // If we have some anonymous cols at the end already, we just
+ // add a new anonymous col.
+ if (!mColFrames.IsEmpty() &&
+ mColFrames.LastElement() && // XXXbz is this ever null?
+ mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
+ AppendAnonymousColFrames(1);
+ } else {
+ // All of our colframes correspond to actual <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;
+}
+
+// XXX this needs to be moved to nsCSSFrameConstructor
+nsTableColGroupFrame*
+nsTableFrame::CreateAnonymousColGroupFrame(nsTableColGroupType aColGroupType)
+{
+ nsIContent* colGroupContent = GetContent();
+ nsPresContext* presContext = PresContext();
+ nsIPresShell *shell = presContext->PresShell();
+
+ RefPtr<nsStyleContext> colGroupStyle;
+ colGroupStyle = shell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableColGroup, mStyleContext);
+ // Create a col group frame
+ nsIFrame* newFrame = NS_NewTableColGroupFrame(shell, colGroupStyle);
+ ((nsTableColGroupFrame *)newFrame)->SetColType(aColGroupType);
+ newFrame->Init(colGroupContent, this, nullptr);
+ return (nsTableColGroupFrame *)newFrame;
+}
+
+void
+nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd)
+{
+ // get the last col group frame
+ nsTableColGroupFrame* colGroupFrame =
+ static_cast<nsTableColGroupFrame*>(mColGroups.LastChild());
+
+ if (!colGroupFrame ||
+ (colGroupFrame->GetColType() != eColGroupAnonymousCell)) {
+ int32_t colIndex = (colGroupFrame) ?
+ colGroupFrame->GetStartColumnIndex() +
+ colGroupFrame->GetColCount() : 0;
+ colGroupFrame = CreateAnonymousColGroupFrame(eColGroupAnonymousCell);
+ if (!colGroupFrame) {
+ return;
+ }
+ // add the new frame to the child list
+ mColGroups.AppendFrame(this, colGroupFrame);
+ colGroupFrame->SetStartColumnIndex(colIndex);
+ }
+ AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell,
+ true);
+
+}
+
+// XXX this needs to be moved to nsCSSFrameConstructor
+// Right now it only creates the col frames at the end
+void
+nsTableFrame::AppendAnonymousColFrames(nsTableColGroupFrame* aColGroupFrame,
+ int32_t aNumColsToAdd,
+ nsTableColType aColType,
+ bool aAddToTable)
+{
+ NS_PRECONDITION(aColGroupFrame, "null frame");
+ NS_PRECONDITION(aColType != eColAnonymousCol, "Shouldn't happen");
+
+ nsIPresShell *shell = PresContext()->PresShell();
+
+ // Get the last col frame
+ nsFrameList newColFrames;
+
+ int32_t startIndex = mColFrames.Length();
+ int32_t lastIndex = startIndex + aNumColsToAdd - 1;
+
+ for (int32_t childX = startIndex; childX <= lastIndex; childX++) {
+ nsIContent* iContent;
+ RefPtr<nsStyleContext> styleContext;
+ nsStyleContext* parentStyleContext;
+
+ // all anonymous cols that we create here use a pseudo style context of the
+ // col group
+ iContent = aColGroupFrame->GetContent();
+ parentStyleContext = aColGroupFrame->StyleContext();
+ styleContext = shell->StyleSet()->
+ ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableCol, parentStyleContext);
+ // ASSERTION to check for bug 54454 sneaking back in...
+ NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames");
+
+ // create the new col frame
+ nsIFrame* colFrame = NS_NewTableColFrame(shell, styleContext);
+ ((nsTableColFrame *) colFrame)->SetColType(aColType);
+ colFrame->Init(iContent, aColGroupFrame, nullptr);
+
+ newColFrames.AppendFrame(nullptr, colFrame);
+ }
+ nsFrameList& cols = aColGroupFrame->GetWritableChildList();
+ nsIFrame* oldLastCol = cols.LastChild();
+ const nsFrameList::Slice& newCols =
+ cols.InsertFrames(nullptr, oldLastCol, newColFrames);
+ if (aAddToTable) {
+ // get the starting col index in the cache
+ int32_t startColIndex;
+ if (oldLastCol) {
+ startColIndex =
+ static_cast<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()
+{
+ NS_PRECONDITION(!GetPrevInFlow(),
+ "should only be called on first-in-flow");
+ if (mBits.mResizedColumns)
+ return; // already marked
+
+ for (nsTableFrame *f = this; f;
+ f = static_cast<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;
+ for (int32_t colIdx = endIndex; colIdx >= startIndex; colIdx--) {
+ nsTableColFrame* colFrame = GetColFrame(colIdx);
+ if (colFrame && (eColAnonymousCell == colFrame->GetColType())) {
+ nsTableColGroupFrame* cgFrame =
+ static_cast<nsTableColGroupFrame*>(colFrame->GetParent());
+ // remove the frame from the colgroup
+ cgFrame->RemoveChild(*colFrame, false);
+ // remove the frame from the cache, but not the cell map
+ RemoveCol(nullptr, colIdx, true, false);
+ numColsRemoved++;
+ }
+ else {
+ break;
+ }
+ }
+ return (aNumFrames - numColsRemoved);
+}
+
+void
+nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame,
+ int32_t aRowIndex)
+{
+ nsTableCellMap* cellMap = GetCellMap();
+ if (cellMap) {
+ TableArea damageArea(0, 0, 0, 0);
+ cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea);
+ MatchCellMapToColCache(cellMap);
+ if (IsBorderCollapse()) {
+ AddBCDamageArea(damageArea);
+ }
+ }
+}
+
+int32_t
+nsTableFrame::GetStartRowIndex(nsTableRowGroupFrame* aRowGroupFrame)
+{
+ RowGroupArray orderedRowGroups;
+ OrderRowGroups(orderedRowGroups);
+
+ int32_t rowIndex = 0;
+ for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
+ nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
+ if (rgFrame == aRowGroupFrame) {
+ break;
+ }
+ int32_t numRows = rgFrame->GetRowCount();
+ rowIndex += numRows;
+ }
+ return rowIndex;
+}
+
+// this cannot extend beyond a single row group
+void
+nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame,
+ int32_t aRowIndex,
+ nsTArray<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);
+ int32_t origNumRows = cellMap->GetRowCount();
+ int32_t numNewRows = aRowFrames.Length();
+ cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans, damageArea);
+ MatchCellMapToColCache(cellMap);
+ if (aRowIndex < origNumRows) {
+ AdjustRowIndices(aRowIndex, numNewRows);
+ }
+ // assign the correct row indices to the new rows. If they were adjusted above
+ // it may not have been done correctly because each row is constructed with index 0
+ for (int32_t rowB = 0; rowB < numNewRows; rowB++) {
+ nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowB);
+ rowFrame->SetRowIndex(aRowIndex + rowB);
+ }
+ if (IsBorderCollapse()) {
+ AddBCDamageArea(damageArea);
+ }
+ }
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("=== insertRowsAfter \n");
+ Dump(true, false, true);
+#endif
+
+ return numColsToAdd;
+}
+
+// this cannot extend beyond a single row group
+void
+nsTableFrame::RemoveRows(nsTableRowFrame& aFirstRowFrame,
+ int32_t aNumRowsToRemove,
+ bool aConsiderSpans)
+{
+#ifdef TBD_OPTIMIZATION
+ // decide if we need to rebalance. we have to do this here because the row group
+ // cannot do it when it gets the dirty reflow corresponding to the frame being destroyed
+ bool stopTelling = false;
+ for (nsIFrame* kidFrame = aFirstFrame.FirstChild(); (kidFrame && !stopAsking);
+ kidFrame = kidFrame->GetNextSibling()) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
+ if (cellFrame) {
+ stopTelling = tableFrame->CellChangedWidth(*cellFrame, cellFrame->GetPass1MaxElementWidth(),
+ cellFrame->GetMaximumWidth(), true);
+ }
+ }
+ // XXX need to consider what happens if there are cells that have rowspans
+ // into the deleted row. Need to consider moving rows if a rebalance doesn't happen
+#endif
+
+ int32_t firstRowIndex = aFirstRowFrame.GetRowIndex();
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex, aNumRowsToRemove);
+ Dump(true, false, true);
+#endif
+ nsTableCellMap* cellMap = GetCellMap();
+ if (cellMap) {
+ TableArea damageArea(0, 0, 0, 0);
+ cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans, damageArea);
+ MatchCellMapToColCache(cellMap);
+ if (IsBorderCollapse()) {
+ AddBCDamageArea(damageArea);
+ }
+ }
+ AdjustRowIndices(firstRowIndex, -aNumRowsToRemove);
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("=== removeRowsAfter\n");
+ Dump(true, true, true);
+#endif
+}
+
+// collect the rows ancestors of aFrame
+int32_t
+nsTableFrame::CollectRows(nsIFrame* aFrame,
+ nsTArray<nsTableRowFrame*>& aCollection)
+{
+ NS_PRECONDITION(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;
+ OrderRowGroups(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 OrderRowGroups,
+ // XXXbz this code is O(N*M) where N is number of new rowgroups
+ // and M is number of rowgroups we have!
+ uint32_t rgIndex;
+ for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
+ for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd();
+ rowgroups.Next()) {
+ if (orderedRowGroups[rgIndex] == rowgroups.get()) {
+ nsTableRowGroupFrame* priorRG =
+ (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
+ // create and add the cell map for the row group
+ cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG);
+
+ break;
+ }
+ }
+ }
+ cellMap->Synchronize(this);
+ ResetRowIndices(aRowGroups);
+
+ //now that the cellmaps are reordered too insert the rows
+ for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
+ for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd();
+ rowgroups.Next()) {
+ if (orderedRowGroups[rgIndex] == rowgroups.get()) {
+ nsTableRowGroupFrame* priorRG =
+ (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1];
+ // collect the new row frames in an array and add them to the table
+ int32_t numRows = CollectRows(rowgroups.get(), rows);
+ if (numRows > 0) {
+ int32_t rowIndex = 0;
+ if (priorRG) {
+ int32_t priorNumRows = priorRG->GetRowCount();
+ rowIndex = priorRG->GetStartRowIndex() + priorNumRows;
+ }
+ InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true);
+ rows.Clear();
+ }
+ break;
+ }
+ }
+ }
+
+ }
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("=== insertRowGroupsAfter\n");
+ Dump(true, true, true);
+#endif
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Child frame enumeration
+
+const nsFrameList&
+nsTableFrame::GetChildList(ChildListID aListID) const
+{
+ if (aListID == kColGroupList) {
+ return mColGroups;
+ }
+ return nsContainerFrame::GetChildList(aListID);
+}
+
+void
+nsTableFrame::GetChildLists(nsTArray<ChildList>* aLists) const
+{
+ nsContainerFrame::GetChildLists(aLists);
+ mColGroups.AppendIfNonempty(aLists, kColGroupList);
+}
+
+nsRect
+nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) {
+ *aSnap = false;
+ return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+void
+nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame)
+{
+ nsStyleContext *bgSC;
+ if (!nsCSSRendering::FindBackground(aFrame, &bgSC))
+ return;
+ if (!bgSC->StyleBackground()->HasFixedBackground(aFrame))
+ return;
+
+ mPartHasFixedBackground = true;
+}
+
+nsDisplayItemGeometry*
+nsDisplayTableItem::AllocateGeometry(nsDisplayListBuilder* aBuilder)
+{
+ return new nsDisplayTableItemGeometry(this, aBuilder,
+ mFrame->GetOffsetTo(mFrame->PresContext()->PresShell()->GetRootFrame()));
+}
+
+void
+nsDisplayTableItem::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayTableItemGeometry*>(aGeometry);
+
+ bool invalidateForAttachmentFixed = false;
+ if (mDrawsBackground && mPartHasFixedBackground) {
+ nsPoint frameOffsetToViewport = mFrame->GetOffsetTo(
+ mFrame->PresContext()->PresShell()->GetRootFrame());
+ invalidateForAttachmentFixed =
+ frameOffsetToViewport != geometry->mFrameOffsetToViewport;
+ }
+
+ if (invalidateForAttachmentFixed ||
+ (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages())) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+class nsDisplayTableBorderBackground : public nsDisplayTableItem {
+public:
+ nsDisplayTableBorderBackground(nsDisplayListBuilder* aBuilder,
+ nsTableFrame* aFrame,
+ bool aDrawsBackground) :
+ nsDisplayTableItem(aBuilder, aFrame, aDrawsBackground) {
+ MOZ_COUNT_CTOR(nsDisplayTableBorderBackground);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayTableBorderBackground() {
+ MOZ_COUNT_DTOR(nsDisplayTableBorderBackground);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("TableBorderBackground", TYPE_TABLE_BORDER_BACKGROUND)
+};
+
+#ifdef DEBUG
+static bool
+IsFrameAllowedInTable(nsIAtom* aType)
+{
+ return IS_TABLE_CELL(aType) ||
+ nsGkAtoms::tableRowFrame == aType ||
+ nsGkAtoms::tableRowGroupFrame == aType ||
+ nsGkAtoms::scrollFrame == aType ||
+ nsGkAtoms::tableFrame == aType ||
+ nsGkAtoms::tableColFrame == aType ||
+ nsGkAtoms::tableColGroupFrame == aType;
+}
+#endif
+
+void
+nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawResult result = static_cast<nsTableFrame*>(mFrame)->
+ PaintTableBorderBackground(aBuilder, *aCtx, mVisibleRect,
+ ToReferenceFrame());
+
+ nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
+}
+
+static int32_t
+GetTablePartRank(nsDisplayItem* aItem)
+{
+ nsIAtom* type = aItem->Frame()->GetType();
+ if (type == nsGkAtoms::tableFrame)
+ return 0;
+ if (type == nsGkAtoms::tableRowGroupFrame)
+ return 1;
+ if (type == nsGkAtoms::tableRowFrame)
+ return 2;
+ return 3;
+}
+
+static bool CompareByTablePartRank(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
+ void* aClosure)
+{
+ return GetTablePartRank(aItem1) <= GetTablePartRank(aItem2);
+}
+
+/* static */ void
+nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
+ const nsRect& aDirtyRect, const nsDisplayListSet& aLists)
+{
+ // This is similar to what nsContainerFrame::BuildDisplayListForNonBlockChildren
+ // does, except that we allow the children's background and borders to go
+ // in our BorderBackground list. This doesn't really affect background
+ // painting --- the children won't actually draw their own backgrounds
+ // because the nsTableFrame already drew them, unless a child has its own
+ // stacking context, in which case the child won't use its passed-in
+ // BorderBackground list anyway. It does affect cell borders though; this
+ // lets us get cell borders into the nsTableFrame's BorderBackground list.
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
+ }
+}
+
+/* static */ void
+nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder,
+ nsFrame* aFrame,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists,
+ nsDisplayTableItem* aDisplayItem,
+ DisplayGenericTablePartTraversal aTraversal)
+{
+ nsDisplayList eventsBorderBackground;
+ // If we need to sort the event backgrounds, then we'll put descendants'
+ // display items into their own set of lists.
+ bool sortEventBackgrounds = aDisplayItem && aBuilder->IsForEventDelivery();
+ nsDisplayListCollection separatedCollection;
+ const nsDisplayListSet* lists = sortEventBackgrounds ? &separatedCollection : &aLists;
+
+ nsAutoPushCurrentTableItem pushTableItem;
+ if (aDisplayItem) {
+ pushTableItem.Push(aBuilder, aDisplayItem);
+ }
+
+ if (aFrame->IsVisibleForPainting(aBuilder)) {
+ nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem();
+ // currentItem may be null, when none of the table parts have a
+ // background or border
+ if (currentItem) {
+ currentItem->UpdateForFrameBackground(aFrame);
+ }
+
+ // Paint the outset box-shadows for the table frames
+ bool hasBoxShadow = aFrame->StyleEffects()->mBoxShadow != nullptr;
+ if (hasBoxShadow) {
+ lists->BorderBackground()->AppendNewToTop(
+ new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, aFrame));
+ }
+
+ // Create dedicated background display items per-frame when we're
+ // handling events.
+ // XXX how to handle collapsed borders?
+ if (aBuilder->IsForEventDelivery()) {
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame,
+ aFrame->GetRectRelativeToSelf(),
+ lists->BorderBackground());
+ }
+
+ // Paint the inset box-shadows for the table frames
+ if (hasBoxShadow) {
+ lists->BorderBackground()->AppendNewToTop(
+ new (aBuilder) nsDisplayBoxShadowInner(aBuilder, aFrame));
+ }
+ }
+
+ aTraversal(aBuilder, aFrame, aDirtyRect, *lists);
+
+ if (sortEventBackgrounds) {
+ // Ensure that the table frame event background goes before the
+ // table rowgroups event backgrounds, before the table row event backgrounds,
+ // before everything else (cells and their blocks)
+ separatedCollection.BorderBackground()->Sort(CompareByTablePartRank, nullptr);
+ separatedCollection.MoveTo(aLists);
+ }
+
+ aFrame->DisplayOutline(aBuilder, aLists);
+}
+
+static bool
+AnyTablePartHasBorderOrBackground(nsIFrame* aStart, nsIFrame* aEnd)
+{
+ for (nsIFrame* f = aStart; f != aEnd; f = f->GetNextSibling()) {
+ NS_ASSERTION(IsFrameAllowedInTable(f->GetType()), "unexpected frame type");
+
+ if (FrameHasBorderOrBackground(f))
+ return true;
+
+ nsTableCellFrame *cellFrame = do_QueryFrame(f);
+ if (cellFrame)
+ continue;
+
+ if (AnyTablePartHasBorderOrBackground(f->PrincipalChildList().FirstChild(), nullptr))
+ return true;
+ }
+
+ return false;
+}
+
+static void
+UpdateItemForColGroupBackgrounds(nsDisplayTableItem* item,
+ const nsFrameList& aFrames) {
+ for (nsFrameList::Enumerator e(aFrames); !e.AtEnd(); e.Next()) {
+ nsTableColGroupFrame* cg = static_cast<nsTableColGroupFrame*>(e.get());
+ item->UpdateForFrameBackground(cg);
+ for (nsTableColFrame* colFrame = cg->GetFirstColumn(); colFrame;
+ colFrame = colFrame->GetNextCol()) {
+ item->UpdateForFrameBackground(colFrame);
+ }
+ }
+}
+
+// table paint code is concerned primarily with borders and bg color
+// SEC: TODO: adjust the rect for captions
+void
+nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255,128,255));
+
+ nsDisplayTableItem* item = nullptr;
+ if (IsVisibleInSelection(aBuilder)) {
+ nsMargin deflate = GetDeflationForBackground(PresContext());
+ if (StyleVisibility()->IsVisible()) {
+ // If 'deflate' is (0,0,0,0) then we can paint the table background
+ // in its own display item, so do that to take advantage of
+ // opacity and visibility optimizations
+ if (deflate == nsMargin(0, 0, 0, 0)) {
+ DisplayBackgroundUnconditional(aBuilder, aLists, false);
+ }
+ }
+
+ // This background is created if any of the table parts are visible,
+ // or if we're doing event handling (since DisplayGenericTablePart
+ // needs the item for the |sortEventBackgrounds|-dependent code).
+ // Specific visibility decisions are delegated to the table background
+ // painter, which handles borders and backgrounds for the table.
+ if (aBuilder->IsForEventDelivery() ||
+ AnyTablePartHasBorderOrBackground(this, GetNextSibling()) ||
+ AnyTablePartHasBorderOrBackground(mColGroups.FirstChild(), nullptr)) {
+ item = new (aBuilder) nsDisplayTableBorderBackground(aBuilder, this,
+ deflate != nsMargin(0, 0, 0, 0));
+ aLists.BorderBackground()->AppendNewToTop(item);
+ }
+ }
+ DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item);
+ if (item) {
+ UpdateItemForColGroupBackgrounds(item, mColGroups);
+ }
+}
+
+nsMargin
+nsTableFrame::GetDeflationForBackground(nsPresContext* aPresContext) const
+{
+ if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode() ||
+ !IsBorderCollapse())
+ return nsMargin(0,0,0,0);
+
+ WritingMode wm = GetWritingMode();
+ return GetOuterBCBorder(wm).GetPhysicalMargin(wm);
+}
+
+// XXX We don't put the borders and backgrounds in tree order like we should.
+// That requires some major surgery which we aren't going to do right now.
+DrawResult
+nsTableFrame::PaintTableBorderBackground(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ nsPresContext* presContext = PresContext();
+
+ uint32_t bgFlags = aBuilder->GetBackgroundPaintFlags();
+ PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SYNC_DECODE_IMAGES
+ : PaintBorderFlags();
+
+ TableBackgroundPainter painter(this, TableBackgroundPainter::eOrigin_Table,
+ presContext, aRenderingContext,
+ aDirtyRect, aPt, bgFlags);
+ nsMargin deflate = GetDeflationForBackground(presContext);
+ // If 'deflate' is (0,0,0,0) then we'll paint the table background
+ // in a separate display item, so don't do it here.
+ DrawResult result =
+ painter.PaintTable(this, deflate, deflate != nsMargin(0, 0, 0, 0));
+
+ if (StyleVisibility()->IsVisible()) {
+ if (!IsBorderCollapse()) {
+ Sides skipSides = GetSkipSides();
+ nsRect rect(aPt, mRect.Size());
+
+ result &=
+ nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
+ aDirtyRect, rect, mStyleContext,
+ borderFlags, skipSides);
+ } else {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(aPt,
+ PresContext()->AppUnitsPerDevPixel());
+
+ // XXX we should probably get rid of this translation at some stage
+ // But that would mean modifying PaintBCBorders, ugh
+ AutoRestoreTransform autoRestoreTransform(drawTarget);
+ drawTarget->SetTransform(
+ drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
+
+ PaintBCBorders(*drawTarget, aDirtyRect - aPt);
+ }
+ }
+
+ return result;
+}
+
+nsIFrame::LogicalSides
+nsTableFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
+{
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return LogicalSides();
+ }
+
+ LogicalSides skip;
+ // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto
+ // account for pagination
+ if (nullptr != GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (nullptr != GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+
+void
+nsTableFrame::SetColumnDimensions(nscoord aBSize, WritingMode aWM,
+ const LogicalMargin& aBorderPadding,
+ const nsSize& aContainerSize)
+{
+ const nscoord colBSize = aBSize - (aBorderPadding.BStartEnd(aWM) +
+ GetRowSpacing(-1) + GetRowSpacing(GetRowCount()));
+ int32_t colIdx = 0;
+ LogicalPoint colGroupOrigin(aWM,
+ aBorderPadding.IStart(aWM) + GetColSpacing(-1),
+ aBorderPadding.BStart(aWM) + GetRowSpacing(-1));
+ nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
+ for (nsIFrame* colGroupFrame : mColGroups) {
+ MOZ_ASSERT(colGroupFrame->GetType() == nsGkAtoms::tableColGroupFrame);
+ // first we need to figure out the size of the colgroup
+ int32_t groupFirstCol = colIdx;
+ nscoord colGroupISize = 0;
+ nscoord cellSpacingI = 0;
+ const nsFrameList& columnList = colGroupFrame->PrincipalChildList();
+ for (nsIFrame* colFrame : columnList) {
+ if (mozilla::StyleDisplay::TableColumn ==
+ colFrame->StyleDisplay()->mDisplay) {
+ NS_ASSERTION(colIdx < GetColCount(), "invalid number of columns");
+ cellSpacingI = GetColSpacing(colIdx);
+ colGroupISize += fif->GetColumnISizeFromFirstInFlow(colIdx) +
+ cellSpacingI;
+ ++colIdx;
+ }
+ }
+ if (colGroupISize) {
+ colGroupISize -= cellSpacingI;
+ }
+
+ LogicalRect colGroupRect(aWM, colGroupOrigin.I(aWM), colGroupOrigin.B(aWM),
+ colGroupISize, colBSize);
+ colGroupFrame->SetRect(aWM, colGroupRect, aContainerSize);
+ nsSize colGroupSize = colGroupFrame->GetSize();
+
+ // then we can place the columns correctly within the group
+ colIdx = groupFirstCol;
+ LogicalPoint colOrigin(aWM);
+ for (nsIFrame* colFrame : columnList) {
+ if (mozilla::StyleDisplay::TableColumn ==
+ colFrame->StyleDisplay()->mDisplay) {
+ nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
+ LogicalRect colRect(aWM, colOrigin.I(aWM), colOrigin.B(aWM),
+ colISize, colBSize);
+ colFrame->SetRect(aWM, colRect, colGroupSize);
+ cellSpacingI = GetColSpacing(colIdx);
+ colOrigin.I(aWM) += colISize + cellSpacingI;
+ ++colIdx;
+ }
+ }
+
+ colGroupOrigin.I(aWM) += colGroupISize + cellSpacingI;
+ }
+}
+
+// SEC: TODO need to worry about continuing frames prev/next in flow for splitting across pages.
+
+// XXX this could be made more general to handle row modifications that change the
+// table bsize, but first we need to scrutinize every Invalidate
+void
+nsTableFrame::ProcessRowInserted(nscoord aNewBSize)
+{
+ SetRowInserted(false); // reset the bit that got us here
+ nsTableFrame::RowGroupArray rowGroups;
+ OrderRowGroups(rowGroups);
+ // find the row group containing the inserted row
+ for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+ NS_ASSERTION(rgFrame, "Must have rgFrame here");
+ // find the row that was inserted first
+ for (nsIFrame* childFrame : rgFrame->PrincipalChildList()) {
+ nsTableRowFrame *rowFrame = do_QueryFrame(childFrame);
+ if (rowFrame) {
+ if (rowFrame->IsFirstInserted()) {
+ rowFrame->SetFirstInserted(false);
+ // damage the table from the 1st row inserted to the end of the table
+ nsIFrame::InvalidateFrame();
+ // XXXbz didn't we do this up front? Why do we need to do it again?
+ SetRowInserted(false);
+ return; // found it, so leave
+ }
+ }
+ }
+ }
+}
+
+/* virtual */ void
+nsTableFrame::MarkIntrinsicISizesDirty()
+{
+ nsITableLayoutStrategy* tls = LayoutStrategy();
+ if (MOZ_UNLIKELY(!tls)) {
+ // This is a FrameNeedsReflow() from nsBlockFrame::RemoveFrame()
+ // walking up the ancestor chain in a table next-in-flow. In this case
+ // our original first-in-flow (which owns the TableLayoutStrategy) has
+ // already been destroyed and unhooked from the flow chain and thusly
+ // LayoutStrategy() returns null. All the frames in the flow will be
+ // destroyed so no need to mark anything dirty here. See bug 595758.
+ return;
+ }
+ tls->MarkIntrinsicISizesDirty();
+
+ // XXXldb Call SetBCDamageArea?
+
+ nsContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+/* virtual */ nscoord
+nsTableFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ if (NeedToCalcBCBorders())
+ CalcBCBorders();
+
+ ReflowColGroups(aRenderingContext);
+
+ return LayoutStrategy()->GetMinISize(aRenderingContext);
+}
+
+/* virtual */ nscoord
+nsTableFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ if (NeedToCalcBCBorders())
+ CalcBCBorders();
+
+ ReflowColGroups(aRenderingContext);
+
+ return LayoutStrategy()->GetPrefISize(aRenderingContext, false);
+}
+
+/* virtual */ nsIFrame::IntrinsicISizeOffsetData
+nsTableFrame::IntrinsicISizeOffsets()
+{
+ IntrinsicISizeOffsetData result = nsContainerFrame::IntrinsicISizeOffsets();
+
+ result.hMargin = 0;
+ result.hPctMargin = 0;
+
+ if (IsBorderCollapse()) {
+ result.hPadding = 0;
+ result.hPctPadding = 0;
+
+ WritingMode wm = GetWritingMode();
+ LogicalMargin outerBC = GetIncludedOuterBCBorder(wm);
+ result.hBorder = outerBC.IStartEnd(wm);
+ }
+
+ return result;
+}
+
+/* virtual */
+LogicalSize
+nsTableFrame::ComputeSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ LogicalSize result =
+ nsContainerFrame::ComputeSize(aRenderingContext, aWM,
+ aCBSize, aAvailableISize,
+ aMargin, aBorder, aPadding, aFlags);
+
+ // XXX The code below doesn't make sense if the caller's writing mode
+ // is orthogonal to this frame's. Not sure yet what should happen then;
+ // for now, just bail out.
+ if (aWM.IsVertical() != GetWritingMode().IsVertical()) {
+ return result;
+ }
+
+ // If we're a container for font size inflation, then shrink
+ // wrapping inside of us should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(this);
+
+ // Tables never shrink below their min inline-size.
+ nscoord minISize = GetMinISize(aRenderingContext);
+ if (minISize > result.ISize(aWM)) {
+ result.ISize(aWM) = minISize;
+ }
+
+ return result;
+}
+
+nscoord
+nsTableFrame::TableShrinkISizeToFit(nsRenderingContext *aRenderingContext,
+ nscoord aISizeInCB)
+{
+ // If we're a container for font size inflation, then shrink
+ // wrapping inside of us should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(this);
+
+ nscoord result;
+ nscoord minISize = GetMinISize(aRenderingContext);
+ if (minISize > aISizeInCB) {
+ result = minISize;
+ } else {
+ // Tables shrink inline-size to fit with a slightly different algorithm
+ // from the one they use for their intrinsic isize (the difference
+ // relates to handling of percentage isizes on columns). So this
+ // function differs from nsFrame::ShrinkWidthToFit by only the
+ // following line.
+ // Since we've already called GetMinISize, we don't need to do any
+ // of the other stuff GetPrefISize does.
+ nscoord prefISize =
+ LayoutStrategy()->GetPrefISize(aRenderingContext, true);
+ if (prefISize > aISizeInCB) {
+ result = aISizeInCB;
+ } else {
+ result = prefISize;
+ }
+ }
+ return result;
+}
+
+/* virtual */
+LogicalSize
+nsTableFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ // Tables always shrink-wrap.
+ nscoord cbBased = aAvailableISize - aMargin.ISize(aWM) - aBorder.ISize(aWM) -
+ aPadding.ISize(aWM);
+ return LogicalSize(aWM, TableShrinkISizeToFit(aRenderingContext, cbBased),
+ NS_UNCONSTRAINEDSIZE);
+}
+
+// Return true if aParentReflowInput.frame or any of its ancestors within
+// the containing table have non-auto bsize. (e.g. pct or fixed bsize)
+bool
+nsTableFrame::AncestorsHaveStyleBSize(const ReflowInput& aParentReflowInput)
+{
+ WritingMode wm = aParentReflowInput.GetWritingMode();
+ for (const ReflowInput* rs = &aParentReflowInput;
+ rs && rs->mFrame; rs = rs->mParentReflowInput) {
+ nsIAtom* frameType = rs->mFrame->GetType();
+ if (IS_TABLE_CELL(frameType) ||
+ (nsGkAtoms::tableRowFrame == frameType) ||
+ (nsGkAtoms::tableRowGroupFrame == frameType)) {
+ const nsStyleCoord &bsize = rs->mStylePosition->BSize(wm);
+ // calc() with percentages treated like 'auto' on internal table elements
+ if (bsize.GetUnit() != eStyleUnit_Auto &&
+ (!bsize.IsCalcUnit() || !bsize.HasPercent())) {
+ return true;
+ }
+ }
+ else if (nsGkAtoms::tableFrame == frameType) {
+ // we reached the containing table, so always return
+ return rs->mStylePosition->BSize(wm).GetUnit() != eStyleUnit_Auto;
+ }
+ }
+ return false;
+}
+
+// See if a special block-size reflow needs to occur and if so,
+// call RequestSpecialBSizeReflow
+void
+nsTableFrame::CheckRequestSpecialBSizeReflow(const ReflowInput& aReflowInput)
+{
+ NS_ASSERTION(IS_TABLE_CELL(aReflowInput.mFrame->GetType()) ||
+ aReflowInput.mFrame->GetType() == nsGkAtoms::tableRowFrame ||
+ aReflowInput.mFrame->GetType() == nsGkAtoms::tableRowGroupFrame ||
+ aReflowInput.mFrame->GetType() == nsGkAtoms::tableFrame,
+ "unexpected frame type");
+ WritingMode wm = aReflowInput.GetWritingMode();
+ if (!aReflowInput.mFrame->GetPrevInFlow() && // 1st in flow
+ (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedBSize() || // no computed bsize
+ 0 == aReflowInput.ComputedBSize()) &&
+ eStyleUnit_Percent == aReflowInput.mStylePosition->BSize(wm).GetUnit() && // pct bsize
+ nsTableFrame::AncestorsHaveStyleBSize(*aReflowInput.mParentReflowInput)) {
+ nsTableFrame::RequestSpecialBSizeReflow(aReflowInput);
+ }
+}
+
+// Notify the frame and its ancestors (up to the containing table) that a special
+// bsize reflow will occur. During a special bsize reflow, a table, row group,
+// row, or cell returns the last size it was reflowed at. However, the table may
+// change the bsize of row groups, rows, cells in DistributeBSizeToRows after.
+// And the row group can change the bsize of rows, cells in CalculateRowBSizes.
+void
+nsTableFrame::RequestSpecialBSizeReflow(const ReflowInput& aReflowInput)
+{
+ // notify the frame and its ancestors of the special reflow, stopping at the containing table
+ for (const ReflowInput* rs = &aReflowInput; rs && rs->mFrame; rs = rs->mParentReflowInput) {
+ nsIAtom* frameType = rs->mFrame->GetType();
+ NS_ASSERTION(IS_TABLE_CELL(frameType) ||
+ nsGkAtoms::tableRowFrame == frameType ||
+ nsGkAtoms::tableRowGroupFrame == frameType ||
+ nsGkAtoms::tableFrame == frameType,
+ "unexpected frame type");
+
+ rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ if (nsGkAtoms::tableFrame == frameType) {
+ NS_ASSERTION(rs != &aReflowInput,
+ "should not request special bsize reflow for table");
+ // always stop when we reach a table
+ break;
+ }
+ }
+}
+
+/******************************************************************************************
+ * Before reflow, intrinsic inline-size calculation is done using GetMinISize
+ * and GetPrefISize. This used to be known as pass 1 reflow.
+ *
+ * After the intrinsic isize calculation, the table determines the
+ * column widths using BalanceColumnISizes() and
+ * then reflows each child again with a constrained avail isize. This reflow is referred to
+ * as the pass 2 reflow.
+ *
+ * A special bsize reflow (pass 3 reflow) can occur during an initial or resize reflow
+ * if (a) a row group, row, cell, or a frame inside a cell has a percent bsize but no computed
+ * bsize or (b) in paginated mode, a table has a bsize. (a) supports percent nested tables
+ * contained inside cells whose bsizes aren't known until after the pass 2 reflow. (b) is
+ * necessary because the table cannot split until after the pass 2 reflow. The mechanics of
+ * the special bsize reflow (variety a) are as follows:
+ *
+ * 1) Each table related frame (table, row group, row, cell) implements NeedsSpecialReflow()
+ * to indicate that it should get the reflow. It does this when it has a percent bsize but
+ * no computed bsize by calling CheckRequestSpecialBSizeReflow(). This method calls
+ * RequestSpecialBSizeReflow() which calls SetNeedSpecialReflow() on its ancestors until
+ * it reaches the containing table and calls SetNeedToInitiateSpecialReflow() on it. For
+ * percent bsize frames inside cells, during DidReflow(), the cell's NotifyPercentBSize()
+ * is called (the cell is the reflow state's mPercentBSizeObserver in this case).
+ * NotifyPercentBSize() calls RequestSpecialBSizeReflow().
+ *
+ * XXX (jfkthame) This comment appears to be out of date; it refers to methods/flags
+ * that are no longer present in the code.
+ * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true) was called, it
+ * will do the special bsize reflow, setting the reflow state's mFlags.mSpecialBSizeReflow
+ * to true and mSpecialHeightInitiator to itself. It won't do this if IsPrematureSpecialHeightReflow()
+ * returns true because in that case another special bsize reflow will be coming along with the
+ * containing table as the mSpecialHeightInitiator. It is only relevant to do the reflow when
+ * the mSpecialHeightInitiator is the containing table, because if it is a remote ancestor, then
+ * appropriate bsizes will not be known.
+ *
+ * 3) Since the bsizes of the table, row groups, rows, and cells was determined during the pass 2
+ * reflow, they return their last desired sizes during the special bsize reflow. The reflow only
+ * permits percent bsize frames inside the cells to resize based on the cells bsize and that bsize
+ * was determined during the pass 2 reflow.
+ *
+ * So, in the case of deeply nested tables, all of the tables that were told to initiate a special
+ * reflow will do so, but if a table is already in a special reflow, it won't inititate the reflow
+ * until the current initiator is its containing table. Since these reflows are only received by
+ * frames that need them and they don't cause any rebalancing of tables, the extra overhead is minimal.
+ *
+ * The type of special reflow that occurs during printing (variety b) follows the same mechanism except
+ * that all frames will receive the reflow even if they don't really need them.
+ *
+ * Open issues with the special bsize reflow:
+ *
+ * 1) At some point there should be 2 kinds of special bsize reflows because (a) and (b) above are
+ * really quite different. This would avoid unnecessary reflows during printing.
+ * 2) When a cell contains frames whose percent bsizes > 100%, there is data loss (see bug 115245).
+ * However, this can also occur if a cell has a fixed bsize and there is no special bsize reflow.
+ *
+ * XXXldb Special bsize reflow should really be its own method, not
+ * part of nsIFrame::Reflow. It should then call nsIFrame::Reflow on
+ * the contents of the cells to do the necessary block-axis resizing.
+ *
+ ******************************************************************************************/
+
+/* Layout the entire inner table. */
+void
+nsTableFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ bool isPaginated = aPresContext->IsPaginated();
+ WritingMode wm = aReflowInput.GetWritingMode();
+
+ aStatus = NS_FRAME_COMPLETE;
+ if (!GetPrevInFlow() && !mTableLayoutStrategy) {
+ NS_ERROR("strategy should have been created in Init");
+ return;
+ }
+
+ // see if collapsing borders need to be calculated
+ if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) {
+ CalcBCBorders();
+ }
+
+ aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
+
+ // Check for an overflow list, and append any row group frames being pushed
+ MoveOverflowToChildList();
+
+ bool haveDesiredBSize = false;
+ SetHaveReflowedColGroups(false);
+
+ // Reflow the entire table (pass 2 and possibly pass 3). This phase is necessary during a
+ // constrained initial reflow and other reflows which require either a strategy init or balance.
+ // This isn't done during an unconstrained reflow, because it will occur later when the parent
+ // reflows with a constrained isize.
+ bool fixupKidPositions = false;
+ if (NS_SUBTREE_DIRTY(this) ||
+ aReflowInput.ShouldReflowAllKids() ||
+ IsGeometryDirty() ||
+ aReflowInput.IsBResize()) {
+
+ if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE ||
+ // Also check IsBResize(), to handle the first Reflow preceding a
+ // special bsize Reflow, when we've already had a special bsize
+ // Reflow (where ComputedBSize() would not be
+ // NS_UNCONSTRAINEDSIZE, but without a style change in between).
+ aReflowInput.IsBResize()) {
+ // XXX Eventually, we should modify DistributeBSizeToRows to use
+ // nsTableRowFrame::GetInitialBSize instead of nsIFrame::BSize().
+ // That way, it will make its calculations based on internal table
+ // frame bsizes as they are before they ever had any extra bsize
+ // distributed to them. In the meantime, this reflows all the
+ // internal table frames, which restores them to their state before
+ // DistributeBSizeToRows was called.
+ SetGeometryDirty();
+ }
+
+ bool needToInitiateSpecialReflow =
+ HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ // see if an extra reflow will be necessary in pagination mode
+ // when there is a specified table bsize
+ if (isPaginated && !GetPrevInFlow() && (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize())) {
+ nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput);
+ if ((tableSpecifiedBSize > 0) &&
+ (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE)) {
+ needToInitiateSpecialReflow = true;
+ }
+ }
+ nsIFrame* lastChildReflowed = nullptr;
+
+ NS_ASSERTION(!aReflowInput.mFlags.mSpecialBSizeReflow,
+ "Shouldn't be in special bsize reflow here!");
+
+ // do the pass 2 reflow unless this is a special bsize reflow and we will be
+ // initiating a special bsize reflow
+ // XXXldb I changed this. Should I change it back?
+
+ // if we need to initiate a special bsize reflow, then don't constrain the
+ // bsize of the reflow before that
+ nscoord availBSize = needToInitiateSpecialReflow
+ ? NS_UNCONSTRAINEDSIZE
+ : aReflowInput.AvailableBSize();
+
+ ReflowTable(aDesiredSize, aReflowInput, availBSize,
+ lastChildReflowed, aStatus);
+ // If ComputedWidth is unconstrained, we may need to fix child positions
+ // later (in vertical-rl mode) due to use of 0 as a dummy
+ // containerSize.width during ReflowChildren.
+ fixupKidPositions = wm.IsVerticalRL() &&
+ aReflowInput.ComputedWidth() == NS_UNCONSTRAINEDSIZE;
+
+ // reevaluate special bsize reflow conditions
+ if (HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ needToInitiateSpecialReflow = true;
+ }
+
+ // XXXldb Are all these conditions correct?
+ if (needToInitiateSpecialReflow && NS_FRAME_IS_COMPLETE(aStatus)) {
+ // XXXldb Do we need to set the IsBResize flag on any reflow states?
+
+ ReflowInput &mutable_rs =
+ const_cast<ReflowInput&>(aReflowInput);
+
+ // distribute extra block-direction space to rows
+ CalcDesiredBSize(aReflowInput, aDesiredSize);
+ mutable_rs.mFlags.mSpecialBSizeReflow = true;
+
+ ReflowTable(aDesiredSize, aReflowInput, aReflowInput.AvailableBSize(),
+ lastChildReflowed, aStatus);
+
+ if (lastChildReflowed && NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
+ // if there is an incomplete child, then set the desired bsize
+ // to include it but not the next one
+ LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
+ aDesiredSize.BSize(wm) =
+ borderPadding.BEnd(wm) + GetRowSpacing(GetRowCount()) +
+ lastChildReflowed->GetNormalRect().YMost(); // XXX YMost should be B-flavored
+ }
+ haveDesiredBSize = true;
+
+ mutable_rs.mFlags.mSpecialBSizeReflow = false;
+ }
+ }
+
+ aDesiredSize.ISize(wm) = aReflowInput.ComputedISize() +
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
+ if (!haveDesiredBSize) {
+ CalcDesiredBSize(aReflowInput, aDesiredSize);
+ }
+ if (IsRowInserted()) {
+ ProcessRowInserted(aDesiredSize.BSize(wm));
+ }
+
+ if (fixupKidPositions) {
+ // If we didn't already know the containerSize (and so used zero during
+ // ReflowChildren), then we need to update the block-position of our kids.
+ for (nsIFrame* kid : mFrames) {
+ kid->MovePositionBy(nsPoint(aDesiredSize.Width(), 0));
+ RePositionViews(kid);
+ }
+ }
+
+ // Calculate the overflow area contribution from our children. We couldn't
+ // do this on the fly during ReflowChildren(), because in vertical-rl mode
+ // with unconstrained width, we weren't placing them in their final positions
+ // until the fixupKidPositions loop just above.
+ for (nsIFrame* kid : mFrames) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid);
+ }
+
+ LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
+ SetColumnDimensions(aDesiredSize.BSize(wm), wm, borderPadding,
+ aDesiredSize.PhysicalSize());
+ if (NeedToCollapse() &&
+ (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize())) {
+ AdjustForCollapsingRowsCols(aDesiredSize, wm, borderPadding);
+ }
+
+ // If there are any relatively-positioned table parts, we need to reflow their
+ // absolutely-positioned descendants now that their dimensions are final.
+ FixupPositionedTableParts(aPresContext, aDesiredSize, aReflowInput);
+
+ // make sure the table overflow area does include the table rect.
+ nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()) ;
+
+ if (!ShouldApplyOverflowClipping(this, aReflowInput.mStyleDisplay)) {
+ // collapsed border may leak out
+ LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm);
+ tableRect.Inflate(bcMargin.GetPhysicalMargin(wm));
+ }
+ aDesiredSize.mOverflowAreas.UnionAllWith(tableRect);
+
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
+ nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) {
+ nsIFrame::InvalidateFrame();
+ }
+
+ FinishAndStoreOverflow(&aDesiredSize);
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+void
+nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput)
+{
+ FrameTArray* positionedParts = Properties().Get(PositionedTablePartArray());
+ if (!positionedParts) {
+ return;
+ }
+
+ OverflowChangedTracker overflowTracker;
+ overflowTracker.SetSubtreeRoot(this);
+
+ for (size_t i = 0; i < positionedParts->Length(); ++i) {
+ nsIFrame* positionedPart = positionedParts->ElementAt(i);
+
+ // As we've already finished reflow, positionedParts's size and overflow
+ // areas have already been assigned, so we just pull them back out.
+ nsSize size(positionedPart->GetSize());
+ ReflowOutput desiredSize(aReflowInput.GetWritingMode());
+ desiredSize.Width() = size.width;
+ desiredSize.Height() = size.height;
+ desiredSize.mOverflowAreas = positionedPart->GetOverflowAreasRelativeToSelf();
+
+ // Construct a dummy reflow state and reflow status.
+ // XXX(seth): Note that the dummy reflow state doesn't have a correct
+ // chain of parent reflow states. It also doesn't necessarily have a
+ // correct containing block.
+ WritingMode wm = positionedPart->GetWritingMode();
+ LogicalSize availSize(wm, size);
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput reflowInput(aPresContext, positionedPart,
+ aReflowInput.mRenderingContext, availSize,
+ ReflowInput::DUMMY_PARENT_REFLOW_STATE);
+ nsReflowStatus reflowStatus = NS_FRAME_COMPLETE;
+
+ // Reflow absolutely-positioned descendants of the positioned part.
+ // FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the bsize and
+ // ignoring any change to the reflow status aren't correct. We'll never
+ // paginate absolutely positioned frames.
+ nsFrame* positionedFrame = static_cast<nsFrame*>(positionedPart);
+ positionedFrame->FinishReflowWithAbsoluteFrames(PresContext(),
+ desiredSize,
+ reflowInput,
+ reflowStatus,
+ true);
+
+ // FinishReflowWithAbsoluteFrames has updated overflow on
+ // |positionedPart|. We need to make sure that update propagates
+ // through the intermediate frames between it and this frame.
+ nsIFrame* positionedFrameParent = positionedPart->GetParent();
+ if (positionedFrameParent != this) {
+ overflowTracker.AddFrame(positionedFrameParent,
+ OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ }
+
+ // Propagate updated overflow areas up the tree.
+ overflowTracker.Flush();
+
+ // Update our own overflow areas. (OverflowChangedTracker doesn't update the
+ // subtree root itself.)
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ nsLayoutUtils::UnionChildOverflow(this, aDesiredSize.mOverflowAreas);
+}
+
+bool
+nsTableFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
+{
+ // As above in Reflow, make sure the table overflow area includes the table
+ // rect, and check for collapsed borders leaking out.
+ if (!ShouldApplyOverflowClipping(this, StyleDisplay())) {
+ nsRect bounds(nsPoint(0, 0), GetSize());
+ WritingMode wm = GetWritingMode();
+ LogicalMargin bcMargin = GetExcludedOuterBCBorder(wm);
+ bounds.Inflate(bcMargin.GetPhysicalMargin(wm));
+
+ aOverflowAreas.UnionAllWith(bounds);
+ }
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+void
+nsTableFrame::ReflowTable(ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nscoord aAvailBSize,
+ nsIFrame*& aLastChildReflowed,
+ nsReflowStatus& aStatus)
+{
+ aLastChildReflowed = nullptr;
+
+ if (!GetPrevInFlow()) {
+ mTableLayoutStrategy->ComputeColumnISizes(aReflowInput);
+ }
+ // Constrain our reflow isize to the computed table isize (of the 1st in flow).
+ // and our reflow bsize to our avail bsize minus border, padding, cellspacing
+ WritingMode wm = aReflowInput.GetWritingMode();
+ aDesiredSize.ISize(wm) = aReflowInput.ComputedISize() +
+ aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm);
+ TableReflowInput reflowInput(aReflowInput,
+ LogicalSize(wm, aDesiredSize.ISize(wm),
+ aAvailBSize));
+ ReflowChildren(reflowInput, aStatus, aLastChildReflowed,
+ aDesiredSize.mOverflowAreas);
+
+ ReflowColGroups(aReflowInput.mRenderingContext);
+}
+
+nsIFrame*
+nsTableFrame::GetFirstBodyRowGroupFrame()
+{
+ nsIFrame* headerFrame = nullptr;
+ nsIFrame* footerFrame = nullptr;
+
+ for (nsIFrame* kidFrame : mFrames) {
+ const nsStyleDisplay* childDisplay = kidFrame->StyleDisplay();
+
+ // We expect the header and footer row group frames to be first, and we only
+ // allow one header and one footer
+ if (mozilla::StyleDisplay::TableHeaderGroup == childDisplay->mDisplay) {
+ if (headerFrame) {
+ // We already have a header frame and so this header frame is treated
+ // like an ordinary body row group frame
+ return kidFrame;
+ }
+ headerFrame = kidFrame;
+
+ } else if (mozilla::StyleDisplay::TableFooterGroup == childDisplay->mDisplay) {
+ if (footerFrame) {
+ // We already have a footer frame and so this footer frame is treated
+ // like an ordinary body row group frame
+ return kidFrame;
+ }
+ footerFrame = kidFrame;
+
+ } else if (mozilla::StyleDisplay::TableRowGroup == childDisplay->mDisplay) {
+ return kidFrame;
+ }
+ }
+
+ return nullptr;
+}
+
+// Table specific version that takes into account repeated header and footer
+// frames when continuing table frames
+void
+nsTableFrame::PushChildren(const RowGroupArray& aRowGroups,
+ int32_t aPushFrom)
+{
+ NS_PRECONDITION(aPushFrom > 0, "pushing first child");
+
+ // extract the frames from the array into a sibling list
+ nsFrameList frames;
+ uint32_t childX;
+ for (childX = aPushFrom; childX < aRowGroups.Length(); ++childX) {
+ nsTableRowGroupFrame* rgFrame = aRowGroups[childX];
+ if (!rgFrame->IsRepeatable()) {
+ mFrames.RemoveFrame(rgFrame);
+ frames.AppendFrame(nullptr, rgFrame);
+ }
+ }
+
+ if (frames.IsEmpty()) {
+ return;
+ }
+
+ nsTableFrame* nextInFlow = static_cast<nsTableFrame*>(GetNextInFlow());
+ if (nextInFlow) {
+ // Insert the frames after any repeated header and footer frames.
+ nsIFrame* firstBodyFrame = nextInFlow->GetFirstBodyRowGroupFrame();
+ nsIFrame* prevSibling = nullptr;
+ if (firstBodyFrame) {
+ prevSibling = firstBodyFrame->GetPrevSibling();
+ }
+ // When pushing and pulling frames we need to check for whether any
+ // views need to be reparented.
+ ReparentFrameViewList(frames, this, nextInFlow);
+ nextInFlow->mFrames.InsertFrames(nextInFlow, prevSibling,
+ frames);
+ }
+ else {
+ // Add the frames to our overflow list.
+ SetOverflowFrames(frames);
+ }
+}
+
+// collapsing row groups, rows, col groups and cols are accounted for after both passes of
+// reflow so that it has no effect on the calculations of reflow.
+void
+nsTableFrame::AdjustForCollapsingRowsCols(ReflowOutput& aDesiredSize,
+ const WritingMode aWM,
+ const LogicalMargin& aBorderPadding)
+{
+ nscoord bTotalOffset = 0; // total offset among all rows in all row groups
+
+ // reset the bit, it will be set again if row/rowgroup or col/colgroup are
+ // collapsed
+ SetNeedToCollapse(false);
+
+ // collapse the rows and/or row groups as necessary
+ // Get the ordered children
+ RowGroupArray rowGroups;
+ OrderRowGroups(rowGroups);
+
+ nsTableFrame* firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
+ nscoord iSize = firstInFlow->GetCollapsedISize(aWM, aBorderPadding);
+ nscoord rgISize = iSize - GetColSpacing(-1) -
+ GetColSpacing(GetColCount());
+ nsOverflowAreas overflow;
+ // Walk the list of children
+ for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[childX];
+ NS_ASSERTION(rgFrame, "Must have row group frame here");
+ bTotalOffset += rgFrame->CollapseRowGroupIfNecessary(bTotalOffset, rgISize,
+ aWM);
+ ConsiderChildOverflow(overflow, rgFrame);
+ }
+
+ aDesiredSize.BSize(aWM) -= bTotalOffset;
+ aDesiredSize.ISize(aWM) = iSize;
+ overflow.UnionAllWith(nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()));
+ FinishAndStoreOverflow(overflow,
+ nsSize(aDesiredSize.Width(), aDesiredSize.Height()));
+}
+
+
+nscoord
+nsTableFrame::GetCollapsedISize(const WritingMode aWM,
+ const LogicalMargin& aBorderPadding)
+{
+ NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedISize called on next in flow");
+ nscoord iSize = GetColSpacing(GetColCount());
+ iSize += aBorderPadding.IStartEnd(aWM);
+ nsTableFrame* fif = static_cast<nsTableFrame*>(FirstInFlow());
+ for (nsIFrame* groupFrame : mColGroups) {
+ const nsStyleVisibility* groupVis = groupFrame->StyleVisibility();
+ bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible);
+ nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame;
+ for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame;
+ colFrame = colFrame->GetNextCol()) {
+ const nsStyleDisplay* colDisplay = colFrame->StyleDisplay();
+ nscoord colIdx = colFrame->GetColIndex();
+ if (mozilla::StyleDisplay::TableColumn == colDisplay->mDisplay) {
+ const nsStyleVisibility* colVis = colFrame->StyleVisibility();
+ bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible);
+ nscoord colISize = fif->GetColumnISizeFromFirstInFlow(colIdx);
+ if (!collapseGroup && !collapseCol) {
+ iSize += colISize;
+ if (ColumnHasCellSpacingBefore(colIdx)) {
+ iSize += GetColSpacing(colIdx - 1);
+ }
+ }
+ else {
+ SetNeedToCollapse(true);
+ }
+ }
+ }
+ }
+ return iSize;
+}
+
+/* virtual */ void
+nsTableFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsContainerFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (!aOldStyleContext) //avoid this on init
+ return;
+
+ if (IsBorderCollapse() &&
+ BCRecalcNeeded(aOldStyleContext, StyleContext())) {
+ SetFullBCDamageArea();
+ }
+
+ //avoid this on init or nextinflow
+ if (!mTableLayoutStrategy || GetPrevInFlow())
+ return;
+
+ bool isAuto = IsAutoLayout();
+ if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) {
+ nsITableLayoutStrategy* temp;
+ if (isAuto)
+ temp = new BasicTableLayoutStrategy(this);
+ else
+ temp = new FixedTableLayoutStrategy(this);
+
+ if (temp) {
+ delete mTableLayoutStrategy;
+ mTableLayoutStrategy = temp;
+ }
+ }
+}
+
+
+
+void
+nsTableFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList || aListID == kColGroupList,
+ "unexpected child list");
+
+ // Because we actually have two child lists, one for col group frames and one
+ // for everything else, we need to look at each frame individually
+ // XXX The frame construction code should be separating out child frames
+ // based on the type, bug 343048.
+ while (!aFrameList.IsEmpty()) {
+ nsIFrame* f = aFrameList.FirstChild();
+ aFrameList.RemoveFrame(f);
+
+ // See what kind of frame we have
+ const nsStyleDisplay* display = f->StyleDisplay();
+
+ if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
+ if (MOZ_UNLIKELY(GetPrevInFlow())) {
+ nsFrameList colgroupFrame(f, f);
+ auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
+ firstInFlow->AppendFrames(aListID, colgroupFrame);
+ continue;
+ }
+ nsTableColGroupFrame* lastColGroup =
+ nsTableColGroupFrame::GetLastRealColGroup(this);
+ int32_t startColIndex = (lastColGroup)
+ ? lastColGroup->GetStartColumnIndex() + lastColGroup->GetColCount() : 0;
+ mColGroups.InsertFrame(this, lastColGroup, f);
+ // Insert the colgroup and its cols into the table
+ InsertColGroups(startColIndex,
+ nsFrameList::Slice(mColGroups, f, f->GetNextSibling()));
+ } else if (IsRowGroup(display->mDisplay)) {
+ DrainSelfOverflowList(); // ensure the last frame is in mFrames
+ // Append the new row group frame to the sibling chain
+ mFrames.AppendFrame(nullptr, f);
+
+ // insert the row group and its rows into the table
+ InsertRowGroups(nsFrameList::Slice(mFrames, f, nullptr));
+ } else {
+ // Nothing special to do, just add the frame to our child list
+ NS_NOTREACHED("How did we get here? Frame construction screwed up");
+ mFrames.AppendFrame(nullptr, f);
+ }
+ }
+
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("=== TableFrame::AppendFrames\n");
+ Dump(true, true, true);
+#endif
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ SetGeometryDirty();
+}
+
+// Needs to be at file scope or ArrayLength fails to compile.
+struct ChildListInsertions {
+ nsIFrame::ChildListID mID;
+ nsFrameList mList;
+};
+
+void
+nsTableFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ // The frames in aFrameList can be a mix of row group frames and col group
+ // frames. The problem is that they should go in separate child lists so
+ // we need to deal with that here...
+ // XXX The frame construction code should be separating out child frames
+ // based on the type, bug 343048.
+
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+
+ if ((aPrevFrame && !aPrevFrame->GetNextSibling()) ||
+ (!aPrevFrame && GetChildList(aListID).IsEmpty())) {
+ // Treat this like an append; still a workaround for bug 343048.
+ AppendFrames(aListID, aFrameList);
+ return;
+ }
+
+ // Collect ColGroupFrames into a separate list and insert those separately
+ // from the other frames (bug 759249).
+ ChildListInsertions insertions[2]; // ColGroup, other
+ const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay();
+ nsFrameList::FrameLinkEnumerator e(aFrameList);
+ for (; !aFrameList.IsEmpty(); e.Next()) {
+ nsIFrame* next = e.NextFrame();
+ if (!next || next->StyleDisplay()->mDisplay != display->mDisplay) {
+ nsFrameList head = aFrameList.ExtractHead(e);
+ if (display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) {
+ insertions[0].mID = kColGroupList;
+ insertions[0].mList.AppendFrames(nullptr, head);
+ } else {
+ insertions[1].mID = kPrincipalList;
+ insertions[1].mList.AppendFrames(nullptr, head);
+ }
+ if (!next) {
+ break;
+ }
+ display = next->StyleDisplay();
+ }
+ }
+ for (uint32_t i = 0; i < ArrayLength(insertions); ++i) {
+ // We pass aPrevFrame for both ColGroup and other frames since
+ // HomogenousInsertFrames will only use it if it's a suitable
+ // prev-sibling for the frames in the frame list.
+ if (!insertions[i].mList.IsEmpty()) {
+ HomogenousInsertFrames(insertions[i].mID, aPrevFrame,
+ insertions[i].mList);
+ }
+ }
+}
+
+void
+nsTableFrame::HomogenousInsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ // See what kind of frame we have
+ const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay();
+ bool isColGroup = mozilla::StyleDisplay::TableColumnGroup == display->mDisplay;
+#ifdef DEBUG
+ // Verify that either all siblings have display:table-column-group, or they
+ // all have display values different from table-column-group.
+ for (nsIFrame* frame : aFrameList) {
+ auto nextDisplay = frame->StyleDisplay()->mDisplay;
+ MOZ_ASSERT(isColGroup ==
+ (nextDisplay == mozilla::StyleDisplay::TableColumnGroup),
+ "heterogenous childlist");
+ }
+#endif
+ if (MOZ_UNLIKELY(isColGroup && GetPrevInFlow())) {
+ auto firstInFlow = static_cast<nsTableFrame*>(FirstInFlow());
+ firstInFlow->AppendFrames(aListID, aFrameList);
+ return;
+ }
+ if (aPrevFrame) {
+ const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay();
+ // Make sure they belong on the same frame list
+ if ((display->mDisplay == mozilla::StyleDisplay::TableColumnGroup) !=
+ (prevDisplay->mDisplay == mozilla::StyleDisplay::TableColumnGroup)) {
+ // the previous frame is not valid, see comment at ::AppendFrames
+ // XXXbz Using content indices here means XBL will get screwed
+ // over... Oh, well.
+ nsIFrame* pseudoFrame = aFrameList.FirstChild();
+ nsIContent* parentContent = GetContent();
+ nsIContent* content = nullptr;
+ aPrevFrame = nullptr;
+ while (pseudoFrame && (parentContent ==
+ (content = pseudoFrame->GetContent()))) {
+ pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
+ }
+ nsCOMPtr<nsIContent> container = content->GetParent();
+ if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823.
+ int32_t newIndex = container->IndexOf(content);
+ nsIFrame* kidFrame;
+ nsTableColGroupFrame* lastColGroup = nullptr;
+ if (isColGroup) {
+ kidFrame = mColGroups.FirstChild();
+ lastColGroup = nsTableColGroupFrame::GetLastRealColGroup(this);
+ }
+ else {
+ kidFrame = mFrames.FirstChild();
+ }
+ // Important: need to start at a value smaller than all valid indices
+ int32_t lastIndex = -1;
+ while (kidFrame) {
+ if (isColGroup) {
+ if (kidFrame == lastColGroup) {
+ aPrevFrame = kidFrame; // there is no real colgroup after this one
+ break;
+ }
+ }
+ pseudoFrame = kidFrame;
+ while (pseudoFrame && (parentContent ==
+ (content = pseudoFrame->GetContent()))) {
+ pseudoFrame = pseudoFrame->PrincipalChildList().FirstChild();
+ }
+ int32_t index = container->IndexOf(content);
+ if (index > lastIndex && index < newIndex) {
+ lastIndex = index;
+ aPrevFrame = kidFrame;
+ }
+ kidFrame = kidFrame->GetNextSibling();
+ }
+ }
+ }
+ }
+ if (mozilla::StyleDisplay::TableColumnGroup == display->mDisplay) {
+ NS_ASSERTION(aListID == kColGroupList, "unexpected child list");
+ // Insert the column group frames
+ const nsFrameList::Slice& newColgroups =
+ mColGroups.InsertFrames(this, aPrevFrame, aFrameList);
+ // find the starting col index for the first new col group
+ int32_t startColIndex = 0;
+ if (aPrevFrame) {
+ nsTableColGroupFrame* prevColGroup =
+ (nsTableColGroupFrame*)GetFrameAtOrBefore(this, aPrevFrame,
+ nsGkAtoms::tableColGroupFrame);
+ if (prevColGroup) {
+ startColIndex = prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount();
+ }
+ }
+ InsertColGroups(startColIndex, newColgroups);
+ } else if (IsRowGroup(display->mDisplay)) {
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+ DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
+ // Insert the frames in the sibling chain
+ const nsFrameList::Slice& newRowGroups =
+ mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
+
+ InsertRowGroups(newRowGroups);
+ } else {
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+ NS_NOTREACHED("How did we even get here?");
+ // Just insert the frame and don't worry about reflowing it
+ mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
+ return;
+ }
+
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ SetGeometryDirty();
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("=== TableFrame::InsertFrames\n");
+ Dump(true, true, true);
+#endif
+ return;
+}
+
+void
+nsTableFrame::DoRemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ if (aListID == kColGroupList) {
+ nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling();
+ nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame;
+ int32_t firstColIndex = colGroup->GetStartColumnIndex();
+ int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1;
+ mColGroups.DestroyFrame(aOldFrame);
+ nsTableColGroupFrame::ResetColIndices(nextColGroupFrame, firstColIndex);
+ // remove the cols from the table
+ int32_t colIdx;
+ for (colIdx = lastColIndex; colIdx >= firstColIndex; colIdx--) {
+ nsTableColFrame* colFrame = mColFrames.SafeElementAt(colIdx);
+ if (colFrame) {
+ RemoveCol(colGroup, colIdx, true, false);
+ }
+ }
+
+ // If we have some anonymous cols at the end already, we just
+ // add more of them.
+ if (!mColFrames.IsEmpty() &&
+ mColFrames.LastElement() && // XXXbz is this ever null?
+ mColFrames.LastElement()->GetColType() == eColAnonymousCell) {
+ int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length();
+ if (numAnonymousColsToAdd > 0) {
+ // this sets the child list, updates the col cache and cell map
+ AppendAnonymousColFrames(numAnonymousColsToAdd);
+ }
+ } else {
+ // All of our colframes correspond to actual <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 == kPrincipalList, "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(aOldFrame);
+
+ // the removal of a row group changes the cellmap, the columns might change
+ if (cellMap) {
+ cellMap->Synchronize(this);
+ // Create an empty slice
+ ResetRowIndices(nsFrameList::Slice(mFrames, nullptr, nullptr));
+ TableArea damageArea;
+ cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false, damageArea);
+
+ static_cast<nsTableFrame*>(FirstInFlow())->MatchCellMapToColCache(cellMap);
+ }
+ }
+}
+
+void
+nsTableFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ NS_ASSERTION(aListID == kColGroupList ||
+ mozilla::StyleDisplay::TableColumnGroup !=
+ aOldFrame->StyleDisplay()->mDisplay,
+ "Wrong list name; use kColGroupList iff colgroup");
+ nsIPresShell* shell = PresContext()->PresShell();
+ nsTableFrame* lastParent = nullptr;
+ while (aOldFrame) {
+ nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation();
+ nsTableFrame* parent = static_cast<nsTableFrame*>(aOldFrame->GetParent());
+ if (parent != lastParent) {
+ parent->DrainSelfOverflowList();
+ }
+ parent->DoRemoveFrame(aListID, aOldFrame);
+ aOldFrame = oldFrameNextContinuation;
+ if (parent != lastParent) {
+ // for now, just bail and recalc all of the collapsing borders
+ // as the cellmap changes we need to recalc
+ if (parent->IsBorderCollapse()) {
+ parent->SetFullBCDamageArea();
+ }
+ parent->SetGeometryDirty();
+ shell->FrameNeedsReflow(parent, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ lastParent = parent;
+ }
+ }
+#ifdef DEBUG_TABLE_CELLMAP
+ printf("=== TableFrame::RemoveFrame\n");
+ Dump(true, true, true);
+#endif
+}
+
+/* virtual */ nsMargin
+nsTableFrame::GetUsedBorder() const
+{
+ if (!IsBorderCollapse())
+ return nsContainerFrame::GetUsedBorder();
+
+ WritingMode wm = GetWritingMode();
+ return GetIncludedOuterBCBorder(wm).GetPhysicalMargin(wm);
+}
+
+/* virtual */ nsMargin
+nsTableFrame::GetUsedPadding() const
+{
+ if (!IsBorderCollapse())
+ return nsContainerFrame::GetUsedPadding();
+
+ return nsMargin(0,0,0,0);
+}
+
+/* virtual */ nsMargin
+nsTableFrame::GetUsedMargin() const
+{
+ // The margin is inherited to the table wrapper frame via
+ // the ::-moz-table-wrapper rule in ua.css.
+ return nsMargin(0, 0, 0, 0);
+}
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(TableBCProperty, BCPropertyData)
+
+BCPropertyData*
+nsTableFrame::GetBCProperty(bool aCreateIfNecessary) const
+{
+ FrameProperties props = Properties();
+ BCPropertyData* value = props.Get(TableBCProperty());
+ if (!value && aCreateIfNecessary) {
+ value = new BCPropertyData();
+ props.Set(TableBCProperty(), value);
+ }
+
+ return value;
+}
+
+static void
+DivideBCBorderSize(BCPixelSize aPixelSize,
+ BCPixelSize& aSmallHalf,
+ BCPixelSize& aLargeHalf)
+{
+ aSmallHalf = aPixelSize / 2;
+ aLargeHalf = aPixelSize - aSmallHalf;
+}
+
+LogicalMargin
+nsTableFrame::GetOuterBCBorder(const WritingMode aWM) const
+{
+ if (NeedToCalcBCBorders()) {
+ const_cast<nsTableFrame*>(this)->CalcBCBorders();
+ }
+
+ int32_t p2t = nsPresContext::AppUnitsPerCSSPixel();
+ BCPropertyData* propData = GetBCProperty();
+ if (propData) {
+ return LogicalMargin(aWM,
+ BC_BORDER_START_HALF_COORD(p2t, propData->mBStartBorderWidth),
+ BC_BORDER_END_HALF_COORD(p2t, propData->mIEndBorderWidth),
+ BC_BORDER_END_HALF_COORD(p2t, propData->mBEndBorderWidth),
+ BC_BORDER_START_HALF_COORD(p2t, propData->mIStartBorderWidth));
+ }
+ return LogicalMargin(aWM);
+}
+
+LogicalMargin
+nsTableFrame::GetIncludedOuterBCBorder(const WritingMode aWM) const
+{
+ if (NeedToCalcBCBorders()) {
+ const_cast<nsTableFrame*>(this)->CalcBCBorders();
+ }
+
+ int32_t p2t = nsPresContext::AppUnitsPerCSSPixel();
+ BCPropertyData* propData = GetBCProperty();
+ if (propData) {
+ return LogicalMargin(aWM,
+ BC_BORDER_START_HALF_COORD(p2t, propData->mBStartBorderWidth),
+ BC_BORDER_END_HALF_COORD(p2t, propData->mIEndCellBorderWidth),
+ BC_BORDER_END_HALF_COORD(p2t, propData->mBEndBorderWidth),
+ BC_BORDER_START_HALF_COORD(p2t, propData->mIStartCellBorderWidth));
+ }
+ return LogicalMargin(aWM);
+}
+
+LogicalMargin
+nsTableFrame::GetExcludedOuterBCBorder(const WritingMode aWM) const
+{
+ return GetOuterBCBorder(aWM) - GetIncludedOuterBCBorder(aWM);
+}
+
+static LogicalMargin
+GetSeparateModelBorderPadding(const WritingMode aWM,
+ const ReflowInput* aReflowInput,
+ nsStyleContext* aStyleContext)
+{
+ // XXXbz Either we _do_ have a reflow state and then we can use its
+ // mComputedBorderPadding or we don't and then we get the padding
+ // wrong!
+ const nsStyleBorder* border = aStyleContext->StyleBorder();
+ LogicalMargin borderPadding(aWM, border->GetComputedBorder());
+ if (aReflowInput) {
+ borderPadding += aReflowInput->ComputedLogicalPadding();
+ }
+ return borderPadding;
+}
+
+LogicalMargin
+nsTableFrame::GetChildAreaOffset(const WritingMode aWM,
+ const ReflowInput* aReflowInput) const
+{
+ return IsBorderCollapse() ? GetIncludedOuterBCBorder(aWM) :
+ GetSeparateModelBorderPadding(aWM, aReflowInput, mStyleContext);
+}
+
+void
+nsTableFrame::InitChildReflowInput(ReflowInput& aReflowInput)
+{
+ nsMargin collapseBorder;
+ nsMargin padding(0,0,0,0);
+ nsMargin* pCollapseBorder = nullptr;
+ nsPresContext* presContext = PresContext();
+ if (IsBorderCollapse()) {
+ nsTableRowGroupFrame* rgFrame =
+ static_cast<nsTableRowGroupFrame*>(aReflowInput.mFrame);
+ WritingMode wm = GetWritingMode();
+ LogicalMargin border = rgFrame->GetBCBorderWidth(wm);
+ collapseBorder = border.GetPhysicalMargin(wm);
+ pCollapseBorder = &collapseBorder;
+ }
+ aReflowInput.Init(presContext, nullptr, pCollapseBorder, &padding);
+
+ NS_ASSERTION(!mBits.mResizedColumns ||
+ !aReflowInput.mParentReflowInput->mFlags.mSpecialBSizeReflow,
+ "should not resize columns on special bsize reflow");
+ if (mBits.mResizedColumns) {
+ aReflowInput.SetIResize(true);
+ }
+}
+
+// Position and size aKidFrame and update our reflow state. The origin of
+// aKidRect is relative to the upper-left origin of our frame
+void
+nsTableFrame::PlaceChild(TableReflowInput& aReflowInput,
+ nsIFrame* aKidFrame,
+ nsPoint aKidPosition,
+ ReflowOutput& aKidDesiredSize,
+ const nsRect& aOriginalKidRect,
+ const nsRect& aOriginalKidVisualOverflow)
+{
+ WritingMode wm = aReflowInput.reflowInput.GetWritingMode();
+ bool isFirstReflow =
+ aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+
+ // Place and size the child
+ FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, nullptr,
+ aKidPosition.x, aKidPosition.y, 0);
+
+ InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow,
+ isFirstReflow);
+
+ // Adjust the running block-offset
+ aReflowInput.bCoord += aKidDesiredSize.BSize(wm);
+
+ // If our bsize is constrained, then update the available bsize
+ if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) {
+ aReflowInput.availSize.BSize(wm) -= aKidDesiredSize.BSize(wm);
+ }
+}
+
+void
+nsTableFrame::OrderRowGroups(RowGroupArray& aChildren,
+ nsTableRowGroupFrame** aHead,
+ nsTableRowGroupFrame** aFoot) const
+{
+ aChildren.Clear();
+ nsTableRowGroupFrame* head = nullptr;
+ nsTableRowGroupFrame* foot = nullptr;
+
+ nsIFrame* kidFrame = mFrames.FirstChild();
+ while (kidFrame) {
+ const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay();
+ nsTableRowGroupFrame* rowGroup =
+ static_cast<nsTableRowGroupFrame*>(kidFrame);
+
+ switch (kidDisplay->mDisplay) {
+ case mozilla::StyleDisplay::TableHeaderGroup:
+ if (head) { // treat additional thead like tbody
+ aChildren.AppendElement(rowGroup);
+ }
+ else {
+ head = rowGroup;
+ }
+ break;
+ case mozilla::StyleDisplay::TableFooterGroup:
+ if (foot) { // treat additional tfoot like tbody
+ aChildren.AppendElement(rowGroup);
+ }
+ else {
+ foot = rowGroup;
+ }
+ break;
+ case mozilla::StyleDisplay::TableRowGroup:
+ aChildren.AppendElement(rowGroup);
+ break;
+ default:
+ NS_NOTREACHED("How did this produce an nsTableRowGroupFrame?");
+ // Just ignore it
+ break;
+ }
+ // Get the next sibling but skip it if it's also the next-in-flow, since
+ // a next-in-flow will not be part of the current table.
+ while (kidFrame) {
+ nsIFrame* nif = kidFrame->GetNextInFlow();
+ kidFrame = kidFrame->GetNextSibling();
+ if (kidFrame != nif)
+ break;
+ }
+ }
+
+ // put the thead first
+ if (head) {
+ aChildren.InsertElementAt(0, head);
+ }
+ if (aHead)
+ *aHead = head;
+ // put the tfoot after the last tbody
+ if (foot) {
+ aChildren.AppendElement(foot);
+ }
+ if (aFoot)
+ *aFoot = foot;
+}
+
+nsTableRowGroupFrame*
+nsTableFrame::GetTHead() const
+{
+ nsIFrame* kidFrame = mFrames.FirstChild();
+ while (kidFrame) {
+ if (kidFrame->StyleDisplay()->mDisplay ==
+ mozilla::StyleDisplay::TableHeaderGroup) {
+ return static_cast<nsTableRowGroupFrame*>(kidFrame);
+ }
+
+ // Get the next sibling but skip it if it's also the next-in-flow, since
+ // a next-in-flow will not be part of the current table.
+ while (kidFrame) {
+ nsIFrame* nif = kidFrame->GetNextInFlow();
+ kidFrame = kidFrame->GetNextSibling();
+ if (kidFrame != nif)
+ break;
+ }
+ }
+
+ return nullptr;
+}
+
+nsTableRowGroupFrame*
+nsTableFrame::GetTFoot() const
+{
+ nsIFrame* kidFrame = mFrames.FirstChild();
+ while (kidFrame) {
+ if (kidFrame->StyleDisplay()->mDisplay ==
+ mozilla::StyleDisplay::TableFooterGroup) {
+ return static_cast<nsTableRowGroupFrame*>(kidFrame);
+ }
+
+ // Get the next sibling but skip it if it's also the next-in-flow, since
+ // a next-in-flow will not be part of the current table.
+ while (kidFrame) {
+ nsIFrame* nif = kidFrame->GetNextInFlow();
+ kidFrame = kidFrame->GetNextSibling();
+ if (kidFrame != nif)
+ break;
+ }
+ }
+
+ return nullptr;
+}
+
+static bool
+IsRepeatable(nscoord aFrameHeight, nscoord aPageHeight)
+{
+ return aFrameHeight < (aPageHeight / 4);
+}
+
+nsresult
+nsTableFrame::SetupHeaderFooterChild(const TableReflowInput& aReflowInput,
+ nsTableRowGroupFrame* aFrame,
+ nscoord* aDesiredHeight)
+{
+ nsPresContext* presContext = PresContext();
+ nscoord pageHeight = presContext->GetPageSize().height;
+
+ // Reflow the child with unconstrained height
+ WritingMode wm = aFrame->GetWritingMode();
+ LogicalSize availSize = aReflowInput.reflowInput.AvailableSize(wm);
+
+ nsSize containerSize = availSize.GetPhysicalSize(wm);
+ // XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE
+
+ availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput kidReflowInput(presContext, aReflowInput.reflowInput,
+ aFrame, availSize, nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(kidReflowInput);
+ kidReflowInput.mFlags.mIsTopOfPage = true;
+ ReflowOutput desiredSize(aReflowInput.reflowInput);
+ desiredSize.ClearSize();
+ nsReflowStatus status;
+ ReflowChild(aFrame, presContext, desiredSize, kidReflowInput,
+ wm, LogicalPoint(wm, aReflowInput.iCoord, aReflowInput.bCoord),
+ containerSize, 0, status);
+ // The child will be reflowed again "for real" so no need to place it now
+
+ aFrame->SetRepeatable(IsRepeatable(desiredSize.Height(), pageHeight));
+ *aDesiredHeight = desiredSize.Height();
+ return NS_OK;
+}
+
+void
+nsTableFrame::PlaceRepeatedFooter(TableReflowInput& aReflowInput,
+ nsTableRowGroupFrame *aTfoot,
+ nscoord aFooterHeight)
+{
+ nsPresContext* presContext = PresContext();
+ WritingMode wm = aTfoot->GetWritingMode();
+ LogicalSize kidAvailSize = aReflowInput.availSize;
+
+ nsSize containerSize = kidAvailSize.GetPhysicalSize(wm);
+ // XXX check for containerSize.* == NS_UNCONSTRAINEDSIZE
+
+ kidAvailSize.BSize(wm) = aFooterHeight;
+ ReflowInput footerReflowInput(presContext,
+ aReflowInput.reflowInput,
+ aTfoot, kidAvailSize,
+ nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(footerReflowInput);
+ aReflowInput.bCoord += GetRowSpacing(GetRowCount());
+
+ nsRect origTfootRect = aTfoot->GetRect();
+ nsRect origTfootVisualOverflow = aTfoot->GetVisualOverflowRect();
+
+ nsReflowStatus footerStatus;
+ ReflowOutput desiredSize(aReflowInput.reflowInput);
+ desiredSize.ClearSize();
+ LogicalPoint kidPosition(wm, aReflowInput.iCoord, aReflowInput.bCoord);
+ ReflowChild(aTfoot, presContext, desiredSize, footerReflowInput,
+ wm, kidPosition, containerSize, 0, footerStatus);
+ footerReflowInput.ApplyRelativePositioning(&kidPosition, containerSize);
+
+ PlaceChild(aReflowInput, aTfoot,
+ // We subtract desiredSize.PhysicalSize() from containerSize here
+ // to account for the fact that in RTL modes, the origin is
+ // on the right-hand side so we're not simply converting a
+ // point, we're also swapping the child's origin side.
+ kidPosition.GetPhysicalPoint(wm, containerSize -
+ desiredSize.PhysicalSize()),
+ desiredSize, origTfootRect, origTfootVisualOverflow);
+}
+
+// Reflow the children based on the avail size and reason in aReflowInput
+void
+nsTableFrame::ReflowChildren(TableReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ nsIFrame*& aLastChildReflowed,
+ nsOverflowAreas& aOverflowAreas)
+{
+ aStatus = NS_FRAME_COMPLETE;
+ aLastChildReflowed = nullptr;
+
+ nsIFrame* prevKidFrame = nullptr;
+ WritingMode wm = aReflowInput.reflowInput.GetWritingMode();
+ NS_WARNING_ASSERTION(
+ wm.IsVertical() ||
+ NS_UNCONSTRAINEDSIZE != aReflowInput.reflowInput.ComputedWidth(),
+ "shouldn't have unconstrained width in horizontal mode");
+ nsSize containerSize =
+ aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained();
+
+ nsPresContext* presContext = PresContext();
+ // XXXldb Should we be checking constrained height instead?
+ // tables are not able to pull back children from its next inflow, so even
+ // under paginated contexts tables are should not paginate if they are inside
+ // column set
+ bool isPaginated = presContext->IsPaginated() &&
+ NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm) &&
+ aReflowInput.reflowInput.mFlags.mTableIsSplittable;
+
+ aOverflowAreas.Clear();
+
+ bool reflowAllKids = aReflowInput.reflowInput.ShouldReflowAllKids() ||
+ mBits.mResizedColumns ||
+ IsGeometryDirty();
+
+ RowGroupArray rowGroups;
+ nsTableRowGroupFrame *thead, *tfoot;
+ OrderRowGroups(rowGroups, &thead, &tfoot);
+ bool pageBreak = false;
+ nscoord footerHeight = 0;
+
+ // Determine the repeatablility of headers and footers, and also the desired
+ // height of any repeatable footer.
+ // The repeatability of headers on continued tables is handled
+ // when they are created in nsCSSFrameConstructor::CreateContinuingTableFrame.
+ // We handle the repeatability of footers again here because we need to
+ // determine the footer's height anyway. We could perhaps optimize by
+ // using the footer's prev-in-flow's height instead of reflowing it again,
+ // but there's no real need.
+ if (isPaginated) {
+ if (thead && !GetPrevInFlow()) {
+ nscoord desiredHeight;
+ nsresult rv = SetupHeaderFooterChild(aReflowInput, thead, &desiredHeight);
+ if (NS_FAILED(rv))
+ return;
+ }
+ if (tfoot) {
+ nsresult rv = SetupHeaderFooterChild(aReflowInput, tfoot, &footerHeight);
+ if (NS_FAILED(rv))
+ return;
+ }
+ }
+ // if the child is a tbody in paginated mode reduce the height by a repeated footer
+ bool allowRepeatedFooter = false;
+ for (size_t childX = 0; childX < rowGroups.Length(); childX++) {
+ nsIFrame* kidFrame = rowGroups[childX];
+ nsTableRowGroupFrame* rowGroupFrame = rowGroups[childX];
+ nscoord cellSpacingB = GetRowSpacing(rowGroupFrame->GetStartRowIndex()+
+ rowGroupFrame->GetRowCount());
+ // Get the frame state bits
+ // See if we should only reflow the dirty child frames
+ if (reflowAllKids ||
+ NS_SUBTREE_DIRTY(kidFrame) ||
+ (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow &&
+ (isPaginated || kidFrame->HasAnyStateBits(
+ NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) {
+ if (pageBreak) {
+ if (allowRepeatedFooter) {
+ PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
+ }
+ else if (tfoot && tfoot->IsRepeatable()) {
+ tfoot->SetRepeatable(false);
+ }
+ PushChildren(rowGroups, childX);
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ break;
+ }
+
+ LogicalSize kidAvailSize(aReflowInput.availSize);
+ allowRepeatedFooter = false;
+ if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.BSize(wm))) {
+ nsTableRowGroupFrame* kidRG =
+ static_cast<nsTableRowGroupFrame*>(kidFrame);
+ if (kidRG != thead && kidRG != tfoot && tfoot && tfoot->IsRepeatable()) {
+ // the child is a tbody and there is a repeatable footer
+ NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1], "Missing footer!");
+ if (footerHeight + cellSpacingB < kidAvailSize.BSize(wm)) {
+ allowRepeatedFooter = true;
+ kidAvailSize.BSize(wm) -= footerHeight + cellSpacingB;
+ }
+ }
+ }
+
+ nsRect oldKidRect = kidFrame->GetRect();
+ nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect();
+
+ ReflowOutput desiredSize(aReflowInput.reflowInput);
+ desiredSize.ClearSize();
+
+ // Reflow the child into the available space
+ ReflowInput kidReflowInput(presContext, aReflowInput.reflowInput,
+ kidFrame,
+ kidAvailSize,
+ nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(kidReflowInput);
+
+ // If this isn't the first row group, and the previous row group has a
+ // nonzero YMost, then we can't be at the top of the page.
+ // We ignore a repeated head row group in this check to avoid causing
+ // infinite loops in some circumstances - see bug 344883.
+ if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) &&
+ (rowGroups[childX - 1]->GetNormalRect().YMost() > 0)) {
+ kidReflowInput.mFlags.mIsTopOfPage = false;
+ }
+ aReflowInput.bCoord += cellSpacingB;
+ if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) {
+ aReflowInput.availSize.BSize(wm) -= cellSpacingB;
+ }
+ // record the presence of a next in flow, it might get destroyed so we
+ // need to reorder the row group array
+ bool reorder = false;
+ if (kidFrame->GetNextInFlow())
+ reorder = true;
+
+ LogicalPoint kidPosition(wm, aReflowInput.iCoord, aReflowInput.bCoord);
+ ReflowChild(kidFrame, presContext, desiredSize, kidReflowInput,
+ wm, kidPosition, containerSize, 0, aStatus);
+ kidReflowInput.ApplyRelativePositioning(&kidPosition, containerSize);
+
+ if (reorder) {
+ // reorder row groups the reflow may have changed the nextinflows
+ OrderRowGroups(rowGroups, &thead, &tfoot);
+ childX = rowGroups.IndexOf(kidFrame);
+ if (childX == RowGroupArray::NoIndex) {
+ // XXXbz can this happen?
+ childX = rowGroups.Length();
+ }
+ }
+ if (isPaginated && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) &&
+ ShouldAvoidBreakInside(aReflowInput.reflowInput)) {
+ aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+ break;
+ }
+ // see if the rowgroup did not fit on this page might be pushed on
+ // the next page
+ if (isPaginated &&
+ (NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
+ (NS_FRAME_IS_COMPLETE(aStatus) &&
+ (NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight()) &&
+ kidReflowInput.AvailableHeight() < desiredSize.Height()))) {
+ if (ShouldAvoidBreakInside(aReflowInput.reflowInput)) {
+ aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+ break;
+ }
+ // if we are on top of the page place with dataloss
+ if (kidReflowInput.mFlags.mIsTopOfPage) {
+ if (childX+1 < rowGroups.Length()) {
+ nsIFrame* nextRowGroupFrame = rowGroups[childX + 1];
+ if (nextRowGroupFrame) {
+ PlaceChild(aReflowInput, kidFrame,
+ kidPosition.GetPhysicalPoint(wm,
+ containerSize - desiredSize.PhysicalSize()),
+ desiredSize, oldKidRect, oldKidVisualOverflow);
+ if (allowRepeatedFooter) {
+ PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
+ }
+ else if (tfoot && tfoot->IsRepeatable()) {
+ tfoot->SetRepeatable(false);
+ }
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ PushChildren(rowGroups, childX + 1);
+ aLastChildReflowed = kidFrame;
+ break;
+ }
+ }
+ }
+ else { // we are not on top, push this rowgroup onto the next page
+ if (prevKidFrame) { // we had a rowgroup before so push this
+ if (allowRepeatedFooter) {
+ PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
+ }
+ else if (tfoot && tfoot->IsRepeatable()) {
+ tfoot->SetRepeatable(false);
+ }
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ PushChildren(rowGroups, childX);
+ aLastChildReflowed = prevKidFrame;
+ break;
+ }
+ else { // we can't push so lets make clear how much space we need
+ PlaceChild(aReflowInput, kidFrame,
+ kidPosition.GetPhysicalPoint(wm,
+ containerSize - desiredSize.PhysicalSize()),
+ desiredSize, oldKidRect, oldKidVisualOverflow);
+ aLastChildReflowed = kidFrame;
+ if (allowRepeatedFooter) {
+ PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
+ aLastChildReflowed = tfoot;
+ }
+ break;
+ }
+ }
+ }
+
+ aLastChildReflowed = kidFrame;
+
+ pageBreak = false;
+ // see if there is a page break after this row group or before the next one
+ if (NS_FRAME_IS_COMPLETE(aStatus) && isPaginated &&
+ (NS_UNCONSTRAINEDSIZE != kidReflowInput.AvailableHeight())) {
+ nsIFrame* nextKid =
+ (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr;
+ pageBreak = PageBreakAfter(kidFrame, nextKid);
+ }
+
+ // Place the child
+ PlaceChild(aReflowInput, kidFrame,
+ kidPosition.GetPhysicalPoint(wm, containerSize -
+ desiredSize.PhysicalSize()),
+ desiredSize, oldKidRect, oldKidVisualOverflow);
+
+ // Remember where we just were in case we end up pushing children
+ prevKidFrame = kidFrame;
+
+ MOZ_ASSERT(!NS_FRAME_IS_NOT_COMPLETE(aStatus) || isPaginated,
+ "Table contents should only fragment in paginated contexts");
+
+ // Special handling for incomplete children
+ if (isPaginated && NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
+ nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow();
+ if (!kidNextInFlow) {
+ // The child doesn't have a next-in-flow so create a continuing
+ // frame. This hooks the child into the flow
+ kidNextInFlow = presContext->PresShell()->FrameConstructor()->
+ CreateContinuingFrame(presContext, kidFrame, this);
+
+ // Insert the kid's new next-in-flow into our sibling list...
+ mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow);
+ // and in rowGroups after childX so that it will get pushed below.
+ rowGroups.InsertElementAt(childX + 1,
+ static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
+ } else if (kidNextInFlow == kidFrame->GetNextSibling()) {
+ // OrderRowGroups excludes NIFs in the child list from 'rowGroups'
+ // so we deal with that here to make sure they get pushed.
+ MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow),
+ "OrderRowGroups must not put our NIF in 'rowGroups'");
+ rowGroups.InsertElementAt(childX + 1,
+ static_cast<nsTableRowGroupFrame*>(kidNextInFlow));
+ }
+
+ // We've used up all of our available space so push the remaining
+ // children.
+ if (allowRepeatedFooter) {
+ PlaceRepeatedFooter(aReflowInput, tfoot, footerHeight);
+ }
+ else if (tfoot && tfoot->IsRepeatable()) {
+ tfoot->SetRepeatable(false);
+ }
+
+ nsIFrame* nextSibling = kidFrame->GetNextSibling();
+ if (nextSibling) {
+ PushChildren(rowGroups, childX + 1);
+ }
+ break;
+ }
+ }
+ else { // it isn't being reflowed
+ aReflowInput.bCoord += cellSpacingB;
+ LogicalRect kidRect(wm, kidFrame->GetNormalRect(), containerSize);
+ if (kidRect.BStart(wm) != aReflowInput.bCoord) {
+ // invalidate the old position
+ kidFrame->InvalidateFrameSubtree();
+ // move to the new position
+ kidFrame->MovePositionBy(wm, LogicalPoint(wm, 0, aReflowInput.bCoord -
+ kidRect.BStart(wm)));
+ RePositionViews(kidFrame);
+ // invalidate the new position
+ kidFrame->InvalidateFrameSubtree();
+ }
+ aReflowInput.bCoord += kidRect.BSize(wm);
+
+ // If our bsize is constrained then update the available bsize.
+ if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) {
+ aReflowInput.availSize.BSize(wm) -= cellSpacingB + kidRect.BSize(wm);
+ }
+ }
+ }
+
+ // We've now propagated the column resizes and geometry changes to all
+ // the children.
+ mBits.mResizedColumns = false;
+ ClearGeometryDirty();
+}
+
+void
+nsTableFrame::ReflowColGroups(nsRenderingContext *aRenderingContext)
+{
+ if (!GetPrevInFlow() && !HaveReflowedColGroups()) {
+ ReflowOutput kidMet(GetWritingMode());
+ nsPresContext *presContext = PresContext();
+ for (nsIFrame* kidFrame : mColGroups) {
+ if (NS_SUBTREE_DIRTY(kidFrame)) {
+ // The column groups don't care about dimensions or reflow states.
+ ReflowInput
+ kidReflowInput(presContext, kidFrame, aRenderingContext,
+ LogicalSize(kidFrame->GetWritingMode()));
+ nsReflowStatus cgStatus;
+ ReflowChild(kidFrame, presContext, kidMet, kidReflowInput, 0, 0, 0,
+ cgStatus);
+ FinishReflowChild(kidFrame, presContext, kidMet, nullptr, 0, 0, 0);
+ }
+ }
+ SetHaveReflowedColGroups(true);
+ }
+}
+
+void
+nsTableFrame::CalcDesiredBSize(const ReflowInput& aReflowInput,
+ ReflowOutput& aDesiredSize)
+{
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nsTableCellMap* cellMap = GetCellMap();
+ if (!cellMap) {
+ NS_ERROR("never ever call me until the cell map is built!");
+ aDesiredSize.BSize(wm) = 0;
+ return;
+ }
+ LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
+
+ // get the natural bsize based on the last child's (row group) rect
+ RowGroupArray rowGroups;
+ OrderRowGroups(rowGroups);
+ if (rowGroups.IsEmpty()) {
+ // tables can be used as rectangular items without content
+ nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput);
+ if ((NS_UNCONSTRAINEDSIZE != tableSpecifiedBSize) &&
+ (tableSpecifiedBSize > 0) &&
+ eCompatibility_NavQuirks != PresContext()->CompatibilityMode()) {
+ // empty tables should not have a size in quirks mode
+ aDesiredSize.BSize(wm) = tableSpecifiedBSize;
+ } else {
+ aDesiredSize.BSize(wm) = 0;
+ }
+ return;
+ }
+ int32_t rowCount = cellMap->GetRowCount();
+ int32_t colCount = cellMap->GetColCount();
+ nscoord desiredBSize = borderPadding.BStartEnd(wm);
+ if (rowCount > 0 && colCount > 0) {
+ desiredBSize += GetRowSpacing(-1);
+ for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ desiredBSize += rowGroups[rgIdx]->BSize(wm) +
+ GetRowSpacing(rowGroups[rgIdx]->GetRowCount() +
+ rowGroups[rgIdx]->GetStartRowIndex());
+ }
+ }
+
+ // see if a specified table bsize requires dividing additional space to rows
+ if (!GetPrevInFlow()) {
+ nscoord tableSpecifiedBSize = CalcBorderBoxBSize(aReflowInput);
+ if ((tableSpecifiedBSize > 0) &&
+ (tableSpecifiedBSize != NS_UNCONSTRAINEDSIZE) &&
+ (tableSpecifiedBSize > desiredBSize)) {
+ // proportionately distribute the excess bsize to unconstrained rows in each
+ // unconstrained row group.
+ DistributeBSizeToRows(aReflowInput, tableSpecifiedBSize - desiredBSize);
+ // this might have changed the overflow area incorporate the childframe overflow area.
+ for (nsIFrame* kidFrame : mFrames) {
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
+ }
+ desiredBSize = tableSpecifiedBSize;
+ }
+ }
+ aDesiredSize.BSize(wm) = desiredBSize;
+}
+
+static
+void ResizeCells(nsTableFrame& aTableFrame)
+{
+ nsTableFrame::RowGroupArray rowGroups;
+ aTableFrame.OrderRowGroups(rowGroups);
+ WritingMode wm = aTableFrame.GetWritingMode();
+ ReflowOutput tableDesiredSize(wm);
+ tableDesiredSize.SetSize(wm, aTableFrame.GetLogicalSize(wm));
+ tableDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+
+ ReflowOutput groupDesiredSize(wm);
+ groupDesiredSize.SetSize(wm, rgFrame->GetLogicalSize(wm));
+ groupDesiredSize.SetOverflowAreasToDesiredBounds();
+
+ nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
+ while (rowFrame) {
+ rowFrame->DidResize();
+ rgFrame->ConsiderChildOverflow(groupDesiredSize.mOverflowAreas, rowFrame);
+ rowFrame = rowFrame->GetNextRow();
+ }
+ rgFrame->FinishAndStoreOverflow(&groupDesiredSize);
+ tableDesiredSize.mOverflowAreas.UnionWith(groupDesiredSize.mOverflowAreas +
+ rgFrame->GetPosition());
+ }
+ aTableFrame.FinishAndStoreOverflow(&tableDesiredSize);
+}
+
+void
+nsTableFrame::DistributeBSizeToRows(const ReflowInput& aReflowInput,
+ nscoord aAmount)
+{
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalMargin borderPadding = GetChildAreaOffset(wm, &aReflowInput);
+
+ nsSize containerSize =
+ aReflowInput.ComputedSizeAsContainerIfConstrained();
+
+ RowGroupArray rowGroups;
+ OrderRowGroups(rowGroups);
+
+ nscoord amountUsed = 0;
+ // distribute space to each pct bsize row whose row group doesn't have a computed
+ // bsize, and base the pct on the table bsize. If the row group had a computed
+ // bsize, then this was already done in nsTableRowGroupFrame::CalculateRowBSizes
+ nscoord pctBasis = aReflowInput.ComputedBSize() - GetRowSpacing(-1, GetRowCount());
+ nscoord bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(0);
+ nscoord bEndRG = bOriginRG;
+ uint32_t rgIdx;
+ for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+ nscoord amountUsedByRG = 0;
+ nscoord bOriginRow = 0;
+ LogicalRect rgNormalRect(wm, rgFrame->GetNormalRect(), containerSize);
+ if (!rgFrame->HasStyleBSize()) {
+ nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
+ while (rowFrame) {
+ // We don't know the final width of the rowGroupFrame yet, so use 0,0
+ // as a dummy containerSize here; we'll adjust the row positions at
+ // the end, after the rowGroup size is finalized.
+ const nsSize dummyContainerSize;
+ LogicalRect rowNormalRect(wm, rowFrame->GetNormalRect(),
+ dummyContainerSize);
+ nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex());
+ if ((amountUsed < aAmount) && rowFrame->HasPctBSize()) {
+ nscoord pctBSize = rowFrame->GetInitialBSize(pctBasis);
+ nscoord amountForRow = std::min(aAmount - amountUsed,
+ pctBSize - rowNormalRect.BSize(wm));
+ if (amountForRow > 0) {
+ // XXXbz we don't need to move the row's b-position to bOriginRow?
+ nsRect origRowRect = rowFrame->GetRect();
+ nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
+ rowFrame->SetSize(wm, LogicalSize(wm, rowNormalRect.ISize(wm),
+ newRowBSize));
+ bOriginRow += newRowBSize + cellSpacingB;
+ bEndRG += newRowBSize + cellSpacingB;
+ amountUsed += amountForRow;
+ amountUsedByRG += amountForRow;
+ //rowFrame->DidResize();
+ nsTableFrame::RePositionViews(rowFrame);
+
+ rgFrame->InvalidateFrameWithRect(origRowRect);
+ rgFrame->InvalidateFrame();
+ }
+ }
+ else {
+ if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm) &&
+ !HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ rowFrame->InvalidateFrameSubtree();
+ rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow -
+ rowNormalRect.BStart(wm)));
+ nsTableFrame::RePositionViews(rowFrame);
+ rowFrame->InvalidateFrameSubtree();
+ }
+ bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB;
+ bEndRG += rowNormalRect.BSize(wm) + cellSpacingB;
+ }
+ rowFrame = rowFrame->GetNextRow();
+ }
+ if (amountUsed > 0) {
+ if (rgNormalRect.BStart(wm) != bOriginRG) {
+ rgFrame->InvalidateFrameSubtree();
+ }
+
+ nsRect origRgNormalRect = rgFrame->GetRect();
+ nsRect origRgVisualOverflow = rgFrame->GetVisualOverflowRect();
+
+ rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
+ rgNormalRect.BStart(wm)));
+ rgFrame->SetSize(wm, LogicalSize(wm, rgNormalRect.ISize(wm),
+ rgNormalRect.BSize(wm) + amountUsedByRG));
+
+ nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
+ origRgVisualOverflow, false);
+ }
+ }
+ else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
+ rgFrame->InvalidateFrameSubtree();
+ rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
+ rgNormalRect.BStart(wm)));
+ // Make sure child views are properly positioned
+ nsTableFrame::RePositionViews(rgFrame);
+ rgFrame->InvalidateFrameSubtree();
+ }
+ bOriginRG = bEndRG;
+ }
+
+ if (amountUsed >= aAmount) {
+ ResizeCells(*this);
+ return;
+ }
+
+ // get the first row without a style bsize where its row group has an
+ // unconstrained bsize
+ nsTableRowGroupFrame* firstUnStyledRG = nullptr;
+ nsTableRowFrame* firstUnStyledRow = nullptr;
+ for (rgIdx = 0; rgIdx < rowGroups.Length() && !firstUnStyledRG; rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+ if (!rgFrame->HasStyleBSize()) {
+ nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
+ while (rowFrame) {
+ if (!rowFrame->HasStyleBSize()) {
+ firstUnStyledRG = rgFrame;
+ firstUnStyledRow = rowFrame;
+ break;
+ }
+ rowFrame = rowFrame->GetNextRow();
+ }
+ }
+ }
+
+ nsTableRowFrame* lastEligibleRow = nullptr;
+ // Accumulate the correct divisor. This will be the total bsize of all
+ // unstyled rows inside unstyled row groups, unless there are none, in which
+ // case, it will be number of all rows. If the unstyled rows don't have a
+ // bsize, divide the space equally among them.
+ nscoord divisor = 0;
+ int32_t eligibleRows = 0;
+ bool expandEmptyRows = false;
+
+ if (!firstUnStyledRow) {
+ // there is no unstyled row
+ divisor = GetRowCount();
+ }
+ else {
+ for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+ if (!firstUnStyledRG || !rgFrame->HasStyleBSize()) {
+ nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
+ while (rowFrame) {
+ if (!firstUnStyledRG || !rowFrame->HasStyleBSize()) {
+ NS_ASSERTION(rowFrame->BSize(wm) >= 0,
+ "negative row frame block-size");
+ divisor += rowFrame->BSize(wm);
+ eligibleRows++;
+ lastEligibleRow = rowFrame;
+ }
+ rowFrame = rowFrame->GetNextRow();
+ }
+ }
+ }
+ if (divisor <= 0) {
+ if (eligibleRows > 0) {
+ expandEmptyRows = true;
+ }
+ else {
+ NS_ERROR("invalid divisor");
+ return;
+ }
+ }
+ }
+ // allocate the extra bsize to the unstyled row groups and rows
+ nscoord bSizeToDistribute = aAmount - amountUsed;
+ bOriginRG = borderPadding.BStart(wm) + GetRowSpacing(-1);
+ bEndRG = bOriginRG;
+ for (rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+ nscoord amountUsedByRG = 0;
+ nscoord bOriginRow = 0;
+ LogicalRect rgNormalRect(wm, rgFrame->GetNormalRect(), containerSize);
+ nsRect rgVisualOverflow = rgFrame->GetVisualOverflowRect();
+ // see if there is an eligible row group or we distribute to all rows
+ if (!firstUnStyledRG || !rgFrame->HasStyleBSize() || !eligibleRows) {
+ for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
+ rowFrame; rowFrame = rowFrame->GetNextRow()) {
+ nscoord cellSpacingB = GetRowSpacing(rowFrame->GetRowIndex());
+ // We don't know the final width of the rowGroupFrame yet, so use 0,0
+ // as a dummy containerSize here; we'll adjust the row positions at
+ // the end, after the rowGroup size is finalized.
+ const nsSize dummyContainerSize;
+ LogicalRect rowNormalRect(wm, rowFrame->GetNormalRect(),
+ dummyContainerSize);
+ nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect();
+ // see if there is an eligible row or we distribute to all rows
+ if (!firstUnStyledRow || !rowFrame->HasStyleBSize() || !eligibleRows) {
+ float ratio;
+ if (eligibleRows) {
+ if (!expandEmptyRows) {
+ // The amount of additional space each row gets is proportional
+ // to its bsize
+ ratio = float(rowNormalRect.BSize(wm)) / float(divisor);
+ } else {
+ // empty rows get all the same additional space
+ ratio = 1.0f / float(eligibleRows);
+ }
+ }
+ else {
+ // all rows get the same additional space
+ ratio = 1.0f / float(divisor);
+ }
+ // give rows their additional space, except for the last row which
+ // gets the remainder
+ nscoord amountForRow =
+ (rowFrame == lastEligibleRow)
+ ? aAmount - amountUsed
+ : NSToCoordRound(((float)(bSizeToDistribute)) * ratio);
+ amountForRow = std::min(amountForRow, aAmount - amountUsed);
+
+ if (bOriginRow != rowNormalRect.BStart(wm)) {
+ rowFrame->InvalidateFrameSubtree();
+ }
+
+ // update the row bsize
+ nsRect origRowRect = rowFrame->GetRect();
+ nscoord newRowBSize = rowNormalRect.BSize(wm) + amountForRow;
+ rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow -
+ rowNormalRect.BStart(wm)));
+ rowFrame->SetSize(wm, LogicalSize(wm, rowNormalRect.ISize(wm),
+ newRowBSize));
+
+ bOriginRow += newRowBSize + cellSpacingB;
+ bEndRG += newRowBSize + cellSpacingB;
+
+ amountUsed += amountForRow;
+ amountUsedByRG += amountForRow;
+ NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation");
+ //rowFrame->DidResize();
+ nsTableFrame::RePositionViews(rowFrame);
+
+ nsTableFrame::InvalidateTableFrame(rowFrame, origRowRect,
+ rowVisualOverflow, false);
+ }
+ else {
+ if (amountUsed > 0 && bOriginRow != rowNormalRect.BStart(wm)) {
+ rowFrame->InvalidateFrameSubtree();
+ rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRow -
+ rowNormalRect.BStart(wm)));
+ nsTableFrame::RePositionViews(rowFrame);
+ rowFrame->InvalidateFrameSubtree();
+ }
+ bOriginRow += rowNormalRect.BSize(wm) + cellSpacingB;
+ bEndRG += rowNormalRect.BSize(wm) + cellSpacingB;
+ }
+ }
+
+ if (amountUsed > 0) {
+ if (rgNormalRect.BStart(wm) != bOriginRG) {
+ rgFrame->InvalidateFrameSubtree();
+ }
+
+ nsRect origRgNormalRect = rgFrame->GetRect();
+ rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
+ rgNormalRect.BStart(wm)));
+ rgFrame->SetSize(wm, LogicalSize(wm, rgNormalRect.ISize(wm),
+ rgNormalRect.BSize(wm) + amountUsedByRG));
+
+ nsTableFrame::InvalidateTableFrame(rgFrame, origRgNormalRect,
+ rgVisualOverflow, false);
+ }
+
+ // For vertical-rl mode, we needed to position the rows relative to the
+ // right-hand (block-start) side of the group; but we couldn't do that
+ // above, as we didn't know the rowGroupFrame's final block size yet.
+ // So we used a dummyContainerSize of 0,0 earlier, placing the rows to
+ // the left of the rowGroupFrame's (physical) origin. Now we move them
+ // all rightwards by its final width.
+ if (wm.IsVerticalRL()) {
+ nscoord rgWidth = rgFrame->GetSize().width;
+ for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow();
+ rowFrame; rowFrame = rowFrame->GetNextRow()) {
+ rowFrame->InvalidateFrameSubtree();
+ rowFrame->MovePositionBy(nsPoint(rgWidth, 0));
+ nsTableFrame::RePositionViews(rowFrame);
+ rowFrame->InvalidateFrameSubtree();
+ }
+ }
+ }
+ else if (amountUsed > 0 && bOriginRG != rgNormalRect.BStart(wm)) {
+ rgFrame->InvalidateFrameSubtree();
+ rgFrame->MovePositionBy(wm, LogicalPoint(wm, 0, bOriginRG -
+ rgNormalRect.BStart(wm)));
+ // Make sure child views are properly positioned
+ nsTableFrame::RePositionViews(rgFrame);
+ rgFrame->InvalidateFrameSubtree();
+ }
+ bOriginRG = bEndRG;
+ }
+
+ ResizeCells(*this);
+}
+
+nscoord
+nsTableFrame::GetColumnISizeFromFirstInFlow(int32_t aColIndex)
+{
+ MOZ_ASSERT(this == FirstInFlow());
+ nsTableColFrame* colFrame = GetColFrame(aColIndex);
+ return colFrame ? colFrame->GetFinalISize() : 0;
+}
+
+nscoord
+nsTableFrame::GetColSpacing()
+{
+ if (IsBorderCollapse())
+ return 0;
+
+ return StyleTableBorder()->mBorderSpacingCol;
+}
+
+// XXX: could cache this. But be sure to check style changes if you do!
+nscoord
+nsTableFrame::GetColSpacing(int32_t aColIndex)
+{
+ NS_ASSERTION(aColIndex >= -1 && aColIndex <= GetColCount(),
+ "Column index exceeds the bounds of the table");
+ // Index is irrelevant for ordinary tables. We check that it falls within
+ // appropriate bounds to increase confidence of correctness in situations
+ // where it does matter.
+ return GetColSpacing();
+}
+
+nscoord
+nsTableFrame::GetColSpacing(int32_t aStartColIndex,
+ int32_t aEndColIndex)
+{
+ NS_ASSERTION(aStartColIndex >= -1 && aStartColIndex <= GetColCount(),
+ "Start column index exceeds the bounds of the table");
+ NS_ASSERTION(aEndColIndex >= -1 && aEndColIndex <= GetColCount(),
+ "End column index exceeds the bounds of the table");
+ NS_ASSERTION(aStartColIndex <= aEndColIndex,
+ "End index must not be less than start index");
+ // Only one possible value so just multiply it out. Tables where index
+ // matters will override this function
+ return GetColSpacing() * (aEndColIndex - aStartColIndex);
+}
+
+nscoord
+nsTableFrame::GetRowSpacing()
+{
+ if (IsBorderCollapse())
+ return 0;
+
+ return StyleTableBorder()->mBorderSpacingRow;
+}
+
+// XXX: could cache this. But be sure to check style changes if you do!
+nscoord
+nsTableFrame::GetRowSpacing(int32_t aRowIndex)
+{
+ NS_ASSERTION(aRowIndex >= -1 && aRowIndex <= GetRowCount(),
+ "Row index exceeds the bounds of the table");
+ // Index is irrelevant for ordinary tables. We check that it falls within
+ // appropriate bounds to increase confidence of correctness in situations
+ // where it does matter.
+ return GetRowSpacing();
+}
+
+nscoord
+nsTableFrame::GetRowSpacing(int32_t aStartRowIndex,
+ int32_t aEndRowIndex)
+{
+ NS_ASSERTION(aStartRowIndex >= -1 && aStartRowIndex <= GetRowCount(),
+ "Start row index exceeds the bounds of the table");
+ NS_ASSERTION(aEndRowIndex >= -1 && aEndRowIndex <= GetRowCount(),
+ "End row index exceeds the bounds of the table");
+ NS_ASSERTION(aStartRowIndex <= aEndRowIndex,
+ "End index must not be less than start index");
+ // Only one possible value so just multiply it out. Tables where index
+ // matters will override this function
+ return GetRowSpacing() * (aEndRowIndex - aStartRowIndex);
+}
+
+/* virtual */ nscoord
+nsTableFrame::GetLogicalBaseline(WritingMode aWM) const
+{
+ nscoord baseline;
+ if (!GetNaturalBaselineBOffset(aWM, BaselineSharingGroup::eFirst, &baseline)) {
+ baseline = BSize(aWM);
+ }
+ return baseline;
+}
+
+/* virtual */ bool
+nsTableFrame::GetNaturalBaselineBOffset(WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ nscoord* aBaseline) const
+{
+ RowGroupArray orderedRowGroups;
+ OrderRowGroups(orderedRowGroups);
+ // XXX not sure if this should be the size of the containing block instead.
+ nsSize containerSize = mRect.Size();
+ auto TableBaseline = [aWM, containerSize] (nsTableRowGroupFrame* aRowGroup,
+ nsTableRowFrame* aRow) {
+ nscoord rgBStart = LogicalRect(aWM, aRowGroup->GetNormalRect(),
+ containerSize).BStart(aWM);
+ nscoord rowBStart = LogicalRect(aWM, aRow->GetNormalRect(),
+ containerSize).BStart(aWM);
+ return rgBStart + rowBStart + aRow->GetRowBaseline(aWM);
+ };
+ if (aBaselineGroup == BaselineSharingGroup::eFirst) {
+ for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) {
+ nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
+ nsTableRowFrame* row = rgFrame->GetFirstRow();
+ if (row) {
+ *aBaseline = TableBaseline(rgFrame, row);
+ return true;
+ }
+ }
+ } else {
+ for (uint32_t rgIndex = orderedRowGroups.Length(); rgIndex-- > 0;) {
+ nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex];
+ nsTableRowFrame* row = rgFrame->GetLastRow();
+ if (row) {
+ *aBaseline = BSize(aWM) - TableBaseline(rgFrame, row);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/* ----- global methods ----- */
+
+nsTableFrame*
+NS_NewTableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTableFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame)
+
+nsTableFrame*
+nsTableFrame::GetTableFrame(nsIFrame* aFrame)
+{
+ for (nsIFrame* ancestor = aFrame->GetParent(); ancestor;
+ ancestor = ancestor->GetParent()) {
+ if (nsGkAtoms::tableFrame == ancestor->GetType()) {
+ return static_cast<nsTableFrame*>(ancestor);
+ }
+ }
+ NS_RUNTIMEABORT("unable to find table parent");
+ return nullptr;
+}
+
+nsTableFrame*
+nsTableFrame::GetTableFramePassingThrough(nsIFrame* aMustPassThrough,
+ nsIFrame* aFrame,
+ bool* aDidPassThrough)
+{
+ MOZ_ASSERT(aMustPassThrough == aFrame ||
+ nsLayoutUtils::IsProperAncestorFrame(aMustPassThrough, aFrame),
+ "aMustPassThrough should be an ancestor");
+
+ // Retrieve the table frame, and check if we hit aMustPassThrough on the
+ // way.
+ *aDidPassThrough = false;
+ nsTableFrame* tableFrame = nullptr;
+ for (nsIFrame* ancestor = aFrame; ancestor; ancestor = ancestor->GetParent()) {
+ if (ancestor == aMustPassThrough) {
+ *aDidPassThrough = true;
+ }
+ if (nsGkAtoms::tableFrame == ancestor->GetType()) {
+ tableFrame = static_cast<nsTableFrame*>(ancestor);
+ break;
+ }
+ }
+
+ MOZ_ASSERT(tableFrame, "Should have a table frame here");
+ return tableFrame;
+}
+
+bool
+nsTableFrame::IsAutoBSize(WritingMode aWM)
+{
+ const nsStyleCoord &bsize = StylePosition()->BSize(aWM);
+ // Don't consider calc() here like this quirk for percent.
+ return bsize.GetUnit() == eStyleUnit_Auto ||
+ (bsize.GetUnit() == eStyleUnit_Percent &&
+ bsize.GetPercentValue() <= 0.0f);
+}
+
+nscoord
+nsTableFrame::CalcBorderBoxBSize(const ReflowInput& aState)
+{
+ nscoord bSize = aState.ComputedBSize();
+ if (NS_AUTOHEIGHT != bSize) {
+ WritingMode wm = aState.GetWritingMode();
+ LogicalMargin borderPadding = GetChildAreaOffset(wm, &aState);
+ bSize += borderPadding.BStartEnd(wm);
+ }
+ bSize = std::max(0, bSize);
+
+ return bSize;
+}
+
+bool
+nsTableFrame::IsAutoLayout()
+{
+ if (StyleTable()->mLayoutStrategy == NS_STYLE_TABLE_LAYOUT_AUTO)
+ return true;
+ // a fixed-layout inline-table must have a inline size
+ // and tables with inline size set to '-moz-max-content' must be
+ // auto-layout (at least as long as
+ // FixedTableLayoutStrategy::GetPrefISize returns nscoord_MAX)
+ const nsStyleCoord &iSize = StylePosition()->ISize(GetWritingMode());
+ return (iSize.GetUnit() == eStyleUnit_Auto) ||
+ (iSize.GetUnit() == eStyleUnit_Enumerated &&
+ iSize.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTableFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("Table"), aResult);
+}
+#endif
+
+// Find the closet sibling before aPriorChildFrame (including aPriorChildFrame) that
+// is of type aChildType
+nsIFrame*
+nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame,
+ nsIFrame* aPriorChildFrame,
+ nsIAtom* aChildType)
+{
+ nsIFrame* result = nullptr;
+ if (!aPriorChildFrame) {
+ return result;
+ }
+ if (aChildType == aPriorChildFrame->GetType()) {
+ return aPriorChildFrame;
+ }
+
+ // aPriorChildFrame is not of type aChildType, so we need start from
+ // the beginnng and find the closest one
+ nsIFrame* lastMatchingFrame = nullptr;
+ nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild();
+ while (childFrame && (childFrame != aPriorChildFrame)) {
+ if (aChildType == childFrame->GetType()) {
+ lastMatchingFrame = childFrame;
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ return lastMatchingFrame;
+}
+
+#ifdef DEBUG
+void
+nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame)
+{
+ if (!aKidFrame)
+ return;
+
+ for (nsIFrame* cFrame : aKidFrame->PrincipalChildList()) {
+ nsTableRowFrame *rowFrame = do_QueryFrame(cFrame);
+ if (rowFrame) {
+ printf("row(%d)=%p ", rowFrame->GetRowIndex(),
+ static_cast<void*>(rowFrame));
+ for (nsIFrame* childFrame : cFrame->PrincipalChildList()) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
+ if (cellFrame) {
+ int32_t colIndex;
+ cellFrame->GetColIndex(colIndex);
+ printf("cell(%d)=%p ", colIndex, static_cast<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 (nsGkAtoms::tableColGroupFrame == childFrame->GetType()) {
+ nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame *)childFrame;
+ colGroupFrame->Dump(1);
+ }
+ }
+ for (colIdx = 0; colIdx < numCols; colIdx++) {
+ printf("\n");
+ nsTableColFrame* colFrame = GetColFrame(colIdx);
+ colFrame->Dump(1);
+ }
+ }
+ if (aDumpCellMap) {
+ nsTableCellMap* cellMap = GetCellMap();
+ cellMap->Dump();
+ }
+ printf(" ***END TABLE DUMP*** \n");
+}
+#endif
+
+bool
+nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex) const
+{
+ // Since fixed-layout tables should not have their column sizes change
+ // as they load, we assume that all columns are significant.
+ if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed)
+ return true;
+ // the first column is always significant
+ if (aColIndex == 0)
+ return true;
+ nsTableCellMap* cellMap = GetCellMap();
+ if (!cellMap)
+ return false;
+ return cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0;
+}
+
+/********************************************************************************
+ * Collapsing Borders
+ *
+ * The CSS spec says to resolve border conflicts in this order:
+ * 1) any border with the style HIDDEN wins
+ * 2) the widest border with a style that is not NONE wins
+ * 3) the border styles are ranked in this order, highest to lowest precedence:
+ * double, solid, dashed, dotted, ridge, outset, groove, inset
+ * 4) borders that are of equal width and style (differ only in color) have this precedence:
+ * cell, row, rowgroup, col, colgroup, table
+ * 5) if all border styles are NONE, then that's the computed border style.
+ *******************************************************************************/
+
+#ifdef DEBUG
+#define VerifyNonNegativeDamageRect(r) \
+ NS_ASSERTION((r).StartCol() >= 0, "negative col index"); \
+ NS_ASSERTION((r).StartRow() >= 0, "negative row index"); \
+ NS_ASSERTION((r).ColCount() >= 0, "negative cols damage"); \
+ NS_ASSERTION((r).RowCount() >= 0, "negative rows damage");
+#define VerifyDamageRect(r) \
+ VerifyNonNegativeDamageRect(r); \
+ NS_ASSERTION((r).EndCol() <= GetColCount(), \
+ "cols damage extends outside table"); \
+ NS_ASSERTION((r).EndRow() <= GetRowCount(), \
+ "rows damage extends outside table");
+#endif
+
+void
+nsTableFrame::AddBCDamageArea(const TableArea& aValue)
+{
+ NS_ASSERTION(IsBorderCollapse(), "invalid AddBCDamageArea call");
+#ifdef DEBUG
+ VerifyDamageRect(aValue);
+#endif
+
+ SetNeedToCalcBCBorders(true);
+ // Get the property
+ BCPropertyData* value = GetBCProperty(true);
+ if (value) {
+#ifdef DEBUG
+ VerifyNonNegativeDamageRect(value->mDamageArea);
+#endif
+ // Clamp the old damage area to the current table area in case it shrunk.
+ int32_t cols = GetColCount();
+ if (value->mDamageArea.EndCol() > cols) {
+ if (value->mDamageArea.StartCol() > cols) {
+ value->mDamageArea.StartCol() = cols;
+ value->mDamageArea.ColCount() = 0;
+ }
+ else {
+ value->mDamageArea.ColCount() = cols - value->mDamageArea.StartCol();
+ }
+ }
+ int32_t rows = GetRowCount();
+ if (value->mDamageArea.EndRow() > rows) {
+ if (value->mDamageArea.StartRow() > rows) {
+ value->mDamageArea.StartRow() = rows;
+ value->mDamageArea.RowCount() = 0;
+ }
+ else {
+ value->mDamageArea.RowCount() = rows - value->mDamageArea.StartRow();
+ }
+ }
+
+ // Construct a union of the new and old damage areas.
+ value->mDamageArea.UnionArea(value->mDamageArea, aValue);
+ }
+}
+
+
+void
+nsTableFrame::SetFullBCDamageArea()
+{
+ NS_ASSERTION(IsBorderCollapse(), "invalid SetFullBCDamageArea call");
+
+ SetNeedToCalcBCBorders(true);
+
+ BCPropertyData* value = GetBCProperty(true);
+ if (value) {
+ value->mDamageArea = TableArea(0, 0, GetColCount(), GetRowCount());
+ }
+}
+
+
+/* BCCellBorder represents a border segment which can be either an inline-dir
+ * or a block-dir segment. For each segment we need to know the color, width,
+ * style, who owns it and how long it is in cellmap coordinates.
+ * Ownership of these segments is important to calculate which corners should
+ * be bevelled. This structure has dual use, its used first to compute the
+ * dominant border for inline-dir and block-dir segments and to store the
+ * preliminary computed border results in the BCCellBorders structure.
+ * This temporary storage is not symmetric with respect to inline-dir and
+ * block-dir border segments, its always column oriented. For each column in
+ * the cellmap there is a temporary stored block-dir and inline-dir segment.
+ * XXX_Bernd this asymmetry is the root of those rowspan bc border errors
+ */
+struct BCCellBorder
+{
+ BCCellBorder() { Reset(0, 1); }
+ void Reset(uint32_t aRowIndex, uint32_t aRowSpan);
+ nscolor color; // border segment color
+ BCPixelSize width; // border segment width in pixel coordinates !!
+ uint8_t style; // border segment style, possible values are defined
+ // in nsStyleConsts.h as NS_STYLE_BORDER_STYLE_*
+ BCBorderOwner owner; // border segment owner, possible values are defined
+ // in celldata.h. In the cellmap for each border
+ // segment we store the owner and later when
+ // painting we know the owner and can retrieve the
+ // style info from the corresponding frame
+ int32_t rowIndex; // rowIndex of temporary stored inline-dir border
+ // segments relative to the table
+ int32_t rowSpan; // row span of temporary stored inline-dir border
+ // segments
+};
+
+void
+BCCellBorder::Reset(uint32_t aRowIndex,
+ uint32_t aRowSpan)
+{
+ style = NS_STYLE_BORDER_STYLE_NONE;
+ color = 0;
+ width = 0;
+ owner = eTableOwner;
+ rowIndex = aRowIndex;
+ rowSpan = aRowSpan;
+}
+
+class BCMapCellIterator;
+
+/*****************************************************************
+ * BCMapCellInfo
+ * This structure stores information about the cellmap and all involved
+ * table related frames that are used during the computation of winning borders
+ * in CalcBCBorders so that they do need to be looked up again and again when
+ * iterating over the cells.
+ ****************************************************************/
+struct BCMapCellInfo
+{
+ explicit BCMapCellInfo(nsTableFrame* aTableFrame);
+ void ResetCellInfo();
+ void SetInfo(nsTableRowFrame* aNewRow,
+ int32_t aColIndex,
+ BCCellData* aCellData,
+ BCMapCellIterator* aIter,
+ nsCellMap* aCellMap = nullptr);
+ // The BCMapCellInfo has functions to set the continous
+ // border widths (see nsTablePainter.cpp for a description of the continous
+ // borders concept). The widths are computed inside these functions based on
+ // the current position inside the table and the cached frames that correspond
+ // to this position. The widths are stored in member variables of the internal
+ // table frames.
+ void SetTableBStartIStartContBCBorder();
+ void SetRowGroupIStartContBCBorder();
+ void SetRowGroupIEndContBCBorder();
+ void SetRowGroupBEndContBCBorder();
+ void SetRowIStartContBCBorder();
+ void SetRowIEndContBCBorder();
+ void SetColumnBStartIEndContBCBorder();
+ void SetColumnBEndContBCBorder();
+ void SetColGroupBEndContBCBorder();
+ void SetInnerRowGroupBEndContBCBorder(const nsIFrame* aNextRowGroup,
+ nsTableRowFrame* aNextRow);
+
+ // functions to set the border widths on the table related frames, where the
+ // knowledge about the current position in the table is used.
+ void SetTableBStartBorderWidth(BCPixelSize aWidth);
+ void SetTableIStartBorderWidth(int32_t aRowB, BCPixelSize aWidth);
+ void SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth);
+ void SetTableBEndBorderWidth(BCPixelSize aWidth);
+ void SetIStartBorderWidths(BCPixelSize aWidth);
+ void SetIEndBorderWidths(BCPixelSize aWidth);
+ void SetBStartBorderWidths(BCPixelSize aWidth);
+ void SetBEndBorderWidths(BCPixelSize aWidth);
+
+ // functions to compute the borders; they depend on the
+ // knowledge about the current position in the table. The edge functions
+ // should be called if a table edge is involved, otherwise the internal
+ // functions should be called.
+ BCCellBorder GetBStartEdgeBorder();
+ BCCellBorder GetBEndEdgeBorder();
+ BCCellBorder GetIStartEdgeBorder();
+ BCCellBorder GetIEndEdgeBorder();
+ BCCellBorder GetIEndInternalBorder();
+ BCCellBorder GetIStartInternalBorder();
+ BCCellBorder GetBStartInternalBorder();
+ BCCellBorder GetBEndInternalBorder();
+
+ // functions to set the internal position information
+ void SetColumn(int32_t aColX);
+ // Increment the row as we loop over the rows of a rowspan
+ void IncrementRow(bool aResetToBStartRowOfCell = false);
+
+ // Helper functions to get extent of the cell
+ int32_t GetCellEndRowIndex() const;
+ int32_t GetCellEndColIndex() const;
+
+ // storage of table information
+ nsTableFrame* mTableFrame;
+ int32_t mNumTableRows;
+ int32_t mNumTableCols;
+ BCPropertyData* mTableBCData;
+ WritingMode mTableWM;
+
+ // a cell can only belong to one rowgroup
+ nsTableRowGroupFrame* mRowGroup;
+
+ // a cell with a rowspan has a bstart and a bend row, and rows in between
+ nsTableRowFrame* mStartRow;
+ nsTableRowFrame* mEndRow;
+ nsTableRowFrame* mCurrentRowFrame;
+
+ // a cell with a colspan has an istart and iend column and columns in between
+ // they can belong to different colgroups
+ nsTableColGroupFrame* mColGroup;
+ nsTableColGroupFrame* mCurrentColGroupFrame;
+
+ nsTableColFrame* mStartCol;
+ nsTableColFrame* mEndCol;
+ nsTableColFrame* mCurrentColFrame;
+
+ // cell information
+ BCCellData* mCellData;
+ nsBCTableCellFrame* mCell;
+
+ int32_t mRowIndex;
+ int32_t mRowSpan;
+ int32_t mColIndex;
+ int32_t mColSpan;
+
+ // flags to describe the position of the cell with respect to the row- and
+ // colgroups, for instance mRgAtStart documents that the bStart cell border hits
+ // a rowgroup border
+ bool mRgAtStart;
+ bool mRgAtEnd;
+ bool mCgAtStart;
+ bool mCgAtEnd;
+
+};
+
+
+BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame)
+ : mTableFrame(aTableFrame)
+ , mNumTableRows(aTableFrame->GetRowCount())
+ , mNumTableCols(aTableFrame->GetColCount())
+ , mTableBCData(mTableFrame->Properties().Get(TableBCProperty()))
+ , mTableWM(aTableFrame->StyleContext())
+{
+ ResetCellInfo();
+}
+
+void
+BCMapCellInfo::ResetCellInfo()
+{
+ mCellData = nullptr;
+ mRowGroup = nullptr;
+ mStartRow = nullptr;
+ mEndRow = nullptr;
+ mColGroup = nullptr;
+ mStartCol = nullptr;
+ mEndCol = nullptr;
+ mCell = nullptr;
+ mRowIndex = mRowSpan = mColIndex = mColSpan = 0;
+ mRgAtStart = mRgAtEnd = mCgAtStart = mCgAtEnd = false;
+}
+
+inline int32_t
+BCMapCellInfo::GetCellEndRowIndex() const
+{
+ return mRowIndex + mRowSpan - 1;
+}
+
+inline int32_t
+BCMapCellInfo::GetCellEndColIndex() const
+{
+ return mColIndex + mColSpan - 1;
+}
+
+
+class BCMapCellIterator
+{
+public:
+ BCMapCellIterator(nsTableFrame* aTableFrame,
+ const TableArea& aDamageArea);
+
+ void First(BCMapCellInfo& aMapCellInfo);
+
+ void Next(BCMapCellInfo& aMapCellInfo);
+
+ void PeekIEnd(BCMapCellInfo& aRefInfo,
+ uint32_t aRowIndex,
+ BCMapCellInfo& aAjaInfo);
+
+ void PeekBEnd(BCMapCellInfo& aRefInfo,
+ uint32_t aColIndex,
+ BCMapCellInfo& aAjaInfo);
+
+ bool IsNewRow() { return mIsNewRow; }
+
+ nsTableRowFrame* GetPrevRow() const { return mPrevRow; }
+ nsTableRowFrame* GetCurrentRow() const { return mRow; }
+ nsTableRowGroupFrame* GetCurrentRowGroup() const { return mRowGroup; }
+
+ int32_t mRowGroupStart;
+ int32_t mRowGroupEnd;
+ bool mAtEnd;
+ nsCellMap* mCellMap;
+
+private:
+ bool SetNewRow(nsTableRowFrame* row = nullptr);
+ bool SetNewRowGroup(bool aFindFirstDamagedRow);
+
+ nsTableFrame* mTableFrame;
+ nsTableCellMap* mTableCellMap;
+ nsTableFrame::RowGroupArray mRowGroups;
+ nsTableRowGroupFrame* mRowGroup;
+ int32_t mRowGroupIndex;
+ uint32_t mNumTableRows;
+ nsTableRowFrame* mRow;
+ nsTableRowFrame* mPrevRow;
+ bool mIsNewRow;
+ int32_t mRowIndex;
+ uint32_t mNumTableCols;
+ int32_t mColIndex;
+ nsPoint mAreaStart; // These are not really points in the usual
+ nsPoint mAreaEnd; // sense; they're column/row coordinates
+ // in the cell map.
+};
+
+BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame,
+ const TableArea& aDamageArea)
+ : mTableFrame(aTableFrame)
+{
+ mTableCellMap = aTableFrame->GetCellMap();
+
+ mAreaStart.x = aDamageArea.StartCol();
+ mAreaStart.y = aDamageArea.StartRow();
+ mAreaEnd.x = aDamageArea.EndCol() - 1;
+ mAreaEnd.y = aDamageArea.EndRow() - 1;
+
+ mNumTableRows = mTableFrame->GetRowCount();
+ mRow = nullptr;
+ mRowIndex = 0;
+ mNumTableCols = mTableFrame->GetColCount();
+ mColIndex = 0;
+ mRowGroupIndex = -1;
+
+ // Get the ordered row groups
+ aTableFrame->OrderRowGroups(mRowGroups);
+
+ mAtEnd = true; // gets reset when First() is called
+}
+
+// fill fields that we need for border collapse computation on a given cell
+void
+BCMapCellInfo::SetInfo(nsTableRowFrame* aNewRow,
+ int32_t aColIndex,
+ BCCellData* aCellData,
+ BCMapCellIterator* aIter,
+ nsCellMap* aCellMap)
+{
+ // fill the cell information
+ mCellData = aCellData;
+ mColIndex = aColIndex;
+
+ // initialize the row information if it was not previously set for cells in
+ // this row
+ mRowIndex = 0;
+ if (aNewRow) {
+ mStartRow = aNewRow;
+ mRowIndex = aNewRow->GetRowIndex();
+ }
+
+ // fill cell frame info and row information
+ mCell = nullptr;
+ mRowSpan = 1;
+ mColSpan = 1;
+ if (aCellData) {
+ mCell = static_cast<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 = mTableFrame->GetColFrame(aColIndex);
+ if (!mStartCol) ABORT0();
+
+ mEndCol = mStartCol;
+ if (mColSpan > 1) {
+ nsTableColFrame* colFrame = mTableFrame->GetColFrame(aColIndex +
+ mColSpan -1);
+ if (!colFrame) ABORT0();
+ mEndCol = colFrame;
+ }
+
+ // col group frame info
+ mColGroup = mStartCol->GetTableColGroupFrame();
+ int32_t cgStart = mColGroup->GetStartColumnIndex();
+ int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1);
+ mCgAtStart = cgStart == aColIndex;
+ mCgAtEnd = cgEnd == aColIndex + mColSpan - 1;
+}
+
+bool
+BCMapCellIterator::SetNewRow(nsTableRowFrame* aRow)
+{
+ mAtEnd = true;
+ mPrevRow = mRow;
+ if (aRow) {
+ mRow = aRow;
+ }
+ else if (mRow) {
+ mRow = mRow->GetNextRow();
+ }
+ if (mRow) {
+ mRowIndex = mRow->GetRowIndex();
+ // get to the first entry with an originating cell
+ int32_t rgRowIndex = mRowIndex - mRowGroupStart;
+ if (uint32_t(rgRowIndex) >= mCellMap->mRows.Length())
+ ABORT1(false);
+ const nsCellMap::CellDataArray& row = mCellMap->mRows[rgRowIndex];
+
+ for (mColIndex = mAreaStart.x; mColIndex <= mAreaEnd.x; mColIndex++) {
+ CellData* cellData = row.SafeElementAt(mColIndex);
+ if (!cellData) { // add a dead cell data
+ TableArea damageArea;
+ cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex,
+ false, 0, damageArea);
+ if (!cellData) ABORT1(false);
+ }
+ if (cellData && (cellData->IsOrig() || cellData->IsDead())) {
+ break;
+ }
+ }
+ mIsNewRow = true;
+ mAtEnd = false;
+ }
+ else ABORT1(false);
+
+ return !mAtEnd;
+}
+
+bool
+BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow)
+{
+ mAtEnd = true;
+ int32_t numRowGroups = mRowGroups.Length();
+ mCellMap = nullptr;
+ for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) {
+ mRowGroup = mRowGroups[mRowGroupIndex];
+ int32_t rowCount = mRowGroup->GetRowCount();
+ mRowGroupStart = mRowGroup->GetStartRowIndex();
+ mRowGroupEnd = mRowGroupStart + rowCount - 1;
+ if (rowCount > 0) {
+ mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap);
+ if (!mCellMap) ABORT1(false);
+ nsTableRowFrame* firstRow = mRowGroup->GetFirstRow();
+ if (aFindFirstDamagedRow) {
+ if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) {
+ // the damage area starts in the row group
+ if (aFindFirstDamagedRow) {
+ // find the correct first damaged row
+ int32_t numRows = mAreaStart.y - mRowGroupStart;
+ for (int32_t i = 0; i < numRows; i++) {
+ firstRow = firstRow->GetNextRow();
+ if (!firstRow) ABORT1(false);
+ }
+ }
+ }
+ else {
+ continue;
+ }
+ }
+ if (SetNewRow(firstRow)) { // sets mAtEnd
+ break;
+ }
+ }
+ }
+
+ return !mAtEnd;
+}
+
+void
+BCMapCellIterator::First(BCMapCellInfo& aMapInfo)
+{
+ aMapInfo.ResetCellInfo();
+
+ SetNewRowGroup(true); // sets mAtEnd
+ while (!mAtEnd) {
+ if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) {
+ BCCellData* cellData =
+ static_cast<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);
+}
+
+// Assign priorities to border styles. For example, styleToPriority(NS_STYLE_BORDER_STYLE_SOLID)
+// will return the priority of NS_STYLE_BORDER_STYLE_SOLID.
+static uint8_t styleToPriority[13] = { 0, // NS_STYLE_BORDER_STYLE_NONE
+ 2, // NS_STYLE_BORDER_STYLE_GROOVE
+ 4, // NS_STYLE_BORDER_STYLE_RIDGE
+ 5, // NS_STYLE_BORDER_STYLE_DOTTED
+ 6, // NS_STYLE_BORDER_STYLE_DASHED
+ 7, // NS_STYLE_BORDER_STYLE_SOLID
+ 8, // NS_STYLE_BORDER_STYLE_DOUBLE
+ 1, // NS_STYLE_BORDER_STYLE_INSET
+ 3, // NS_STYLE_BORDER_STYLE_OUTSET
+ 9 };// NS_STYLE_BORDER_STYLE_HIDDEN
+// priority rules follow CSS 2.1 spec
+// 'hidden', 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove',
+// and the lowest: 'inset'. none is even weaker
+#define CELL_CORNER true
+
+/** return the border style, border color and optionally the width in
+ * pixel for a given frame and side
+ * @param aFrame - query the info for this frame
+ * @param aTableWM - the writing-mode of the frame
+ * @param aSide - the side of the frame
+ * @param aStyle - the border style
+ * @param aColor - the border color
+ * @param aWidth - the border width in px
+ */
+static void
+GetColorAndStyle(const nsIFrame* aFrame,
+ WritingMode aTableWM,
+ LogicalSide aSide,
+ uint8_t* aStyle,
+ nscolor* aColor,
+ BCPixelSize* aWidth = nullptr)
+{
+ NS_PRECONDITION(aFrame, "null frame");
+ NS_PRECONDITION(aStyle && aColor, "null argument");
+ // initialize out arg
+ *aColor = 0;
+ if (aWidth) {
+ *aWidth = 0;
+ }
+
+ const nsStyleBorder* styleData = aFrame->StyleBorder();
+ mozilla::Side physicalSide = aTableWM.PhysicalSide(aSide);
+ *aStyle = styleData->GetBorderStyle(physicalSide);
+
+ if ((NS_STYLE_BORDER_STYLE_NONE == *aStyle) ||
+ (NS_STYLE_BORDER_STYLE_HIDDEN == *aStyle)) {
+ return;
+ }
+ *aColor = aFrame->StyleContext()->GetVisitedDependentColor(
+ nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[physicalSide]);
+
+ if (aWidth) {
+ nscoord width = styleData->GetComputedBorderWidth(physicalSide);
+ *aWidth = nsPresContext::AppUnitsToIntCSSPixels(width);
+ }
+}
+
+/** coerce the paint style as required by CSS2.1
+ * @param aFrame - query the info for this frame
+ * @param aTableWM - the writing mode of the frame
+ * @param aSide - the side of the frame
+ * @param aStyle - the border style
+ * @param aColor - the border color
+ */
+static void
+GetPaintStyleInfo(const nsIFrame* aFrame,
+ WritingMode aTableWM,
+ LogicalSide aSide,
+ uint8_t* aStyle,
+ nscolor* aColor)
+{
+ GetColorAndStyle(aFrame, aTableWM, aSide, aStyle, aColor);
+ if (NS_STYLE_BORDER_STYLE_INSET == *aStyle) {
+ *aStyle = NS_STYLE_BORDER_STYLE_RIDGE;
+ } else if (NS_STYLE_BORDER_STYLE_OUTSET == *aStyle) {
+ *aStyle = NS_STYLE_BORDER_STYLE_GROOVE;
+ }
+}
+
+class nsDelayedCalcBCBorders : public Runnable {
+public:
+ explicit nsDelayedCalcBCBorders(nsIFrame* aFrame) :
+ mFrame(aFrame) {}
+
+ NS_IMETHOD Run() override {
+ if (mFrame) {
+ nsTableFrame* tableFrame = static_cast <nsTableFrame*>(mFrame.GetFrame());
+ if (tableFrame->NeedToCalcBCBorders()) {
+ tableFrame->CalcBCBorders();
+ }
+ }
+ return NS_OK;
+ }
+private:
+ nsWeakFrame mFrame;
+};
+
+bool
+nsTableFrame::BCRecalcNeeded(nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext)
+{
+ // Attention: the old style context is the one we're forgetting,
+ // and hence possibly completely bogus for GetStyle* purposes.
+ // We use PeekStyleData instead.
+
+ const nsStyleBorder* oldStyleData = aOldStyleContext->PeekStyleBorder();
+ if (!oldStyleData)
+ return false;
+
+ const nsStyleBorder* newStyleData = aNewStyleContext->StyleBorder();
+ nsChangeHint change = newStyleData->CalcDifference(*oldStyleData);
+ if (!change)
+ return false;
+ if (change & nsChangeHint_NeedReflow)
+ return true; // the caller only needs to mark the bc damage area
+ if (change & nsChangeHint_RepaintFrame) {
+ // we need to recompute the borders and the caller needs to mark
+ // the bc damage area
+ // XXX In principle this should only be necessary for border style changes
+ // However the bc painting code tries to maximize the drawn border segments
+ // so it stores in the cellmap where a new border segment starts and this
+ // introduces a unwanted cellmap data dependence on color
+ nsCOMPtr<nsIRunnable> evt = new nsDelayedCalcBCBorders(this);
+ NS_DispatchToCurrentThread(evt);
+ return true;
+ }
+ return false;
+}
+
+
+// Compare two border segments, this comparison depends whether the two
+// segments meet at a corner and whether the second segment is inline-dir.
+// The return value is whichever of aBorder1 or aBorder2 dominates.
+static const BCCellBorder&
+CompareBorders(bool aIsCorner, // Pass true for corner calculations
+ const BCCellBorder& aBorder1,
+ const BCCellBorder& aBorder2,
+ bool aSecondIsInlineDir,
+ bool* aFirstDominates = nullptr)
+{
+ bool firstDominates = true;
+
+ if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder1.style) {
+ firstDominates = (aIsCorner) ? false : true;
+ }
+ else if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder2.style) {
+ firstDominates = (aIsCorner) ? true : false;
+ }
+ else if (aBorder1.width < aBorder2.width) {
+ firstDominates = false;
+ }
+ else if (aBorder1.width == aBorder2.width) {
+ if (styleToPriority[aBorder1.style] < styleToPriority[aBorder2.style]) {
+ firstDominates = false;
+ }
+ else if (styleToPriority[aBorder1.style] == styleToPriority[aBorder2.style]) {
+ if (aBorder1.owner == aBorder2.owner) {
+ firstDominates = !aSecondIsInlineDir;
+ }
+ else if (aBorder1.owner < aBorder2.owner) {
+ firstDominates = false;
+ }
+ }
+ }
+
+ if (aFirstDominates)
+ *aFirstDominates = firstDominates;
+
+ if (firstDominates)
+ return aBorder1;
+ return aBorder2;
+}
+
+/** calc the dominant border by considering the table, row/col group, row/col,
+ * cell.
+ * Depending on whether the side is block-dir or inline-dir and whether
+ * adjacent frames are taken into account the ownership of a single border
+ * segment is defined. The return value is the dominating border
+ * The cellmap stores only bstart and istart borders for each cellmap position.
+ * If the cell border is owned by the cell that is istart-wards of the border
+ * it will be an adjacent owner aka eAjaCellOwner. See celldata.h for the other
+ * scenarios with a adjacent owner.
+ * @param xxxFrame - the frame for style information, might be zero if
+ * it should not be considered
+ * @param aTableWM - the writing mode of the frame
+ * @param aSide - side of the frames that should be considered
+ * @param aAja - the border comparison takes place from the point of
+ * a frame that is adjacent to the cellmap entry, for
+ * when a cell owns its lower border it will be the
+ * adjacent owner as in the cellmap only bstart and
+ * istart borders are stored.
+ */
+static BCCellBorder
+CompareBorders(const nsIFrame* aTableFrame,
+ const nsIFrame* aColGroupFrame,
+ const nsIFrame* aColFrame,
+ const nsIFrame* aRowGroupFrame,
+ const nsIFrame* aRowFrame,
+ const nsIFrame* aCellFrame,
+ WritingMode aTableWM,
+ LogicalSide aSide,
+ bool aAja)
+{
+ BCCellBorder border, tempBorder;
+ bool inlineAxis = IsBlock(aSide);
+
+ // start with the table as dominant if present
+ if (aTableFrame) {
+ GetColorAndStyle(aTableFrame, aTableWM, aSide,
+ &border.style, &border.color, &border.width);
+ border.owner = eTableOwner;
+ if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
+ return border;
+ }
+ }
+ // see if the colgroup is dominant
+ if (aColGroupFrame) {
+ GetColorAndStyle(aColGroupFrame, aTableWM, aSide,
+ &tempBorder.style, &tempBorder.color, &tempBorder.width);
+ tempBorder.owner = aAja && !inlineAxis ? eAjaColGroupOwner : eColGroupOwner;
+ // pass here and below false for aSecondIsInlineDir as it is only used for corner calculations.
+ border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
+ if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
+ return border;
+ }
+ }
+ // see if the col is dominant
+ if (aColFrame) {
+ GetColorAndStyle(aColFrame, aTableWM, aSide,
+ &tempBorder.style, &tempBorder.color, &tempBorder.width);
+ tempBorder.owner = aAja && !inlineAxis ? eAjaColOwner : eColOwner;
+ border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
+ if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
+ return border;
+ }
+ }
+ // see if the rowgroup is dominant
+ if (aRowGroupFrame) {
+ GetColorAndStyle(aRowGroupFrame, aTableWM, aSide,
+ &tempBorder.style, &tempBorder.color, &tempBorder.width);
+ tempBorder.owner = aAja && inlineAxis ? eAjaRowGroupOwner : eRowGroupOwner;
+ border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
+ if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
+ return border;
+ }
+ }
+ // see if the row is dominant
+ if (aRowFrame) {
+ GetColorAndStyle(aRowFrame, aTableWM, aSide,
+ &tempBorder.style, &tempBorder.color, &tempBorder.width);
+ tempBorder.owner = aAja && inlineAxis ? eAjaRowOwner : eRowOwner;
+ border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
+ if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) {
+ return border;
+ }
+ }
+ // see if the cell is dominant
+ if (aCellFrame) {
+ GetColorAndStyle(aCellFrame, aTableWM, aSide,
+ &tempBorder.style, &tempBorder.color, &tempBorder.width);
+ tempBorder.owner = aAja ? eAjaCellOwner : eCellOwner;
+ border = CompareBorders(!CELL_CORNER, border, tempBorder, false);
+ }
+ return border;
+}
+
+static bool
+Perpendicular(mozilla::LogicalSide aSide1,
+ mozilla::LogicalSide aSide2)
+{
+ return IsInline(aSide1) != IsInline(aSide2);
+}
+
+// XXX allocate this as number-of-cols+1 instead of number-of-cols+1 * number-of-rows+1
+struct BCCornerInfo
+{
+ BCCornerInfo() { ownerColor = 0; ownerWidth = subWidth = ownerElem = subSide =
+ subElem = hasDashDot = numSegs = bevel = 0; ownerSide = eLogicalSideBStart;
+ ownerStyle = 0xFF; subStyle = NS_STYLE_BORDER_STYLE_SOLID; }
+ void Set(mozilla::LogicalSide aSide,
+ BCCellBorder border);
+
+ void Update(mozilla::LogicalSide aSide,
+ BCCellBorder border);
+
+ nscolor ownerColor; // color of borderOwner
+ uint16_t ownerWidth; // pixel width of borderOwner
+ uint16_t subWidth; // pixel width of the largest border intersecting the border perpendicular
+ // to ownerSide
+ uint32_t ownerSide:2; // LogicalSide (e.g eLogicalSideBStart, etc) of the border
+ // owning the corner relative to the corner
+ uint32_t ownerElem:3; // elem type (e.g. eTable, eGroup, etc) owning the corner
+ uint32_t ownerStyle:8; // border style of ownerElem
+ uint32_t subSide:2; // side of border with subWidth relative to the corner
+ uint32_t subElem:3; // elem type (e.g. eTable, eGroup, etc) of sub owner
+ uint32_t subStyle:8; // border style of subElem
+ uint32_t hasDashDot:1; // does a dashed, dotted segment enter the corner, they cannot be beveled
+ uint32_t numSegs:3; // number of segments entering corner
+ uint32_t bevel:1; // is the corner beveled (uses the above two fields together with subWidth)
+ uint32_t unused:1;
+};
+
+void
+BCCornerInfo::Set(mozilla::LogicalSide aSide,
+ BCCellBorder aBorder)
+{
+ ownerElem = aBorder.owner;
+ ownerStyle = aBorder.style;
+ ownerWidth = aBorder.width;
+ ownerColor = aBorder.color;
+ ownerSide = aSide;
+ hasDashDot = 0;
+ numSegs = 0;
+ if (aBorder.width > 0) {
+ numSegs++;
+ hasDashDot = (NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) ||
+ (NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style);
+ }
+ bevel = 0;
+ subWidth = 0;
+ // the following will get set later
+ subSide = IsInline(aSide) ? eLogicalSideBStart : eLogicalSideIStart;
+ subElem = eTableOwner;
+ subStyle = NS_STYLE_BORDER_STYLE_SOLID;
+}
+
+void
+BCCornerInfo::Update(mozilla::LogicalSide aSide,
+ BCCellBorder aBorder)
+{
+ bool existingWins = false;
+ if (0xFF == ownerStyle) { // initial value indiating that it hasn't been set yet
+ Set(aSide, aBorder);
+ }
+ else {
+ bool isInline = IsInline(aSide); // relative to the corner
+ BCCellBorder oldBorder, tempBorder;
+ oldBorder.owner = (BCBorderOwner) ownerElem;
+ oldBorder.style = ownerStyle;
+ oldBorder.width = ownerWidth;
+ oldBorder.color = ownerColor;
+
+ LogicalSide oldSide = LogicalSide(ownerSide);
+
+ tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, isInline, &existingWins);
+
+ ownerElem = tempBorder.owner;
+ ownerStyle = tempBorder.style;
+ ownerWidth = tempBorder.width;
+ ownerColor = tempBorder.color;
+ if (existingWins) { // existing corner is dominant
+ if (::Perpendicular(LogicalSide(ownerSide), aSide)) {
+ // see if the new sub info replaces the old
+ BCCellBorder subBorder;
+ subBorder.owner = (BCBorderOwner) subElem;
+ subBorder.style = subStyle;
+ subBorder.width = subWidth;
+ subBorder.color = 0; // we are not interested in subBorder color
+ bool firstWins;
+
+ tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, isInline, &firstWins);
+
+ subElem = tempBorder.owner;
+ subStyle = tempBorder.style;
+ subWidth = tempBorder.width;
+ if (!firstWins) {
+ subSide = aSide;
+ }
+ }
+ }
+ else { // input args are dominant
+ ownerSide = aSide;
+ if (::Perpendicular(oldSide, LogicalSide(ownerSide))) {
+ subElem = oldBorder.owner;
+ subStyle = oldBorder.style;
+ subWidth = oldBorder.width;
+ subSide = oldSide;
+ }
+ }
+ if (aBorder.width > 0) {
+ numSegs++;
+ if (!hasDashDot && ((NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) ||
+ (NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style))) {
+ hasDashDot = 1;
+ }
+ }
+
+ // bevel the corner if only two perpendicular non dashed/dotted segments enter the corner
+ bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot);
+ }
+}
+
+struct BCCorners
+{
+ BCCorners(int32_t aNumCorners,
+ int32_t aStartIndex);
+
+ ~BCCorners() { delete [] corners; }
+
+ BCCornerInfo& operator [](int32_t i) const
+ { NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
+ return corners[clamped(i, startIndex, endIndex) - startIndex]; }
+
+ int32_t startIndex;
+ int32_t endIndex;
+ BCCornerInfo* corners;
+};
+
+BCCorners::BCCorners(int32_t aNumCorners,
+ int32_t aStartIndex)
+{
+ NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error");
+ startIndex = aStartIndex;
+ endIndex = aStartIndex + aNumCorners - 1;
+ corners = new BCCornerInfo[aNumCorners];
+}
+
+
+struct BCCellBorders
+{
+ BCCellBorders(int32_t aNumBorders,
+ int32_t aStartIndex);
+
+ ~BCCellBorders() { delete [] borders; }
+
+ BCCellBorder& operator [](int32_t i) const
+ { NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error");
+ return borders[clamped(i, startIndex, endIndex) - startIndex]; }
+
+ int32_t startIndex;
+ int32_t endIndex;
+ BCCellBorder* borders;
+};
+
+BCCellBorders::BCCellBorders(int32_t aNumBorders,
+ int32_t aStartIndex)
+{
+ NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error");
+ startIndex = aStartIndex;
+ endIndex = aStartIndex + aNumBorders - 1;
+ borders = new BCCellBorder[aNumBorders];
+}
+
+// this function sets the new border properties and returns true if the border
+// segment will start a new segment and not be accumulated into the previous
+// segment.
+static bool
+SetBorder(const BCCellBorder& aNewBorder,
+ BCCellBorder& aBorder)
+{
+ bool changed = (aNewBorder.style != aBorder.style) ||
+ (aNewBorder.width != aBorder.width) ||
+ (aNewBorder.color != aBorder.color);
+ aBorder.color = aNewBorder.color;
+ aBorder.width = aNewBorder.width;
+ aBorder.style = aNewBorder.style;
+ aBorder.owner = aNewBorder.owner;
+
+ return changed;
+}
+
+// this function will set the inline-dir border. It will return true if the
+// existing segment will not be continued. Having a block-dir owner of a corner
+// should also start a new segment.
+static bool
+SetInlineDirBorder(const BCCellBorder& aNewBorder,
+ const BCCornerInfo& aCorner,
+ BCCellBorder& aBorder)
+{
+ bool startSeg = ::SetBorder(aNewBorder, aBorder);
+ if (!startSeg) {
+ startSeg = !IsInline(LogicalSide(aCorner.ownerSide));
+ }
+ return startSeg;
+}
+
+// Make the damage area larger on the top and bottom by at least one row and on the left and right
+// at least one column. This is done so that adjacent elements are part of the border calculations.
+// The extra segments and borders outside the actual damage area will not be updated in the cell map,
+// because they in turn would need info from adjacent segments outside the damage area to be accurate.
+void
+nsTableFrame::ExpandBCDamageArea(TableArea& aArea) const
+{
+ int32_t numRows = GetRowCount();
+ int32_t numCols = GetColCount();
+
+ int32_t dStartX = aArea.StartCol();
+ int32_t dEndX = aArea.EndCol() - 1;
+ int32_t dStartY = aArea.StartRow();
+ int32_t dEndY = aArea.EndRow() - 1;
+
+ // expand the damage area in each direction
+ if (dStartX > 0) {
+ dStartX--;
+ }
+ if (dEndX < (numCols - 1)) {
+ dEndX++;
+ }
+ if (dStartY > 0) {
+ dStartY--;
+ }
+ if (dEndY < (numRows - 1)) {
+ dEndY++;
+ }
+ // Check the damage area so that there are no cells spanning in or out. If there are any then
+ // make the damage area as big as the table, similarly to the way the cell map decides whether
+ // to rebuild versus expand. This could be optimized to expand to the smallest area that contains
+ // no spanners, but it may not be worth the effort in general, and it would need to be done in the
+ // cell map as well.
+ bool haveSpanner = false;
+ if ((dStartX > 0) || (dEndX < (numCols - 1)) || (dStartY > 0) || (dEndY < (numRows - 1))) {
+ nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT0();
+ // Get the ordered row groups
+ RowGroupArray rowGroups;
+ OrderRowGroups(rowGroups);
+
+ // Scope outside loop to be used as hint.
+ nsCellMap* cellMap = nullptr;
+ for (uint32_t rgIdx = 0; rgIdx < rowGroups.Length(); rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = rowGroups[rgIdx];
+ int32_t rgStartY = rgFrame->GetStartRowIndex();
+ int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1;
+ if (dEndY < rgStartY)
+ break;
+ cellMap = tableCellMap->GetMapFor(rgFrame, cellMap);
+ if (!cellMap) ABORT0();
+ // check for spanners from above and below
+ if ((dStartY > 0) && (dStartY >= rgStartY) && (dStartY <= rgEndY)) {
+ if (uint32_t(dStartY - rgStartY) >= cellMap->mRows.Length())
+ ABORT0();
+ const nsCellMap::CellDataArray& row =
+ cellMap->mRows[dStartY - rgStartY];
+ for (int32_t x = dStartX; x <= dEndX; x++) {
+ CellData* cellData = row.SafeElementAt(x);
+ if (cellData && (cellData->IsRowSpan())) {
+ haveSpanner = true;
+ break;
+ }
+ }
+ if (dEndY < rgEndY) {
+ if (uint32_t(dEndY + 1 - rgStartY) >= cellMap->mRows.Length())
+ ABORT0();
+ const nsCellMap::CellDataArray& row2 =
+ cellMap->mRows[dEndY + 1 - rgStartY];
+ for (int32_t x = dStartX; x <= dEndX; x++) {
+ CellData* cellData = row2.SafeElementAt(x);
+ if (cellData && (cellData->IsRowSpan())) {
+ haveSpanner = true;
+ break;
+ }
+ }
+ }
+ }
+ // check for spanners on the left and right
+ int32_t iterStartY = -1;
+ int32_t iterEndY = -1;
+ if ((dStartY >= rgStartY) && (dStartY <= rgEndY)) {
+ // the damage area starts in the row group
+ iterStartY = dStartY;
+ iterEndY = std::min(dEndY, rgEndY);
+ }
+ else if ((dEndY >= rgStartY) && (dEndY <= rgEndY)) {
+ // the damage area ends in the row group
+ iterStartY = rgStartY;
+ iterEndY = dEndY;
+ }
+ else if ((rgStartY >= dStartY) && (rgEndY <= dEndY)) {
+ // the damage area contains the row group
+ iterStartY = rgStartY;
+ iterEndY = rgEndY;
+ }
+ if ((iterStartY >= 0) && (iterEndY >= 0)) {
+ for (int32_t y = iterStartY; y <= iterEndY; y++) {
+ if (uint32_t(y - rgStartY) >= cellMap->mRows.Length())
+ ABORT0();
+ const nsCellMap::CellDataArray& row =
+ cellMap->mRows[y - rgStartY];
+ CellData* cellData = row.SafeElementAt(dStartX);
+ if (cellData && (cellData->IsColSpan())) {
+ haveSpanner = true;
+ break;
+ }
+ if (dEndX < (numCols - 1)) {
+ cellData = row.SafeElementAt(dEndX + 1);
+ if (cellData && (cellData->IsColSpan())) {
+ haveSpanner = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (haveSpanner) {
+ // make the damage area the whole table
+ aArea.StartCol() = 0;
+ aArea.StartRow() = 0;
+ aArea.ColCount() = numCols;
+ aArea.RowCount() = numRows;
+ }
+ else {
+ aArea.StartCol() = dStartX;
+ aArea.StartRow() = dStartY;
+ aArea.ColCount() = 1 + dEndX - dStartX;
+ aArea.RowCount() = 1 + dEndY - dStartY;
+ }
+}
+
+
+#define ADJACENT true
+#define INLINE_DIR true
+
+void
+BCMapCellInfo::SetTableBStartIStartContBCBorder()
+{
+ BCCellBorder currentBorder;
+ //calculate continuous top first row & rowgroup border: special case
+ //because it must include the table in the collapse
+ if (mStartRow) {
+ currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup,
+ mStartRow, nullptr, mTableWM,
+ eLogicalSideBStart, !ADJACENT);
+ mStartRow->SetContinuousBCBorderWidth(eLogicalSideBStart,
+ currentBorder.width);
+ }
+ if (mCgAtEnd && mColGroup) {
+ //calculate continuous top colgroup border once per colgroup
+ currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup,
+ mStartRow, nullptr, mTableWM,
+ eLogicalSideBStart, !ADJACENT);
+ mColGroup->SetContinuousBCBorderWidth(eLogicalSideBStart,
+ currentBorder.width);
+ }
+ if (0 == mColIndex) {
+ currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol, nullptr,
+ nullptr, nullptr, mTableWM,
+ eLogicalSideIStart, !ADJACENT);
+ mTableFrame->SetContinuousIStartBCBorderWidth(currentBorder.width);
+ }
+}
+
+void
+BCMapCellInfo::SetRowGroupIStartContBCBorder()
+{
+ BCCellBorder currentBorder;
+ //get row group continuous borders
+ if (mRgAtEnd && mRowGroup) { //once per row group, so check for bottom
+ currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol,
+ mRowGroup, nullptr, nullptr, mTableWM,
+ eLogicalSideIStart, !ADJACENT);
+ mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIStart,
+ currentBorder.width);
+ }
+}
+
+void
+BCMapCellInfo::SetRowGroupIEndContBCBorder()
+{
+ BCCellBorder currentBorder;
+ //get row group continuous borders
+ if (mRgAtEnd && mRowGroup) { //once per mRowGroup, so check for bottom
+ currentBorder = CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
+ nullptr, nullptr, mTableWM, eLogicalSideIEnd,
+ ADJACENT);
+ mRowGroup->SetContinuousBCBorderWidth(eLogicalSideIEnd,
+ currentBorder.width);
+ }
+}
+
+void
+BCMapCellInfo::SetColumnBStartIEndContBCBorder()
+{
+ BCCellBorder currentBorder;
+ //calculate column continuous borders
+ //we only need to do this once, so we'll do it only on the first row
+ currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
+ mCurrentColFrame, mRowGroup, mStartRow,
+ nullptr, mTableWM, eLogicalSideBStart,
+ !ADJACENT);
+ mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBStart,
+ currentBorder.width);
+ if (mNumTableCols == GetCellEndColIndex() + 1) {
+ currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
+ mCurrentColFrame, nullptr, nullptr, nullptr,
+ mTableWM, eLogicalSideIEnd, !ADJACENT);
+ }
+ else {
+ currentBorder = CompareBorders(nullptr, mCurrentColGroupFrame,
+ mCurrentColFrame, nullptr,nullptr, nullptr,
+ mTableWM, eLogicalSideIEnd, !ADJACENT);
+ }
+ mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd,
+ currentBorder.width);
+}
+
+void
+BCMapCellInfo::SetColumnBEndContBCBorder()
+{
+ BCCellBorder currentBorder;
+ //get col continuous border
+ currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame,
+ mCurrentColFrame, mRowGroup, mEndRow,
+ nullptr, mTableWM, eLogicalSideBEnd, ADJACENT);
+ mCurrentColFrame->SetContinuousBCBorderWidth(eLogicalSideBEnd,
+ currentBorder.width);
+}
+
+void
+BCMapCellInfo::SetColGroupBEndContBCBorder()
+{
+ BCCellBorder currentBorder;
+ if (mColGroup) {
+ currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup,
+ mEndRow, nullptr, mTableWM,
+ eLogicalSideBEnd, ADJACENT);
+ mColGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width);
+ }
+}
+
+void
+BCMapCellInfo::SetRowGroupBEndContBCBorder()
+{
+ BCCellBorder currentBorder;
+ if (mRowGroup) {
+ currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup,
+ mEndRow, nullptr, mTableWM,
+ eLogicalSideBEnd, ADJACENT);
+ mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width);
+ }
+}
+
+void
+BCMapCellInfo::SetInnerRowGroupBEndContBCBorder(const nsIFrame* aNextRowGroup,
+ nsTableRowFrame* aNextRow)
+{
+ BCCellBorder currentBorder, adjacentBorder;
+
+ const nsIFrame* rowgroup = mRgAtEnd ? mRowGroup : nullptr;
+ currentBorder = CompareBorders(nullptr, nullptr, nullptr, rowgroup, mEndRow,
+ nullptr, mTableWM, eLogicalSideBEnd, ADJACENT);
+
+ adjacentBorder = CompareBorders(nullptr, nullptr, nullptr, aNextRowGroup,
+ aNextRow, nullptr, mTableWM, eLogicalSideBStart,
+ !ADJACENT);
+ currentBorder = CompareBorders(false, currentBorder, adjacentBorder,
+ INLINE_DIR);
+ if (aNextRow) {
+ aNextRow->SetContinuousBCBorderWidth(eLogicalSideBStart,
+ currentBorder.width);
+ }
+ if (mRgAtEnd && mRowGroup) {
+ mRowGroup->SetContinuousBCBorderWidth(eLogicalSideBEnd, currentBorder.width);
+ }
+}
+
+void
+BCMapCellInfo::SetRowIStartContBCBorder()
+{
+ //get row continuous borders
+ if (mCurrentRowFrame) {
+ BCCellBorder currentBorder;
+ currentBorder = CompareBorders(mTableFrame, mColGroup, mStartCol,
+ mRowGroup, mCurrentRowFrame, nullptr,
+ mTableWM, eLogicalSideIStart, !ADJACENT);
+ mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIStart,
+ currentBorder.width);
+ }
+}
+
+void
+BCMapCellInfo::SetRowIEndContBCBorder()
+{
+ if (mCurrentRowFrame) {
+ BCCellBorder currentBorder;
+ currentBorder = CompareBorders(mTableFrame, mColGroup, mEndCol, mRowGroup,
+ mCurrentRowFrame, nullptr, mTableWM,
+ eLogicalSideIEnd, ADJACENT);
+ mCurrentRowFrame->SetContinuousBCBorderWidth(eLogicalSideIEnd,
+ currentBorder.width);
+ }
+}
+void
+BCMapCellInfo::SetTableBStartBorderWidth(BCPixelSize aWidth)
+{
+ mTableBCData->mBStartBorderWidth = std::max(mTableBCData->mBStartBorderWidth,
+ aWidth);
+}
+
+void
+BCMapCellInfo::SetTableIStartBorderWidth(int32_t aRowB, BCPixelSize aWidth)
+{
+ // update the iStart first cell border
+ if (aRowB == 0) {
+ mTableBCData->mIStartCellBorderWidth = aWidth;
+ }
+ mTableBCData->mIStartBorderWidth = std::max(mTableBCData->mIStartBorderWidth,
+ aWidth);
+}
+
+void
+BCMapCellInfo::SetTableIEndBorderWidth(int32_t aRowB, BCPixelSize aWidth)
+{
+ // update the iEnd first cell border
+ if (aRowB == 0) {
+ mTableBCData->mIEndCellBorderWidth = aWidth;
+ }
+ mTableBCData->mIEndBorderWidth = std::max(mTableBCData->mIEndBorderWidth,
+ aWidth);
+}
+
+void
+BCMapCellInfo::SetIEndBorderWidths(BCPixelSize aWidth)
+{
+ // update the borders of the cells and cols affected
+ if (mCell) {
+ mCell->SetBorderWidth(eLogicalSideIEnd, std::max(aWidth,
+ mCell->GetBorderWidth(eLogicalSideIEnd)));
+ }
+ if (mEndCol) {
+ BCPixelSize half = BC_BORDER_START_HALF(aWidth);
+ mEndCol->SetIEndBorderWidth(
+ std::max(nscoord(half), mEndCol->GetIEndBorderWidth()));
+ }
+}
+
+void
+BCMapCellInfo::SetBEndBorderWidths(BCPixelSize aWidth)
+{
+ // update the borders of the affected cells and rows
+ if (mCell) {
+ mCell->SetBorderWidth(eLogicalSideBEnd, std::max(aWidth,
+ mCell->GetBorderWidth(eLogicalSideBEnd)));
+ }
+ if (mEndRow) {
+ BCPixelSize half = BC_BORDER_START_HALF(aWidth);
+ mEndRow->SetBEndBCBorderWidth(
+ std::max(nscoord(half), mEndRow->GetBEndBCBorderWidth()));
+ }
+}
+void
+BCMapCellInfo::SetBStartBorderWidths(BCPixelSize aWidth)
+{
+ if (mCell) {
+ mCell->SetBorderWidth(eLogicalSideBStart, std::max(aWidth,
+ mCell->GetBorderWidth(eLogicalSideBStart)));
+ }
+ if (mStartRow) {
+ BCPixelSize half = BC_BORDER_END_HALF(aWidth);
+ mStartRow->SetBStartBCBorderWidth(
+ std::max(nscoord(half), mStartRow->GetBStartBCBorderWidth()));
+ }
+}
+void
+BCMapCellInfo::SetIStartBorderWidths(BCPixelSize aWidth)
+{
+ if (mCell) {
+ mCell->SetBorderWidth(eLogicalSideIStart, std::max(aWidth,
+ mCell->GetBorderWidth(eLogicalSideIStart)));
+ }
+ if (mStartCol) {
+ BCPixelSize half = BC_BORDER_END_HALF(aWidth);
+ mStartCol->SetIStartBorderWidth(
+ std::max(nscoord(half), mStartCol->GetIStartBorderWidth()));
+ }
+}
+
+void
+BCMapCellInfo::SetTableBEndBorderWidth(BCPixelSize aWidth)
+{
+ mTableBCData->mBEndBorderWidth = std::max(mTableBCData->mBEndBorderWidth,
+ aWidth);
+}
+
+void
+BCMapCellInfo::SetColumn(int32_t aColX)
+{
+ mCurrentColFrame = mTableFrame->GetColFrame(aColX);
+ if (!mCurrentColFrame) {
+ NS_ERROR("null mCurrentColFrame");
+ }
+ mCurrentColGroupFrame = static_cast<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);
+}
+
+/* XXX This comment is still written in physical (horizontal-tb) terms.
+
+ Here is the order for storing border edges in the cell map as a cell is processed. There are
+ n=colspan top and bottom border edges per cell and n=rowspan left and right border edges per cell.
+
+ 1) On the top edge of the table, store the top edge. Never store the top edge otherwise, since
+ a bottom edge from a cell above will take care of it.
+ 2) On the left edge of the table, store the left edge. Never store the left edge othewise, since
+ a right edge from a cell to the left will take care of it.
+ 3) Store the right edge (or edges if a row span)
+ 4) Store the bottom edge (or edges if a col span)
+
+ Since corners are computed with only an array of BCCornerInfo indexed by the number-of-cols, corner
+ calculations are somewhat complicated. Using an array with number-of-rows * number-of-col entries
+ would simplify this, but at an extra in memory cost of nearly 12 bytes per cell map entry. Collapsing
+ borders already have about an extra 8 byte per cell map entry overhead (this could be
+ reduced to 4 bytes if we are willing to not store border widths in nsTableCellFrame), Here are the
+ rules in priority order for storing cornes in the cell map as a cell is processed. top-left means the
+ left endpoint of the border edge on the top of the cell. There are n=colspan top and bottom border
+ edges per cell and n=rowspan left and right border edges per cell.
+
+ 1) On the top edge of the table, store the top-left corner, unless on the left edge of the table.
+ Never store the top-right corner, since it will get stored as a right-top corner.
+ 2) On the left edge of the table, store the left-top corner. Never store the left-bottom corner,
+ since it will get stored as a bottom-left corner.
+ 3) Store the right-top corner if (a) it is the top right corner of the table or (b) it is not on
+ the top edge of the table. Never store the right-bottom corner since it will get stored as a
+ bottom-right corner.
+ 4) Store the bottom-right corner, if it is the bottom right corner of the table. Never store it
+ otherwise, since it will get stored as either a right-top corner by a cell below or
+ a bottom-left corner from a cell to the right.
+ 5) Store the bottom-left corner, if (a) on the bottom edge of the table or (b) if the left edge hits
+ the top side of a colspan in its interior. Never store the corner otherwise, since it will
+ get stored as a right-top corner by a cell from below.
+
+ XXX the BC-RTL hack - The correct fix would be a rewrite as described in bug 203686.
+ In order to draw borders in rtl conditions somehow correct, the existing structure which relies
+ heavily on the assumption that the next cell sibling will be on the right side, has been modified.
+ We flip the border during painting and during style lookup. Look for tableIsLTR for places where
+ the flipping is done.
+ */
+
+
+
+// Calc the dominant border at every cell edge and corner within the current damage area
+void
+nsTableFrame::CalcBCBorders()
+{
+ NS_ASSERTION(IsBorderCollapse(),
+ "calling CalcBCBorders on separated-border table");
+ nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT0();
+ int32_t numRows = GetRowCount();
+ int32_t numCols = GetColCount();
+ if (!numRows || !numCols)
+ return; // nothing to do
+
+ // Get the property holding the table damage area and border widths
+ BCPropertyData* propData = GetBCProperty();
+ if (!propData) ABORT0();
+
+
+
+ // calculate an expanded damage area
+ TableArea damageArea(propData->mDamageArea);
+ ExpandBCDamageArea(damageArea);
+
+ // segments that are on the table border edges need
+ // to be initialized only once
+ bool tableBorderReset[4];
+ for (uint32_t sideX = 0; sideX < ArrayLength(tableBorderReset); sideX++) {
+ tableBorderReset[sideX] = false;
+ }
+
+ // block-dir borders indexed in inline-direction (cols)
+ BCCellBorders lastBlockDirBorders(damageArea.ColCount() + 1,
+ damageArea.StartCol());
+ if (!lastBlockDirBorders.borders) ABORT0();
+ BCCellBorder lastBStartBorder, lastBEndBorder;
+ // inline-dir borders indexed in inline-direction (cols)
+ BCCellBorders lastBEndBorders(damageArea.ColCount() + 1,
+ damageArea.StartCol());
+ if (!lastBEndBorders.borders) ABORT0();
+ bool startSeg;
+ bool gotRowBorder = false;
+
+ BCMapCellInfo info(this), ajaInfo(this);
+
+ BCCellBorder currentBorder, adjacentBorder;
+ BCCorners bStartCorners(damageArea.ColCount() + 1, damageArea.StartCol());
+ if (!bStartCorners.corners) ABORT0();
+ BCCorners bEndCorners(damageArea.ColCount() + 1, damageArea.StartCol());
+ if (!bEndCorners.corners) ABORT0();
+
+ BCMapCellIterator iter(this, damageArea);
+ for (iter.First(info); !iter.mAtEnd; iter.Next(info)) {
+ // see if lastBStartBorder, lastBEndBorder need to be reset
+ if (iter.IsNewRow()) {
+ gotRowBorder = false;
+ lastBStartBorder.Reset(info.mRowIndex, info.mRowSpan);
+ lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
+ }
+ else if (info.mColIndex > damageArea.StartCol()) {
+ lastBEndBorder = lastBEndBorders[info.mColIndex - 1];
+ if (info.mRowIndex >
+ (lastBEndBorder.rowIndex - lastBEndBorder.rowSpan)) {
+ // the bStart border's iStart edge butts against the middle of a rowspan
+ lastBStartBorder.Reset(info.mRowIndex, info.mRowSpan);
+ }
+ if (lastBEndBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) {
+ // the bEnd border's iStart edge butts against the middle of a rowspan
+ lastBEndBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan);
+ }
+ }
+
+ // find the dominant border considering the cell's bStart border and the table,
+ // row group, row if the border is at the bStart of the table, otherwise it was
+ // processed in a previous row
+ if (0 == info.mRowIndex) {
+ if (!tableBorderReset[eLogicalSideBStart]) {
+ propData->mBStartBorderWidth = 0;
+ tableBorderReset[eLogicalSideBStart] = true;
+ }
+ for (int32_t colIdx = info.mColIndex;
+ colIdx <= info.GetCellEndColIndex(); colIdx++) {
+ info.SetColumn(colIdx);
+ currentBorder = info.GetBStartEdgeBorder();
+ // update/store the bStart-iStart & bStart-iEnd corners of the seg
+ BCCornerInfo& tlCorner = bStartCorners[colIdx]; // bStart-iStart
+ if (0 == colIdx) {
+ // we are on the iEnd side of the corner
+ tlCorner.Set(eLogicalSideIEnd, currentBorder);
+ }
+ else {
+ tlCorner.Update(eLogicalSideIEnd, currentBorder);
+ tableCellMap->SetBCBorderCorner(eBStartIStart, *iter.mCellMap, 0, 0, colIdx,
+ LogicalSide(tlCorner.ownerSide),
+ tlCorner.subWidth,
+ tlCorner.bevel);
+ }
+ bStartCorners[colIdx + 1].Set(eLogicalSideIStart, currentBorder); // bStart-iEnd
+ // update lastBStartBorder and see if a new segment starts
+ startSeg = SetInlineDirBorder(currentBorder, tlCorner, lastBStartBorder);
+ // store the border segment in the cell map
+ tableCellMap->SetBCBorderEdge(eLogicalSideBStart, *iter.mCellMap, 0, 0, colIdx,
+ 1, currentBorder.owner,
+ currentBorder.width, startSeg);
+
+ info.SetTableBStartBorderWidth(currentBorder.width);
+ info.SetBStartBorderWidths(currentBorder.width);
+ info.SetColumnBStartIEndContBCBorder();
+ }
+ info.SetTableBStartIStartContBCBorder();
+ }
+ else {
+ // see if the bStart border needs to be the start of a segment due to a
+ // block-dir border owning the corner
+ if (info.mColIndex > 0) {
+ BCData& data = info.mCellData->mData;
+ if (!data.IsBStartStart()) {
+ LogicalSide cornerSide;
+ bool bevel;
+ data.GetCorner(cornerSide, bevel);
+ if (IsBlock(cornerSide)) {
+ data.SetBStartStart(true);
+ }
+ }
+ }
+ }
+
+ // find the dominant border considering the cell's iStart border and the
+ // table, col group, col if the border is at the iStart of the table,
+ // otherwise it was processed in a previous col
+ if (0 == info.mColIndex) {
+ if (!tableBorderReset[eLogicalSideIStart]) {
+ propData->mIStartBorderWidth = 0;
+ tableBorderReset[eLogicalSideIStart] = true;
+ }
+ info.mCurrentRowFrame = nullptr;
+ for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
+ rowB++) {
+ info.IncrementRow(rowB == info.mRowIndex);
+ currentBorder = info.GetIStartEdgeBorder();
+ BCCornerInfo& tlCorner = (0 == rowB) ? bStartCorners[0] : bEndCorners[0];
+ tlCorner.Update(eLogicalSideBEnd, currentBorder);
+ tableCellMap->SetBCBorderCorner(eBStartIStart, *iter.mCellMap,
+ iter.mRowGroupStart, rowB, 0,
+ LogicalSide(tlCorner.ownerSide),
+ tlCorner.subWidth,
+ tlCorner.bevel);
+ bEndCorners[0].Set(eLogicalSideBStart, currentBorder); // bEnd-iStart
+
+ // update lastBlockDirBorders and see if a new segment starts
+ startSeg = SetBorder(currentBorder, lastBlockDirBorders[0]);
+ // store the border segment in the cell map
+ tableCellMap->SetBCBorderEdge(eLogicalSideIStart, *iter.mCellMap,
+ iter.mRowGroupStart, rowB, info.mColIndex,
+ 1, currentBorder.owner,
+ currentBorder.width, startSeg);
+ info.SetTableIStartBorderWidth(rowB , currentBorder.width);
+ info.SetIStartBorderWidths(currentBorder.width);
+ info.SetRowIStartContBCBorder();
+ }
+ info.SetRowGroupIStartContBCBorder();
+ }
+
+ // find the dominant border considering the cell's iEnd border, adjacent
+ // cells and the table, row group, row
+ if (info.mNumTableCols == info.GetCellEndColIndex() + 1) {
+ // touches iEnd edge of table
+ if (!tableBorderReset[eLogicalSideIEnd]) {
+ propData->mIEndBorderWidth = 0;
+ tableBorderReset[eLogicalSideIEnd] = true;
+ }
+ info.mCurrentRowFrame = nullptr;
+ for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
+ rowB++) {
+ info.IncrementRow(rowB == info.mRowIndex);
+ currentBorder = info.GetIEndEdgeBorder();
+ // update/store the bStart-iEnd & bEnd-iEnd corners
+ BCCornerInfo& trCorner = (0 == rowB) ?
+ bStartCorners[info.GetCellEndColIndex() + 1] :
+ bEndCorners[info.GetCellEndColIndex() + 1];
+ trCorner.Update(eLogicalSideBEnd, currentBorder); // bStart-iEnd
+ tableCellMap->SetBCBorderCorner(eBStartIEnd, *iter.mCellMap,
+ iter.mRowGroupStart, rowB,
+ info.GetCellEndColIndex(),
+ LogicalSide(trCorner.ownerSide),
+ trCorner.subWidth,
+ trCorner.bevel);
+ BCCornerInfo& brCorner = bEndCorners[info.GetCellEndColIndex() + 1];
+ brCorner.Set(eLogicalSideBStart, currentBorder); // bEnd-iEnd
+ tableCellMap->SetBCBorderCorner(eBEndIEnd, *iter.mCellMap,
+ iter.mRowGroupStart, rowB,
+ info.GetCellEndColIndex(),
+ LogicalSide(brCorner.ownerSide),
+ brCorner.subWidth,
+ brCorner.bevel);
+ // update lastBlockDirBorders and see if a new segment starts
+ startSeg = SetBorder(currentBorder,
+ lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
+ // store the border segment in the cell map and update cellBorders
+ tableCellMap->SetBCBorderEdge(eLogicalSideIEnd, *iter.mCellMap,
+ iter.mRowGroupStart, rowB,
+ info.GetCellEndColIndex(), 1,
+ currentBorder.owner, currentBorder.width,
+ startSeg);
+ info.SetTableIEndBorderWidth(rowB, currentBorder.width);
+ info.SetIEndBorderWidths(currentBorder.width);
+ info.SetRowIEndContBCBorder();
+ }
+ info.SetRowGroupIEndContBCBorder();
+ }
+ else {
+ int32_t segLength = 0;
+ BCMapCellInfo priorAjaInfo(this);
+ for (int32_t rowB = info.mRowIndex; rowB <= info.GetCellEndRowIndex();
+ rowB += segLength) {
+ iter.PeekIEnd(info, rowB, ajaInfo);
+ currentBorder = info.GetIEndInternalBorder();
+ adjacentBorder = ajaInfo.GetIStartInternalBorder();
+ currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
+ adjacentBorder, !INLINE_DIR);
+
+ segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowB);
+ segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowB);
+
+ // update lastBlockDirBorders and see if a new segment starts
+ startSeg = SetBorder(currentBorder,
+ lastBlockDirBorders[info.GetCellEndColIndex() + 1]);
+ // store the border segment in the cell map and update cellBorders
+ if (info.GetCellEndColIndex() < damageArea.EndCol() &&
+ rowB >= damageArea.StartRow() && rowB < damageArea.EndRow()) {
+ tableCellMap->SetBCBorderEdge(eLogicalSideIEnd, *iter.mCellMap,
+ iter.mRowGroupStart, rowB,
+ info.GetCellEndColIndex(), segLength,
+ currentBorder.owner,
+ currentBorder.width, startSeg);
+ info.SetIEndBorderWidths(currentBorder.width);
+ ajaInfo.SetIStartBorderWidths(currentBorder.width);
+ }
+ // update the bStart-iEnd corner
+ bool hitsSpanOnIEnd = (rowB > ajaInfo.mRowIndex) &&
+ (rowB < ajaInfo.mRowIndex + ajaInfo.mRowSpan);
+ BCCornerInfo* trCorner = ((0 == rowB) || hitsSpanOnIEnd) ?
+ &bStartCorners[info.GetCellEndColIndex() + 1] :
+ &bEndCorners[info.GetCellEndColIndex() + 1];
+ trCorner->Update(eLogicalSideBEnd, currentBorder);
+ // if this is not the first time through,
+ // consider the segment to the iEnd side
+ if (rowB != info.mRowIndex) {
+ currentBorder = priorAjaInfo.GetBEndInternalBorder();
+ adjacentBorder = ajaInfo.GetBStartInternalBorder();
+ currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
+ adjacentBorder, INLINE_DIR);
+ trCorner->Update(eLogicalSideIEnd, currentBorder);
+ }
+ // store the bStart-iEnd corner in the cell map
+ if (info.GetCellEndColIndex() < damageArea.EndCol() &&
+ rowB >= damageArea.StartRow()) {
+ if (0 != rowB) {
+ tableCellMap->SetBCBorderCorner(eBStartIEnd, *iter.mCellMap,
+ iter.mRowGroupStart, rowB,
+ info.GetCellEndColIndex(),
+ LogicalSide(trCorner->ownerSide),
+ trCorner->subWidth,
+ trCorner->bevel);
+ }
+ // store any corners this cell spans together with the aja cell
+ for (int32_t rX = rowB + 1; rX < rowB + segLength; rX++) {
+ tableCellMap->SetBCBorderCorner(eBEndIEnd, *iter.mCellMap,
+ iter.mRowGroupStart, rX,
+ info.GetCellEndColIndex(),
+ LogicalSide(trCorner->ownerSide),
+ trCorner->subWidth, false);
+ }
+ }
+ // update bEnd-iEnd corner, bStartCorners, bEndCorners
+ hitsSpanOnIEnd = (rowB + segLength <
+ ajaInfo.mRowIndex + ajaInfo.mRowSpan);
+ BCCornerInfo& brCorner = (hitsSpanOnIEnd) ?
+ bStartCorners[info.GetCellEndColIndex() + 1] :
+ bEndCorners[info.GetCellEndColIndex() + 1];
+ brCorner.Set(eLogicalSideBStart, currentBorder);
+ priorAjaInfo = ajaInfo;
+ }
+ }
+ for (int32_t colIdx = info.mColIndex + 1;
+ colIdx <= info.GetCellEndColIndex(); colIdx++) {
+ lastBlockDirBorders[colIdx].Reset(0,1);
+ }
+
+ // find the dominant border considering the cell's bEnd border, adjacent
+ // cells and the table, row group, row
+ if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) {
+ // touches bEnd edge of table
+ if (!tableBorderReset[eLogicalSideBEnd]) {
+ propData->mBEndBorderWidth = 0;
+ tableBorderReset[eLogicalSideBEnd] = true;
+ }
+ for (int32_t colIdx = info.mColIndex;
+ colIdx <= info.GetCellEndColIndex(); colIdx++) {
+ info.SetColumn(colIdx);
+ currentBorder = info.GetBEndEdgeBorder();
+ // update/store the bEnd-iStart & bEnd-IEnd corners
+ BCCornerInfo& blCorner = bEndCorners[colIdx]; // bEnd-iStart
+ blCorner.Update(eLogicalSideIEnd, currentBorder);
+ tableCellMap->SetBCBorderCorner(eBEndIStart, *iter.mCellMap,
+ iter.mRowGroupStart,
+ info.GetCellEndRowIndex(),
+ colIdx,
+ LogicalSide(blCorner.ownerSide),
+ blCorner.subWidth, blCorner.bevel);
+ BCCornerInfo& brCorner = bEndCorners[colIdx + 1]; // bEnd-iEnd
+ brCorner.Update(eLogicalSideIStart, currentBorder);
+ if (info.mNumTableCols == colIdx + 1) { // bEnd-IEnd corner of the table
+ tableCellMap->SetBCBorderCorner(eBEndIEnd, *iter.mCellMap,
+ iter.mRowGroupStart,
+ info.GetCellEndRowIndex(), colIdx,
+ LogicalSide(brCorner.ownerSide),
+ brCorner.subWidth,
+ brCorner.bevel, true);
+ }
+ // update lastBEndBorder and see if a new segment starts
+ startSeg = SetInlineDirBorder(currentBorder, blCorner, lastBEndBorder);
+ if (!startSeg) {
+ // make sure that we did not compare apples to oranges i.e. the
+ // current border should be a continuation of the lastBEndBorder,
+ // as it is a bEnd border
+ // add 1 to the info.GetCellEndRowIndex()
+ startSeg = (lastBEndBorder.rowIndex !=
+ (info.GetCellEndRowIndex() + 1));
+ }
+ // store the border segment in the cell map and update cellBorders
+ tableCellMap->SetBCBorderEdge(eLogicalSideBEnd, *iter.mCellMap,
+ iter.mRowGroupStart,
+ info.GetCellEndRowIndex(),
+ colIdx, 1, currentBorder.owner,
+ currentBorder.width, startSeg);
+ // update lastBEndBorders
+ lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
+ lastBEndBorder.rowSpan = info.mRowSpan;
+ lastBEndBorders[colIdx] = lastBEndBorder;
+
+ info.SetBEndBorderWidths(currentBorder.width);
+ info.SetTableBEndBorderWidth(currentBorder.width);
+ info.SetColumnBEndContBCBorder();
+ }
+ info.SetRowGroupBEndContBCBorder();
+ info.SetColGroupBEndContBCBorder();
+ }
+ else {
+ int32_t segLength = 0;
+ for (int32_t colIdx = info.mColIndex;
+ colIdx <= info.GetCellEndColIndex(); colIdx += segLength) {
+ iter.PeekBEnd(info, colIdx, ajaInfo);
+ currentBorder = info.GetBEndInternalBorder();
+ adjacentBorder = ajaInfo.GetBStartInternalBorder();
+ currentBorder = CompareBorders(!CELL_CORNER, currentBorder,
+ adjacentBorder, INLINE_DIR);
+ segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colIdx);
+ segLength = std::min(segLength, info.mColIndex + info.mColSpan - colIdx);
+
+ // update, store the bEnd-iStart corner
+ BCCornerInfo& blCorner = bEndCorners[colIdx]; // bEnd-iStart
+ bool hitsSpanBelow = (colIdx > ajaInfo.mColIndex) &&
+ (colIdx < ajaInfo.mColIndex + ajaInfo.mColSpan);
+ bool update = true;
+ if (colIdx == info.mColIndex && colIdx > damageArea.StartCol()) {
+ int32_t prevRowIndex = lastBEndBorders[colIdx - 1].rowIndex;
+ if (prevRowIndex > info.GetCellEndRowIndex() + 1) {
+ // hits a rowspan on the iEnd side
+ update = false;
+ // the corner was taken care of during the cell on the iStart side
+ }
+ else if (prevRowIndex < info.GetCellEndRowIndex() + 1) {
+ // spans below the cell to the iStart side
+ bStartCorners[colIdx] = blCorner;
+ blCorner.Set(eLogicalSideIEnd, currentBorder);
+ update = false;
+ }
+ }
+ if (update) {
+ blCorner.Update(eLogicalSideIEnd, currentBorder);
+ }
+ if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
+ colIdx >= damageArea.StartCol()) {
+ if (hitsSpanBelow) {
+ tableCellMap->SetBCBorderCorner(eBEndIStart, *iter.mCellMap,
+ iter.mRowGroupStart,
+ info.GetCellEndRowIndex(), colIdx,
+ LogicalSide(blCorner.ownerSide),
+ blCorner.subWidth, blCorner.bevel);
+ }
+ // store any corners this cell spans together with the aja cell
+ for (int32_t c = colIdx + 1; c < colIdx + segLength; c++) {
+ BCCornerInfo& corner = bEndCorners[c];
+ corner.Set(eLogicalSideIEnd, currentBorder);
+ tableCellMap->SetBCBorderCorner(eBEndIStart, *iter.mCellMap,
+ iter.mRowGroupStart,
+ info.GetCellEndRowIndex(), c,
+ LogicalSide(corner.ownerSide),
+ corner.subWidth,
+ false);
+ }
+ }
+ // update lastBEndBorders and see if a new segment starts
+ startSeg = SetInlineDirBorder(currentBorder, blCorner, lastBEndBorder);
+ if (!startSeg) {
+ // make sure that we did not compare apples to oranges i.e. the
+ // current border should be a continuation of the lastBEndBorder,
+ // as it is a bEnd border
+ // add 1 to the info.GetCellEndRowIndex()
+ startSeg = (lastBEndBorder.rowIndex !=
+ info.GetCellEndRowIndex() + 1);
+ }
+ lastBEndBorder.rowIndex = info.GetCellEndRowIndex() + 1;
+ lastBEndBorder.rowSpan = info.mRowSpan;
+ for (int32_t c = colIdx; c < colIdx + segLength; c++) {
+ lastBEndBorders[c] = lastBEndBorder;
+ }
+
+ // store the border segment the cell map and update cellBorders
+ if (info.GetCellEndRowIndex() < damageArea.EndRow() &&
+ colIdx >= damageArea.StartCol() && colIdx < damageArea.EndCol()) {
+ tableCellMap->SetBCBorderEdge(eLogicalSideBEnd, *iter.mCellMap,
+ iter.mRowGroupStart,
+ info.GetCellEndRowIndex(),
+ colIdx, segLength, currentBorder.owner,
+ currentBorder.width, startSeg);
+ info.SetBEndBorderWidths(currentBorder.width);
+ ajaInfo.SetBStartBorderWidths(currentBorder.width);
+ }
+ // update bEnd-iEnd corner
+ BCCornerInfo& brCorner = bEndCorners[colIdx + segLength];
+ brCorner.Update(eLogicalSideIStart, currentBorder);
+ }
+ if (!gotRowBorder && 1 == info.mRowSpan &&
+ (ajaInfo.mStartRow || info.mRgAtEnd)) {
+ //get continuous row/row group border
+ //we need to check the row group's bEnd border if this is
+ //the last row in the row group, but only a cell with rowspan=1
+ //will know whether *this* row is at the bEnd
+ const nsIFrame* nextRowGroup =
+ ajaInfo.mRgAtStart ? ajaInfo.mRowGroup : nullptr;
+ info.SetInnerRowGroupBEndContBCBorder(nextRowGroup, ajaInfo.mStartRow);
+ gotRowBorder = true;
+ }
+ }
+
+ // see if the cell to the iEnd side had a rowspan and its bEnd-iStart border
+ // needs be joined with this one's bEnd
+ // if there is a cell to the iEnd and the cell to iEnd side was a rowspan
+ if ((info.mNumTableCols != info.GetCellEndColIndex() + 1) &&
+ (lastBEndBorders[info.GetCellEndColIndex() + 1].rowSpan > 1)) {
+ BCCornerInfo& corner = bEndCorners[info.GetCellEndColIndex() + 1];
+ if (!IsBlock(LogicalSide(corner.ownerSide))) {
+ // not a block-dir owner
+ BCCellBorder& thisBorder = lastBEndBorder;
+ BCCellBorder& nextBorder = lastBEndBorders[info.mColIndex + 1];
+ if ((thisBorder.color == nextBorder.color) &&
+ (thisBorder.width == nextBorder.width) &&
+ (thisBorder.style == nextBorder.style)) {
+ // set the flag on the next border indicating it is not the start of a
+ // new segment
+ if (iter.mCellMap) {
+ tableCellMap->ResetBStartStart(eLogicalSideBEnd, *iter.mCellMap,
+ info.GetCellEndRowIndex(),
+ info.GetCellEndColIndex() + 1);
+ }
+ }
+ }
+ }
+ } // for (iter.First(info); info.mCell; iter.Next(info)) {
+ // reset the bc flag and damage area
+ SetNeedToCalcBCBorders(false);
+ propData->mDamageArea = TableArea(0, 0, 0, 0);
+#ifdef DEBUG_TABLE_CELLMAP
+ mCellMap->Dump();
+#endif
+}
+
+class BCPaintBorderIterator;
+
+struct BCBlockDirSeg
+{
+ BCBlockDirSeg();
+
+ void Start(BCPaintBorderIterator& aIter,
+ BCBorderOwner aBorderOwner,
+ BCPixelSize aBlockSegISize,
+ BCPixelSize aInlineSegBSize);
+
+ void Initialize(BCPaintBorderIterator& aIter);
+ void GetBEndCorner(BCPaintBorderIterator& aIter,
+ BCPixelSize aInlineSegBSize);
+
+
+ void Paint(BCPaintBorderIterator& aIter,
+ DrawTarget& aDrawTarget,
+ BCPixelSize aInlineSegBSize);
+ void AdvanceOffsetB();
+ void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
+
+
+ union {
+ nsTableColFrame* mCol;
+ int32_t mColWidth;
+ };
+ nscoord mOffsetI; // i-offset with respect to the table edge
+ nscoord mOffsetB; // b-offset with respect to the table edge
+ nscoord mLength; // block-dir length including corners
+ BCPixelSize mWidth; // thickness in pixels
+
+ nsTableCellFrame* mAjaCell; // previous sibling to the first cell
+ // where the segment starts, it can be
+ // the owner of a segment
+ nsTableCellFrame* mFirstCell; // cell at the start of the segment
+ nsTableRowGroupFrame* mFirstRowGroup; // row group at the start of the segment
+ nsTableRowFrame* mFirstRow; // row at the start of the segment
+ nsTableCellFrame* mLastCell; // cell at the current end of the
+ // segment
+
+
+ uint8_t mOwner; // owner of the border, defines the
+ // style
+ LogicalSide mBStartBevelSide; // direction to bevel at the bStart
+ nscoord mBStartBevelOffset; // how much to bevel at the bStart
+ BCPixelSize mBEndInlineSegBSize; // bSize of the crossing
+ // inline-dir border
+ nscoord mBEndOffset; // how much longer is the segment due
+ // to the inline-dir border, by this
+ // amount the next segment needs to be
+ // shifted.
+ bool mIsBEndBevel; // should we bevel at the bEnd
+};
+
+struct BCInlineDirSeg
+{
+ BCInlineDirSeg();
+
+ void Start(BCPaintBorderIterator& aIter,
+ BCBorderOwner aBorderOwner,
+ BCPixelSize aBEndBlockSegISize,
+ BCPixelSize aInlineSegBSize);
+ void GetIEndCorner(BCPaintBorderIterator& aIter,
+ BCPixelSize aIStartSegISize);
+ void AdvanceOffsetI();
+ void IncludeCurrentBorder(BCPaintBorderIterator& aIter);
+ void Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget);
+
+ nscoord mOffsetI; // i-offset with respect to the table edge
+ nscoord mOffsetB; // b-offset with respect to the table edge
+ nscoord mLength; // inline-dir length including corners
+ BCPixelSize mWidth; // border thickness in pixels
+ nscoord mIStartBevelOffset; // how much to bevel at the iStart
+ LogicalSide mIStartBevelSide; // direction to bevel at the iStart
+ bool mIsIEndBevel; // should we bevel at the iEnd end
+ nscoord mIEndBevelOffset; // how much to bevel at the iEnd
+ LogicalSide mIEndBevelSide; // direction to bevel at the iEnd
+ nscoord mEndOffset; // how much longer is the segment due
+ // to the block-dir border, by this
+ // amount the next segment needs to be
+ // shifted.
+ uint8_t mOwner; // owner of the border, defines the
+ // style
+ nsTableCellFrame* mFirstCell; // cell at the start of the segment
+ nsTableCellFrame* mAjaCell; // neighboring cell to the first cell
+ // where the segment starts, it can be
+ // the owner of a segment
+};
+
+// Iterates over borders (iStart border, corner, bStart border) in the cell map within a damage area
+// from iStart to iEnd, bStart to bEnd. All members are in terms of the 1st in flow frames, except
+// where suffixed by InFlow.
+class BCPaintBorderIterator
+{
+public:
+ explicit BCPaintBorderIterator(nsTableFrame* aTable);
+ ~BCPaintBorderIterator() { if (mBlockDirInfo) {
+ delete [] mBlockDirInfo;
+ }}
+ void Reset();
+
+ /**
+ * Determine the damage area in terms of rows and columns and finalize
+ * mInitialOffsetI and mInitialOffsetB.
+ * @param aDirtyRect - dirty rect in table coordinates
+ * @return - true if we need to paint something given dirty rect
+ */
+ bool SetDamageArea(const nsRect& aDamageRect);
+ void First();
+ void Next();
+ void AccumulateOrPaintInlineDirSegment(DrawTarget& aDrawTarget);
+ void AccumulateOrPaintBlockDirSegment(DrawTarget& aDrawTarget);
+ void ResetVerInfo();
+ void StoreColumnWidth(int32_t aIndex);
+ bool BlockDirSegmentOwnsCorner();
+
+ nsTableFrame* mTable;
+ nsTableFrame* mTableFirstInFlow;
+ nsTableCellMap* mTableCellMap;
+ nsCellMap* mCellMap;
+ WritingMode mTableWM;
+ const nsStyleBackground* mTableBgColor;
+ nsTableFrame::RowGroupArray mRowGroups;
+
+ nsTableRowGroupFrame* mPrevRg;
+ nsTableRowGroupFrame* mRg;
+ bool mIsRepeatedHeader;
+ bool mIsRepeatedFooter;
+ nsTableRowGroupFrame* mStartRg; // first row group in the damagearea
+ int32_t mRgIndex; // current row group index in the
+ // mRowgroups array
+ int32_t mFifRgFirstRowIndex; // start row index of the first in
+ // flow of the row group
+ int32_t mRgFirstRowIndex; // row index of the first row in the
+ // row group
+ int32_t mRgLastRowIndex; // row index of the last row in the row
+ // group
+ int32_t mNumTableRows; // number of rows in the table and all
+ // continuations
+ int32_t mNumTableCols; // number of columns in the table
+ int32_t mColIndex; // with respect to the table
+ int32_t mRowIndex; // with respect to the table
+ int32_t mRepeatedHeaderRowIndex; // row index in a repeated
+ //header, it's equivalent to
+ // mRowIndex when we're in a repeated
+ // header, and set to the last row
+ // index of a repeated header when
+ // we're not
+ bool mIsNewRow;
+ bool mAtEnd; // the iterator cycled over all
+ // borders
+ nsTableRowFrame* mPrevRow;
+ nsTableRowFrame* mRow;
+ nsTableRowFrame* mStartRow; //first row in a inside the damagearea
+
+
+ // cell properties
+ nsTableCellFrame* mPrevCell;
+ nsTableCellFrame* mCell;
+ BCCellData* mPrevCellData;
+ BCCellData* mCellData;
+ BCData* mBCData;
+
+ bool IsTableBStartMost() {return (mRowIndex == 0) && !mTable->GetPrevInFlow();}
+ bool IsTableIEndMost() {return (mColIndex >= mNumTableCols);}
+ bool IsTableBEndMost() {return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow();}
+ bool IsTableIStartMost() {return (mColIndex == 0);}
+ bool IsDamageAreaBStartMost() const
+ { return mRowIndex == mDamageArea.StartRow(); }
+ bool IsDamageAreaIEndMost() const
+ { return mColIndex >= mDamageArea.EndCol(); }
+ bool IsDamageAreaBEndMost() const
+ { return mRowIndex >= mDamageArea.EndRow(); }
+ bool IsDamageAreaIStartMost() const
+ { return mColIndex == mDamageArea.StartCol(); }
+ int32_t GetRelativeColIndex() const
+ { return mColIndex - mDamageArea.StartCol(); }
+
+ TableArea mDamageArea; // damageArea in cellmap coordinates
+ bool IsAfterRepeatedHeader()
+ { return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1)); }
+ bool StartRepeatedFooter() const
+ {
+ return mIsRepeatedFooter && mRowIndex == mRgFirstRowIndex &&
+ mRowIndex != mDamageArea.StartRow();
+ }
+
+ nscoord mInitialOffsetI; // offsetI of the first border with
+ // respect to the table
+ nscoord mInitialOffsetB; // offsetB of the first border with
+ // respect to the table
+ nscoord mNextOffsetB; // offsetB of the next segment
+ BCBlockDirSeg* mBlockDirInfo; // this array is used differently when
+ // inline-dir and block-dir borders are drawn
+ // When inline-dir border are drawn we cache
+ // the column widths and the width of the
+ // block-dir borders that arrive from bStart
+ // When we draw block-dir borders we store
+ // lengths and width for block-dir borders
+ // before they are drawn while we move over
+ // the columns in the damage area
+ // It has one more elements than columns are
+ // in the table.
+ BCInlineDirSeg mInlineSeg; // the inline-dir segment while we
+ // move over the colums
+ BCPixelSize mPrevInlineSegBSize; // the bSize of the previous
+ // inline-dir border
+
+private:
+
+ bool SetNewRow(nsTableRowFrame* aRow = nullptr);
+ bool SetNewRowGroup();
+ void SetNewData(int32_t aRowIndex, int32_t aColIndex);
+
+};
+
+
+
+BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable)
+ : mTable(aTable)
+ , mTableFirstInFlow(static_cast<nsTableFrame*>(aTable->FirstInFlow()))
+ , mTableCellMap(aTable->GetCellMap())
+ , mTableWM(aTable->StyleContext())
+{
+ mBlockDirInfo = nullptr;
+ LogicalMargin childAreaOffset = mTable->GetChildAreaOffset(mTableWM, nullptr);
+ // y position of first row in damage area
+ mInitialOffsetB =
+ mTable->GetPrevInFlow() ? 0 : childAreaOffset.BStart(mTableWM);
+ mNumTableRows = mTable->GetRowCount();
+ mNumTableCols = mTable->GetColCount();
+
+ // Get the ordered row groups
+ mTable->OrderRowGroups(mRowGroups);
+ // initialize to a non existing index
+ mRepeatedHeaderRowIndex = -99;
+
+ nsIFrame* bgFrame =
+ nsCSSRendering::FindNonTransparentBackgroundFrame(aTable);
+ mTableBgColor = bgFrame->StyleBackground();
+}
+
+bool
+BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect)
+{
+ nsSize containerSize = mTable->GetSize();
+ LogicalRect dirtyRect(mTableWM, aDirtyRect, containerSize);
+ uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex;
+ startRowIndex = endRowIndex = startColIndex = endColIndex = 0;
+ bool done = false;
+ bool haveIntersect = false;
+ // find startRowIndex, endRowIndex
+ nscoord rowB = mInitialOffsetB;
+ for (uint32_t rgIdx = 0; rgIdx < mRowGroups.Length() && !done; rgIdx++) {
+ nsTableRowGroupFrame* rgFrame = mRowGroups[rgIdx];
+ for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame;
+ rowFrame = rowFrame->GetNextRow()) {
+ // get the row rect relative to the table rather than the row group
+ nscoord rowBSize = rowFrame->BSize(mTableWM);
+ if (haveIntersect) {
+ // conservatively estimate the half border widths outside the row
+ nscoord borderHalf = mTable->GetPrevInFlow() ? 0 : nsPresContext::
+ CSSPixelsToAppUnits(rowFrame->GetBStartBCBorderWidth() + 1);
+ if (dirtyRect.BEnd(mTableWM) >= rowB - borderHalf) {
+ nsTableRowFrame* fifRow =
+ static_cast<nsTableRowFrame*>(rowFrame->FirstInFlow());
+ endRowIndex = fifRow->GetRowIndex();
+ }
+ else done = true;
+ }
+ else {
+ // conservatively estimate the half border widths outside the row
+ nscoord borderHalf = mTable->GetNextInFlow() ? 0 : nsPresContext::
+ CSSPixelsToAppUnits(rowFrame->GetBEndBCBorderWidth() + 1);
+ if (rowB + rowBSize + borderHalf >= dirtyRect.BStart(mTableWM)) {
+ mStartRg = rgFrame;
+ mStartRow = rowFrame;
+ nsTableRowFrame* fifRow =
+ static_cast<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 childAreaOffset = mTable->GetChildAreaOffset(mTableWM, nullptr);
+
+ // inline position of first col in damage area
+ mInitialOffsetI = childAreaOffset.IStart(mTableWM);
+
+ nscoord x = 0;
+ int32_t colIdx;
+ for (colIdx = 0; colIdx != mNumTableCols; colIdx++) {
+ nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colIdx);
+ if (!colFrame) ABORT1(false);
+ // get the col rect relative to the table rather than the col group
+ nscoord colISize = colFrame->ISize(mTableWM);
+ if (haveIntersect) {
+ // conservatively estimate the iStart half border width outside the col
+ nscoord iStartBorderHalf = nsPresContext::
+ CSSPixelsToAppUnits(colFrame->GetIStartBorderWidth() + 1);
+ if (dirtyRect.IEnd(mTableWM) >= x - iStartBorderHalf) {
+ endColIndex = colIdx;
+ }
+ else break;
+ }
+ else {
+ // conservatively estimate the iEnd half border width outside the col
+ nscoord iEndBorderHalf = nsPresContext::
+ CSSPixelsToAppUnits(colFrame->GetIEndBorderWidth() + 1);
+ if (x + colISize + iEndBorderHalf >= dirtyRect.IStart(mTableWM)) {
+ startColIndex = endColIndex = colIdx;
+ haveIntersect = true;
+ }
+ else {
+ mInitialOffsetI += colISize;
+ }
+ }
+ x += colISize;
+ }
+ if (!haveIntersect)
+ return false;
+ mDamageArea = TableArea(startColIndex, startRowIndex,
+ 1 + DeprecatedAbs<int32_t>(endColIndex - startColIndex),
+ 1 + endRowIndex - startRowIndex);
+
+ Reset();
+ mBlockDirInfo = new BCBlockDirSeg[mDamageArea.ColCount() + 1];
+ if (!mBlockDirInfo)
+ return false;
+ return true;
+}
+
+void
+BCPaintBorderIterator::Reset()
+{
+ mAtEnd = true; // gets reset when First() is called
+ mRg = mStartRg;
+ mPrevRow = nullptr;
+ mRow = mStartRow;
+ mRowIndex = 0;
+ mColIndex = 0;
+ mRgIndex = -1;
+ mPrevCell = nullptr;
+ mCell = nullptr;
+ mPrevCellData = nullptr;
+ mCellData = nullptr;
+ mBCData = nullptr;
+ ResetVerInfo();
+}
+
+/**
+ * Set the iterator data to a new cellmap coordinate
+ * @param aRowIndex - the row index
+ * @param aColIndex - the col index
+ */
+void
+BCPaintBorderIterator::SetNewData(int32_t aY,
+ int32_t aX)
+{
+ if (!mTableCellMap || !mTableCellMap->mBCInfo) ABORT0();
+
+ mColIndex = aX;
+ mRowIndex = aY;
+ mPrevCellData = mCellData;
+ if (IsTableIEndMost() && IsTableBEndMost()) {
+ mCell = nullptr;
+ mBCData = &mTableCellMap->mBCInfo->mBEndIEndCorner;
+ }
+ else if (IsTableIEndMost()) {
+ mCellData = nullptr;
+ mBCData = &mTableCellMap->mBCInfo->mIEndBorders.ElementAt(aY);
+ }
+ else if (IsTableBEndMost()) {
+ mCellData = nullptr;
+ mBCData = &mTableCellMap->mBCInfo->mBEndBorders.ElementAt(aX);
+ }
+ else {
+ if (uint32_t(mRowIndex - mFifRgFirstRowIndex) < mCellMap->mRows.Length()) {
+ mBCData = nullptr;
+ mCellData =
+ (BCCellData*)mCellMap->mRows[mRowIndex - mFifRgFirstRowIndex].SafeElementAt(mColIndex);
+ if (mCellData) {
+ mBCData = &mCellData->mData;
+ if (!mCellData->IsOrig()) {
+ if (mCellData->IsRowSpan()) {
+ aY -= mCellData->GetRowSpanOffset();
+ }
+ if (mCellData->IsColSpan()) {
+ aX -= mCellData->GetColSpanOffset();
+ }
+ if ((aX >= 0) && (aY >= 0)) {
+ mCellData = (BCCellData*)mCellMap->mRows[aY - mFifRgFirstRowIndex][aX];
+ }
+ }
+ if (mCellData->IsOrig()) {
+ mPrevCell = mCell;
+ mCell = mCellData->GetCellFrame();
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Set the iterator to a new row
+ * @param aRow - the new row frame, if null the iterator will advance to the
+ * next row
+ */
+bool
+BCPaintBorderIterator::SetNewRow(nsTableRowFrame* aRow)
+{
+ mPrevRow = mRow;
+ mRow = (aRow) ? aRow : mRow->GetNextRow();
+ if (mRow) {
+ mIsNewRow = true;
+ mRowIndex = mRow->GetRowIndex();
+ mColIndex = mDamageArea.StartCol();
+ mPrevInlineSegBSize = 0;
+ if (mIsRepeatedHeader) {
+ mRepeatedHeaderRowIndex = mRowIndex;
+ }
+ }
+ else {
+ mAtEnd = true;
+ }
+ return !mAtEnd;
+}
+
+/**
+ * Advance the iterator to the next row group
+ */
+bool
+BCPaintBorderIterator::SetNewRowGroup()
+{
+
+ mRgIndex++;
+
+ mIsRepeatedHeader = false;
+ mIsRepeatedFooter = false;
+
+ NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds");
+ if (uint32_t(mRgIndex) < mRowGroups.Length()) {
+ mPrevRg = mRg;
+ mRg = mRowGroups[mRgIndex];
+ nsTableRowGroupFrame* fifRg =
+ static_cast<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 (mRg && mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) {
+ // if mRowGroup doesn't have a prev in flow, then it may be a repeated
+ // header or footer
+ const nsStyleDisplay* display = mRg->StyleDisplay();
+ if (mRowIndex == mDamageArea.StartRow()) {
+ mIsRepeatedHeader = (mozilla::StyleDisplay::TableHeaderGroup == display->mDisplay);
+ } else {
+ mIsRepeatedFooter = (mozilla::StyleDisplay::TableFooterGroup == display->mDisplay);
+ }
+ }
+ }
+ else {
+ mAtEnd = true;
+ }
+ return !mAtEnd;
+}
+
+/**
+ * Move the iterator to the first position in the damageArea
+ */
+void
+BCPaintBorderIterator::First()
+{
+ if (!mTable || mDamageArea.StartCol() >= mNumTableCols ||
+ mDamageArea.StartRow() >= mNumTableRows) ABORT0();
+
+ mAtEnd = false;
+
+ uint32_t numRowGroups = mRowGroups.Length();
+ for (uint32_t rgY = 0; rgY < numRowGroups; rgY++) {
+ nsTableRowGroupFrame* rowG = mRowGroups[rgY];
+ int32_t start = rowG->GetStartRowIndex();
+ int32_t end = start + rowG->GetRowCount() - 1;
+ if (mDamageArea.StartRow() >= start && mDamageArea.StartRow() <= end) {
+ mRgIndex = rgY - 1; // SetNewRowGroup increments rowGroupIndex
+ if (SetNewRowGroup()) {
+ while (mRowIndex < mDamageArea.StartRow() && !mAtEnd) {
+ SetNewRow();
+ }
+ if (!mAtEnd) {
+ SetNewData(mDamageArea.StartRow(), mDamageArea.StartCol());
+ }
+ }
+ return;
+ }
+ }
+ mAtEnd = true;
+}
+
+/**
+ * Advance the iterator to the next position
+ */
+void
+BCPaintBorderIterator::Next()
+{
+ if (mAtEnd) ABORT0();
+ mIsNewRow = false;
+
+ mColIndex++;
+ if (mColIndex > mDamageArea.EndCol()) {
+ mRowIndex++;
+ if (mRowIndex == mDamageArea.EndRow()) {
+ mColIndex = mDamageArea.StartCol();
+ }
+ else if (mRowIndex < mDamageArea.EndRow()) {
+ if (mRowIndex <= mRgLastRowIndex) {
+ SetNewRow();
+ }
+ else {
+ SetNewRowGroup();
+ }
+ }
+ else {
+ mAtEnd = true;
+ }
+ }
+ if (!mAtEnd) {
+ SetNewData(mRowIndex, mColIndex);
+ }
+}
+
+// XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine
+// them
+// XXX Update terminology from physical to logical
+/** Compute the vertical offset of a vertical border segment
+ * @param aCornerOwnerSide - which side owns the corner
+ * @param aCornerSubWidth - how wide is the nonwinning side of the corner
+ * @param aHorWidth - how wide is the horizontal edge of the corner
+ * @param aIsStartOfSeg - does this corner start a new segment
+ * @param aIsBevel - is this corner beveled
+ * @return - offset in twips
+ */
+static nscoord
+CalcVerCornerOffset(LogicalSide aCornerOwnerSide,
+ BCPixelSize aCornerSubWidth,
+ BCPixelSize aHorWidth,
+ bool aIsStartOfSeg,
+ bool aIsBevel)
+{
+ nscoord offset = 0;
+ // XXX These should be replaced with appropriate side-specific macros (which?)
+ BCPixelSize smallHalf, largeHalf;
+ if (IsBlock(aCornerOwnerSide)) {
+ DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
+ if (aIsBevel) {
+ offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
+ }
+ else {
+ offset = (eLogicalSideBStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
+ }
+ }
+ else {
+ DivideBCBorderSize(aHorWidth, smallHalf, largeHalf);
+ if (aIsBevel) {
+ offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
+ }
+ else {
+ offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
+ }
+ }
+ return nsPresContext::CSSPixelsToAppUnits(offset);
+}
+
+/** Compute the horizontal offset of a horizontal border segment
+ * @param aCornerOwnerSide - which side owns the corner
+ * @param aCornerSubWidth - how wide is the nonwinning side of the corner
+ * @param aVerWidth - how wide is the vertical edge of the corner
+ * @param aIsStartOfSeg - does this corner start a new segment
+ * @param aIsBevel - is this corner beveled
+ * @return - offset in twips
+ */
+static nscoord
+CalcHorCornerOffset(LogicalSide aCornerOwnerSide,
+ BCPixelSize aCornerSubWidth,
+ BCPixelSize aVerWidth,
+ bool aIsStartOfSeg,
+ bool aIsBevel)
+{
+ nscoord offset = 0;
+ // XXX These should be replaced with appropriate side-specific macros (which?)
+ BCPixelSize smallHalf, largeHalf;
+ if (IsInline(aCornerOwnerSide)) {
+ DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf);
+ if (aIsBevel) {
+ offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
+ }
+ else {
+ offset = (eLogicalSideIStart == aCornerOwnerSide) ? smallHalf : -largeHalf;
+ }
+ }
+ else {
+ DivideBCBorderSize(aVerWidth, smallHalf, largeHalf);
+ if (aIsBevel) {
+ offset = (aIsStartOfSeg) ? -largeHalf : smallHalf;
+ }
+ else {
+ offset = (aIsStartOfSeg) ? smallHalf : -largeHalf;
+ }
+ }
+ return nsPresContext::CSSPixelsToAppUnits(offset);
+}
+
+BCBlockDirSeg::BCBlockDirSeg()
+{
+ mCol = nullptr;
+ mFirstCell = mLastCell = mAjaCell = nullptr;
+ mOffsetI = mOffsetB = mLength = mWidth = mBStartBevelOffset = 0;
+ mBStartBevelSide = eLogicalSideBStart;
+ mOwner = eCellOwner;
+}
+
+/**
+ * Start a new block-direction segment
+ * @param aIter - iterator containing the structural information
+ * @param aBorderOwner - determines the border style
+ * @param aBlockSegISize - the width of segment in pixel
+ * @param aInlineSegBSize - the width of the inline-dir segment joining the corner
+ * at the start
+ */
+void
+BCBlockDirSeg::Start(BCPaintBorderIterator& aIter,
+ BCBorderOwner aBorderOwner,
+ BCPixelSize aBlockSegISize,
+ BCPixelSize aInlineSegBSize)
+{
+ LogicalSide ownerSide = eLogicalSideBStart;
+ bool bevel = false;
+
+ nscoord cornerSubWidth = (aIter.mBCData) ?
+ aIter.mBCData->GetCorner(ownerSide, bevel) : 0;
+
+ bool bStartBevel = (aBlockSegISize > 0) ? bevel : false;
+ BCPixelSize maxInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
+ nscoord offset = CalcVerCornerOffset(ownerSide, cornerSubWidth,
+ maxInlineSegBSize, true,
+ bStartBevel);
+
+ mBStartBevelOffset = bStartBevel ?
+ nsPresContext::CSSPixelsToAppUnits(maxInlineSegBSize): 0;
+ // XXX this assumes that only corners where 2 segments join can be beveled
+ mBStartBevelSide = (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart;
+ mOffsetB += offset;
+ mLength = -offset;
+ mWidth = aBlockSegISize;
+ mOwner = aBorderOwner;
+ mFirstCell = aIter.mCell;
+ mFirstRowGroup = aIter.mRg;
+ mFirstRow = aIter.mRow;
+ if (aIter.GetRelativeColIndex() > 0) {
+ mAjaCell = aIter.mBlockDirInfo[aIter.GetRelativeColIndex() - 1].mLastCell;
+ }
+}
+
+/**
+ * Initialize the block-dir segments with information that will persist for any
+ * block-dir segment in this column
+ * @param aIter - iterator containing the structural information
+ */
+void
+BCBlockDirSeg::Initialize(BCPaintBorderIterator& aIter)
+{
+ int32_t relColIndex = aIter.GetRelativeColIndex();
+ mCol = aIter.IsTableIEndMost() ? aIter.mBlockDirInfo[relColIndex - 1].mCol :
+ aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex);
+ if (!mCol) ABORT0();
+ if (0 == relColIndex) {
+ mOffsetI = aIter.mInitialOffsetI;
+ }
+ // set mOffsetI for the next column
+ if (!aIter.IsDamageAreaIEndMost()) {
+ aIter.mBlockDirInfo[relColIndex + 1].mOffsetI =
+ mOffsetI + mCol->ISize(aIter.mTableWM);
+ }
+ mOffsetB = aIter.mInitialOffsetB;
+ mLastCell = aIter.mCell;
+}
+
+/**
+ * Compute the offsets for the bEnd corner of a block-dir segment
+ * @param aIter - iterator containing the structural information
+ * @param aInlineSegBSize - the width of the inline-dir segment joining the corner
+ * at the start
+ */
+void
+BCBlockDirSeg::GetBEndCorner(BCPaintBorderIterator& aIter,
+ BCPixelSize aInlineSegBSize)
+{
+ LogicalSide ownerSide = eLogicalSideBStart;
+ nscoord cornerSubWidth = 0;
+ bool bevel = false;
+ if (aIter.mBCData) {
+ cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
+ }
+ mIsBEndBevel = (mWidth > 0) ? bevel : false;
+ mBEndInlineSegBSize = std::max(aIter.mPrevInlineSegBSize, aInlineSegBSize);
+ mBEndOffset = CalcVerCornerOffset(ownerSide, cornerSubWidth,
+ mBEndInlineSegBSize,
+ false, mIsBEndBevel);
+ mLength += mBEndOffset;
+}
+
+/**
+ * Paint the block-dir segment
+ * @param aIter - iterator containing the structural information
+ * @param aDrawTarget - the draw target
+ * @param aInlineSegBSize - the width of the inline-dir segment joining the
+ * corner at the start
+ */
+void
+BCBlockDirSeg::Paint(BCPaintBorderIterator& aIter,
+ DrawTarget& aDrawTarget,
+ BCPixelSize aInlineSegBSize)
+{
+ // get the border style, color and paint the segment
+ LogicalSide side =
+ aIter.IsDamageAreaIEndMost() ? eLogicalSideIEnd : eLogicalSideIStart;
+ int32_t relColIndex = aIter.GetRelativeColIndex();
+ nsTableColFrame* col = mCol; if (!col) ABORT0();
+ nsTableCellFrame* cell = mFirstCell; // ???
+ nsIFrame* owner = nullptr;
+ uint8_t style = NS_STYLE_BORDER_STYLE_SOLID;
+ nscolor color = 0xFFFFFFFF;
+
+ // All the tables frames have the same presContext, so we just use any one
+ // that exists here:
+ int32_t appUnitsPerDevPixel = col->PresContext()->AppUnitsPerDevPixel();
+
+ switch (mOwner) {
+ case eTableOwner:
+ owner = aIter.mTable;
+ break;
+ case eAjaColGroupOwner:
+ side = eLogicalSideIEnd;
+ if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
+ col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
+ }
+ MOZ_FALLTHROUGH;
+ case eColGroupOwner:
+ if (col) {
+ owner = col->GetParent();
+ }
+ break;
+ case eAjaColOwner:
+ side = eLogicalSideIEnd;
+ if (!aIter.IsTableIEndMost() && (relColIndex > 0)) {
+ col = aIter.mBlockDirInfo[relColIndex - 1].mCol;
+ }
+ MOZ_FALLTHROUGH;
+ case eColOwner:
+ owner = col;
+ break;
+ case eAjaRowGroupOwner:
+ NS_ERROR("a neighboring rowgroup can never own a vertical border");
+ MOZ_FALLTHROUGH;
+ case eRowGroupOwner:
+ NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
+ "row group can own border only at table edge");
+ owner = mFirstRowGroup;
+ break;
+ case eAjaRowOwner:
+ NS_ERROR("program error");
+ MOZ_FALLTHROUGH;
+ case eRowOwner:
+ NS_ASSERTION(aIter.IsTableIStartMost() || aIter.IsTableIEndMost(),
+ "row can own border only at table edge");
+ owner = mFirstRow;
+ break;
+ case eAjaCellOwner:
+ side = eLogicalSideIEnd;
+ cell = mAjaCell;
+ MOZ_FALLTHROUGH;
+ case eCellOwner:
+ owner = cell;
+ break;
+ }
+ if (owner) {
+ ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &style, &color);
+ }
+ BCPixelSize smallHalf, largeHalf;
+ DivideBCBorderSize(mWidth, smallHalf, largeHalf);
+ LogicalRect segRect(aIter.mTableWM,
+ mOffsetI - nsPresContext::CSSPixelsToAppUnits(largeHalf),
+ mOffsetB,
+ nsPresContext::CSSPixelsToAppUnits(mWidth), mLength);
+ nscoord bEndBevelOffset = (mIsBEndBevel) ?
+ nsPresContext::CSSPixelsToAppUnits(mBEndInlineSegBSize) : 0;
+ LogicalSide bEndBevelSide =
+ (aInlineSegBSize > 0) ? eLogicalSideIEnd : eLogicalSideIStart;
+
+ // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
+
+ nsRect physicalRect = segRect.GetPhysicalRect(aIter.mTableWM,
+ aIter.mTable->GetSize());
+ // XXX For reversed vertical writing-modes (with direction:rtl), we need to
+ // invert physicalRect's y-position here, with respect to the table.
+ // However, it's not worth fixing the border positions here until the
+ // ordering of the table columns themselves is also fixed (bug 1180528).
+
+ uint8_t startBevelSide = aIter.mTableWM.PhysicalSide(mBStartBevelSide);
+ uint8_t endBevelSide = aIter.mTableWM.PhysicalSide(bEndBevelSide);
+ nscoord startBevelOffset = mBStartBevelOffset;
+ nscoord endBevelOffset = bEndBevelOffset;
+ // In vertical-rl mode, the 'start' and 'end' of the block-dir (horizontal)
+ // border segment need to be swapped because DrawTableBorderSegment will
+ // apply the 'start' bevel at the left edge, and 'end' at the right.
+ // (Note: In this case, startBevelSide/endBevelSide will usually both be
+ // "top" or "bottom". DrawTableBorderSegment works purely with physical
+ // coordinates, so it expects startBevelOffset to be the indentation-from-
+ // the-left for the "start" (left) end of the border-segment, and
+ // endBevelOffset is the indentation-from-the-right for the "end" (right)
+ // end of the border-segment. We've got them reversed, since our block dir
+ // is RTL, so we have to swap them here.)
+ if (aIter.mTableWM.IsVerticalRL()) {
+ Swap(startBevelSide, endBevelSide);
+ Swap(startBevelOffset, endBevelOffset);
+ }
+ nsCSSRendering::DrawTableBorderSegment(aDrawTarget, style, color,
+ aIter.mTableBgColor, physicalRect,
+ appUnitsPerDevPixel,
+ nsPresContext::AppUnitsPerCSSPixel(),
+ startBevelSide, startBevelOffset,
+ endBevelSide, endBevelOffset);
+}
+
+/**
+ * Advance the start point of a segment
+ */
+void
+BCBlockDirSeg::AdvanceOffsetB()
+{
+ mOffsetB += mLength - mBEndOffset;
+}
+
+/**
+ * Accumulate the current segment
+ */
+void
+BCBlockDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter)
+{
+ mLastCell = aIter.mCell;
+ mLength += aIter.mRow->BSize(aIter.mTableWM);
+}
+
+BCInlineDirSeg::BCInlineDirSeg()
+{
+ mOffsetI = mOffsetB = mLength = mWidth = mIStartBevelOffset = 0;
+ mIStartBevelSide = eLogicalSideBStart;
+ mFirstCell = mAjaCell = nullptr;
+}
+
+/** Initialize an inline-dir border segment for painting
+ * @param aIter - iterator storing the current and adjacent frames
+ * @param aBorderOwner - which frame owns the border
+ * @param aBEndBlockSegISize - block-dir segment width coming from up
+ * @param aInlineSegBSize - the thickness of the segment
+ + */
+void
+BCInlineDirSeg::Start(BCPaintBorderIterator& aIter,
+ BCBorderOwner aBorderOwner,
+ BCPixelSize aBEndBlockSegISize,
+ BCPixelSize aInlineSegBSize)
+{
+ LogicalSide cornerOwnerSide = eLogicalSideBStart;
+ bool bevel = false;
+
+ mOwner = aBorderOwner;
+ nscoord cornerSubWidth = (aIter.mBCData) ?
+ aIter.mBCData->GetCorner(cornerOwnerSide,
+ bevel) : 0;
+
+ bool iStartBevel = (aInlineSegBSize > 0) ? bevel : false;
+ int32_t relColIndex = aIter.GetRelativeColIndex();
+ nscoord maxBlockSegISize = std::max(aIter.mBlockDirInfo[relColIndex].mWidth,
+ aBEndBlockSegISize);
+ nscoord offset = CalcHorCornerOffset(cornerOwnerSide, cornerSubWidth,
+ maxBlockSegISize, true, iStartBevel);
+ mIStartBevelOffset = (iStartBevel && (aInlineSegBSize > 0)) ? maxBlockSegISize : 0;
+ // XXX this assumes that only corners where 2 segments join can be beveled
+ mIStartBevelSide = (aBEndBlockSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart;
+ mOffsetI += offset;
+ mLength = -offset;
+ mWidth = aInlineSegBSize;
+ mFirstCell = aIter.mCell;
+ mAjaCell = (aIter.IsDamageAreaBStartMost()) ? nullptr :
+ aIter.mBlockDirInfo[relColIndex].mLastCell;
+}
+
+/**
+ * Compute the offsets for the iEnd corner of an inline-dir segment
+ * @param aIter - iterator containing the structural information
+ * @param aIStartSegISize - the iSize of the block-dir segment joining the corner
+ * at the start
+ */
+void
+BCInlineDirSeg::GetIEndCorner(BCPaintBorderIterator& aIter,
+ BCPixelSize aIStartSegISize)
+{
+ LogicalSide ownerSide = eLogicalSideBStart;
+ nscoord cornerSubWidth = 0;
+ bool bevel = false;
+ if (aIter.mBCData) {
+ cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel);
+ }
+
+ mIsIEndBevel = (mWidth > 0) ? bevel : 0;
+ int32_t relColIndex = aIter.GetRelativeColIndex();
+ nscoord verWidth = std::max(aIter.mBlockDirInfo[relColIndex].mWidth,
+ aIStartSegISize);
+ mEndOffset = CalcHorCornerOffset(ownerSide, cornerSubWidth, verWidth,
+ false, mIsIEndBevel);
+ mLength += mEndOffset;
+ mIEndBevelOffset = (mIsIEndBevel) ?
+ nsPresContext::CSSPixelsToAppUnits(verWidth) : 0;
+ mIEndBevelSide = (aIStartSegISize > 0) ? eLogicalSideBEnd : eLogicalSideBStart;
+}
+
+/**
+ * Paint the inline-dir segment
+ * @param aIter - iterator containing the structural information
+ * @param aDrawTarget - the draw target
+ */
+void
+BCInlineDirSeg::Paint(BCPaintBorderIterator& aIter, DrawTarget& aDrawTarget)
+{
+ // get the border style, color and paint the segment
+ LogicalSide side =
+ aIter.IsDamageAreaBEndMost() ? eLogicalSideBEnd : eLogicalSideBStart;
+ nsIFrame* rg = aIter.mRg; if (!rg) ABORT0();
+ nsIFrame* row = aIter.mRow; if (!row) ABORT0();
+ nsIFrame* cell = mFirstCell;
+ nsIFrame* col;
+ nsIFrame* owner = nullptr;
+
+ // All the tables frames have the same presContext, so we just use any one
+ // that exists here:
+ int32_t appUnitsPerDevPixel = row->PresContext()->AppUnitsPerDevPixel();
+
+ uint8_t style = NS_STYLE_BORDER_STYLE_SOLID;
+ nscolor color = 0xFFFFFFFF;
+
+ switch (mOwner) {
+ case eTableOwner:
+ owner = aIter.mTable;
+ break;
+ case eAjaColGroupOwner:
+ NS_ERROR("neighboring colgroups can never own an inline-dir border");
+ MOZ_FALLTHROUGH;
+ case eColGroupOwner:
+ NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
+ "col group can own border only at the table edge");
+ col = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
+ if (!col) ABORT0();
+ owner = col->GetParent();
+ break;
+ case eAjaColOwner:
+ NS_ERROR("neighboring column can never own an inline-dir border");
+ MOZ_FALLTHROUGH;
+ case eColOwner:
+ NS_ASSERTION(aIter.IsTableBStartMost() || aIter.IsTableBEndMost(),
+ "col can own border only at the table edge");
+ owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1);
+ break;
+ case eAjaRowGroupOwner:
+ side = eLogicalSideBEnd;
+ rg = (aIter.IsTableBEndMost()) ? aIter.mRg : aIter.mPrevRg;
+ MOZ_FALLTHROUGH;
+ case eRowGroupOwner:
+ owner = rg;
+ break;
+ case eAjaRowOwner:
+ side = eLogicalSideBEnd;
+ row = (aIter.IsTableBEndMost()) ? aIter.mRow : aIter.mPrevRow;
+ MOZ_FALLTHROUGH;
+ case eRowOwner:
+ owner = row;
+ break;
+ case eAjaCellOwner:
+ side = eLogicalSideBEnd;
+ // if this is null due to the damage area origin-y > 0, then the border
+ // won't show up anyway
+ cell = mAjaCell;
+ MOZ_FALLTHROUGH;
+ case eCellOwner:
+ owner = cell;
+ break;
+ }
+ if (owner) {
+ ::GetPaintStyleInfo(owner, aIter.mTableWM, side, &style, &color);
+ }
+ BCPixelSize smallHalf, largeHalf;
+ DivideBCBorderSize(mWidth, smallHalf, largeHalf);
+ LogicalRect segRect(aIter.mTableWM, mOffsetI,
+ mOffsetB - nsPresContext::CSSPixelsToAppUnits(largeHalf),
+ mLength,
+ nsPresContext::CSSPixelsToAppUnits(mWidth));
+
+ // Convert logical to physical sides/coordinates for DrawTableBorderSegment.
+ nsRect physicalRect = segRect.GetPhysicalRect(aIter.mTableWM,
+ aIter.mTable->GetSize());
+ uint8_t startBevelSide = aIter.mTableWM.PhysicalSide(mIStartBevelSide);
+ uint8_t endBevelSide = aIter.mTableWM.PhysicalSide(mIEndBevelSide);
+ nscoord startBevelOffset =
+ nsPresContext::CSSPixelsToAppUnits(mIStartBevelOffset);
+ nscoord endBevelOffset = mIEndBevelOffset;
+ // With inline-RTL directionality, the 'start' and 'end' of the inline-dir
+ // border segment need to be swapped because DrawTableBorderSegment will
+ // apply the 'start' bevel physically at the left or top edge, and 'end' at
+ // the right or bottom.
+ // (Note: startBevelSide/endBevelSide will be "top" or "bottom" in horizontal
+ // writing mode, or "left" or "right" in vertical mode.
+ // DrawTableBorderSegment works purely with physical coordinates, so it
+ // expects startBevelOffset to be the indentation-from-the-left or top end
+ // of the border-segment, and endBevelOffset is the indentation-from-the-
+ // right or bottom end. If the writing mode is inline-RTL, our "start" and
+ // "end" will be reversed from this physical-coord view, so we have to swap
+ // them here.
+ if (!aIter.mTableWM.IsBidiLTR()) {
+ Swap(startBevelSide, endBevelSide);
+ Swap(startBevelOffset, endBevelOffset);
+ }
+ nsCSSRendering::DrawTableBorderSegment(aDrawTarget, style, color,
+ aIter.mTableBgColor, physicalRect,
+ appUnitsPerDevPixel,
+ nsPresContext::AppUnitsPerCSSPixel(),
+ startBevelSide, startBevelOffset,
+ endBevelSide, endBevelOffset);
+}
+
+/**
+ * Advance the start point of a segment
+ */
+void
+BCInlineDirSeg::AdvanceOffsetI()
+{
+ mOffsetI += (mLength - mEndOffset);
+}
+
+/**
+ * Accumulate the current segment
+ */
+void
+BCInlineDirSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter)
+{
+ mLength += aIter.mBlockDirInfo[aIter.GetRelativeColIndex()].mColWidth;
+}
+
+/**
+ * store the column width information while painting inline-dir segment
+ */
+void
+BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex)
+{
+ if (IsTableIEndMost()) {
+ mBlockDirInfo[aIndex].mColWidth = mBlockDirInfo[aIndex - 1].mColWidth;
+ }
+ else {
+ nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex);
+ if (!col) ABORT0();
+ mBlockDirInfo[aIndex].mColWidth = col->ISize(mTableWM);
+ }
+}
+/**
+ * Determine if a block-dir segment owns the corner
+ */
+bool
+BCPaintBorderIterator::BlockDirSegmentOwnsCorner()
+{
+ LogicalSide cornerOwnerSide = eLogicalSideBStart;
+ bool bevel = false;
+ if (mBCData) {
+ mBCData->GetCorner(cornerOwnerSide, bevel);
+ }
+ // unitialized ownerside, bevel
+ return (eLogicalSideBStart == cornerOwnerSide) ||
+ (eLogicalSideBEnd == cornerOwnerSide);
+}
+
+/**
+ * Paint if necessary an inline-dir segment, otherwise accumulate it
+ * @param aDrawTarget - the draw target
+ */
+void
+BCPaintBorderIterator::AccumulateOrPaintInlineDirSegment(DrawTarget& aDrawTarget)
+{
+
+ int32_t relColIndex = GetRelativeColIndex();
+ // store the current col width if it hasn't been already
+ if (mBlockDirInfo[relColIndex].mColWidth < 0) {
+ StoreColumnWidth(relColIndex);
+ }
+
+ BCBorderOwner borderOwner = eCellOwner;
+ BCBorderOwner ignoreBorderOwner;
+ bool isSegStart = true;
+ bool ignoreSegStart;
+
+ nscoord iStartSegISize =
+ mBCData ? mBCData->GetIStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
+ nscoord bStartSegBSize =
+ mBCData ? mBCData->GetBStartEdge(borderOwner, isSegStart) : 0;
+
+ if (mIsNewRow || (IsDamageAreaIStartMost() && IsDamageAreaBEndMost())) {
+ // reset for every new row and on the bottom of the last row
+ mInlineSeg.mOffsetB = mNextOffsetB;
+ mNextOffsetB = mNextOffsetB + mRow->BSize(mTableWM);
+ mInlineSeg.mOffsetI = mInitialOffsetI;
+ mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
+ }
+
+ if (!IsDamageAreaIStartMost() && (isSegStart || IsDamageAreaIEndMost() ||
+ BlockDirSegmentOwnsCorner())) {
+ // paint the previous seg or the current one if IsDamageAreaIEndMost()
+ if (mInlineSeg.mLength > 0) {
+ mInlineSeg.GetIEndCorner(*this, iStartSegISize);
+ if (mInlineSeg.mWidth > 0) {
+ mInlineSeg.Paint(*this, aDrawTarget);
+ }
+ mInlineSeg.AdvanceOffsetI();
+ }
+ mInlineSeg.Start(*this, borderOwner, iStartSegISize, bStartSegBSize);
+ }
+ mInlineSeg.IncludeCurrentBorder(*this);
+ mBlockDirInfo[relColIndex].mWidth = iStartSegISize;
+ mBlockDirInfo[relColIndex].mLastCell = mCell;
+}
+/**
+ * Paint if necessary a block-dir segment, otherwise accumulate it
+ * @param aDrawTarget - the draw target
+ */
+void
+BCPaintBorderIterator::AccumulateOrPaintBlockDirSegment(DrawTarget& aDrawTarget)
+{
+ BCBorderOwner borderOwner = eCellOwner;
+ BCBorderOwner ignoreBorderOwner;
+ bool isSegStart = true;
+ bool ignoreSegStart;
+
+ nscoord blockSegISize =
+ mBCData ? mBCData->GetIStartEdge(borderOwner, isSegStart) : 0;
+ nscoord inlineSegBSize =
+ mBCData ? mBCData->GetBStartEdge(ignoreBorderOwner, ignoreSegStart) : 0;
+
+ int32_t relColIndex = GetRelativeColIndex();
+ BCBlockDirSeg& blockDirSeg = mBlockDirInfo[relColIndex];
+ if (!blockDirSeg.mCol) { // on the first damaged row and the first segment in the
+ // col
+ blockDirSeg.Initialize(*this);
+ blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize);
+ }
+
+ if (!IsDamageAreaBStartMost() && (isSegStart || IsDamageAreaBEndMost() ||
+ IsAfterRepeatedHeader() ||
+ StartRepeatedFooter())) {
+ // paint the previous seg or the current one if IsDamageAreaBEndMost()
+ if (blockDirSeg.mLength > 0) {
+ blockDirSeg.GetBEndCorner(*this, inlineSegBSize);
+ if (blockDirSeg.mWidth > 0) {
+ blockDirSeg.Paint(*this, aDrawTarget, inlineSegBSize);
+ }
+ blockDirSeg.AdvanceOffsetB();
+ }
+ blockDirSeg.Start(*this, borderOwner, blockSegISize, inlineSegBSize);
+ }
+ blockDirSeg.IncludeCurrentBorder(*this);
+ mPrevInlineSegBSize = inlineSegBSize;
+}
+
+/**
+ * Reset the block-dir information cache
+ */
+void
+BCPaintBorderIterator::ResetVerInfo()
+{
+ if (mBlockDirInfo) {
+ memset(mBlockDirInfo, 0, mDamageArea.ColCount() * sizeof(BCBlockDirSeg));
+ // XXX reinitialize properly
+ for (auto xIndex : MakeRange(mDamageArea.ColCount())) {
+ mBlockDirInfo[xIndex].mColWidth = -1;
+ }
+ }
+}
+
+/**
+ * Method to paint BCBorders, this does not use currently display lists although
+ * it will do this in future
+ * @param aDrawTarget - the rendering context
+ * @param aDirtyRect - inside this rectangle the BC Borders will redrawn
+ */
+void
+nsTableFrame::PaintBCBorders(DrawTarget& aDrawTarget, const nsRect& aDirtyRect)
+{
+ // We first transfer the aDirtyRect into cellmap coordinates to compute which
+ // cell borders need to be painted
+ BCPaintBorderIterator iter(this);
+ if (!iter.SetDamageArea(aDirtyRect))
+ return;
+
+ // XXX comment still has physical terminology
+ // First, paint all of the vertical borders from top to bottom and left to
+ // right as they become complete. They are painted first, since they are less
+ // efficient to paint than horizontal segments. They were stored with as few
+ // segments as possible (since horizontal borders are painted last and
+ // possibly over them). For every cell in a row that fails in the damage are
+ // we look up if the current border would start a new segment, if so we paint
+ // the previously stored vertical segment and start a new segment. After
+ // this we the now active segment with the current border. These
+ // segments are stored in mBlockDirInfo to be used on the next row
+ for (iter.First(); !iter.mAtEnd; iter.Next()) {
+ iter.AccumulateOrPaintBlockDirSegment(aDrawTarget);
+ }
+
+ // Next, paint all of the inline-dir border segments from bStart to bEnd reuse
+ // the mBlockDirInfo array to keep track of col widths and block-dir segments for
+ // corner calculations
+ iter.Reset();
+ for (iter.First(); !iter.mAtEnd; iter.Next()) {
+ iter.AccumulateOrPaintInlineDirSegment(aDrawTarget);
+ }
+}
+
+bool
+nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols)
+{
+ bool result = false;
+ nsTableCellMap* cellMap = GetCellMap();
+ NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated.");
+ if (cellMap) {
+ result = cellMap->RowHasSpanningCells(aRowIndex, aNumEffCols);
+ }
+ return result;
+}
+
+bool
+nsTableFrame::RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols)
+{
+ bool result = false;
+ nsTableCellMap* cellMap = GetCellMap();
+ NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated.");
+ if (cellMap) {
+ result = cellMap->RowIsSpannedInto(aRowIndex, aNumEffCols);
+ }
+ return result;
+}
+
+/* static */
+void
+nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame,
+ const nsRect& aOrigRect,
+ const nsRect& aOrigVisualOverflow,
+ bool aIsFirstReflow)
+{
+ nsIFrame* parent = aFrame->GetParent();
+ NS_ASSERTION(parent, "What happened here?");
+
+ if (parent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // Don't bother; we'll invalidate the parent's overflow rect when
+ // we finish reflowing it.
+ return;
+ }
+
+ // The part that looks at both the rect and the overflow rect is a
+ // bit of a hack. See nsBlockFrame::ReflowLine for an eloquent
+ // description of its hackishness.
+ //
+ // This doesn't really make sense now that we have DLBI.
+ // This code can probably be simplified a fair bit.
+ nsRect visualOverflow = aFrame->GetVisualOverflowRect();
+ if (aIsFirstReflow ||
+ aOrigRect.TopLeft() != aFrame->GetPosition() ||
+ aOrigVisualOverflow.TopLeft() != visualOverflow.TopLeft()) {
+ // Invalidate the old and new overflow rects. Note that if the
+ // frame moved, we can't just use aOrigVisualOverflow, since it's in
+ // coordinates relative to the old position. So invalidate via
+ // aFrame's parent, and reposition that overflow rect to the right
+ // place.
+ // XXXbz this doesn't handle outlines, does it?
+ aFrame->InvalidateFrame();
+ parent->InvalidateFrameWithRect(aOrigVisualOverflow + aOrigRect.TopLeft());
+ } else if (aOrigRect.Size() != aFrame->GetSize() ||
+ aOrigVisualOverflow.Size() != visualOverflow.Size()){
+ aFrame->InvalidateFrameWithRect(aOrigVisualOverflow);
+ aFrame->InvalidateFrame();
+ parent->InvalidateFrameWithRect(aOrigRect);
+ parent->InvalidateFrame();
+ }
+}
diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h
new file mode 100644
index 0000000000..a786253395
--- /dev/null
+++ b/layout/tables/nsTableFrame.h
@@ -0,0 +1,997 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTableFrame_h__
+#define nsTableFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "celldata.h"
+#include "imgIContainer.h"
+#include "nscore.h"
+#include "nsContainerFrame.h"
+#include "nsStyleCoord.h"
+#include "nsStyleConsts.h"
+#include "nsCellMap.h"
+#include "nsGkAtoms.h"
+#include "nsDisplayList.h"
+#include "TableArea.h"
+
+class nsTableCellFrame;
+class nsTableCellMap;
+class nsTableColFrame;
+class nsTableRowGroupFrame;
+class nsTableRowFrame;
+class nsTableColGroupFrame;
+class nsITableLayoutStrategy;
+class nsStyleContext;
+namespace mozilla {
+class WritingMode;
+class LogicalMargin;
+struct TableReflowInput;
+} // namespace mozilla
+
+struct BCPropertyData;
+
+static inline bool IS_TABLE_CELL(nsIAtom* frameType) {
+ return nsGkAtoms::tableCellFrame == frameType ||
+ nsGkAtoms::bcTableCellFrame == frameType;
+}
+
+static inline bool FrameHasBorderOrBackground(nsIFrame* f) {
+ return (f->StyleVisibility()->IsVisible() &&
+ (!f->StyleBackground()->IsTransparent() ||
+ f->StyleDisplay()->mAppearance ||
+ f->StyleBorder()->HasBorder()));
+}
+
+class nsDisplayTableItem : public nsDisplayItem
+{
+public:
+ nsDisplayTableItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ bool aDrawsBackground = true) :
+ nsDisplayItem(aBuilder, aFrame),
+ mPartHasFixedBackground(false),
+ mDrawsBackground(aDrawsBackground) {}
+
+ // With collapsed borders, parts of the collapsed border can extend outside
+ // the table part frames, so allow this display element to blow out to our
+ // overflow rect. This is also useful for row frames that have spanning
+ // cells extending outside them.
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+
+ void UpdateForFrameBackground(nsIFrame* aFrame);
+
+private:
+ bool mPartHasFixedBackground;
+ bool mDrawsBackground;
+};
+
+class nsAutoPushCurrentTableItem
+{
+public:
+ nsAutoPushCurrentTableItem() : mBuilder(nullptr) {}
+
+ void Push(nsDisplayListBuilder* aBuilder, nsDisplayTableItem* aPushItem)
+ {
+ mBuilder = aBuilder;
+ mOldCurrentItem = aBuilder->GetCurrentTableItem();
+ aBuilder->SetCurrentTableItem(aPushItem);
+#ifdef DEBUG
+ mPushedItem = aPushItem;
+#endif
+ }
+ ~nsAutoPushCurrentTableItem() {
+ if (!mBuilder)
+ return;
+#ifdef DEBUG
+ NS_ASSERTION(mBuilder->GetCurrentTableItem() == mPushedItem,
+ "Someone messed with the current table item behind our back!");
+#endif
+ mBuilder->SetCurrentTableItem(mOldCurrentItem);
+ }
+
+private:
+ nsDisplayListBuilder* mBuilder;
+ nsDisplayTableItem* mOldCurrentItem;
+#ifdef DEBUG
+ nsDisplayTableItem* mPushedItem;
+#endif
+};
+
+/* ============================================================================ */
+
+enum nsTableColGroupType {
+ eColGroupContent = 0, // there is real col group content associated
+ eColGroupAnonymousCol = 1, // the result of a col
+ eColGroupAnonymousCell = 2 // the result of a cell alone
+};
+
+enum nsTableColType {
+ eColContent = 0, // there is real col content associated
+ eColAnonymousCol = 1, // the result of a span on a col
+ eColAnonymousColGroup = 2, // the result of a span on a col group
+ eColAnonymousCell = 3 // the result of a cell alone
+};
+
+/**
+ * nsTableFrame maps the inner portion of a table (everything except captions.)
+ * Used as a pseudo-frame within nsTableWrapperFrame, it may also be used
+ * stand-alone as the top-level frame.
+ *
+ * The principal child list contains row group frames. There is also an
+ * additional child list, kColGroupList, which contains the col group frames.
+ */
+class nsTableFrame : public nsContainerFrame
+{
+ typedef mozilla::image::DrawResult DrawResult;
+ typedef mozilla::WritingMode WritingMode;
+ typedef mozilla::LogicalMargin LogicalMargin;
+ typedef mozilla::TableReflowInput TableReflowInput;
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsTableFrame)
+ NS_DECL_FRAMEARENA_HELPERS
+
+ typedef nsTArray<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(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ /** sets defaults for table-specific style.
+ * @see nsIFrame::Init
+ */
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ static float GetTwipsToPixels(nsPresContext* aPresContext);
+
+ // Return true if aParentReflowInput.frame or any of its ancestors within
+ // the containing table have non-auto bsize. (e.g. pct or fixed bsize)
+ static bool AncestorsHaveStyleBSize(const ReflowInput& aParentReflowInput);
+
+ // See if a special bsize reflow will occur due to having a pct bsize when
+ // the pct bsize basis may not yet be valid.
+ static void CheckRequestSpecialBSizeReflow(const ReflowInput& aReflowInput);
+
+ // Notify the frame and its ancestors (up to the containing table) that a special
+ // height reflow will occur.
+ static void RequestSpecialBSizeReflow(const ReflowInput& aReflowInput);
+
+ static void RePositionViews(nsIFrame* aFrame);
+
+ static bool PageBreakAfter(nsIFrame* aSourceFrame,
+ nsIFrame* aNextFrame);
+
+ // Register a positioned table part with its nsTableFrame. These objects will
+ // be visited by FixupPositionedTableParts after reflow is complete. (See that
+ // function for more explanation.) Should be called during frame construction.
+ static void RegisterPositionedTablePart(nsIFrame* aFrame);
+
+ // Unregister a positioned table part with its nsTableFrame.
+ static void UnregisterPositionedTablePart(nsIFrame* aFrame,
+ nsIFrame* aDestructRoot);
+
+ nsPoint GetFirstSectionOrigin(const ReflowInput& aReflowInput) const;
+ /*
+ * Notification that aAttribute has changed for content inside a table (cell, row, etc)
+ */
+ void AttributeChangedFor(nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsIAtom* aAttribute);
+
+ /** @see nsIFrame::DestroyFrom */
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ /** @see nsIFrame::DidSetStyleContext */
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual nsMargin GetUsedBorder() const override;
+ virtual nsMargin GetUsedPadding() const override;
+ virtual nsMargin GetUsedMargin() const override;
+
+ // Get the offset from the border box to the area where the row groups fit
+ LogicalMargin GetChildAreaOffset(const WritingMode aWM,
+ const ReflowInput* aReflowInput) const;
+
+ /** helper method to find the table parent of any table frame object */
+ static nsTableFrame* GetTableFrame(nsIFrame* aSourceFrame);
+
+ /* Like GetTableFrame, but will set *aDidPassThrough to false if we don't
+ * pass through aMustPassThrough on the way to the table.
+ */
+ static nsTableFrame* GetTableFramePassingThrough(nsIFrame* aMustPassThrough,
+ nsIFrame* aSourceFrame,
+ bool* aDidPassThrough);
+
+ typedef void (* DisplayGenericTablePartTraversal)
+ (nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
+ const nsRect& aDirtyRect, const nsDisplayListSet& aLists);
+ static void GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
+ const nsRect& aDirtyRect, const nsDisplayListSet& aLists);
+
+ /**
+ * Helper method to handle display common to table frames, rowgroup frames
+ * and row frames. It creates a background display item for handling events
+ * if necessary, an outline display item if necessary, and displays
+ * all the the frame's children.
+ * @param aDisplayItem the display item created for this part, or null
+ * if this part's border/background painting is delegated to an ancestor
+ * @param aTraversal a function that gets called to traverse the table
+ * part's child frames and add their display list items to a
+ * display list set.
+ */
+ static void DisplayGenericTablePart(nsDisplayListBuilder* aBuilder,
+ nsFrame* aFrame,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists,
+ nsDisplayTableItem* aDisplayItem,
+ DisplayGenericTablePartTraversal aTraversal = GenericTraversal);
+
+ // Return the closest sibling of aPriorChildFrame (including aPriroChildFrame)
+ // of type aChildType.
+ static nsIFrame* GetFrameAtOrBefore(nsIFrame* aParentFrame,
+ nsIFrame* aPriorChildFrame,
+ nsIAtom* aChildType);
+ bool IsAutoBSize(mozilla::WritingMode aWM);
+
+ /** @return true if aDisplayType represents a rowgroup of any sort
+ * (header, footer, or body)
+ */
+ bool IsRowGroup(mozilla::StyleDisplay aDisplayType) const;
+
+ virtual const nsFrameList& GetChildList(ChildListID aListID) const override;
+ virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ /**
+ * Paint the background of the table and its parts (column groups,
+ * columns, row groups, rows, and cells), and the table border, and all
+ * internal borders if border-collapse is on.
+ */
+ DrawResult PaintTableBorderBackground(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt);
+
+ /** Get the outer half (i.e., the part outside the height and width of
+ * the table) of the largest segment (?) of border-collapsed border on
+ * the table on each side, or 0 for non border-collapsed tables.
+ */
+ LogicalMargin GetOuterBCBorder(const WritingMode aWM) const;
+
+ /** Same as above, but only if it's included from the border-box width
+ * of the table.
+ */
+ LogicalMargin GetIncludedOuterBCBorder(const WritingMode aWM) const;
+
+ /** Same as above, but only if it's excluded from the border-box width
+ * of the table. This is the area that leaks out into the margin
+ * (or potentially past it, if there is no margin).
+ */
+ LogicalMargin GetExcludedOuterBCBorder(const WritingMode aWM) const;
+
+ /**
+ * In quirks mode, the size of the table background is reduced
+ * by the outer BC border. Compute the reduction needed.
+ */
+ nsMargin GetDeflationForBackground(nsPresContext* aPresContext) const;
+
+ /** Get width of table + colgroup + col collapse: elements that
+ * continue along the length of the whole iStart side.
+ * see nsTablePainter about continuous borders
+ */
+ nscoord GetContinuousIStartBCBorderWidth() const;
+ void SetContinuousIStartBCBorderWidth(nscoord aValue);
+
+ friend class nsDelayedCalcBCBorders;
+
+ void AddBCDamageArea(const mozilla::TableArea& aValue);
+ bool BCRecalcNeeded(nsStyleContext* aOldStyleContext,
+ nsStyleContext* aNewStyleContext);
+ void PaintBCBorders(DrawTarget& aDrawTarget, const nsRect& aDirtyRect);
+
+ virtual void MarkIntrinsicISizesDirty() override;
+ // For border-collapse tables, the caller must not add padding and
+ // border to the results of these functions.
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+ virtual IntrinsicISizeOffsetData IntrinsicISizeOffsets() override;
+
+ virtual mozilla::LogicalSize
+ ComputeSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ /**
+ * A copy of nsFrame::ShrinkWidthToFit that calls a different
+ * GetPrefISize, since tables have two different ones.
+ */
+ nscoord TableShrinkISizeToFit(nsRenderingContext *aRenderingContext,
+ nscoord aWidthInCB);
+
+ // XXXldb REWRITE THIS COMMENT!
+ /** inner tables are reflowed in two steps.
+ * <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
+ */
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void ReflowTable(ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nscoord aAvailBSize,
+ nsIFrame*& aLastChildReflowed,
+ nsReflowStatus& aStatus);
+
+ nsFrameList& GetColGroups();
+
+ virtual nsStyleContext*
+ GetParentStyleContext(nsIFrame** aProviderFrame) const override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::tableFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ if (aFlags & eSupportsCSSTransforms) {
+ return false;
+ }
+ return nsContainerFrame::IsFrameOfType(aFlags);
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ /** @see nsIFrame::GetFrameName */
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ /** Return the isize of the column at aColIndex.
+ * This may only be called on the table's first-in-flow.
+ */
+ nscoord GetColumnISizeFromFirstInFlow(int32_t aColIndex);
+
+ /** Helper to get the column spacing style value.
+ * The argument refers to the space between column aColIndex and column
+ * aColIndex + 1. An index of -1 indicates the padding between the table
+ * and the left border, an index equal to the number of columns indicates
+ * the padding between the table and the right border.
+ *
+ * Although in this class cell spacing does not depend on the index, it
+ * may be important for overriding classes.
+ */
+ virtual nscoord GetColSpacing(int32_t aColIndex);
+
+ /** Helper to find the sum of the cell spacing between arbitrary columns.
+ * The argument refers to the space between column aColIndex and column
+ * aColIndex + 1. An index of -1 indicates the padding between the table
+ * and the left border, an index equal to the number of columns indicates
+ * the padding between the table and the right border.
+ *
+ * This method is equivalent to
+ * nscoord result = 0;
+ * for (i = aStartColIndex; i < aEndColIndex; i++) {
+ * result += GetColSpacing(i);
+ * }
+ * return result;
+ */
+ virtual nscoord GetColSpacing(int32_t aStartColIndex,
+ int32_t aEndColIndex);
+
+ /** Helper to get the row spacing style value.
+ * The argument refers to the space between row aRowIndex and row
+ * aRowIndex + 1. An index of -1 indicates the padding between the table
+ * and the top border, an index equal to the number of rows indicates
+ * the padding between the table and the bottom border.
+ *
+ * Although in this class cell spacing does not depend on the index, it
+ * may be important for overriding classes.
+ */
+ virtual nscoord GetRowSpacing(int32_t aRowIndex);
+
+ /** Helper to find the sum of the cell spacing between arbitrary rows.
+ * The argument refers to the space between row aRowIndex and row
+ * aRowIndex + 1. An index of -1 indicates the padding between the table
+ * and the top border, an index equal to the number of rows indicates
+ * the padding between the table and the bottom border.
+ *
+ * This method is equivalent to
+ * nscoord result = 0;
+ * for (i = aStartRowIndex; i < aEndRowIndex; i++) {
+ * result += GetRowSpacing(i);
+ * }
+ * return result;
+ */
+ virtual nscoord GetRowSpacing(int32_t aStartRowIndex,
+ int32_t aEndRowIndex);
+
+private:
+ /* For the base implementation of nsTableFrame, cell spacing does not depend
+ * on row/column indexing.
+ */
+ nscoord GetColSpacing();
+ nscoord GetRowSpacing();
+
+public:
+ virtual nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const override;
+ bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ nscoord* aBaseline) const override;
+
+ /** return the row span of a cell, taking into account row span magic at the bottom
+ * of a table. The row span equals the number of rows spanned by aCell starting at
+ * aStartRowIndex, and can be smaller if aStartRowIndex is greater than the row
+ * index in which aCell originates.
+ *
+ * @param aStartRowIndex the cell
+ * @param aCell the cell
+ *
+ * @return the row span, correcting for row spans that extend beyond the bottom
+ * of the table.
+ */
+ int32_t GetEffectiveRowSpan(int32_t aStartRowIndex,
+ const nsTableCellFrame& aCell) const;
+ int32_t GetEffectiveRowSpan(const nsTableCellFrame& aCell,
+ nsCellMap* aCellMap = nullptr);
+
+ /** return the col span of a cell, taking into account col span magic at the edge
+ * of a table.
+ *
+ * @param aCell the cell
+ *
+ * @return the col span, correcting for col spans that extend beyond the edge
+ * of the table.
+ */
+ int32_t GetEffectiveColSpan(const nsTableCellFrame& aCell,
+ nsCellMap* aCellMap = nullptr) const;
+
+ /** indicate whether the row has more than one cell that either originates
+ * or is spanned from the rows above
+ */
+ bool HasMoreThanOneCell(int32_t aRowIndex) const;
+
+ /** return the column frame associated with aColIndex
+ * returns nullptr if the col frame has not yet been allocated, or if
+ * aColIndex is out of range
+ */
+ nsTableColFrame* GetColFrame(int32_t aColIndex) const;
+
+ /** Insert a col frame reference into the colframe cache and adapt the cellmap
+ * @param aColFrame - the column frame
+ * @param aColIndex - index where the column should be inserted into the
+ * colframe cache
+ */
+ void InsertCol(nsTableColFrame& aColFrame,
+ int32_t aColIndex);
+
+ nsTableColGroupFrame* CreateAnonymousColGroupFrame(nsTableColGroupType aType);
+
+ int32_t DestroyAnonymousColFrames(int32_t aNumFrames);
+
+ // Append aNumColsToAdd anonymous col frames of type eColAnonymousCell to our
+ // last eColGroupAnonymousCell colgroup. If we have no such colgroup, then
+ // create one.
+ void AppendAnonymousColFrames(int32_t aNumColsToAdd);
+
+ // Append aNumColsToAdd anonymous col frames of type aColType to
+ // aColGroupFrame. If aAddToTable is true, also call AddColsToTable on the
+ // new cols.
+ void AppendAnonymousColFrames(nsTableColGroupFrame* aColGroupFrame,
+ int32_t aNumColsToAdd,
+ nsTableColType aColType,
+ bool aAddToTable);
+
+ void MatchCellMapToColCache(nsTableCellMap* aCellMap);
+ /** empty the column frame cache */
+ void ClearColCache();
+
+ void DidResizeColumns();
+
+ void AppendCell(nsTableCellFrame& aCellFrame,
+ int32_t aRowIndex);
+
+ void InsertCells(nsTArray<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 aOrigVisualOverflow The original overflow rect of aFrame.
+ * @param aIsFirstReflow True if the size/position change is due to the
+ * first reflow of aFrame.
+ */
+ static void InvalidateTableFrame(nsIFrame* aFrame,
+ const nsRect& aOrigRect,
+ const nsRect& aOrigVisualOverflow,
+ bool aIsFirstReflow);
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override;
+
+protected:
+
+ /** protected constructor.
+ * @see NewFrame
+ */
+ explicit nsTableFrame(nsStyleContext* aContext);
+
+ /** destructor, responsible for mColumnLayoutData */
+ virtual ~nsTableFrame();
+
+ void InitChildReflowInput(ReflowInput& aReflowInput);
+
+ virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override;
+
+public:
+ bool IsRowInserted() const;
+ void SetRowInserted(bool aValue);
+
+protected:
+
+ // A helper function to reflow a header or footer with unconstrained height
+ // to see if it should be made repeatable and also to determine its desired
+ // height.
+ nsresult SetupHeaderFooterChild(const TableReflowInput& aReflowInput,
+ nsTableRowGroupFrame* aFrame,
+ nscoord* aDesiredHeight);
+
+ void ReflowChildren(TableReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ nsIFrame*& aLastChildReflowed,
+ nsOverflowAreas& aOverflowAreas);
+
+ // This calls the col group and column reflow methods, which do two things:
+ // (1) set all the dimensions to 0
+ // (2) notify the table about colgroups or columns with hidden visibility
+ void ReflowColGroups(nsRenderingContext* aRenderingContext);
+
+ /** return the isize of the table taking into account visibility collapse
+ * on columns and colgroups
+ * @param aBorderPadding the border and padding of the table
+ */
+ nscoord GetCollapsedISize(const WritingMode aWM,
+ const LogicalMargin& aBorderPadding);
+
+
+ /** Adjust the table for visibility.collapse set on rowgroups, rows,
+ * colgroups and cols
+ * @param aDesiredSize the metrics of the table
+ * @param aBorderPadding the border and padding of the table
+ */
+ void AdjustForCollapsingRowsCols(ReflowOutput& aDesiredSize,
+ const WritingMode aWM,
+ const LogicalMargin& aBorderPadding);
+
+ /** FixupPositionedTableParts is called at the end of table reflow to reflow
+ * the absolutely positioned descendants of positioned table parts. This is
+ * necessary because the dimensions of table parts may change after they've
+ * been reflowed (e.g. in AdjustForCollapsingRowsCols).
+ */
+ void FixupPositionedTableParts(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput);
+
+ // Clears the list of positioned table parts.
+ void ClearAllPositionedTableParts();
+
+ nsITableLayoutStrategy* LayoutStrategy() const {
+ return static_cast<nsTableFrame*>(FirstInFlow())->
+ mTableLayoutStrategy;
+ }
+
+ // Helper for InsertFrames.
+ void HomogenousInsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList);
+private:
+ /* Handle a row that got inserted during reflow. aNewHeight is the
+ new height of the table after reflow. */
+ void ProcessRowInserted(nscoord aNewHeight);
+
+ // WIDTH AND HEIGHT CALCULATION
+
+public:
+
+ // calculate the computed block-size of aFrame including its border and
+ // padding given its reflow state.
+ nscoord CalcBorderBoxBSize(const ReflowInput& aReflowInput);
+
+protected:
+
+ // update the desired block-size of this table taking into account the current
+ // reflow state, the table attributes and the content driven rowgroup bsizes
+ // this function can change the overflow area
+ void CalcDesiredBSize(const ReflowInput& aReflowInput,
+ ReflowOutput& aDesiredSize);
+
+ // The following is a helper for CalcDesiredBSize
+
+ void DistributeBSizeToRows(const ReflowInput& aReflowInput,
+ nscoord aAmount);
+
+ void PlaceChild(TableReflowInput& aReflowInput,
+ nsIFrame* aKidFrame,
+ nsPoint aKidPosition,
+ ReflowOutput& aKidDesiredSize,
+ const nsRect& aOriginalKidRect,
+ const nsRect& aOriginalKidVisualOverflow);
+ void PlaceRepeatedFooter(TableReflowInput& aReflowInput,
+ nsTableRowGroupFrame *aTfoot,
+ nscoord aFooterHeight);
+
+ nsIFrame* GetFirstBodyRowGroupFrame();
+public:
+ typedef AutoTArray<nsTableRowGroupFrame*, 8> RowGroupArray;
+ /**
+ * Push all our child frames from the aRowGroups array, in order, starting
+ * from the frame at aPushFrom to the end of the array. The frames are put on
+ * our overflow list or moved directly to our next-in-flow if one exists.
+ */
+protected:
+ void PushChildren(const RowGroupArray& aRowGroups, int32_t aPushFrom);
+
+public:
+ // put the children frames in the display order (e.g. thead before tbodies
+ // before tfoot). This will handle calling GetRowGroupFrame() on the
+ // children, and not append nulls, so the array is guaranteed to contain
+ // nsTableRowGroupFrames. If there are multiple theads or tfoots, all but
+ // the first one are treated as tbodies instead.
+
+ void OrderRowGroups(RowGroupArray& aChildren,
+ nsTableRowGroupFrame** aHead = nullptr,
+ nsTableRowGroupFrame** aFoot = nullptr) const;
+
+ // Return the thead, if any
+ nsTableRowGroupFrame* GetTHead() const;
+
+ // Return the tfoot, if any
+ nsTableRowGroupFrame* GetTFoot() const;
+
+ // Returns true if there are any cells above the row at
+ // aRowIndex and spanning into the row at aRowIndex, the number of
+ // effective columns limits the search up to that column
+ bool RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols);
+
+ // Returns true if there is a cell originating in aRowIndex
+ // which spans into the next row, the number of effective
+ // columns limits the search up to that column
+ bool RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols);
+
+protected:
+
+ bool HaveReflowedColGroups() const;
+ void SetHaveReflowedColGroups(bool aValue);
+
+public:
+ bool IsBorderCollapse() const;
+
+ bool NeedToCalcBCBorders() const;
+ void SetNeedToCalcBCBorders(bool aValue);
+
+ bool NeedToCollapse() const;
+ void SetNeedToCollapse(bool aValue);
+
+ /** The GeometryDirty bit is similar to the NS_FRAME_IS_DIRTY frame
+ * state bit, which implies that all descendants are dirty. The
+ * GeometryDirty still implies that all the parts of the table are
+ * dirty, but resizing optimizations should still apply to the
+ * contents of the individual cells.
+ */
+ void SetGeometryDirty() { mBits.mGeometryDirty = true; }
+ void ClearGeometryDirty() { mBits.mGeometryDirty = false; }
+ bool IsGeometryDirty() const { return mBits.mGeometryDirty; }
+
+ /** Get the cell map for this table frame. It is not always mCellMap.
+ * Only the firstInFlow has a legit cell map
+ */
+ nsTableCellMap* GetCellMap() const;
+
+ /** Iterate over the row groups and adjust the row indices of all rows
+ * whose index is >= aRowIndex.
+ * @param aRowIndex - start adjusting with this index
+ * @param aAdjustment - shift the row index by this amount
+ */
+ void AdjustRowIndices(int32_t aRowIndex,
+ int32_t aAdjustment);
+
+ /** Reset the rowindices of all rows as they might have changed due to
+ * rowgroup reordering, exclude new row group frames that show in the
+ * reordering but are not yet inserted into the cellmap
+ * @param aRowGroupsToExclude - an iterator that will produce the row groups
+ * to exclude.
+ */
+ void ResetRowIndices(const nsFrameList::Slice& aRowGroupsToExclude);
+
+ nsTArray<nsTableColFrame*>& GetColCache();
+
+
+protected:
+
+ void SetBorderCollapse(bool aValue);
+
+ BCPropertyData* GetBCProperty(bool aCreateIfNecessary = false) const;
+ void SetFullBCDamageArea();
+ void CalcBCBorders();
+
+ void ExpandBCDamageArea(mozilla::TableArea& aRect) const;
+
+ void SetColumnDimensions(nscoord aHeight, WritingMode aWM,
+ const LogicalMargin& aBorderPadding,
+ const nsSize& aContainerSize);
+
+ int32_t CollectRows(nsIFrame* aFrame,
+ nsTArray<nsTableRowFrame*>& aCollection);
+
+public: /* ----- Cell Map public methods ----- */
+
+ int32_t GetStartRowIndex(nsTableRowGroupFrame* aRowGroupFrame);
+
+ /** returns the number of rows in this table.
+ */
+ int32_t GetRowCount () const
+ {
+ return GetCellMap()->GetRowCount();
+ }
+
+ /** returns the number of columns in this table after redundant columns have been removed
+ */
+ int32_t GetEffectiveColCount() const;
+
+ /* return the col count including dead cols */
+ int32_t GetColCount () const
+ {
+ return GetCellMap()->GetColCount();
+ }
+
+ // return the last col index which isn't of type eColAnonymousCell
+ int32_t GetIndexOfLastRealCol();
+
+ /** returns true if table-layout:auto */
+ bool IsAutoLayout();
+
+public:
+
+#ifdef DEBUG
+ void Dump(bool aDumpRows,
+ bool aDumpCols,
+ bool aDumpCellMap);
+#endif
+
+protected:
+ /**
+ * Helper method for RemoveFrame.
+ */
+ void DoRemoveFrame(ChildListID aListID, nsIFrame* aOldFrame);
+#ifdef DEBUG
+ void DumpRowGroup(nsIFrame* aChildFrame);
+#endif
+ // DATA MEMBERS
+ AutoTArray<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 mIStartContBCBorder:8;
+ uint32_t mNeedToCollapse:1; // rows, cols that have visibility:collapse need to be collapsed
+ uint32_t mResizedColumns:1; // have we resized columns since last reflow?
+ } mBits;
+
+ nsTableCellMap* mCellMap; // maintains the relationships between rows, cols, and cells
+ nsITableLayoutStrategy* mTableLayoutStrategy;// the layout strategy for this frame
+ nsFrameList mColGroups; // the list of colgroup frames
+};
+
+
+inline bool nsTableFrame::IsRowGroup(mozilla::StyleDisplay aDisplayType) const
+{
+ return mozilla::StyleDisplay::TableHeaderGroup == aDisplayType ||
+ mozilla::StyleDisplay::TableFooterGroup == aDisplayType ||
+ mozilla::StyleDisplay::TableRowGroup == aDisplayType;
+}
+
+inline void nsTableFrame::SetHaveReflowedColGroups(bool aValue)
+{
+ mBits.mHaveReflowedColGroups = aValue;
+}
+
+inline bool nsTableFrame::HaveReflowedColGroups() const
+{
+ return (bool)mBits.mHaveReflowedColGroups;
+}
+
+inline bool nsTableFrame::HasPctCol() const
+{
+ return (bool)mBits.mHasPctCol;
+}
+
+inline void nsTableFrame::SetHasPctCol(bool aValue)
+{
+ mBits.mHasPctCol = (unsigned)aValue;
+}
+
+inline bool nsTableFrame::HasCellSpanningPctCol() const
+{
+ return (bool)mBits.mCellSpansPctCol;
+}
+
+inline void nsTableFrame::SetHasCellSpanningPctCol(bool aValue)
+{
+ mBits.mCellSpansPctCol = (unsigned)aValue;
+}
+
+inline bool nsTableFrame::IsRowInserted() const
+{
+ return (bool)mBits.mRowInserted;
+}
+
+inline void nsTableFrame::SetRowInserted(bool aValue)
+{
+ mBits.mRowInserted = (unsigned)aValue;
+}
+
+inline void nsTableFrame::SetNeedToCollapse(bool aValue)
+{
+ static_cast<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 nscoord
+nsTableFrame::GetContinuousIStartBCBorderWidth() const
+{
+ int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
+ return BC_BORDER_END_HALF_COORD(aPixelsToTwips, mBits.mIStartContBCBorder);
+}
+
+inline void nsTableFrame::SetContinuousIStartBCBorderWidth(nscoord aValue)
+{
+ mBits.mIStartContBCBorder = (unsigned) aValue;
+}
+
+#define ABORT0() \
+{NS_ASSERTION(false, "CellIterator program error"); \
+return;}
+
+#define ABORT1(aReturn) \
+{NS_ASSERTION(false, "CellIterator program error"); \
+return aReturn;}
+
+#endif
diff --git a/layout/tables/nsTablePainter.cpp b/layout/tables/nsTablePainter.cpp
new file mode 100644
index 0000000000..bfe2a7d429
--- /dev/null
+++ b/layout/tables/nsTablePainter.cpp
@@ -0,0 +1,696 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTableFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableColGroupFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsTablePainter.h"
+#include "nsCSSRendering.h"
+#include "nsDisplayList.h"
+#include "mozilla/WritingModes.h"
+
+/* ~*~ Table Background Painting ~*~
+
+ Mozilla's Table Background painting follows CSS2.1:17.5.1
+ That section does not, however, describe the effect of
+ borders on background image positioning. What we do is:
+
+ - in separate borders, the borders are passed in so that
+ their width figures in image positioning, even for rows/cols, which
+ don't have visible borders. This is done to allow authors
+ to position row backgrounds by, for example, aligning the
+ top left corner with the top left padding corner of the
+ top left table cell in the row in cases where all cells
+ have consistent border widths. If we didn't honor these
+ invisible borders, there would be no way to align
+ backgrounds with the padding edges, and designs would be
+ lost underneath the border.
+
+ - in collapsing borders, because the borders collapse, we
+ use the -continuous border- width to synthesize a border
+ style and pass that in instead of using the element's
+ assigned style directly.
+
+ The continuous border on a given edge of an element is
+ the collapse of all borders guaranteed to be continuous
+ along that edge. Cell borders are ignored (because, for
+ example, setting a thick border on the leftmost cell
+ should not shift the row background over; this way a
+ striped background set on <tr> will line up across rows
+ even if the cells are assigned arbitrary border widths.
+
+ For example, the continuous border on the top edge of a
+ row group is the collapse of any row group, row, and
+ table borders involved. (The first row group's top would
+ be [table-top + row group top + first row top]. It's bottom
+ would be [row group bottom + last row bottom + next row
+ top + next row group top].)
+ The top edge of a column group likewise includes the
+ table top, row group top, and first row top borders. However,
+ it *also* includes its own top border, since that is guaranteed
+ to be continuous. It does not include column borders because
+ those are not guaranteed to be continuous: there may be two
+ columns with different borders in a single column group.
+
+ An alternative would be to define the continuous border as
+ [table? + row group + row] for horizontal
+ [table? + col group + col] for vertical
+ This makes it easier to line up backgrounds across elements
+ despite varying border widths, but it does not give much
+ flexibility in aligning /to/ those border widths.
+*/
+
+
+/* ~*~ TableBackgroundPainter ~*~
+
+ The TableBackgroundPainter is created and destroyed in one painting call.
+ Its principal function is PaintTable, which paints all table element
+ backgrounds. The initial code in that method sets up an array of column
+ data that caches the background styles and the border sizes for the
+ columns and colgroups in TableBackgroundData structs in mCols. Data for
+ BC borders are calculated and stashed in a synthesized border style struct
+ in the data struct since collapsed borders aren't the same width as style-
+ assigned borders. The data struct optimizes by only doing this if there's
+ an image background; otherwise we don't care. //XXX should also check background-origin
+ The class then loops through the row groups, rows, and cells. At the cell
+ level, it paints the backgrounds, one over the other, inside the cell rect.
+
+ The exception to this pattern is when a table element creates a (pseudo)
+ stacking context. Elements with stacking contexts (e.g., 'opacity' applied)
+ are <dfn>passed through</dfn>, which means their data (and their
+ descendants' data) are not cached. The full loop is still executed, however,
+ so that underlying layers can get painted at the cell level.
+
+ The TableBackgroundPainter is then destroyed.
+
+ Elements with stacking contexts set up their own painter to finish the
+ painting process, since they were skipped. They call the appropriate
+ sub-part of the loop (e.g. PaintRow) which will paint the frame and
+ descendants.
+
+ XXX views are going
+ */
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+TableBackgroundPainter::TableBackgroundData::TableBackgroundData()
+ : mFrame(nullptr)
+ , mVisible(false)
+ , mUsesSynthBorder(false)
+{
+}
+
+TableBackgroundPainter::TableBackgroundData::TableBackgroundData(nsIFrame* aFrame)
+ : mFrame(aFrame)
+ , mRect(aFrame->GetRect())
+ , mVisible(mFrame->IsVisibleForPainting())
+ , mUsesSynthBorder(false)
+{
+}
+
+inline bool
+TableBackgroundPainter::TableBackgroundData::ShouldSetBCBorder() const
+{
+ /* we only need accurate border data when positioning background images*/
+ if (!mVisible) {
+ return false;
+ }
+
+ const nsStyleImageLayers& layers = mFrame->StyleBackground()->mImage;
+ NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) {
+ if (!layers.mLayers[i].mImage.IsEmpty())
+ return true;
+ }
+ return false;
+}
+
+void
+TableBackgroundPainter::TableBackgroundData::SetBCBorder(const nsMargin& aBorder)
+{
+ mUsesSynthBorder = true;
+ mSynthBorderWidths = aBorder;
+}
+
+nsStyleBorder
+TableBackgroundPainter::TableBackgroundData::StyleBorder(const nsStyleBorder& aZeroBorder) const
+{
+ MOZ_ASSERT(mVisible, "Don't call StyleBorder on an invisible TableBackgroundData");
+
+ if (mUsesSynthBorder) {
+ nsStyleBorder result = aZeroBorder;
+ NS_FOR_CSS_SIDES(side) {
+ result.SetBorderWidth(side, mSynthBorderWidths.Side(side));
+ }
+ return result;
+ }
+
+ MOZ_ASSERT(mFrame);
+
+ return *mFrame->StyleBorder();
+}
+
+TableBackgroundPainter::TableBackgroundPainter(nsTableFrame* aTableFrame,
+ Origin aOrigin,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsPoint& aRenderPt,
+ uint32_t aBGPaintFlags)
+ : mPresContext(aPresContext),
+ mRenderingContext(aRenderingContext),
+ mRenderPt(aRenderPt),
+ mDirtyRect(aDirtyRect),
+ mOrigin(aOrigin),
+ mZeroBorder(aPresContext),
+ mBGPaintFlags(aBGPaintFlags)
+{
+ MOZ_COUNT_CTOR(TableBackgroundPainter);
+
+ NS_FOR_CSS_SIDES(side) {
+ mZeroBorder.SetBorderStyle(side, NS_STYLE_BORDER_STYLE_SOLID);
+ mZeroBorder.SetBorderWidth(side, 0);
+ }
+
+ mIsBorderCollapse = aTableFrame->IsBorderCollapse();
+#ifdef DEBUG
+ mCompatMode = mPresContext->CompatibilityMode();
+#endif
+ mNumCols = aTableFrame->GetColCount();
+}
+
+TableBackgroundPainter::~TableBackgroundPainter()
+{
+ MOZ_COUNT_DTOR(TableBackgroundPainter);
+}
+
+DrawResult
+TableBackgroundPainter::PaintTableFrame(nsTableFrame* aTableFrame,
+ nsTableRowGroupFrame* aFirstRowGroup,
+ nsTableRowGroupFrame* aLastRowGroup,
+ const nsMargin& aDeflate)
+{
+ MOZ_ASSERT(aTableFrame, "null frame");
+ TableBackgroundData tableData(aTableFrame);
+ tableData.mRect.MoveTo(0,0); //using table's coords
+ tableData.mRect.Deflate(aDeflate);
+ WritingMode wm = aTableFrame->GetWritingMode();
+ if (mIsBorderCollapse && tableData.ShouldSetBCBorder()) {
+ if (aFirstRowGroup && aLastRowGroup && mNumCols > 0) {
+ //only handle non-degenerate tables; we need a more robust BC model
+ //to make degenerate tables' borders reasonable to deal with
+ LogicalMargin border(wm);
+ LogicalMargin tempBorder(wm);
+ nsTableColFrame* colFrame = aTableFrame->GetColFrame(mNumCols - 1);
+ if (colFrame) {
+ colFrame->GetContinuousBCBorderWidth(wm, tempBorder);
+ }
+ border.IEnd(wm) = tempBorder.IEnd(wm);
+
+ aLastRowGroup->GetContinuousBCBorderWidth(wm, tempBorder);
+ border.BEnd(wm) = tempBorder.BEnd(wm);
+
+ nsTableRowFrame* rowFrame = aFirstRowGroup->GetFirstRow();
+ if (rowFrame) {
+ rowFrame->GetContinuousBCBorderWidth(wm, tempBorder);
+ border.BStart(wm) = tempBorder.BStart(wm);
+ }
+
+ border.IStart(wm) = aTableFrame->GetContinuousIStartBCBorderWidth();
+
+ tableData.SetBCBorder(border.GetPhysicalMargin(wm));
+ }
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (tableData.IsVisible()) {
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext,
+ mRenderingContext,
+ mDirtyRect,
+ tableData.mRect + mRenderPt,
+ tableData.mFrame,
+ mBGPaintFlags);
+
+ result &=
+ nsCSSRendering::PaintBackgroundWithSC(params,
+ tableData.mFrame->StyleContext(),
+ tableData.StyleBorder(mZeroBorder));
+ }
+
+ return result;
+}
+
+void
+TableBackgroundPainter::TranslateContext(nscoord aDX,
+ nscoord aDY)
+{
+ mRenderPt += nsPoint(aDX, aDY);
+ for (auto& col : mCols) {
+ col.mCol.mRect.MoveBy(-aDX, -aDY);
+ }
+ for (auto& colGroup : mColGroups) {
+ colGroup.mRect.MoveBy(-aDX, -aDY);
+ }
+}
+
+TableBackgroundPainter::ColData::ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData)
+ : mCol(aFrame)
+ , mColGroup(aColGroupBGData)
+{
+}
+
+DrawResult
+TableBackgroundPainter::PaintTable(nsTableFrame* aTableFrame,
+ const nsMargin& aDeflate,
+ bool aPaintTableBackground)
+{
+ NS_PRECONDITION(aTableFrame, "null table frame");
+
+ nsTableFrame::RowGroupArray rowGroups;
+ aTableFrame->OrderRowGroups(rowGroups);
+ WritingMode wm = aTableFrame->GetWritingMode();
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ if (rowGroups.Length() < 1) { //degenerate case
+ if (aPaintTableBackground) {
+ result &= PaintTableFrame(aTableFrame, nullptr, nullptr, nsMargin(0,0,0,0));
+ }
+ /* No cells; nothing else to paint */
+ return result;
+ }
+
+ if (aPaintTableBackground) {
+ result &=
+ PaintTableFrame(aTableFrame, rowGroups[0], rowGroups[rowGroups.Length() - 1],
+ aDeflate);
+ }
+
+ /*Set up column background/border data*/
+ if (mNumCols > 0) {
+ nsFrameList& colGroupList = aTableFrame->GetColGroups();
+ NS_ASSERTION(colGroupList.FirstChild(), "table should have at least one colgroup");
+
+ // Collect all col group frames first so that we know how many there are.
+ nsTArray<nsTableColGroupFrame*> colGroupFrames;
+ for (nsTableColGroupFrame* cgFrame = static_cast<nsTableColGroupFrame*>(colGroupList.FirstChild());
+ cgFrame; cgFrame = static_cast<nsTableColGroupFrame*>(cgFrame->GetNextSibling())) {
+
+ if (cgFrame->GetColCount() < 1) {
+ //No columns, no cells, so no need for data
+ continue;
+ }
+ colGroupFrames.AppendElement(cgFrame);
+ }
+
+ // Ensure that mColGroups won't reallocate during the loop below, because
+ // we grab references to its contents and need those to stay valid until
+ // mColGroups is destroyed as part of TablePainter destruction.
+ mColGroups.SetCapacity(colGroupFrames.Length());
+
+ LogicalMargin border(wm);
+ /* BC iStart borders aren't stored on cols, but the previous column's
+ iEnd border is the next one's iStart border.*/
+ //Start with table's iStart border.
+ nscoord lastIStartBorder = aTableFrame->GetContinuousIStartBCBorderWidth();
+
+ for (nsTableColGroupFrame* cgFrame : colGroupFrames) {
+ /*Create data struct for column group*/
+ TableBackgroundData& cgData = *mColGroups.AppendElement(TableBackgroundData(cgFrame));
+ if (mIsBorderCollapse && cgData.ShouldSetBCBorder()) {
+ border.IStart(wm) = lastIStartBorder;
+ cgFrame->GetContinuousBCBorderWidth(wm, border);
+ cgData.SetBCBorder(border.GetPhysicalMargin(wm));
+ }
+
+ /*Loop over columns in this colgroup*/
+ for (nsTableColFrame* col = cgFrame->GetFirstColumn(); col;
+ col = static_cast<nsTableColFrame*>(col->GetNextSibling())) {
+ MOZ_ASSERT(size_t(col->GetColIndex()) == mCols.Length());
+ // Store a reference to the colGroup in the ColData element.
+ ColData& colData = *mCols.AppendElement(ColData(col, cgData));
+ //Bring column mRect into table's coord system
+ colData.mCol.mRect.MoveBy(cgData.mRect.x, cgData.mRect.y);
+ if (mIsBorderCollapse) {
+ border.IStart(wm) = lastIStartBorder;
+ lastIStartBorder = col->GetContinuousBCBorderWidth(wm, border);
+ if (colData.mCol.ShouldSetBCBorder()) {
+ colData.mCol.SetBCBorder(border.GetPhysicalMargin(wm));
+ }
+ }
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < rowGroups.Length(); i++) {
+ nsTableRowGroupFrame* rg = rowGroups[i];
+ TableBackgroundData rowGroupBGData(rg);
+ // Need to compute the right rect via GetOffsetTo, since the row
+ // group may not be a child of the table.
+ rowGroupBGData.mRect.MoveTo(rg->GetOffsetTo(aTableFrame));
+
+ // We have to draw backgrounds not only within the overflow region of this
+ // row group, but also possibly (in the case of column / column group
+ // backgrounds) at its pre-relative-positioning location.
+ nsRect rgVisualOverflow = rg->GetVisualOverflowRectRelativeToSelf();
+ nsRect rgOverflowRect = rgVisualOverflow + rg->GetPosition();
+ nsRect rgNormalRect = rgVisualOverflow + rg->GetNormalPosition();
+
+ if (rgOverflowRect.Union(rgNormalRect).Intersects(mDirtyRect - mRenderPt)) {
+ result &=
+ PaintRowGroup(rg, rowGroupBGData, rg->IsPseudoStackingContextFromStyle());
+ }
+ }
+
+ return result;
+}
+
+DrawResult
+TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame)
+{
+ return PaintRowGroup(aFrame, TableBackgroundData(aFrame), false);
+}
+
+DrawResult
+TableBackgroundPainter::PaintRowGroup(nsTableRowGroupFrame* aFrame,
+ TableBackgroundData aRowGroupBGData,
+ bool aPassThrough)
+{
+ MOZ_ASSERT(aFrame, "null frame");
+
+ nsTableRowFrame* firstRow = aFrame->GetFirstRow();
+ WritingMode wm = aFrame->GetWritingMode();
+
+ /* Load row group data */
+ if (aPassThrough) {
+ aRowGroupBGData.MakeInvisible();
+ } else {
+ if (mIsBorderCollapse && aRowGroupBGData.ShouldSetBCBorder()) {
+ LogicalMargin border(wm);
+ if (firstRow) {
+ //pick up first row's bstart border (= rg bstart border)
+ firstRow->GetContinuousBCBorderWidth(wm, border);
+ /* (row group doesn't store its bstart border) */
+ }
+ //overwrite sides+bottom borders with rg's own
+ aFrame->GetContinuousBCBorderWidth(wm, border);
+ aRowGroupBGData.SetBCBorder(border.GetPhysicalMargin(wm));
+ }
+ aPassThrough = !aRowGroupBGData.IsVisible();
+ }
+
+ /* translate everything into row group coord system*/
+ if (eOrigin_TableRowGroup != mOrigin) {
+ TranslateContext(aRowGroupBGData.mRect.x, aRowGroupBGData.mRect.y);
+ }
+ nsRect rgRect = aRowGroupBGData.mRect;
+ aRowGroupBGData.mRect.MoveTo(0, 0);
+
+ /* Find the right row to start with */
+
+ // Note that mDirtyRect - mRenderPt is guaranteed to be in the row
+ // group's coordinate system here, so passing its .y to
+ // GetFirstRowContaining is ok.
+ nscoord overflowAbove;
+ nsIFrame* cursor = aFrame->GetFirstRowContaining(mDirtyRect.y - mRenderPt.y, &overflowAbove);
+
+ // Sadly, it seems like there may be non-row frames in there... or something?
+ // There are certainly null-checks in GetFirstRow() and GetNextRow(). :(
+ while (cursor && cursor->GetType() != nsGkAtoms::tableRowFrame) {
+ cursor = cursor->GetNextSibling();
+ }
+
+ // It's OK if cursor is null here.
+ nsTableRowFrame* row = static_cast<nsTableRowFrame*>(cursor);
+ if (!row) {
+ // No useful cursor; just start at the top. Don't bother to set up a
+ // cursor; if we've gotten this far then we've already built the display
+ // list for the rowgroup, so not having a cursor means that there's some
+ // good reason we don't have a cursor and we shouldn't create one here.
+ row = firstRow;
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ /* Finally paint */
+ for (; row; row = row->GetNextRow()) {
+ TableBackgroundData rowBackgroundData(row);
+
+ // Be sure to consider our positions both pre- and post-relative
+ // positioning, since we potentially need to paint at both places.
+ nscoord rowY = std::min(rowBackgroundData.mRect.y, row->GetNormalPosition().y);
+
+ // Intersect wouldn't handle rowspans.
+ if (cursor &&
+ (mDirtyRect.YMost() - mRenderPt.y) <= (rowY - overflowAbove)) {
+ // All done; cells originating in later rows can't intersect mDirtyRect.
+ break;
+ }
+
+ result &=
+ PaintRow(row, aRowGroupBGData, rowBackgroundData,
+ aPassThrough || row->IsPseudoStackingContextFromStyle());
+ }
+
+ /* translate back into table coord system */
+ if (eOrigin_TableRowGroup != mOrigin) {
+ TranslateContext(-rgRect.x, -rgRect.y);
+ }
+
+ return result;
+}
+
+DrawResult
+TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame)
+{
+ return PaintRow(aFrame, TableBackgroundData(), TableBackgroundData(aFrame), false);
+}
+
+DrawResult
+TableBackgroundPainter::PaintRow(nsTableRowFrame* aFrame,
+ const TableBackgroundData& aRowGroupBGData,
+ TableBackgroundData aRowBGData,
+ bool aPassThrough)
+{
+ MOZ_ASSERT(aFrame, "null frame");
+
+ /* Load row data */
+ WritingMode wm = aFrame->GetWritingMode();
+ if (aPassThrough) {
+ aRowBGData.MakeInvisible();
+ } else {
+ if (mIsBorderCollapse && aRowBGData.ShouldSetBCBorder()) {
+ LogicalMargin border(wm);
+ nsTableRowFrame* nextRow = aFrame->GetNextRow();
+ if (nextRow) { //outer bStart after us is inner bEnd for us
+ border.BEnd(wm) = nextRow->GetOuterBStartContBCBorderWidth();
+ }
+ else { //acquire rg's bEnd border
+ nsTableRowGroupFrame* rowGroup = static_cast<nsTableRowGroupFrame*>(aFrame->GetParent());
+ rowGroup->GetContinuousBCBorderWidth(wm, border);
+ }
+ //get the rest of the borders; will overwrite all but bEnd
+ aFrame->GetContinuousBCBorderWidth(wm, border);
+
+ aRowBGData.SetBCBorder(border.GetPhysicalMargin(wm));
+ }
+ aPassThrough = !aRowBGData.IsVisible();
+ }
+
+ /* Translate */
+ if (eOrigin_TableRow == mOrigin) {
+ /* If we originate from the row, then make the row the origin. */
+ aRowBGData.mRect.MoveTo(0, 0);
+ }
+ //else: Use row group's coord system -> no translation necessary
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ for (nsTableCellFrame* cell = aFrame->GetFirstCell(); cell; cell = cell->GetNextCell()) {
+ nsRect cellBGRect, rowBGRect, rowGroupBGRect, colBGRect;
+ ComputeCellBackgrounds(cell, aRowGroupBGData, aRowBGData,
+ cellBGRect, rowBGRect,
+ rowGroupBGRect, colBGRect);
+
+ // Find the union of all the cell background layers.
+ nsRect combinedRect(cellBGRect);
+ combinedRect.UnionRect(combinedRect, rowBGRect);
+ combinedRect.UnionRect(combinedRect, rowGroupBGRect);
+ combinedRect.UnionRect(combinedRect, colBGRect);
+
+ if (combinedRect.Intersects(mDirtyRect)) {
+ bool passCell = aPassThrough || cell->IsPseudoStackingContextFromStyle();
+ result &=
+ PaintCell(cell, aRowGroupBGData, aRowBGData, cellBGRect, rowBGRect,
+ rowGroupBGRect, colBGRect, passCell);
+ }
+ }
+
+ return result;
+}
+
+DrawResult
+TableBackgroundPainter::PaintCell(nsTableCellFrame* aCell,
+ const TableBackgroundData& aRowGroupBGData,
+ const TableBackgroundData& aRowBGData,
+ nsRect& aCellBGRect,
+ nsRect& aRowBGRect,
+ nsRect& aRowGroupBGRect,
+ nsRect& aColBGRect,
+ bool aPassSelf)
+{
+ MOZ_ASSERT(aCell, "null frame");
+
+ const nsStyleTableBorder* cellTableStyle;
+ cellTableStyle = aCell->StyleTableBorder();
+ if (NS_STYLE_TABLE_EMPTY_CELLS_SHOW != cellTableStyle->mEmptyCells &&
+ aCell->GetContentEmpty() && !mIsBorderCollapse) {
+ return DrawResult::SUCCESS;
+ }
+
+ int32_t colIndex;
+ aCell->GetColIndex(colIndex);
+ // We're checking mNumCols instead of mCols.Length() here because mCols can
+ // be empty even if mNumCols > 0.
+ NS_ASSERTION(size_t(colIndex) < mNumCols, "out-of-bounds column index");
+ if (size_t(colIndex) >= mNumCols) {
+ return DrawResult::SUCCESS;
+ }
+
+ // If callers call PaintRowGroup or PaintRow directly, we haven't processed
+ // our columns. Ignore column / col group backgrounds in that case.
+ bool haveColumns = !mCols.IsEmpty();
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ //Paint column group background
+ if (haveColumns && mCols[colIndex].mColGroup.IsVisible()) {
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext,
+ mDirtyRect,
+ mCols[colIndex].mColGroup.mRect + mRenderPt,
+ mCols[colIndex].mColGroup.mFrame,
+ mBGPaintFlags);
+ params.bgClipRect = &aColBGRect;
+ result &=
+ nsCSSRendering::PaintBackgroundWithSC(params,
+ mCols[colIndex].mColGroup.mFrame->StyleContext(),
+ mCols[colIndex].mColGroup.StyleBorder(mZeroBorder));
+ }
+
+ //Paint column background
+ if (haveColumns && mCols[colIndex].mCol.IsVisible()) {
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext,
+ mDirtyRect,
+ mCols[colIndex].mCol.mRect + mRenderPt,
+ mCols[colIndex].mCol.mFrame,
+ mBGPaintFlags);
+ params.bgClipRect = &aColBGRect;
+ result &=
+ nsCSSRendering::PaintBackgroundWithSC(params,
+ mCols[colIndex].mCol.mFrame->StyleContext(),
+ mCols[colIndex].mCol.StyleBorder(mZeroBorder));
+ }
+
+ //Paint row group background
+ if (aRowGroupBGData.IsVisible()) {
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext,
+ mDirtyRect,
+ aRowGroupBGData.mRect + mRenderPt,
+ aRowGroupBGData.mFrame, mBGPaintFlags);
+ params.bgClipRect = &aRowGroupBGRect;
+ result &=
+ nsCSSRendering::PaintBackgroundWithSC(params,
+ aRowGroupBGData.mFrame->StyleContext(),
+ aRowGroupBGData.StyleBorder(mZeroBorder));
+ }
+
+ //Paint row background
+ if (aRowBGData.IsVisible()) {
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForAllLayers(*mPresContext, mRenderingContext,
+ mDirtyRect,
+ aRowBGData.mRect + mRenderPt,
+ aRowBGData.mFrame, mBGPaintFlags);
+ params.bgClipRect = &aRowBGRect;
+ result &=
+ nsCSSRendering::PaintBackgroundWithSC(params,
+ aRowBGData.mFrame->StyleContext(),
+ aRowBGData.StyleBorder(mZeroBorder));
+ }
+
+ //Paint cell background in border-collapse unless we're just passing
+ if (mIsBorderCollapse && !aPassSelf) {
+ result &=
+ aCell->PaintCellBackground(mRenderingContext, mDirtyRect,
+ aCellBGRect.TopLeft(), mBGPaintFlags);
+ }
+
+ return result;
+}
+
+void
+TableBackgroundPainter::ComputeCellBackgrounds(nsTableCellFrame* aCell,
+ const TableBackgroundData& aRowGroupBGData,
+ const TableBackgroundData& aRowBGData,
+ nsRect& aCellBGRect,
+ nsRect& aRowBGRect,
+ nsRect& aRowGroupBGRect,
+ nsRect& aColBGRect)
+{
+ // We need to compute table background layer rects for this cell space,
+ // adjusted for possible relative positioning. This behavior is not specified
+ // at the time of this writing, but the approach below should be web
+ // compatible.
+ //
+ // Our goal is that relative positioning of a table part should leave
+ // backgrounds *under* that part unchanged. ("Under" being defined by CSS 2.1
+ // Section 17.5.1.) If a cell is positioned, we do not expect the row
+ // background to move. On the other hand, the backgrounds of layers *above*
+ // the positioned part are taken along for the ride -- for example,
+ // positioning a row group will also cause the row background to be drawn in
+ // the new location, unless it has further positioning applied.
+ //
+ // Each table part layer has its position stored in the coordinate space of
+ // the layer below (which is to say, its geometric parent), and the stored
+ // position is the post-relative-positioning one. The position of each
+ // background layer rect is thus determined by peeling off successive table
+ // part layers, removing the contribution of each layer's positioning one by
+ // one. Every rect we generate will be the same size, the size of the cell
+ // space.
+
+ // We cannot rely on the row group background data to be available, since some
+ // callers enter through PaintRow.
+ nsIFrame* rowGroupFrame =
+ aRowGroupBGData.mFrame ? aRowGroupBGData.mFrame : aRowBGData.mFrame->GetParent();
+
+ // The cell background goes at the cell's position, translated to use the same
+ // coordinate system as aRowBGData.
+ aCellBGRect = aCell->GetRect() + aRowBGData.mRect.TopLeft() + mRenderPt;
+
+ // The row background goes at the normal position of the cell, which is to say
+ // the position without relative positioning applied.
+ aRowBGRect = aCellBGRect + (aCell->GetNormalPosition() - aCell->GetPosition());
+
+ // The row group background goes at the position we'd find the cell if neither
+ // the cell's relative positioning nor the row's were applied.
+ aRowGroupBGRect = aRowBGRect +
+ (aRowBGData.mFrame->GetNormalPosition() - aRowBGData.mFrame->GetPosition());
+
+ // The column and column group backgrounds (they're always at the same
+ // location, since relative positioning doesn't apply to columns or column
+ // groups) are drawn at the position we'd find the cell if none of the cell's,
+ // row's, or row group's relative positioning were applied.
+ aColBGRect = aRowGroupBGRect +
+ (rowGroupFrame->GetNormalPosition() - rowGroupFrame->GetPosition());
+
+}
diff --git a/layout/tables/nsTablePainter.h b/layout/tables/nsTablePainter.h
new file mode 100644
index 0000000000..dfba421565
--- /dev/null
+++ b/layout/tables/nsTablePainter.h
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTablePainter_h__
+#define nsTablePainter_h__
+
+#include "imgIContainer.h"
+
+#include "celldata.h"
+
+// flags for Paint, PaintChild, PaintChildren are currently only used by tables.
+//Table-based paint call; not a direct call as with views
+#define NS_PAINT_FLAG_TABLE_BG_PAINT 0x00000001
+//Cells should paint their backgrounds only, no children
+#define NS_PAINT_FLAG_TABLE_CELL_BG_PASS 0x00000002
+
+class nsIFrame;
+class nsTableFrame;
+class nsTableRowGroupFrame;
+class nsTableRowFrame;
+class nsTableCellFrame;
+
+class TableBackgroundPainter
+{
+ /*
+ * Helper class for painting table backgrounds
+ *
+ */
+
+ typedef mozilla::image::DrawResult DrawResult;
+
+ public:
+
+ enum Origin { eOrigin_Table, eOrigin_TableRowGroup, eOrigin_TableRow };
+
+ /** Public constructor
+ * @param aTableFrame - the table's table frame
+ * @param aOrigin - what type of table frame is creating this instance
+ * @param aPresContext - the presentation context
+ * @param aRenderingContext - the rendering context
+ * @param aDirtyRect - the area that needs to be painted,
+ * relative to aRenderingContext
+ * @param aPt - offset of the table frame relative to
+ * aRenderingContext
+ * @param aBGPaintFlags - Flags of the nsCSSRendering::PAINTBG_* variety
+ */
+ TableBackgroundPainter(nsTableFrame* aTableFrame,
+ Origin aOrigin,
+ nsPresContext* aPresContext,
+ nsRenderingContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsPoint& aPt,
+ uint32_t aBGPaintFlags);
+
+ /** Destructor */
+ ~TableBackgroundPainter();
+
+ /* ~*~ The Border Collapse Painting Issue ~*~
+
+ In border-collapse, the *table* paints the cells' borders,
+ so we need to make sure the backgrounds get painted first
+ (underneath) by doing a cell-background-only painting pass.
+ */
+
+ /* ~*~ Using nsTablePainter Background Painting ~*~
+
+ A call to PaintTable will normally paint all of the table's
+ elements (except for the table background, if aPaintTableBackground
+ is false).
+ Elements with views however, will be skipped and must create their
+ own painter to call the appropriate paint function in their ::Paint
+ method (e.g. painter.PaintRow in nsTableRow::Paint)
+ */
+
+ /** Paint background for the table frame (if requested) and its children
+ * down through cells.
+ * (Cells themselves will only be painted in border collapse)
+ * Table must do a flagged TABLE_BG_PAINT ::Paint call on its
+ * children afterwards
+ * @param aTableFrame - the table frame
+ * @param aDeflate - deflation needed to bring table's mRect
+ * to the outer grid lines in border-collapse
+ * @param aPaintTableBackground - if true, the table background
+ * is included, otherwise it isn't
+ * @returns DrawResult::SUCCESS if all painting was successful. If some
+ * painting failed or an improved result could be achieved by sync
+ * decoding images, returns another value.
+ */
+ DrawResult PaintTable(nsTableFrame* aTableFrame, const nsMargin& aDeflate,
+ bool aPaintTableBackground);
+
+ /** Paint background for the row group and its children down through cells
+ * (Cells themselves will only be painted in border collapse)
+ * Standards mode only
+ * Table Row Group must do a flagged TABLE_BG_PAINT ::Paint call on its
+ * children afterwards
+ * @param aFrame - the table row group frame
+ * @returns DrawResult::SUCCESS if all painting was successful. If some
+ * painting failed or an improved result could be achieved by sync
+ * decoding images, returns another value.
+ */
+ DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame);
+
+ /** Paint background for the row and its children down through cells
+ * (Cells themselves will only be painted in border collapse)
+ * Standards mode only
+ * Table Row must do a flagged TABLE_BG_PAINT ::Paint call on its
+ * children afterwards
+ * @param aFrame - the table row frame
+ * @returns DrawResult::SUCCESS if all painting was successful. If some
+ * painting failed or an improved result could be achieved by sync
+ * decoding images, returns another value.
+ */
+ DrawResult PaintRow(nsTableRowFrame* aFrame);
+
+ private:
+ struct TableBackgroundData;
+
+ /** Paint table frame's background
+ * @param aTableFrame - the table frame
+ * @param aFirstRowGroup - the first (in layout order) row group
+ * may be null
+ * @param aLastRowGroup - the last (in layout order) row group
+ * may be null
+ * @param aDeflate - adjustment to frame's rect (used for quirks BC)
+ * may be null
+ */
+ DrawResult PaintTableFrame(nsTableFrame* aTableFrame,
+ nsTableRowGroupFrame* aFirstRowGroup,
+ nsTableRowGroupFrame* aLastRowGroup,
+ const nsMargin& aDeflate);
+
+ /* aPassThrough params indicate whether to paint the element or to just
+ * pass through and paint underlying layers only.
+ * aRowGroupBGData is not a const reference because the function modifies
+ * its copy. Same for aRowBGData in PaintRow.
+ * See Public versions for function descriptions
+ */
+ DrawResult PaintRowGroup(nsTableRowGroupFrame* aFrame,
+ TableBackgroundData aRowGroupBGData,
+ bool aPassThrough);
+
+ DrawResult PaintRow(nsTableRowFrame* aFrame,
+ const TableBackgroundData& aRowGroupBGData,
+ TableBackgroundData aRowBGData,
+ bool aPassThrough);
+
+ /** Paint table background layers for this cell space
+ * Also paints cell's own background in border-collapse mode
+ * @param aCell - the cell
+ * @param aRowGroupBGData - background drawing info for the row group
+ * @param aRowBGData - background drawing info for the row
+ * @param aCellBGRect - background rect for the cell
+ * @param aRowBGRect - background rect for the row
+ * @param aRowGroupBGRect - background rect for the row group
+ * @param aColBGRect - background rect for the column and column group
+ * @param aPassSelf - pass this cell; i.e. paint only underlying layers
+ */
+ DrawResult PaintCell(nsTableCellFrame* aCell,
+ const TableBackgroundData& aRowGroupBGData,
+ const TableBackgroundData& aRowBGData,
+ nsRect& aCellBGRect,
+ nsRect& aRowBGRect,
+ nsRect& aRowGroupBGRect,
+ nsRect& aColBGRect,
+ bool aPassSelf);
+
+ /** Compute table background layer positions for this cell space
+ * @param aCell - the cell
+ * @param aRowGroupBGData - background drawing info for the row group
+ * @param aRowBGData - background drawing info for the row
+ * @param aCellBGRectOut - outparam: background rect for the cell
+ * @param aRowBGRectOut - outparam: background rect for the row
+ * @param aRowGroupBGRectOut - outparam: background rect for the row group
+ * @param aColBGRectOut - outparam: background rect for the column
+ and column group
+ */
+ void ComputeCellBackgrounds(nsTableCellFrame* aCell,
+ const TableBackgroundData& aRowGroupBGData,
+ const TableBackgroundData& aRowBGData,
+ nsRect& aCellBGRect,
+ nsRect& aRowBGRect,
+ nsRect& aRowGroupBGRect,
+ nsRect& aColBGRect);
+
+ /** Translate mRenderingContext, mDirtyRect, and mCols' column and
+ * colgroup coords
+ * @param aDX - origin's x-coord change
+ * @param aDY - origin's y-coord change
+ */
+ void TranslateContext(nscoord aDX,
+ nscoord aDY);
+
+ struct TableBackgroundData {
+ public:
+ /**
+ * Construct an empty TableBackgroundData instance, which is invisible.
+ */
+ TableBackgroundData();
+
+ /**
+ * Construct a TableBackgroundData instance for a frame. Visibility will
+ * be derived from the frame and can be overridden using MakeInvisible().
+ */
+ explicit TableBackgroundData(nsIFrame* aFrame);
+
+ /** Destructor */
+ ~TableBackgroundData() {}
+
+ /** Data is valid & frame is visible */
+ bool IsVisible() const { return mVisible; }
+
+ /** Override visibility of the frame, force it to be invisible */
+ void MakeInvisible() { mVisible = false; }
+
+ /** True if need to set border-collapse border; must call SetFull beforehand */
+ bool ShouldSetBCBorder() const;
+
+ /** Set border-collapse border with aBorderWidth as widths */
+ void SetBCBorder(const nsMargin& aBorderWidth);
+
+ /**
+ * @param aZeroBorder An nsStyleBorder instance that has been initialized
+ * for the right nsPresContext, with all border widths
+ * set to zero and border styles set to solid.
+ * @return The nsStyleBorder that should be used for rendering
+ * this background.
+ */
+ nsStyleBorder StyleBorder(const nsStyleBorder& aZeroBorder) const;
+
+ nsIFrame* const mFrame;
+
+ /** mRect is the rect of mFrame in the current coordinate system */
+ nsRect mRect;
+
+ private:
+ nsMargin mSynthBorderWidths;
+ bool mVisible;
+ bool mUsesSynthBorder;
+ };
+
+ struct ColData {
+ ColData(nsIFrame* aFrame, TableBackgroundData& aColGroupBGData);
+ TableBackgroundData mCol;
+ TableBackgroundData& mColGroup; // reference to col's parent colgroup's data, owned by TablePainter in mColGroups
+ };
+
+ nsPresContext* mPresContext;
+ nsRenderingContext& mRenderingContext;
+ nsPoint mRenderPt;
+ nsRect mDirtyRect;
+#ifdef DEBUG
+ nsCompatibility mCompatMode;
+#endif
+ bool mIsBorderCollapse;
+ Origin mOrigin; //user's table frame type
+
+ nsTArray<TableBackgroundData> mColGroups;
+ nsTArray<ColData> mCols;
+ size_t mNumCols;
+
+ nsStyleBorder mZeroBorder; //cached zero-width border
+ uint32_t mBGPaintFlags;
+};
+
+#endif
diff --git a/layout/tables/nsTableRowFrame.cpp b/layout/tables/nsTableRowFrame.cpp
new file mode 100644
index 0000000000..81b5d6699a
--- /dev/null
+++ b/layout/tables/nsTableRowFrame.cpp
@@ -0,0 +1,1517 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Maybe.h"
+
+#include "nsTableRowFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsTableFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsCSSRendering.h"
+#include "nsHTMLParts.h"
+#include "nsTableColGroupFrame.h"
+#include "nsTableColFrame.h"
+#include "nsCOMPtr.h"
+#include "nsDisplayList.h"
+#include "nsIFrameInlines.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+namespace mozilla {
+
+struct TableCellReflowInput : public ReflowInput
+{
+ TableCellReflowInput(nsPresContext* aPresContext,
+ const ReflowInput& aParentReflowInput,
+ nsIFrame* aFrame,
+ const LogicalSize& aAvailableSpace,
+ uint32_t aFlags = 0)
+ : ReflowInput(aPresContext, aParentReflowInput, aFrame,
+ aAvailableSpace, nullptr, aFlags)
+ {
+ }
+
+ void FixUp(const LogicalSize& aAvailSpace);
+};
+
+} // namespace mozilla
+
+void TableCellReflowInput::FixUp(const LogicalSize& aAvailSpace)
+{
+ // fix the mComputed values during a pass 2 reflow since the cell can be a percentage base
+ NS_WARNING_ASSERTION(
+ NS_UNCONSTRAINEDSIZE != aAvailSpace.ISize(mWritingMode),
+ "have unconstrained inline-size; this should only result from very large "
+ "sizes, not attempts at intrinsic inline size calculation");
+ if (NS_UNCONSTRAINEDSIZE != ComputedISize()) {
+ nscoord computedISize = aAvailSpace.ISize(mWritingMode) -
+ ComputedLogicalBorderPadding().IStartEnd(mWritingMode);
+ computedISize = std::max(0, computedISize);
+ SetComputedISize(computedISize);
+ }
+ if (NS_UNCONSTRAINEDSIZE != ComputedBSize() &&
+ NS_UNCONSTRAINEDSIZE != aAvailSpace.BSize(mWritingMode)) {
+ nscoord computedBSize = aAvailSpace.BSize(mWritingMode) -
+ ComputedLogicalBorderPadding().BStartEnd(mWritingMode);
+ computedBSize = std::max(0, computedBSize);
+ SetComputedBSize(computedBSize);
+ }
+}
+
+void
+nsTableRowFrame::InitChildReflowInput(nsPresContext& aPresContext,
+ const LogicalSize& aAvailSize,
+ bool aBorderCollapse,
+ TableCellReflowInput& aReflowInput)
+{
+ nsMargin collapseBorder;
+ nsMargin* pCollapseBorder = nullptr;
+ if (aBorderCollapse) {
+ // we only reflow cells, so don't need to check frame type
+ nsBCTableCellFrame* bcCellFrame = (nsBCTableCellFrame*)aReflowInput.mFrame;
+ if (bcCellFrame) {
+ WritingMode wm = GetWritingMode();
+ collapseBorder = bcCellFrame->GetBorderWidth(wm).GetPhysicalMargin(wm);
+ pCollapseBorder = &collapseBorder;
+ }
+ }
+ aReflowInput.Init(&aPresContext, nullptr, pCollapseBorder);
+ aReflowInput.FixUp(aAvailSize);
+}
+
+void
+nsTableRowFrame::SetFixedBSize(nscoord aValue)
+{
+ nscoord bsize = std::max(0, aValue);
+ if (HasFixedBSize()) {
+ if (bsize > mStyleFixedBSize) {
+ mStyleFixedBSize = bsize;
+ }
+ }
+ else {
+ mStyleFixedBSize = bsize;
+ if (bsize > 0) {
+ SetHasFixedBSize(true);
+ }
+ }
+}
+
+void
+nsTableRowFrame::SetPctBSize(float aPctValue,
+ bool aForce)
+{
+ nscoord bsize = std::max(0, NSToCoordRound(aPctValue * 100.0f));
+ if (HasPctBSize()) {
+ if ((bsize > mStylePctBSize) || aForce) {
+ mStylePctBSize = bsize;
+ }
+ }
+ else {
+ mStylePctBSize = bsize;
+ if (bsize > 0) {
+ SetHasPctBSize(true);
+ }
+ }
+}
+
+/* ----------- nsTableRowFrame ---------- */
+
+NS_QUERYFRAME_HEAD(nsTableRowFrame)
+ NS_QUERYFRAME_ENTRY(nsTableRowFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+nsTableRowFrame::nsTableRowFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+{
+ mBits.mRowIndex = mBits.mFirstInserted = 0;
+ ResetBSize(0);
+}
+
+nsTableRowFrame::~nsTableRowFrame()
+{
+}
+
+void
+nsTableRowFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ // Let the base class do its initialization
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ NS_ASSERTION(mozilla::StyleDisplay::TableRow == StyleDisplay()->mDisplay,
+ "wrong display on table row frame");
+
+ if (aPrevInFlow) {
+ // Set the row index
+ nsTableRowFrame* rowFrame = (nsTableRowFrame*)aPrevInFlow;
+
+ SetRowIndex(rowFrame->GetRowIndex());
+ }
+}
+
+void
+nsTableRowFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
+ nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot);
+ }
+
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+/* virtual */ void
+nsTableRowFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsContainerFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (!aOldStyleContext) //avoid this on init
+ return;
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse() &&
+ tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) {
+ TableArea damageArea(0, GetRowIndex(), tableFrame->GetColCount(), 1);
+ tableFrame->AddBCDamageArea(damageArea);
+ }
+}
+
+void
+nsTableRowFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+
+ DrainSelfOverflowList(); // ensure the last frame is in mFrames
+ const nsFrameList::Slice& newCells = mFrames.AppendFrames(nullptr, aFrameList);
+
+ // Add the new cell frames to the table
+ nsTableFrame* tableFrame = GetTableFrame();
+ for (nsFrameList::Enumerator e(newCells) ; !e.AtEnd(); e.Next()) {
+ nsIFrame *childFrame = e.get();
+ NS_ASSERTION(IS_TABLE_CELL(childFrame->GetType()),
+ "Not a table cell frame/pseudo frame construction failure");
+ tableFrame->AppendCell(static_cast<nsTableCellFrame&>(*childFrame), GetRowIndex());
+ }
+
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ tableFrame->SetGeometryDirty();
+}
+
+
+void
+nsTableRowFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+ DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
+ //Insert Frames in the frame list
+ const nsFrameList::Slice& newCells = mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
+
+ // Get the table frame
+ nsTableFrame* tableFrame = GetTableFrame();
+ nsIAtom* cellFrameType = tableFrame->IsBorderCollapse() ? nsGkAtoms::bcTableCellFrame : nsGkAtoms::tableCellFrame;
+ nsTableCellFrame* prevCellFrame = (nsTableCellFrame *)nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, cellFrameType);
+ nsTArray<nsTableCellFrame*> cellChildren;
+ for (nsFrameList::Enumerator e(newCells); !e.AtEnd(); e.Next()) {
+ nsIFrame *childFrame = e.get();
+ NS_ASSERTION(IS_TABLE_CELL(childFrame->GetType()),
+ "Not a table cell frame/pseudo frame construction failure");
+ cellChildren.AppendElement(static_cast<nsTableCellFrame*>(childFrame));
+ }
+ // insert the cells into the cell map
+ int32_t colIndex = -1;
+ if (prevCellFrame) {
+ prevCellFrame->GetColIndex(colIndex);
+ }
+ tableFrame->InsertCells(cellChildren, GetRowIndex(), colIndex);
+
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ tableFrame->SetGeometryDirty();
+}
+
+void
+nsTableRowFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+
+ MOZ_ASSERT((nsTableCellFrame*)do_QueryFrame(aOldFrame));
+ nsTableCellFrame* cellFrame = static_cast<nsTableCellFrame*>(aOldFrame);
+ // remove the cell from the cell map
+ nsTableFrame* tableFrame = GetTableFrame();
+ tableFrame->RemoveCell(cellFrame, GetRowIndex());
+
+ // Remove the frame and destroy it
+ mFrames.DestroyFrame(aOldFrame);
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ tableFrame->SetGeometryDirty();
+}
+
+/* virtual */ nsMargin
+nsTableRowFrame::GetUsedMargin() const
+{
+ return nsMargin(0,0,0,0);
+}
+
+/* virtual */ nsMargin
+nsTableRowFrame::GetUsedBorder() const
+{
+ return nsMargin(0,0,0,0);
+}
+
+/* virtual */ nsMargin
+nsTableRowFrame::GetUsedPadding() const
+{
+ return nsMargin(0,0,0,0);
+}
+
+nscoord
+GetBSizeOfRowsSpannedBelowFirst(nsTableCellFrame& aTableCellFrame,
+ nsTableFrame& aTableFrame,
+ const WritingMode aWM)
+{
+ nscoord bsize = 0;
+ int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(aTableCellFrame);
+ // add in bsize of rows spanned beyond the 1st one
+ nsIFrame* nextRow = aTableCellFrame.GetParent()->GetNextSibling();
+ for (int32_t rowX = 1; ((rowX < rowSpan) && nextRow);) {
+ if (nsGkAtoms::tableRowFrame == nextRow->GetType()) {
+ bsize += nextRow->BSize(aWM);
+ rowX++;
+ }
+ bsize += aTableFrame.GetRowSpacing(rowX);
+ nextRow = nextRow->GetNextSibling();
+ }
+ return bsize;
+}
+
+nsTableCellFrame*
+nsTableRowFrame::GetFirstCell()
+{
+ for (nsIFrame* childFrame : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
+ if (cellFrame) {
+ return cellFrame;
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Post-reflow hook. This is where the table row does its post-processing
+ */
+void
+nsTableRowFrame::DidResize()
+{
+ // Resize and re-align the cell frames based on our row bsize
+ nsTableFrame* tableFrame = GetTableFrame();
+
+ WritingMode wm = GetWritingMode();
+ ReflowOutput desiredSize(wm);
+ desiredSize.SetSize(wm, GetLogicalSize(wm));
+ desiredSize.SetOverflowAreasToDesiredBounds();
+
+ nsSize containerSize = mRect.Size();
+
+ for (nsIFrame* childFrame : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
+ if (cellFrame) {
+ nscoord cellBSize = BSize(wm) +
+ GetBSizeOfRowsSpannedBelowFirst(*cellFrame, *tableFrame, wm);
+
+ // If the bsize for the cell has changed, we need to reset it;
+ // and in vertical-rl mode, we need to update the cell's block position
+ // to account for the containerSize, which may not have been known
+ // earlier, so we always apply it here.
+ LogicalSize cellSize = cellFrame->GetLogicalSize(wm);
+ if (cellSize.BSize(wm) != cellBSize || wm.IsVerticalRL()) {
+ nsRect cellOldRect = cellFrame->GetRect();
+ nsRect cellVisualOverflow = cellFrame->GetVisualOverflowRect();
+
+ if (wm.IsVerticalRL()) {
+ // Get the old position of the cell, as we want to preserve its
+ // inline coordinate.
+ LogicalPoint oldPos =
+ cellFrame->GetLogicalPosition(wm, containerSize);
+
+ // The cell should normally be aligned with the row's block-start,
+ // so set the B component of the position to zero:
+ LogicalPoint newPos(wm, oldPos.I(wm), 0);
+
+ // ...unless relative positioning is in effect, in which case the
+ // cell may have been moved away from the row's block-start
+ if (cellFrame->IsRelativelyPositioned()) {
+ // Find out where the cell would have been without relative
+ // positioning.
+ LogicalPoint oldNormalPos =
+ cellFrame->GetLogicalNormalPosition(wm, containerSize);
+ // The difference (if any) between oldPos and oldNormalPos reflects
+ // relative positioning that was applied to the cell, and which we
+ // need to incorporate when resetting the position.
+ newPos.B(wm) = oldPos.B(wm) - oldNormalPos.B(wm);
+ }
+
+ if (oldPos != newPos) {
+ cellFrame->SetPosition(wm, newPos, containerSize);
+ nsTableFrame::RePositionViews(cellFrame);
+ }
+ }
+
+ cellSize.BSize(wm) = cellBSize;
+ cellFrame->SetSize(wm, cellSize);
+ nsTableFrame::InvalidateTableFrame(cellFrame, cellOldRect,
+ cellVisualOverflow,
+ false);
+ }
+
+ // realign cell content based on the new bsize. We might be able to
+ // skip this if the bsize didn't change... maybe. Hard to tell.
+ cellFrame->BlockDirAlignChild(wm, mMaxCellAscent);
+
+ // Always store the overflow, even if the height didn't change, since
+ // we'll lose part of our overflow area otherwise.
+ ConsiderChildOverflow(desiredSize.mOverflowAreas, cellFrame);
+
+ // Note that if the cell's *content* needs to change in response
+ // to this height, it will get a special bsize reflow.
+ }
+ }
+ FinishAndStoreOverflow(&desiredSize);
+ if (HasView()) {
+ nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this, GetView(),
+ desiredSize.VisualOverflow(), 0);
+ }
+ // Let our base class do the usual work
+}
+
+// returns max-ascent amongst all cells that have 'vertical-align: baseline'
+// *including* cells with rowspans
+nscoord nsTableRowFrame::GetMaxCellAscent() const
+{
+ return mMaxCellAscent;
+}
+
+nscoord nsTableRowFrame::GetRowBaseline(WritingMode aWM)
+{
+ if (mMaxCellAscent) {
+ return mMaxCellAscent;
+ }
+
+ // If we don't have a baseline on any of the cells we go for the lowest
+ // content edge of the inner block frames.
+ // Every table cell has a cell frame with its border and padding. Inside
+ // the cell is a block frame. The cell is as high as the tallest cell in
+ // the parent row. As a consequence the block frame might not touch both
+ // the top and the bottom padding of it parent cell frame at the same time.
+ //
+ // bbbbbbbbbbbbbbbbbb cell border: b
+ // bppppppppppppppppb cell padding: p
+ // bpxxxxxxxxxxxxxxpb inner block: x
+ // bpx xpb
+ // bpx xpb
+ // bpx xpb
+ // bpxxxxxxxxxxxxxxpb base line
+ // bp pb
+ // bp pb
+ // bppppppppppppppppb
+ // bbbbbbbbbbbbbbbbbb
+
+ nscoord ascent = 0;
+ nsSize containerSize = GetSize();
+ for (nsIFrame* childFrame : mFrames) {
+ if (IS_TABLE_CELL(childFrame->GetType())) {
+ nsIFrame* firstKid = childFrame->PrincipalChildList().FirstChild();
+ ascent = std::max(ascent,
+ LogicalRect(aWM, firstKid->GetNormalRect(),
+ containerSize).BEnd(aWM));
+ }
+ }
+ return ascent;
+}
+
+nscoord
+nsTableRowFrame::GetInitialBSize(nscoord aPctBasis) const
+{
+ nscoord bsize = 0;
+ if ((aPctBasis > 0) && HasPctBSize()) {
+ bsize = NSToCoordRound(GetPctBSize() * (float)aPctBasis);
+ }
+ if (HasFixedBSize()) {
+ bsize = std::max(bsize, GetFixedBSize());
+ }
+ return std::max(bsize, GetContentBSize());
+}
+
+void
+nsTableRowFrame::ResetBSize(nscoord aFixedBSize)
+{
+ SetHasFixedBSize(false);
+ SetHasPctBSize(false);
+ SetFixedBSize(0);
+ SetPctBSize(0);
+ SetContentBSize(0);
+
+ if (aFixedBSize > 0) {
+ SetFixedBSize(aFixedBSize);
+ }
+
+ mMaxCellAscent = 0;
+ mMaxCellDescent = 0;
+}
+
+void
+nsTableRowFrame::UpdateBSize(nscoord aBSize,
+ nscoord aAscent,
+ nscoord aDescent,
+ nsTableFrame* aTableFrame,
+ nsTableCellFrame* aCellFrame)
+{
+ if (!aTableFrame || !aCellFrame) {
+ NS_ASSERTION(false , "invalid call");
+ return;
+ }
+
+ if (aBSize != NS_UNCONSTRAINEDSIZE) {
+ if (!(aCellFrame->HasVerticalAlignBaseline())) { // only the cell's height matters
+ if (GetInitialBSize() < aBSize) {
+ int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame);
+ if (rowSpan == 1) {
+ SetContentBSize(aBSize);
+ }
+ }
+ }
+ else { // the alignment on the baseline can change the bsize
+ NS_ASSERTION((aAscent != NS_UNCONSTRAINEDSIZE) &&
+ (aDescent != NS_UNCONSTRAINEDSIZE), "invalid call");
+ // see if this is a long ascender
+ if (mMaxCellAscent < aAscent) {
+ mMaxCellAscent = aAscent;
+ }
+ // see if this is a long descender and without rowspan
+ if (mMaxCellDescent < aDescent) {
+ int32_t rowSpan = aTableFrame->GetEffectiveRowSpan(*aCellFrame);
+ if (rowSpan == 1) {
+ mMaxCellDescent = aDescent;
+ }
+ }
+ // keep the tallest bsize in sync
+ if (GetInitialBSize() < mMaxCellAscent + mMaxCellDescent) {
+ SetContentBSize(mMaxCellAscent + mMaxCellDescent);
+ }
+ }
+ }
+}
+
+nscoord
+nsTableRowFrame::CalcBSize(const ReflowInput& aReflowInput)
+{
+ nsTableFrame* tableFrame = GetTableFrame();
+ nscoord computedBSize = (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedBSize())
+ ? 0 : aReflowInput.ComputedBSize();
+ ResetBSize(computedBSize);
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ const nsStylePosition* position = StylePosition();
+ const nsStyleCoord& bsizeStyleCoord = position->BSize(wm);
+ if (bsizeStyleCoord.ConvertsToLength()) {
+ SetFixedBSize(nsRuleNode::ComputeCoordPercentCalc(bsizeStyleCoord, 0));
+ }
+ else if (eStyleUnit_Percent == bsizeStyleCoord.GetUnit()) {
+ SetPctBSize(bsizeStyleCoord.GetPercentValue());
+ }
+ // calc() with percentages is treated like 'auto' on table rows.
+
+ for (nsIFrame* kidFrame : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
+ if (cellFrame) {
+ MOZ_ASSERT(cellFrame->GetWritingMode() == wm);
+ LogicalSize desSize = cellFrame->GetDesiredSize();
+ if ((NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) && !GetPrevInFlow()) {
+ CalculateCellActualBSize(cellFrame, desSize.BSize(wm), wm);
+ }
+ // bsize may have changed, adjust descent to absorb any excess difference
+ nscoord ascent;
+ if (!kidFrame->PrincipalChildList().FirstChild()->PrincipalChildList().FirstChild())
+ ascent = desSize.BSize(wm);
+ else
+ ascent = cellFrame->GetCellBaseline();
+ nscoord descent = desSize.BSize(wm) - ascent;
+ UpdateBSize(desSize.BSize(wm), ascent, descent, tableFrame, cellFrame);
+ }
+ }
+ return GetInitialBSize();
+}
+
+/**
+ * We need a custom display item for table row backgrounds. This is only used
+ * when the table row is the root of a stacking context (e.g., has 'opacity').
+ * Table row backgrounds can extend beyond the row frame bounds, when
+ * the row contains row-spanning cells.
+ */
+class nsDisplayTableRowBackground : public nsDisplayTableItem {
+public:
+ nsDisplayTableRowBackground(nsDisplayListBuilder* aBuilder,
+ nsTableRowFrame* aFrame) :
+ nsDisplayTableItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayTableRowBackground);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayTableRowBackground() {
+ MOZ_COUNT_DTOR(nsDisplayTableRowBackground);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("TableRowBackground", TYPE_TABLE_ROW_BACKGROUND)
+};
+
+void
+nsDisplayTableRowBackground::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ auto rowFrame = static_cast<nsTableRowFrame*>(mFrame);
+ TableBackgroundPainter painter(rowFrame->GetTableFrame(),
+ TableBackgroundPainter::eOrigin_TableRow,
+ mFrame->PresContext(), *aCtx,
+ mVisibleRect, ToReferenceFrame(),
+ aBuilder->GetBackgroundPaintFlags());
+
+ DrawResult result = painter.PaintRow(rowFrame);
+ nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
+}
+
+void
+nsTableRowFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsDisplayTableItem* item = nullptr;
+ if (IsVisibleInSelection(aBuilder)) {
+ bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext();
+ if (isRoot) {
+ // This background is created regardless of whether this frame is
+ // visible or not. Visibility decisions are delegated to the
+ // table background painter.
+ // We would use nsDisplayGeneric for this rare case except that we
+ // need the background to be larger than the row frame in some
+ // cases.
+ item = new (aBuilder) nsDisplayTableRowBackground(aBuilder, this);
+ aLists.BorderBackground()->AppendNewToTop(item);
+ }
+ }
+ nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item);
+}
+
+nsIFrame::LogicalSides
+nsTableRowFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
+{
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return LogicalSides();
+ }
+
+ LogicalSides skip;
+ if (nullptr != GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (nullptr != GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+
+// Calculate the cell's actual bsize given its pass2 bsize.
+// Takes into account the specified bsize (in the style).
+// Modifies the desired bsize that is passed in.
+nsresult
+nsTableRowFrame::CalculateCellActualBSize(nsTableCellFrame* aCellFrame,
+ nscoord& aDesiredBSize,
+ WritingMode aWM)
+{
+ nscoord specifiedBSize = 0;
+
+ // Get the bsize specified in the style information
+ const nsStylePosition* position = aCellFrame->StylePosition();
+
+ int32_t rowSpan = GetTableFrame()->GetEffectiveRowSpan(*aCellFrame);
+
+ const nsStyleCoord& bsizeStyleCoord = position->BSize(aWM);
+ switch (bsizeStyleCoord.GetUnit()) {
+ case eStyleUnit_Calc: {
+ if (bsizeStyleCoord.CalcHasPercent()) {
+ // Treat this like "auto"
+ break;
+ }
+ // Fall through to the coord case
+ MOZ_FALLTHROUGH;
+ }
+ case eStyleUnit_Coord: {
+ // In quirks mode, table cell isize should be content-box, but bsize
+ // should be border-box.
+ // Because of this historic anomaly, we do not use quirk.css
+ // (since we can't specify one value of box-sizing for isize and another
+ // for bsize)
+ specifiedBSize = nsRuleNode::ComputeCoordPercentCalc(bsizeStyleCoord, 0);
+ if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks &&
+ position->mBoxSizing == StyleBoxSizing::Content) {
+ specifiedBSize +=
+ aCellFrame->GetLogicalUsedBorderAndPadding(aWM).BStartEnd(aWM);
+ }
+
+ if (1 == rowSpan) {
+ SetFixedBSize(specifiedBSize);
+ }
+ break;
+ }
+ case eStyleUnit_Percent: {
+ if (1 == rowSpan) {
+ SetPctBSize(bsizeStyleCoord.GetPercentValue());
+ }
+ // pct bsizes are handled when all of the cells are finished,
+ // so don't set specifiedBSize
+ break;
+ }
+ case eStyleUnit_Auto:
+ default:
+ break;
+ }
+
+ // If the specified bsize is greater than the desired bsize,
+ // then use the specified bsize
+ if (specifiedBSize > aDesiredBSize) {
+ aDesiredBSize = specifiedBSize;
+ }
+
+ return NS_OK;
+}
+
+// Calculates the available isize for the table cell based on the known
+// column isizes taking into account column spans and column spacing
+static nscoord
+CalcAvailISize(nsTableFrame& aTableFrame,
+ nsTableCellFrame& aCellFrame)
+{
+ nscoord cellAvailISize = 0;
+ int32_t colIndex;
+ aCellFrame.GetColIndex(colIndex);
+ int32_t colspan = aTableFrame.GetEffectiveColSpan(aCellFrame);
+ NS_ASSERTION(colspan > 0, "effective colspan should be positive");
+ nsTableFrame* fifTable =
+ static_cast<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;
+}
+
+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 = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible);
+ nsIFrame* cgFrame = colFrame->GetParent();
+ const nsStyleVisibility* groupVis = cgFrame->StyleVisibility();
+ bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE ==
+ groupVis->mVisible);
+ isCollapsed = collapseCol || collapseGroup;
+ if (!isCollapsed)
+ space += fifTable->GetColumnISizeFromFirstInFlow(colIdx);
+ }
+ if (!isCollapsed && aTableFrame.ColumnHasCellSpacingBefore(colIdx)) {
+ space += aTableFrame.GetColSpacing(colIdx - 1);
+ }
+ }
+ return space;
+}
+
+// subtract the bsizes of aRow's prev in flows from the unpaginated bsize
+static
+nscoord CalcBSizeFromUnpaginatedBSize(nsTableRowFrame& aRow,
+ WritingMode aWM)
+{
+ nscoord bsize = 0;
+ nsTableRowFrame* firstInFlow =
+ static_cast<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 = NS_FRAME_COMPLETE;
+
+ // XXXldb Should we be checking constrained bsize instead?
+ const bool isPaginated = aPresContext->IsPaginated();
+ const bool borderCollapse = aTableFrame.IsBorderCollapse();
+
+ int32_t cellColSpan = 1; // must be defined here so it's set properly for non-cell kids
+
+ // remember the col index of the previous cell to handle rowspans into this row
+ int32_t prevColIndex = -1;
+ nscoord iCoord = 0; // running total of children inline-coord offset
+
+ // This computes the max of all cell bsizes
+ nscoord cellMaxBSize = 0;
+
+ // Reflow each of our existing cell frames
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nsSize containerSize =
+ aReflowInput.ComputedSizeAsContainerIfConstrained();
+
+ for (nsIFrame* kidFrame : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
+ if (!cellFrame) {
+ // XXXldb nsCSSFrameConstructor needs to enforce this!
+ NS_NOTREACHED("yikes, a non-row child");
+
+ // it's an unknown frame type, give it a generic reflow and ignore the results
+ TableCellReflowInput
+ kidReflowInput(aPresContext, aReflowInput, kidFrame,
+ LogicalSize(kidFrame->GetWritingMode(), 0, 0),
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(*aPresContext, LogicalSize(wm), false, kidReflowInput);
+ ReflowOutput desiredSize(aReflowInput);
+ nsReflowStatus status;
+ ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowInput, 0, 0, 0, status);
+ kidFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED);
+
+ continue;
+ }
+
+ // See if we should only reflow the dirty child frames
+ bool doReflowChild = true;
+ if (!aReflowInput.ShouldReflowAllKids() &&
+ !aTableFrame.IsGeometryDirty() &&
+ !NS_SUBTREE_DIRTY(kidFrame)) {
+ if (!aReflowInput.mFlags.mSpecialBSizeReflow)
+ doReflowChild = false;
+ }
+ else if ((NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize())) {
+ // We don't reflow a rowspan >1 cell here with a constrained bsize.
+ // That happens in nsTableRowGroupFrame::SplitSpanningCells.
+ if (aTableFrame.GetEffectiveRowSpan(*cellFrame) > 1) {
+ doReflowChild = false;
+ }
+ }
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) {
+ if (!isPaginated &&
+ !cellFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
+ continue;
+ }
+ }
+
+ int32_t cellColIndex;
+ cellFrame->GetColIndex(cellColIndex);
+ cellColSpan = aTableFrame.GetEffectiveColSpan(*cellFrame);
+
+ // If the adjacent cell is in a prior row (because of a rowspan) add in the space
+ if (prevColIndex != (cellColIndex - 1)) {
+ iCoord += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan, aTableFrame,
+ false);
+ }
+
+ // remember the rightmost (ltr) or leftmost (rtl) column this cell spans into
+ prevColIndex = cellColIndex + (cellColSpan - 1);
+
+ // Reflow the child frame
+ nsRect kidRect = kidFrame->GetRect();
+ LogicalPoint origKidNormalPosition =
+ kidFrame->GetLogicalNormalPosition(wm, containerSize);
+ // All cells' no-relative-positioning position should be snapped to the
+ // row's bstart edge.
+ // This doesn't hold in vertical-rl mode, where we don't yet know the
+ // correct containerSize for the row frame. In that case, we'll have to
+ // fix up child positions later, after determining our desiredSize.
+ NS_ASSERTION(origKidNormalPosition.B(wm) == 0 || wm.IsVerticalRL(),
+ "unexpected kid position");
+
+ nsRect kidVisualOverflow = kidFrame->GetVisualOverflowRect();
+ LogicalPoint kidPosition(wm, iCoord, 0);
+ bool firstReflow = kidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+
+ if (doReflowChild) {
+ // Calculate the available isize for the table cell using the known
+ // column isizes
+ nscoord availCellISize = CalcAvailISize(aTableFrame, *cellFrame);
+
+ Maybe<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?
+ WritingMode wm = aReflowInput.GetWritingMode();
+ NS_ASSERTION(cellFrame->GetWritingMode() == wm,
+ "expected consistent writing-mode within table");
+ LogicalSize cellDesiredSize = cellFrame->GetDesiredSize();
+ if ((availCellISize != cellFrame->GetPriorAvailISize()) ||
+ (cellDesiredSize.ISize(wm) > cellFrame->GetPriorAvailISize()) ||
+ HasAnyStateBits(NS_FRAME_IS_DIRTY) ||
+ isPaginated ||
+ NS_SUBTREE_DIRTY(cellFrame) ||
+ // See if it needs a special reflow, or if it had one that we need to undo.
+ cellFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE) ||
+ HasPctBSize()) {
+ // Reflow the cell to fit the available isize, bsize
+ // XXX The old IR_ChildIsDirty code used availCellISize here.
+ LogicalSize kidAvailSize(wm, availCellISize, aReflowInput.AvailableBSize());
+
+ // Reflow the child
+ kidReflowInput.emplace(aPresContext, aReflowInput, kidFrame,
+ kidAvailSize,
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(*aPresContext, kidAvailSize, borderCollapse,
+ *kidReflowInput);
+
+ nsReflowStatus status;
+ ReflowChild(kidFrame, aPresContext, desiredSize, *kidReflowInput,
+ wm, kidPosition, containerSize, 0, status);
+
+ // allow the table to determine if/how the table needs to be rebalanced
+ // If any of the cells are not complete, then we're not complete
+ if (NS_FRAME_IS_NOT_COMPLETE(status)) {
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ }
+ } else {
+ if (iCoord != origKidNormalPosition.I(wm)) {
+ kidFrame->InvalidateFrameSubtree();
+ }
+
+ desiredSize.SetSize(wm, cellDesiredSize);
+ desiredSize.mOverflowAreas = cellFrame->GetOverflowAreas();
+
+ // if we are in a floated table, our position is not yet established, so we cannot reposition our views
+ // the containing block will do this for us after positioning the table
+ if (!aTableFrame.IsFloating()) {
+ // Because we may have moved the frame we need to make sure any views are
+ // positioned properly. We have to do this, because any one of our parent
+ // frames could have moved and we have no way of knowing...
+ nsTableFrame::RePositionViews(kidFrame);
+ }
+ }
+
+ if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) {
+ if (!GetPrevInFlow()) {
+ // Calculate the cell's actual bsize given its pass2 bsize. This
+ // function takes into account the specified bsize (in the style)
+ CalculateCellActualBSize(cellFrame, desiredSize.BSize(wm), wm);
+ }
+ // bsize may have changed, adjust descent to absorb any excess difference
+ nscoord ascent;
+ if (!kidFrame->PrincipalChildList().FirstChild()->PrincipalChildList().FirstChild()) {
+ ascent = desiredSize.BSize(wm);
+ } else {
+ ascent = ((nsTableCellFrame *)kidFrame)->GetCellBaseline();
+ }
+ nscoord descent = desiredSize.BSize(wm) - ascent;
+ UpdateBSize(desiredSize.BSize(wm), ascent, descent, &aTableFrame, cellFrame);
+ } else {
+ cellMaxBSize = std::max(cellMaxBSize, desiredSize.BSize(wm));
+ int32_t rowSpan = aTableFrame.GetEffectiveRowSpan((nsTableCellFrame&)*kidFrame);
+ if (1 == rowSpan) {
+ SetContentBSize(cellMaxBSize);
+ }
+ }
+
+ // Place the child
+ desiredSize.ISize(wm) = availCellISize;
+
+ if (kidReflowInput) {
+ // We reflowed. Apply relative positioning in the normal way.
+ kidReflowInput->ApplyRelativePositioning(&kidPosition, containerSize);
+ } else if (kidFrame->IsRelativelyPositioned()) {
+ // We didn't reflow. Do the positioning part of what
+ // MovePositionBy does internally. (This codepath should really
+ // be merged into the else below if we can.)
+ nsMargin* computedOffsetProp =
+ kidFrame->Properties().Get(nsIFrame::ComputedOffsetProperty());
+ // Bug 975644: a position:sticky kid can end up with a null
+ // property value here.
+ LogicalMargin computedOffsets(wm, computedOffsetProp ?
+ *computedOffsetProp : nsMargin());
+ ReflowInput::ApplyRelativePositioning(kidFrame, wm, computedOffsets,
+ &kidPosition, containerSize);
+ }
+
+ // In vertical-rl mode, we are likely to have containerSize.width = 0
+ // because ComputedWidth() was NS_UNCONSTRAINEDSIZE.
+ // For cases where that's wrong, we will fix up the position later.
+ FinishReflowChild(kidFrame, aPresContext, desiredSize, nullptr,
+ wm, kidPosition, containerSize, 0);
+
+ nsTableFrame::InvalidateTableFrame(kidFrame, kidRect, kidVisualOverflow,
+ firstReflow);
+
+ iCoord += desiredSize.ISize(wm);
+ } else {
+ if (iCoord != origKidNormalPosition.I(wm)) {
+ // Invalidate the old position
+ kidFrame->InvalidateFrameSubtree();
+ // Move to the new position. As above, we need to account for relative
+ // positioning.
+ kidFrame->MovePositionBy(wm,
+ LogicalPoint(wm, iCoord - origKidNormalPosition.I(wm), 0));
+ nsTableFrame::RePositionViews(kidFrame);
+ // invalidate the new position
+ kidFrame->InvalidateFrameSubtree();
+ }
+ // we need to account for the cell's isize even if it isn't reflowed
+ iCoord += kidFrame->ISize(wm);
+
+ if (kidFrame->GetNextInFlow()) {
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ }
+ }
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
+ iCoord += aTableFrame.GetColSpacing(cellColIndex);
+ }
+
+ // Just set our isize to what was available.
+ // The table will calculate the isize and not use our value.
+ aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) {
+ aDesiredSize.BSize(wm) = BSize(wm);
+ } else if (NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) {
+ aDesiredSize.BSize(wm) = CalcBSize(aReflowInput);
+ if (GetPrevInFlow()) {
+ nscoord bsize = CalcBSizeFromUnpaginatedBSize(*this, wm);
+ aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm), bsize);
+ } else {
+ if (isPaginated && HasStyleBSize()) {
+ // set the unpaginated bsize so next in flows can try to honor it
+ SetHasUnpaginatedBSize(true);
+ SetUnpaginatedBSize(aPresContext, aDesiredSize.BSize(wm));
+ }
+ if (isPaginated && HasUnpaginatedBSize()) {
+ aDesiredSize.BSize(wm) = std::max(aDesiredSize.BSize(wm),
+ GetUnpaginatedBSize());
+ }
+ }
+ } else { // constrained bsize, paginated
+ // Compute the bsize we should have from style (subtracting the
+ // bsize from our prev-in-flows from the style bsize)
+ nscoord styleBSize = CalcBSizeFromUnpaginatedBSize(*this, wm);
+ if (styleBSize > aReflowInput.AvailableBSize()) {
+ styleBSize = aReflowInput.AvailableBSize();
+ NS_FRAME_SET_INCOMPLETE(aStatus);
+ }
+ aDesiredSize.BSize(wm) = std::max(cellMaxBSize, styleBSize);
+ }
+
+ if (wm.IsVerticalRL()) {
+ // Any children whose width was not the same as our final
+ // aDesiredSize.BSize will have been misplaced earlier at the
+ // FinishReflowChild stage. So fix them up now.
+ for (nsIFrame* kidFrame : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
+ if (!cellFrame) {
+ continue;
+ }
+ if (kidFrame->BSize(wm) != aDesiredSize.BSize(wm)) {
+ kidFrame->MovePositionBy(wm,
+ LogicalPoint(wm, 0, kidFrame->BSize(wm) - aDesiredSize.BSize(wm)));
+ nsTableFrame::RePositionViews(kidFrame);
+ // Do we need to InvalidateFrameSubtree() here?
+ }
+ }
+ }
+
+ aDesiredSize.UnionOverflowAreasWithDesiredBounds();
+ FinishAndStoreOverflow(&aDesiredSize);
+}
+
+/** Layout the entire row.
+ * This method stacks cells in the inline dir according to HTML 4.0 rules.
+ */
+void
+nsTableRowFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableRowFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ const nsStyleVisibility* rowVis = StyleVisibility();
+ bool collapseRow = (NS_STYLE_VISIBILITY_COLLAPSE == rowVis->mVisible);
+ if (collapseRow) {
+ tableFrame->SetNeedToCollapse(true);
+ }
+
+ // see if a special bsize reflow needs to occur due to having a pct bsize
+ nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
+
+ // See if we have a cell with specified/pct bsize
+ InitHasCellWithStyleBSize(tableFrame);
+
+ ReflowChildren(aPresContext, aDesiredSize, aReflowInput, *tableFrame, aStatus);
+
+ if (aPresContext->IsPaginated() && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) &&
+ ShouldAvoidBreakInside(aReflowInput)) {
+ aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+ }
+
+ // Just set our isize to what was available.
+ // The table will calculate the isize and not use our value.
+ aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
+
+ // If our parent is in initial reflow, it'll handle invalidating our
+ // entire overflow rect.
+ if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
+ nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) {
+ InvalidateFrame();
+ }
+
+ // Any absolutely-positioned children will get reflowed in
+ // nsFrame::FixupPositionedTableParts in another pass, so propagate our
+ // dirtiness to them before our parent clears our dirty bits.
+ PushDirtyBitToAbsoluteFrames();
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+/**
+ * This function is called by the row group frame's SplitRowGroup() code when
+ * pushing a row frame that has cell frames that span into it. The cell frame
+ * should be reflowed with the specified height
+ */
+nscoord
+nsTableRowFrame::ReflowCellFrame(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ bool aIsTopOfPage,
+ nsTableCellFrame* aCellFrame,
+ nscoord aAvailableBSize,
+ nsReflowStatus& aStatus)
+{
+ WritingMode wm = aReflowInput.GetWritingMode();
+
+ // Reflow the cell frame with the specified height. Use the existing width
+ nsSize containerSize = aCellFrame->GetSize();
+ LogicalRect cellRect = aCellFrame->GetLogicalRect(wm, containerSize);
+ nsRect cellVisualOverflow = aCellFrame->GetVisualOverflowRect();
+
+ LogicalSize cellSize = cellRect.Size(wm);
+ LogicalSize availSize(wm, cellRect.ISize(wm), aAvailableBSize);
+ bool borderCollapse = GetTableFrame()->IsBorderCollapse();
+ NS_ASSERTION(aCellFrame->GetWritingMode() == wm,
+ "expected consistent writing-mode within table");
+ TableCellReflowInput
+ cellReflowInput(aPresContext, aReflowInput, aCellFrame, availSize,
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(*aPresContext, availSize, borderCollapse, cellReflowInput);
+ cellReflowInput.mFlags.mIsTopOfPage = aIsTopOfPage;
+
+ ReflowOutput desiredSize(aReflowInput);
+
+ ReflowChild(aCellFrame, aPresContext, desiredSize, cellReflowInput,
+ 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus);
+ bool fullyComplete = NS_FRAME_IS_COMPLETE(aStatus) && !NS_FRAME_IS_TRUNCATED(aStatus);
+ if (fullyComplete) {
+ desiredSize.BSize(wm) = aAvailableBSize;
+ }
+ aCellFrame->SetSize(wm, LogicalSize(wm, cellSize.ISize(wm),
+ desiredSize.BSize(wm)));
+
+ // Note: BlockDirAlignChild can affect the overflow rect.
+ // XXX What happens if this cell has 'vertical-align: baseline' ?
+ // XXX Why is it assumed that the cell's ascent hasn't changed ?
+ if (fullyComplete) {
+ aCellFrame->BlockDirAlignChild(wm, mMaxCellAscent);
+ }
+
+ nsTableFrame::InvalidateTableFrame(aCellFrame,
+ cellRect.GetPhysicalRect(wm, containerSize),
+ cellVisualOverflow,
+ aCellFrame->
+ HasAnyStateBits(NS_FRAME_FIRST_REFLOW));
+
+ aCellFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED);
+
+ return desiredSize.BSize(wm);
+}
+
+nscoord
+nsTableRowFrame::CollapseRowIfNecessary(nscoord aRowOffset,
+ nscoord aISize,
+ bool aCollapseGroup,
+ bool& aDidCollapse)
+{
+ const nsStyleVisibility* rowVis = StyleVisibility();
+ bool collapseRow = (NS_STYLE_VISIBILITY_COLLAPSE == rowVis->mVisible);
+ nsTableFrame* tableFrame =
+ static_cast<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 oldVisualOverflow = GetVisualOverflowRect();
+
+ rowRect.BStart(wm) -= aRowOffset;
+ rowRect.ISize(wm) = aISize;
+ nsOverflowAreas overflow;
+ nscoord shift = 0;
+ nsSize containerSize = mRect.Size();
+
+ if (aCollapseGroup || collapseRow) {
+ aDidCollapse = true;
+ shift = rowRect.BSize(wm);
+ nsTableCellFrame* cellFrame = GetFirstCell();
+ if (cellFrame) {
+ int32_t rowIndex;
+ cellFrame->GetRowIndex(rowIndex);
+ shift += tableFrame->GetRowSpacing(rowIndex);
+ while (cellFrame) {
+ LogicalRect cRect = cellFrame->GetLogicalRect(wm, containerSize);
+ // If aRowOffset != 0, there's no point in invalidating the cells, since
+ // we've already invalidated our overflow area. Note that we _do_ still
+ // need to invalidate if our row is not moving, because the cell might
+ // span out of this row, so invalidating our row rect won't do enough.
+ if (aRowOffset == 0) {
+ InvalidateFrame();
+ }
+ cRect.BSize(wm) = 0;
+ cellFrame->SetRect(wm, cRect, containerSize);
+ cellFrame = cellFrame->GetNextCell();
+ }
+ } else {
+ shift += tableFrame->GetRowSpacing(GetRowIndex());
+ }
+ rowRect.BSize(wm) = 0;
+ }
+ else { // row is not collapsed
+ // remember the col index of the previous cell to handle rowspans into this
+ // row
+ int32_t prevColIndex = -1;
+ nscoord iPos = 0; // running total of children inline-axis offset
+ nsTableFrame* fifTable =
+ static_cast<nsTableFrame*>(tableFrame->FirstInFlow());
+
+ for (nsIFrame* kidFrame : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
+ if (cellFrame) {
+ int32_t cellColIndex;
+ cellFrame->GetColIndex(cellColIndex);
+ int32_t cellColSpan = tableFrame->GetEffectiveColSpan(*cellFrame);
+
+ // If the adjacent cell is in a prior row (because of a rowspan) add in
+ // the space
+ if (prevColIndex != (cellColIndex - 1)) {
+ iPos += GetSpaceBetween(prevColIndex, cellColIndex, cellColSpan,
+ *tableFrame, true);
+ }
+ LogicalRect cRect(wm, iPos, 0, 0, rowRect.BSize(wm));
+
+ // remember the last (iend-wards-most) column this cell spans into
+ prevColIndex = cellColIndex + cellColSpan - 1;
+ int32_t actualColSpan = cellColSpan;
+ bool isVisible = false;
+ for (int32_t colIdx = cellColIndex; actualColSpan > 0;
+ colIdx++, actualColSpan--) {
+
+ nsTableColFrame* colFrame = tableFrame->GetColFrame(colIdx);
+ const nsStyleVisibility* colVis = colFrame->StyleVisibility();
+ bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE ==
+ colVis->mVisible);
+ nsIFrame* cgFrame = colFrame->GetParent();
+ const nsStyleVisibility* groupVis = cgFrame->StyleVisibility();
+ bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE ==
+ groupVis->mVisible);
+ bool isCollapsed = collapseCol || collapseGroup;
+ if (!isCollapsed) {
+ cRect.ISize(wm) += fifTable->GetColumnISizeFromFirstInFlow(colIdx);
+ isVisible = true;
+ if ((actualColSpan > 1)) {
+ nsTableColFrame* nextColFrame =
+ tableFrame->GetColFrame(colIdx + 1);
+ const nsStyleVisibility* nextColVis =
+ nextColFrame->StyleVisibility();
+ if ( (NS_STYLE_VISIBILITY_COLLAPSE != nextColVis->mVisible) &&
+ tableFrame->ColumnHasCellSpacingBefore(colIdx + 1)) {
+ cRect.ISize(wm) += tableFrame->GetColSpacing(cellColIndex);
+ }
+ }
+ }
+ }
+ iPos += cRect.ISize(wm);
+ if (isVisible) {
+ iPos += tableFrame->GetColSpacing(cellColIndex);
+ }
+ int32_t actualRowSpan = tableFrame->GetEffectiveRowSpan(*cellFrame);
+ nsTableRowFrame* rowFrame = GetNextRow();
+ for (actualRowSpan--; actualRowSpan > 0 && rowFrame; actualRowSpan--) {
+ const nsStyleVisibility* nextRowVis = rowFrame->StyleVisibility();
+ bool collapseNextRow = (NS_STYLE_VISIBILITY_COLLAPSE ==
+ nextRowVis->mVisible);
+ if (!collapseNextRow) {
+ LogicalRect nextRect = rowFrame->GetLogicalRect(wm,
+ containerSize);
+ cRect.BSize(wm) +=
+ nextRect.BSize(wm) +
+ tableFrame->GetRowSpacing(rowFrame->GetRowIndex());
+ }
+ rowFrame = rowFrame->GetNextRow();
+ }
+
+ nsRect oldCellRect = cellFrame->GetRect();
+ LogicalPoint oldCellNormalPos =
+ cellFrame->GetLogicalNormalPosition(wm, containerSize);
+
+ nsRect oldCellVisualOverflow = cellFrame->GetVisualOverflowRect();
+
+ if (aRowOffset == 0 && cRect.Origin(wm) != oldCellNormalPos) {
+ // We're moving the cell. Invalidate the old overflow area
+ cellFrame->InvalidateFrameSubtree();
+ }
+
+ cellFrame->MovePositionBy(wm, cRect.Origin(wm) - oldCellNormalPos);
+ cellFrame->SetSize(wm, cRect.Size(wm));
+
+ // XXXbz This looks completely bogus in the cases when we didn't
+ // collapse the cell!
+ LogicalRect cellBounds(wm, 0, 0, cRect.ISize(wm), cRect.BSize(wm));
+ nsRect cellPhysicalBounds =
+ cellBounds.GetPhysicalRect(wm, containerSize);
+ nsOverflowAreas cellOverflow(cellPhysicalBounds, cellPhysicalBounds);
+ cellFrame->FinishAndStoreOverflow(cellOverflow,
+ cRect.Size(wm).GetPhysicalSize(wm));
+ nsTableFrame::RePositionViews(cellFrame);
+ ConsiderChildOverflow(overflow, cellFrame);
+
+ if (aRowOffset == 0) {
+ nsTableFrame::InvalidateTableFrame(cellFrame, oldCellRect,
+ oldCellVisualOverflow, false);
+ }
+ }
+ }
+ }
+
+ SetRect(wm, rowRect, containerSize);
+ overflow.UnionAllWith(nsRect(0, 0, rowRect.Width(wm), rowRect.Height(wm)));
+ FinishAndStoreOverflow(overflow, rowRect.Size(wm).GetPhysicalSize(wm));
+
+ nsTableFrame::RePositionViews(this);
+ nsTableFrame::InvalidateTableFrame(this, oldRect, oldVisualOverflow, false);
+ return shift;
+}
+
+/*
+ * The following method is called by the row group frame's SplitRowGroup()
+ * when it creates a continuing cell frame and wants to insert it into the
+ * row's child list.
+ */
+void
+nsTableRowFrame::InsertCellFrame(nsTableCellFrame* aFrame,
+ int32_t aColIndex)
+{
+ // Find the cell frame where col index < aColIndex
+ nsTableCellFrame* priorCell = nullptr;
+ for (nsIFrame* child : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(child);
+ if (cellFrame) {
+ int32_t colIndex;
+ cellFrame->GetColIndex(colIndex);
+ if (colIndex < aColIndex) {
+ priorCell = cellFrame;
+ }
+ else break;
+ }
+ }
+ mFrames.InsertFrame(this, priorCell, aFrame);
+}
+
+nsIAtom*
+nsTableRowFrame::GetType() const
+{
+ return nsGkAtoms::tableRowFrame;
+}
+
+nsTableRowFrame*
+nsTableRowFrame::GetNextRow() const
+{
+ nsIFrame* childFrame = GetNextSibling();
+ while (childFrame) {
+ nsTableRowFrame *rowFrame = do_QueryFrame(childFrame);
+ if (rowFrame) {
+ NS_ASSERTION(mozilla::StyleDisplay::TableRow == childFrame->StyleDisplay()->mDisplay,
+ "wrong display type on rowframe");
+ return rowFrame;
+ }
+ childFrame = childFrame->GetNextSibling();
+ }
+ return nullptr;
+}
+
+NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(RowUnpaginatedHeightProperty, nscoord)
+
+void
+nsTableRowFrame::SetUnpaginatedBSize(nsPresContext* aPresContext,
+ nscoord aValue)
+{
+ NS_ASSERTION(!GetPrevInFlow(), "program error");
+ // Get the property
+ aPresContext->PropertyTable()->
+ Set(this, RowUnpaginatedHeightProperty(), aValue);
+}
+
+nscoord
+nsTableRowFrame::GetUnpaginatedBSize()
+{
+ FrameProperties props = FirstInFlow()->Properties();
+ return props.Get(RowUnpaginatedHeightProperty());
+}
+
+void nsTableRowFrame::SetContinuousBCBorderWidth(LogicalSide aForSide,
+ BCPixelSize aPixelValue)
+{
+ switch (aForSide) {
+ case eLogicalSideIEnd:
+ mIEndContBorderWidth = aPixelValue;
+ return;
+ case eLogicalSideBStart:
+ mBStartContBorderWidth = aPixelValue;
+ return;
+ case eLogicalSideIStart:
+ mIStartContBorderWidth = aPixelValue;
+ return;
+ default:
+ NS_ERROR("invalid NS_SIDE arg");
+ }
+}
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsTableRowFrame::AccessibleType()
+{
+ return a11y::eHTMLTableRowType;
+}
+#endif
+/**
+ * Sets the NS_ROW_HAS_CELL_WITH_STYLE_BSIZE bit to indicate whether
+ * this row has any cells that have non-auto-bsize. (Row-spanning
+ * cells are ignored.)
+ */
+void nsTableRowFrame::InitHasCellWithStyleBSize(nsTableFrame* aTableFrame)
+{
+ WritingMode wm = GetWritingMode();
+
+ for (nsIFrame* kidFrame : mFrames) {
+ nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame);
+ if (!cellFrame) {
+ NS_NOTREACHED("Table row has a non-cell child.");
+ continue;
+ }
+ // Ignore row-spanning cells
+ const nsStyleCoord &cellBSize = cellFrame->StylePosition()->BSize(wm);
+ if (aTableFrame->GetEffectiveRowSpan(*cellFrame) == 1 &&
+ cellBSize.GetUnit() != eStyleUnit_Auto &&
+ /* calc() with percentages treated like 'auto' */
+ (!cellBSize.IsCalcUnit() || !cellBSize.HasPercent())) {
+ AddStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE);
+ return;
+ }
+ }
+ RemoveStateBits(NS_ROW_HAS_CELL_WITH_STYLE_BSIZE);
+}
+
+void
+nsTableRowFrame::InvalidateFrame(uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrame(aDisplayItemKey);
+ GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey);
+}
+
+void
+nsTableRowFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey);
+ // If we have filters applied that would affects our bounds, then
+ // we get an inactive layer created and this is computed
+ // within FrameLayerBuilder
+ GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey);
+}
+
+/* ----- global methods ----- */
+
+nsTableRowFrame*
+NS_NewTableRowFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTableRowFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableRowFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTableRowFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("TableRow"), aResult);
+}
+#endif
diff --git a/layout/tables/nsTableRowFrame.h b/layout/tables/nsTableRowFrame.h
new file mode 100644
index 0000000000..a6aba81e7d
--- /dev/null
+++ b/layout/tables/nsTableRowFrame.h
@@ -0,0 +1,437 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTableRowFrame_h__
+#define nsTableRowFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsContainerFrame.h"
+#include "nsTablePainter.h"
+#include "nsTableRowGroupFrame.h"
+#include "mozilla/WritingModes.h"
+
+class nsTableCellFrame;
+namespace mozilla {
+struct TableCellReflowInput;
+} // namespace mozilla
+
+/**
+ * nsTableRowFrame is the frame that maps table rows
+ * (HTML tag TR). This class cannot be reused
+ * outside of an nsTableRowGroupFrame. It assumes that its parent is an nsTableRowGroupFrame,
+ * and its children are nsTableCellFrames.
+ *
+ * @see nsTableFrame
+ * @see nsTableRowGroupFrame
+ * @see nsTableCellFrame
+ */
+class nsTableRowFrame : public nsContainerFrame
+{
+ using TableCellReflowInput = mozilla::TableCellReflowInput;
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsTableRowFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual ~nsTableRowFrame();
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ /** @see nsIFrame::DidSetStyleContext */
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ /** instantiate a new instance of nsTableRowFrame.
+ * @param aPresShell the pres shell for this frame
+ *
+ * @return the frame that was created
+ */
+ friend nsTableRowFrame* NS_NewTableRowFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ nsTableRowGroupFrame* GetTableRowGroupFrame() const
+ {
+ nsIFrame* parent = GetParent();
+ MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableRowGroupFrame);
+ return static_cast<nsTableRowGroupFrame*>(parent);
+ }
+
+ nsTableFrame* GetTableFrame() const
+ {
+ return GetTableRowGroupFrame()->GetTableFrame();
+ }
+
+ virtual nsMargin GetUsedMargin() const override;
+ virtual nsMargin GetUsedBorder() const override;
+ virtual nsMargin GetUsedPadding() const override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ nsTableCellFrame* GetFirstCell() ;
+
+ /** calls Reflow for all of its child cells.
+ * Cells with rowspan=1 are all set to the same height and stacked horizontally.
+ * <P> Cells are not split unless absolutely necessary.
+ * <P> Cells are resized in nsTableFrame::BalanceColumnWidths
+ * and nsTableFrame::ShrinkWrapChildren
+ *
+ * @param aDesiredSize width set to width of the sum of the cells, height set to
+ * height of cells with rowspan=1.
+ *
+ * @see nsIFrame::Reflow
+ * @see nsTableFrame::BalanceColumnWidths
+ * @see nsTableFrame::ShrinkWrapChildren
+ */
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ void DidResize();
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::tableRowFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual mozilla::WritingMode GetWritingMode() const override
+ { return GetTableFrame()->GetWritingMode(); }
+
+ void UpdateBSize(nscoord aBSize,
+ nscoord aAscent,
+ nscoord aDescent,
+ nsTableFrame* aTableFrame = nullptr,
+ nsTableCellFrame* aCellFrame = nullptr);
+
+ void ResetBSize(nscoord aRowStyleBSize);
+
+ // calculate the bsize, considering content bsize of the
+ // cells and the style bsize of the row and cells, excluding pct bsizes
+ nscoord CalcBSize(const ReflowInput& aReflowInput);
+
+ // Support for cells with 'vertical-align: baseline'.
+
+ /**
+ * returns the max-ascent amongst all the cells that have
+ * 'vertical-align: baseline', *including* cells with rowspans.
+ * returns 0 if we don't have any cell with 'vertical-align: baseline'
+ */
+ nscoord GetMaxCellAscent() const;
+
+ /* return the row ascent
+ */
+ nscoord GetRowBaseline(mozilla::WritingMode aWritingMode);
+
+ /** returns the ordinal position of this row in its table */
+ virtual int32_t GetRowIndex() const;
+
+ /** set this row's starting row index */
+ void SetRowIndex (int aRowIndex);
+
+ /** used by row group frame code */
+ nscoord ReflowCellFrame(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ bool aIsTopOfPage,
+ nsTableCellFrame* aCellFrame,
+ nscoord aAvailableBSize,
+ nsReflowStatus& aStatus);
+ /**
+ * Collapse the row if required, apply col and colgroup visibility: collapse
+ * info to the cells in the row.
+ * @return the amount to shift bstart-wards all following rows
+ * @param aRowOffset - shift the row bstart-wards by this amount
+ * @param aISize - new isize of the row
+ * @param aCollapseGroup - parent rowgroup is collapsed so this row needs
+ * to be collapsed
+ * @param aDidCollapse - the row has been collapsed
+ */
+ nscoord CollapseRowIfNecessary(nscoord aRowOffset,
+ nscoord aISize,
+ bool aCollapseGroup,
+ bool& aDidCollapse);
+
+ /**
+ * Insert a cell frame after the last cell frame that has a col index
+ * that is less than aColIndex. If no such cell frame is found the
+ * frame to insert is prepended to the child list.
+ * @param aFrame the cell frame to insert
+ * @param aColIndex the col index
+ */
+ void InsertCellFrame(nsTableCellFrame* aFrame,
+ int32_t aColIndex);
+
+ nsresult CalculateCellActualBSize(nsTableCellFrame* aCellFrame,
+ nscoord& aDesiredBSize,
+ mozilla::WritingMode aWM);
+
+ bool IsFirstInserted() const;
+ void SetFirstInserted(bool aValue);
+
+ nscoord GetContentBSize() const;
+ void SetContentBSize(nscoord aTwipValue);
+
+ bool HasStyleBSize() const;
+
+ bool HasFixedBSize() const;
+ void SetHasFixedBSize(bool aValue);
+
+ bool HasPctBSize() const;
+ void SetHasPctBSize(bool aValue);
+
+ nscoord GetFixedBSize() const;
+ void SetFixedBSize(nscoord aValue);
+
+ float GetPctBSize() const;
+ void SetPctBSize(float aPctValue,
+ bool aForce = false);
+
+ nscoord GetInitialBSize(nscoord aBasis = 0) const;
+
+ nsTableRowFrame* GetNextRow() const;
+
+ bool HasUnpaginatedBSize();
+ void SetHasUnpaginatedBSize(bool aValue);
+ nscoord GetUnpaginatedBSize();
+ void SetUnpaginatedBSize(nsPresContext* aPresContext, nscoord aValue);
+
+ nscoord GetBStartBCBorderWidth() const { return mBStartBorderWidth; }
+ nscoord GetBEndBCBorderWidth() const { return mBEndBorderWidth; }
+ void SetBStartBCBorderWidth(BCPixelSize aWidth) { mBStartBorderWidth = aWidth; }
+ void SetBEndBCBorderWidth(BCPixelSize aWidth) { mBEndBorderWidth = aWidth; }
+ mozilla::LogicalMargin GetBCBorderWidth(mozilla::WritingMode aWM);
+
+ /**
+ * Gets inner border widths before collapsing with cell borders
+ * Caller must get block-end border from next row or from table
+ * GetContinuousBCBorderWidth will not overwrite that border
+ * see nsTablePainter about continuous borders
+ */
+ void GetContinuousBCBorderWidth(mozilla::WritingMode aWM,
+ mozilla::LogicalMargin& aBorder);
+
+ /**
+ * @returns outer block-start bc border == prev row's block-end inner
+ */
+ nscoord GetOuterBStartContBCBorderWidth();
+ /**
+ * Sets full border widths before collapsing with cell borders
+ * @param aForSide - side to set; only accepts iend, istart, and bstart
+ */
+ void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide,
+ BCPixelSize aPixelValue);
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart));
+ }
+
+ virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); }
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+protected:
+
+ /** protected constructor.
+ * @see NewFrame
+ */
+ explicit nsTableRowFrame(nsStyleContext *aContext);
+
+ void InitChildReflowInput(nsPresContext& aPresContext,
+ const mozilla::LogicalSize& aAvailSize,
+ bool aBorderCollapse,
+ TableCellReflowInput& aReflowInput);
+
+ virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override;
+
+ // row-specific methods
+
+ nscoord ComputeCellXOffset(const ReflowInput& aState,
+ nsIFrame* aKidFrame,
+ const nsMargin& aKidMargin) const;
+ /**
+ * Called for incremental/dirty and resize reflows. If aDirtyOnly is true then
+ * only reflow dirty cells.
+ */
+ void ReflowChildren(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsTableFrame& aTableFrame,
+ nsReflowStatus& aStatus);
+
+private:
+ struct RowBits {
+ unsigned mRowIndex:29;
+ unsigned mHasFixedBSize:1; // set if the dominating style bsize on the row or any cell is pixel based
+ unsigned mHasPctBSize:1; // set if the dominating style bsize on the row or any cell is pct based
+ unsigned mFirstInserted:1; // if true, then it was the bstart-most newly inserted row
+ } mBits;
+
+ // the desired bsize based on the content of the tallest cell in the row
+ nscoord mContentBSize;
+ // the bsize based on a style percentage bsize on either the row or any cell
+ // if mHasPctBSize is set
+ nscoord mStylePctBSize;
+ // the bsize based on a style pixel bsize on the row or any
+ // cell if mHasFixedBSize is set
+ nscoord mStyleFixedBSize;
+
+ // max-ascent and max-descent amongst all cells that have 'vertical-align: baseline'
+ nscoord mMaxCellAscent; // does include cells with rowspan > 1
+ nscoord mMaxCellDescent; // does *not* include cells with rowspan > 1
+
+ // border widths in pixels in the collapsing border model of the *inner*
+ // half of the border only
+ BCPixelSize mBStartBorderWidth;
+ BCPixelSize mBEndBorderWidth;
+ BCPixelSize mIEndContBorderWidth;
+ BCPixelSize mBStartContBorderWidth;
+ BCPixelSize mIStartContBorderWidth;
+
+ /**
+ * Sets the NS_ROW_HAS_CELL_WITH_STYLE_BSIZE bit to indicate whether
+ * this row has any cells that have non-auto-bsize. (Row-spanning
+ * cells are ignored.)
+ */
+ void InitHasCellWithStyleBSize(nsTableFrame* aTableFrame);
+
+};
+
+inline int32_t nsTableRowFrame::GetRowIndex() const
+{
+ return int32_t(mBits.mRowIndex);
+}
+
+inline void nsTableRowFrame::SetRowIndex (int aRowIndex)
+{
+ mBits.mRowIndex = aRowIndex;
+}
+
+inline bool nsTableRowFrame::IsFirstInserted() const
+{
+ return bool(mBits.mFirstInserted);
+}
+
+inline void nsTableRowFrame::SetFirstInserted(bool aValue)
+{
+ mBits.mFirstInserted = aValue;
+}
+
+inline bool nsTableRowFrame::HasStyleBSize() const
+{
+ return (bool)mBits.mHasFixedBSize || (bool)mBits.mHasPctBSize;
+}
+
+inline bool nsTableRowFrame::HasFixedBSize() const
+{
+ return (bool)mBits.mHasFixedBSize;
+}
+
+inline void nsTableRowFrame::SetHasFixedBSize(bool aValue)
+{
+ mBits.mHasFixedBSize = aValue;
+}
+
+inline bool nsTableRowFrame::HasPctBSize() const
+{
+ return (bool)mBits.mHasPctBSize;
+}
+
+inline void nsTableRowFrame::SetHasPctBSize(bool aValue)
+{
+ mBits.mHasPctBSize = aValue;
+}
+
+inline nscoord nsTableRowFrame::GetContentBSize() const
+{
+ return mContentBSize;
+}
+
+inline void nsTableRowFrame::SetContentBSize(nscoord aValue)
+{
+ mContentBSize = aValue;
+}
+
+inline nscoord nsTableRowFrame::GetFixedBSize() const
+{
+ if (mBits.mHasFixedBSize) {
+ return mStyleFixedBSize;
+ }
+ return 0;
+}
+
+inline float nsTableRowFrame::GetPctBSize() const
+{
+ if (mBits.mHasPctBSize) {
+ return (float)mStylePctBSize / 100.0f;
+ }
+ return 0.0f;
+}
+
+inline bool nsTableRowFrame::HasUnpaginatedBSize()
+{
+ return HasAnyStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE);
+}
+
+inline void nsTableRowFrame::SetHasUnpaginatedBSize(bool aValue)
+{
+ if (aValue) {
+ AddStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE);
+ } else {
+ RemoveStateBits(NS_TABLE_ROW_HAS_UNPAGINATED_BSIZE);
+ }
+}
+
+inline mozilla::LogicalMargin
+nsTableRowFrame::GetBCBorderWidth(mozilla::WritingMode aWM)
+{
+ return mozilla::LogicalMargin(
+ aWM, nsPresContext::CSSPixelsToAppUnits(mBStartBorderWidth), 0,
+ nsPresContext::CSSPixelsToAppUnits(mBEndBorderWidth), 0);
+}
+
+inline void
+nsTableRowFrame::GetContinuousBCBorderWidth(mozilla::WritingMode aWM,
+ mozilla::LogicalMargin& aBorder)
+{
+ int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
+ aBorder.IEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips,
+ mIStartContBorderWidth);
+ aBorder.BStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips,
+ mBStartContBorderWidth);
+ aBorder.IStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips,
+ mIEndContBorderWidth);
+}
+
+inline nscoord nsTableRowFrame::GetOuterBStartContBCBorderWidth()
+{
+ int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
+ return BC_BORDER_START_HALF_COORD(aPixelsToTwips, mBStartContBorderWidth);
+}
+
+#endif
diff --git a/layout/tables/nsTableRowGroupFrame.cpp b/layout/tables/nsTableRowGroupFrame.cpp
new file mode 100644
index 0000000000..60596f12b0
--- /dev/null
+++ b/layout/tables/nsTableRowGroupFrame.cpp
@@ -0,0 +1,2019 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsCOMPtr.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsPresContext.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsIContent.h"
+#include "nsGkAtoms.h"
+#include "nsIPresShell.h"
+#include "nsCSSRendering.h"
+#include "nsHTMLParts.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsDisplayList.h"
+
+#include "nsCellMap.h"//table cell navigation
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+namespace mozilla {
+
+struct TableRowGroupReflowInput {
+ const ReflowInput& reflowInput; // Our reflow state
+
+ nsTableFrame* tableFrame;
+
+ // The available size (computed from the parent)
+ mozilla::LogicalSize availSize;
+
+ // Running block-offset
+ nscoord bCoord;
+
+ TableRowGroupReflowInput(const ReflowInput& aReflowInput,
+ nsTableFrame* aTableFrame)
+ : reflowInput(aReflowInput)
+ , tableFrame(aTableFrame)
+ , availSize(aReflowInput.GetWritingMode(),
+ aReflowInput.AvailableISize(),
+ aReflowInput.AvailableBSize())
+ , bCoord(0)
+ {
+ }
+
+ ~TableRowGroupReflowInput() {}
+};
+
+} // namespace mozilla
+
+nsTableRowGroupFrame::nsTableRowGroupFrame(nsStyleContext* aContext):
+ nsContainerFrame(aContext)
+{
+ SetRepeatable(false);
+}
+
+nsTableRowGroupFrame::~nsTableRowGroupFrame()
+{
+}
+
+void
+nsTableRowGroupFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
+ nsTableFrame::UnregisterPositionedTablePart(this, aDestructRoot);
+ }
+
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+NS_QUERYFRAME_HEAD(nsTableRowGroupFrame)
+ NS_QUERYFRAME_ENTRY(nsTableRowGroupFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+int32_t
+nsTableRowGroupFrame::GetRowCount()
+{
+#ifdef DEBUG
+ for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
+ NS_ASSERTION(e.get()->StyleDisplay()->mDisplay ==
+ mozilla::StyleDisplay::TableRow,
+ "Unexpected display");
+ NS_ASSERTION(e.get()->GetType() == nsGkAtoms::tableRowFrame,
+ "Unexpected frame type");
+ }
+#endif
+
+ return mFrames.GetLength();
+}
+
+int32_t nsTableRowGroupFrame::GetStartRowIndex()
+{
+ int32_t result = -1;
+ if (mFrames.NotEmpty()) {
+ NS_ASSERTION(mFrames.FirstChild()->GetType() == nsGkAtoms::tableRowFrame,
+ "Unexpected frame type");
+ result = static_cast<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);
+ }
+ }
+}
+nsresult
+nsTableRowGroupFrame::InitRepeatedFrame(nsTableRowGroupFrame* aHeaderFooterFrame)
+{
+ nsTableRowFrame* copyRowFrame = GetFirstRow();
+ nsTableRowFrame* originalRowFrame = aHeaderFooterFrame->GetFirstRow();
+ AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
+ while (copyRowFrame && originalRowFrame) {
+ copyRowFrame->AddStateBits(NS_REPEATED_ROW_OR_ROWGROUP);
+ int rowIndex = originalRowFrame->GetRowIndex();
+ copyRowFrame->SetRowIndex(rowIndex);
+
+ // For each table cell frame set its column index
+ nsTableCellFrame* originalCellFrame = originalRowFrame->GetFirstCell();
+ nsTableCellFrame* copyCellFrame = copyRowFrame->GetFirstCell();
+ while (copyCellFrame && originalCellFrame) {
+ NS_ASSERTION(originalCellFrame->GetContent() == copyCellFrame->GetContent(),
+ "cell frames have different content");
+ int32_t colIndex;
+ originalCellFrame->GetColIndex(colIndex);
+ copyCellFrame->SetColIndex(colIndex);
+
+ // Move to the next cell frame
+ copyCellFrame = copyCellFrame->GetNextCell();
+ originalCellFrame = originalCellFrame->GetNextCell();
+ }
+
+ // Move to the next row frame
+ originalRowFrame = originalRowFrame->GetNextRow();
+ copyRowFrame = copyRowFrame->GetNextRow();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * We need a custom display item for table row backgrounds. This is only used
+ * when the table row is the root of a stacking context (e.g., has 'opacity').
+ * Table row backgrounds can extend beyond the row frame bounds, when
+ * the row contains row-spanning cells.
+ */
+class nsDisplayTableRowGroupBackground : public nsDisplayTableItem {
+public:
+ nsDisplayTableRowGroupBackground(nsDisplayListBuilder* aBuilder,
+ nsTableRowGroupFrame* aFrame) :
+ nsDisplayTableItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayTableRowGroupBackground);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayTableRowGroupBackground() {
+ MOZ_COUNT_DTOR(nsDisplayTableRowGroupBackground);
+ }
+#endif
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+
+ NS_DISPLAY_DECL_NAME("TableRowGroupBackground", TYPE_TABLE_ROW_GROUP_BACKGROUND)
+};
+
+void
+nsDisplayTableRowGroupBackground::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ auto rgFrame = static_cast<nsTableRowGroupFrame*>(mFrame);
+ TableBackgroundPainter painter(rgFrame->GetTableFrame(),
+ TableBackgroundPainter::eOrigin_TableRowGroup,
+ mFrame->PresContext(), *aCtx,
+ mVisibleRect, ToReferenceFrame(),
+ aBuilder->GetBackgroundPaintFlags());
+
+ DrawResult result = painter.PaintRowGroup(rgFrame);
+ nsDisplayTableItemGeometry::UpdateDrawResult(this, result);
+}
+
+// Handle the child-traversal part of DisplayGenericTablePart
+static void
+DisplayRows(nsDisplayListBuilder* aBuilder, nsFrame* aFrame,
+ const nsRect& aDirtyRect, const nsDisplayListSet& aLists)
+{
+ nscoord overflowAbove;
+ nsTableRowGroupFrame* f = static_cast<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) ?
+ nullptr : f->GetFirstRowContaining(aDirtyRect.y, &overflowAbove);
+
+ if (kid) {
+ // have a cursor, use it
+ while (kid) {
+ if (kid->GetRect().y - overflowAbove >= aDirtyRect.YMost() &&
+ kid->GetNormalRect().y - overflowAbove >= aDirtyRect.YMost())
+ break;
+ f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
+ kid = kid->GetNextSibling();
+ }
+ return;
+ }
+
+ // No cursor. Traverse children the hard way and build a cursor while we're at it
+ nsTableRowGroupFrame::FrameCursorData* cursor = f->SetupRowCursor();
+ kid = f->PrincipalChildList().FirstChild();
+ while (kid) {
+ f->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
+
+ if (cursor) {
+ if (!cursor->AppendFrame(kid)) {
+ f->ClearRowCursor();
+ return;
+ }
+ }
+
+ kid = kid->GetNextSibling();
+ }
+ if (cursor) {
+ cursor->FinishBuildingCursor();
+ }
+}
+
+void
+nsTableRowGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsDisplayTableItem* item = nullptr;
+ if (IsVisibleInSelection(aBuilder)) {
+ bool isRoot = aBuilder->IsAtRootOfPseudoStackingContext();
+ if (isRoot) {
+ // This background is created regardless of whether this frame is
+ // visible or not. Visibility decisions are delegated to the
+ // table background painter.
+ item = new (aBuilder) nsDisplayTableRowGroupBackground(aBuilder, this);
+ aLists.BorderBackground()->AppendNewToTop(item);
+ }
+ }
+ nsTableFrame::DisplayGenericTablePart(aBuilder, this, aDirtyRect,
+ aLists, item, DisplayRows);
+}
+
+nsIFrame::LogicalSides
+nsTableRowGroupFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const
+{
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return LogicalSides();
+ }
+
+ LogicalSides skip;
+ if (nullptr != GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (nullptr != GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+
+// Position and size aKidFrame and update our reflow state.
+void
+nsTableRowGroupFrame::PlaceChild(nsPresContext* aPresContext,
+ TableRowGroupReflowInput& aReflowInput,
+ nsIFrame* aKidFrame,
+ WritingMode aWM,
+ const LogicalPoint& aKidPosition,
+ const nsSize& aContainerSize,
+ ReflowOutput& aDesiredSize,
+ const nsRect& aOriginalKidRect,
+ const nsRect& aOriginalKidVisualOverflow)
+{
+ bool isFirstReflow = aKidFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+
+ // Place and size the child
+ FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, nullptr,
+ aWM, aKidPosition, aContainerSize, 0);
+
+ nsTableFrame::InvalidateTableFrame(aKidFrame, aOriginalKidRect,
+ aOriginalKidVisualOverflow, isFirstReflow);
+
+ // Adjust the running block-offset
+ aReflowInput.bCoord += aDesiredSize.BSize(aWM);
+
+ // If our block-size is constrained then update the available bsize
+ if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(aWM)) {
+ aReflowInput.availSize.BSize(aWM) -= aDesiredSize.BSize(aWM);
+ }
+}
+
+void
+nsTableRowGroupFrame::InitChildReflowInput(nsPresContext& aPresContext,
+ bool aBorderCollapse,
+ ReflowInput& aReflowInput)
+{
+ nsMargin collapseBorder;
+ nsMargin padding(0,0,0,0);
+ nsMargin* pCollapseBorder = nullptr;
+ if (aBorderCollapse) {
+ nsTableRowFrame *rowFrame = do_QueryFrame(aReflowInput.mFrame);
+ if (rowFrame) {
+ WritingMode wm = GetWritingMode();
+ LogicalMargin border = rowFrame->GetBCBorderWidth(wm);
+ collapseBorder = border.GetPhysicalMargin(wm);
+ pCollapseBorder = &collapseBorder;
+ }
+ }
+ aReflowInput.Init(&aPresContext, nullptr, pCollapseBorder, &padding);
+}
+
+static void
+CacheRowBSizesForPrinting(nsPresContext* aPresContext,
+ nsTableRowFrame* aFirstRow,
+ WritingMode aWM)
+{
+ for (nsTableRowFrame* row = aFirstRow; row; row = row->GetNextRow()) {
+ if (!row->GetPrevInFlow()) {
+ row->SetHasUnpaginatedBSize(true);
+ row->SetUnpaginatedBSize(aPresContext, row->BSize(aWM));
+ }
+ }
+}
+
+void
+nsTableRowGroupFrame::ReflowChildren(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ TableRowGroupReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ bool* aPageBreakBeforeEnd)
+{
+ if (aPageBreakBeforeEnd) {
+ *aPageBreakBeforeEnd = false;
+ }
+
+ WritingMode wm = aReflowInput.reflowInput.GetWritingMode();
+ nsTableFrame* tableFrame = GetTableFrame();
+ const bool borderCollapse = tableFrame->IsBorderCollapse();
+
+ // XXXldb Should we really be checking IsPaginated(),
+ // or should we *only* check available block-size?
+ // (Think about multi-column layout!)
+ bool isPaginated = aPresContext->IsPaginated() &&
+ NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm);
+
+ bool haveRow = false;
+ bool reflowAllKids = aReflowInput.reflowInput.ShouldReflowAllKids() ||
+ tableFrame->IsGeometryDirty();
+
+ // in vertical-rl mode, we always need the row bsizes in order to
+ // get the necessary containerSize for placing our kids
+ bool needToCalcRowBSizes = reflowAllKids || wm.IsVerticalRL();
+
+ nsSize containerSize =
+ aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained();
+
+ nsIFrame *prevKidFrame = nullptr;
+ for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame;
+ prevKidFrame = kidFrame, kidFrame = kidFrame->GetNextSibling()) {
+ nsTableRowFrame *rowFrame = do_QueryFrame(kidFrame);
+ if (!rowFrame) {
+ // XXXldb nsCSSFrameConstructor needs to enforce this!
+ NS_NOTREACHED("yikes, a non-row child");
+ continue;
+ }
+ nscoord cellSpacingB = tableFrame->GetRowSpacing(rowFrame->GetRowIndex());
+ haveRow = true;
+
+ // Reflow the row frame
+ if (reflowAllKids ||
+ NS_SUBTREE_DIRTY(kidFrame) ||
+ (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow &&
+ (isPaginated ||
+ kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)))) {
+ LogicalRect oldKidRect = kidFrame->GetLogicalRect(wm, containerSize);
+ nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect();
+
+ // XXXldb We used to only pass aDesiredSize.mFlags through for the
+ // incremental reflow codepath.
+ ReflowOutput desiredSize(aReflowInput.reflowInput,
+ aDesiredSize.mFlags);
+ desiredSize.ClearSize();
+
+ // Reflow the child into the available space, giving it as much bsize as
+ // it wants. We'll deal with splitting later after we've computed the row
+ // bsizes, taking into account cells with row spans...
+ LogicalSize kidAvailSize = aReflowInput.availSize;
+ kidAvailSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+ ReflowInput kidReflowInput(aPresContext, aReflowInput.reflowInput,
+ kidFrame, kidAvailSize,
+ nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(*aPresContext, borderCollapse, kidReflowInput);
+
+ // This can indicate that columns were resized.
+ if (aReflowInput.reflowInput.IsIResize()) {
+ kidReflowInput.SetIResize(true);
+ }
+
+ NS_ASSERTION(kidFrame == mFrames.FirstChild() || prevKidFrame,
+ "If we're not on the first frame, we should have a "
+ "previous sibling...");
+ // If prev row has nonzero YMost, then we can't be at the top of the page
+ if (prevKidFrame && prevKidFrame->GetNormalRect().YMost() > 0) {
+ kidReflowInput.mFlags.mIsTopOfPage = false;
+ }
+
+ LogicalPoint kidPosition(wm, 0, aReflowInput.bCoord);
+ ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowInput,
+ wm, kidPosition, containerSize, 0, aStatus);
+ kidReflowInput.ApplyRelativePositioning(&kidPosition, containerSize);
+
+ // Place the child
+ PlaceChild(aPresContext, aReflowInput, kidFrame,
+ wm, kidPosition, containerSize,
+ desiredSize, oldKidRect.GetPhysicalRect(wm, containerSize),
+ oldKidVisualOverflow);
+ aReflowInput.bCoord += cellSpacingB;
+
+ if (!reflowAllKids) {
+ if (IsSimpleRowFrame(aReflowInput.tableFrame, rowFrame)) {
+ // Inform the row of its new bsize.
+ rowFrame->DidResize();
+ // the overflow area may have changed inflate the overflow area
+ const nsStylePosition *stylePos = StylePosition();
+ nsStyleUnit unit = stylePos->BSize(wm).GetUnit();
+ if (aReflowInput.tableFrame->IsAutoBSize(wm) &&
+ unit != eStyleUnit_Coord) {
+ // Because other cells in the row may need to be aligned
+ // differently, repaint the entire row
+ InvalidateFrame();
+ } else if (oldKidRect.BSize(wm) != desiredSize.BSize(wm)) {
+ needToCalcRowBSizes = true;
+ }
+ } else {
+ needToCalcRowBSizes = true;
+ }
+ }
+
+ if (isPaginated && aPageBreakBeforeEnd && !*aPageBreakBeforeEnd) {
+ nsTableRowFrame* nextRow = rowFrame->GetNextRow();
+ if (nextRow) {
+ *aPageBreakBeforeEnd = nsTableFrame::PageBreakAfter(kidFrame, nextRow);
+ }
+ }
+ } else {
+ SlideChild(aReflowInput, kidFrame);
+
+ // Adjust the running b-offset so we know where the next row should be placed
+ nscoord bSize = kidFrame->BSize(wm) + cellSpacingB;
+ aReflowInput.bCoord += bSize;
+
+ if (NS_UNCONSTRAINEDSIZE != aReflowInput.availSize.BSize(wm)) {
+ aReflowInput.availSize.BSize(wm) -= bSize;
+ }
+ }
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame);
+ }
+
+ if (haveRow) {
+ aReflowInput.bCoord -= tableFrame->GetRowSpacing(GetStartRowIndex() +
+ GetRowCount());
+ }
+
+ // Return our desired rect
+ aDesiredSize.ISize(wm) = aReflowInput.reflowInput.AvailableISize();
+ aDesiredSize.BSize(wm) = aReflowInput.bCoord;
+
+ if (aReflowInput.reflowInput.mFlags.mSpecialBSizeReflow) {
+ DidResizeRows(aDesiredSize);
+ if (isPaginated) {
+ CacheRowBSizesForPrinting(aPresContext, GetFirstRow(), wm);
+ }
+ }
+ else if (needToCalcRowBSizes) {
+ CalculateRowBSizes(aPresContext, aDesiredSize, aReflowInput.reflowInput);
+ if (!reflowAllKids) {
+ InvalidateFrame();
+ }
+ }
+}
+
+nsTableRowFrame*
+nsTableRowGroupFrame::GetFirstRow()
+{
+ for (nsIFrame* childFrame : mFrames) {
+ nsTableRowFrame* rowFrame = do_QueryFrame(childFrame);
+ if (rowFrame) {
+ return rowFrame;
+ }
+ }
+ return nullptr;
+}
+
+nsTableRowFrame*
+nsTableRowGroupFrame::GetLastRow()
+{
+ for (auto iter = mFrames.rbegin(), end = mFrames.rend(); iter != end; ++iter) {
+ nsTableRowFrame* rowFrame = do_QueryFrame(*iter);
+ if (rowFrame) {
+ return rowFrame;
+ }
+ }
+ return nullptr;
+}
+
+
+struct RowInfo {
+ RowInfo() { bSize = pctBSize = hasStyleBSize = hasPctBSize = isSpecial = 0; }
+ unsigned bSize; // content bsize or fixed bsize, excluding pct bsize
+ unsigned pctBSize:29; // pct bsize
+ unsigned hasStyleBSize:1;
+ unsigned hasPctBSize:1;
+ unsigned isSpecial:1; // there is no cell originating in the row with rowspan=1 and there are at
+ // least 2 cells spanning the row and there is no style bsize on the row
+};
+
+static void
+UpdateBSizes(RowInfo& aRowInfo,
+ nscoord aAdditionalBSize,
+ nscoord& aTotal,
+ nscoord& aUnconstrainedTotal)
+{
+ aRowInfo.bSize += aAdditionalBSize;
+ aTotal += aAdditionalBSize;
+ if (!aRowInfo.hasStyleBSize) {
+ aUnconstrainedTotal += aAdditionalBSize;
+ }
+}
+
+void
+nsTableRowGroupFrame::DidResizeRows(ReflowOutput& aDesiredSize)
+{
+ // Update the cells spanning rows with their new bsizes.
+ // This is the place where all of the cells in the row get set to the bsize
+ // of the row.
+ // Reset the overflow area.
+ aDesiredSize.mOverflowAreas.Clear();
+ for (nsTableRowFrame* rowFrame = GetFirstRow();
+ rowFrame; rowFrame = rowFrame->GetNextRow()) {
+ rowFrame->DidResize();
+ ConsiderChildOverflow(aDesiredSize.mOverflowAreas, rowFrame);
+ }
+}
+
+// This calculates the bsize of all the rows and takes into account
+// style bsize on the row group, style bsizes on rows and cells, style bsizes on rowspans.
+// Actual row bsizes will be adjusted later if the table has a style bsize.
+// Even if rows don't change bsize, this method must be called to set the bsizes of each
+// cell in the row to the bsize of its row.
+void
+nsTableRowGroupFrame::CalculateRowBSizes(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput)
+{
+ nsTableFrame* tableFrame = GetTableFrame();
+ const bool isPaginated = aPresContext->IsPaginated();
+
+ int32_t numEffCols = tableFrame->GetEffectiveColCount();
+
+ int32_t startRowIndex = GetStartRowIndex();
+ // find the row corresponding to the row index we just found
+ nsTableRowFrame* startRowFrame = GetFirstRow();
+
+ if (!startRowFrame) {
+ return;
+ }
+
+ // The current row group block-size is the block-origin of the 1st row
+ // we are about to calculate a block-size for.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ nsSize containerSize; // actual value is unimportant as we're initially
+ // computing sizes, not physical positions
+ nscoord startRowGroupBSize =
+ startRowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm);
+
+ int32_t numRows = GetRowCount() - (startRowFrame->GetRowIndex() - GetStartRowIndex());
+ // Collect the current bsize of each row.
+ if (numRows <= 0)
+ return;
+
+ nsTArray<RowInfo> rowInfo;
+ if (!rowInfo.AppendElements(numRows)) {
+ return;
+ }
+
+ bool hasRowSpanningCell = false;
+ nscoord bSizeOfRows = 0;
+ nscoord bSizeOfUnStyledRows = 0;
+ // Get the bsize of each row without considering rowspans. This will be the max of
+ // the largest desired bsize of each cell, the largest style bsize of each cell,
+ // the style bsize of the row.
+ nscoord pctBSizeBasis = GetBSizeBasis(aReflowInput);
+ int32_t rowIndex; // the index in rowInfo, not among the rows in the row group
+ nsTableRowFrame* rowFrame;
+ for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) {
+ nscoord nonPctBSize = rowFrame->GetContentBSize();
+ if (isPaginated) {
+ nonPctBSize = std::max(nonPctBSize, rowFrame->BSize(wm));
+ }
+ if (!rowFrame->GetPrevInFlow()) {
+ if (rowFrame->HasPctBSize()) {
+ rowInfo[rowIndex].hasPctBSize = true;
+ rowInfo[rowIndex].pctBSize = rowFrame->GetInitialBSize(pctBSizeBasis);
+ }
+ rowInfo[rowIndex].hasStyleBSize = rowFrame->HasStyleBSize();
+ nonPctBSize = std::max(nonPctBSize, rowFrame->GetFixedBSize());
+ }
+ UpdateBSizes(rowInfo[rowIndex], nonPctBSize, bSizeOfRows, bSizeOfUnStyledRows);
+
+ if (!rowInfo[rowIndex].hasStyleBSize) {
+ if (isPaginated || tableFrame->HasMoreThanOneCell(rowIndex + startRowIndex)) {
+ rowInfo[rowIndex].isSpecial = true;
+ // iteratate the row's cell frames to see if any do not have rowspan > 1
+ nsTableCellFrame* cellFrame = rowFrame->GetFirstCell();
+ while (cellFrame) {
+ int32_t rowSpan = tableFrame->GetEffectiveRowSpan(rowIndex + startRowIndex, *cellFrame);
+ if (1 == rowSpan) {
+ rowInfo[rowIndex].isSpecial = false;
+ break;
+ }
+ cellFrame = cellFrame->GetNextCell();
+ }
+ }
+ }
+ // See if a cell spans into the row. If so we'll have to do the next step
+ if (!hasRowSpanningCell) {
+ if (tableFrame->RowIsSpannedInto(rowIndex + startRowIndex, numEffCols)) {
+ hasRowSpanningCell = true;
+ }
+ }
+ }
+
+ if (hasRowSpanningCell) {
+ // Get the bsize of cells with rowspans and allocate any extra space to the rows they span
+ // iteratate the child frames and process the row frames among them
+ for (rowFrame = startRowFrame, rowIndex = 0; rowFrame; rowFrame = rowFrame->GetNextRow(), rowIndex++) {
+ // See if the row has an originating cell with rowspan > 1. We cannot determine this for a row in a
+ // continued row group by calling RowHasSpanningCells, because the row's fif may not have any originating
+ // cells yet the row may have a continued cell which originates in it.
+ if (GetPrevInFlow() || tableFrame->RowHasSpanningCells(startRowIndex + rowIndex, numEffCols)) {
+ nsTableCellFrame* cellFrame = rowFrame->GetFirstCell();
+ // iteratate the row's cell frames
+ while (cellFrame) {
+ nscoord cellSpacingB = tableFrame->GetRowSpacing(startRowIndex + rowIndex);
+ int32_t rowSpan = tableFrame->GetEffectiveRowSpan(rowIndex + startRowIndex, *cellFrame);
+ if ((rowIndex + rowSpan) > numRows) {
+ // there might be rows pushed already to the nextInFlow
+ rowSpan = numRows - rowIndex;
+ }
+ if (rowSpan > 1) { // a cell with rowspan > 1, determine the bsize of the rows it spans
+ nscoord bsizeOfRowsSpanned = 0;
+ nscoord bsizeOfUnStyledRowsSpanned = 0;
+ nscoord numSpecialRowsSpanned = 0;
+ nscoord cellSpacingTotal = 0;
+ int32_t spanX;
+ for (spanX = 0; spanX < rowSpan; spanX++) {
+ bsizeOfRowsSpanned += rowInfo[rowIndex + spanX].bSize;
+ if (!rowInfo[rowIndex + spanX].hasStyleBSize) {
+ bsizeOfUnStyledRowsSpanned += rowInfo[rowIndex + spanX].bSize;
+ }
+ if (0 != spanX) {
+ cellSpacingTotal += cellSpacingB;
+ }
+ if (rowInfo[rowIndex + spanX].isSpecial) {
+ numSpecialRowsSpanned++;
+ }
+ }
+ nscoord bsizeOfAreaSpanned = bsizeOfRowsSpanned + cellSpacingTotal;
+ // get the bsize of the cell
+ LogicalSize cellFrameSize = cellFrame->GetLogicalSize(wm);
+ LogicalSize cellDesSize = cellFrame->GetDesiredSize();
+ rowFrame->CalculateCellActualBSize(cellFrame, cellDesSize.BSize(wm), wm);
+ cellFrameSize.BSize(wm) = cellDesSize.BSize(wm);
+ if (cellFrame->HasVerticalAlignBaseline()) {
+ // to ensure that a spanning cell with a long descender doesn't
+ // collide with the next row, we need to take into account the shift
+ // that will be done to align the cell on the baseline of the row.
+ cellFrameSize.BSize(wm) += rowFrame->GetMaxCellAscent() -
+ cellFrame->GetCellBaseline();
+ }
+
+ if (bsizeOfAreaSpanned < cellFrameSize.BSize(wm)) {
+ // the cell's bsize is larger than the available space of the rows it
+ // spans so distribute the excess bsize to the rows affected
+ nscoord extra = cellFrameSize.BSize(wm) - bsizeOfAreaSpanned;
+ nscoord extraUsed = 0;
+ if (0 == numSpecialRowsSpanned) {
+ //NS_ASSERTION(bsizeOfRowsSpanned > 0, "invalid row span situation");
+ bool haveUnStyledRowsSpanned = (bsizeOfUnStyledRowsSpanned > 0);
+ nscoord divisor = (haveUnStyledRowsSpanned)
+ ? bsizeOfUnStyledRowsSpanned : bsizeOfRowsSpanned;
+ if (divisor > 0) {
+ for (spanX = rowSpan - 1; spanX >= 0; spanX--) {
+ if (!haveUnStyledRowsSpanned || !rowInfo[rowIndex + spanX].hasStyleBSize) {
+ // The amount of additional space each row gets is proportional to its bsize
+ float percent = ((float)rowInfo[rowIndex + spanX].bSize) / ((float)divisor);
+
+ // give rows their percentage, except for the first row which gets the remainder
+ nscoord extraForRow = (0 == spanX) ? extra - extraUsed
+ : NSToCoordRound(((float)(extra)) * percent);
+ extraForRow = std::min(extraForRow, extra - extraUsed);
+ // update the row bsize
+ UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, bSizeOfRows, bSizeOfUnStyledRows);
+ extraUsed += extraForRow;
+ if (extraUsed >= extra) {
+ NS_ASSERTION((extraUsed == extra), "invalid row bsize calculation");
+ break;
+ }
+ }
+ }
+ }
+ else {
+ // put everything in the last row
+ UpdateBSizes(rowInfo[rowIndex + rowSpan - 1], extra, bSizeOfRows, bSizeOfUnStyledRows);
+ }
+ }
+ else {
+ // give the extra to the special rows
+ nscoord numSpecialRowsAllocated = 0;
+ for (spanX = rowSpan - 1; spanX >= 0; spanX--) {
+ if (rowInfo[rowIndex + spanX].isSpecial) {
+ // The amount of additional space each degenerate row gets is proportional to the number of them
+ float percent = 1.0f / ((float)numSpecialRowsSpanned);
+
+ // give rows their percentage, except for the first row which gets the remainder
+ nscoord extraForRow = (numSpecialRowsSpanned - 1 == numSpecialRowsAllocated)
+ ? extra - extraUsed
+ : NSToCoordRound(((float)(extra)) * percent);
+ extraForRow = std::min(extraForRow, extra - extraUsed);
+ // update the row bsize
+ UpdateBSizes(rowInfo[rowIndex + spanX], extraForRow, bSizeOfRows, bSizeOfUnStyledRows);
+ extraUsed += extraForRow;
+ if (extraUsed >= extra) {
+ NS_ASSERTION((extraUsed == extra), "invalid row bsize calculation");
+ break;
+ }
+ }
+ }
+ }
+ }
+ } // if (rowSpan > 1)
+ cellFrame = cellFrame->GetNextCell();
+ } // while (cellFrame)
+ } // if (tableFrame->RowHasSpanningCells(startRowIndex + rowIndex) {
+ } // while (rowFrame)
+ }
+
+ // pct bsize rows have already got their content bsizes.
+ // Give them their pct bsizes up to pctBSizeBasis
+ nscoord extra = pctBSizeBasis - bSizeOfRows;
+ for (rowFrame = startRowFrame, rowIndex = 0; rowFrame && (extra > 0);
+ rowFrame = rowFrame->GetNextRow(), rowIndex++) {
+ RowInfo& rInfo = rowInfo[rowIndex];
+ if (rInfo.hasPctBSize) {
+ nscoord rowExtra = (rInfo.pctBSize > rInfo.bSize)
+ ? rInfo.pctBSize - rInfo.bSize: 0;
+ rowExtra = std::min(rowExtra, extra);
+ UpdateBSizes(rInfo, rowExtra, bSizeOfRows, bSizeOfUnStyledRows);
+ extra -= rowExtra;
+ }
+ }
+
+ bool styleBSizeAllocation = false;
+ nscoord rowGroupBSize = startRowGroupBSize + bSizeOfRows +
+ tableFrame->GetRowSpacing(0, numRows-1);
+ // if we have a style bsize, allocate the extra bsize to unconstrained rows
+ if ((aReflowInput.ComputedBSize() > rowGroupBSize) &&
+ (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize())) {
+ nscoord extraComputedBSize = aReflowInput.ComputedBSize() - rowGroupBSize;
+ nscoord extraUsed = 0;
+ bool haveUnStyledRows = (bSizeOfUnStyledRows > 0);
+ nscoord divisor = (haveUnStyledRows)
+ ? bSizeOfUnStyledRows : bSizeOfRows;
+ if (divisor > 0) {
+ styleBSizeAllocation = true;
+ for (rowIndex = 0; rowIndex < numRows; rowIndex++) {
+ if (!haveUnStyledRows || !rowInfo[rowIndex].hasStyleBSize) {
+ // The amount of additional space each row gets is based on the
+ // percentage of space it occupies
+ float percent = ((float)rowInfo[rowIndex].bSize) / ((float)divisor);
+ // give rows their percentage, except for the last row which gets the remainder
+ nscoord extraForRow = (numRows - 1 == rowIndex)
+ ? extraComputedBSize - extraUsed
+ : NSToCoordRound(((float)extraComputedBSize) * percent);
+ extraForRow = std::min(extraForRow, extraComputedBSize - extraUsed);
+ // update the row bsize
+ UpdateBSizes(rowInfo[rowIndex], extraForRow, bSizeOfRows, bSizeOfUnStyledRows);
+ extraUsed += extraForRow;
+ if (extraUsed >= extraComputedBSize) {
+ NS_ASSERTION((extraUsed == extraComputedBSize), "invalid row bsize calculation");
+ break;
+ }
+ }
+ }
+ }
+ rowGroupBSize = aReflowInput.ComputedBSize();
+ }
+
+ if (wm.IsVertical()) {
+ // we need the correct containerSize below for block positioning in
+ // vertical-rl writing mode
+ containerSize.width = rowGroupBSize;
+ }
+
+ nscoord bOrigin = startRowGroupBSize;
+ // update the rows with their (potentially) new bsizes
+ for (rowFrame = startRowFrame, rowIndex = 0; rowFrame;
+ rowFrame = rowFrame->GetNextRow(), rowIndex++) {
+ nsRect rowBounds = rowFrame->GetRect();
+ LogicalSize rowBoundsSize(wm, rowBounds.Size());
+ nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect();
+ nscoord deltaB =
+ bOrigin - rowFrame->GetLogicalNormalPosition(wm, containerSize).B(wm);
+
+ nscoord rowBSize = (rowInfo[rowIndex].bSize > 0) ? rowInfo[rowIndex].bSize : 0;
+
+ if (deltaB != 0 || (rowBSize != rowBoundsSize.BSize(wm))) {
+ // Resize/move the row to its final size and position
+ if (deltaB != 0) {
+ rowFrame->InvalidateFrameSubtree();
+ }
+
+ rowFrame->MovePositionBy(wm, LogicalPoint(wm, 0, deltaB));
+ rowFrame->SetSize(LogicalSize(wm, rowBoundsSize.ISize(wm), rowBSize));
+
+ nsTableFrame::InvalidateTableFrame(rowFrame, rowBounds, rowVisualOverflow,
+ false);
+
+ if (deltaB != 0) {
+ nsTableFrame::RePositionViews(rowFrame);
+ // XXXbz we don't need to update our overflow area?
+ }
+ }
+ bOrigin += rowBSize + tableFrame->GetRowSpacing(startRowIndex + rowIndex);
+ }
+
+ if (isPaginated && styleBSizeAllocation) {
+ // since the row group has a style bsize, cache the row bsizes,
+ // so next in flows can honor them
+ CacheRowBSizesForPrinting(aPresContext, GetFirstRow(), wm);
+ }
+
+ DidResizeRows(aDesiredSize);
+
+ aDesiredSize.BSize(wm) = rowGroupBSize; // Adjust our desired size
+}
+
+nscoord
+nsTableRowGroupFrame::CollapseRowGroupIfNecessary(nscoord aBTotalOffset,
+ nscoord aISize,
+ WritingMode aWM)
+{
+ nsTableFrame* tableFrame = GetTableFrame();
+ nsSize containerSize = tableFrame->GetSize();
+ const nsStyleVisibility* groupVis = StyleVisibility();
+ bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible);
+ if (collapseGroup) {
+ tableFrame->SetNeedToCollapse(true);
+ }
+
+ nsOverflowAreas overflow;
+
+ nsTableRowFrame* rowFrame = GetFirstRow();
+ bool didCollapse = false;
+ nscoord bGroupOffset = 0;
+ while (rowFrame) {
+ bGroupOffset += rowFrame->CollapseRowIfNecessary(bGroupOffset,
+ aISize, collapseGroup,
+ didCollapse);
+ ConsiderChildOverflow(overflow, rowFrame);
+ rowFrame = rowFrame->GetNextRow();
+ }
+
+ LogicalRect groupRect = GetLogicalRect(aWM, containerSize);
+ nsRect oldGroupRect = GetRect();
+ nsRect oldGroupVisualOverflow = GetVisualOverflowRect();
+
+ groupRect.BSize(aWM) -= bGroupOffset;
+ if (didCollapse) {
+ // add back the cellspacing between rowgroups
+ groupRect.BSize(aWM) += tableFrame->GetRowSpacing(GetStartRowIndex() +
+ GetRowCount());
+ }
+
+ groupRect.BStart(aWM) -= aBTotalOffset;
+ groupRect.ISize(aWM) = aISize;
+
+ if (aBTotalOffset != 0) {
+ InvalidateFrameSubtree();
+ }
+
+ SetRect(aWM, groupRect, containerSize);
+ overflow.UnionAllWith(nsRect(0, 0, groupRect.Width(aWM),
+ groupRect.Height(aWM)));
+ FinishAndStoreOverflow(overflow, groupRect.Size(aWM).GetPhysicalSize(aWM));
+ nsTableFrame::RePositionViews(this);
+ nsTableFrame::InvalidateTableFrame(this, oldGroupRect, oldGroupVisualOverflow,
+ false);
+
+ return bGroupOffset;
+}
+
+// Move a child that was skipped during a reflow.
+void
+nsTableRowGroupFrame::SlideChild(TableRowGroupReflowInput& aReflowInput,
+ nsIFrame* aKidFrame)
+{
+ // Move the frame if we need to.
+ WritingMode wm = aReflowInput.reflowInput.GetWritingMode();
+ const nsSize containerSize =
+ aReflowInput.reflowInput.ComputedSizeAsContainerIfConstrained();
+ LogicalPoint oldPosition =
+ aKidFrame->GetLogicalNormalPosition(wm, containerSize);
+ LogicalPoint newPosition = oldPosition;
+ newPosition.B(wm) = aReflowInput.bCoord;
+ if (oldPosition.B(wm) != newPosition.B(wm)) {
+ aKidFrame->InvalidateFrameSubtree();
+ aReflowInput.reflowInput.ApplyRelativePositioning(&newPosition,
+ containerSize);
+ aKidFrame->SetPosition(wm, newPosition, containerSize);
+ nsTableFrame::RePositionViews(aKidFrame);
+ aKidFrame->InvalidateFrameSubtree();
+ }
+}
+
+// Create a continuing frame, add it to the child list, and then push it
+// and the frames that follow
+void
+nsTableRowGroupFrame::CreateContinuingRowFrame(nsPresContext& aPresContext,
+ nsIFrame& aRowFrame,
+ nsIFrame** aContRowFrame)
+{
+ // XXX what is the row index?
+ if (!aContRowFrame) {NS_ASSERTION(false, "bad call"); return;}
+ // create the continuing frame which will create continuing cell frames
+ *aContRowFrame = aPresContext.PresShell()->FrameConstructor()->
+ CreateContinuingFrame(&aPresContext, &aRowFrame, this);
+
+ // Add the continuing row frame to the child list
+ mFrames.InsertFrame(nullptr, &aRowFrame, *aContRowFrame);
+
+ // Push the continuing row frame and the frames that follow
+ PushChildren(*aContRowFrame, &aRowFrame);
+}
+
+// Reflow the cells with rowspan > 1 which originate between aFirstRow
+// and end on or after aLastRow. aFirstTruncatedRow is the highest row on the
+// page that contains a cell which cannot split on this page
+void
+nsTableRowGroupFrame::SplitSpanningCells(nsPresContext& aPresContext,
+ const ReflowInput& aReflowInput,
+ nsTableFrame& aTable,
+ nsTableRowFrame& aFirstRow,
+ nsTableRowFrame& aLastRow,
+ bool aFirstRowIsTopOfPage,
+ nscoord aSpanningRowBEnd,
+ nsTableRowFrame*& aContRow,
+ nsTableRowFrame*& aFirstTruncatedRow,
+ nscoord& aDesiredBSize)
+{
+ NS_ASSERTION(aSpanningRowBEnd >= 0, "Can't split negative bsizes");
+ aFirstTruncatedRow = nullptr;
+ aDesiredBSize = 0;
+
+ const bool borderCollapse = aTable.IsBorderCollapse();
+ int32_t lastRowIndex = aLastRow.GetRowIndex();
+ bool wasLast = false;
+ bool haveRowSpan = false;
+ // Iterate the rows between aFirstRow and aLastRow
+ for (nsTableRowFrame* row = &aFirstRow; !wasLast; row = row->GetNextRow()) {
+ wasLast = (row == &aLastRow);
+ int32_t rowIndex = row->GetRowIndex();
+ nsPoint rowPos = row->GetNormalPosition();
+ // Iterate the cells looking for those that have rowspan > 1
+ for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) {
+ int32_t rowSpan = aTable.GetEffectiveRowSpan(rowIndex, *cell);
+ // Only reflow rowspan > 1 cells which span aLastRow. Those which don't span aLastRow
+ // were reflowed correctly during the unconstrained bsize reflow.
+ if ((rowSpan > 1) && (rowIndex + rowSpan > lastRowIndex)) {
+ haveRowSpan = true;
+ nsReflowStatus status;
+ // Ask the row to reflow the cell to the bsize of all the rows it spans up through aLastRow
+ // cellAvailBSize is the space between the row group start and the end of the page
+ nscoord cellAvailBSize = aSpanningRowBEnd - rowPos.y;
+ NS_ASSERTION(cellAvailBSize >= 0, "No space for cell?");
+ bool isTopOfPage = (row == &aFirstRow) && aFirstRowIsTopOfPage;
+
+ nsRect rowRect = row->GetNormalRect();
+ nsSize rowAvailSize(aReflowInput.AvailableWidth(),
+ std::max(aReflowInput.AvailableHeight() - rowRect.y,
+ 0));
+ // don't let the available height exceed what
+ // CalculateRowBSizes set for it
+ rowAvailSize.height = std::min(rowAvailSize.height, rowRect.height);
+ ReflowInput rowReflowInput(&aPresContext, aReflowInput, row,
+ LogicalSize(row->GetWritingMode(),
+ rowAvailSize),
+ nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(aPresContext, borderCollapse, rowReflowInput);
+ rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page
+
+ nscoord cellBSize = row->ReflowCellFrame(&aPresContext, rowReflowInput,
+ isTopOfPage, cell,
+ cellAvailBSize, status);
+ aDesiredBSize = std::max(aDesiredBSize, rowPos.y + cellBSize);
+ if (NS_FRAME_IS_COMPLETE(status)) {
+ if (cellBSize > cellAvailBSize) {
+ aFirstTruncatedRow = row;
+ if ((row != &aFirstRow) || !aFirstRowIsTopOfPage) {
+ // return now, since we will be getting another reflow after either (1) row is
+ // moved to the next page or (2) the row group is moved to the next page
+ return;
+ }
+ }
+ }
+ else {
+ if (!aContRow) {
+ CreateContinuingRowFrame(aPresContext, aLastRow, (nsIFrame**)&aContRow);
+ }
+ if (aContRow) {
+ if (row != &aLastRow) {
+ // aContRow needs a continuation for cell, since cell spanned into aLastRow
+ // but does not originate there
+ nsTableCellFrame* contCell = static_cast<nsTableCellFrame*>(
+ aPresContext.PresShell()->FrameConstructor()->
+ CreateContinuingFrame(&aPresContext, cell, &aLastRow));
+ int32_t colIndex;
+ cell->GetColIndex(colIndex);
+ aContRow->InsertCellFrame(contCell, colIndex);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!haveRowSpan) {
+ aDesiredBSize = aLastRow.GetNormalRect().YMost();
+ }
+}
+
+// Remove the next-in-flow of the row, its cells and their cell blocks. This
+// is necessary in case the row doesn't need a continuation later on or needs
+// a continuation which doesn't have the same number of cells that now exist.
+void
+nsTableRowGroupFrame::UndoContinuedRow(nsPresContext* aPresContext,
+ nsTableRowFrame* aRow)
+{
+ if (!aRow) return; // allow null aRow to avoid callers doing null checks
+
+ // rowBefore was the prev-sibling of aRow's next-sibling before aRow was created
+ nsTableRowFrame* rowBefore = (nsTableRowFrame*)aRow->GetPrevInFlow();
+ NS_PRECONDITION(mFrames.ContainsFrame(rowBefore),
+ "rowBefore not in our frame list?");
+
+ AutoFrameListPtr overflows(aPresContext, StealOverflowFrames());
+ if (!rowBefore || !overflows || overflows->IsEmpty() ||
+ overflows->FirstChild() != aRow) {
+ NS_ERROR("invalid continued row");
+ return;
+ }
+
+ // Destroy aRow, its cells, and their cell blocks. Cell blocks that have split
+ // will not have reflowed yet to pick up content from any overflow lines.
+ overflows->DestroyFrame(aRow);
+
+ // Put the overflow rows into our child list
+ if (!overflows->IsEmpty()) {
+ mFrames.InsertFrames(nullptr, rowBefore, *overflows);
+ }
+}
+
+static nsTableRowFrame*
+GetRowBefore(nsTableRowFrame& aStartRow,
+ nsTableRowFrame& aRow)
+{
+ nsTableRowFrame* rowBefore = nullptr;
+ for (nsTableRowFrame* sib = &aStartRow; sib && (sib != &aRow); sib = sib->GetNextRow()) {
+ rowBefore = sib;
+ }
+ return rowBefore;
+}
+
+nsresult
+nsTableRowGroupFrame::SplitRowGroup(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsTableFrame* aTableFrame,
+ nsReflowStatus& aStatus,
+ bool aRowForcedPageBreak)
+{
+ NS_PRECONDITION(aPresContext->IsPaginated(), "SplitRowGroup currently supports only paged media");
+
+ nsTableRowFrame* prevRowFrame = nullptr;
+ aDesiredSize.Height() = 0;
+
+ nscoord availWidth = aReflowInput.AvailableWidth();
+ nscoord availHeight = aReflowInput.AvailableHeight();
+
+ const bool borderCollapse = aTableFrame->IsBorderCollapse();
+
+ // get the page height
+ nscoord pageHeight = aPresContext->GetPageSize().height;
+ NS_ASSERTION(pageHeight != NS_UNCONSTRAINEDSIZE,
+ "The table shouldn't be split when there should be space");
+
+ bool isTopOfPage = aReflowInput.mFlags.mIsTopOfPage;
+ nsTableRowFrame* firstRowThisPage = GetFirstRow();
+
+ // Need to dirty the table's geometry, or else the row might skip
+ // reflowing its cell as an optimization.
+ aTableFrame->SetGeometryDirty();
+
+ // Walk each of the row frames looking for the first row frame that doesn't fit
+ // in the available space
+ for (nsTableRowFrame* rowFrame = firstRowThisPage; rowFrame; rowFrame = rowFrame->GetNextRow()) {
+ bool rowIsOnPage = true;
+ nscoord cellSpacingB = aTableFrame->GetRowSpacing(rowFrame->GetRowIndex());
+ nsRect rowRect = rowFrame->GetNormalRect();
+ // See if the row fits on this page
+ if (rowRect.YMost() > availHeight) {
+ nsTableRowFrame* contRow = nullptr;
+ // Reflow the row in the availabe space and have it split if it is the 1st
+ // row (on the page) or there is at least 5% of the current page available
+ // XXX this 5% should be made a preference
+ if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20)) {
+ nsSize availSize(availWidth, std::max(availHeight - rowRect.y, 0));
+ // don't let the available height exceed what CalculateRowHeights set for it
+ availSize.height = std::min(availSize.height, rowRect.height);
+
+ ReflowInput rowReflowInput(aPresContext, aReflowInput, rowFrame,
+ LogicalSize(rowFrame->GetWritingMode(),
+ availSize),
+ nullptr,
+ ReflowInput::CALLER_WILL_INIT);
+
+ InitChildReflowInput(*aPresContext, borderCollapse, rowReflowInput);
+ rowReflowInput.mFlags.mIsTopOfPage = isTopOfPage; // set top of page
+ ReflowOutput rowMetrics(aReflowInput);
+
+ // Get the old size before we reflow.
+ nsRect oldRowRect = rowFrame->GetRect();
+ nsRect oldRowVisualOverflow = rowFrame->GetVisualOverflowRect();
+
+ // Reflow the cell with the constrained height. A cell with rowspan >1 will get this
+ // reflow later during SplitSpanningCells.
+ ReflowChild(rowFrame, aPresContext, rowMetrics, rowReflowInput,
+ 0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus);
+ rowFrame->SetSize(nsSize(rowMetrics.Width(), rowMetrics.Height()));
+ rowFrame->DidReflow(aPresContext, nullptr, nsDidReflowStatus::FINISHED);
+ rowFrame->DidResize();
+
+ if (!aRowForcedPageBreak && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) &&
+ ShouldAvoidBreakInside(aReflowInput)) {
+ aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+ break;
+ }
+
+ nsTableFrame::InvalidateTableFrame(rowFrame, oldRowRect,
+ oldRowVisualOverflow,
+ false);
+
+ if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) {
+ // The row frame is incomplete and all of the rowspan 1 cells' block frames split
+ if ((rowMetrics.Height() <= rowReflowInput.AvailableHeight()) || isTopOfPage) {
+ // The row stays on this page because either it split ok or we're on the top of page.
+ // If top of page and the height exceeded the avail height, then there will be data loss
+ NS_ASSERTION(rowMetrics.Height() <= rowReflowInput.AvailableHeight(),
+ "data loss - incomplete row needed more height than available, on top of page");
+ CreateContinuingRowFrame(*aPresContext, *rowFrame, (nsIFrame**)&contRow);
+ if (contRow) {
+ aDesiredSize.Height() += rowMetrics.Height();
+ if (prevRowFrame)
+ aDesiredSize.Height() += cellSpacingB;
+ }
+ else return NS_ERROR_NULL_POINTER;
+ }
+ else {
+ // Put the row on the next page to give it more height
+ rowIsOnPage = false;
+ }
+ }
+ else {
+ // The row frame is complete because either (1) its minimum height is greater than the
+ // available height we gave it, or (2) it may have been given a larger height through
+ // style than its content, or (3) it contains a rowspan >1 cell which hasn't been
+ // reflowed with a constrained height yet (we will find out when SplitSpanningCells is
+ // called below)
+ if (rowMetrics.Height() > availSize.height ||
+ (NS_INLINE_IS_BREAK_BEFORE(aStatus) && !aRowForcedPageBreak)) {
+ // cases (1) and (2)
+ if (isTopOfPage) {
+ // We're on top of the page, so keep the row on this page. There will be data loss.
+ // Push the row frame that follows
+ nsTableRowFrame* nextRowFrame = rowFrame->GetNextRow();
+ if (nextRowFrame) {
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ }
+ aDesiredSize.Height() += rowMetrics.Height();
+ if (prevRowFrame)
+ aDesiredSize.Height() += cellSpacingB;
+ NS_WARNING("data loss - complete row needed more height than available, on top of page");
+ }
+ else {
+ // We're not on top of the page, so put the row on the next page to give it more height
+ rowIsOnPage = false;
+ }
+ }
+ }
+ } //if (!prevRowFrame || (availHeight - aDesiredSize.Height() > pageHeight / 20))
+ else {
+ // put the row on the next page to give it more height
+ rowIsOnPage = false;
+ }
+
+ nsTableRowFrame* lastRowThisPage = rowFrame;
+ nscoord spanningRowBottom = availHeight;
+ if (!rowIsOnPage) {
+ NS_ASSERTION(!contRow, "We should not have created a continuation if none of this row fits");
+ if (!aRowForcedPageBreak && ShouldAvoidBreakInside(aReflowInput)) {
+ aStatus = NS_INLINE_LINE_BREAK_BEFORE();
+ break;
+ }
+ if (prevRowFrame) {
+ spanningRowBottom = prevRowFrame->GetNormalRect().YMost();
+ lastRowThisPage = prevRowFrame;
+ isTopOfPage = (lastRowThisPage == firstRowThisPage) && aReflowInput.mFlags.mIsTopOfPage;
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ }
+ else {
+ // We can't push children, so let our parent reflow us again with more space
+ aDesiredSize.Height() = rowRect.YMost();
+ aStatus = NS_FRAME_COMPLETE;
+ break;
+ }
+ }
+ // reflow the cells with rowspan >1 that occur on the page
+
+ nsTableRowFrame* firstTruncatedRow;
+ nscoord bMost;
+ SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, *firstRowThisPage,
+ *lastRowThisPage, aReflowInput.mFlags.mIsTopOfPage, spanningRowBottom, contRow,
+ firstTruncatedRow, bMost);
+ if (firstTruncatedRow) {
+ // A rowspan >1 cell did not fit (and could not split) in the space we gave it
+ if (firstTruncatedRow == firstRowThisPage) {
+ if (aReflowInput.mFlags.mIsTopOfPage) {
+ NS_WARNING("data loss in a row spanned cell");
+ }
+ else {
+ // We can't push children, so let our parent reflow us again with more space
+ aDesiredSize.Height() = rowRect.YMost();
+ aStatus = NS_FRAME_COMPLETE;
+ UndoContinuedRow(aPresContext, contRow);
+ contRow = nullptr;
+ }
+ }
+ else { // (firstTruncatedRow != firstRowThisPage)
+ // Try to put firstTruncateRow on the next page
+ nsTableRowFrame* rowBefore = ::GetRowBefore(*firstRowThisPage, *firstTruncatedRow);
+ nscoord oldSpanningRowBottom = spanningRowBottom;
+ spanningRowBottom = rowBefore->GetNormalRect().YMost();
+
+ UndoContinuedRow(aPresContext, contRow);
+ contRow = nullptr;
+ nsTableRowFrame* oldLastRowThisPage = lastRowThisPage;
+ lastRowThisPage = rowBefore;
+ aStatus = NS_FRAME_NOT_COMPLETE;
+
+ // Call SplitSpanningCells again with rowBefore as the last row on the page
+ SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame,
+ *firstRowThisPage, *rowBefore, aReflowInput.mFlags.mIsTopOfPage,
+ spanningRowBottom, contRow, firstTruncatedRow, aDesiredSize.Height());
+ if (firstTruncatedRow) {
+ if (aReflowInput.mFlags.mIsTopOfPage) {
+ // We were better off with the 1st call to SplitSpanningCells, do it again
+ UndoContinuedRow(aPresContext, contRow);
+ contRow = nullptr;
+ lastRowThisPage = oldLastRowThisPage;
+ spanningRowBottom = oldSpanningRowBottom;
+ SplitSpanningCells(*aPresContext, aReflowInput, *aTableFrame, *firstRowThisPage,
+ *lastRowThisPage, aReflowInput.mFlags.mIsTopOfPage, spanningRowBottom, contRow,
+ firstTruncatedRow, aDesiredSize.Height());
+ NS_WARNING("data loss in a row spanned cell");
+ }
+ else {
+ // Let our parent reflow us again with more space
+ aDesiredSize.Height() = rowRect.YMost();
+ aStatus = NS_FRAME_COMPLETE;
+ UndoContinuedRow(aPresContext, contRow);
+ contRow = nullptr;
+ }
+ }
+ } // if (firstTruncatedRow == firstRowThisPage)
+ } // if (firstTruncatedRow)
+ else {
+ aDesiredSize.Height() = std::max(aDesiredSize.Height(), bMost);
+ if (contRow) {
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ }
+ }
+ if (NS_FRAME_IS_NOT_COMPLETE(aStatus) && !contRow) {
+ nsTableRowFrame* nextRow = lastRowThisPage->GetNextRow();
+ if (nextRow) {
+ PushChildren(nextRow, lastRowThisPage);
+ }
+ }
+ break;
+ } // if (rowRect.YMost() > availHeight)
+ else {
+ aDesiredSize.Height() = rowRect.YMost();
+ prevRowFrame = rowFrame;
+ // see if there is a page break after the row
+ nsTableRowFrame* nextRow = rowFrame->GetNextRow();
+ if (nextRow && nsTableFrame::PageBreakAfter(rowFrame, nextRow)) {
+ PushChildren(nextRow, rowFrame);
+ aStatus = NS_FRAME_NOT_COMPLETE;
+ break;
+ }
+ }
+ // after the 1st row that has a height, we can't be on top
+ // of the page anymore.
+ isTopOfPage = isTopOfPage && rowRect.YMost() == 0;
+ }
+ return NS_OK;
+}
+
+/** Layout the entire row group.
+ * This method stacks rows vertically according to HTML 4.0 rules.
+ * Rows are responsible for layout of their children.
+ */
+void
+nsTableRowGroupFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableRowGroupFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ // Row geometry may be going to change so we need to invalidate any row cursor.
+ ClearRowCursor();
+
+ // see if a special bsize reflow needs to occur due to having a pct bsize
+ nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ TableRowGroupReflowInput state(aReflowInput, tableFrame);
+ const nsStyleVisibility* groupVis = StyleVisibility();
+ bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible);
+ if (collapseGroup) {
+ tableFrame->SetNeedToCollapse(true);
+ }
+
+ // Check for an overflow list
+ MoveOverflowToChildList();
+
+ // Reflow the existing frames.
+ bool splitDueToPageBreak = false;
+ ReflowChildren(aPresContext, aDesiredSize, state, aStatus,
+ &splitDueToPageBreak);
+
+ // See if all the frames fit. Do not try to split anything if we're
+ // not paginated ... we can't split across columns yet.
+ if (aReflowInput.mFlags.mTableIsSplittable &&
+ NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() &&
+ (NS_FRAME_NOT_COMPLETE == aStatus || splitDueToPageBreak ||
+ aDesiredSize.Height() > aReflowInput.AvailableHeight())) {
+ // Nope, find a place to split the row group
+ bool specialReflow = (bool)aReflowInput.mFlags.mSpecialBSizeReflow;
+ ((ReflowInput::ReflowInputFlags&)aReflowInput.mFlags).mSpecialBSizeReflow = false;
+
+ SplitRowGroup(aPresContext, aDesiredSize, aReflowInput, tableFrame, aStatus,
+ splitDueToPageBreak);
+
+ ((ReflowInput::ReflowInputFlags&)aReflowInput.mFlags).mSpecialBSizeReflow = specialReflow;
+ }
+
+ // XXXmats The following is just bogus. We leave it here for now because
+ // ReflowChildren should pull up rows from our next-in-flow before returning
+ // a Complete status, but doesn't (bug 804888).
+ if (GetNextInFlow() && GetNextInFlow()->PrincipalChildList().FirstChild()) {
+ NS_FRAME_SET_INCOMPLETE(aStatus);
+ }
+
+ SetHasStyleBSize((NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) &&
+ (aReflowInput.ComputedBSize() > 0));
+
+ // Just set our isize to what was available.
+ // The table will calculate the isize and not use our value.
+ WritingMode wm = aReflowInput.GetWritingMode();
+ aDesiredSize.ISize(wm) = aReflowInput.AvailableISize();
+
+ aDesiredSize.UnionOverflowAreasWithDesiredBounds();
+
+ // If our parent is in initial reflow, it'll handle invalidating our
+ // entire overflow rect.
+ if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
+ nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) {
+ InvalidateFrame();
+ }
+
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ // Any absolutely-positioned children will get reflowed in
+ // nsFrame::FixupPositionedTableParts in another pass, so propagate our
+ // dirtiness to them before our parent clears our dirty bits.
+ PushDirtyBitToAbsoluteFrames();
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+bool
+nsTableRowGroupFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
+{
+ // Row cursor invariants depend on the visual overflow area of the rows,
+ // which may have changed, so we need to clear the cursor now.
+ ClearRowCursor();
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+/* virtual */ void
+nsTableRowGroupFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsContainerFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (!aOldStyleContext) //avoid this on init
+ return;
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse() &&
+ tableFrame->BCRecalcNeeded(aOldStyleContext, StyleContext())) {
+ TableArea damageArea(0, GetStartRowIndex(), tableFrame->GetColCount(),
+ GetRowCount());
+ tableFrame->AddBCDamageArea(damageArea);
+ }
+}
+
+void
+nsTableRowGroupFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+
+ DrainSelfOverflowList(); // ensure the last frame is in mFrames
+ ClearRowCursor();
+
+ // collect the new row frames in an array
+ // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
+ AutoTArray<nsTableRowFrame*, 8> rows;
+ for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
+ nsTableRowFrame *rowFrame = do_QueryFrame(e.get());
+ NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
+ if (rowFrame) {
+ NS_ASSERTION(mozilla::StyleDisplay::TableRow ==
+ e.get()->StyleDisplay()->mDisplay,
+ "wrong display type on rowframe");
+ rows.AppendElement(rowFrame);
+ }
+ }
+
+ int32_t rowIndex = GetRowCount();
+ // Append the frames to the sibling chain
+ mFrames.AppendFrames(nullptr, aFrameList);
+
+ if (rows.Length() > 0) {
+ nsTableFrame* tableFrame = GetTableFrame();
+ tableFrame->AppendRows(this, rowIndex, rows);
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ tableFrame->SetGeometryDirty();
+ }
+}
+
+void
+nsTableRowGroupFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+
+ DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames
+ ClearRowCursor();
+
+ // collect the new row frames in an array
+ // XXXbz why are we doing the QI stuff? There shouldn't be any non-rows here.
+ nsTableFrame* tableFrame = GetTableFrame();
+ nsTArray<nsTableRowFrame*> rows;
+ bool gotFirstRow = false;
+ for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
+ nsTableRowFrame *rowFrame = do_QueryFrame(e.get());
+ NS_ASSERTION(rowFrame, "Unexpected frame; frame constructor screwed up");
+ if (rowFrame) {
+ NS_ASSERTION(mozilla::StyleDisplay::TableRow ==
+ e.get()->StyleDisplay()->mDisplay,
+ "wrong display type on rowframe");
+ rows.AppendElement(rowFrame);
+ if (!gotFirstRow) {
+ rowFrame->SetFirstInserted(true);
+ gotFirstRow = true;
+ tableFrame->SetRowInserted(true);
+ }
+ }
+ }
+
+ int32_t startRowIndex = GetStartRowIndex();
+ // Insert the frames in the sibling chain
+ mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
+
+ int32_t numRows = rows.Length();
+ if (numRows > 0) {
+ nsTableRowFrame* prevRow = (nsTableRowFrame *)nsTableFrame::GetFrameAtOrBefore(this, aPrevFrame, nsGkAtoms::tableRowFrame);
+ int32_t rowIndex = (prevRow) ? prevRow->GetRowIndex() + 1 : startRowIndex;
+ tableFrame->InsertRows(this, rows, rowIndex, true);
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ tableFrame->SetGeometryDirty();
+ }
+}
+
+void
+nsTableRowGroupFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+
+ ClearRowCursor();
+
+ // XXX why are we doing the QI stuff? There shouldn't be any non-rows here.
+ nsTableRowFrame* rowFrame = do_QueryFrame(aOldFrame);
+ if (rowFrame) {
+ nsTableFrame* tableFrame = GetTableFrame();
+ // remove the rows from the table (and flag a rebalance)
+ tableFrame->RemoveRows(*rowFrame, 1, true);
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ tableFrame->SetGeometryDirty();
+ }
+ mFrames.DestroyFrame(aOldFrame);
+}
+
+/* virtual */ nsMargin
+nsTableRowGroupFrame::GetUsedMargin() const
+{
+ return nsMargin(0,0,0,0);
+}
+
+/* virtual */ nsMargin
+nsTableRowGroupFrame::GetUsedBorder() const
+{
+ return nsMargin(0,0,0,0);
+}
+
+/* virtual */ nsMargin
+nsTableRowGroupFrame::GetUsedPadding() const
+{
+ return nsMargin(0,0,0,0);
+}
+
+nscoord
+nsTableRowGroupFrame::GetBSizeBasis(const ReflowInput& aReflowInput)
+{
+ nscoord result = 0;
+ nsTableFrame* tableFrame = GetTableFrame();
+ int32_t startRowIndex = GetStartRowIndex();
+ if ((aReflowInput.ComputedBSize() > 0) && (aReflowInput.ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
+ nscoord cellSpacing = tableFrame->GetRowSpacing(startRowIndex,
+ std::max(startRowIndex,
+ startRowIndex + GetRowCount() - 1));
+ result = aReflowInput.ComputedBSize() - cellSpacing;
+ }
+ else {
+ const ReflowInput* parentRI = aReflowInput.mParentReflowInput;
+ if (parentRI && (tableFrame != parentRI->mFrame)) {
+ parentRI = parentRI->mParentReflowInput;
+ }
+ if (parentRI && (tableFrame == parentRI->mFrame) &&
+ (parentRI->ComputedBSize() > 0) && (parentRI->ComputedBSize() < NS_UNCONSTRAINEDSIZE)) {
+ nscoord cellSpacing = tableFrame->GetRowSpacing(-1, tableFrame->GetRowCount());
+ result = parentRI->ComputedBSize() - cellSpacing;
+ }
+ }
+
+ return result;
+}
+
+bool
+nsTableRowGroupFrame::IsSimpleRowFrame(nsTableFrame* aTableFrame,
+ nsTableRowFrame* aRowFrame)
+{
+ int32_t rowIndex = aRowFrame->GetRowIndex();
+
+ // It's a simple row frame if there are no cells that span into or
+ // across the row
+ int32_t numEffCols = aTableFrame->GetEffectiveColCount();
+ if (!aTableFrame->RowIsSpannedInto(rowIndex, numEffCols) &&
+ !aTableFrame->RowHasSpanningCells(rowIndex, numEffCols)) {
+ return true;
+ }
+
+ return false;
+}
+
+nsIAtom*
+nsTableRowGroupFrame::GetType() const
+{
+ return nsGkAtoms::tableRowGroupFrame;
+}
+
+/** find page break before the first row **/
+bool
+nsTableRowGroupFrame::HasInternalBreakBefore() const
+{
+ nsIFrame* firstChild = mFrames.FirstChild();
+ if (!firstChild)
+ return false;
+ return firstChild->StyleDisplay()->mBreakBefore;
+}
+
+/** find page break after the last row **/
+bool
+nsTableRowGroupFrame::HasInternalBreakAfter() const
+{
+ nsIFrame* lastChild = mFrames.LastChild();
+ if (!lastChild)
+ return false;
+ return lastChild->StyleDisplay()->mBreakAfter;
+}
+/* ----- global methods ----- */
+
+nsTableRowGroupFrame*
+NS_NewTableRowGroupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTableRowGroupFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableRowGroupFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTableRowGroupFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("TableRowGroup"), aResult);
+}
+#endif
+
+LogicalMargin
+nsTableRowGroupFrame::GetBCBorderWidth(WritingMode aWM)
+{
+ LogicalMargin border(aWM);
+ nsTableRowFrame* firstRowFrame = nullptr;
+ nsTableRowFrame* lastRowFrame = nullptr;
+ for (nsTableRowFrame* rowFrame = GetFirstRow(); rowFrame; rowFrame = rowFrame->GetNextRow()) {
+ if (!firstRowFrame) {
+ firstRowFrame = rowFrame;
+ }
+ lastRowFrame = rowFrame;
+ }
+ if (firstRowFrame) {
+ border.BStart(aWM) = nsPresContext::
+ CSSPixelsToAppUnits(firstRowFrame->GetBStartBCBorderWidth());
+ border.BEnd(aWM) = nsPresContext::
+ CSSPixelsToAppUnits(lastRowFrame->GetBEndBCBorderWidth());
+ }
+ return border;
+}
+
+void nsTableRowGroupFrame::SetContinuousBCBorderWidth(LogicalSide aForSide,
+ BCPixelSize aPixelValue)
+{
+ switch (aForSide) {
+ case eLogicalSideIEnd:
+ mIEndContBorderWidth = aPixelValue;
+ return;
+ case eLogicalSideBEnd:
+ mBEndContBorderWidth = aPixelValue;
+ return;
+ case eLogicalSideIStart:
+ mIStartContBorderWidth = aPixelValue;
+ return;
+ default:
+ NS_ERROR("invalid LogicalSide argument");
+ }
+}
+
+//nsILineIterator methods
+int32_t
+nsTableRowGroupFrame::GetNumLines()
+{
+ return GetRowCount();
+}
+
+bool
+nsTableRowGroupFrame::GetDirection()
+{
+ return (NS_STYLE_DIRECTION_RTL ==
+ GetTableFrame()->StyleVisibility()->mDirection);
+}
+
+NS_IMETHODIMP
+nsTableRowGroupFrame::GetLine(int32_t aLineNumber,
+ nsIFrame** aFirstFrameOnLine,
+ int32_t* aNumFramesOnLine,
+ nsRect& aLineBounds)
+{
+ NS_ENSURE_ARG_POINTER(aFirstFrameOnLine);
+ NS_ENSURE_ARG_POINTER(aNumFramesOnLine);
+
+ nsTableFrame* table = GetTableFrame();
+ nsTableCellMap* cellMap = table->GetCellMap();
+
+ *aFirstFrameOnLine = nullptr;
+ *aNumFramesOnLine = 0;
+ aLineBounds.SetRect(0, 0, 0, 0);
+
+ if ((aLineNumber < 0) || (aLineNumber >= GetRowCount())) {
+ return NS_OK;
+ }
+ aLineNumber += GetStartRowIndex();
+
+ *aNumFramesOnLine = cellMap->GetNumCellsOriginatingInRow(aLineNumber);
+ if (*aNumFramesOnLine == 0) {
+ return NS_OK;
+ }
+ int32_t colCount = table->GetColCount();
+ for (int32_t i = 0; i < colCount; i++) {
+ CellData* data = cellMap->GetDataAt(aLineNumber, i);
+ if (data && data->IsOrig()) {
+ *aFirstFrameOnLine = (nsIFrame*)data->GetCellFrame();
+ nsIFrame* parent = (*aFirstFrameOnLine)->GetParent();
+ aLineBounds = parent->GetRect();
+ return NS_OK;
+ }
+ }
+ NS_ERROR("cellmap is lying");
+ return NS_ERROR_FAILURE;
+}
+
+int32_t
+nsTableRowGroupFrame::FindLineContaining(nsIFrame* aFrame, int32_t aStartLine)
+{
+ NS_ENSURE_TRUE(aFrame, -1);
+
+ nsTableRowFrame *rowFrame = do_QueryFrame(aFrame);
+ NS_ASSERTION(rowFrame, "RowGroup contains a frame that is not a row");
+
+ int32_t rowIndexInGroup = rowFrame->GetRowIndex() - GetStartRowIndex();
+
+ return rowIndexInGroup >= aStartLine ? rowIndexInGroup : -1;
+}
+
+NS_IMETHODIMP
+nsTableRowGroupFrame::CheckLineOrder(int32_t aLine,
+ bool *aIsReordered,
+ nsIFrame **aFirstVisual,
+ nsIFrame **aLastVisual)
+{
+ *aIsReordered = false;
+ *aFirstVisual = nullptr;
+ *aLastVisual = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTableRowGroupFrame::FindFrameAt(int32_t aLineNumber,
+ nsPoint aPos,
+ nsIFrame** aFrameFound,
+ bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame)
+{
+ nsTableFrame* table = GetTableFrame();
+ nsTableCellMap* cellMap = table->GetCellMap();
+
+ WritingMode wm = table->GetWritingMode();
+ nsSize containerSize = table->GetSize();
+ LogicalPoint pos(wm, aPos, containerSize);
+
+ *aFrameFound = nullptr;
+ *aPosIsBeforeFirstFrame = true;
+ *aPosIsAfterLastFrame = false;
+
+ aLineNumber += GetStartRowIndex();
+ int32_t numCells = cellMap->GetNumCellsOriginatingInRow(aLineNumber);
+ if (numCells == 0) {
+ return NS_OK;
+ }
+
+ nsIFrame* frame = nullptr;
+ int32_t colCount = table->GetColCount();
+ for (int32_t i = 0; i < colCount; i++) {
+ CellData* data = cellMap->GetDataAt(aLineNumber, i);
+ if (data && data->IsOrig()) {
+ frame = (nsIFrame*)data->GetCellFrame();
+ break;
+ }
+ }
+ NS_ASSERTION(frame, "cellmap is lying");
+ bool isRTL = (NS_STYLE_DIRECTION_RTL ==
+ table->StyleVisibility()->mDirection);
+
+ nsIFrame* closestFromStart = nullptr;
+ nsIFrame* closestFromEnd = nullptr;
+ int32_t n = numCells;
+ nsIFrame* firstFrame = frame;
+ while (n--) {
+ LogicalRect rect = frame->GetLogicalRect(wm, containerSize);
+ if (rect.ISize(wm) > 0) {
+ // If pos.I() is inside this frame - this is it
+ if (rect.IStart(wm) <= pos.I(wm) && rect.IEnd(wm) > pos.I(wm)) {
+ closestFromStart = closestFromEnd = frame;
+ break;
+ }
+ if (rect.IStart(wm) < pos.I(wm)) {
+ if (!closestFromStart ||
+ rect.IEnd(wm) > closestFromStart->
+ GetLogicalRect(wm, containerSize).IEnd(wm))
+ closestFromStart = frame;
+ }
+ else {
+ if (!closestFromEnd ||
+ rect.IStart(wm) < closestFromEnd->
+ GetLogicalRect(wm, containerSize).IStart(wm))
+ closestFromEnd = frame;
+ }
+ }
+ frame = frame->GetNextSibling();
+ }
+ if (!closestFromStart && !closestFromEnd) {
+ // All frames were zero-width. Just take the first one.
+ closestFromStart = closestFromEnd = firstFrame;
+ }
+ *aPosIsBeforeFirstFrame = isRTL ? !closestFromEnd : !closestFromStart;
+ *aPosIsAfterLastFrame = isRTL ? !closestFromStart : !closestFromEnd;
+ if (closestFromStart == closestFromEnd) {
+ *aFrameFound = closestFromStart;
+ }
+ else if (!closestFromStart) {
+ *aFrameFound = closestFromEnd;
+ }
+ else if (!closestFromEnd) {
+ *aFrameFound = closestFromStart;
+ }
+ else { // we're between two frames
+ nscoord delta =
+ closestFromEnd->GetLogicalRect(wm, containerSize).IStart(wm) -
+ closestFromStart->GetLogicalRect(wm, containerSize).IEnd(wm);
+ if (pos.I(wm) < closestFromStart->
+ GetLogicalRect(wm, containerSize).IEnd(wm) + delta/2) {
+ *aFrameFound = closestFromStart;
+ } else {
+ *aFrameFound = closestFromEnd;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTableRowGroupFrame::GetNextSiblingOnLine(nsIFrame*& aFrame,
+ int32_t aLineNumber)
+{
+ NS_ENSURE_ARG_POINTER(aFrame);
+ aFrame = aFrame->GetNextSibling();
+ return NS_OK;
+}
+
+//end nsLineIterator methods
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowCursorProperty,
+ nsTableRowGroupFrame::FrameCursorData)
+
+void
+nsTableRowGroupFrame::ClearRowCursor()
+{
+ if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
+ return;
+ }
+
+ RemoveStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
+ Properties().Delete(RowCursorProperty());
+}
+
+nsTableRowGroupFrame::FrameCursorData*
+nsTableRowGroupFrame::SetupRowCursor()
+{
+ if (HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
+ // We already have a valid row cursor. Don't waste time rebuilding it.
+ return nullptr;
+ }
+
+ nsIFrame* f = mFrames.FirstChild();
+ int32_t count;
+ for (count = 0; f && count < MIN_ROWS_NEEDING_CURSOR; ++count) {
+ f = f->GetNextSibling();
+ }
+ if (!f) {
+ // Less than MIN_ROWS_NEEDING_CURSOR rows, so just don't bother
+ return nullptr;
+ }
+
+ FrameCursorData* data = new FrameCursorData();
+ if (!data)
+ return nullptr;
+ Properties().Set(RowCursorProperty(), data);
+ AddStateBits(NS_ROWGROUP_HAS_ROW_CURSOR);
+ return data;
+}
+
+nsIFrame*
+nsTableRowGroupFrame::GetFirstRowContaining(nscoord aY, nscoord* aOverflowAbove)
+{
+ if (!HasAnyStateBits(NS_ROWGROUP_HAS_ROW_CURSOR)) {
+ return nullptr;
+ }
+
+ FrameCursorData* property = Properties().Get(RowCursorProperty());
+ uint32_t cursorIndex = property->mCursorIndex;
+ uint32_t frameCount = property->mFrames.Length();
+ if (cursorIndex >= frameCount)
+ return nullptr;
+ nsIFrame* cursorFrame = property->mFrames[cursorIndex];
+
+ // The cursor's frame list excludes frames with empty overflow-area, so
+ // we don't need to check that here.
+
+ // We use property->mOverflowBelow here instead of computing the frame's
+ // true overflowArea.YMost(), because it is essential for the thresholds
+ // to form a monotonically increasing sequence. Otherwise we would break
+ // encountering a row whose overflowArea.YMost() is <= aY but which has
+ // a row above it containing cell(s) that span to include aY.
+ while (cursorIndex > 0 &&
+ cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow > aY) {
+ --cursorIndex;
+ cursorFrame = property->mFrames[cursorIndex];
+ }
+ while (cursorIndex + 1 < frameCount &&
+ cursorFrame->GetNormalRect().YMost() + property->mOverflowBelow <= aY) {
+ ++cursorIndex;
+ cursorFrame = property->mFrames[cursorIndex];
+ }
+
+ property->mCursorIndex = cursorIndex;
+ *aOverflowAbove = property->mOverflowAbove;
+ return cursorFrame;
+}
+
+bool
+nsTableRowGroupFrame::FrameCursorData::AppendFrame(nsIFrame* aFrame)
+{
+ // Relative positioning can cause table parts to move, but we will still paint
+ // the backgrounds for the parts under them at their 'normal' position. That
+ // means that we must consider the overflow rects at both positions. For
+ // example, if we use relative positioning to move a row-spanning cell, we
+ // will still paint the row background for that cell at its normal position,
+ // which will overflow the row.
+ // XXX(seth): This probably isn't correct in the presence of transforms.
+ nsRect positionedOverflowRect = aFrame->GetVisualOverflowRect();
+ nsPoint positionedToNormal = aFrame->GetNormalPosition() - aFrame->GetPosition();
+ nsRect normalOverflowRect = positionedOverflowRect + positionedToNormal;
+
+ nsRect overflowRect = positionedOverflowRect.Union(normalOverflowRect);
+ if (overflowRect.IsEmpty())
+ return true;
+ nscoord overflowAbove = -overflowRect.y;
+ nscoord overflowBelow = overflowRect.YMost() - aFrame->GetSize().height;
+ mOverflowAbove = std::max(mOverflowAbove, overflowAbove);
+ mOverflowBelow = std::max(mOverflowBelow, overflowBelow);
+ return mFrames.AppendElement(aFrame) != nullptr;
+}
+
+void
+nsTableRowGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrame(aDisplayItemKey);
+ GetParent()->InvalidateFrameWithRect(GetVisualOverflowRect() + GetPosition(), aDisplayItemKey);
+}
+
+void
+nsTableRowGroupFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
+{
+ nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey);
+ // If we have filters applied that would affects our bounds, then
+ // we get an inactive layer created and this is computed
+ // within FrameLayerBuilder
+ GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey);
+}
diff --git a/layout/tables/nsTableRowGroupFrame.h b/layout/tables/nsTableRowGroupFrame.h
new file mode 100644
index 0000000000..7abdd4b74b
--- /dev/null
+++ b/layout/tables/nsTableRowGroupFrame.h
@@ -0,0 +1,450 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTableRowGroupFrame_h__
+#define nsTableRowGroupFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsContainerFrame.h"
+#include "nsIAtom.h"
+#include "nsILineIterator.h"
+#include "nsTablePainter.h"
+#include "nsTArray.h"
+#include "nsTableFrame.h"
+#include "mozilla/WritingModes.h"
+
+class nsTableRowFrame;
+namespace mozilla {
+struct TableRowGroupReflowInput;
+} // namespace mozilla
+
+#define MIN_ROWS_NEEDING_CURSOR 20
+
+/**
+ * nsTableRowGroupFrame is the frame that maps row groups
+ * (HTML tags THEAD, TFOOT, and TBODY). This class cannot be reused
+ * outside of an nsTableFrame. It assumes that its parent is an nsTableFrame, and
+ * its children are nsTableRowFrames.
+ *
+ * @see nsTableFrame
+ * @see nsTableRowFrame
+ */
+class nsTableRowGroupFrame final
+ : public nsContainerFrame
+ , public nsILineIterator
+{
+ using TableRowGroupReflowInput = mozilla::TableRowGroupReflowInput;
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsTableRowGroupFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ /** instantiate a new instance of nsTableRowFrame.
+ * @param aPresShell the pres shell for this frame
+ *
+ * @return the frame that was created
+ */
+ friend nsTableRowGroupFrame* NS_NewTableRowGroupFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+ virtual ~nsTableRowGroupFrame();
+
+ nsTableFrame* GetTableFrame() const
+ {
+ nsIFrame* parent = GetParent();
+ MOZ_ASSERT(parent && parent->GetType() == nsGkAtoms::tableFrame);
+ return static_cast<nsTableFrame*>(parent);
+ }
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ /** @see nsIFrame::DidSetStyleContext */
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual nsMargin GetUsedMargin() const override;
+ virtual nsMargin GetUsedBorder() const override;
+ virtual nsMargin GetUsedPadding() const override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ /** calls Reflow for all of its child rows.
+ * Rows are all set to the same isize and stacked in the block direction.
+ * <P> rows are not split unless absolutely necessary.
+ *
+ * @param aDesiredSize isize set to isize of rows, bsize set to
+ * sum of bsize of rows that fit in AvailableBSize.
+ *
+ * @see nsIFrame::Reflow
+ */
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::tableRowGroupFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ nsTableRowFrame* GetFirstRow();
+ nsTableRowFrame* GetLastRow();
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual mozilla::WritingMode GetWritingMode() const override
+ { return GetTableFrame()->GetWritingMode(); }
+
+ /** return the number of child rows (not necessarily == number of child frames) */
+ int32_t GetRowCount();
+
+ /** return the table-relative row index of the first row in this rowgroup.
+ * if there are no rows, -1 is returned.
+ */
+ int32_t GetStartRowIndex();
+
+ /** Adjust the row indices of all rows whose index is >= aRowIndex.
+ * @param aRowIndex - start adjusting with this index
+ * @param aAdjustment - shift the row index by this amount
+ */
+ void AdjustRowIndices(int32_t aRowIndex,
+ int32_t anAdjustment);
+
+ /**
+ * Used for header and footer row group frames that are repeated when
+ * splitting a table frame.
+ *
+ * Performs any table specific initialization
+ *
+ * @param aHeaderFooterFrame the original header or footer row group frame
+ * that was repeated
+ */
+ nsresult InitRepeatedFrame(nsTableRowGroupFrame* aHeaderFooterFrame);
+
+
+ /**
+ * Get the total bsize of all the row rects
+ */
+ nscoord GetBSizeBasis(const ReflowInput& aReflowInput);
+
+ mozilla::LogicalMargin GetBCBorderWidth(mozilla::WritingMode aWM);
+
+ /**
+ * Gets inner border widths before collapsing with cell borders
+ * Caller must get bstart border from previous row group or from table
+ * GetContinuousBCBorderWidth will not overwrite aBorder.BStart()
+ * see nsTablePainter about continuous borders
+ */
+ void GetContinuousBCBorderWidth(mozilla::WritingMode aWM,
+ mozilla::LogicalMargin& aBorder);
+
+ /**
+ * Sets full border widths before collapsing with cell borders
+ * @param aForSide - side to set; only IEnd, IStart, BEnd are valid
+ */
+ void SetContinuousBCBorderWidth(mozilla::LogicalSide aForSide,
+ BCPixelSize aPixelValue);
+ /**
+ * Adjust to the effect of visibility:collapse on the row group and
+ * its children
+ * @return additional shift bstart-wards that should be applied
+ * to subsequent rowgroups due to rows and this
+ * rowgroup being collapsed
+ * @param aBTotalOffset the total amount that the rowgroup is shifted
+ * @param aISize new isize of the rowgroup
+ * @param aWM the table's writing mode
+ */
+ nscoord CollapseRowGroupIfNecessary(nscoord aBTotalOffset,
+ nscoord aISize,
+ mozilla::WritingMode aWM);
+
+// nsILineIterator methods
+public:
+ virtual void DisposeLineIterator() override { }
+
+ // The table row is the equivalent to a line in block layout.
+ // The nsILineIterator assumes that a line resides in a block, this role is
+ // fullfilled by the row group. Rows in table are counted relative to the
+ // table. The row index of row corresponds to the cellmap coordinates. The
+ // line index with respect to a row group can be computed by substracting the
+ // row index of the first row in the row group.
+
+ /** Get the number of rows in a row group
+ * @return the number of lines in a row group
+ */
+ virtual int32_t GetNumLines() override;
+
+ /** @see nsILineIterator.h GetDirection
+ * @return true if the table is rtl
+ */
+ virtual bool GetDirection() override;
+
+ /** Return structural information about a line.
+ * @param aLineNumber - the index of the row relative to the row group
+ * If the line-number is invalid then
+ * aFirstFrameOnLine will be nullptr and
+ * aNumFramesOnLine will be zero.
+ * @param aFirstFrameOnLine - the first cell frame that originates in row
+ * with a rowindex that matches a line number
+ * @param aNumFramesOnLine - return the numbers of cells originating in
+ * this row
+ * @param aLineBounds - rect of the row
+ */
+ NS_IMETHOD GetLine(int32_t aLineNumber,
+ nsIFrame** aFirstFrameOnLine,
+ int32_t* aNumFramesOnLine,
+ nsRect& aLineBounds) override;
+
+ /** Given a frame that's a child of the rowgroup, find which line its on.
+ * @param aFrame - frame, should be a row
+ * @param aStartLine - minimal index to return
+ * @return row index relative to the row group if this a row
+ * frame and the index is at least aStartLine.
+ * -1 if the frame cannot be found.
+ */
+ virtual int32_t FindLineContaining(nsIFrame* aFrame, int32_t aStartLine = 0) override;
+
+ /** Find the orginating cell frame on a row that is the nearest to the
+ * inline-dir coordinate of aPos.
+ * @param aLineNumber - the index of the row relative to the row group
+ * @param aPos - coordinate in twips relative to the
+ * origin of the row group
+ * @param aFrameFound - pointer to the cellframe
+ * @param aPosIsBeforeFirstFrame - the point is before the first originating
+ * cellframe
+ * @param aPosIsAfterLastFrame - the point is after the last originating
+ * cellframe
+ */
+ NS_IMETHOD FindFrameAt(int32_t aLineNumber,
+ nsPoint aPos,
+ nsIFrame** aFrameFound,
+ bool* aPosIsBeforeFirstFrame,
+ bool* aPosIsAfterLastFrame) override;
+
+ /** Check whether visual and logical order of cell frames within a line are
+ * identical. As the layout will reorder them this is always the case
+ * @param aLine - the index of the row relative to the table
+ * @param aIsReordered - returns false
+ * @param aFirstVisual - if the table is rtl first originating cell frame
+ * @param aLastVisual - if the table is rtl last originating cell frame
+ */
+
+ NS_IMETHOD CheckLineOrder(int32_t aLine,
+ bool *aIsReordered,
+ nsIFrame **aFirstVisual,
+ nsIFrame **aLastVisual) override;
+
+ /** Find the next originating cell frame that originates in the row.
+ * @param aFrame - cell frame to start with, will return the next cell
+ * originating in a row
+ * @param aLineNumber - the index of the row relative to the table
+ */
+ NS_IMETHOD GetNextSiblingOnLine(nsIFrame*& aFrame, int32_t aLineNumber) override;
+
+ // row cursor methods to speed up searching for the row(s)
+ // containing a point. The basic idea is that we set the cursor
+ // property if the rows' y and yMosts are non-decreasing (considering only
+ // rows with nonempty overflowAreas --- empty overflowAreas never participate
+ // in event handling or painting), and the rowgroup has sufficient number of
+ // rows. The cursor property points to a "recently used" row. If we get a
+ // series of requests that work on rows "near" the cursor, then we can find
+ // those nearby rows quickly by starting our search at the cursor.
+ // This code is based on the line cursor code in nsBlockFrame. It's more general
+ // though, and could be extracted and used elsewhere.
+ struct FrameCursorData {
+ nsTArray<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();
+
+ virtual nsILineIterator* GetLineIterator() override { return this; }
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags & ~(nsIFrame::eTablePart));
+ }
+
+ virtual void InvalidateFrame(uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0) override;
+ virtual void InvalidateFrameForRemoval() override { InvalidateFrameSubtree(); }
+
+protected:
+ explicit nsTableRowGroupFrame(nsStyleContext* aContext);
+
+ void InitChildReflowInput(nsPresContext& aPresContext,
+ bool aBorderCollapse,
+ ReflowInput& aReflowInput);
+
+ virtual LogicalSides GetLogicalSkipSides(const ReflowInput* aReflowInput = nullptr) const override;
+
+ void PlaceChild(nsPresContext* aPresContext,
+ TableRowGroupReflowInput& aReflowInput,
+ nsIFrame* aKidFrame,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalPoint& aKidPosition,
+ const nsSize& aContainerSize,
+ ReflowOutput& aDesiredSize,
+ const nsRect& aOriginalKidRect,
+ const nsRect& aOriginalKidVisualOverflow);
+
+ void CalculateRowBSizes(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput);
+
+ void DidResizeRows(ReflowOutput& aDesiredSize);
+
+ void SlideChild(TableRowGroupReflowInput& aReflowInput,
+ nsIFrame* aKidFrame);
+
+ /**
+ * Reflow the frames we've already created
+ *
+ * @param aPresContext presentation context to use
+ * @param aReflowInput current inline state
+ */
+ void ReflowChildren(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ TableRowGroupReflowInput& aReflowInput,
+ nsReflowStatus& aStatus,
+ bool* aPageBreakBeforeEnd = nullptr);
+
+ nsresult SplitRowGroup(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsTableFrame* aTableFrame,
+ nsReflowStatus& aStatus,
+ bool aRowForcedPageBreak);
+
+ void SplitSpanningCells(nsPresContext& aPresContext,
+ const ReflowInput& aReflowInput,
+ nsTableFrame& aTableFrame,
+ nsTableRowFrame& aFirstRow,
+ nsTableRowFrame& aLastRow,
+ bool aFirstRowIsTopOfPage,
+ nscoord aSpanningRowBottom,
+ nsTableRowFrame*& aContRowFrame,
+ nsTableRowFrame*& aFirstTruncatedRow,
+ nscoord& aDesiredHeight);
+
+ void CreateContinuingRowFrame(nsPresContext& aPresContext,
+ nsIFrame& aRowFrame,
+ nsIFrame** aContRowFrame);
+
+ bool IsSimpleRowFrame(nsTableFrame* aTableFrame,
+ nsTableRowFrame* aRowFrame);
+
+ void GetNextRowSibling(nsIFrame** aRowFrame);
+
+ void UndoContinuedRow(nsPresContext* aPresContext,
+ nsTableRowFrame* aRow);
+
+private:
+ // border widths in pixels in the collapsing border model
+ BCPixelSize mIEndContBorderWidth;
+ BCPixelSize mBEndContBorderWidth;
+ BCPixelSize mIStartContBorderWidth;
+
+public:
+ bool IsRepeatable() const;
+ void SetRepeatable(bool aRepeatable);
+ bool HasStyleBSize() const;
+ void SetHasStyleBSize(bool aValue);
+ bool HasInternalBreakBefore() const;
+ bool HasInternalBreakAfter() const;
+};
+
+
+inline bool nsTableRowGroupFrame::IsRepeatable() const
+{
+ return HasAnyStateBits(NS_ROWGROUP_REPEATABLE);
+}
+
+inline void nsTableRowGroupFrame::SetRepeatable(bool aRepeatable)
+{
+ if (aRepeatable) {
+ AddStateBits(NS_ROWGROUP_REPEATABLE);
+ } else {
+ RemoveStateBits(NS_ROWGROUP_REPEATABLE);
+ }
+}
+
+inline bool nsTableRowGroupFrame::HasStyleBSize() const
+{
+ return HasAnyStateBits(NS_ROWGROUP_HAS_STYLE_BSIZE);
+}
+
+inline void nsTableRowGroupFrame::SetHasStyleBSize(bool aValue)
+{
+ if (aValue) {
+ AddStateBits(NS_ROWGROUP_HAS_STYLE_BSIZE);
+ } else {
+ RemoveStateBits(NS_ROWGROUP_HAS_STYLE_BSIZE);
+ }
+}
+
+inline void
+nsTableRowGroupFrame::GetContinuousBCBorderWidth(mozilla::WritingMode aWM,
+ mozilla::LogicalMargin& aBorder)
+{
+ int32_t aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
+ aBorder.IEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips,
+ mIEndContBorderWidth);
+ aBorder.BEnd(aWM) = BC_BORDER_START_HALF_COORD(aPixelsToTwips,
+ mBEndContBorderWidth);
+ aBorder.IStart(aWM) = BC_BORDER_END_HALF_COORD(aPixelsToTwips,
+ mIStartContBorderWidth);
+}
+#endif
diff --git a/layout/tables/nsTableWrapperFrame.cpp b/layout/tables/nsTableWrapperFrame.cpp
new file mode 100644
index 0000000000..e44652a733
--- /dev/null
+++ b/layout/tables/nsTableWrapperFrame.cpp
@@ -0,0 +1,1101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTableWrapperFrame.h"
+
+#include "nsFrameManager.h"
+#include "nsTableFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsStyleContext.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsCSSRendering.h"
+#include "nsIContent.h"
+#include "prinrval.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLParts.h"
+#include "nsIPresShell.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMNode.h"
+#include "nsDisplayList.h"
+#include "nsLayoutUtils.h"
+#include "nsIFrameInlines.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::layout;
+
+#define NO_SIDE 100
+
+/* virtual */ nscoord
+nsTableWrapperFrame::GetLogicalBaseline(WritingMode aWritingMode) const
+{
+ nsIFrame* kid = mFrames.FirstChild();
+ if (!kid) {
+ NS_NOTREACHED("no inner table");
+ return nsContainerFrame::GetLogicalBaseline(aWritingMode);
+ }
+
+ return kid->GetLogicalBaseline(aWritingMode) +
+ kid->BStart(aWritingMode, mRect.Size());
+}
+
+nsTableWrapperFrame::nsTableWrapperFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+{
+}
+
+nsTableWrapperFrame::~nsTableWrapperFrame()
+{
+}
+
+NS_QUERYFRAME_HEAD(nsTableWrapperFrame)
+ NS_QUERYFRAME_ENTRY(nsTableWrapperFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsTableWrapperFrame::AccessibleType()
+{
+ return a11y::eHTMLTableType;
+}
+#endif
+
+void
+nsTableWrapperFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ DestroyAbsoluteFrames(aDestructRoot);
+ mCaptionFrames.DestroyFramesFrom(aDestructRoot);
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+const nsFrameList&
+nsTableWrapperFrame::GetChildList(ChildListID aListID) const
+{
+ if (aListID == kCaptionList) {
+ return mCaptionFrames;
+ }
+
+ return nsContainerFrame::GetChildList(aListID);
+}
+
+void
+nsTableWrapperFrame::GetChildLists(nsTArray<ChildList>* aLists) const
+{
+ nsContainerFrame::GetChildLists(aLists);
+ mCaptionFrames.AppendIfNonempty(aLists, kCaptionList);
+}
+
+void
+nsTableWrapperFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList)
+{
+ if (kCaptionList == aListID) {
+ // the frame constructor already checked for table-caption display type
+ MOZ_ASSERT(mCaptionFrames.IsEmpty(),
+ "already have child frames in CaptionList");
+ mCaptionFrames.SetFrames(aChildList);
+ } else {
+ MOZ_ASSERT(kPrincipalList != aListID ||
+ (aChildList.FirstChild() &&
+ aChildList.FirstChild() == aChildList.LastChild() &&
+ nsGkAtoms::tableFrame == aChildList.FirstChild()->GetType()),
+ "expected a single table frame in principal child list");
+ nsContainerFrame::SetInitialChildList(aListID, aChildList);
+ }
+}
+
+void
+nsTableWrapperFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ // We only have two child frames: the inner table and a caption frame.
+ // The inner frame is provided when we're initialized, and it cannot change
+ MOZ_ASSERT(kCaptionList == aListID, "unexpected child list");
+ MOZ_ASSERT(aFrameList.IsEmpty() ||
+ aFrameList.FirstChild()->IsTableCaption(),
+ "appending non-caption frame to captionList");
+ mCaptionFrames.AppendFrames(this, aFrameList);
+
+ // Reflow the new caption frame. It's already marked dirty, so
+ // just tell the pres shell.
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsTableWrapperFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ MOZ_ASSERT(kCaptionList == aListID, "unexpected child list");
+ MOZ_ASSERT(aFrameList.IsEmpty() ||
+ aFrameList.FirstChild()->IsTableCaption(),
+ "inserting non-caption frame into captionList");
+ MOZ_ASSERT(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+ mCaptionFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
+
+ // Reflow the new caption frame. It's already marked dirty, so
+ // just tell the pres shell.
+ PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsTableWrapperFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ // We only have two child frames: the inner table and one caption frame.
+ // The inner frame can't be removed so this should be the caption
+ NS_PRECONDITION(kCaptionList == aListID, "can't remove inner frame");
+
+ if (HasSideCaption()) {
+ // The old caption isize had an effect on the inner table isize, so
+ // we're going to need to reflow it. Mark it dirty
+ InnerTableFrame()->AddStateBits(NS_FRAME_IS_DIRTY);
+ }
+
+ // Remove the frame and destroy it
+ mCaptionFrames.DestroyFrame(aOldFrame);
+
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eTreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN); // also means child removed
+}
+
+void
+nsTableWrapperFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // No border, background or outline are painted because they all belong
+ // to the inner table.
+
+ // If there's no caption, take a short cut to avoid having to create
+ // the special display list set and then sort it.
+ if (mCaptionFrames.IsEmpty()) {
+ BuildDisplayListForInnerTable(aBuilder, aDirtyRect, aLists);
+ return;
+ }
+
+ nsDisplayListCollection set;
+ BuildDisplayListForInnerTable(aBuilder, aDirtyRect, set);
+
+ nsDisplayListSet captionSet(set, set.BlockBorderBackgrounds());
+ BuildDisplayListForChild(aBuilder, mCaptionFrames.FirstChild(),
+ aDirtyRect, captionSet);
+
+ // Now we have to sort everything by content order, since the caption
+ // may be somewhere inside the table
+ set.SortAllByContentOrder(GetContent());
+ set.MoveTo(aLists);
+}
+
+void
+nsTableWrapperFrame::BuildDisplayListForInnerTable(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // Just paint the regular children, but the children's background is our
+ // true background (there should only be one, the real table)
+ nsIFrame* kid = mFrames.FirstChild();
+ // The children should be in content order
+ while (kid) {
+ BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
+ kid = kid->GetNextSibling();
+ }
+}
+
+nsStyleContext*
+nsTableWrapperFrame::GetParentStyleContext(nsIFrame** aProviderFrame) const
+{
+ // The table wrapper frame and the (inner) table frame split the style
+ // data by giving the table frame the style context associated with
+ // the table content node and creating a style context for the wrapper
+ // frame that is a *child* of the table frame's style context,
+ // matching the ::-moz-table-wrapper pseudo-element. html.css has a
+ // rule that causes that pseudo-element (and thus the wrapper table)
+ // to inherit *some* style properties from the table frame. The
+ // children of the table inherit directly from the inner table, and
+ // the table wrapper's style context is a leaf.
+
+ return (*aProviderFrame = InnerTableFrame())->StyleContext();
+}
+
+// INCREMENTAL REFLOW HELPER FUNCTIONS
+
+void
+nsTableWrapperFrame::InitChildReflowInput(nsPresContext& aPresContext,
+ ReflowInput& aReflowInput)
+{
+ nsMargin collapseBorder;
+ nsMargin collapsePadding(0,0,0,0);
+ nsMargin* pCollapseBorder = nullptr;
+ nsMargin* pCollapsePadding = nullptr;
+ Maybe<LogicalSize> cbSize;
+ if (aReflowInput.mFrame == InnerTableFrame()) {
+ WritingMode wm = aReflowInput.GetWritingMode();
+ if (InnerTableFrame()->IsBorderCollapse()) {
+ LogicalMargin border = InnerTableFrame()->GetIncludedOuterBCBorder(wm);
+ collapseBorder = border.GetPhysicalMargin(wm);
+ pCollapseBorder = &collapseBorder;
+ pCollapsePadding = &collapsePadding;
+ }
+ // Propagate our stored CB size if present, minus any margins.
+ if (!HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ LogicalSize* cb = Properties().Get(GridItemCBSizeProperty());
+ if (cb) {
+ cbSize.emplace(*cb);
+ *cbSize -= aReflowInput.ComputedLogicalMargin().Size(wm);
+ }
+ }
+ }
+ aReflowInput.Init(&aPresContext, cbSize.ptrOr(nullptr), pCollapseBorder,
+ pCollapsePadding);
+}
+
+// get the margin and padding data. ReflowInput doesn't handle the
+// case of auto margins
+void
+nsTableWrapperFrame::GetChildMargin(nsPresContext* aPresContext,
+ const ReflowInput& aOuterRI,
+ nsIFrame* aChildFrame,
+ nscoord aAvailISize,
+ LogicalMargin& aMargin)
+{
+ NS_ASSERTION(!aChildFrame->IsTableCaption(),
+ "didn't expect caption frame; writing-mode may be wrong!");
+
+ // construct a reflow state to compute margin and padding. Auto margins
+ // will not be computed at this time.
+
+ // create and init the child reflow state
+ // XXX We really shouldn't construct a reflow state to do this.
+ WritingMode wm = aOuterRI.GetWritingMode();
+ LogicalSize availSize(wm, aAvailISize, aOuterRI.AvailableSize(wm).BSize(wm));
+ ReflowInput childRI(aPresContext, aOuterRI, aChildFrame, availSize,
+ nullptr, ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(*aPresContext, childRI);
+
+ aMargin = childRI.ComputedLogicalMargin();
+}
+
+static nsSize
+GetContainingBlockSize(const ReflowInput& aOuterRI)
+{
+ nsSize size(0,0);
+ const ReflowInput* containRS = aOuterRI.mCBReflowInput;
+
+ if (containRS) {
+ size.width = containRS->ComputedWidth();
+ if (NS_UNCONSTRAINEDSIZE == size.width) {
+ size.width = 0;
+ }
+ size.height = containRS->ComputedHeight();
+ if (NS_UNCONSTRAINEDSIZE == size.height) {
+ size.height = 0;
+ }
+ }
+ return size;
+}
+
+/* virtual */ nscoord
+nsTableWrapperFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord iSize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ InnerTableFrame(), nsLayoutUtils::MIN_ISIZE);
+ DISPLAY_MIN_WIDTH(this, iSize);
+ if (mCaptionFrames.NotEmpty()) {
+ nscoord capISize =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ mCaptionFrames.FirstChild(),
+ nsLayoutUtils::MIN_ISIZE);
+ if (HasSideCaption()) {
+ iSize += capISize;
+ } else {
+ if (capISize > iSize) {
+ iSize = capISize;
+ }
+ }
+ }
+ return iSize;
+}
+
+/* virtual */ nscoord
+nsTableWrapperFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord maxISize;
+ DISPLAY_PREF_WIDTH(this, maxISize);
+
+ maxISize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ InnerTableFrame(), nsLayoutUtils::PREF_ISIZE);
+ if (mCaptionFrames.NotEmpty()) {
+ uint8_t captionSide = GetCaptionSide();
+ switch (captionSide) {
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ {
+ nscoord capMin =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ mCaptionFrames.FirstChild(),
+ nsLayoutUtils::MIN_ISIZE);
+ maxISize += capMin;
+ }
+ break;
+ default:
+ {
+ nsLayoutUtils::IntrinsicISizeType iwt;
+ if (captionSide == NS_STYLE_CAPTION_SIDE_TOP ||
+ captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) {
+ // Don't let the caption's pref isize expand the table's pref
+ // isize.
+ iwt = nsLayoutUtils::MIN_ISIZE;
+ } else {
+ NS_ASSERTION(captionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE ||
+ captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE,
+ "unexpected caption side");
+ iwt = nsLayoutUtils::PREF_ISIZE;
+ }
+ nscoord capPref =
+ nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+ mCaptionFrames.FirstChild(),
+ iwt);
+ maxISize = std::max(maxISize, capPref);
+ }
+ break;
+ }
+ }
+ return maxISize;
+}
+
+nscoord
+nsTableWrapperFrame::ChildShrinkWrapISize(nsRenderingContext* aRenderingContext,
+ nsIFrame* aChildFrame,
+ WritingMode aWM,
+ LogicalSize aCBSize,
+ nscoord aAvailableISize,
+ nscoord* aMarginResult) const
+{
+ AutoMaybeDisableFontInflation an(aChildFrame);
+
+ // For the caption frame, child's WM may differ from the table's main WM.
+ WritingMode childWM = aChildFrame->GetWritingMode();
+
+ SizeComputationInput offsets(aChildFrame, aRenderingContext, aWM,
+ aCBSize.ISize(aWM));
+ LogicalSize marginSize =
+ offsets.ComputedLogicalMargin().Size(childWM).ConvertTo(aWM, childWM);
+ LogicalSize paddingSize =
+ offsets.ComputedLogicalPadding().Size(childWM).ConvertTo(aWM, childWM);
+ LogicalSize bpSize =
+ offsets.ComputedLogicalBorderPadding().Size(childWM).ConvertTo(aWM, childWM);
+
+ // Shrink-wrap aChildFrame by default, except if we're a stretched grid item.
+ auto flags = ComputeSizeFlags::eShrinkWrap;
+ auto parent = GetParent();
+ nsIAtom* parentFrameType = parent ? parent->GetType() : nullptr;
+ bool isGridItem = (parentFrameType == nsGkAtoms::gridContainerFrame &&
+ !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+ if (MOZ_UNLIKELY(isGridItem) &&
+ !StyleMargin()->HasInlineAxisAuto(aWM)) {
+ auto inlineAxisAlignment = aWM.IsOrthogonalTo(parent->GetWritingMode()) ?
+ StylePosition()->UsedAlignSelf(parent->StyleContext()) :
+ StylePosition()->UsedJustifySelf(parent->StyleContext());
+ if (inlineAxisAlignment == NS_STYLE_ALIGN_NORMAL ||
+ inlineAxisAlignment == NS_STYLE_ALIGN_STRETCH) {
+ flags = nsIFrame::ComputeSizeFlags::eDefault;
+ }
+ }
+
+ LogicalSize size =
+ aChildFrame->ComputeSize(aRenderingContext, aWM, aCBSize, aAvailableISize,
+ marginSize, bpSize - paddingSize, paddingSize,
+ flags);
+ if (aMarginResult) {
+ *aMarginResult = offsets.ComputedLogicalMargin().IStartEnd(aWM);
+ }
+ return size.ISize(aWM) + marginSize.ISize(aWM) + bpSize.ISize(aWM);
+}
+
+/* virtual */
+LogicalSize
+nsTableWrapperFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ nscoord kidAvailableISize = aAvailableISize - aMargin.ISize(aWM);
+ NS_ASSERTION(aBorder.IsAllZero() && aPadding.IsAllZero(),
+ "Table wrapper frames cannot have borders or paddings");
+
+ // When we're shrink-wrapping, our auto size needs to wrap around the
+ // actual size of the table, which (if it is specified as a percent)
+ // could be something that is not reflected in our GetMinISize and
+ // GetPrefISize. See bug 349457 for an example.
+
+ // Match the availableISize logic in Reflow.
+ uint8_t captionSide = GetCaptionSide();
+ nscoord inlineSize;
+ if (captionSide == NO_SIDE) {
+ inlineSize = ChildShrinkWrapISize(aRenderingContext, InnerTableFrame(), aWM,
+ aCBSize, kidAvailableISize);
+ } else if (captionSide == NS_STYLE_CAPTION_SIDE_LEFT ||
+ captionSide == NS_STYLE_CAPTION_SIDE_RIGHT) {
+ nscoord capISize = ChildShrinkWrapISize(aRenderingContext,
+ mCaptionFrames.FirstChild(), aWM,
+ aCBSize, kidAvailableISize);
+ inlineSize = capISize + ChildShrinkWrapISize(aRenderingContext,
+ InnerTableFrame(), aWM,
+ aCBSize,
+ kidAvailableISize - capISize);
+ } else if (captionSide == NS_STYLE_CAPTION_SIDE_TOP ||
+ captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) {
+ nscoord margin;
+ inlineSize = ChildShrinkWrapISize(aRenderingContext, InnerTableFrame(), aWM,
+ aCBSize, kidAvailableISize, &margin);
+ nscoord capISize = ChildShrinkWrapISize(aRenderingContext,
+ mCaptionFrames.FirstChild(), aWM,
+ aCBSize, inlineSize - margin);
+ if (capISize > inlineSize) {
+ inlineSize = capISize;
+ }
+ } else {
+ NS_ASSERTION(captionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE ||
+ captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE,
+ "unexpected caption-side");
+ inlineSize = ChildShrinkWrapISize(aRenderingContext, InnerTableFrame(), aWM,
+ aCBSize, kidAvailableISize);
+ nscoord capISize = ChildShrinkWrapISize(aRenderingContext,
+ mCaptionFrames.FirstChild(), aWM,
+ aCBSize, kidAvailableISize);
+ if (capISize > inlineSize) {
+ inlineSize = capISize;
+ }
+ }
+
+ return LogicalSize(aWM, inlineSize, NS_UNCONSTRAINEDSIZE);
+}
+
+uint8_t
+nsTableWrapperFrame::GetCaptionSide()
+{
+ if (mCaptionFrames.NotEmpty()) {
+ return mCaptionFrames.FirstChild()->StyleTableBorder()->mCaptionSide;
+ }
+ else {
+ return NO_SIDE; // no caption
+ }
+}
+
+uint8_t
+nsTableWrapperFrame::GetCaptionVerticalAlign()
+{
+ const nsStyleCoord& va =
+ mCaptionFrames.FirstChild()->StyleDisplay()->mVerticalAlign;
+
+ return (va.GetUnit() == eStyleUnit_Enumerated)
+ ? va.GetIntValue()
+ : NS_STYLE_VERTICAL_ALIGN_TOP;
+}
+
+void
+nsTableWrapperFrame::SetDesiredSize(uint8_t aCaptionSide,
+ const LogicalSize& aInnerSize,
+ const LogicalSize& aCaptionSize,
+ const LogicalMargin& aInnerMargin,
+ const LogicalMargin& aCaptionMargin,
+ nscoord& aISize,
+ nscoord& aBSize,
+ WritingMode aWM)
+{
+ aISize = aBSize = 0;
+
+ // compute the overall inline-size
+ switch (aCaptionSide) {
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ aISize =
+ std::max(aInnerMargin.LineLeft(aWM),
+ aCaptionMargin.IStartEnd(aWM) + aCaptionSize.ISize(aWM)) +
+ aInnerSize.ISize(aWM) + aInnerMargin.LineRight(aWM);
+ break;
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ aISize =
+ std::max(aInnerMargin.LineRight(aWM),
+ aCaptionMargin.IStartEnd(aWM) + aCaptionSize.ISize(aWM)) +
+ aInnerSize.ISize(aWM) + aInnerMargin.LineLeft(aWM);
+ break;
+ default:
+ aISize =
+ std::max(aInnerMargin.IStartEnd(aWM) + aInnerSize.ISize(aWM),
+ aCaptionMargin.IStartEnd(aWM) + aCaptionSize.ISize(aWM));
+ break;
+ }
+
+ // compute the overall block-size
+ switch (aCaptionSide) {
+ case NS_STYLE_CAPTION_SIDE_TOP:
+ case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE:
+ aBSize = aInnerSize.BSize(aWM) + aInnerMargin.BEnd(aWM);
+ aBSize +=
+ std::max(aInnerMargin.BStart(aWM),
+ aCaptionSize.BSize(aWM) + aCaptionMargin.BStartEnd(aWM));
+ break;
+ case NS_STYLE_CAPTION_SIDE_BOTTOM:
+ case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE:
+ aBSize = aInnerSize.BSize(aWM) + aInnerMargin.BStart(aWM);
+ aBSize +=
+ std::max(aInnerMargin.BEnd(aWM),
+ aCaptionSize.BSize(aWM) + aCaptionMargin.BStartEnd(aWM));
+ break;
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ aBSize = aInnerMargin.BStart(aWM);
+ aBSize +=
+ std::max(aInnerSize.BSize(aWM) + aInnerMargin.BEnd(aWM),
+ aCaptionSize.BSize(aWM) + aCaptionMargin.BEnd(aWM));
+ break;
+ default:
+ NS_ASSERTION(aCaptionSide == NO_SIDE, "unexpected caption side");
+ aBSize = aInnerSize.BSize(aWM) + aInnerMargin.BStartEnd(aWM);
+ break;
+ }
+
+ // negative sizes can upset overflow-area code
+ aISize = std::max(aISize, 0);
+ aBSize = std::max(aBSize, 0);
+}
+
+nsresult
+nsTableWrapperFrame::GetCaptionOrigin(uint32_t aCaptionSide,
+ const LogicalSize& aContainBlockSize,
+ const LogicalSize& aInnerSize,
+ const LogicalMargin& aInnerMargin,
+ const LogicalSize& aCaptionSize,
+ LogicalMargin& aCaptionMargin,
+ LogicalPoint& aOrigin,
+ WritingMode aWM)
+{
+ aOrigin.I(aWM) = aOrigin.B(aWM) = 0;
+ if ((NS_UNCONSTRAINEDSIZE == aInnerSize.ISize(aWM)) ||
+ (NS_UNCONSTRAINEDSIZE == aInnerSize.BSize(aWM)) ||
+ (NS_UNCONSTRAINEDSIZE == aCaptionSize.ISize(aWM)) ||
+ (NS_UNCONSTRAINEDSIZE == aCaptionSize.BSize(aWM))) {
+ return NS_OK;
+ }
+ if (mCaptionFrames.IsEmpty()) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) &&
+ NS_AUTOMARGIN != aCaptionMargin.BStart(aWM) &&
+ NS_AUTOMARGIN != aCaptionMargin.BEnd(aWM),
+ "The computed caption margin is auto?");
+
+ // inline-dir computation
+ switch (aCaptionSide) {
+ case NS_STYLE_CAPTION_SIDE_BOTTOM:
+ case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE:
+ aOrigin.I(aWM) = aCaptionMargin.IStart(aWM);
+ if (aCaptionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) {
+ // We placed the caption using only the table's isize as available
+ // isize, and we should position it this way as well.
+ aOrigin.I(aWM) += aInnerMargin.IStart(aWM);
+ }
+ break;
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ aOrigin.I(aWM) = aCaptionMargin.IStart(aWM);
+ if (aWM.IsBidiLTR() == (aCaptionSide == NS_STYLE_CAPTION_SIDE_RIGHT)) {
+ aOrigin.I(aWM) += aInnerMargin.IStart(aWM) + aInnerSize.ISize(aWM);
+ }
+ break;
+ default: // block-start
+ NS_ASSERTION(aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP ||
+ aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE,
+ "unexpected caption side");
+ aOrigin.I(aWM) = aCaptionMargin.IStart(aWM);
+ if (aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP) {
+ // We placed the caption using only the table's isize as available
+ // isize, and we should position it this way as well.
+ aOrigin.I(aWM) += aInnerMargin.IStart(aWM);
+ }
+ break;
+ }
+ // block-dir computation
+ switch (aCaptionSide) {
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ aOrigin.B(aWM) = aInnerMargin.BStart(aWM);
+ switch (GetCaptionVerticalAlign()) {
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
+ aOrigin.B(aWM) = std::max(0, aInnerMargin.BStart(aWM) +
+ ((aInnerSize.BSize(aWM) -
+ aCaptionSize.BSize(aWM)) / 2));
+ break;
+ case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
+ aOrigin.B(aWM) = std::max(0, aInnerMargin.BStart(aWM) +
+ aInnerSize.BSize(aWM) -
+ aCaptionSize.BSize(aWM));
+ break;
+ default:
+ break;
+ }
+ break;
+ case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE:
+ case NS_STYLE_CAPTION_SIDE_BOTTOM:
+ aOrigin.B(aWM) = aInnerMargin.BStart(aWM) + aInnerSize.BSize(aWM) +
+ aCaptionMargin.BStart(aWM);
+ break;
+ case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE:
+ case NS_STYLE_CAPTION_SIDE_TOP:
+ aOrigin.B(aWM) = aInnerMargin.BStart(aWM) + aCaptionMargin.BStart(aWM);
+ break;
+ default:
+ NS_NOTREACHED("Unknown caption alignment type");
+ break;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsTableWrapperFrame::GetInnerOrigin(uint32_t aCaptionSide,
+ const LogicalSize& aContainBlockSize,
+ const LogicalSize& aCaptionSize,
+ const LogicalMargin& aCaptionMargin,
+ const LogicalSize& aInnerSize,
+ LogicalMargin& aInnerMargin,
+ LogicalPoint& aOrigin,
+ WritingMode aWM)
+{
+ NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) &&
+ NS_AUTOMARGIN != aCaptionMargin.IEnd(aWM),
+ "The computed caption margin is auto?");
+ NS_ASSERTION(NS_AUTOMARGIN != aInnerMargin.IStart(aWM) &&
+ NS_AUTOMARGIN != aInnerMargin.IEnd(aWM) &&
+ NS_AUTOMARGIN != aInnerMargin.BStart(aWM) &&
+ NS_AUTOMARGIN != aInnerMargin.BEnd(aWM),
+ "The computed inner margin is auto?");
+
+ aOrigin.I(aWM) = aOrigin.B(aWM) = 0;
+ if ((NS_UNCONSTRAINEDSIZE == aInnerSize.ISize(aWM)) ||
+ (NS_UNCONSTRAINEDSIZE == aInnerSize.BSize(aWM)) ||
+ (NS_UNCONSTRAINEDSIZE == aCaptionSize.ISize(aWM)) ||
+ (NS_UNCONSTRAINEDSIZE == aCaptionSize.BSize(aWM))) {
+ return NS_OK;
+ }
+
+ nscoord minCapISize =
+ aCaptionSize.ISize(aWM) + aCaptionMargin.IStartEnd(aWM);
+
+ // inline-dir computation
+ switch (aCaptionSide) {
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ if (aWM.IsBidiLTR() == (aCaptionSide == NS_STYLE_CAPTION_SIDE_LEFT)) {
+ if (aInnerMargin.IStart(aWM) < minCapISize) {
+ // shift the inner table to get some place for the caption
+ aInnerMargin.IEnd(aWM) += aInnerMargin.IStart(aWM) - minCapISize;
+ aInnerMargin.IEnd(aWM) = std::max(0, aInnerMargin.IEnd(aWM));
+ aInnerMargin.IStart(aWM) = minCapISize;
+ }
+ }
+ aOrigin.I(aWM) = aInnerMargin.IStart(aWM);
+ break;
+ default:
+ NS_ASSERTION(aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP ||
+ aCaptionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE ||
+ aCaptionSide == NS_STYLE_CAPTION_SIDE_BOTTOM ||
+ aCaptionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE ||
+ aCaptionSide == NO_SIDE,
+ "unexpected caption side");
+ aOrigin.I(aWM) = aInnerMargin.IStart(aWM);
+ break;
+ }
+
+ // block-dir computation
+ switch (aCaptionSide) {
+ case NS_STYLE_CAPTION_SIDE_BOTTOM:
+ case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE:
+ aOrigin.B(aWM) = aInnerMargin.BStart(aWM);
+ break;
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ aOrigin.B(aWM) = aInnerMargin.BStart(aWM);
+ switch (GetCaptionVerticalAlign()) {
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
+ aOrigin.B(aWM) =
+ std::max(aInnerMargin.BStart(aWM),
+ (aCaptionSize.BSize(aWM) - aInnerSize.BSize(aWM)) / 2);
+ break;
+ case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
+ aOrigin.B(aWM) =
+ std::max(aInnerMargin.BStart(aWM),
+ aCaptionSize.BSize(aWM) - aInnerSize.BSize(aWM));
+ break;
+ default:
+ break;
+ }
+ break;
+ case NO_SIDE:
+ case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE:
+ case NS_STYLE_CAPTION_SIDE_TOP:
+ aOrigin.B(aWM) = aInnerMargin.BStart(aWM) + aCaptionSize.BSize(aWM) +
+ aCaptionMargin.BStartEnd(aWM);
+ break;
+ default:
+ NS_NOTREACHED("Unknown caption alignment type");
+ break;
+ }
+ return NS_OK;
+}
+
+void
+nsTableWrapperFrame::OuterBeginReflowChild(nsPresContext* aPresContext,
+ nsIFrame* aChildFrame,
+ const ReflowInput& aOuterRI,
+ Maybe<ReflowInput>& aChildRI,
+ nscoord aAvailISize)
+{
+ // work around pixel rounding errors, round down to ensure we don't exceed the avail height in
+ WritingMode wm = aChildFrame->GetWritingMode();
+ LogicalSize outerSize = aOuterRI.AvailableSize(wm);
+ nscoord availBSize = outerSize.BSize(wm);
+ if (NS_UNCONSTRAINEDSIZE != availBSize) {
+ if (mCaptionFrames.FirstChild() == aChildFrame) {
+ availBSize = NS_UNCONSTRAINEDSIZE;
+ } else {
+ LogicalMargin margin(wm);
+ GetChildMargin(aPresContext, aOuterRI, aChildFrame,
+ outerSize.ISize(wm), margin);
+
+ NS_ASSERTION(NS_UNCONSTRAINEDSIZE != margin.BStart(wm),
+ "No unconstrainedsize arithmetic, please");
+ availBSize -= margin.BStart(wm);
+
+ NS_ASSERTION(NS_UNCONSTRAINEDSIZE != margin.BEnd(wm),
+ "No unconstrainedsize arithmetic, please");
+ availBSize -= margin.BEnd(wm);
+ }
+ }
+ LogicalSize availSize(wm, aAvailISize, availBSize);
+ // create and init the child reflow state, using passed-in Maybe<>,
+ // so that caller can use it after we return.
+ aChildRI.emplace(aPresContext, aOuterRI, aChildFrame, availSize,
+ nullptr, ReflowInput::CALLER_WILL_INIT);
+ InitChildReflowInput(*aPresContext, *aChildRI);
+
+ // see if we need to reset top-of-page due to a caption
+ if (aChildRI->mFlags.mIsTopOfPage &&
+ mCaptionFrames.FirstChild() == aChildFrame) {
+ uint8_t captionSide = GetCaptionSide();
+ if (captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM ||
+ captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE) {
+ aChildRI->mFlags.mIsTopOfPage = false;
+ }
+ }
+}
+
+void
+nsTableWrapperFrame::OuterDoReflowChild(nsPresContext* aPresContext,
+ nsIFrame* aChildFrame,
+ const ReflowInput& aChildRI,
+ ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus)
+{
+ // Using zero as containerSize here because we want consistency between
+ // the GetLogicalPosition and ReflowChild calls, to avoid unnecessarily
+ // changing the frame's coordinates; but we don't yet know its final
+ // position anyway so the actual value is unimportant.
+ const nsSize zeroCSize;
+ WritingMode wm = aChildRI.GetWritingMode();
+
+ // Use the current position as a best guess for placement.
+ LogicalPoint childPt = aChildFrame->GetLogicalPosition(wm, zeroCSize);
+ uint32_t flags = NS_FRAME_NO_MOVE_FRAME;
+
+ // We don't want to delete our next-in-flow's child if it's an inner table
+ // frame, because table wrapper frames always assume that their inner table
+ // frames don't go away. If a table wrapper frame is removed because it is
+ // a next-in-flow of an already complete table wrapper frame, then it will
+ // take care of removing it's inner table frame.
+ if (aChildFrame == InnerTableFrame()) {
+ flags |= NS_FRAME_NO_DELETE_NEXT_IN_FLOW_CHILD;
+ }
+
+ ReflowChild(aChildFrame, aPresContext, aMetrics, aChildRI,
+ wm, childPt, zeroCSize, flags, aStatus);
+}
+
+void
+nsTableWrapperFrame::UpdateOverflowAreas(ReflowOutput& aMet)
+{
+ aMet.SetOverflowAreasToDesiredBounds();
+ ConsiderChildOverflow(aMet.mOverflowAreas, InnerTableFrame());
+ if (mCaptionFrames.NotEmpty()) {
+ ConsiderChildOverflow(aMet.mOverflowAreas, mCaptionFrames.FirstChild());
+ }
+}
+
+void
+nsTableWrapperFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aOuterRI,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableWrapperFrame");
+ DISPLAY_REFLOW(aPresContext, this, aOuterRI, aDesiredSize, aStatus);
+
+ // Initialize out parameters
+ aDesiredSize.ClearSize();
+ aStatus = NS_FRAME_COMPLETE;
+
+ if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // Set up our kids. They're already present, on an overflow list,
+ // or there are none so we'll create them now
+ MoveOverflowToChildList();
+ }
+
+ Maybe<ReflowInput> captionRI;
+ Maybe<ReflowInput> innerRI;
+
+ nsRect origInnerRect = InnerTableFrame()->GetRect();
+ nsRect origInnerVisualOverflow = InnerTableFrame()->GetVisualOverflowRect();
+ bool innerFirstReflow =
+ InnerTableFrame()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+ nsRect origCaptionRect;
+ nsRect origCaptionVisualOverflow;
+ bool captionFirstReflow = false;
+ if (mCaptionFrames.NotEmpty()) {
+ origCaptionRect = mCaptionFrames.FirstChild()->GetRect();
+ origCaptionVisualOverflow =
+ mCaptionFrames.FirstChild()->GetVisualOverflowRect();
+ captionFirstReflow =
+ mCaptionFrames.FirstChild()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+ }
+
+ // ComputeAutoSize has to match this logic.
+ WritingMode wm = aOuterRI.GetWritingMode();
+ uint8_t captionSide = GetCaptionSide();
+ WritingMode captionWM = wm; // will be changed below if necessary
+
+ if (captionSide == NO_SIDE) {
+ // We don't have a caption.
+ OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI,
+ innerRI, aOuterRI.ComputedSize(wm).ISize(wm));
+ } else if (captionSide == NS_STYLE_CAPTION_SIDE_LEFT ||
+ captionSide == NS_STYLE_CAPTION_SIDE_RIGHT) {
+ // ComputeAutoSize takes care of making side captions small. Compute
+ // the caption's size first, and tell the table to fit in what's left.
+ OuterBeginReflowChild(aPresContext, mCaptionFrames.FirstChild(), aOuterRI,
+ captionRI, aOuterRI.ComputedSize(wm).ISize(wm));
+ captionWM = captionRI->GetWritingMode();
+ nscoord innerAvailISize = aOuterRI.ComputedSize(wm).ISize(wm) -
+ captionRI->ComputedSizeWithMarginBorderPadding(wm).ISize(wm);
+ OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI,
+ innerRI, innerAvailISize);
+ } else if (captionSide == NS_STYLE_CAPTION_SIDE_TOP ||
+ captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM) {
+ // Compute the table's size first, and then prevent the caption from
+ // being larger in the inline dir unless it has to be.
+ //
+ // Note that CSS 2.1 (but not 2.0) says:
+ // The width of the anonymous box is the border-edge width of the
+ // table box inside it
+ // We don't actually make our anonymous box that isize (if we did,
+ // it would break 'auto' margins), but this effectively does that.
+ OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI,
+ innerRI, aOuterRI.ComputedSize(wm).ISize(wm));
+ // It's good that CSS 2.1 says not to include margins, since we
+ // can't, since they already been converted so they exactly
+ // fill the available isize (ignoring the margin on one side if
+ // neither are auto). (We take advantage of that later when we call
+ // GetCaptionOrigin, though.)
+ nscoord innerBorderISize =
+ innerRI->ComputedSizeWithBorderPadding(wm).ISize(wm);
+ OuterBeginReflowChild(aPresContext, mCaptionFrames.FirstChild(), aOuterRI,
+ captionRI, innerBorderISize);
+ captionWM = captionRI->GetWritingMode();
+ } else {
+ NS_ASSERTION(captionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE ||
+ captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE,
+ "unexpected caption-side");
+ // Size the table and the caption independently.
+ captionWM = mCaptionFrames.FirstChild()->GetWritingMode();
+ OuterBeginReflowChild(aPresContext, mCaptionFrames.FirstChild(),
+ aOuterRI, captionRI,
+ aOuterRI.ComputedSize(captionWM).ISize(captionWM));
+ OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI,
+ innerRI, aOuterRI.ComputedSize(wm).ISize(wm));
+ }
+
+ // First reflow the caption.
+ Maybe<ReflowOutput> captionMet;
+ LogicalSize captionSize(wm);
+ LogicalMargin captionMargin(wm);
+ if (mCaptionFrames.NotEmpty()) {
+ captionMet.emplace(wm);
+ nsReflowStatus capStatus; // don't let the caption cause incomplete
+ OuterDoReflowChild(aPresContext, mCaptionFrames.FirstChild(),
+ *captionRI, *captionMet, capStatus);
+ captionSize.ISize(wm) = captionMet->ISize(wm);
+ captionSize.BSize(wm) = captionMet->BSize(wm);
+ captionMargin =
+ captionRI->ComputedLogicalMargin().ConvertTo(wm, captionWM);
+ // Now that we know the bsize of the caption, reduce the available bsize
+ // for the table frame if we are bsize constrained and the caption is above
+ // or below the inner table. Also reduce the CB size that we store for
+ // our children in case we're a grid item, by the same amount.
+ LogicalSize* cbSize = Properties().Get(GridItemCBSizeProperty());
+ if (NS_UNCONSTRAINEDSIZE != aOuterRI.AvailableBSize() || cbSize) {
+ nscoord captionBSize = 0;
+ nscoord captionISize = 0;
+ switch (captionSide) {
+ case NS_STYLE_CAPTION_SIDE_TOP:
+ case NS_STYLE_CAPTION_SIDE_BOTTOM:
+ case NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE:
+ case NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE:
+ captionBSize = captionSize.BSize(wm) + captionMargin.BStartEnd(wm);
+ break;
+ case NS_STYLE_CAPTION_SIDE_LEFT:
+ case NS_STYLE_CAPTION_SIDE_RIGHT:
+ captionISize = captionSize.ISize(wm) + captionMargin.IStartEnd(wm);
+ break;
+ }
+ if (NS_UNCONSTRAINEDSIZE != aOuterRI.AvailableBSize()) {
+ innerRI->AvailableBSize() =
+ std::max(0, innerRI->AvailableBSize() - captionBSize);
+ }
+ if (cbSize) {
+ // Shrink the CB size by the size reserved for the caption.
+ LogicalSize oldCBSize = *cbSize;
+ cbSize->ISize(wm) = std::max(0, cbSize->ISize(wm) - captionISize);
+ cbSize->BSize(wm) = std::max(0, cbSize->BSize(wm) - captionBSize);
+ if (oldCBSize != *cbSize) {
+ // Reset the inner table's ReflowInput to stretch it to the new size.
+ innerRI.reset();
+ OuterBeginReflowChild(aPresContext, InnerTableFrame(), aOuterRI,
+ innerRI, aOuterRI.ComputedSize(wm).ISize(wm));
+ }
+ }
+ }
+ }
+
+ // Then, now that we know how much to reduce the isize of the inner
+ // table to account for side captions, reflow the inner table.
+ ReflowOutput innerMet(innerRI->GetWritingMode());
+ OuterDoReflowChild(aPresContext, InnerTableFrame(), *innerRI,
+ innerMet, aStatus);
+ LogicalSize innerSize(wm, innerMet.ISize(wm), innerMet.BSize(wm));
+ LogicalMargin innerMargin = innerRI->ComputedLogicalMargin();
+
+ LogicalSize containSize(wm, GetContainingBlockSize(aOuterRI));
+
+ // Now that we've reflowed both we can place them.
+ // XXXldb Most of the input variables here are now uninitialized!
+
+ // XXX Need to recompute inner table's auto margins for the case of side
+ // captions. (Caption's are broken too, but that should be fixed earlier.)
+
+ // Compute the desiredSize so that we can use it as the containerSize
+ // for the FinishReflowChild calls below.
+ LogicalSize desiredSize(wm);
+ SetDesiredSize(captionSide, innerSize, captionSize,
+ innerMargin, captionMargin,
+ desiredSize.ISize(wm), desiredSize.BSize(wm), wm);
+ aDesiredSize.SetSize(wm, desiredSize);
+ nsSize containerSize = aDesiredSize.PhysicalSize();
+ // XXX It's possible for this to be NS_UNCONSTRAINEDSIZE, which will result
+ // in assertions from FinishReflowChild.
+
+ if (mCaptionFrames.NotEmpty()) {
+ LogicalPoint captionOrigin(wm);
+ GetCaptionOrigin(captionSide, containSize, innerSize, innerMargin,
+ captionSize, captionMargin, captionOrigin, wm);
+ FinishReflowChild(mCaptionFrames.FirstChild(), aPresContext, *captionMet,
+ captionRI.ptr(), wm, captionOrigin, containerSize, 0);
+ captionRI.reset();
+ }
+ // XXX If the bsize is constrained then we need to check whether
+ // everything still fits...
+
+ LogicalPoint innerOrigin(wm);
+ GetInnerOrigin(captionSide, containSize, captionSize, captionMargin,
+ innerSize, innerMargin, innerOrigin, wm);
+ FinishReflowChild(InnerTableFrame(), aPresContext, innerMet, innerRI.ptr(),
+ wm, innerOrigin, containerSize, 0);
+ innerRI.reset();
+
+ nsTableFrame::InvalidateTableFrame(InnerTableFrame(), origInnerRect,
+ origInnerVisualOverflow,
+ innerFirstReflow);
+ if (mCaptionFrames.NotEmpty()) {
+ nsTableFrame::InvalidateTableFrame(mCaptionFrames.FirstChild(),
+ origCaptionRect,
+ origCaptionVisualOverflow,
+ captionFirstReflow);
+ }
+
+ UpdateOverflowAreas(aDesiredSize);
+
+ if (GetPrevInFlow()) {
+ ReflowOverflowContainerChildren(aPresContext, aOuterRI,
+ aDesiredSize.mOverflowAreas, 0,
+ aStatus);
+ }
+
+ FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aOuterRI, aStatus);
+
+ // Return our desired rect
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aOuterRI, aDesiredSize);
+}
+
+nsIAtom*
+nsTableWrapperFrame::GetType() const
+{
+ return nsGkAtoms::tableWrapperFrame;
+}
+
+/* ----- global methods ----- */
+
+nsIContent*
+nsTableWrapperFrame::GetCellAt(uint32_t aRowIdx, uint32_t aColIdx) const
+{
+ nsTableCellMap* cellMap = InnerTableFrame()->GetCellMap();
+ if (!cellMap) {
+ return nullptr;
+ }
+
+ nsTableCellFrame* cell = cellMap->GetCellInfoAt(aRowIdx, aColIdx);
+ if (!cell) {
+ return nullptr;
+ }
+
+ return cell->GetContent();
+}
+
+
+nsTableWrapperFrame*
+NS_NewTableWrapperFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsTableWrapperFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableWrapperFrame)
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult
+nsTableWrapperFrame::GetFrameName(nsAString& aResult) const
+{
+ return MakeFrameName(NS_LITERAL_STRING("TableWrapper"), aResult);
+}
+#endif
+
diff --git a/layout/tables/nsTableWrapperFrame.h b/layout/tables/nsTableWrapperFrame.h
new file mode 100644
index 0000000000..45d7c33e41
--- /dev/null
+++ b/layout/tables/nsTableWrapperFrame.h
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsTableWrapperFrame_h__
+#define nsTableWrapperFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "nscore.h"
+#include "nsContainerFrame.h"
+#include "nsCellMap.h"
+#include "nsTableFrame.h"
+
+/**
+ * Primary frame for a table element,
+ * the nsTableWrapperFrame contains 0 or one caption frame, and a nsTableFrame
+ * pseudo-frame (referred to as the "inner frame').
+ */
+class nsTableWrapperFrame : public nsContainerFrame
+{
+public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ NS_DECL_QUERYFRAME_TARGET(nsTableWrapperFrame)
+
+ /** instantiate a new instance of nsTableRowFrame.
+ * @param aPresShell the pres shell for this frame
+ *
+ * @return the frame that was created
+ */
+ friend nsTableWrapperFrame* NS_NewTableWrapperFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ // nsIFrame overrides - see there for a description
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual const nsFrameList& GetChildList(ChildListID aListID) const override;
+ virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
+
+ virtual void SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) override;
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+#ifdef ACCESSIBILITY
+ virtual mozilla::a11y::AccType AccessibleType() override;
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ void BuildDisplayListForInnerTable(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists);
+
+ virtual nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const override;
+
+ bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
+ BaselineSharingGroup aBaselineGroup,
+ nscoord* aBaseline) const override
+ {
+ auto innerTable = InnerTableFrame();
+ nscoord offset;
+ if (innerTable->GetNaturalBaselineBOffset(aWM, aBaselineGroup, &offset)) {
+ auto bStart = innerTable->BStart(aWM, mRect.Size());
+ if (aBaselineGroup == BaselineSharingGroup::eFirst) {
+ *aBaseline = offset + bStart;
+ } else {
+ auto bEnd = bStart + innerTable->BSize(aWM);
+ *aBaseline = BSize(aWM) - (bEnd - offset);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual mozilla::LogicalSize
+ ComputeAutoSize(nsRenderingContext* aRenderingContext,
+ mozilla::WritingMode aWM,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ /** process a reflow command for the table.
+ * This involves reflowing the caption and the inner table.
+ * @see nsIFrame::Reflow */
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::tableWrapperFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override;
+#endif
+
+ virtual nsStyleContext* GetParentStyleContext(nsIFrame** aProviderFrame) const override;
+
+ /**
+ * Return the content for the cell at the given row and column.
+ */
+ nsIContent* GetCellAt(uint32_t aRowIdx, uint32_t aColIdx) const;
+
+ /**
+ * Return the number of rows in the table.
+ */
+ int32_t GetRowCount() const
+ {
+ return InnerTableFrame()->GetRowCount();
+ }
+
+ /**
+ * Return the number of columns in the table.
+ */
+ int32_t GetColCount() const
+ {
+ return InnerTableFrame()->GetColCount();
+ }
+
+ /**
+ * Return the index of the cell at the given row and column.
+ */
+ int32_t GetIndexByRowAndColumn(int32_t aRowIdx, int32_t aColIdx) const
+ {
+ nsTableCellMap* cellMap = InnerTableFrame()->GetCellMap();
+ if (!cellMap)
+ return -1;
+
+ return cellMap->GetIndexByRowAndColumn(aRowIdx, aColIdx);
+ }
+
+ /**
+ * Get the row and column indices for the cell at the given index.
+ */
+ void GetRowAndColumnByIndex(int32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx) const
+ {
+ *aRowIdx = *aColIdx = 0;
+ nsTableCellMap* cellMap = InnerTableFrame()->GetCellMap();
+ if (cellMap) {
+ cellMap->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx);
+ }
+ }
+
+ /**
+ * return the frame for the cell at the given row and column.
+ */
+ nsTableCellFrame* GetCellFrameAt(uint32_t aRowIdx, uint32_t aColIdx) const
+ {
+ nsTableCellMap* map = InnerTableFrame()->GetCellMap();
+ if (!map) {
+ return nullptr;
+ }
+
+ return map->GetCellInfoAt(aRowIdx, aColIdx);
+ }
+
+ /**
+ * Return the col span of the cell at the given row and column indices.
+ */
+ uint32_t GetEffectiveColSpanAt(uint32_t aRowIdx, uint32_t aColIdx) const
+ {
+ nsTableCellMap* map = InnerTableFrame()->GetCellMap();
+ return map->GetEffectiveColSpan(aRowIdx, aColIdx);
+ }
+
+ /**
+ * Return the effective row span of the cell at the given row and column.
+ */
+ uint32_t GetEffectiveRowSpanAt(uint32_t aRowIdx, uint32_t aColIdx) const
+ {
+ nsTableCellMap* map = InnerTableFrame()->GetCellMap();
+ return map->GetEffectiveRowSpan(aRowIdx, aColIdx);
+ }
+
+ /**
+ * The CB size to use for the inner table frame if we're a grid item.
+ */
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(GridItemCBSizeProperty, mozilla::LogicalSize);
+
+protected:
+
+ explicit nsTableWrapperFrame(nsStyleContext* aContext);
+ virtual ~nsTableWrapperFrame();
+
+ void InitChildReflowInput(nsPresContext& aPresContext,
+ ReflowInput& aReflowInput);
+
+ // Get a NS_STYLE_CAPTION_SIDE_* value, or NO_SIDE if no caption is present.
+ // (Remember that caption-side values are interpreted logically, despite
+ // having "physical" names.)
+ uint8_t GetCaptionSide();
+
+ bool HasSideCaption() {
+ uint8_t captionSide = GetCaptionSide();
+ return captionSide == NS_STYLE_CAPTION_SIDE_LEFT ||
+ captionSide == NS_STYLE_CAPTION_SIDE_RIGHT;
+ }
+
+ uint8_t GetCaptionVerticalAlign();
+
+ void SetDesiredSize(uint8_t aCaptionSide,
+ const mozilla::LogicalSize& aInnerSize,
+ const mozilla::LogicalSize& aCaptionSize,
+ const mozilla::LogicalMargin& aInnerMargin,
+ const mozilla::LogicalMargin& aCaptionMargin,
+ nscoord& aISize,
+ nscoord& aBSize,
+ mozilla::WritingMode aWM);
+
+ nsresult GetCaptionOrigin(uint32_t aCaptionSide,
+ const mozilla::LogicalSize& aContainBlockSize,
+ const mozilla::LogicalSize& aInnerSize,
+ const mozilla::LogicalMargin& aInnerMargin,
+ const mozilla::LogicalSize& aCaptionSize,
+ mozilla::LogicalMargin& aCaptionMargin,
+ mozilla::LogicalPoint& aOrigin,
+ mozilla::WritingMode aWM);
+
+ nsresult GetInnerOrigin(uint32_t aCaptionSide,
+ const mozilla::LogicalSize& aContainBlockSize,
+ const mozilla::LogicalSize& aCaptionSize,
+ const mozilla::LogicalMargin& aCaptionMargin,
+ const mozilla::LogicalSize& aInnerSize,
+ mozilla::LogicalMargin& aInnerMargin,
+ mozilla::LogicalPoint& aOrigin,
+ mozilla::WritingMode aWM);
+
+ // reflow the child (caption or innertable frame)
+ void OuterBeginReflowChild(nsPresContext* aPresContext,
+ nsIFrame* aChildFrame,
+ const ReflowInput& aOuterRI,
+ mozilla::Maybe<ReflowInput>& aChildRI,
+ nscoord aAvailISize);
+
+ void OuterDoReflowChild(nsPresContext* aPresContext,
+ nsIFrame* aChildFrame,
+ const ReflowInput& aChildRI,
+ ReflowOutput& aMetrics,
+ nsReflowStatus& aStatus);
+
+ // Set the overflow areas in our reflow metrics
+ void UpdateOverflowAreas(ReflowOutput& aMet);
+
+ // Get the margin.
+ void GetChildMargin(nsPresContext* aPresContext,
+ const ReflowInput& aOuterRI,
+ nsIFrame* aChildFrame,
+ nscoord aAvailableWidth,
+ mozilla::LogicalMargin& aMargin);
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ (~eCanContainOverflowContainers));
+ }
+
+ nsTableFrame* InnerTableFrame() const
+ {
+ return static_cast<nsTableFrame*>(mFrames.FirstChild());
+ }
+
+ /**
+ * Helper for ComputeAutoSize.
+ * Compute the margin-box inline size of aChildFrame given the inputs.
+ * If aMarginResult is non-null, fill it with the part of the
+ * margin-isize that was contributed by the margin.
+ */
+ nscoord ChildShrinkWrapISize(nsRenderingContext* aRenderingContext,
+ nsIFrame* aChildFrame,
+ mozilla::WritingMode aWM,
+ mozilla::LogicalSize aCBSize,
+ nscoord aAvailableISize,
+ nscoord* aMarginResult = nullptr) const;
+
+private:
+ nsFrameList mCaptionFrames;
+};
+
+#endif
diff --git a/layout/tables/reftests/1031934-ref.html b/layout/tables/reftests/1031934-ref.html
new file mode 100644
index 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/reftest-stylo.list b/layout/tables/reftests/reftest-stylo.list
new file mode 100644
index 0000000000..b1315a657f
--- /dev/null
+++ b/layout/tables/reftests/reftest-stylo.list
@@ -0,0 +1,10 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+== 1031934.html 1031934.html
+== 1220621-1a.html 1220621-1a.html
+== 1220621-1b.html 1220621-1b.html
+== 1220621-1c.html 1220621-1c.html
+== 1220621-1d.html 1220621-1d.html
+== 1220621-1e.html 1220621-1e.html
+== 1220621-1f.html 1220621-1f.html
+== 1220621-2a.html 1220621-2a.html
+== 1220621-2b.html 1220621-2b.html
diff --git a/layout/tables/reftests/reftest.list b/layout/tables/reftests/reftest.list
new file mode 100644
index 0000000000..f6f7d5bcef
--- /dev/null
+++ b/layout/tables/reftests/reftest.list
@@ -0,0 +1,9 @@
+== 1031934.html 1031934-ref.html
+== 1220621-1a.html 1220621-1-ref.html
+== 1220621-1b.html 1220621-1-ref.html
+== 1220621-1c.html 1220621-1-ref.html
+== 1220621-1d.html 1220621-1-ref.html
+== 1220621-1e.html 1220621-1-ref.html
+== 1220621-1f.html 1220621-1-ref.html
+== 1220621-2a.html 1220621-2-ref.html
+== 1220621-2b.html 1220621-2-ref.html
diff --git a/layout/tables/test/mochitest.ini b/layout/tables/test/mochitest.ini
new file mode 100644
index 0000000000..8172770b45
--- /dev/null
+++ b/layout/tables/test/mochitest.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+
+[test_bug337124.html]
+[test_bug541668_table_event_delivery.html]
diff --git a/layout/tables/test/test_bug337124.html b/layout/tables/test/test_bug337124.html
new file mode 100644
index 0000000000..e4f85532dc
--- /dev/null
+++ b/layout/tables/test/test_bug337124.html
@@ -0,0 +1,32 @@
+<html><head>
+<title>Test for Bug 337124</title>
+ <script type="text/javascript" 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..94835d469f
--- /dev/null
+++ b/layout/tables/test/test_bug541668_table_event_delivery.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=541668
+-->
+<head>
+ <title>Test for Bug 541668</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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 },
+ false);
+ synthesizeMouse(target, 150, 150, { type: "mousemove" });
+ is(got_mousemove, true, "should get mousemove on block");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>