diff -r 000000000000 -r 6474c204b198 layout/tables/nsTableFrame.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/tables/nsTableFrame.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,7386 @@ +/* -*- 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 "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 "nsTableOuterFrame.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 "nsAutoPtr.h" +#include "nsCSSFrameConstructor.h" +#include "nsStyleSet.h" +#include "nsDisplayList.h" +#include "nsIScrollableFrame.h" +#include "nsCSSProps.h" +#include "RestyleTracker.h" +#include + +using namespace mozilla; +using namespace mozilla::layout; + +/******************************************************************************** + ** nsTableReflowState ** + ********************************************************************************/ + +struct nsTableReflowState { + + // the real reflow state + const nsHTMLReflowState& reflowState; + + // The table's available size + nsSize availSize; + + // Stationary x-offset + nscoord x; + + // Running y-offset + nscoord y; + + nsTableReflowState(nsPresContext& aPresContext, + const nsHTMLReflowState& aReflowState, + nsTableFrame& aTableFrame, + nscoord aAvailWidth, + nscoord aAvailHeight) + : reflowState(aReflowState) + { + Init(aPresContext, aTableFrame, aAvailWidth, aAvailHeight); + } + + void Init(nsPresContext& aPresContext, + nsTableFrame& aTableFrame, + nscoord aAvailWidth, + nscoord aAvailHeight) + { + nsTableFrame* table = static_cast(aTableFrame.FirstInFlow()); + nsMargin borderPadding = table->GetChildAreaOffset(&reflowState); + nscoord cellSpacingX = table->GetCellSpacingX(); + + x = borderPadding.left + cellSpacingX; + y = borderPadding.top; //cellspacing added during reflow + + availSize.width = aAvailWidth; + if (NS_UNCONSTRAINEDSIZE != availSize.width) { + availSize.width -= borderPadding.left + borderPadding.right + + (2 * cellSpacingX); + availSize.width = std::max(0, availSize.width); + } + + availSize.height = aAvailHeight; + if (NS_UNCONSTRAINEDSIZE != availSize.height) { + availSize.height -= borderPadding.top + borderPadding.bottom + + (2 * table->GetCellSpacingY()); + availSize.height = std::max(0, availSize.height); + } + } + + nsTableReflowState(nsPresContext& aPresContext, + const nsHTMLReflowState& aReflowState, + nsTableFrame& aTableFrame) + : reflowState(aReflowState) + { + Init(aPresContext, aTableFrame, aReflowState.AvailableWidth(), aReflowState.AvailableHeight()); + } + +}; + +/******************************************************************************** + ** nsTableFrame ** + ********************************************************************************/ + +struct BCPropertyData +{ + BCPropertyData() : mTopBorderWidth(0), mRightBorderWidth(0), + mBottomBorderWidth(0), mLeftBorderWidth(0), + mLeftCellBorderWidth(0), mRightCellBorderWidth(0) {} + nsIntRect mDamageArea; + BCPixelSize mTopBorderWidth; + BCPixelSize mRightBorderWidth; + BCPixelSize mBottomBorderWidth; + BCPixelSize mLeftBorderWidth; + BCPixelSize mLeftCellBorderWidth; + BCPixelSize mRightCellBorderWidth; +}; + +nsIFrame* +nsTableFrame::GetParentStyleContextFrame() const +{ + // Since our parent, the table outer frame, returned this frame, we + // must return whatever our parent would normally have returned. + + NS_PRECONDITION(mParent, "table constructed without outer table"); + if (!mContent->GetParent() && !StyleContext()->GetPseudo()) { + // We're the root. We have no style context parent. + return nullptr; + } + + return static_cast(GetParent())->DoGetParentStyleContextFrame(); +} + + +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, + nsIFrame* aParent, + nsIFrame* aPrevInFlow) +{ + NS_PRECONDITION(!mCellMap, "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); + + // Create the cell map if this frame is the first-in-flow. + if (!aPrevInFlow) { + mCellMap = new nsTableCellMap(*this, borderCollapse); + } + + if (aPrevInFlow) { + // set my width, because all frames in a table flow are the same width and + // code in nsTableOuterFrame depends on this being set + mRect.width = aPrevInFlow->GetSize().width; + } + else { + NS_ASSERTION(!mTableLayoutStrategy, "strategy was created before Init was called"); + // create the strategy + if (IsAutoLayout()) + mTableLayoutStrategy = new BasicTableLayoutStrategy(this); + else + mTableLayoutStrategy = new FixedTableLayoutStrategy(this); + } +} + +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->GetStateBits() & 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; +} + +typedef nsTArray FrameTArray; + +/* static */ void +nsTableFrame::DestroyPositionedTablePartArray(void* aPropertyValue) +{ + auto positionedObjs = static_cast(aPropertyValue); + delete positionedObjs; +} + +/* 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 (nsGkAtoms::tableCellFrame != aFrame->GetType()) { + nsIContent* content = aFrame->GetContent(); + nsPresContext* presContext = aFrame->PresContext(); + if (content && !presContext->HasWarnedAboutPositionedTableParts()) { + presContext->SetHasWarnedAboutPositionedTableParts(); + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Layout: Tables"), + content->OwnerDoc(), + nsContentUtils::eLAYOUT_PROPERTIES, + "TablePartRelPosWarning"); + } + } + + nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(aFrame); + MOZ_ASSERT(tableFrame, "Should have a table frame here"); + tableFrame = static_cast(tableFrame->FirstContinuation()); + + // Retrieve the positioned parts array for this table. + FrameProperties props = tableFrame->Properties(); + auto positionedParts = + static_cast(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 ensure that we hit aDestructRoot on the way. + // If we don't, that means that the table frame will be destroyed, so we don't + // need to bother with unregistering this frame. + nsTableFrame* tableFrame = GetTableFramePassingThrough(aDestructRoot, aFrame); + if (!tableFrame) { + return; + } + tableFrame = static_cast(tableFrame->FirstContinuation()); + + // Retrieve the positioned parts array for this table. + FrameProperties props = tableFrame->Properties(); + auto positionedParts = + static_cast(props.Get(PositionedTablePartArray())); + + // Remove the frame. + MOZ_ASSERT(positionedParts && + positionedParts->IndexOf(aFrame) != FrameTArray::NoIndex, + "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. +nsresult +nsTableFrame::SetInitialChildList(ChildListID aListID, + nsFrameList& aChildList) +{ + + if (!mFrames.IsEmpty() || !mColGroups.IsEmpty()) { + // We already have child frames which means we've already been + // initialized + NS_NOTREACHED("unexpected second call to SetInitialChildList"); + return NS_ERROR_UNEXPECTED; + } + if (aListID != kPrincipalList) { + // All we know about is the principal child list. + NS_NOTREACHED("unknown frame list"); + return NS_ERROR_INVALID_ARG; + } + + // 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 (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == 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(); + } + } + + return NS_OK; +} + +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); + nsAutoTArray 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 colX = colCount - 1; colX >= 0; colX--) { + if (cellMap->GetNumCellsOriginatingInCol(colX) > 0) { + break; + } + colCount--; + } + } + return colCount; +} + +int32_t nsTableFrame::GetIndexOfLastRealCol() +{ + int32_t numCols = mColFrames.Length(); + if (numCols > 0) { + for (int32_t colX = numCols - 1; colX >= 0; colX--) { + nsTableColFrame* colFrame = GetColFrame(colX); + if (colFrame) { + if (eColAnonymousCell != colFrame->GetColType()) { + return colX; + } + } + } + } + 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); + bool ignore; + + if (aCellMap) + return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex, ignore); + 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 rgX = 0; rgX < rowGroups.Length(); rgX++) { + rowGroups[rgX]->AdjustRowIndices(aRowIndex, aAdjustment); + } +} + + +void nsTableFrame::ResetRowIndices(const nsFrameList::Slice& aRowGroupsToExclude) +{ + // Iterate over the row groups and adjust the row indices of all rows + // omit the rowgroups that will be inserted later + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + int32_t rowIndex = 0; + nsTHashtable > excludeRowGroups; + nsFrameList::Enumerator excludeRowGroupsEnumerator(aRowGroupsToExclude); + while (!excludeRowGroupsEnumerator.AtEnd()) { + excludeRowGroups.PutEntry(static_cast(excludeRowGroupsEnumerator.get())); + excludeRowGroupsEnumerator.Next(); + } + + for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + if (!excludeRowGroups.GetEntry(rgFrame)) { + const nsFrameList& rowFrames = rgFrame->PrincipalChildList(); + for (nsFrameList::Enumerator rows(rowFrames); !rows.AtEnd(); rows.Next()) { + if (NS_STYLE_DISPLAY_TABLE_ROW==rows.get()->StyleDisplay()->mDisplay) { + ((nsTableRowFrame *)rows.get())->SetRowIndex(rowIndex); + rowIndex++; + } + } + } + } +} +void nsTableFrame::InsertColGroups(int32_t aStartColIndex, + const nsFrameList::Slice& aColGroups) +{ + int32_t colIndex = aStartColIndex; + nsFrameList::Enumerator colGroups(aColGroups); + for (; !colGroups.AtEnd(); colGroups.Next()) { + MOZ_ASSERT(colGroups.get()->GetType() == nsGkAtoms::tableColGroupFrame); + nsTableColGroupFrame* cgFrame = + static_cast(colGroups.get()); + cgFrame->SetStartColumnIndex(colIndex); + // XXXbz this sucks. AddColsToTable will actually remove colgroups from + // the list we're traversing! Need to fix things here. :( I guess this is + // why the old code used pointer-to-last-frame as opposed to + // pointer-to-frame-after-last.... + + // How about dealing with this by storing a const reference to the + // mNextSibling of the framelist's last frame, instead of storing a pointer + // to the first-after-next frame? Will involve making nsFrameList friend + // of nsIFrame, but it's time for that anyway. + cgFrame->AddColsToTable(colIndex, false, + colGroups.get()->PrincipalChildList()); + int32_t numCols = cgFrame->GetColCount(); + colIndex += numCols; + } + + nsFrameList::Enumerator remainingColgroups = colGroups.GetUnlimitedEnumerator(); + if (!remainingColgroups.AtEnd()) { + nsTableColGroupFrame::ResetColIndices( + static_cast(remainingColgroups.get()), colIndex); + } +} + +void nsTableFrame::InsertCol(nsTableColFrame& aColFrame, + int32_t aColIndex) +{ + mColFrames.InsertElementAt(aColIndex, &aColFrame); + nsTableColType insertedColType = aColFrame.GetColType(); + int32_t numCacheCols = mColFrames.Length(); + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + int32_t numMapCols = cellMap->GetColCount(); + if (numCacheCols > numMapCols) { + bool removedFromCache = false; + if (eColAnonymousCell != insertedColType) { + nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1); + if (lastCol) { + nsTableColType lastColType = lastCol->GetColType(); + if (eColAnonymousCell == lastColType) { + // remove the col from the cache + mColFrames.RemoveElementAt(numCacheCols - 1); + // remove the col from the eColGroupAnonymousCell col group + nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame *)mColGroups.LastChild(); + if (lastColGroup) { + lastColGroup->RemoveChild(*lastCol, false); + + // remove the col group if it is empty + if (lastColGroup->GetColCount() <= 0) { + mColGroups.DestroyFrame((nsIFrame*)lastColGroup); + } + } + removedFromCache = true; + } + } + } + if (!removedFromCache) { + cellMap->AddColsAtEnd(1); + } + } + } + // for now, just bail and recalc all of the collapsing borders + if (IsBorderCollapse()) { + nsIntRect 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) { + AppendAnonymousColFrames(1); + } + } + // for now, just bail and recalc all of the collapsing borders + if (IsBorderCollapse()) { + nsIntRect damageArea(0, 0, GetColCount(), GetRowCount()); + AddBCDamageArea(damageArea); + } +} + +/** Get the cell map for this table frame. It is not always mCellMap. + * Only the first-in-flow has a legit cell map. + */ +nsTableCellMap* +nsTableFrame::GetCellMap() const +{ + return static_cast(FirstInFlow())->mCellMap; +} + +// XXX this needs to be moved to nsCSSFrameConstructor +nsTableColGroupFrame* +nsTableFrame::CreateAnonymousColGroupFrame(nsTableColGroupType aColGroupType) +{ + nsIContent* colGroupContent = GetContent(); + nsPresContext* presContext = PresContext(); + nsIPresShell *shell = presContext->PresShell(); + + nsRefPtr colGroupStyle; + colGroupStyle = shell->StyleSet()-> + ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableColGroup, mStyleContext); + // Create a col group frame + nsIFrame* newFrame = NS_NewTableColGroupFrame(shell, colGroupStyle); + ((nsTableColGroupFrame *)newFrame)->SetColType(aColGroupType); + newFrame->Init(colGroupContent, this, nullptr); + return (nsTableColGroupFrame *)newFrame; +} + +void +nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) +{ + // get the last col group frame + nsTableColGroupFrame* colGroupFrame = + static_cast(mColGroups.LastChild()); + + if (!colGroupFrame || + (colGroupFrame->GetColType() != eColGroupAnonymousCell)) { + int32_t colIndex = (colGroupFrame) ? + colGroupFrame->GetStartColumnIndex() + + colGroupFrame->GetColCount() : 0; + colGroupFrame = CreateAnonymousColGroupFrame(eColGroupAnonymousCell); + if (!colGroupFrame) { + return; + } + // add the new frame to the child list + mColGroups.AppendFrame(this, colGroupFrame); + colGroupFrame->SetStartColumnIndex(colIndex); + } + AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell, + true); + +} + +// XXX this needs to be moved to nsCSSFrameConstructor +// Right now it only creates the col frames at the end +void +nsTableFrame::AppendAnonymousColFrames(nsTableColGroupFrame* aColGroupFrame, + int32_t aNumColsToAdd, + nsTableColType aColType, + bool aAddToTable) +{ + NS_PRECONDITION(aColGroupFrame, "null frame"); + NS_PRECONDITION(aColType != eColAnonymousCol, "Shouldn't happen"); + + nsIPresShell *shell = PresContext()->PresShell(); + + // Get the last col frame + nsFrameList newColFrames; + + int32_t startIndex = mColFrames.Length(); + int32_t lastIndex = startIndex + aNumColsToAdd - 1; + + for (int32_t childX = startIndex; childX <= lastIndex; childX++) { + nsIContent* iContent; + nsRefPtr styleContext; + nsStyleContext* parentStyleContext; + + // all anonymous cols that we create here use a pseudo style context of the + // col group + iContent = aColGroupFrame->GetContent(); + parentStyleContext = aColGroupFrame->StyleContext(); + styleContext = shell->StyleSet()-> + ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableCol, parentStyleContext); + // ASSERTION to check for bug 54454 sneaking back in... + NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames"); + + // create the new col frame + nsIFrame* colFrame = NS_NewTableColFrame(shell, styleContext); + ((nsTableColFrame *) colFrame)->SetColType(aColType); + colFrame->Init(iContent, aColGroupFrame, nullptr); + + newColFrames.AppendFrame(nullptr, colFrame); + } + nsFrameList& cols = aColGroupFrame->GetWritableChildList(); + nsIFrame* oldLastCol = cols.LastChild(); + const nsFrameList::Slice& newCols = + cols.InsertFrames(nullptr, oldLastCol, newColFrames); + if (aAddToTable) { + // get the starting col index in the cache + int32_t startColIndex; + if (oldLastCol) { + startColIndex = + static_cast(oldLastCol)->GetColIndex() + 1; + } else { + startColIndex = aColGroupFrame->GetStartColumnIndex(); + } + + aColGroupFrame->AddColsToTable(startColIndex, true, newCols); + } +} + +void +nsTableFrame::MatchCellMapToColCache(nsTableCellMap* aCellMap) +{ + int32_t numColsInMap = GetColCount(); + int32_t numColsInCache = mColFrames.Length(); + int32_t numColsToAdd = numColsInMap - numColsInCache; + if (numColsToAdd > 0) { + // this sets the child list, updates the col cache and cell map + AppendAnonymousColFrames(numColsToAdd); + } + if (numColsToAdd < 0) { + int32_t numColsNotRemoved = DestroyAnonymousColFrames(-numColsToAdd); + // if the cell map has fewer cols than the cache, correct it + if (numColsNotRemoved > 0) { + aCellMap->AddColsAtEnd(numColsNotRemoved); + } + } + if (numColsToAdd && HasZeroColSpans()) { + SetNeedColSpanExpansion(true); + } + if (NeedColSpanExpansion()) { + // This flag can be set in two ways -- either by changing + // the number of columns (that happens in the block above), + // or by adding a cell with colspan="0" to the cellmap. To + // handle the latter case we need to explicitly check the + // flag here -- it may be set even if the number of columns + // did not change. + // + // @see nsCellMap::AppendCell + + aCellMap->ExpandZeroColSpans(); + } +} + +void +nsTableFrame::DidResizeColumns() +{ + NS_PRECONDITION(!GetPrevInFlow(), + "should only be called on first-in-flow"); + if (mBits.mResizedColumns) + return; // already marked + + for (nsTableFrame *f = this; f; + f = static_cast(f->GetNextInFlow())) + f->mBits.mResizedColumns = true; +} + +void +nsTableFrame::AppendCell(nsTableCellFrame& aCellFrame, + int32_t aRowIndex) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + nsIntRect damageArea(0,0,0,0); + cellMap->AppendCell(aCellFrame, aRowIndex, true, damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } +} + +void nsTableFrame::InsertCells(nsTArray& aCellFrames, + int32_t aRowIndex, + int32_t aColIndexBefore) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + nsIntRect 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 colX = endIndex; colX >= startIndex; colX--) { + nsTableColFrame* colFrame = GetColFrame(colX); + if (colFrame && (eColAnonymousCell == colFrame->GetColType())) { + nsTableColGroupFrame* cgFrame = + static_cast(colFrame->GetParent()); + // remove the frame from the colgroup + cgFrame->RemoveChild(*colFrame, false); + // remove the frame from the cache, but not the cell map + RemoveCol(nullptr, colX, true, false); + numColsRemoved++; + } + else { + break; + } + } + return (aNumFrames - numColsRemoved); +} + +void nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame, + int32_t aRowIndex) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + nsIntRect damageArea(0,0,0,0); + cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } +} + +int32_t +nsTableFrame::GetStartRowIndex(nsTableRowGroupFrame* aRowGroupFrame) +{ + RowGroupArray orderedRowGroups; + OrderRowGroups(orderedRowGroups); + + int32_t rowIndex = 0; + for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; + if (rgFrame == aRowGroupFrame) { + break; + } + int32_t numRows = rgFrame->GetRowCount(); + rowIndex += numRows; + } + return rowIndex; +} + +// this cannot extend beyond a single row group +void nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame, + int32_t aRowIndex, + nsTArray& aRowFrames) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + int32_t absRowIndex = GetStartRowIndex(aRowGroupFrame) + aRowIndex; + InsertRows(aRowGroupFrame, aRowFrames, absRowIndex, true); + } +} + +// this cannot extend beyond a single row group +int32_t +nsTableFrame::InsertRows(nsTableRowGroupFrame* aRowGroupFrame, + nsTArray& aRowFrames, + int32_t aRowIndex, + bool aConsiderSpans) +{ +#ifdef DEBUG_TABLE_CELLMAP + printf("=== insertRowsBefore firstRow=%d \n", aRowIndex); + Dump(true, false, true); +#endif + + int32_t numColsToAdd = 0; + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + nsIntRect 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 rowY = 0; rowY < numNewRows; rowY++) { + nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowY); + rowFrame->SetRowIndex(aRowIndex + rowY); + } + 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) { + nsIntRect damageArea(0,0,0,0); + cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans, damageArea); + MatchCellMapToColCache(cellMap); + if (IsBorderCollapse()) { + AddBCDamageArea(damageArea); + } + } + AdjustRowIndices(firstRowIndex, -aNumRowsToRemove); +#ifdef DEBUG_TABLE_CELLMAP + printf("=== removeRowsAfter\n"); + Dump(true, true, true); +#endif +} + +// collect the rows ancestors of aFrame +int32_t +nsTableFrame::CollectRows(nsIFrame* aFrame, + nsTArray& aCollection) +{ + NS_PRECONDITION(aFrame, "null frame"); + int32_t numRows = 0; + nsIFrame* childFrame = aFrame->GetFirstPrincipalChild(); + while (childFrame) { + aCollection.AppendElement(static_cast(childFrame)); + numRows++; + childFrame = childFrame->GetNextSibling(); + } + 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); + + nsAutoTArray rows; + // Loop over the rowgroups and check if some of them are new, if they are + // insert cellmaps in the order that is predefined by OrderRowGroups, + // XXXbz this code is O(N*M) where N is number of new rowgroups + // and M is number of rowgroups we have! + uint32_t rgIndex; + for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd(); + rowgroups.Next()) { + if (orderedRowGroups[rgIndex] == rowgroups.get()) { + nsTableRowGroupFrame* priorRG = + (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; + // create and add the cell map for the row group + cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG); + + break; + } + } + } + cellMap->Synchronize(this); + ResetRowIndices(aRowGroups); + + //now that the cellmaps are reordered too insert the rows + for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd(); + rowgroups.Next()) { + if (orderedRowGroups[rgIndex] == rowgroups.get()) { + nsTableRowGroupFrame* priorRG = + (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; + // collect the new row frames in an array and add them to the table + int32_t numRows = CollectRows(rowgroups.get(), rows); + if (numRows > 0) { + int32_t rowIndex = 0; + if (priorRG) { + int32_t priorNumRows = priorRG->GetRowCount(); + rowIndex = priorRG->GetStartRowIndex() + priorNumRows; + } + InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true); + rows.Clear(); + } + break; + } + } + } + + } +#ifdef DEBUG_TABLE_CELLMAP + printf("=== insertRowGroupsAfter\n"); + Dump(true, true, true); +#endif +} + + +///////////////////////////////////////////////////////////////////////////// +// Child frame enumeration + +const nsFrameList& +nsTableFrame::GetChildList(ChildListID aListID) const +{ + if (aListID == kColGroupList) { + return mColGroups; + } + return nsContainerFrame::GetChildList(aListID); +} + +void +nsTableFrame::GetChildLists(nsTArray* aLists) const +{ + nsContainerFrame::GetChildLists(aLists); + mColGroups.AppendIfNonempty(aLists, kColGroupList); +} + +nsRect +nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { + *aSnap = false; + return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +bool +nsDisplayTableItem::IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) +{ + if (!mPartHasFixedBackground) + return false; + + // If aFrame is mFrame or an ancestor in this document, and aFrame is + // not the viewport frame, then moving aFrame will move mFrame + // relative to the viewport, so our fixed-pos background will change. + return mFrame == aFrame || + nsLayoutUtils::IsProperAncestorFrame(aFrame, mFrame); +} + +/* static */ void +nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame) +{ + nsStyleContext *bgSC; + if (!nsCSSRendering::FindBackground(aFrame, &bgSC)) + return; + if (!bgSC->StyleBackground()->HasFixedBackground()) + return; + + mPartHasFixedBackground = true; +} + +class nsDisplayTableBorderBackground : public nsDisplayTableItem { +public: + nsDisplayTableBorderBackground(nsDisplayListBuilder* aBuilder, + nsTableFrame* aFrame) : + nsDisplayTableItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayTableBorderBackground); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayTableBorderBackground() { + MOZ_COUNT_DTOR(nsDisplayTableBorderBackground); + } +#endif + + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) MOZ_OVERRIDE; + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) MOZ_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 + +/* static */ bool +nsTableFrame::AnyTablePartHasUndecodedBackgroundImage(nsIFrame* aStart, + nsIFrame* aEnd) +{ + for (nsIFrame* f = aStart; f != aEnd; f = f->GetNextSibling()) { + NS_ASSERTION(IsFrameAllowedInTable(f->GetType()), "unexpected frame type"); + + if (!nsCSSRendering::AreAllBackgroundImagesDecodedForFrame(f)) + return true; + + nsTableCellFrame *cellFrame = do_QueryFrame(f); + if (cellFrame) + continue; + + if (AnyTablePartHasUndecodedBackgroundImage(f->PrincipalChildList().FirstChild(), nullptr)) + return true; + } + + return false; +} + +void +nsDisplayTableBorderBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) +{ + if (aBuilder->ShouldSyncDecodeImages()) { + if (nsTableFrame::AnyTablePartHasUndecodedBackgroundImage(mFrame, mFrame->GetNextSibling()) || + nsTableFrame::AnyTablePartHasUndecodedBackgroundImage( + mFrame->GetChildList(nsIFrame::kColGroupList).FirstChild(), nullptr)) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + } + + nsDisplayTableItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); +} + +void +nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + static_cast(mFrame)-> + PaintTableBorderBackground(*aCtx, mVisibleRect, + ToReferenceFrame(), + aBuilder->GetBackgroundPaintFlags()); +} + +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. + nsIFrame* kid = aFrame->GetFirstPrincipalChild(); + while (kid) { + aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); + kid = kid->GetNextSibling(); + } +} + +/* 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->StyleBorder()->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, + 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(aBuilder, 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; +} + +// 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)) { + if (StyleVisibility()->IsVisible()) { + nsMargin deflate = GetDeflationForBackground(PresContext()); + // 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); + aLists.BorderBackground()->AppendNewToTop(item); + } + } + DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item); +} + +nsMargin +nsTableFrame::GetDeflationForBackground(nsPresContext* aPresContext) const +{ + if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode() || + !IsBorderCollapse()) + return nsMargin(0,0,0,0); + + return GetOuterBCBorder(); +} + +// 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. +void +nsTableFrame::PaintTableBorderBackground(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, uint32_t aBGPaintFlags) +{ + nsPresContext* presContext = PresContext(); + + TableBackgroundPainter painter(this, TableBackgroundPainter::eOrigin_Table, + presContext, aRenderingContext, + aDirtyRect, aPt, aBGPaintFlags); + 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. + nsresult rv = painter.PaintTable(this, deflate, deflate != nsMargin(0, 0, 0, 0)); + if (NS_FAILED(rv)) return; + + if (StyleVisibility()->IsVisible()) { + if (!IsBorderCollapse()) { + int skipSides = GetSkipSides(); + nsRect rect(aPt, mRect.Size()); + nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, + aDirtyRect, rect, mStyleContext, skipSides); + } + else { + // XXX we should probably get rid of this translation at some stage + // But that would mean modifying PaintBCBorders, ugh + nsRenderingContext::AutoPushTranslation translate(&aRenderingContext, aPt); + PaintBCBorders(aRenderingContext, aDirtyRect - aPt); + } + } +} + +int +nsTableFrame::GetLogicalSkipSides(const nsHTMLReflowState* aReflowState) const +{ + int skip = 0; + // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto + // account for pagination + if (nullptr != GetPrevInFlow()) { + skip |= LOGICAL_SIDE_B_START; + } + if (nullptr != GetNextInFlow()) { + skip |= LOGICAL_SIDE_B_END; + } + return skip; +} + +void +nsTableFrame::SetColumnDimensions(nscoord aHeight, + const nsMargin& aBorderPadding) +{ + nscoord cellSpacingX = GetCellSpacingX(); + nscoord cellSpacingY = GetCellSpacingY(); + nscoord colHeight = aHeight -= aBorderPadding.top + aBorderPadding.bottom + + 2* cellSpacingY; + + nsTableIterator iter(mColGroups); + nsIFrame* colGroupFrame = iter.First(); + bool tableIsLTR = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR; + int32_t colX =tableIsLTR ? 0 : std::max(0, GetColCount() - 1); + int32_t tableColIncr = tableIsLTR ? 1 : -1; + nsPoint colGroupOrigin(aBorderPadding.left + cellSpacingX, + aBorderPadding.top + cellSpacingY); + while (colGroupFrame) { + MOZ_ASSERT(colGroupFrame->GetType() == nsGkAtoms::tableColGroupFrame); + nscoord colGroupWidth = 0; + nsTableIterator iterCol(*colGroupFrame); + nsIFrame* colFrame = iterCol.First(); + nsPoint colOrigin(0,0); + while (colFrame) { + if (NS_STYLE_DISPLAY_TABLE_COLUMN == + colFrame->StyleDisplay()->mDisplay) { + NS_ASSERTION(colX < GetColCount(), "invalid number of columns"); + nscoord colWidth = GetColumnWidth(colX); + nsRect colRect(colOrigin.x, colOrigin.y, colWidth, colHeight); + colFrame->SetRect(colRect); + colOrigin.x += colWidth + cellSpacingX; + colGroupWidth += colWidth + cellSpacingX; + colX += tableColIncr; + } + colFrame = iterCol.Next(); + } + if (colGroupWidth) { + colGroupWidth -= cellSpacingX; + } + + nsRect colGroupRect(colGroupOrigin.x, colGroupOrigin.y, colGroupWidth, colHeight); + colGroupFrame->SetRect(colGroupRect); + colGroupFrame = iter.Next(); + colGroupOrigin.x += colGroupWidth + cellSpacingX; + } +} + +// 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 height, but first we need to scrutinize every Invalidate +void +nsTableFrame::ProcessRowInserted(nscoord aNewHeight) +{ + 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 rgX = 0; rgX < rowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + NS_ASSERTION(rgFrame, "Must have rgFrame here"); + nsIFrame* childFrame = rgFrame->GetFirstPrincipalChild(); + // find the row that was inserted first + while (childFrame) { + 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 + } + } + childFrame = childFrame->GetNextSibling(); + } + } +} + +/* virtual */ void +nsTableFrame::MarkIntrinsicWidthsDirty() +{ + 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->MarkIntrinsicWidthsDirty(); + + // XXXldb Call SetBCDamageArea? + + nsContainerFrame::MarkIntrinsicWidthsDirty(); +} + +/* virtual */ nscoord +nsTableFrame::GetMinWidth(nsRenderingContext *aRenderingContext) +{ + if (NeedToCalcBCBorders()) + CalcBCBorders(); + + ReflowColGroups(aRenderingContext); + + return LayoutStrategy()->GetMinWidth(aRenderingContext); +} + +/* virtual */ nscoord +nsTableFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) +{ + if (NeedToCalcBCBorders()) + CalcBCBorders(); + + ReflowColGroups(aRenderingContext); + + return LayoutStrategy()->GetPrefWidth(aRenderingContext, false); +} + +/* virtual */ nsIFrame::IntrinsicWidthOffsetData +nsTableFrame::IntrinsicWidthOffsets(nsRenderingContext* aRenderingContext) +{ + IntrinsicWidthOffsetData result = + nsContainerFrame::IntrinsicWidthOffsets(aRenderingContext); + + result.hMargin = 0; + result.hPctMargin = 0; + + if (IsBorderCollapse()) { + result.hPadding = 0; + result.hPctPadding = 0; + + nsMargin outerBC = GetIncludedOuterBCBorder(); + result.hBorder = outerBC.LeftRight(); + } + + return result; +} + +/* virtual */ nsSize +nsTableFrame::ComputeSize(nsRenderingContext *aRenderingContext, + nsSize aCBSize, nscoord aAvailableWidth, + nsSize aMargin, nsSize aBorder, nsSize aPadding, + uint32_t aFlags) +{ + nsSize result = + nsContainerFrame::ComputeSize(aRenderingContext, aCBSize, aAvailableWidth, + aMargin, aBorder, aPadding, aFlags); + + // 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 width. + nscoord minWidth = GetMinWidth(aRenderingContext); + if (minWidth > result.width) + result.width = minWidth; + + return result; +} + +nscoord +nsTableFrame::TableShrinkWidthToFit(nsRenderingContext *aRenderingContext, + nscoord aWidthInCB) +{ + // 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 minWidth = GetMinWidth(aRenderingContext); + if (minWidth > aWidthInCB) { + result = minWidth; + } else { + // Tables shrink width to fit with a slightly different algorithm + // from the one they use for their intrinsic widths (the difference + // relates to handling of percentage widths on columns). So this + // function differs from nsFrame::ShrinkWidthToFit by only the + // following line. + // Since we've already called GetMinWidth, we don't need to do any + // of the other stuff GetPrefWidth does. + nscoord prefWidth = + LayoutStrategy()->GetPrefWidth(aRenderingContext, true); + if (prefWidth > aWidthInCB) { + result = aWidthInCB; + } else { + result = prefWidth; + } + } + return result; +} + +/* virtual */ nsSize +nsTableFrame::ComputeAutoSize(nsRenderingContext *aRenderingContext, + nsSize aCBSize, nscoord aAvailableWidth, + nsSize aMargin, nsSize aBorder, nsSize aPadding, + bool aShrinkWrap) +{ + // Tables always shrink-wrap. + nscoord cbBased = aAvailableWidth - aMargin.width - aBorder.width - + aPadding.width; + return nsSize(TableShrinkWidthToFit(aRenderingContext, cbBased), + NS_UNCONSTRAINEDSIZE); +} + +// Return true if aParentReflowState.frame or any of its ancestors within +// the containing table have non-auto height. (e.g. pct or fixed height) +bool +nsTableFrame::AncestorsHaveStyleHeight(const nsHTMLReflowState& aParentReflowState) +{ + for (const nsHTMLReflowState* rs = &aParentReflowState; + rs && rs->frame; rs = rs->parentReflowState) { + nsIAtom* frameType = rs->frame->GetType(); + if (IS_TABLE_CELL(frameType) || + (nsGkAtoms::tableRowFrame == frameType) || + (nsGkAtoms::tableRowGroupFrame == frameType)) { + const nsStyleCoord &height = rs->mStylePosition->mHeight; + // calc() with percentages treated like 'auto' on internal table elements + if (height.GetUnit() != eStyleUnit_Auto && + (!height.IsCalcUnit() || !height.HasPercent())) { + return true; + } + } + else if (nsGkAtoms::tableFrame == frameType) { + // we reached the containing table, so always return + return rs->mStylePosition->mHeight.GetUnit() != eStyleUnit_Auto; + } + } + return false; +} + +// See if a special height reflow needs to occur and if so, call RequestSpecialHeightReflow +void +nsTableFrame::CheckRequestSpecialHeightReflow(const nsHTMLReflowState& aReflowState) +{ + NS_ASSERTION(IS_TABLE_CELL(aReflowState.frame->GetType()) || + aReflowState.frame->GetType() == nsGkAtoms::tableRowFrame || + aReflowState.frame->GetType() == nsGkAtoms::tableRowGroupFrame || + aReflowState.frame->GetType() == nsGkAtoms::tableFrame, + "unexpected frame type"); + if (!aReflowState.frame->GetPrevInFlow() && // 1st in flow + (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight() || // no computed height + 0 == aReflowState.ComputedHeight()) && + eStyleUnit_Percent == aReflowState.mStylePosition->mHeight.GetUnit() && // pct height + nsTableFrame::AncestorsHaveStyleHeight(*aReflowState.parentReflowState)) { + nsTableFrame::RequestSpecialHeightReflow(aReflowState); + } +} + +// Notify the frame and its ancestors (up to the containing table) that a special +// height reflow will occur. During a special height reflow, a table, row group, +// row, or cell returns the last size it was reflowed at. However, the table may +// change the height of row groups, rows, cells in DistributeHeightToRows after. +// And the row group can change the height of rows, cells in CalculateRowHeights. +void +nsTableFrame::RequestSpecialHeightReflow(const nsHTMLReflowState& aReflowState) +{ + // notify the frame and its ancestors of the special reflow, stopping at the containing table + for (const nsHTMLReflowState* rs = &aReflowState; rs && rs->frame; rs = rs->parentReflowState) { + nsIAtom* frameType = rs->frame->GetType(); + NS_ASSERTION(IS_TABLE_CELL(frameType) || + nsGkAtoms::tableRowFrame == frameType || + nsGkAtoms::tableRowGroupFrame == frameType || + nsGkAtoms::tableFrame == frameType, + "unexpected frame type"); + + rs->frame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT); + if (nsGkAtoms::tableFrame == frameType) { + NS_ASSERTION(rs != &aReflowState, + "should not request special height reflow for table"); + // always stop when we reach a table + break; + } + } +} + +/****************************************************************************************** + * Before reflow, intrinsic width calculation is done using GetMinWidth + * and GetPrefWidth. This used to be known as pass 1 reflow. + * + * After the intrinsic width calculation, the table determines the + * column widths using BalanceColumnWidths() and + * then reflows each child again with a constrained avail width. This reflow is referred to + * as the pass 2 reflow. + * + * A special height 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 height but no computed + * height or (b) in paginated mode, a table has a height. (a) supports percent nested tables + * contained inside cells whose heights 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 height 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 height but + * no computed height by calling CheckRequestSpecialHeightReflow(). This method calls + * RequestSpecialHeightReflow() which calls SetNeedSpecialReflow() on its ancestors until + * it reaches the containing table and calls SetNeedToInitiateSpecialReflow() on it. For + * percent height frames inside cells, during DidReflow(), the cell's NotifyPercentHeight() + * is called (the cell is the reflow state's mPercentHeightObserver in this case). + * NotifyPercentHeight() calls RequestSpecialHeightReflow(). + * + * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true) was called, it + * will do the special height reflow, setting the reflow state's mFlags.mSpecialHeightReflow + * to true and mSpecialHeightInitiator to itself. It won't do this if IsPrematureSpecialHeightReflow() + * returns true because in that case another special height 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 heights will not be known. + * + * 3) Since the heights of the table, row groups, rows, and cells was determined during the pass 2 + * reflow, they return their last desired sizes during the special height reflow. The reflow only + * permits percent height frames inside the cells to resize based on the cells height and that height + * 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 height reflow: + * + * 1) At some point there should be 2 kinds of special height 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 heights > 100%, there is data loss (see bug 115245). + * However, this can also occur if a cell has a fixed height and there is no special height reflow. + * + * XXXldb Special height 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 vertical resizing. + * + ******************************************************************************************/ + +/* Layout the entire inner table. */ +nsresult nsTableFrame::Reflow(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + DO_GLOBAL_REFLOW_COUNT("nsTableFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); + bool isPaginated = aPresContext->IsPaginated(); + + aStatus = NS_FRAME_COMPLETE; + if (!GetPrevInFlow() && !mTableLayoutStrategy) { + NS_ASSERTION(false, "strategy should have been created in Init"); + return NS_ERROR_NULL_POINTER; + } + nsresult rv = NS_OK; + + // see if collapsing borders need to be calculated + if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) { + CalcBCBorders(); + } + + aDesiredSize.Width() = aReflowState.AvailableWidth(); + + // Check for an overflow list, and append any row group frames being pushed + MoveOverflowToChildList(); + + bool haveDesiredHeight = 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 width. + if (NS_SUBTREE_DIRTY(this) || + aReflowState.ShouldReflowAllKids() || + IsGeometryDirty() || + aReflowState.mFlags.mVResize) { + + if (aReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE || + // Also check mVResize, to handle the first Reflow preceding a + // special height Reflow, when we've already had a special height + // Reflow (where mComputedHeight would not be + // NS_UNCONSTRAINEDSIZE, but without a style change in between). + aReflowState.mFlags.mVResize) { + // XXX Eventually, we should modify DistributeHeightToRows to use + // nsTableRowFrame::GetHeight instead of nsIFrame::GetSize().height. + // That way, it will make its calculations based on internal table + // frame heights as they are before they ever had any extra height + // distributed to them. In the meantime, this reflows all the + // internal table frames, which restores them to their state before + // DistributeHeightToRows was called. + SetGeometryDirty(); + } + + bool needToInitiateSpecialReflow = + !!(GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT); + // see if an extra reflow will be necessary in pagination mode when there is a specified table height + if (isPaginated && !GetPrevInFlow() && (NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight())) { + nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState); + if ((tableSpecifiedHeight > 0) && + (tableSpecifiedHeight != NS_UNCONSTRAINEDSIZE)) { + needToInitiateSpecialReflow = true; + } + } + nsIFrame* lastChildReflowed = nullptr; + + NS_ASSERTION(!aReflowState.mFlags.mSpecialHeightReflow, + "Shouldn't be in special height reflow here!"); + + // do the pass 2 reflow unless this is a special height reflow and we will be + // initiating a special height reflow + // XXXldb I changed this. Should I change it back? + + // if we need to initiate a special height reflow, then don't constrain the + // height of the reflow before that + nscoord availHeight = needToInitiateSpecialReflow + ? NS_UNCONSTRAINEDSIZE : aReflowState.AvailableHeight(); + + ReflowTable(aDesiredSize, aReflowState, availHeight, + lastChildReflowed, aStatus); + + // reevaluate special height reflow conditions + if (GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) + needToInitiateSpecialReflow = true; + + // XXXldb Are all these conditions correct? + if (needToInitiateSpecialReflow && NS_FRAME_IS_COMPLETE(aStatus)) { + // XXXldb Do we need to set the mVResize flag on any reflow states? + + nsHTMLReflowState &mutable_rs = + const_cast(aReflowState); + + // distribute extra vertical space to rows + CalcDesiredHeight(aReflowState, aDesiredSize); + mutable_rs.mFlags.mSpecialHeightReflow = true; + + ReflowTable(aDesiredSize, aReflowState, aReflowState.AvailableHeight(), + lastChildReflowed, aStatus); + + if (lastChildReflowed && NS_FRAME_IS_NOT_COMPLETE(aStatus)) { + // if there is an incomplete child, then set the desired height to include it but not the next one + nsMargin borderPadding = GetChildAreaOffset(&aReflowState); + aDesiredSize.Height() = borderPadding.bottom + GetCellSpacingY() + + lastChildReflowed->GetRect().YMost(); + } + haveDesiredHeight = true; + + mutable_rs.mFlags.mSpecialHeightReflow = false; + } + } + else { + // Calculate the overflow area contribution from our children. + for (nsIFrame* kid = GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) { + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid); + } + } + + aDesiredSize.Width() = aReflowState.ComputedWidth() + + aReflowState.ComputedPhysicalBorderPadding().LeftRight(); + if (!haveDesiredHeight) { + CalcDesiredHeight(aReflowState, aDesiredSize); + } + if (IsRowInserted()) { + ProcessRowInserted(aDesiredSize.Height()); + } + + nsMargin borderPadding = GetChildAreaOffset(&aReflowState); + SetColumnDimensions(aDesiredSize.Height(), borderPadding); + if (NeedToCollapse() && + (NS_UNCONSTRAINEDSIZE != aReflowState.AvailableWidth())) { + AdjustForCollapsingRowsCols(aDesiredSize, 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, aReflowState); + + // make sure the table overflow area does include the table rect. + nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()) ; + + if (!ShouldApplyOverflowClipping(this, aReflowState.mStyleDisplay)) { + // collapsed border may leak out + nsMargin bcMargin = GetExcludedOuterBCBorder(); + tableRect.Inflate(bcMargin); + } + aDesiredSize.mOverflowAreas.UnionAllWith(tableRect); + + if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) || + nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { + nsIFrame::InvalidateFrame(); + } + + FinishAndStoreOverflow(&aDesiredSize); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); + return rv; +} + +void +nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext, + nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState) +{ + auto positionedParts = + static_cast(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()); + nsHTMLReflowMetrics desiredSize(aReflowState.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. + nsHTMLReflowState reflowState(aPresContext, positionedPart, + aReflowState.rendContext, + nsSize(size.width, NS_UNCONSTRAINEDSIZE), + nsHTMLReflowState::DUMMY_PARENT_REFLOW_STATE); + nsReflowStatus reflowStatus = NS_FRAME_COMPLETE; + + // Reflow absolutely-positioned descendants of the positioned part. + // FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the height and + // ignoring any change to the reflow status aren't correct. We'll never + // paginate absolutely positioned frames. + overflowTracker.AddFrame(positionedPart, + OverflowChangedTracker::CHILDREN_AND_PARENT_CHANGED); + nsFrame* positionedFrame = static_cast(positionedPart); + positionedFrame->FinishReflowWithAbsoluteFrames(PresContext(), + desiredSize, + reflowState, + reflowStatus, + true); + } + + // 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::UpdateOverflow() +{ + nsRect bounds(nsPoint(0, 0), GetSize()); + + // 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())) { + nsMargin bcMargin = GetExcludedOuterBCBorder(); + bounds.Inflate(bcMargin); + } + + nsOverflowAreas overflowAreas(bounds, bounds); + nsLayoutUtils::UnionChildOverflow(this, overflowAreas); + + return FinishAndStoreOverflow(overflowAreas, GetSize()); +} + +nsresult +nsTableFrame::ReflowTable(nsHTMLReflowMetrics& aDesiredSize, + const nsHTMLReflowState& aReflowState, + nscoord aAvailHeight, + nsIFrame*& aLastChildReflowed, + nsReflowStatus& aStatus) +{ + nsresult rv = NS_OK; + aLastChildReflowed = nullptr; + + if (!GetPrevInFlow()) { + mTableLayoutStrategy->ComputeColumnWidths(aReflowState); + } + // Constrain our reflow width to the computed table width (of the 1st in flow). + // and our reflow height to our avail height minus border, padding, cellspacing + aDesiredSize.Width() = aReflowState.ComputedWidth() + + aReflowState.ComputedPhysicalBorderPadding().LeftRight(); + nsTableReflowState reflowState(*PresContext(), aReflowState, *this, + aDesiredSize.Width(), aAvailHeight); + ReflowChildren(reflowState, aStatus, aLastChildReflowed, + aDesiredSize.mOverflowAreas); + + ReflowColGroups(aReflowState.rendContext); + return rv; +} + +nsIFrame* +nsTableFrame::GetFirstBodyRowGroupFrame() +{ + nsIFrame* headerFrame = nullptr; + nsIFrame* footerFrame = nullptr; + + for (nsIFrame* kidFrame = mFrames.FirstChild(); nullptr != kidFrame; ) { + 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 (NS_STYLE_DISPLAY_TABLE_HEADER_GROUP == 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 (NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP == 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 (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == childDisplay->mDisplay) { + return kidFrame; + } + + // Get the next child + kidFrame = kidFrame->GetNextSibling(); + } + + return nullptr; +} + +// Table specific version that takes into account repeated header and footer +// frames when continuing table frames +void +nsTableFrame::PushChildren(const RowGroupArray& aRowGroups, + int32_t aPushFrom) +{ + NS_PRECONDITION(aPushFrom > 0, "pushing first child"); + + // extract the frames from the array into a sibling list + nsFrameList frames; + uint32_t childX; + for (childX = aPushFrom; childX < aRowGroups.Length(); ++childX) { + nsTableRowGroupFrame* rgFrame = aRowGroups[childX]; + if (!rgFrame->IsRepeatable()) { + mFrames.RemoveFrame(rgFrame); + frames.AppendFrame(nullptr, rgFrame); + } + } + + if (frames.IsEmpty()) { + return; + } + + nsTableFrame* nextInFlow = static_cast(GetNextInFlow()); + if (nextInFlow) { + // Insert the frames after any repeated header and footer frames. + nsIFrame* firstBodyFrame = nextInFlow->GetFirstBodyRowGroupFrame(); + nsIFrame* prevSibling = nullptr; + if (firstBodyFrame) { + prevSibling = firstBodyFrame->GetPrevSibling(); + } + // When pushing and pulling frames we need to check for whether any + // views need to be reparented. + ReparentFrameViewList(frames, this, nextInFlow); + nextInFlow->mFrames.InsertFrames(nextInFlow, prevSibling, + frames); + } + else { + // Add the frames to our overflow list. + SetOverflowFrames(frames); + } +} + +// collapsing row groups, rows, col groups and cols are accounted for after both passes of +// reflow so that it has no effect on the calculations of reflow. +void +nsTableFrame::AdjustForCollapsingRowsCols(nsHTMLReflowMetrics& aDesiredSize, + nsMargin aBorderPadding) +{ + nscoord yTotalOffset = 0; // total offset among all rows in all row groups + + // reset the bit, it will be set again if row/rowgroup or col/colgroup are + // collapsed + SetNeedToCollapse(false); + + // collapse the rows and/or row groups as necessary + // Get the ordered children + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + nsTableFrame* firstInFlow = static_cast(FirstInFlow()); + nscoord width = firstInFlow->GetCollapsedWidth(aBorderPadding); + nscoord rgWidth = width - 2 * GetCellSpacingX(); + 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"); + yTotalOffset += rgFrame->CollapseRowGroupIfNecessary(yTotalOffset, rgWidth); + ConsiderChildOverflow(overflow, rgFrame); + } + + aDesiredSize.Height() -= yTotalOffset; + aDesiredSize.Width() = width; + overflow.UnionAllWith(nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height())); + FinishAndStoreOverflow(overflow, + nsSize(aDesiredSize.Width(), aDesiredSize.Height())); +} + + +nscoord +nsTableFrame::GetCollapsedWidth(nsMargin aBorderPadding) +{ + NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedWidth called on next in flow"); + nscoord cellSpacingX = GetCellSpacingX(); + nscoord width = cellSpacingX; + width += aBorderPadding.left + aBorderPadding.right; + for (nsIFrame* groupFrame = mColGroups.FirstChild(); groupFrame; + groupFrame = groupFrame->GetNextSibling()) { + 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(); + int32_t colX = colFrame->GetColIndex(); + if (NS_STYLE_DISPLAY_TABLE_COLUMN == colDisplay->mDisplay) { + const nsStyleVisibility* colVis = colFrame->StyleVisibility(); + bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible); + int32_t colWidth = GetColumnWidth(colX); + if (!collapseGroup && !collapseCol) { + width += colWidth; + if (ColumnHasCellSpacingBefore(colX)) + width += cellSpacingX; + } + else { + SetNeedToCollapse(true); + } + } + } + } + return width; +} + +/* 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; + } + } +} + + + +nsresult +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 (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay) { + nsTableColGroupFrame* lastColGroup = + nsTableColGroupFrame::GetLastRealColGroup(this); + int32_t startColIndex = (lastColGroup) + ? lastColGroup->GetStartColumnIndex() + lastColGroup->GetColCount() : 0; + mColGroups.InsertFrame(nullptr, lastColGroup, f); + // Insert the colgroup and its cols into the table + InsertColGroups(startColIndex, + nsFrameList::Slice(mColGroups, f, f->GetNextSibling())); + } else if (IsRowGroup(display->mDisplay)) { + // 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(); + + return NS_OK; +} + +// Needs to be at file scope or ArrayLength fails to compile. +struct ChildListInsertions { + nsIFrame::ChildListID mID; + nsFrameList mList; +}; + +nsresult +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. + return AppendFrames(aListID, aFrameList); + } + + // 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 == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP) { + 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); + } + } + return NS_OK; +} + +void +nsTableFrame::HomogenousInsertFrames(ChildListID aListID, + nsIFrame* aPrevFrame, + nsFrameList& aFrameList) +{ + // See what kind of frame we have + const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay(); +#ifdef DEBUG + // Verify that either all siblings have display:table-column-group, or they + // all have display values different from table-column-group. + for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { + const nsStyleDisplay* nextDisplay = e.get()->StyleDisplay(); + MOZ_ASSERT((display->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP) == + (nextDisplay->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP), + "heterogenous childlist"); + } +#endif + if (aPrevFrame) { + const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay(); + // Make sure they belong on the same frame list + if ((display->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP) != + (prevDisplay->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP)) { + // 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; + aPrevFrame = nullptr; + while (pseudoFrame && (parentContent == + (content = pseudoFrame->GetContent()))) { + pseudoFrame = pseudoFrame->GetFirstPrincipalChild(); + } + nsCOMPtr container = content->GetParent(); + if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823. + int32_t newIndex = container->IndexOf(content); + nsIFrame* kidFrame; + bool isColGroup = (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == + display->mDisplay); + nsTableColGroupFrame* lastColGroup; + 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->GetFirstPrincipalChild(); + } + int32_t index = container->IndexOf(content); + if (index > lastIndex && index < newIndex) { + lastIndex = index; + aPrevFrame = kidFrame; + } + kidFrame = kidFrame->GetNextSibling(); + } + } + } + } + if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay) { + NS_ASSERTION(aListID == kColGroupList, "unexpected child list"); + // Insert the column group frames + const nsFrameList::Slice& newColgroups = + mColGroups.InsertFrames(nullptr, 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"); + // 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 colX; + for (colX = lastColIndex; colX >= firstColIndex; colX--) { + nsTableColFrame* colFrame = mColFrames.SafeElementAt(colX); + if (colFrame) { + RemoveCol(colGroup, colX, true, false); + } + } + + int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length(); + if (numAnonymousColsToAdd > 0) { + // this sets the child list, updates the col cache and cell map + AppendAnonymousColFrames(numAnonymousColsToAdd); + } + + } else { + NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); + nsTableRowGroupFrame* rgFrame = + static_cast(aOldFrame); + // remove the row group from the cell map + nsTableCellMap* cellMap = GetCellMap(); + if (cellMap) { + cellMap->RemoveGroupCellMap(rgFrame); + } + + // remove the row group frame from the sibling chain + mFrames.DestroyFrame(aOldFrame); + + // the removal of a row group changes the cellmap, the columns might change + if (cellMap) { + cellMap->Synchronize(this); + // Create an empty slice + ResetRowIndices(nsFrameList::Slice(mFrames, nullptr, nullptr)); + nsIntRect damageArea; + cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false, damageArea); + + static_cast(FirstInFlow())->MatchCellMapToColCache(cellMap); + } + } +} + +nsresult +nsTableFrame::RemoveFrame(ChildListID aListID, + nsIFrame* aOldFrame) +{ + NS_ASSERTION(aListID == kColGroupList || + NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP != + aOldFrame->StyleDisplay()->mDisplay, + "Wrong list name; use kColGroupList iff colgroup"); + nsIPresShell* shell = PresContext()->PresShell(); + nsTableFrame* lastParent = nullptr; + while (aOldFrame) { + nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation(); + nsTableFrame* parent = static_cast(aOldFrame->GetParent()); + if (parent != lastParent) { + parent->DrainSelfOverflowList(); + } + parent->DoRemoveFrame(aListID, aOldFrame); + aOldFrame = oldFrameNextContinuation; + if (parent != lastParent) { + // for now, just bail and recalc all of the collapsing borders + // as the cellmap changes we need to recalc + if (parent->IsBorderCollapse()) { + parent->SetFullBCDamageArea(); + } + parent->SetGeometryDirty(); + shell->FrameNeedsReflow(parent, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + lastParent = parent; + } + } +#ifdef DEBUG_TABLE_CELLMAP + printf("=== TableFrame::RemoveFrame\n"); + Dump(true, true, true); +#endif + return NS_OK; +} + +/* virtual */ nsMargin +nsTableFrame::GetUsedBorder() const +{ + if (!IsBorderCollapse()) + return nsContainerFrame::GetUsedBorder(); + + return GetIncludedOuterBCBorder(); +} + +/* 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 outer table frame via + // the ::-moz-table-outer rule in ua.css. + return nsMargin(0, 0, 0, 0); +} + +// Destructor function for BCPropertyData properties +static void +DestroyBCProperty(void* aPropertyValue) +{ + delete static_cast(aPropertyValue); +} + +NS_DECLARE_FRAME_PROPERTY(TableBCProperty, DestroyBCProperty) + +BCPropertyData* +nsTableFrame::GetBCProperty(bool aCreateIfNecessary) const +{ + FrameProperties props = Properties(); + BCPropertyData* value = static_cast + (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; +} + +nsMargin +nsTableFrame::GetOuterBCBorder() const +{ + if (NeedToCalcBCBorders()) + const_cast(this)->CalcBCBorders(); + + nsMargin border(0, 0, 0, 0); + int32_t p2t = nsPresContext::AppUnitsPerCSSPixel(); + BCPropertyData* propData = GetBCProperty(); + if (propData) { + border.top = BC_BORDER_TOP_HALF_COORD(p2t, propData->mTopBorderWidth); + border.right = BC_BORDER_RIGHT_HALF_COORD(p2t, propData->mRightBorderWidth); + border.bottom = BC_BORDER_BOTTOM_HALF_COORD(p2t, propData->mBottomBorderWidth); + border.left = BC_BORDER_LEFT_HALF_COORD(p2t, propData->mLeftBorderWidth); + } + return border; +} + +nsMargin +nsTableFrame::GetIncludedOuterBCBorder() const +{ + if (NeedToCalcBCBorders()) + const_cast(this)->CalcBCBorders(); + + nsMargin border(0, 0, 0, 0); + int32_t p2t = nsPresContext::AppUnitsPerCSSPixel(); + BCPropertyData* propData = GetBCProperty(); + if (propData) { + border.top += BC_BORDER_TOP_HALF_COORD(p2t, propData->mTopBorderWidth); + border.right += BC_BORDER_RIGHT_HALF_COORD(p2t, propData->mRightCellBorderWidth); + border.bottom += BC_BORDER_BOTTOM_HALF_COORD(p2t, propData->mBottomBorderWidth); + border.left += BC_BORDER_LEFT_HALF_COORD(p2t, propData->mLeftCellBorderWidth); + } + return border; +} + +nsMargin +nsTableFrame::GetExcludedOuterBCBorder() const +{ + return GetOuterBCBorder() - GetIncludedOuterBCBorder(); +} + +static +void GetSeparateModelBorderPadding(const nsHTMLReflowState* aReflowState, + nsStyleContext& aStyleContext, + nsMargin& aBorderPadding) +{ + // 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(); + aBorderPadding = border->GetComputedBorder(); + if (aReflowState) { + aBorderPadding += aReflowState->ComputedPhysicalPadding(); + } +} + +nsMargin +nsTableFrame::GetChildAreaOffset(const nsHTMLReflowState* aReflowState) const +{ + nsMargin offset(0,0,0,0); + if (IsBorderCollapse()) { + offset = GetIncludedOuterBCBorder(); + } + else { + GetSeparateModelBorderPadding(aReflowState, *mStyleContext, offset); + } + return offset; +} + +void +nsTableFrame::InitChildReflowState(nsHTMLReflowState& aReflowState) +{ + nsMargin collapseBorder; + nsMargin padding(0,0,0,0); + nsMargin* pCollapseBorder = nullptr; + nsPresContext* presContext = PresContext(); + if (IsBorderCollapse()) { + nsTableRowGroupFrame* rgFrame = + static_cast(aReflowState.frame); + pCollapseBorder = rgFrame->GetBCBorderWidth(collapseBorder); + } + aReflowState.Init(presContext, -1, -1, pCollapseBorder, &padding); + + NS_ASSERTION(!mBits.mResizedColumns || + !aReflowState.parentReflowState->mFlags.mSpecialHeightReflow, + "should not resize columns on special height reflow"); + if (mBits.mResizedColumns) { + aReflowState.mFlags.mHResize = 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(nsTableReflowState& aReflowState, + nsIFrame* aKidFrame, + nsHTMLReflowMetrics& aKidDesiredSize, + const nsRect& aOriginalKidRect, + const nsRect& aOriginalKidVisualOverflow) +{ + bool isFirstReflow = + (aKidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0; + + // Place and size the child + FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, nullptr, + aReflowState.x, aReflowState.y, 0); + + InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow, + isFirstReflow); + + // Adjust the running y-offset + aReflowState.y += aKidDesiredSize.Height(); + + // If our height is constrained, then update the available height + if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { + aReflowState.availSize.height -= aKidDesiredSize.Height(); + } +} + +void +nsTableFrame::OrderRowGroups(RowGroupArray& aChildren, + nsTableRowGroupFrame** aHead, + nsTableRowGroupFrame** aFoot) const +{ + aChildren.Clear(); + nsTableRowGroupFrame* head = nullptr; + nsTableRowGroupFrame* foot = nullptr; + + nsIFrame* kidFrame = mFrames.FirstChild(); + while (kidFrame) { + const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay(); + nsTableRowGroupFrame* rowGroup = + static_cast(kidFrame); + + switch (kidDisplay->mDisplay) { + case NS_STYLE_DISPLAY_TABLE_HEADER_GROUP: + if (head) { // treat additional thead like tbody + aChildren.AppendElement(rowGroup); + } + else { + head = rowGroup; + } + break; + case NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP: + if (foot) { // treat additional tfoot like tbody + aChildren.AppendElement(rowGroup); + } + else { + foot = rowGroup; + } + break; + case NS_STYLE_DISPLAY_TABLE_ROW_GROUP: + 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 == + NS_STYLE_DISPLAY_TABLE_HEADER_GROUP) { + return static_cast(kidFrame); + } + + // Get the next sibling but skip it if it's also the next-in-flow, since + // a next-in-flow will not be part of the current table. + while (kidFrame) { + nsIFrame* nif = kidFrame->GetNextInFlow(); + kidFrame = kidFrame->GetNextSibling(); + if (kidFrame != nif) + break; + } + } + + return nullptr; +} + +nsTableRowGroupFrame* +nsTableFrame::GetTFoot() const +{ + nsIFrame* kidFrame = mFrames.FirstChild(); + while (kidFrame) { + if (kidFrame->StyleDisplay()->mDisplay == + NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP) { + return static_cast(kidFrame); + } + + // Get the next sibling but skip it if it's also the next-in-flow, since + // a next-in-flow will not be part of the current table. + while (kidFrame) { + nsIFrame* nif = kidFrame->GetNextInFlow(); + kidFrame = kidFrame->GetNextSibling(); + if (kidFrame != nif) + break; + } + } + + return nullptr; +} + +static bool +IsRepeatable(nscoord aFrameHeight, nscoord aPageHeight) +{ + return aFrameHeight < (aPageHeight / 4); +} + +nsresult +nsTableFrame::SetupHeaderFooterChild(const nsTableReflowState& aReflowState, + nsTableRowGroupFrame* aFrame, + nscoord* aDesiredHeight) +{ + nsPresContext* presContext = PresContext(); + nscoord pageHeight = presContext->GetPageSize().height; + + // Reflow the child with unconstrainted height + nsHTMLReflowState kidReflowState(presContext, aReflowState.reflowState, + aFrame, + nsSize(aReflowState.availSize.width, NS_UNCONSTRAINEDSIZE), + -1, -1, nsHTMLReflowState::CALLER_WILL_INIT); + InitChildReflowState(kidReflowState); + kidReflowState.mFlags.mIsTopOfPage = true; + nsHTMLReflowMetrics desiredSize(aReflowState.reflowState); + desiredSize.Width() = desiredSize.Height() = 0; + nsReflowStatus status; + nsresult rv = ReflowChild(aFrame, presContext, desiredSize, kidReflowState, + aReflowState.x, aReflowState.y, 0, status); + NS_ENSURE_SUCCESS(rv, rv); + // 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(nsTableReflowState& aReflowState, + nsTableRowGroupFrame *aTfoot, + nscoord aFooterHeight) +{ + nsPresContext* presContext = PresContext(); + nsSize kidAvailSize(aReflowState.availSize); + kidAvailSize.height = aFooterHeight; + nsHTMLReflowState footerReflowState(presContext, + aReflowState.reflowState, + aTfoot, kidAvailSize, + -1, -1, + nsHTMLReflowState::CALLER_WILL_INIT); + InitChildReflowState(footerReflowState); + aReflowState.y += GetCellSpacingY(); + + nsRect origTfootRect = aTfoot->GetRect(); + nsRect origTfootVisualOverflow = aTfoot->GetVisualOverflowRect(); + + nsReflowStatus footerStatus; + nsHTMLReflowMetrics desiredSize(aReflowState.reflowState); + desiredSize.Width() = desiredSize.Height() = 0; + ReflowChild(aTfoot, presContext, desiredSize, footerReflowState, + aReflowState.x, aReflowState.y, 0, footerStatus); + PlaceChild(aReflowState, aTfoot, desiredSize, origTfootRect, + origTfootVisualOverflow); +} + +// Reflow the children based on the avail size and reason in aReflowState +// update aReflowMetrics a aStatus +nsresult +nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, + nsReflowStatus& aStatus, + nsIFrame*& aLastChildReflowed, + nsOverflowAreas& aOverflowAreas) +{ + aStatus = NS_FRAME_COMPLETE; + aLastChildReflowed = nullptr; + + nsIFrame* prevKidFrame = nullptr; + nsresult rv = NS_OK; + nscoord cellSpacingY = GetCellSpacingY(); + + 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 != aReflowState.availSize.height && + aReflowState.reflowState.mFlags.mTableIsSplittable; + + aOverflowAreas.Clear(); + + bool reflowAllKids = aReflowState.reflowState.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; + rv = SetupHeaderFooterChild(aReflowState, thead, &desiredHeight); + if (NS_FAILED(rv)) + return rv; + } + if (tfoot) { + rv = SetupHeaderFooterChild(aReflowState, tfoot, &footerHeight); + if (NS_FAILED(rv)) + return rv; + } + } + // if the child is a tbody in paginated mode reduce the height by a repeated footer + bool allowRepeatedFooter = false; + for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) { + nsIFrame* kidFrame = rowGroups[childX]; + // Get the frame state bits + // See if we should only reflow the dirty child frames + if (reflowAllKids || + NS_SUBTREE_DIRTY(kidFrame) || + (aReflowState.reflowState.mFlags.mSpecialHeightReflow && + (isPaginated || (kidFrame->GetStateBits() & + NS_FRAME_CONTAINS_RELATIVE_HEIGHT)))) { + if (pageBreak) { + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); + } + else if (tfoot && tfoot->IsRepeatable()) { + tfoot->SetRepeatable(false); + } + PushChildren(rowGroups, childX); + aStatus = NS_FRAME_NOT_COMPLETE; + break; + } + + nsSize kidAvailSize(aReflowState.availSize); + allowRepeatedFooter = false; + if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.height)) { + nsTableRowGroupFrame* kidRG = + static_cast(kidFrame); + if (kidRG != thead && kidRG != tfoot && tfoot && tfoot->IsRepeatable()) { + // the child is a tbody and there is a repeatable footer + NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1], "Missing footer!"); + if (footerHeight + cellSpacingY < kidAvailSize.height) { + allowRepeatedFooter = true; + kidAvailSize.height -= footerHeight + cellSpacingY; + } + } + } + + nsRect oldKidRect = kidFrame->GetRect(); + nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect(); + + nsHTMLReflowMetrics desiredSize(aReflowState.reflowState); + desiredSize.Width() = desiredSize.Height() = 0; + + // Reflow the child into the available space + nsHTMLReflowState kidReflowState(presContext, aReflowState.reflowState, + kidFrame, kidAvailSize, + -1, -1, + nsHTMLReflowState::CALLER_WILL_INIT); + InitChildReflowState(kidReflowState); + + // 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]->GetRect().YMost() > 0)) { + kidReflowState.mFlags.mIsTopOfPage = false; + } + aReflowState.y += cellSpacingY; + if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { + aReflowState.availSize.height -= cellSpacingY; + } + // 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; + + rv = ReflowChild(kidFrame, presContext, desiredSize, kidReflowState, + aReflowState.x, aReflowState.y, 0, aStatus); + + 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(aReflowState.reflowState)) { + 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 != kidReflowState.AvailableHeight()) && + kidReflowState.AvailableHeight() < desiredSize.Height()))) { + if (ShouldAvoidBreakInside(aReflowState.reflowState)) { + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); + break; + } + // if we are on top of the page place with dataloss + if (kidReflowState.mFlags.mIsTopOfPage) { + if (childX+1 < rowGroups.Length()) { + nsIFrame* nextRowGroupFrame = rowGroups[childX + 1]; + if (nextRowGroupFrame) { + PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, + oldKidVisualOverflow); + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowState, 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(aReflowState, 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(aReflowState, kidFrame, desiredSize, oldKidRect, + oldKidVisualOverflow); + aLastChildReflowed = kidFrame; + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowState, 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 != kidReflowState.AvailableHeight())) { + nsIFrame* nextKid = + (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr; + pageBreak = PageBreakAfter(kidFrame, nextKid); + } + + // Place the child + PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, + oldKidVisualOverflow); + + // Remember where we just were in case we end up pushing children + prevKidFrame = kidFrame; + + // Special handling for incomplete children + if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { + nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow(); + if (!kidNextInFlow) { + // The child doesn't have a next-in-flow so create a continuing + // frame. This hooks the child into the flow + kidNextInFlow = presContext->PresShell()->FrameConstructor()-> + CreateContinuingFrame(presContext, kidFrame, this); + + // Insert the kid's new next-in-flow into our sibling list... + mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow); + // and in rowGroups after childX so that it will get pushed below. + rowGroups.InsertElementAt(childX + 1, + static_cast(kidNextInFlow)); + } else if (kidNextInFlow == kidFrame->GetNextSibling()) { + // OrderRowGroups excludes NIFs in the child list from 'rowGroups' + // so we deal with that here to make sure they get pushed. + MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow), + "OrderRowGroups must not put our NIF in 'rowGroups'"); + rowGroups.InsertElementAt(childX + 1, + static_cast(kidNextInFlow)); + } + + // We've used up all of our available space so push the remaining + // children. + if (allowRepeatedFooter) { + PlaceRepeatedFooter(aReflowState, 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 + aReflowState.y += cellSpacingY; + nsRect kidRect = kidFrame->GetRect(); + if (kidRect.y != aReflowState.y) { + // invalidate the old position + kidFrame->InvalidateFrameSubtree(); + kidRect.y = aReflowState.y; + kidFrame->SetRect(kidRect); // move to the new position + RePositionViews(kidFrame); + // invalidate the new position + kidFrame->InvalidateFrameSubtree(); + } + aReflowState.y += kidRect.height; + + // If our height is constrained then update the available height. + if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { + aReflowState.availSize.height -= cellSpacingY + kidRect.height; + } + } + ConsiderChildOverflow(aOverflowAreas, kidFrame); + } + + // We've now propagated the column resizes and geometry changes to all + // the children. + mBits.mResizedColumns = false; + ClearGeometryDirty(); + + return rv; +} + +void +nsTableFrame::ReflowColGroups(nsRenderingContext *aRenderingContext) +{ + if (!GetPrevInFlow() && !HaveReflowedColGroups()) { + nsHTMLReflowMetrics kidMet(GetWritingMode()); + nsPresContext *presContext = PresContext(); + for (nsIFrame* kidFrame = mColGroups.FirstChild(); kidFrame; + kidFrame = kidFrame->GetNextSibling()) { + if (NS_SUBTREE_DIRTY(kidFrame)) { + // The column groups don't care about dimensions or reflow states. + nsHTMLReflowState kidReflowState(presContext, kidFrame, + aRenderingContext, nsSize(0,0)); + nsReflowStatus cgStatus; + ReflowChild(kidFrame, presContext, kidMet, kidReflowState, 0, 0, 0, + cgStatus); + FinishReflowChild(kidFrame, presContext, kidMet, nullptr, 0, 0, 0); + } + } + SetHaveReflowedColGroups(true); + } +} + +void +nsTableFrame::CalcDesiredHeight(const nsHTMLReflowState& aReflowState, nsHTMLReflowMetrics& aDesiredSize) +{ + nsTableCellMap* cellMap = GetCellMap(); + if (!cellMap) { + NS_ASSERTION(false, "never ever call me until the cell map is built!"); + aDesiredSize.Height() = 0; + return; + } + nscoord cellSpacingY = GetCellSpacingY(); + nsMargin borderPadding = GetChildAreaOffset(&aReflowState); + + // get the natural height 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 tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState); + if ((NS_UNCONSTRAINEDSIZE != tableSpecifiedHeight) && + (tableSpecifiedHeight > 0) && + eCompatibility_NavQuirks != PresContext()->CompatibilityMode()) { + // empty tables should not have a size in quirks mode + aDesiredSize.Height() = tableSpecifiedHeight; + } + else + aDesiredSize.Height() = 0; + return; + } + int32_t rowCount = cellMap->GetRowCount(); + int32_t colCount = cellMap->GetColCount(); + nscoord desiredHeight = borderPadding.top + borderPadding.bottom; + if (rowCount > 0 && colCount > 0) { + desiredHeight += cellSpacingY; + for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { + desiredHeight += rowGroups[rgX]->GetSize().height + cellSpacingY; + } + } + + // see if a specified table height requires dividing additional space to rows + if (!GetPrevInFlow()) { + nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState); + if ((tableSpecifiedHeight > 0) && + (tableSpecifiedHeight != NS_UNCONSTRAINEDSIZE) && + (tableSpecifiedHeight > desiredHeight)) { + // proportionately distribute the excess height to unconstrained rows in each + // unconstrained row group. + DistributeHeightToRows(aReflowState, tableSpecifiedHeight - desiredHeight); + // this might have changed the overflow area incorporate the childframe overflow area. + for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame; kidFrame = kidFrame->GetNextSibling()) { + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); + } + desiredHeight = tableSpecifiedHeight; + } + } + aDesiredSize.Height() = desiredHeight; +} + +static +void ResizeCells(nsTableFrame& aTableFrame) +{ + nsTableFrame::RowGroupArray rowGroups; + aTableFrame.OrderRowGroups(rowGroups); + nsHTMLReflowMetrics tableDesiredSize(aTableFrame.GetWritingMode()); // ??? + nsRect tableRect = aTableFrame.GetRect(); + tableDesiredSize.Width() = tableRect.width; + tableDesiredSize.Height() = tableRect.height; + tableDesiredSize.SetOverflowAreasToDesiredBounds(); + + for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + + nsRect rowGroupRect = rgFrame->GetRect(); + nsHTMLReflowMetrics groupDesiredSize(tableDesiredSize.GetWritingMode()); + groupDesiredSize.Width() = rowGroupRect.width; + groupDesiredSize.Height() = rowGroupRect.height; + 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::DistributeHeightToRows(const nsHTMLReflowState& aReflowState, + nscoord aAmount) +{ + nscoord cellSpacingY = GetCellSpacingY(); + + nsMargin borderPadding = GetChildAreaOffset(&aReflowState); + + RowGroupArray rowGroups; + OrderRowGroups(rowGroups); + + nscoord amountUsed = 0; + // distribute space to each pct height row whose row group doesn't have a computed + // height, and base the pct on the table height. If the row group had a computed + // height, then this was already done in nsTableRowGroupFrame::CalculateRowHeights + nscoord pctBasis = aReflowState.ComputedHeight() - (GetCellSpacingY() * (GetRowCount() + 1)); + nscoord yOriginRG = borderPadding.top + GetCellSpacingY(); + nscoord yEndRG = yOriginRG; + uint32_t rgX; + for (rgX = 0; rgX < rowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + nscoord amountUsedByRG = 0; + nscoord yOriginRow = 0; + nsRect rgRect = rgFrame->GetRect(); + if (!rgFrame->HasStyleHeight()) { + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + nsRect rowRect = rowFrame->GetRect(); + if ((amountUsed < aAmount) && rowFrame->HasPctHeight()) { + nscoord pctHeight = rowFrame->GetHeight(pctBasis); + nscoord amountForRow = std::min(aAmount - amountUsed, pctHeight - rowRect.height); + if (amountForRow > 0) { + nsRect oldRowRect = rowRect; + rowRect.height += amountForRow; + // XXXbz we don't need to change rowRect.y to be yOriginRow? + rowFrame->SetRect(rowRect); + yOriginRow += rowRect.height + cellSpacingY; + yEndRG += rowRect.height + cellSpacingY; + amountUsed += amountForRow; + amountUsedByRG += amountForRow; + //rowFrame->DidResize(); + nsTableFrame::RePositionViews(rowFrame); + + rgFrame->InvalidateFrameWithRect(oldRowRect); + rgFrame->InvalidateFrame(); + } + } + else { + if (amountUsed > 0 && yOriginRow != rowRect.y && + !(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { + rowFrame->InvalidateFrameSubtree(); + rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow)); + nsTableFrame::RePositionViews(rowFrame); + rowFrame->InvalidateFrameSubtree(); + } + yOriginRow += rowRect.height + cellSpacingY; + yEndRG += rowRect.height + cellSpacingY; + } + rowFrame = rowFrame->GetNextRow(); + } + if (amountUsed > 0) { + if (rgRect.y != yOriginRG) { + rgFrame->InvalidateFrameSubtree(); + } + + nsRect origRgRect = rgRect; + nsRect origRgVisualOverflow = rgFrame->GetVisualOverflowRect(); + + rgRect.y = yOriginRG; + rgRect.height += amountUsedByRG; + + rgFrame->SetRect(rgRect); + + nsTableFrame::InvalidateTableFrame(rgFrame, origRgRect, + origRgVisualOverflow, false); + } + } + else if (amountUsed > 0 && yOriginRG != rgRect.y) { + rgFrame->InvalidateFrameSubtree(); + rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG)); + // Make sure child views are properly positioned + nsTableFrame::RePositionViews(rgFrame); + rgFrame->InvalidateFrameSubtree(); + } + yOriginRG = yEndRG; + } + + if (amountUsed >= aAmount) { + ResizeCells(*this); + return; + } + + // get the first row without a style height where its row group has an + // unconstrained height + nsTableRowGroupFrame* firstUnStyledRG = nullptr; + nsTableRowFrame* firstUnStyledRow = nullptr; + for (rgX = 0; rgX < rowGroups.Length() && !firstUnStyledRG; rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + if (!rgFrame->HasStyleHeight()) { + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + if (!rowFrame->HasStyleHeight()) { + firstUnStyledRG = rgFrame; + firstUnStyledRow = rowFrame; + break; + } + rowFrame = rowFrame->GetNextRow(); + } + } + } + + nsTableRowFrame* lastEligibleRow = nullptr; + // Accumulate the correct divisor. This will be the total total height 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 + // height, 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 (rgX = 0; rgX < rowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + if (!firstUnStyledRG || !rgFrame->HasStyleHeight()) { + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + if (!firstUnStyledRG || !rowFrame->HasStyleHeight()) { + NS_ASSERTION(rowFrame->GetSize().height >= 0, + "negative row frame height"); + divisor += rowFrame->GetSize().height; + eligibleRows++; + lastEligibleRow = rowFrame; + } + rowFrame = rowFrame->GetNextRow(); + } + } + } + if (divisor <= 0) { + if (eligibleRows > 0) { + expandEmptyRows = true; + } + else { + NS_ERROR("invalid divisor"); + return; + } + } + } + // allocate the extra height to the unstyled row groups and rows + nscoord heightToDistribute = aAmount - amountUsed; + yOriginRG = borderPadding.top + cellSpacingY; + yEndRG = yOriginRG; + for (rgX = 0; rgX < rowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + nscoord amountUsedByRG = 0; + nscoord yOriginRow = 0; + nsRect rgRect = rgFrame->GetRect(); + nsRect rgVisualOverflow = rgFrame->GetVisualOverflowRect(); + // see if there is an eligible row group or we distribute to all rows + if (!firstUnStyledRG || !rgFrame->HasStyleHeight() || !eligibleRows) { + nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); + while (rowFrame) { + nsRect rowRect = rowFrame->GetRect(); + nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect(); + // see if there is an eligible row or we distribute to all rows + if (!firstUnStyledRow || !rowFrame->HasStyleHeight() || !eligibleRows) { + float ratio; + if (eligibleRows) { + if (!expandEmptyRows) { + // The amount of additional space each row gets is proportional to + // its height + ratio = float(rowRect.height) / 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)(heightToDistribute)) * ratio); + amountForRow = std::min(amountForRow, aAmount - amountUsed); + + if (yOriginRow != rowRect.y) { + rowFrame->InvalidateFrameSubtree(); + } + + // update the row height + nsRect newRowRect(rowRect.x, yOriginRow, rowRect.width, + rowRect.height + amountForRow); + rowFrame->SetRect(newRowRect); + + yOriginRow += newRowRect.height + cellSpacingY; + yEndRG += newRowRect.height + cellSpacingY; + + amountUsed += amountForRow; + amountUsedByRG += amountForRow; + NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation"); + //rowFrame->DidResize(); + nsTableFrame::RePositionViews(rowFrame); + + nsTableFrame::InvalidateTableFrame(rowFrame, rowRect, rowVisualOverflow, + false); + } + else { + if (amountUsed > 0 && yOriginRow != rowRect.y) { + rowFrame->InvalidateFrameSubtree(); + rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow)); + nsTableFrame::RePositionViews(rowFrame); + rowFrame->InvalidateFrameSubtree(); + } + yOriginRow += rowRect.height + cellSpacingY; + yEndRG += rowRect.height + cellSpacingY; + } + rowFrame = rowFrame->GetNextRow(); + } + if (amountUsed > 0) { + if (rgRect.y != yOriginRG) { + rgFrame->InvalidateFrameSubtree(); + } + + rgFrame->SetRect(nsRect(rgRect.x, yOriginRG, rgRect.width, + rgRect.height + amountUsedByRG)); + + nsTableFrame::InvalidateTableFrame(rgFrame, rgRect, rgVisualOverflow, + false); + } + // Make sure child views are properly positioned + } + else if (amountUsed > 0 && yOriginRG != rgRect.y) { + rgFrame->InvalidateFrameSubtree(); + rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG)); + // Make sure child views are properly positioned + nsTableFrame::RePositionViews(rgFrame); + rgFrame->InvalidateFrameSubtree(); + } + yOriginRG = yEndRG; + } + + ResizeCells(*this); +} + +int32_t nsTableFrame::GetColumnWidth(int32_t aColIndex) +{ + nsTableFrame* firstInFlow = static_cast(FirstInFlow()); + if (this == firstInFlow) { + nsTableColFrame* colFrame = GetColFrame(aColIndex); + return colFrame ? colFrame->GetFinalWidth() : 0; + } + return firstInFlow->GetColumnWidth(aColIndex); +} + +// XXX: could cache this. But be sure to check style changes if you do! +nscoord nsTableFrame::GetCellSpacingX() +{ + if (IsBorderCollapse()) + return 0; + + return StyleTableBorder()->mBorderSpacingX; +} + +// XXX: could cache this. But be sure to check style changes if you do! +nscoord nsTableFrame::GetCellSpacingY() +{ + if (IsBorderCollapse()) + return 0; + + return StyleTableBorder()->mBorderSpacingY; +} + + +/* virtual */ nscoord +nsTableFrame::GetBaseline() const +{ + nscoord ascent = 0; + RowGroupArray orderedRowGroups; + OrderRowGroups(orderedRowGroups); + nsTableRowFrame* firstRow = nullptr; + for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { + nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; + if (rgFrame->GetRowCount()) { + firstRow = rgFrame->GetFirstRow(); + ascent = rgFrame->GetRect().y + firstRow->GetRect().y + firstRow->GetRowBaseline(); + break; + } + } + if (!firstRow) + ascent = GetRect().height; + return ascent; +} +/* ----- global methods ----- */ + +nsIFrame* +NS_NewTableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTableFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame) + +nsTableFrame* +nsTableFrame::GetTableFrame(nsIFrame* aFrame) +{ + for (nsIFrame* ancestor = aFrame->GetParent(); ancestor; + ancestor = ancestor->GetParent()) { + if (nsGkAtoms::tableFrame == ancestor->GetType()) { + return static_cast(ancestor); + } + } + NS_RUNTIMEABORT("unable to find table parent"); + return nullptr; +} + +nsTableFrame* +nsTableFrame::GetTableFramePassingThrough(nsIFrame* aMustPassThrough, + nsIFrame* aFrame) +{ + MOZ_ASSERT(aMustPassThrough == aFrame || + nsLayoutUtils::IsProperAncestorFrame(aMustPassThrough, aFrame), + "aMustPassThrough should be an ancestor"); + + // Retrieve the table frame, and ensure that we hit aMustPassThrough on the + // way. If we don't, just return null. + bool hitPassThroughFrame = false; + nsTableFrame* tableFrame = nullptr; + for (nsIFrame* ancestor = aFrame; ancestor; ancestor = ancestor->GetParent()) { + if (ancestor == aMustPassThrough) { + hitPassThroughFrame = true; + } + if (nsGkAtoms::tableFrame == ancestor->GetType()) { + tableFrame = static_cast(ancestor); + break; + } + } + + MOZ_ASSERT(tableFrame, "Should have a table frame here"); + return hitPassThroughFrame ? tableFrame : nullptr; +} + +bool +nsTableFrame::IsAutoHeight() +{ + const nsStyleCoord &height = StylePosition()->mHeight; + // Don't consider calc() here like this quirk for percent. + return height.GetUnit() == eStyleUnit_Auto || + (height.GetUnit() == eStyleUnit_Percent && + height.GetPercentValue() <= 0.0f); +} + +nscoord +nsTableFrame::CalcBorderBoxHeight(const nsHTMLReflowState& aState) +{ + nscoord height = aState.ComputedHeight(); + if (NS_AUTOHEIGHT != height) { + nsMargin borderPadding = GetChildAreaOffset(&aState); + height += borderPadding.top + borderPadding.bottom; + } + height = std::max(0, height); + + return height; +} + +bool +nsTableFrame::IsAutoLayout() +{ + if (StyleTable()->mLayoutStrategy == NS_STYLE_TABLE_LAYOUT_AUTO) + return true; + // a fixed-layout inline-table must have a width + // and tables with 'width: -moz-max-content' must be auto-layout + // (at least as long as FixedTableLayoutStrategy::GetPrefWidth returns + // nscoord_MAX) + const nsStyleCoord &width = StylePosition()->mWidth; + return (width.GetUnit() == eStyleUnit_Auto) || + (width.GetUnit() == eStyleUnit_Enumerated && + width.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->GetFirstPrincipalChild(); + 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; + + nsIFrame* cFrame = aKidFrame->GetFirstPrincipalChild(); + while (cFrame) { + nsTableRowFrame *rowFrame = do_QueryFrame(cFrame); + if (rowFrame) { + printf("row(%d)=%p ", rowFrame->GetRowIndex(), + static_cast(rowFrame)); + nsIFrame* childFrame = cFrame->GetFirstPrincipalChild(); + while (childFrame) { + nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); + if (cellFrame) { + int32_t colIndex; + cellFrame->GetColIndex(colIndex); + printf("cell(%d)=%p ", colIndex, static_cast(childFrame)); + } + childFrame = childFrame->GetNextSibling(); + } + printf("\n"); + } + else { + DumpRowGroup(rowFrame); + } + cFrame = cFrame->GetNextSibling(); + } +} + +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 colX; + for (colX = 0; colX < numCols; colX++) { + printf("%d ", GetColumnWidth(colX)); + } + 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 (colX = 0; colX < numCols; colX++) { + nsTableColFrame* colFrame = mColFrames.ElementAt(colX); + if (0 == (colX % 8)) { + printf("\n"); + } + printf ("%d=%p ", colX, static_cast(colFrame)); + nsTableColType colType = colFrame->GetColType(); + switch (colType) { + case eColContent: + printf(" content "); + break; + case eColAnonymousCol: + printf(" anonymous-column "); + break; + case eColAnonymousColGroup: + printf(" anonymous-colgroup "); + break; + case eColAnonymousCell: + printf(" anonymous-cell "); + break; + } + } + printf("\n colgroups->"); + for (nsIFrame* childFrame = mColGroups.FirstChild(); childFrame; + childFrame = childFrame->GetNextSibling()) { + if (nsGkAtoms::tableColGroupFrame == childFrame->GetType()) { + nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame *)childFrame; + colGroupFrame->Dump(1); + } + } + for (colX = 0; colX < numCols; colX++) { + printf("\n"); + nsTableColFrame* colFrame = GetColFrame(colX); + colFrame->Dump(1); + } + } + if (aDumpCellMap) { + nsTableCellMap* cellMap = GetCellMap(); + cellMap->Dump(); + } + printf(" ***END TABLE DUMP*** \n"); +} +#endif + +// nsTableIterator +nsTableIterator::nsTableIterator(nsIFrame& aSource) +{ + nsIFrame* firstChild = aSource.GetFirstPrincipalChild(); + Init(firstChild); +} + +nsTableIterator::nsTableIterator(nsFrameList& aSource) +{ + nsIFrame* firstChild = aSource.FirstChild(); + Init(firstChild); +} + +void nsTableIterator::Init(nsIFrame* aFirstChild) +{ + mFirstListChild = aFirstChild; + mFirstChild = aFirstChild; + mCurrentChild = nullptr; + mLeftToRight = true; + mCount = -1; + + if (!mFirstChild) { + return; + } + + nsTableFrame* table = nsTableFrame::GetTableFrame(mFirstChild); + mLeftToRight = (NS_STYLE_DIRECTION_LTR == + table->StyleVisibility()->mDirection); + + if (!mLeftToRight) { + mCount = 0; + nsIFrame* nextChild = mFirstChild->GetNextSibling(); + while (nullptr != nextChild) { + mCount++; + mFirstChild = nextChild; + nextChild = nextChild->GetNextSibling(); + } + } +} + +nsIFrame* nsTableIterator::First() +{ + mCurrentChild = mFirstChild; + return mCurrentChild; +} + +nsIFrame* nsTableIterator::Next() +{ + if (!mCurrentChild) { + return nullptr; + } + + if (mLeftToRight) { + mCurrentChild = mCurrentChild->GetNextSibling(); + return mCurrentChild; + } + else { + nsIFrame* targetChild = mCurrentChild; + mCurrentChild = nullptr; + nsIFrame* child = mFirstListChild; + while (child && (child != targetChild)) { + mCurrentChild = child; + child = child->GetNextSibling(); + } + return mCurrentChild; + } +} + +bool nsTableIterator::IsLeftToRight() +{ + return mLeftToRight; +} + +int32_t nsTableIterator::Count() +{ + if (-1 == mCount) { + mCount = 0; + nsIFrame* child = mFirstListChild; + while (nullptr != child) { + mCount++; + child = child->GetNextSibling(); + } + } + return mCount; +} + +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).x >= 0, "negative col index"); \ + NS_ASSERTION((r).y >= 0, "negative row index"); \ + NS_ASSERTION((r).width >= 0, "negative horizontal damage"); \ + NS_ASSERTION((r).height >= 0, "negative vertical damage"); +#define VerifyDamageRect(r) \ + VerifyNonNegativeDamageRect(r); \ + NS_ASSERTION((r).XMost() <= GetColCount(), \ + "horizontal damage extends outside table"); \ + NS_ASSERTION((r).YMost() <= GetRowCount(), \ + "vertical damage extends outside table"); +#endif + +void +nsTableFrame::AddBCDamageArea(const nsIntRect& 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.XMost() > cols) { + if (value->mDamageArea.x > cols) { + value->mDamageArea.x = cols; + value->mDamageArea.width = 0; + } + else { + value->mDamageArea.width = cols - value->mDamageArea.x; + } + } + int32_t rows = GetRowCount(); + if (value->mDamageArea.YMost() > rows) { + if (value->mDamageArea.y > rows) { + value->mDamageArea.y = rows; + value->mDamageArea.height = 0; + } + else { + value->mDamageArea.height = rows - value->mDamageArea.y; + } + } + + // Construct a union of the new and old damage areas. + value->mDamageArea.UnionRect(value->mDamageArea, aValue); + } +} + + +void +nsTableFrame::SetFullBCDamageArea() +{ + NS_ASSERTION(IsBorderCollapse(), "invalid SetFullBCDamageArea call"); + + SetNeedToCalcBCBorders(true); + + BCPropertyData* value = GetBCProperty(true); + if (value) { + value->mDamageArea = nsIntRect(0, 0, GetColCount(), GetRowCount()); + } +} + + +/* BCCellBorder represents a border segment which can be either a horizontal + * or a vertical 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 horizontal and vertical segments and to store the + * preliminary computed border results in the BCCellBorders structure. + * This temporary storage is not symmetric with respect to horizontal and + * vertical border segments, its always column oriented. For each column in + * the cellmap there is a temporary stored vertical and horizontal 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 horizontal border + // segments relative to the table + int32_t rowSpan; // row span of temporary stored horizontal 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 +{ + 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 SetTableTopLeftContBCBorder(); + void SetRowGroupLeftContBCBorder(); + void SetRowGroupRightContBCBorder(); + void SetRowGroupBottomContBCBorder(); + void SetRowLeftContBCBorder(); + void SetRowRightContBCBorder(); + void SetColumnTopRightContBCBorder(); + void SetColumnBottomContBCBorder(); + void SetColGroupBottomContBCBorder(); + void SetInnerRowGroupBottomContBCBorder(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 SetTableTopBorderWidth(BCPixelSize aWidth); + void SetTableLeftBorderWidth(int32_t aRowY, BCPixelSize aWidth); + void SetTableRightBorderWidth(int32_t aRowY, BCPixelSize aWidth); + void SetTableBottomBorderWidth(BCPixelSize aWidth); + void SetLeftBorderWidths(BCPixelSize aWidth); + void SetRightBorderWidths(BCPixelSize aWidth); + void SetTopBorderWidths(BCPixelSize aWidth); + void SetBottomBorderWidths(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 GetTopEdgeBorder(); + BCCellBorder GetBottomEdgeBorder(); + BCCellBorder GetLeftEdgeBorder(); + BCCellBorder GetRightEdgeBorder(); + BCCellBorder GetRightInternalBorder(); + BCCellBorder GetLeftInternalBorder(); + BCCellBorder GetTopInternalBorder(); + BCCellBorder GetBottomInternalBorder(); + + // functions to set the interal position information + void SetColumn(int32_t aColX); + // Increment the row as we loop over the rows of a rowspan + void IncrementRow(bool aResetToTopRowOfCell = 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; + + // storage of table ltr information, the border collapse code swaps the sides + // to account for rtl tables, this is done through mStartSide and mEndSide + bool mTableIsLTR; + mozilla::css::Side mStartSide; + mozilla::css::Side mEndSide; + + // a cell can only belong to one rowgroup + nsTableRowGroupFrame* mRowGroup; + + // a cell with a rowspan has a top and a bottom row, and rows in between + nsTableRowFrame* mTopRow; + nsTableRowFrame* mBottomRow; + nsTableRowFrame* mCurrentRowFrame; + + // a cell with a colspan has a left and right column and columns in between + // they can belong to different colgroups + nsTableColGroupFrame* mColGroup; + nsTableColGroupFrame* mCurrentColGroupFrame; + + nsTableColFrame* mLeftCol; + nsTableColFrame* mRightCol; + 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 mRgAtTop documents that the top cell border hits + // a rowgroup border + bool mRgAtTop; + bool mRgAtBottom; + bool mCgAtLeft; + bool mCgAtRight; + +}; + + +BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame) +{ + mTableFrame = aTableFrame; + mTableIsLTR = + aTableFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR; + if (mTableIsLTR) { + mStartSide = NS_SIDE_LEFT; + mEndSide = NS_SIDE_RIGHT; + } + else { + mStartSide = NS_SIDE_RIGHT; + mEndSide = NS_SIDE_LEFT; + } + mNumTableRows = mTableFrame->GetRowCount(); + mNumTableCols = mTableFrame->GetColCount(); + mTableBCData = static_cast + (mTableFrame->Properties().Get(TableBCProperty())); + + ResetCellInfo(); +} + +void BCMapCellInfo::ResetCellInfo() +{ + mCellData = nullptr; + mRowGroup = nullptr; + mTopRow = nullptr; + mBottomRow = nullptr; + mColGroup = nullptr; + mLeftCol = nullptr; + mRightCol = nullptr; + mCell = nullptr; + mRowIndex = mRowSpan = mColIndex = mColSpan = 0; + mRgAtTop = mRgAtBottom = mCgAtLeft = mCgAtRight = 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 nsIntRect& aDamageArea); + + void First(BCMapCellInfo& aMapCellInfo); + + void Next(BCMapCellInfo& aMapCellInfo); + + void PeekRight(BCMapCellInfo& aRefInfo, + uint32_t aRowIndex, + BCMapCellInfo& aAjaInfo); + + void PeekBottom(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; + nsPoint mAreaEnd; +}; + +BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame, + const nsIntRect& aDamageArea) +:mTableFrame(aTableFrame) +{ + mTableCellMap = aTableFrame->GetCellMap(); + + mAreaStart.x = aDamageArea.x; + mAreaStart.y = aDamageArea.y; + mAreaEnd.y = aDamageArea.y + aDamageArea.height - 1; + mAreaEnd.x = aDamageArea.x + aDamageArea.width - 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) { + mTopRow = aNewRow; + mRowIndex = aNewRow->GetRowIndex(); + } + + // fill cell frame info and row information + mCell = nullptr; + mRowSpan = 1; + mColSpan = 1; + if (aCellData) { + mCell = static_cast(aCellData->GetCellFrame()); + if (mCell) { + if (!mTopRow) { + mTopRow = static_cast(mCell->GetParent()); + if (!mTopRow) ABORT0(); + mRowIndex = mTopRow->GetRowIndex(); + } + mColSpan = mTableFrame->GetEffectiveColSpan(*mCell, aCellMap); + mRowSpan = mTableFrame->GetEffectiveRowSpan(*mCell, aCellMap); + } + } + + if (!mTopRow) { + mTopRow = aIter->GetCurrentRow(); + } + if (1 == mRowSpan) { + mBottomRow = mTopRow; + } + else { + mBottomRow = mTopRow->GetNextRow(); + if (mBottomRow) { + for (int32_t spanY = 2; mBottomRow && (spanY < mRowSpan); spanY++) { + mBottomRow = mBottomRow->GetNextRow(); + } + NS_ASSERTION(mBottomRow, "spanned row not found"); + } + else { + NS_ASSERTION(false, "error in cell map"); + mRowSpan = 1; + mBottomRow = mTopRow; + } + } + // 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 = static_cast(mTopRow->GetParent()); + if (mRowGroup != aIter->GetCurrentRowGroup()) { + rgStart = mRowGroup->GetStartRowIndex(); + rgEnd = rgStart + mRowGroup->GetRowCount() - 1; + } + uint32_t rowIndex = mTopRow->GetRowIndex(); + mRgAtTop = (rgStart == rowIndex); + mRgAtBottom = (rgEnd == rowIndex + mRowSpan - 1); + + // col frame info + mLeftCol = mTableFrame->GetColFrame(aColIndex); + if (!mLeftCol) ABORT0(); + + mRightCol = mLeftCol; + if (mColSpan > 1) { + nsTableColFrame* colFrame = mTableFrame->GetColFrame(aColIndex + + mColSpan -1); + if (!colFrame) ABORT0(); + mRightCol = colFrame; + } + + // col group frame info + mColGroup = static_cast(mLeftCol->GetParent()); + int32_t cgStart = mColGroup->GetStartColumnIndex(); + int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1); + mCgAtLeft = (cgStart == aColIndex); + mCgAtRight = (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 + nsIntRect damageArea; + cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex, + false, 0, damageArea); + if (!cellData) ABORT1(false); + } + if (cellData && (cellData->IsOrig() || cellData->IsDead())) { + break; + } + } + mIsNewRow = true; + mAtEnd = false; + } + else ABORT1(false); + + return !mAtEnd; +} + +bool +BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow) +{ + mAtEnd = true; + int32_t numRowGroups = mRowGroups.Length(); + mCellMap = nullptr; + for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) { + mRowGroup = mRowGroups[mRowGroupIndex]; + int32_t rowCount = mRowGroup->GetRowCount(); + mRowGroupStart = mRowGroup->GetStartRowIndex(); + mRowGroupEnd = mRowGroupStart + rowCount - 1; + if (rowCount > 0) { + mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap); + if (!mCellMap) ABORT1(false); + nsTableRowFrame* firstRow = mRowGroup->GetFirstRow(); + if (aFindFirstDamagedRow) { + if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) { + // the damage area starts in the row group + if (aFindFirstDamagedRow) { + // find the correct first damaged row + int32_t numRows = mAreaStart.y - mRowGroupStart; + for (int32_t i = 0; i < numRows; i++) { + firstRow = firstRow->GetNextRow(); + if (!firstRow) ABORT1(false); + } + } + } + else { + continue; + } + } + if (SetNewRow(firstRow)) { // sets mAtEnd + break; + } + } + } + + return !mAtEnd; +} + +void +BCMapCellIterator::First(BCMapCellInfo& aMapInfo) +{ + aMapInfo.ResetCellInfo(); + + SetNewRowGroup(true); // sets mAtEnd + while (!mAtEnd) { + if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) { + BCCellData* cellData = + static_cast(mCellMap->GetDataAt(mAreaStart.y - + mRowGroupStart, + mAreaStart.x)); + if (cellData && (cellData->IsOrig() || cellData->IsDead())) { + aMapInfo.SetInfo(mRow, mAreaStart.x, cellData, this); + return; + } + else { + NS_ASSERTION(((0 == mAreaStart.x) && (mRowGroupStart == mAreaStart.y)) , + "damage area expanded incorrectly"); + } + } + SetNewRowGroup(true); // sets mAtEnd + } +} + +void +BCMapCellIterator::Next(BCMapCellInfo& aMapInfo) +{ + if (mAtEnd) ABORT0(); + aMapInfo.ResetCellInfo(); + + mIsNewRow = false; + mColIndex++; + while ((mRowIndex <= mAreaEnd.y) && !mAtEnd) { + for (; mColIndex <= mAreaEnd.x; mColIndex++) { + int32_t rgRowIndex = mRowIndex - mRowGroupStart; + BCCellData* cellData = + static_cast(mCellMap->GetDataAt(rgRowIndex, mColIndex)); + if (!cellData) { // add a dead cell data + nsIntRect damageArea; + cellData = + static_cast(mCellMap->AppendCell(*mTableCellMap, nullptr, + rgRowIndex, false, 0, + damageArea)); + if (!cellData) ABORT0(); + } + if (cellData && (cellData->IsOrig() || cellData->IsDead())) { + aMapInfo.SetInfo(mRow, mColIndex, cellData, this); + return; + } + } + if (mRowIndex >= mRowGroupEnd) { + SetNewRowGroup(false); // could set mAtEnd + } + else { + SetNewRow(); // could set mAtEnd + } + } + mAtEnd = true; +} + +void +BCMapCellIterator::PeekRight(BCMapCellInfo& aRefInfo, + uint32_t aRowIndex, + BCMapCellInfo& aAjaInfo) +{ + aAjaInfo.ResetCellInfo(); + int32_t colIndex = aRefInfo.mColIndex + aRefInfo.mColSpan; + uint32_t rgRowIndex = aRowIndex - mRowGroupStart; + + BCCellData* cellData = + static_cast(mCellMap->GetDataAt(rgRowIndex, colIndex)); + if (!cellData) { // add a dead cell data + NS_ASSERTION(colIndex < mTableCellMap->GetColCount(), "program error"); + nsIntRect damageArea; + cellData = + static_cast(mCellMap->AppendCell(*mTableCellMap, nullptr, + rgRowIndex, false, 0, + damageArea)); + if (!cellData) ABORT0(); + } + nsTableRowFrame* row = nullptr; + if (cellData->IsRowSpan()) { + rgRowIndex -= cellData->GetRowSpanOffset(); + cellData = + static_cast(mCellMap->GetDataAt(rgRowIndex, colIndex)); + if (!cellData) + ABORT0(); + } + else { + row = mRow; + } + aAjaInfo.SetInfo(row, colIndex, cellData, this); +} + +void +BCMapCellIterator::PeekBottom(BCMapCellInfo& aRefInfo, + uint32_t aColIndex, + BCMapCellInfo& aAjaInfo) +{ + aAjaInfo.ResetCellInfo(); + int32_t rowIndex = aRefInfo.mRowIndex + aRefInfo.mRowSpan; + int32_t rgRowIndex = rowIndex - mRowGroupStart; + nsTableRowGroupFrame* rg = mRowGroup; + nsCellMap* cellMap = mCellMap; + nsTableRowFrame* nextRow = nullptr; + if (rowIndex > mRowGroupEnd) { + int32_t nextRgIndex = mRowGroupIndex; + do { + nextRgIndex++; + rg = mRowGroups.SafeElementAt(nextRgIndex); + if (rg) { + cellMap = mTableCellMap->GetMapFor(rg, cellMap); if (!cellMap) ABORT0(); + rgRowIndex = 0; + nextRow = rg->GetFirstRow(); + } + } + while (rg && !nextRow); + if(!rg) return; + } + else { + // get the row within the same row group + nextRow = mRow; + for (int32_t i = 0; i < aRefInfo.mRowSpan; i++) { + nextRow = nextRow->GetNextRow(); if (!nextRow) ABORT0(); + } + } + + BCCellData* cellData = + static_cast(cellMap->GetDataAt(rgRowIndex, aColIndex)); + if (!cellData) { // add a dead cell data + NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error"); + nsIntRect damageArea; + cellData = + static_cast(cellMap->AppendCell(*mTableCellMap, nullptr, + rgRowIndex, false, 0, + damageArea)); + if (!cellData) ABORT0(); + } + if (cellData->IsColSpan()) { + aColIndex -= cellData->GetColSpanOffset(); + cellData = + static_cast(cellMap->GetDataAt(rgRowIndex, aColIndex)); + } + aAjaInfo.SetInfo(nextRow, aColIndex, cellData, this, cellMap); +} + +// Assign priorities to border styles. For example, styleToPriority(NS_STYLE_BORDER_STYLE_SOLID) +// will return the priority of NS_STYLE_BORDER_STYLE_SOLID. +static uint8_t styleToPriority[13] = { 0, // NS_STYLE_BORDER_STYLE_NONE + 2, // NS_STYLE_BORDER_STYLE_GROOVE + 4, // NS_STYLE_BORDER_STYLE_RIDGE + 5, // NS_STYLE_BORDER_STYLE_DOTTED + 6, // NS_STYLE_BORDER_STYLE_DASHED + 7, // NS_STYLE_BORDER_STYLE_SOLID + 8, // NS_STYLE_BORDER_STYLE_DOUBLE + 1, // NS_STYLE_BORDER_STYLE_INSET + 3, // NS_STYLE_BORDER_STYLE_OUTSET + 9 };// NS_STYLE_BORDER_STYLE_HIDDEN +// priority rules follow CSS 2.1 spec +// 'hidden', 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', +// and the lowest: 'inset'. none is even weaker +#define CELL_CORNER true + +/** return the border style, border color for a given frame and side + * @param aFrame - query the info for this frame + * @param aSide - the side of the frame + * @param aStyle - the border style + * @param aColor - the border color + * @param aTableIsLTR - table direction is LTR + */ +static void +GetColorAndStyle(const nsIFrame* aFrame, + mozilla::css::Side aSide, + uint8_t& aStyle, + nscolor& aColor, + bool aTableIsLTR) +{ + NS_PRECONDITION(aFrame, "null frame"); + // initialize out arg + aColor = 0; + const nsStyleBorder* styleData = aFrame->StyleBorder(); + if(!aTableIsLTR) { // revert the directions + if (NS_SIDE_RIGHT == aSide) { + aSide = NS_SIDE_LEFT; + } + else if (NS_SIDE_LEFT == aSide) { + aSide = NS_SIDE_RIGHT; + } + } + aStyle = styleData->GetBorderStyle(aSide); + + if ((NS_STYLE_BORDER_STYLE_NONE == aStyle) || + (NS_STYLE_BORDER_STYLE_HIDDEN == aStyle)) { + return; + } + aColor = aFrame->StyleContext()->GetVisitedDependentColor( + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[aSide]); +} + +/** coerce the paint style as required by CSS2.1 + * @param aFrame - query the info for this frame + * @param aSide - the side of the frame + * @param aStyle - the border style + * @param aColor - the border color + * @param aTableIsLTR - table direction is LTR + */ +static void +GetPaintStyleInfo(const nsIFrame* aFrame, + mozilla::css::Side aSide, + uint8_t& aStyle, + nscolor& aColor, + bool aTableIsLTR) +{ + GetColorAndStyle(aFrame, aSide, aStyle, aColor, aTableIsLTR); + 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; + } +} + +/** return the border style, border color and the width in pixel for a given + * frame and side + * @param aFrame - query the info for this frame + * @param aSide - the side of the frame + * @param aStyle - the border style + * @param aColor - the border color + * @param aTableIsLTR - table direction is LTR + * @param aWidth - the border width in px. + * @param aTwipsToPixels - conversion factor from twips to pixel + */ +static void +GetColorAndStyle(const nsIFrame* aFrame, + mozilla::css::Side aSide, + uint8_t& aStyle, + nscolor& aColor, + bool aTableIsLTR, + BCPixelSize& aWidth) +{ + GetColorAndStyle(aFrame, aSide, aStyle, aColor, aTableIsLTR); + if ((NS_STYLE_BORDER_STYLE_NONE == aStyle) || + (NS_STYLE_BORDER_STYLE_HIDDEN == aStyle)) { + aWidth = 0; + return; + } + const nsStyleBorder* styleData = aFrame->StyleBorder(); + nscoord width; + if(!aTableIsLTR) { // revert the directions + if (NS_SIDE_RIGHT == aSide) { + aSide = NS_SIDE_LEFT; + } + else if (NS_SIDE_LEFT == aSide) { + aSide = NS_SIDE_RIGHT; + } + } + width = styleData->GetComputedBorderWidth(aSide); + aWidth = nsPresContext::AppUnitsToIntCSSPixels(width); +} + +class nsDelayedCalcBCBorders : public nsRunnable { +public: + nsDelayedCalcBCBorders(nsIFrame* aFrame) : + mFrame(aFrame) {} + + NS_IMETHOD Run() MOZ_OVERRIDE { + if (mFrame) { + nsTableFrame* tableFrame = static_cast (mFrame.GetFrame()); + if (tableFrame->NeedToCalcBCBorders()) { + tableFrame->CalcBCBorders(); + } + } + return NS_OK; + } +private: + nsWeakFrame mFrame; +}; + +bool +nsTableFrame::BCRecalcNeeded(nsStyleContext* aOldStyleContext, + nsStyleContext* aNewStyleContext) +{ + // Attention: the old style context is the one we're forgetting, + // and hence possibly completely bogus for GetStyle* purposes. + // We use PeekStyleData instead. + + const nsStyleBorder* oldStyleData = aOldStyleContext->PeekStyleBorder(); + if (!oldStyleData) + return false; + + const nsStyleBorder* newStyleData = aNewStyleContext->StyleBorder(); + nsChangeHint change = newStyleData->CalcDifference(*oldStyleData); + if (!change) + return false; + if (change & nsChangeHint_NeedReflow) + return true; // the caller only needs to mark the bc damage area + if (change & nsChangeHint_RepaintFrame) { + // we need to recompute the borders and the caller needs to mark + // the bc damage area + // XXX In principle this should only be necessary for border style changes + // However the bc painting code tries to maximize the drawn border segments + // so it stores in the cellmap where a new border segment starts and this + // introduces a unwanted cellmap data dependence on color + nsCOMPtr evt = new nsDelayedCalcBCBorders(this); + NS_DispatchToCurrentThread(evt); + return true; + } + return false; +} + + +// Compare two border segments, this comparison depends whether the two +// segments meet at a corner and whether the second segment is horizontal. +// 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 aSecondIsHorizontal, + 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 = !aSecondIsHorizontal; + } + 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 vertical or horizontal 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 top and left borders for each cellmap position. + * If the cell border is owned by the cell that is left 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 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 top and left + * borders are stored. + * @param aTwipsToPixels - conversion factor as borders need to be drawn pixel + * aligned. + */ +static BCCellBorder +CompareBorders(const nsIFrame* aTableFrame, + const nsIFrame* aColGroupFrame, + const nsIFrame* aColFrame, + const nsIFrame* aRowGroupFrame, + const nsIFrame* aRowFrame, + const nsIFrame* aCellFrame, + bool aTableIsLTR, + mozilla::css::Side aSide, + bool aAja) +{ + BCCellBorder border, tempBorder; + bool horizontal = (NS_SIDE_TOP == aSide) || (NS_SIDE_BOTTOM == aSide); + + // start with the table as dominant if present + if (aTableFrame) { + GetColorAndStyle(aTableFrame, aSide, border.style, border.color, aTableIsLTR, 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, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); + tempBorder.owner = (aAja && !horizontal) ? eAjaColGroupOwner : eColGroupOwner; + // pass here and below false for aSecondIsHorizontal 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, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); + tempBorder.owner = (aAja && !horizontal) ? 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, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); + tempBorder.owner = (aAja && horizontal) ? 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, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); + tempBorder.owner = (aAja && horizontal) ? 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, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); + tempBorder.owner = (aAja) ? eAjaCellOwner : eCellOwner; + border = CompareBorders(!CELL_CORNER, border, tempBorder, false); + } + return border; +} + +static bool +Perpendicular(mozilla::css::Side aSide1, + mozilla::css::Side aSide2) +{ + switch (aSide1) { + case NS_SIDE_TOP: + return (NS_SIDE_BOTTOM != aSide2); + case NS_SIDE_RIGHT: + return (NS_SIDE_LEFT != aSide2); + case NS_SIDE_BOTTOM: + return (NS_SIDE_TOP != aSide2); + default: // NS_SIDE_LEFT + return (NS_SIDE_RIGHT != 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 = NS_SIDE_TOP; + ownerStyle = 0xFF; subStyle = NS_STYLE_BORDER_STYLE_SOLID; } + void Set(mozilla::css::Side aSide, + BCCellBorder border); + + void Update(mozilla::css::Side 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; // mozilla::css::Side (e.g NS_SIDE_TOP, NS_SIDE_RIGHT, 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::css::Side 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 = ((aSide == NS_SIDE_LEFT) || (aSide == NS_SIDE_RIGHT)) ? NS_SIDE_TOP : NS_SIDE_LEFT; + subElem = eTableOwner; + subStyle = NS_STYLE_BORDER_STYLE_SOLID; +} + +void +BCCornerInfo::Update(mozilla::css::Side aSide, + BCCellBorder aBorder) +{ + bool existingWins = false; + if (0xFF == ownerStyle) { // initial value indiating that it hasn't been set yet + Set(aSide, aBorder); + } + else { + bool horizontal = (NS_SIDE_LEFT == aSide) || (NS_SIDE_RIGHT == aSide); // relative to the corner + BCCellBorder oldBorder, tempBorder; + oldBorder.owner = (BCBorderOwner) ownerElem; + oldBorder.style = ownerStyle; + oldBorder.width = ownerWidth; + oldBorder.color = ownerColor; + + mozilla::css::Side oldSide = mozilla::css::Side(ownerSide); + + tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, horizontal, &existingWins); + + ownerElem = tempBorder.owner; + ownerStyle = tempBorder.style; + ownerWidth = tempBorder.width; + ownerColor = tempBorder.color; + if (existingWins) { // existing corner is dominant + if (::Perpendicular(mozilla::css::Side(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, horizontal, &firstWins); + + subElem = tempBorder.owner; + subStyle = tempBorder.style; + subWidth = tempBorder.width; + if (!firstWins) { + subSide = aSide; + } + } + } + else { // input args are dominant + ownerSide = aSide; + if (::Perpendicular(oldSide, mozilla::css::Side(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 horizontal border. It will return true if the +// existing segment will not be continued. Having a vertical owner of a corner +// should also start a new segment. +static bool +SetHorBorder(const BCCellBorder& aNewBorder, + const BCCornerInfo& aCorner, + BCCellBorder& aBorder) +{ + bool startSeg = ::SetBorder(aNewBorder, aBorder); + if (!startSeg) { + startSeg = ((NS_SIDE_LEFT != aCorner.ownerSide) && (NS_SIDE_RIGHT != 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(nsIntRect& aRect) const +{ + int32_t numRows = GetRowCount(); + int32_t numCols = GetColCount(); + + int32_t dStartX = aRect.x; + int32_t dEndX = aRect.XMost() - 1; + int32_t dStartY = aRect.y; + int32_t dEndY = aRect.YMost() - 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 rgX = 0; rgX < rowGroups.Length(); rgX++) { + nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; + 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 + aRect.x = 0; + aRect.y = 0; + aRect.width = numCols; + aRect.height = numRows; + } + else { + aRect.x = dStartX; + aRect.y = dStartY; + aRect.width = 1 + dEndX - dStartX; + aRect.height = 1 + dEndY - dStartY; + } +} + + +#define ADJACENT true +#define HORIZONTAL true + +void +BCMapCellInfo::SetTableTopLeftContBCBorder() +{ + BCCellBorder currentBorder; + //calculate continuous top first row & rowgroup border: special case + //because it must include the table in the collapse + if (mTopRow) { + currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, + mTopRow, nullptr, mTableIsLTR, + NS_SIDE_TOP, !ADJACENT); + mTopRow->SetContinuousBCBorderWidth(NS_SIDE_TOP, currentBorder.width); + } + if (mCgAtRight && mColGroup) { + //calculate continuous top colgroup border once per colgroup + currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, + mTopRow, nullptr, mTableIsLTR, + NS_SIDE_TOP, !ADJACENT); + mColGroup->SetContinuousBCBorderWidth(NS_SIDE_TOP, currentBorder.width); + } + if (0 == mColIndex) { + currentBorder = CompareBorders(mTableFrame, mColGroup, mLeftCol, nullptr, + nullptr, nullptr, mTableIsLTR, NS_SIDE_LEFT, + !ADJACENT); + mTableFrame->SetContinuousLeftBCBorderWidth(currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowGroupLeftContBCBorder() +{ + BCCellBorder currentBorder; + //get row group continuous borders + if (mRgAtBottom && mRowGroup) { //once per row group, so check for bottom + currentBorder = CompareBorders(mTableFrame, mColGroup, mLeftCol, mRowGroup, + nullptr, nullptr, mTableIsLTR, NS_SIDE_LEFT, + !ADJACENT); + mRowGroup->SetContinuousBCBorderWidth(mStartSide, currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowGroupRightContBCBorder() +{ + BCCellBorder currentBorder; + //get row group continuous borders + if (mRgAtBottom && mRowGroup) { //once per mRowGroup, so check for bottom + currentBorder = CompareBorders(mTableFrame, mColGroup, mRightCol, mRowGroup, + nullptr, nullptr, mTableIsLTR, NS_SIDE_RIGHT, + ADJACENT); + mRowGroup->SetContinuousBCBorderWidth(mEndSide, currentBorder.width); + } +} + +void +BCMapCellInfo::SetColumnTopRightContBCBorder() +{ + 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, mTopRow, nullptr, + mTableIsLTR, NS_SIDE_TOP, !ADJACENT); + ((nsTableColFrame*) mCurrentColFrame)->SetContinuousBCBorderWidth(NS_SIDE_TOP, + currentBorder.width); + if (mNumTableCols == GetCellEndColIndex() + 1) { + currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, + mCurrentColFrame, nullptr, nullptr, nullptr, + mTableIsLTR, NS_SIDE_RIGHT, !ADJACENT); + } + else { + currentBorder = CompareBorders(nullptr, mCurrentColGroupFrame, + mCurrentColFrame, nullptr,nullptr, nullptr, + mTableIsLTR, NS_SIDE_RIGHT, !ADJACENT); + } + mCurrentColFrame->SetContinuousBCBorderWidth(NS_SIDE_RIGHT, + currentBorder.width); +} + +void +BCMapCellInfo::SetColumnBottomContBCBorder() +{ + BCCellBorder currentBorder; + //get col continuous border + currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, + mCurrentColFrame, mRowGroup, mBottomRow, + nullptr, mTableIsLTR, NS_SIDE_BOTTOM, ADJACENT); + mCurrentColFrame->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, + currentBorder.width); +} + +void +BCMapCellInfo::SetColGroupBottomContBCBorder() +{ + BCCellBorder currentBorder; + if (mColGroup) { + currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, + mBottomRow, nullptr, mTableIsLTR, + NS_SIDE_BOTTOM, ADJACENT); + mColGroup->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowGroupBottomContBCBorder() +{ + BCCellBorder currentBorder; + if (mRowGroup) { + currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, + mBottomRow, nullptr, mTableIsLTR, + NS_SIDE_BOTTOM, ADJACENT); + mRowGroup->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, currentBorder.width); + } +} + +void +BCMapCellInfo::SetInnerRowGroupBottomContBCBorder(const nsIFrame* aNextRowGroup, + nsTableRowFrame* aNextRow) +{ + BCCellBorder currentBorder, adjacentBorder; + + const nsIFrame* rowgroup = (mRgAtBottom) ? mRowGroup : nullptr; + currentBorder = CompareBorders(nullptr, nullptr, nullptr, rowgroup, mBottomRow, + nullptr, mTableIsLTR, NS_SIDE_BOTTOM, ADJACENT); + + adjacentBorder = CompareBorders(nullptr, nullptr, nullptr, aNextRowGroup, + aNextRow, nullptr, mTableIsLTR, NS_SIDE_TOP, + !ADJACENT); + currentBorder = CompareBorders(false, currentBorder, adjacentBorder, + HORIZONTAL); + if (aNextRow) { + aNextRow->SetContinuousBCBorderWidth(NS_SIDE_TOP, currentBorder.width); + } + if (mRgAtBottom && mRowGroup) { + mRowGroup->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowLeftContBCBorder() +{ + //get row continuous borders + if (mCurrentRowFrame) { + BCCellBorder currentBorder; + currentBorder = CompareBorders(mTableFrame, mColGroup, mLeftCol, mRowGroup, + mCurrentRowFrame, nullptr, mTableIsLTR, + NS_SIDE_LEFT, !ADJACENT); + mCurrentRowFrame->SetContinuousBCBorderWidth(mStartSide, + currentBorder.width); + } +} + +void +BCMapCellInfo::SetRowRightContBCBorder() +{ + if (mCurrentRowFrame) { + BCCellBorder currentBorder; + currentBorder = CompareBorders(mTableFrame, mColGroup, mRightCol, mRowGroup, + mCurrentRowFrame, nullptr, mTableIsLTR, + NS_SIDE_RIGHT, ADJACENT); + mCurrentRowFrame->SetContinuousBCBorderWidth(mEndSide, + currentBorder.width); + } +} +void +BCMapCellInfo::SetTableTopBorderWidth(BCPixelSize aWidth) +{ + mTableBCData->mTopBorderWidth = std::max(mTableBCData->mTopBorderWidth, aWidth); +} + +void +BCMapCellInfo::SetTableLeftBorderWidth(int32_t aRowY, BCPixelSize aWidth) +{ + // update the left/right first cell border + if (aRowY == 0) { + if (mTableIsLTR) { + mTableBCData->mLeftCellBorderWidth = aWidth; + } + else { + mTableBCData->mRightCellBorderWidth = aWidth; + } + } + mTableBCData->mLeftBorderWidth = std::max(mTableBCData->mLeftBorderWidth, + aWidth); +} + +void +BCMapCellInfo::SetTableRightBorderWidth(int32_t aRowY, BCPixelSize aWidth) +{ + // update the left/right first cell border + if (aRowY == 0) { + if (mTableIsLTR) { + mTableBCData->mRightCellBorderWidth = aWidth; + } + else { + mTableBCData->mLeftCellBorderWidth = aWidth; + } + } + mTableBCData->mRightBorderWidth = std::max(mTableBCData->mRightBorderWidth, + aWidth); +} + +void +BCMapCellInfo::SetRightBorderWidths(BCPixelSize aWidth) +{ + // update the borders of the cells and cols affected + if (mCell) { + mCell->SetBorderWidth(mEndSide, std::max(aWidth, + mCell->GetBorderWidth(mEndSide))); + } + if (mRightCol) { + BCPixelSize half = BC_BORDER_LEFT_HALF(aWidth); + mRightCol->SetRightBorderWidth(std::max(nscoord(half), + mRightCol->GetRightBorderWidth())); + } +} + +void +BCMapCellInfo::SetBottomBorderWidths(BCPixelSize aWidth) +{ + // update the borders of the affected cells and rows + if (mCell) { + mCell->SetBorderWidth(NS_SIDE_BOTTOM, std::max(aWidth, + mCell->GetBorderWidth(NS_SIDE_BOTTOM))); + } + if (mBottomRow) { + BCPixelSize half = BC_BORDER_TOP_HALF(aWidth); + mBottomRow->SetBottomBCBorderWidth(std::max(nscoord(half), + mBottomRow->GetBottomBCBorderWidth())); + } +} +void +BCMapCellInfo::SetTopBorderWidths(BCPixelSize aWidth) +{ + if (mCell) { + mCell->SetBorderWidth(NS_SIDE_TOP, std::max(aWidth, + mCell->GetBorderWidth(NS_SIDE_TOP))); + } + if (mTopRow) { + BCPixelSize half = BC_BORDER_BOTTOM_HALF(aWidth); + mTopRow->SetTopBCBorderWidth(std::max(nscoord(half), + mTopRow->GetTopBCBorderWidth())); + } +} +void +BCMapCellInfo::SetLeftBorderWidths(BCPixelSize aWidth) +{ + if (mCell) { + mCell->SetBorderWidth(mStartSide, std::max(aWidth, + mCell->GetBorderWidth(mStartSide))); + } + if (mLeftCol) { + BCPixelSize half = BC_BORDER_RIGHT_HALF(aWidth); + mLeftCol->SetLeftBorderWidth(std::max(nscoord(half), + mLeftCol->GetLeftBorderWidth())); + } +} + +void +BCMapCellInfo::SetTableBottomBorderWidth(BCPixelSize aWidth) +{ + mTableBCData->mBottomBorderWidth = std::max(mTableBCData->mBottomBorderWidth, + aWidth); +} + +void +BCMapCellInfo::SetColumn(int32_t aColX) +{ + mCurrentColFrame = mTableFrame->GetColFrame(aColX); + if (!mCurrentColFrame) { + NS_ERROR("null mCurrentColFrame"); + } + mCurrentColGroupFrame = static_cast + (mCurrentColFrame->GetParent()); + if (!mCurrentColGroupFrame) { + NS_ERROR("null mCurrentColGroupFrame"); + } +} + +void +BCMapCellInfo::IncrementRow(bool aResetToTopRowOfCell) +{ + mCurrentRowFrame = (aResetToTopRowOfCell) ? mTopRow : + mCurrentRowFrame->GetNextRow(); +} + +BCCellBorder +BCMapCellInfo::GetTopEdgeBorder() +{ + return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, + mRowGroup, mTopRow, mCell, mTableIsLTR, NS_SIDE_TOP, + !ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetBottomEdgeBorder() +{ + return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, + mRowGroup, mBottomRow, mCell, mTableIsLTR, + NS_SIDE_BOTTOM, ADJACENT); +} +BCCellBorder +BCMapCellInfo::GetLeftEdgeBorder() +{ + return CompareBorders(mTableFrame, mColGroup, mLeftCol, mRowGroup, + mCurrentRowFrame, mCell, mTableIsLTR, NS_SIDE_LEFT, + !ADJACENT); +} +BCCellBorder +BCMapCellInfo::GetRightEdgeBorder() +{ + return CompareBorders(mTableFrame, mColGroup, mRightCol, mRowGroup, + mCurrentRowFrame, mCell, mTableIsLTR, NS_SIDE_RIGHT, + ADJACENT); +} +BCCellBorder +BCMapCellInfo::GetRightInternalBorder() +{ + const nsIFrame* cg = (mCgAtRight) ? mColGroup : nullptr; + return CompareBorders(nullptr, cg, mRightCol, nullptr, nullptr, mCell, + mTableIsLTR, NS_SIDE_RIGHT, ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetLeftInternalBorder() +{ + const nsIFrame* cg = (mCgAtLeft) ? mColGroup : nullptr; + return CompareBorders(nullptr, cg, mLeftCol, nullptr, nullptr, mCell, + mTableIsLTR, NS_SIDE_LEFT, !ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetBottomInternalBorder() +{ + const nsIFrame* rg = (mRgAtBottom) ? mRowGroup : nullptr; + return CompareBorders(nullptr, nullptr, nullptr, rg, mBottomRow, mCell, + mTableIsLTR, NS_SIDE_BOTTOM, ADJACENT); +} + +BCCellBorder +BCMapCellInfo::GetTopInternalBorder() +{ + const nsIFrame* rg = (mRgAtTop) ? mRowGroup : nullptr; + return CompareBorders(nullptr, nullptr, nullptr, rg, mTopRow, mCell, + mTableIsLTR, NS_SIDE_TOP, !ADJACENT); +} + +/* 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 + nsIntRect 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 = NS_SIDE_TOP; sideX <= NS_SIDE_LEFT; sideX++) { + tableBorderReset[sideX] = false; + } + + // vertical borders indexed in x-direction (cols) + BCCellBorders lastVerBorders(damageArea.width + 1, damageArea.x); + if (!lastVerBorders.borders) ABORT0(); + BCCellBorder lastTopBorder, lastBottomBorder; + // horizontal borders indexed in x-direction (cols) + BCCellBorders lastBottomBorders(damageArea.width + 1, damageArea.x); + if (!lastBottomBorders.borders) ABORT0(); + bool startSeg; + bool gotRowBorder = false; + + BCMapCellInfo info(this), ajaInfo(this); + + BCCellBorder currentBorder, adjacentBorder; + BCCorners topCorners(damageArea.width + 1, damageArea.x); + if (!topCorners.corners) ABORT0(); + BCCorners bottomCorners(damageArea.width + 1, damageArea.x); + if (!bottomCorners.corners) ABORT0(); + + BCMapCellIterator iter(this, damageArea); + for (iter.First(info); !iter.mAtEnd; iter.Next(info)) { + // see if lastTopBorder, lastBottomBorder need to be reset + if (iter.IsNewRow()) { + gotRowBorder = false; + lastTopBorder.Reset(info.mRowIndex, info.mRowSpan); + lastBottomBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); + } + else if (info.mColIndex > damageArea.x) { + lastBottomBorder = lastBottomBorders[info.mColIndex - 1]; + if (info.mRowIndex > + (lastBottomBorder.rowIndex - lastBottomBorder.rowSpan)) { + // the top border's left edge butts against the middle of a rowspan + lastTopBorder.Reset(info.mRowIndex, info.mRowSpan); + } + if (lastBottomBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) { + // the bottom border's left edge butts against the middle of a rowspan + lastBottomBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); + } + } + + // find the dominant border considering the cell's top border and the table, + // row group, row if the border is at the top of the table, otherwise it was + // processed in a previous row + if (0 == info.mRowIndex) { + if (!tableBorderReset[NS_SIDE_TOP]) { + propData->mTopBorderWidth = 0; + tableBorderReset[NS_SIDE_TOP] = true; + } + for (int32_t colX = info.mColIndex; colX <= info.GetCellEndColIndex(); + colX++) { + info.SetColumn(colX); + currentBorder = info.GetTopEdgeBorder(); + // update/store the top left & top right corners of the seg + BCCornerInfo& tlCorner = topCorners[colX]; // top left + if (0 == colX) { + // we are on right hand side of the corner + tlCorner.Set(NS_SIDE_RIGHT, currentBorder); + } + else { + tlCorner.Update(NS_SIDE_RIGHT, currentBorder); + tableCellMap->SetBCBorderCorner(eTopLeft, *iter.mCellMap, 0, 0, colX, + mozilla::css::Side(tlCorner.ownerSide), + tlCorner.subWidth, + tlCorner.bevel); + } + topCorners[colX + 1].Set(NS_SIDE_LEFT, currentBorder); // top right + // update lastTopBorder and see if a new segment starts + startSeg = SetHorBorder(currentBorder, tlCorner, lastTopBorder); + // store the border segment in the cell map + tableCellMap->SetBCBorderEdge(NS_SIDE_TOP, *iter.mCellMap, 0, 0, colX, + 1, currentBorder.owner, + currentBorder.width, startSeg); + + info.SetTableTopBorderWidth(currentBorder.width); + info.SetTopBorderWidths(currentBorder.width); + info.SetColumnTopRightContBCBorder(); + } + info.SetTableTopLeftContBCBorder(); + } + else { + // see if the top border needs to be the start of a segment due to a + // vertical border owning the corner + if (info.mColIndex > 0) { + BCData& data = info.mCellData->mData; + if (!data.IsTopStart()) { + mozilla::css::Side cornerSide; + bool bevel; + data.GetCorner(cornerSide, bevel); + if ((NS_SIDE_TOP == cornerSide) || (NS_SIDE_BOTTOM == cornerSide)) { + data.SetTopStart(true); + } + } + } + } + + // find the dominant border considering the cell's left border and the + // table, col group, col if the border is at the left of the table, + // otherwise it was processed in a previous col + if (0 == info.mColIndex) { + if (!tableBorderReset[NS_SIDE_LEFT]) { + propData->mLeftBorderWidth = 0; + tableBorderReset[NS_SIDE_LEFT] = true; + } + info.mCurrentRowFrame = nullptr; + for (int32_t rowY = info.mRowIndex; rowY <= info.GetCellEndRowIndex(); + rowY++) { + info.IncrementRow(rowY == info.mRowIndex); + currentBorder = info.GetLeftEdgeBorder(); + BCCornerInfo& tlCorner = (0 == rowY) ? topCorners[0] : bottomCorners[0]; + tlCorner.Update(NS_SIDE_BOTTOM, currentBorder); + tableCellMap->SetBCBorderCorner(eTopLeft, *iter.mCellMap, + iter.mRowGroupStart, rowY, 0, + mozilla::css::Side(tlCorner.ownerSide), + tlCorner.subWidth, + tlCorner.bevel); + bottomCorners[0].Set(NS_SIDE_TOP, currentBorder); // bottom left + + // update lastVerBordersBorder and see if a new segment starts + startSeg = SetBorder(currentBorder, lastVerBorders[0]); + // store the border segment in the cell map + tableCellMap->SetBCBorderEdge(NS_SIDE_LEFT, *iter.mCellMap, + iter.mRowGroupStart, rowY, info.mColIndex, + 1, currentBorder.owner, + currentBorder.width, startSeg); + info.SetTableLeftBorderWidth(rowY , currentBorder.width); + info.SetLeftBorderWidths(currentBorder.width); + info.SetRowLeftContBCBorder(); + } + info.SetRowGroupLeftContBCBorder(); + } + + // find the dominant border considering the cell's right border, adjacent + // cells and the table, row group, row + if (info.mNumTableCols == info.GetCellEndColIndex() + 1) { + // touches right edge of table + if (!tableBorderReset[NS_SIDE_RIGHT]) { + propData->mRightBorderWidth = 0; + tableBorderReset[NS_SIDE_RIGHT] = true; + } + info.mCurrentRowFrame = nullptr; + for (int32_t rowY = info.mRowIndex; rowY <= info.GetCellEndRowIndex(); + rowY++) { + info.IncrementRow(rowY == info.mRowIndex); + currentBorder = info.GetRightEdgeBorder(); + // update/store the top right & bottom right corners + BCCornerInfo& trCorner = (0 == rowY) ? + topCorners[info.GetCellEndColIndex() + 1] : + bottomCorners[info.GetCellEndColIndex() + 1]; + trCorner.Update(NS_SIDE_BOTTOM, currentBorder); // top right + tableCellMap->SetBCBorderCorner(eTopRight, *iter.mCellMap, + iter.mRowGroupStart, rowY, + info.GetCellEndColIndex(), + mozilla::css::Side(trCorner.ownerSide), + trCorner.subWidth, + trCorner.bevel); + BCCornerInfo& brCorner = bottomCorners[info.GetCellEndColIndex() + 1]; + brCorner.Set(NS_SIDE_TOP, currentBorder); // bottom right + tableCellMap->SetBCBorderCorner(eBottomRight, *iter.mCellMap, + iter.mRowGroupStart, rowY, + info.GetCellEndColIndex(), + mozilla::css::Side(brCorner.ownerSide), + brCorner.subWidth, + brCorner.bevel); + // update lastVerBorders and see if a new segment starts + startSeg = SetBorder(currentBorder, + lastVerBorders[info.GetCellEndColIndex() + 1]); + // store the border segment in the cell map and update cellBorders + tableCellMap->SetBCBorderEdge(NS_SIDE_RIGHT, *iter.mCellMap, + iter.mRowGroupStart, rowY, + info.GetCellEndColIndex(), 1, + currentBorder.owner, currentBorder.width, + startSeg); + info.SetTableRightBorderWidth(rowY, currentBorder.width); + info.SetRightBorderWidths(currentBorder.width); + info.SetRowRightContBCBorder(); + } + info.SetRowGroupRightContBCBorder(); + } + else { + int32_t segLength = 0; + BCMapCellInfo priorAjaInfo(this); + for (int32_t rowY = info.mRowIndex; rowY <= info.GetCellEndRowIndex(); + rowY += segLength) { + iter.PeekRight(info, rowY, ajaInfo); + currentBorder = info.GetRightInternalBorder(); + adjacentBorder = ajaInfo.GetLeftInternalBorder(); + currentBorder = CompareBorders(!CELL_CORNER, currentBorder, + adjacentBorder, !HORIZONTAL); + + segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowY); + segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowY); + + // update lastVerBorders and see if a new segment starts + startSeg = SetBorder(currentBorder, + lastVerBorders[info.GetCellEndColIndex() + 1]); + // store the border segment in the cell map and update cellBorders + if (info.GetCellEndColIndex() < damageArea.XMost() && + rowY >= damageArea.y && rowY < damageArea.YMost()) { + tableCellMap->SetBCBorderEdge(NS_SIDE_RIGHT, *iter.mCellMap, + iter.mRowGroupStart, rowY, + info.GetCellEndColIndex(), segLength, + currentBorder.owner, + currentBorder.width, startSeg); + info.SetRightBorderWidths(currentBorder.width); + ajaInfo.SetLeftBorderWidths(currentBorder.width); + } + // update the top right corner + bool hitsSpanOnRight = (rowY > ajaInfo.mRowIndex) && + (rowY < ajaInfo.mRowIndex + ajaInfo.mRowSpan); + BCCornerInfo* trCorner = ((0 == rowY) || hitsSpanOnRight) ? + &topCorners[info.GetCellEndColIndex() + 1] : + &bottomCorners[info.GetCellEndColIndex() + 1]; + trCorner->Update(NS_SIDE_BOTTOM, currentBorder); + // if this is not the first time through, + // consider the segment to the right + if (rowY != info.mRowIndex) { + currentBorder = priorAjaInfo.GetBottomInternalBorder(); + adjacentBorder = ajaInfo.GetTopInternalBorder(); + currentBorder = CompareBorders(!CELL_CORNER, currentBorder, + adjacentBorder, HORIZONTAL); + trCorner->Update(NS_SIDE_RIGHT, currentBorder); + } + // store the top right corner in the cell map + if (info.GetCellEndColIndex() < damageArea.XMost() && + rowY >= damageArea.y) { + if (0 != rowY) { + tableCellMap->SetBCBorderCorner(eTopRight, *iter.mCellMap, + iter.mRowGroupStart, rowY, + info.GetCellEndColIndex(), + mozilla::css::Side(trCorner->ownerSide), + trCorner->subWidth, + trCorner->bevel); + } + // store any corners this cell spans together with the aja cell + for (int32_t rX = rowY + 1; rX < rowY + segLength; rX++) { + tableCellMap->SetBCBorderCorner(eBottomRight, *iter.mCellMap, + iter.mRowGroupStart, rX, + info.GetCellEndColIndex(), + mozilla::css::Side(trCorner->ownerSide), + trCorner->subWidth, false); + } + } + // update bottom right corner, topCorners, bottomCorners + hitsSpanOnRight = (rowY + segLength < + ajaInfo.mRowIndex + ajaInfo.mRowSpan); + BCCornerInfo& brCorner = (hitsSpanOnRight) ? + topCorners[info.GetCellEndColIndex() + 1] : + bottomCorners[info.GetCellEndColIndex() + 1]; + brCorner.Set(NS_SIDE_TOP, currentBorder); + priorAjaInfo = ajaInfo; + } + } + for (int32_t colX = info.mColIndex + 1; colX <= info.GetCellEndColIndex(); + colX++) { + lastVerBorders[colX].Reset(0,1); + } + + // find the dominant border considering the cell's bottom border, adjacent + // cells and the table, row group, row + if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) { + // touches bottom edge of table + if (!tableBorderReset[NS_SIDE_BOTTOM]) { + propData->mBottomBorderWidth = 0; + tableBorderReset[NS_SIDE_BOTTOM] = true; + } + for (int32_t colX = info.mColIndex; colX <= info.GetCellEndColIndex(); + colX++) { + info.SetColumn(colX); + currentBorder = info.GetBottomEdgeBorder(); + // update/store the bottom left & bottom right corners + BCCornerInfo& blCorner = bottomCorners[colX]; // bottom left + blCorner.Update(NS_SIDE_RIGHT, currentBorder); + tableCellMap->SetBCBorderCorner(eBottomLeft, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), + colX, + mozilla::css::Side(blCorner.ownerSide), + blCorner.subWidth, blCorner.bevel); + BCCornerInfo& brCorner = bottomCorners[colX + 1]; // bottom right + brCorner.Update(NS_SIDE_LEFT, currentBorder); + if (info.mNumTableCols == colX + 1) { // lower right corner of the table + tableCellMap->SetBCBorderCorner(eBottomRight, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(),colX, + mozilla::css::Side(brCorner.ownerSide), + brCorner.subWidth, + brCorner.bevel, true); + } + // update lastBottomBorder and see if a new segment starts + startSeg = SetHorBorder(currentBorder, blCorner, lastBottomBorder); + if (!startSeg) { + // make sure that we did not compare apples to oranges i.e. the + // current border should be a continuation of the lastBottomBorder, + // as it is a bottom border + // add 1 to the info.GetCellEndRowIndex() + startSeg = (lastBottomBorder.rowIndex != + (info.GetCellEndRowIndex() + 1)); + } + // store the border segment in the cell map and update cellBorders + tableCellMap->SetBCBorderEdge(NS_SIDE_BOTTOM, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), + colX, 1, currentBorder.owner, + currentBorder.width, startSeg); + // update lastBottomBorders + lastBottomBorder.rowIndex = info.GetCellEndRowIndex() + 1; + lastBottomBorder.rowSpan = info.mRowSpan; + lastBottomBorders[colX] = lastBottomBorder; + + info.SetBottomBorderWidths(currentBorder.width); + info.SetTableBottomBorderWidth(currentBorder.width); + info.SetColumnBottomContBCBorder(); + } + info.SetRowGroupBottomContBCBorder(); + info.SetColGroupBottomContBCBorder(); + } + else { + int32_t segLength = 0; + for (int32_t colX = info.mColIndex; colX <= info.GetCellEndColIndex(); + colX += segLength) { + iter.PeekBottom(info, colX, ajaInfo); + currentBorder = info.GetBottomInternalBorder(); + adjacentBorder = ajaInfo.GetTopInternalBorder(); + currentBorder = CompareBorders(!CELL_CORNER, currentBorder, + adjacentBorder, HORIZONTAL); + segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colX); + segLength = std::min(segLength, info.mColIndex + info.mColSpan - colX); + + // update, store the bottom left corner + BCCornerInfo& blCorner = bottomCorners[colX]; // bottom left + bool hitsSpanBelow = (colX > ajaInfo.mColIndex) && + (colX < ajaInfo.mColIndex + ajaInfo.mColSpan); + bool update = true; + if ((colX == info.mColIndex) && (colX > damageArea.x)) { + int32_t prevRowIndex = lastBottomBorders[colX - 1].rowIndex; + if (prevRowIndex > info.GetCellEndRowIndex() + 1) { + // hits a rowspan on the right + update = false; + // the corner was taken care of during the cell on the left + } + else if (prevRowIndex < info.GetCellEndRowIndex() + 1) { + // spans below the cell to the left + topCorners[colX] = blCorner; + blCorner.Set(NS_SIDE_RIGHT, currentBorder); + update = false; + } + } + if (update) { + blCorner.Update(NS_SIDE_RIGHT, currentBorder); + } + if (info.GetCellEndRowIndex() < damageArea.YMost() && + (colX >= damageArea.x)) { + if (hitsSpanBelow) { + tableCellMap->SetBCBorderCorner(eBottomLeft, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), colX, + mozilla::css::Side(blCorner.ownerSide), + blCorner.subWidth, blCorner.bevel); + } + // store any corners this cell spans together with the aja cell + for (int32_t cX = colX + 1; cX < colX + segLength; cX++) { + BCCornerInfo& corner = bottomCorners[cX]; + corner.Set(NS_SIDE_RIGHT, currentBorder); + tableCellMap->SetBCBorderCorner(eBottomLeft, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), cX, + mozilla::css::Side(corner.ownerSide), + corner.subWidth, + false); + } + } + // update lastBottomBorders and see if a new segment starts + startSeg = SetHorBorder(currentBorder, blCorner, lastBottomBorder); + if (!startSeg) { + // make sure that we did not compare apples to oranges i.e. the + // current border should be a continuation of the lastBottomBorder, + // as it is a bottom border + // add 1 to the info.GetCellEndRowIndex() + startSeg = (lastBottomBorder.rowIndex != + info.GetCellEndRowIndex() + 1); + } + lastBottomBorder.rowIndex = info.GetCellEndRowIndex() + 1; + lastBottomBorder.rowSpan = info.mRowSpan; + for (int32_t cX = colX; cX < colX + segLength; cX++) { + lastBottomBorders[cX] = lastBottomBorder; + } + + // store the border segment the cell map and update cellBorders + if (info.GetCellEndRowIndex() < damageArea.YMost() && + (colX >= damageArea.x) && + (colX < damageArea.XMost())) { + tableCellMap->SetBCBorderEdge(NS_SIDE_BOTTOM, *iter.mCellMap, + iter.mRowGroupStart, + info.GetCellEndRowIndex(), + colX, segLength, currentBorder.owner, + currentBorder.width, startSeg); + info.SetBottomBorderWidths(currentBorder.width); + ajaInfo.SetTopBorderWidths(currentBorder.width); + } + // update bottom right corner + BCCornerInfo& brCorner = bottomCorners[colX + segLength]; + brCorner.Update(NS_SIDE_LEFT, currentBorder); + } + if (!gotRowBorder && 1 == info.mRowSpan && + (ajaInfo.mTopRow || info.mRgAtBottom)) { + //get continuous row/row group border + //we need to check the row group's bottom 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 bottom + const nsIFrame* nextRowGroup = (ajaInfo.mRgAtTop) ? ajaInfo.mRowGroup : + nullptr; + info.SetInnerRowGroupBottomContBCBorder(nextRowGroup, ajaInfo.mTopRow); + gotRowBorder = true; + } + } + + // see if the cell to the right had a rowspan and its lower left border + // needs be joined with this one's bottom + // if there is a cell to the right and the cell to right was a rowspan + if ((info.mNumTableCols != info.GetCellEndColIndex() + 1) && + (lastBottomBorders[info.GetCellEndColIndex() + 1].rowSpan > 1)) { + BCCornerInfo& corner = bottomCorners[info.GetCellEndColIndex() + 1]; + if ((NS_SIDE_TOP != corner.ownerSide) && + (NS_SIDE_BOTTOM != corner.ownerSide)) { + // not a vertical owner + BCCellBorder& thisBorder = lastBottomBorder; + BCCellBorder& nextBorder = lastBottomBorders[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->ResetTopStart(NS_SIDE_BOTTOM, *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 = nsIntRect(0,0,0,0); +#ifdef DEBUG_TABLE_CELLMAP + mCellMap->Dump(); +#endif +} + +class BCPaintBorderIterator; + +struct BCVerticalSeg +{ + BCVerticalSeg(); + + void Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aVerSegWidth, + BCPixelSize aHorSegHeight); + + void Initialize(BCPaintBorderIterator& aIter); + void GetBottomCorner(BCPaintBorderIterator& aIter, + BCPixelSize aHorSegHeight); + + + void Paint(BCPaintBorderIterator& aIter, + nsRenderingContext& aRenderingContext, + BCPixelSize aHorSegHeight); + void AdvanceOffsetY(); + void IncludeCurrentBorder(BCPaintBorderIterator& aIter); + + + union { + nsTableColFrame* mCol; + int32_t mColWidth; + }; + nscoord mOffsetX; // x-offset with respect to the table edge + nscoord mOffsetY; // y-offset with respect to the table edge + nscoord mLength; // vertical length including corners + BCPixelSize mWidth; // width 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 + mozilla::css::Side mTopBevelSide; // direction to bevel at the top + nscoord mTopBevelOffset; // how much to bevel at the top + BCPixelSize mBottomHorSegHeight; // height of the crossing + //horizontal border + nscoord mBottomOffset; // how much longer is the segment due + // to the horizontal border, by this + // amount the next segment needs to be + // shifted. + bool mIsBottomBevel; // should we bevel at the bottom +}; + +struct BCHorizontalSeg +{ + BCHorizontalSeg(); + + void Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aBottomVerSegWidth, + BCPixelSize aHorSegHeight); + void GetRightCorner(BCPaintBorderIterator& aIter, + BCPixelSize aLeftSegWidth); + void AdvanceOffsetX(int32_t aIncrement); + void IncludeCurrentBorder(BCPaintBorderIterator& aIter); + void Paint(BCPaintBorderIterator& aIter, + nsRenderingContext& aRenderingContext); + + nscoord mOffsetX; // x-offset with respect to the table edge + nscoord mOffsetY; // y-offset with respect to the table edge + nscoord mLength; // horizontal length including corners + BCPixelSize mWidth; // border width in pixels + nscoord mLeftBevelOffset; // how much to bevel at the left + mozilla::css::Side mLeftBevelSide; // direction to bevel at the left + bool mIsRightBevel; // should we bevel at the right end + nscoord mRightBevelOffset; // how much to bevel at the right + mozilla::css::Side mRightBevelSide; // direction to bevel at the right + nscoord mEndOffset; // how much longer is the segment due + // to the vertical 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 (left border, corner, top border) in the cell map within a damage area +// from left to right, top to bottom. All members are in terms of the 1st in flow frames, except +// where suffixed by InFlow. +class BCPaintBorderIterator +{ +public: + + + BCPaintBorderIterator(nsTableFrame* aTable); + ~BCPaintBorderIterator() { if (mVerInfo) { + delete [] mVerInfo; + }} + void Reset(); + + /** + * Determine the damage area in terms of rows and columns and finalize + * mInitialOffsetX and mInitialOffsetY. + * @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 AccumulateOrPaintHorizontalSegment(nsRenderingContext& aRenderingContext); + void AccumulateOrPaintVerticalSegment(nsRenderingContext& aRenderingContext); + void ResetVerInfo(); + void StoreColumnWidth(int32_t aIndex); + bool VerticalSegmentOwnsCorner(); + + nsTableFrame* mTable; + nsTableFrame* mTableFirstInFlow; + nsTableCellMap* mTableCellMap; + nsCellMap* mCellMap; + bool mTableIsLTR; + int32_t mColInc; // +1 for ltr -1 for rtl + 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 IsTableTopMost() {return (mRowIndex == 0) && !mTable->GetPrevInFlow();} + bool IsTableRightMost() {return (mColIndex >= mNumTableCols);} + bool IsTableBottomMost() {return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow();} + bool IsTableLeftMost() {return (mColIndex == 0);} + bool IsDamageAreaTopMost() {return (mRowIndex == mDamageArea.y);} + bool IsDamageAreaRightMost() {return (mColIndex >= mDamageArea.XMost());} + bool IsDamageAreaBottomMost() {return (mRowIndex >= mDamageArea.YMost());} + bool IsDamageAreaLeftMost() {return (mColIndex == mDamageArea.x);} + int32_t GetRelativeColIndex() {return (mColIndex - mDamageArea.x);} + + nsIntRect mDamageArea; // damageArea in cellmap coordinates + bool IsAfterRepeatedHeader() { return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1));} + bool StartRepeatedFooter() {return mIsRepeatedFooter && (mRowIndex == mRgFirstRowIndex) && (mRowIndex != mDamageArea.y);} + nscoord mInitialOffsetX; // offsetX of the first border with + // respect to the table + nscoord mInitialOffsetY; // offsetY of the first border with + // respect to the table + nscoord mNextOffsetY; // offsetY of the next segment + BCVerticalSeg* mVerInfo; // this array is used differently when + // horizontal and vertical borders are drawn + // When horizontal border are drawn we cache + // the column widths and the width of the + // vertical borders that arrive from top + // When we draw vertical borders we store + // lengths and width for vertical 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. + BCHorizontalSeg mHorSeg; // the horizontal segment while we + // move over the colums + BCPixelSize mPrevHorSegHeight; // the height of the previous + // horizontal border + +private: + + bool SetNewRow(nsTableRowFrame* aRow = nullptr); + bool SetNewRowGroup(); + void SetNewData(int32_t aRowIndex, int32_t aColIndex); + +}; + + + +BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable) +{ + mTable = aTable; + mVerInfo = nullptr; + nsMargin childAreaOffset = mTable->GetChildAreaOffset(nullptr); + mTableFirstInFlow = static_cast(mTable->FirstInFlow()); + mTableCellMap = mTable->GetCellMap(); + // y position of first row in damage area + mInitialOffsetY = mTable->GetPrevInFlow() ? 0 : childAreaOffset.top; + mNumTableRows = mTable->GetRowCount(); + mNumTableCols = mTable->GetColCount(); + + // Get the ordered row groups + mTable->OrderRowGroups(mRowGroups); + // initialize to a non existing index + mRepeatedHeaderRowIndex = -99; + + mTableIsLTR = mTable->StyleVisibility()->mDirection == + NS_STYLE_DIRECTION_LTR; + mColInc = (mTableIsLTR) ? 1 : -1; + + nsIFrame* bgFrame = + nsCSSRendering::FindNonTransparentBackgroundFrame(aTable); + mTableBgColor = bgFrame->StyleBackground(); +} + +bool +BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect) +{ + + uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex; + startRowIndex = endRowIndex = startColIndex = endColIndex = 0; + bool done = false; + bool haveIntersect = false; + // find startRowIndex, endRowIndex + nscoord rowY = mInitialOffsetY; + for (uint32_t rgX = 0; rgX < mRowGroups.Length() && !done; rgX++) { + nsTableRowGroupFrame* rgFrame = mRowGroups[rgX]; + for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame; + rowFrame = rowFrame->GetNextRow()) { + // conservatively estimate the half border widths outside the row + nscoord topBorderHalf = (mTable->GetPrevInFlow()) ? 0 : + nsPresContext::CSSPixelsToAppUnits(rowFrame->GetTopBCBorderWidth() + 1); + nscoord bottomBorderHalf = (mTable->GetNextInFlow()) ? 0 : + nsPresContext::CSSPixelsToAppUnits(rowFrame->GetBottomBCBorderWidth() + 1); + // get the row rect relative to the table rather than the row group + nsSize rowSize = rowFrame->GetSize(); + if (haveIntersect) { + if (aDirtyRect.YMost() >= (rowY - topBorderHalf)) { + nsTableRowFrame* fifRow = + static_cast(rowFrame->FirstInFlow()); + endRowIndex = fifRow->GetRowIndex(); + } + else done = true; + } + else { + if ((rowY + rowSize.height + bottomBorderHalf) >= aDirtyRect.y) { + mStartRg = rgFrame; + mStartRow = rowFrame; + nsTableRowFrame* fifRow = + static_cast(rowFrame->FirstInFlow()); + startRowIndex = endRowIndex = fifRow->GetRowIndex(); + haveIntersect = true; + } + else { + mInitialOffsetY += rowSize.height; + } + } + rowY += rowSize.height; + } + } + mNextOffsetY = mInitialOffsetY; + + // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag + // XXX but I don't understand it, so not changing it for now + // outer table 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; + int32_t leftCol, rightCol; // columns are in the range [leftCol, rightCol) + + nsMargin childAreaOffset = mTable->GetChildAreaOffset(nullptr); + if (mTableIsLTR) { + mInitialOffsetX = childAreaOffset.left; // x position of first col in + // damage area + leftCol = 0; + rightCol = mNumTableCols; + } else { + // x position of first col in damage area + mInitialOffsetX = mTable->GetRect().width - childAreaOffset.right; + leftCol = mNumTableCols-1; + rightCol = -1; + } + nscoord x = 0; + int32_t colX; + for (colX = leftCol; colX != rightCol; colX += mColInc) { + nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colX); + if (!colFrame) ABORT1(false); + // get the col rect relative to the table rather than the col group + nsSize size = colFrame->GetSize(); + if (haveIntersect) { + // conservatively estimate the left half border width outside the col + nscoord leftBorderHalf = + nsPresContext::CSSPixelsToAppUnits(colFrame->GetLeftBorderWidth() + 1); + if (aDirtyRect.XMost() >= (x - leftBorderHalf)) { + endColIndex = colX; + } + else break; + } + else { + // conservatively estimate the right half border width outside the col + nscoord rightBorderHalf = + nsPresContext::CSSPixelsToAppUnits(colFrame->GetRightBorderWidth() + 1); + if ((x + size.width + rightBorderHalf) >= aDirtyRect.x) { + startColIndex = endColIndex = colX; + haveIntersect = true; + } + else { + mInitialOffsetX += mColInc * size.width; + } + } + x += size.width; + } + if (!mTableIsLTR) { + uint32_t temp; + mInitialOffsetX = mTable->GetRect().width - childAreaOffset.right; + temp = startColIndex; startColIndex = endColIndex; endColIndex = temp; + for (uint32_t column = 0; column < startColIndex; column++) { + nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(column); + if (!colFrame) ABORT1(false); + nsSize size = colFrame->GetSize(); + mInitialOffsetX += mColInc * size.width; + } + } + if (!haveIntersect) + return false; + mDamageArea = nsIntRect(startColIndex, startRowIndex, + 1 + DeprecatedAbs(endColIndex - startColIndex), + 1 + endRowIndex - startRowIndex); + + Reset(); + mVerInfo = new BCVerticalSeg[mDamageArea.width + 1]; + if (!mVerInfo) + 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 (IsTableRightMost() && IsTableBottomMost()) { + mCell = nullptr; + mBCData = &mTableCellMap->mBCInfo->mLowerRightCorner; + } + else if (IsTableRightMost()) { + mCellData = nullptr; + mBCData = &mTableCellMap->mBCInfo->mRightBorders.ElementAt(aY); + } + else if (IsTableBottomMost()) { + mCellData = nullptr; + mBCData = &mTableCellMap->mBCInfo->mBottomBorders.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.x; + mPrevHorSegHeight = 0; + if (mIsRepeatedHeader) { + mRepeatedHeaderRowIndex = mRowIndex; + } + } + else { + mAtEnd = true; + } + return !mAtEnd; +} + +/** + * Advance the iterator to the next row group + */ +bool +BCPaintBorderIterator::SetNewRowGroup() +{ + + mRgIndex++; + + mIsRepeatedHeader = false; + mIsRepeatedFooter = false; + + NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds"); + if (uint32_t(mRgIndex) < mRowGroups.Length()) { + mPrevRg = mRg; + mRg = mRowGroups[mRgIndex]; + nsTableRowGroupFrame* fifRg = + static_cast(mRg->FirstInFlow()); + mFifRgFirstRowIndex = fifRg->GetStartRowIndex(); + mRgFirstRowIndex = mRg->GetStartRowIndex(); + mRgLastRowIndex = mRgFirstRowIndex + mRg->GetRowCount() - 1; + + if (SetNewRow(mRg->GetFirstRow())) { + mCellMap = mTableCellMap->GetMapFor(fifRg, nullptr); + if (!mCellMap) ABORT1(false); + } + if (mRg && mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) { + // if mRowGroup doesn't have a prev in flow, then it may be a repeated + // header or footer + const nsStyleDisplay* display = mRg->StyleDisplay(); + if (mRowIndex == mDamageArea.y) { + mIsRepeatedHeader = (NS_STYLE_DISPLAY_TABLE_HEADER_GROUP == display->mDisplay); + } + else { + mIsRepeatedFooter = (NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP == display->mDisplay); + } + } + } + else { + mAtEnd = true; + } + return !mAtEnd; +} + +/** + * Move the iterator to the first position in the damageArea + */ +void +BCPaintBorderIterator::First() +{ + if (!mTable || (mDamageArea.x >= mNumTableCols) || + (mDamageArea.y >= 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.y >= start) && (mDamageArea.y <= end)) { + mRgIndex = rgY - 1; // SetNewRowGroup increments rowGroupIndex + if (SetNewRowGroup()) { + while ((mRowIndex < mDamageArea.y) && !mAtEnd) { + SetNewRow(); + } + if (!mAtEnd) { + SetNewData(mDamageArea.y, mDamageArea.x); + } + } + return; + } + } + mAtEnd = true; +} + +/** + * Advance the iterator to the next position + */ +void +BCPaintBorderIterator::Next() +{ + if (mAtEnd) ABORT0(); + mIsNewRow = false; + + mColIndex++; + if (mColIndex > mDamageArea.XMost()) { + mRowIndex++; + if (mRowIndex == mDamageArea.YMost()) { + mColIndex = mDamageArea.x; + } + else if (mRowIndex < mDamageArea.YMost()) { + if (mRowIndex <= mRgLastRowIndex) { + SetNewRow(); + } + else { + SetNewRowGroup(); + } + } + else { + mAtEnd = true; + } + } + if (!mAtEnd) { + SetNewData(mRowIndex, mColIndex); + } +} + +// XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine +// them +/** 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(mozilla::css::Side 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 ((NS_SIDE_TOP == aCornerOwnerSide) || + (NS_SIDE_BOTTOM == aCornerOwnerSide)) { + DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); + if (aIsBevel) { + offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; + } + else { + offset = (NS_SIDE_TOP == 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 + * @param aTableIsLTR - direction, the computation depends on ltr or rtl + * @return - offset in twips + */ +static nscoord +CalcHorCornerOffset(mozilla::css::Side aCornerOwnerSide, + BCPixelSize aCornerSubWidth, + BCPixelSize aVerWidth, + bool aIsStartOfSeg, + bool aIsBevel, + bool aTableIsLTR) +{ + nscoord offset = 0; + // XXX These should be replaced with appropriate side-specific macros (which?) + BCPixelSize smallHalf, largeHalf; + if ((NS_SIDE_LEFT == aCornerOwnerSide) || + (NS_SIDE_RIGHT == aCornerOwnerSide)) { + if (aTableIsLTR) { + DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); + } + else { + DivideBCBorderSize(aCornerSubWidth, largeHalf, smallHalf); + } + if (aIsBevel) { + offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; + } + else { + offset = (NS_SIDE_LEFT == aCornerOwnerSide) ? smallHalf : -largeHalf; + } + } + else { + if (aTableIsLTR) { + DivideBCBorderSize(aVerWidth, smallHalf, largeHalf); + } + else { + DivideBCBorderSize(aVerWidth, largeHalf, smallHalf); + } + if (aIsBevel) { + offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; + } + else { + offset = (aIsStartOfSeg) ? smallHalf : -largeHalf; + } + } + return nsPresContext::CSSPixelsToAppUnits(offset); +} + +BCVerticalSeg::BCVerticalSeg() +{ + mCol = nullptr; + mFirstCell = mLastCell = mAjaCell = nullptr; + mOffsetX = mOffsetY = mLength = mWidth = mTopBevelOffset = 0; + mTopBevelSide = NS_SIDE_TOP; + mOwner = eCellOwner; +} + +/** + * Start a new vertical segment + * @param aIter - iterator containing the structural information + * @param aBorderOwner - determines the border style + * @param aVerSegWidth - the width of segment in pixel + * @param aHorSegHeight - the width of the horizontal segment joining the corner + * at the start + */ +void +BCVerticalSeg::Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aVerSegWidth, + BCPixelSize aHorSegHeight) +{ + mozilla::css::Side ownerSide = NS_SIDE_TOP; + bool bevel = false; + + + nscoord cornerSubWidth = (aIter.mBCData) ? + aIter.mBCData->GetCorner(ownerSide, bevel) : 0; + + bool topBevel = (aVerSegWidth > 0) ? bevel : false; + BCPixelSize maxHorSegHeight = std::max(aIter.mPrevHorSegHeight, aHorSegHeight); + nscoord offset = CalcVerCornerOffset(ownerSide, cornerSubWidth, + maxHorSegHeight, true, + topBevel); + + mTopBevelOffset = topBevel ? + nsPresContext::CSSPixelsToAppUnits(maxHorSegHeight): 0; + // XXX this assumes that only corners where 2 segments join can be beveled + mTopBevelSide = (aHorSegHeight > 0) ? NS_SIDE_RIGHT : NS_SIDE_LEFT; + mOffsetY += offset; + mLength = -offset; + mWidth = aVerSegWidth; + mOwner = aBorderOwner; + mFirstCell = aIter.mCell; + mFirstRowGroup = aIter.mRg; + mFirstRow = aIter.mRow; + if (aIter.GetRelativeColIndex() > 0) { + mAjaCell = aIter.mVerInfo[aIter.GetRelativeColIndex() - 1].mLastCell; + } +} + +/** + * Initialize the vertical segments with information that will persist for any + * vertical segment in this column + * @param aIter - iterator containing the structural information + */ +void +BCVerticalSeg::Initialize(BCPaintBorderIterator& aIter) +{ + int32_t relColIndex = aIter.GetRelativeColIndex(); + mCol = aIter.IsTableRightMost() ? aIter.mVerInfo[relColIndex - 1].mCol : + aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex); + if (!mCol) ABORT0(); + if (0 == relColIndex) { + mOffsetX = aIter.mInitialOffsetX; + } + // set colX for the next column + if (!aIter.IsDamageAreaRightMost()) { + aIter.mVerInfo[relColIndex + 1].mOffsetX = mOffsetX + + aIter.mColInc * mCol->GetSize().width; + } + mOffsetY = aIter.mInitialOffsetY; + mLastCell = aIter.mCell; +} + +/** + * Compute the offsets for the bottom corner of a vertical segment + * @param aIter - iterator containing the structural information + * @param aHorSegHeight - the width of the horizontal segment joining the corner + * at the start + */ +void +BCVerticalSeg::GetBottomCorner(BCPaintBorderIterator& aIter, + BCPixelSize aHorSegHeight) +{ + mozilla::css::Side ownerSide = NS_SIDE_TOP; + nscoord cornerSubWidth = 0; + bool bevel = false; + if (aIter.mBCData) { + cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); + } + mIsBottomBevel = (mWidth > 0) ? bevel : false; + mBottomHorSegHeight = std::max(aIter.mPrevHorSegHeight, aHorSegHeight); + mBottomOffset = CalcVerCornerOffset(ownerSide, cornerSubWidth, + mBottomHorSegHeight, + false, mIsBottomBevel); + mLength += mBottomOffset; +} + +/** + * Paint the vertical segment + * @param aIter - iterator containing the structural information + * @param aRenderingContext - the rendering context + * @param aHorSegHeight - the width of the horizontal segment joining the corner + * at the start + */ +void +BCVerticalSeg::Paint(BCPaintBorderIterator& aIter, + nsRenderingContext& aRenderingContext, + BCPixelSize aHorSegHeight) +{ + // get the border style, color and paint the segment + mozilla::css::Side side = (aIter.IsDamageAreaRightMost()) ? NS_SIDE_RIGHT : + NS_SIDE_LEFT; + 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; + + switch (mOwner) { + case eTableOwner: + owner = aIter.mTable; + break; + case eAjaColGroupOwner: + side = NS_SIDE_RIGHT; + if (!aIter.IsTableRightMost() && (relColIndex > 0)) { + col = aIter.mVerInfo[relColIndex - 1].mCol; + } // and fall through + case eColGroupOwner: + if (col) { + owner = col->GetParent(); + } + break; + case eAjaColOwner: + side = NS_SIDE_RIGHT; + if (!aIter.IsTableRightMost() && (relColIndex > 0)) { + col = aIter.mVerInfo[relColIndex - 1].mCol; + } // and fall through + case eColOwner: + owner = col; + break; + case eAjaRowGroupOwner: + NS_ERROR("a neighboring rowgroup can never own a vertical border"); + // and fall through + case eRowGroupOwner: + NS_ASSERTION(aIter.IsTableLeftMost() || aIter.IsTableRightMost(), + "row group can own border only at table edge"); + owner = mFirstRowGroup; + break; + case eAjaRowOwner: + NS_ASSERTION(false, "program error"); // and fall through + case eRowOwner: + NS_ASSERTION(aIter.IsTableLeftMost() || aIter.IsTableRightMost(), + "row can own border only at table edge"); + owner = mFirstRow; + break; + case eAjaCellOwner: + side = NS_SIDE_RIGHT; + cell = mAjaCell; // and fall through + case eCellOwner: + owner = cell; + break; + } + if (owner) { + ::GetPaintStyleInfo(owner, side, style, color, aIter.mTableIsLTR); + } + BCPixelSize smallHalf, largeHalf; + DivideBCBorderSize(mWidth, smallHalf, largeHalf); + nsRect segRect(mOffsetX - nsPresContext::CSSPixelsToAppUnits(largeHalf), + mOffsetY, + nsPresContext::CSSPixelsToAppUnits(mWidth), mLength); + nscoord bottomBevelOffset = (mIsBottomBevel) ? + nsPresContext::CSSPixelsToAppUnits(mBottomHorSegHeight) : 0; + mozilla::css::Side bottomBevelSide = ((aHorSegHeight > 0) ^ !aIter.mTableIsLTR) ? + NS_SIDE_RIGHT : NS_SIDE_LEFT; + mozilla::css::Side topBevelSide = ((mTopBevelSide == NS_SIDE_RIGHT) ^ !aIter.mTableIsLTR)? + NS_SIDE_RIGHT : NS_SIDE_LEFT; + nsCSSRendering::DrawTableBorderSegment(aRenderingContext, style, color, + aIter.mTableBgColor, segRect, + nsPresContext::AppUnitsPerCSSPixel(), + topBevelSide, mTopBevelOffset, + bottomBevelSide, bottomBevelOffset); +} + +/** + * Advance the start point of a segment + */ +void +BCVerticalSeg::AdvanceOffsetY() +{ + mOffsetY += mLength - mBottomOffset; +} + +/** + * Accumulate the current segment + */ +void +BCVerticalSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) +{ + mLastCell = aIter.mCell; + mLength += aIter.mRow->GetRect().height; +} + +BCHorizontalSeg::BCHorizontalSeg() +{ + mOffsetX = mOffsetY = mLength = mWidth = mLeftBevelOffset = 0; + mLeftBevelSide = NS_SIDE_TOP; + mFirstCell = mAjaCell = nullptr; +} + +/** Initialize a horizontal border segment for painting + * @param aIter - iterator storing the current and adjacent frames + * @param aBorderOwner - which frame owns the border + * @param aBottomVerSegWidth - vertical segment width coming from up + * @param aHorSegHeight - the height of the segment + + */ +void +BCHorizontalSeg::Start(BCPaintBorderIterator& aIter, + BCBorderOwner aBorderOwner, + BCPixelSize aBottomVerSegWidth, + BCPixelSize aHorSegHeight) +{ + mozilla::css::Side cornerOwnerSide = NS_SIDE_TOP; + bool bevel = false; + + mOwner = aBorderOwner; + nscoord cornerSubWidth = (aIter.mBCData) ? + aIter.mBCData->GetCorner(cornerOwnerSide, + bevel) : 0; + + bool leftBevel = (aHorSegHeight > 0) ? bevel : false; + int32_t relColIndex = aIter.GetRelativeColIndex(); + nscoord maxVerSegWidth = std::max(aIter.mVerInfo[relColIndex].mWidth, + aBottomVerSegWidth); + nscoord offset = CalcHorCornerOffset(cornerOwnerSide, cornerSubWidth, + maxVerSegWidth, true, leftBevel, + aIter.mTableIsLTR); + mLeftBevelOffset = (leftBevel && (aHorSegHeight > 0)) ? maxVerSegWidth : 0; + // XXX this assumes that only corners where 2 segments join can be beveled + mLeftBevelSide = (aBottomVerSegWidth > 0) ? NS_SIDE_BOTTOM : NS_SIDE_TOP; + if (aIter.mTableIsLTR) { + mOffsetX += offset; + } + else { + mOffsetX -= offset; + } + mLength = -offset; + mWidth = aHorSegHeight; + mFirstCell = aIter.mCell; + mAjaCell = (aIter.IsDamageAreaTopMost()) ? nullptr : + aIter.mVerInfo[relColIndex].mLastCell; +} + +/** + * Compute the offsets for the right corner of a horizontal segment + * @param aIter - iterator containing the structural information + * @param aLeftSegWidth - the width of the vertical segment joining the corner + * at the start + */ +void +BCHorizontalSeg::GetRightCorner(BCPaintBorderIterator& aIter, + BCPixelSize aLeftSegWidth) +{ + mozilla::css::Side ownerSide = NS_SIDE_TOP; + nscoord cornerSubWidth = 0; + bool bevel = false; + if (aIter.mBCData) { + cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); + } + + mIsRightBevel = (mWidth > 0) ? bevel : 0; + int32_t relColIndex = aIter.GetRelativeColIndex(); + nscoord verWidth = std::max(aIter.mVerInfo[relColIndex].mWidth, aLeftSegWidth); + mEndOffset = CalcHorCornerOffset(ownerSide, cornerSubWidth, verWidth, + false, mIsRightBevel, aIter.mTableIsLTR); + mLength += mEndOffset; + mRightBevelOffset = (mIsRightBevel) ? + nsPresContext::CSSPixelsToAppUnits(verWidth) : 0; + mRightBevelSide = (aLeftSegWidth > 0) ? NS_SIDE_BOTTOM : NS_SIDE_TOP; +} + +/** + * Paint the horizontal segment + * @param aIter - iterator containing the structural information + * @param aRenderingContext - the rendering context + */ +void +BCHorizontalSeg::Paint(BCPaintBorderIterator& aIter, + nsRenderingContext& aRenderingContext) +{ + // get the border style, color and paint the segment + mozilla::css::Side side = (aIter.IsDamageAreaBottomMost()) ? NS_SIDE_BOTTOM : + NS_SIDE_TOP; + nsIFrame* rg = aIter.mRg; if (!rg) ABORT0(); + nsIFrame* row = aIter.mRow; if (!row) ABORT0(); + nsIFrame* cell = mFirstCell; + nsIFrame* col; + nsIFrame* owner = nullptr; + + 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 a horizontal border"); + // and fall through + case eColGroupOwner: + NS_ASSERTION(aIter.IsTableTopMost() || aIter.IsTableBottomMost(), + "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 a horizontal border"); + // and fall through + case eColOwner: + NS_ASSERTION(aIter.IsTableTopMost() || aIter.IsTableBottomMost(), + "col can own border only at the table edge"); + owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1); + break; + case eAjaRowGroupOwner: + side = NS_SIDE_BOTTOM; + rg = (aIter.IsTableBottomMost()) ? aIter.mRg : aIter.mPrevRg; + // and fall through + case eRowGroupOwner: + owner = rg; + break; + case eAjaRowOwner: + side = NS_SIDE_BOTTOM; + row = (aIter.IsTableBottomMost()) ? aIter.mRow : aIter.mPrevRow; + // and fall through + case eRowOwner: + owner = row; + break; + case eAjaCellOwner: + side = NS_SIDE_BOTTOM; + // if this is null due to the damage area origin-y > 0, then the border + // won't show up anyway + cell = mAjaCell; + // and fall through + case eCellOwner: + owner = cell; + break; + } + if (owner) { + ::GetPaintStyleInfo(owner, side, style, color, aIter.mTableIsLTR); + } + BCPixelSize smallHalf, largeHalf; + DivideBCBorderSize(mWidth, smallHalf, largeHalf); + nsRect segRect(mOffsetX, + mOffsetY - nsPresContext::CSSPixelsToAppUnits(largeHalf), + mLength, + nsPresContext::CSSPixelsToAppUnits(mWidth)); + if (aIter.mTableIsLTR) { + nsCSSRendering::DrawTableBorderSegment(aRenderingContext, style, color, + aIter.mTableBgColor, segRect, + nsPresContext::AppUnitsPerCSSPixel(), + mLeftBevelSide, + nsPresContext::CSSPixelsToAppUnits(mLeftBevelOffset), + mRightBevelSide, mRightBevelOffset); + } + else { + segRect.x -= segRect.width; + nsCSSRendering::DrawTableBorderSegment(aRenderingContext, style, color, + aIter.mTableBgColor, segRect, + nsPresContext::AppUnitsPerCSSPixel(), + mRightBevelSide, mRightBevelOffset, + mLeftBevelSide, + nsPresContext::CSSPixelsToAppUnits(mLeftBevelOffset)); + } +} + +/** + * Advance the start point of a segment + */ +void +BCHorizontalSeg::AdvanceOffsetX(int32_t aIncrement) +{ + mOffsetX += aIncrement * (mLength - mEndOffset); +} + +/** + * Accumulate the current segment + */ +void +BCHorizontalSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) +{ + mLength += aIter.mVerInfo[aIter.GetRelativeColIndex()].mColWidth; +} + +/** + * store the column width information while painting horizontal segment + */ +void +BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex) +{ + if (IsTableRightMost()) { + mVerInfo[aIndex].mColWidth = mVerInfo[aIndex - 1].mColWidth; + } + else { + nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex); + if (!col) ABORT0(); + mVerInfo[aIndex].mColWidth = col->GetSize().width; + } +} +/** + * Determine if a vertical segment owns the corder + */ +bool +BCPaintBorderIterator::VerticalSegmentOwnsCorner() +{ + mozilla::css::Side cornerOwnerSide = NS_SIDE_TOP; + bool bevel = false; + if (mBCData) { + mBCData->GetCorner(cornerOwnerSide, bevel); + } + // unitialized ownerside, bevel + return (NS_SIDE_TOP == cornerOwnerSide) || + (NS_SIDE_BOTTOM == cornerOwnerSide); +} + +/** + * Paint if necessary a horizontal segment, otherwise accumulate it + * @param aRenderingContext - the rendering context + */ +void +BCPaintBorderIterator::AccumulateOrPaintHorizontalSegment(nsRenderingContext& aRenderingContext) +{ + + int32_t relColIndex = GetRelativeColIndex(); + // store the current col width if it hasn't been already + if (mVerInfo[relColIndex].mColWidth < 0) { + StoreColumnWidth(relColIndex); + } + + BCBorderOwner borderOwner = eCellOwner; + BCBorderOwner ignoreBorderOwner; + bool isSegStart = true; + bool ignoreSegStart; + + nscoord leftSegWidth = + mBCData ? mBCData->GetLeftEdge(ignoreBorderOwner, ignoreSegStart) : 0; + nscoord topSegHeight = + mBCData ? mBCData->GetTopEdge(borderOwner, isSegStart) : 0; + + if (mIsNewRow || (IsDamageAreaLeftMost() && IsDamageAreaBottomMost())) { + // reset for every new row and on the bottom of the last row + mHorSeg.mOffsetY = mNextOffsetY; + mNextOffsetY = mNextOffsetY + mRow->GetSize().height; + mHorSeg.mOffsetX = mInitialOffsetX; + mHorSeg.Start(*this, borderOwner, leftSegWidth, topSegHeight); + } + + if (!IsDamageAreaLeftMost() && (isSegStart || IsDamageAreaRightMost() || + VerticalSegmentOwnsCorner())) { + // paint the previous seg or the current one if IsDamageAreaRightMost() + if (mHorSeg.mLength > 0) { + mHorSeg.GetRightCorner(*this, leftSegWidth); + if (mHorSeg.mWidth > 0) { + mHorSeg.Paint(*this, aRenderingContext); + } + mHorSeg.AdvanceOffsetX(mColInc); + } + mHorSeg.Start(*this, borderOwner, leftSegWidth, topSegHeight); + } + mHorSeg.IncludeCurrentBorder(*this); + mVerInfo[relColIndex].mWidth = leftSegWidth; + mVerInfo[relColIndex].mLastCell = mCell; +} +/** + * Paint if necessary a vertical segment, otherwise it + * @param aRenderingContext - the rendering context + */ +void +BCPaintBorderIterator::AccumulateOrPaintVerticalSegment(nsRenderingContext& aRenderingContext) +{ + BCBorderOwner borderOwner = eCellOwner; + BCBorderOwner ignoreBorderOwner; + bool isSegStart = true; + bool ignoreSegStart; + + nscoord verSegWidth = + mBCData ? mBCData->GetLeftEdge(borderOwner, isSegStart) : 0; + nscoord horSegHeight = + mBCData ? mBCData->GetTopEdge(ignoreBorderOwner, ignoreSegStart) : 0; + + int32_t relColIndex = GetRelativeColIndex(); + BCVerticalSeg& verSeg = mVerInfo[relColIndex]; + if (!verSeg.mCol) { // on the first damaged row and the first segment in the + // col + verSeg.Initialize(*this); + verSeg.Start(*this, borderOwner, verSegWidth, horSegHeight); + } + + if (!IsDamageAreaTopMost() && (isSegStart || IsDamageAreaBottomMost() || + IsAfterRepeatedHeader() || + StartRepeatedFooter())) { + // paint the previous seg or the current one if IsDamageAreaBottomMost() + if (verSeg.mLength > 0) { + verSeg.GetBottomCorner(*this, horSegHeight); + if (verSeg.mWidth > 0) { + verSeg.Paint(*this, aRenderingContext, horSegHeight); + } + verSeg.AdvanceOffsetY(); + } + verSeg.Start(*this, borderOwner, verSegWidth, horSegHeight); + } + verSeg.IncludeCurrentBorder(*this); + mPrevHorSegHeight = horSegHeight; +} + +/** + * Reset the vertical information cache + */ +void +BCPaintBorderIterator::ResetVerInfo() +{ + if (mVerInfo) { + memset(mVerInfo, 0, mDamageArea.width * sizeof(BCVerticalSeg)); + // XXX reinitialize properly + for (int32_t xIndex = 0; xIndex < mDamageArea.width; xIndex++) { + mVerInfo[xIndex].mColWidth = -1; + } + } +} + +/** + * Method to paint BCBorders, this does not use currently display lists although + * it will do this in future + * @param aRenderingContext - the rendering context + * @param aDirtyRect - inside this rectangle the BC Borders will redrawn + */ +void +nsTableFrame::PaintBCBorders(nsRenderingContext& aRenderingContext, + 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; + + // 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 mVerInfo to be used on the next row + for (iter.First(); !iter.mAtEnd; iter.Next()) { + iter.AccumulateOrPaintVerticalSegment(aRenderingContext); + } + + // Next, paint all of the horizontal border segments from top to bottom reuse + // the mVerInfo array to keep track of col widths and vertical segments for + // corner calculations + iter.Reset(); + for (iter.First(); !iter.mAtEnd; iter.Next()) { + iter.AccumulateOrPaintHorizontalSegment(aRenderingContext); + } +} + +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->GetStateBits() & 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(); + } +}