michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsTableFrame.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsIContent.h" michael@0: #include "nsCellMap.h" michael@0: #include "nsTableCellFrame.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsTableColFrame.h" michael@0: #include "nsTableColGroupFrame.h" michael@0: #include "nsTableRowFrame.h" michael@0: #include "nsTableRowGroupFrame.h" michael@0: #include "nsTableOuterFrame.h" michael@0: #include "nsTablePainter.h" michael@0: michael@0: #include "BasicTableLayoutStrategy.h" michael@0: #include "FixedTableLayoutStrategy.h" michael@0: michael@0: #include "nsPresContext.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMHTMLElement.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsFrameManager.h" michael@0: #include "nsError.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsCSSProps.h" michael@0: #include "RestyleTracker.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layout; michael@0: michael@0: /******************************************************************************** michael@0: ** nsTableReflowState ** michael@0: ********************************************************************************/ michael@0: michael@0: struct nsTableReflowState { michael@0: michael@0: // the real reflow state michael@0: const nsHTMLReflowState& reflowState; michael@0: michael@0: // The table's available size michael@0: nsSize availSize; michael@0: michael@0: // Stationary x-offset michael@0: nscoord x; michael@0: michael@0: // Running y-offset michael@0: nscoord y; michael@0: michael@0: nsTableReflowState(nsPresContext& aPresContext, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsTableFrame& aTableFrame, michael@0: nscoord aAvailWidth, michael@0: nscoord aAvailHeight) michael@0: : reflowState(aReflowState) michael@0: { michael@0: Init(aPresContext, aTableFrame, aAvailWidth, aAvailHeight); michael@0: } michael@0: michael@0: void Init(nsPresContext& aPresContext, michael@0: nsTableFrame& aTableFrame, michael@0: nscoord aAvailWidth, michael@0: nscoord aAvailHeight) michael@0: { michael@0: nsTableFrame* table = static_cast(aTableFrame.FirstInFlow()); michael@0: nsMargin borderPadding = table->GetChildAreaOffset(&reflowState); michael@0: nscoord cellSpacingX = table->GetCellSpacingX(); michael@0: michael@0: x = borderPadding.left + cellSpacingX; michael@0: y = borderPadding.top; //cellspacing added during reflow michael@0: michael@0: availSize.width = aAvailWidth; michael@0: if (NS_UNCONSTRAINEDSIZE != availSize.width) { michael@0: availSize.width -= borderPadding.left + borderPadding.right michael@0: + (2 * cellSpacingX); michael@0: availSize.width = std::max(0, availSize.width); michael@0: } michael@0: michael@0: availSize.height = aAvailHeight; michael@0: if (NS_UNCONSTRAINEDSIZE != availSize.height) { michael@0: availSize.height -= borderPadding.top + borderPadding.bottom michael@0: + (2 * table->GetCellSpacingY()); michael@0: availSize.height = std::max(0, availSize.height); michael@0: } michael@0: } michael@0: michael@0: nsTableReflowState(nsPresContext& aPresContext, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsTableFrame& aTableFrame) michael@0: : reflowState(aReflowState) michael@0: { michael@0: Init(aPresContext, aTableFrame, aReflowState.AvailableWidth(), aReflowState.AvailableHeight()); michael@0: } michael@0: michael@0: }; michael@0: michael@0: /******************************************************************************** michael@0: ** nsTableFrame ** michael@0: ********************************************************************************/ michael@0: michael@0: struct BCPropertyData michael@0: { michael@0: BCPropertyData() : mTopBorderWidth(0), mRightBorderWidth(0), michael@0: mBottomBorderWidth(0), mLeftBorderWidth(0), michael@0: mLeftCellBorderWidth(0), mRightCellBorderWidth(0) {} michael@0: nsIntRect mDamageArea; michael@0: BCPixelSize mTopBorderWidth; michael@0: BCPixelSize mRightBorderWidth; michael@0: BCPixelSize mBottomBorderWidth; michael@0: BCPixelSize mLeftBorderWidth; michael@0: BCPixelSize mLeftCellBorderWidth; michael@0: BCPixelSize mRightCellBorderWidth; michael@0: }; michael@0: michael@0: nsIFrame* michael@0: nsTableFrame::GetParentStyleContextFrame() const michael@0: { michael@0: // Since our parent, the table outer frame, returned this frame, we michael@0: // must return whatever our parent would normally have returned. michael@0: michael@0: NS_PRECONDITION(mParent, "table constructed without outer table"); michael@0: if (!mContent->GetParent() && !StyleContext()->GetPseudo()) { michael@0: // We're the root. We have no style context parent. michael@0: return nullptr; michael@0: } michael@0: michael@0: return static_cast(GetParent())->DoGetParentStyleContextFrame(); michael@0: } michael@0: michael@0: michael@0: nsIAtom* michael@0: nsTableFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::tableFrame; michael@0: } michael@0: michael@0: michael@0: nsTableFrame::nsTableFrame(nsStyleContext* aContext) michael@0: : nsContainerFrame(aContext), michael@0: mCellMap(nullptr), michael@0: mTableLayoutStrategy(nullptr) michael@0: { michael@0: memset(&mBits, 0, sizeof(mBits)); michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_PRECONDITION(!mCellMap, "Init called twice"); michael@0: NS_PRECONDITION(!aPrevInFlow || michael@0: aPrevInFlow->GetType() == nsGkAtoms::tableFrame, michael@0: "prev-in-flow must be of same type"); michael@0: michael@0: // Let the base class do its processing michael@0: nsContainerFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: // see if border collapse is on, if so set it michael@0: const nsStyleTableBorder* tableStyle = StyleTableBorder(); michael@0: bool borderCollapse = (NS_STYLE_BORDER_COLLAPSE == tableStyle->mBorderCollapse); michael@0: SetBorderCollapse(borderCollapse); michael@0: michael@0: // Create the cell map if this frame is the first-in-flow. michael@0: if (!aPrevInFlow) { michael@0: mCellMap = new nsTableCellMap(*this, borderCollapse); michael@0: } michael@0: michael@0: if (aPrevInFlow) { michael@0: // set my width, because all frames in a table flow are the same width and michael@0: // code in nsTableOuterFrame depends on this being set michael@0: mRect.width = aPrevInFlow->GetSize().width; michael@0: } michael@0: else { michael@0: NS_ASSERTION(!mTableLayoutStrategy, "strategy was created before Init was called"); michael@0: // create the strategy michael@0: if (IsAutoLayout()) michael@0: mTableLayoutStrategy = new BasicTableLayoutStrategy(this); michael@0: else michael@0: mTableLayoutStrategy = new FixedTableLayoutStrategy(this); michael@0: } michael@0: } michael@0: michael@0: nsTableFrame::~nsTableFrame() michael@0: { michael@0: delete mCellMap; michael@0: delete mTableLayoutStrategy; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: mColGroups.DestroyFramesFrom(aDestructRoot); michael@0: nsContainerFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: // Make sure any views are positioned properly michael@0: void michael@0: nsTableFrame::RePositionViews(nsIFrame* aFrame) michael@0: { michael@0: nsContainerFrame::PositionFrameView(aFrame); michael@0: nsContainerFrame::PositionChildViews(aFrame); michael@0: } michael@0: michael@0: static bool michael@0: IsRepeatedFrame(nsIFrame* kidFrame) michael@0: { michael@0: return (kidFrame->GetType() == nsGkAtoms::tableRowFrame || michael@0: kidFrame->GetType() == nsGkAtoms::tableRowGroupFrame) && michael@0: (kidFrame->GetStateBits() & NS_REPEATED_ROW_OR_ROWGROUP); michael@0: } michael@0: michael@0: bool michael@0: nsTableFrame::PageBreakAfter(nsIFrame* aSourceFrame, michael@0: nsIFrame* aNextFrame) michael@0: { michael@0: const nsStyleDisplay* display = aSourceFrame->StyleDisplay(); michael@0: nsTableRowGroupFrame* prevRg = do_QueryFrame(aSourceFrame); michael@0: // don't allow a page break after a repeated element ... michael@0: if ((display->mBreakAfter || (prevRg && prevRg->HasInternalBreakAfter())) && michael@0: !IsRepeatedFrame(aSourceFrame)) { michael@0: return !(aNextFrame && IsRepeatedFrame(aNextFrame)); // or before michael@0: } michael@0: michael@0: if (aNextFrame) { michael@0: display = aNextFrame->StyleDisplay(); michael@0: // don't allow a page break before a repeated element ... michael@0: nsTableRowGroupFrame* nextRg = do_QueryFrame(aNextFrame); michael@0: if ((display->mBreakBefore || michael@0: (nextRg && nextRg->HasInternalBreakBefore())) && michael@0: !IsRepeatedFrame(aNextFrame)) { michael@0: return !IsRepeatedFrame(aSourceFrame); // or after michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: typedef nsTArray FrameTArray; michael@0: michael@0: /* static */ void michael@0: nsTableFrame::DestroyPositionedTablePartArray(void* aPropertyValue) michael@0: { michael@0: auto positionedObjs = static_cast(aPropertyValue); michael@0: delete positionedObjs; michael@0: } michael@0: michael@0: /* static */ void michael@0: nsTableFrame::RegisterPositionedTablePart(nsIFrame* aFrame) michael@0: { michael@0: // Supporting relative positioning for table parts other than table cells has michael@0: // the potential to break sites that apply 'position: relative' to those michael@0: // parts, expecting nothing to happen. We warn at the console to make tracking michael@0: // down the issue easy. michael@0: if (nsGkAtoms::tableCellFrame != aFrame->GetType()) { michael@0: nsIContent* content = aFrame->GetContent(); michael@0: nsPresContext* presContext = aFrame->PresContext(); michael@0: if (content && !presContext->HasWarnedAboutPositionedTableParts()) { michael@0: presContext->SetHasWarnedAboutPositionedTableParts(); michael@0: nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("Layout: Tables"), michael@0: content->OwnerDoc(), michael@0: nsContentUtils::eLAYOUT_PROPERTIES, michael@0: "TablePartRelPosWarning"); michael@0: } michael@0: } michael@0: michael@0: nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(aFrame); michael@0: MOZ_ASSERT(tableFrame, "Should have a table frame here"); michael@0: tableFrame = static_cast(tableFrame->FirstContinuation()); michael@0: michael@0: // Retrieve the positioned parts array for this table. michael@0: FrameProperties props = tableFrame->Properties(); michael@0: auto positionedParts = michael@0: static_cast(props.Get(PositionedTablePartArray())); michael@0: michael@0: // Lazily create the array if it doesn't exist yet. michael@0: if (!positionedParts) { michael@0: positionedParts = new FrameTArray; michael@0: props.Set(PositionedTablePartArray(), positionedParts); michael@0: } michael@0: michael@0: // Add this frame to the list. michael@0: positionedParts->AppendElement(aFrame); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsTableFrame::UnregisterPositionedTablePart(nsIFrame* aFrame, michael@0: nsIFrame* aDestructRoot) michael@0: { michael@0: // Retrieve the table frame, and ensure that we hit aDestructRoot on the way. michael@0: // If we don't, that means that the table frame will be destroyed, so we don't michael@0: // need to bother with unregistering this frame. michael@0: nsTableFrame* tableFrame = GetTableFramePassingThrough(aDestructRoot, aFrame); michael@0: if (!tableFrame) { michael@0: return; michael@0: } michael@0: tableFrame = static_cast(tableFrame->FirstContinuation()); michael@0: michael@0: // Retrieve the positioned parts array for this table. michael@0: FrameProperties props = tableFrame->Properties(); michael@0: auto positionedParts = michael@0: static_cast(props.Get(PositionedTablePartArray())); michael@0: michael@0: // Remove the frame. michael@0: MOZ_ASSERT(positionedParts && michael@0: positionedParts->IndexOf(aFrame) != FrameTArray::NoIndex, michael@0: "Asked to unregister a positioned table part that wasn't registered"); michael@0: if (positionedParts) { michael@0: positionedParts->RemoveElement(aFrame); michael@0: } michael@0: } michael@0: michael@0: // XXX this needs to be cleaned up so that the frame constructor breaks out col group michael@0: // frames into a separate child list, bug 343048. michael@0: nsresult michael@0: nsTableFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: michael@0: if (!mFrames.IsEmpty() || !mColGroups.IsEmpty()) { michael@0: // We already have child frames which means we've already been michael@0: // initialized michael@0: NS_NOTREACHED("unexpected second call to SetInitialChildList"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: if (aListID != kPrincipalList) { michael@0: // All we know about is the principal child list. michael@0: NS_NOTREACHED("unknown frame list"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // XXXbz the below code is an icky cesspit that's only needed in its current michael@0: // form for two reasons: michael@0: // 1) Both rowgroups and column groups come in on the principal child list. michael@0: while (aChildList.NotEmpty()) { michael@0: nsIFrame* childFrame = aChildList.FirstChild(); michael@0: aChildList.RemoveFirstChild(); michael@0: const nsStyleDisplay* childDisplay = childFrame->StyleDisplay(); michael@0: michael@0: if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == childDisplay->mDisplay) { michael@0: NS_ASSERTION(nsGkAtoms::tableColGroupFrame == childFrame->GetType(), michael@0: "This is not a colgroup"); michael@0: mColGroups.AppendFrame(nullptr, childFrame); michael@0: } michael@0: else { // row groups and unknown frames go on the main list for now michael@0: mFrames.AppendFrame(nullptr, childFrame); michael@0: } michael@0: } michael@0: michael@0: // If we have a prev-in-flow, then we're a table that has been split and michael@0: // so don't treat this like an append michael@0: if (!GetPrevInFlow()) { michael@0: // process col groups first so that real cols get constructed before michael@0: // anonymous ones due to cells in rows. michael@0: InsertColGroups(0, mColGroups); michael@0: InsertRowGroups(mFrames); michael@0: // calc collapsing borders michael@0: if (IsBorderCollapse()) { michael@0: SetFullBCDamageArea(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsTableFrame::AttributeChangedFor(nsIFrame* aFrame, michael@0: nsIContent* aContent, michael@0: nsIAtom* aAttribute) michael@0: { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(aFrame); michael@0: if (cellFrame) { michael@0: if ((nsGkAtoms::rowspan == aAttribute) || michael@0: (nsGkAtoms::colspan == aAttribute)) { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: // for now just remove the cell from the map and reinsert it michael@0: int32_t rowIndex, colIndex; michael@0: cellFrame->GetRowIndex(rowIndex); michael@0: cellFrame->GetColIndex(colIndex); michael@0: RemoveCell(cellFrame, rowIndex); michael@0: nsAutoTArray cells; michael@0: cells.AppendElement(cellFrame); michael@0: InsertCells(cells, rowIndex, colIndex - 1); michael@0: michael@0: // XXX Should this use eStyleChange? It currently doesn't need michael@0: // to, but it might given more optimization. michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: /* ****** CellMap methods ******* */ michael@0: michael@0: /* return the effective col count */ michael@0: int32_t nsTableFrame::GetEffectiveColCount() const michael@0: { michael@0: int32_t colCount = GetColCount(); michael@0: if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto) { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (!cellMap) { michael@0: return 0; michael@0: } michael@0: // don't count cols at the end that don't have originating cells michael@0: for (int32_t colX = colCount - 1; colX >= 0; colX--) { michael@0: if (cellMap->GetNumCellsOriginatingInCol(colX) > 0) { michael@0: break; michael@0: } michael@0: colCount--; michael@0: } michael@0: } michael@0: return colCount; michael@0: } michael@0: michael@0: int32_t nsTableFrame::GetIndexOfLastRealCol() michael@0: { michael@0: int32_t numCols = mColFrames.Length(); michael@0: if (numCols > 0) { michael@0: for (int32_t colX = numCols - 1; colX >= 0; colX--) { michael@0: nsTableColFrame* colFrame = GetColFrame(colX); michael@0: if (colFrame) { michael@0: if (eColAnonymousCell != colFrame->GetColType()) { michael@0: return colX; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: nsTableColFrame* michael@0: nsTableFrame::GetColFrame(int32_t aColIndex) const michael@0: { michael@0: NS_ASSERTION(!GetPrevInFlow(), "GetColFrame called on next in flow"); michael@0: int32_t numCols = mColFrames.Length(); michael@0: if ((aColIndex >= 0) && (aColIndex < numCols)) { michael@0: return mColFrames.ElementAt(aColIndex); michael@0: } michael@0: else { michael@0: NS_ERROR("invalid col index"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: int32_t nsTableFrame::GetEffectiveRowSpan(int32_t aRowIndex, michael@0: const nsTableCellFrame& aCell) const michael@0: { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: NS_PRECONDITION (nullptr != cellMap, "bad call, cellMap not yet allocated."); michael@0: michael@0: int32_t colIndex; michael@0: aCell.GetColIndex(colIndex); michael@0: return cellMap->GetEffectiveRowSpan(aRowIndex, colIndex); michael@0: } michael@0: michael@0: int32_t nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell, michael@0: nsCellMap* aCellMap) michael@0: { michael@0: nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1); michael@0: michael@0: int32_t colIndex, rowIndex; michael@0: aCell.GetColIndex(colIndex); michael@0: aCell.GetRowIndex(rowIndex); michael@0: michael@0: if (aCellMap) michael@0: return aCellMap->GetRowSpan(rowIndex, colIndex, true); michael@0: else michael@0: return tableCellMap->GetEffectiveRowSpan(rowIndex, colIndex); michael@0: } michael@0: michael@0: int32_t nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell, michael@0: nsCellMap* aCellMap) const michael@0: { michael@0: nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1); michael@0: michael@0: int32_t colIndex, rowIndex; michael@0: aCell.GetColIndex(colIndex); michael@0: aCell.GetRowIndex(rowIndex); michael@0: bool ignore; michael@0: michael@0: if (aCellMap) michael@0: return aCellMap->GetEffectiveColSpan(*tableCellMap, rowIndex, colIndex, ignore); michael@0: else michael@0: return tableCellMap->GetEffectiveColSpan(rowIndex, colIndex); michael@0: } michael@0: michael@0: bool nsTableFrame::HasMoreThanOneCell(int32_t aRowIndex) const michael@0: { michael@0: nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT1(1); michael@0: return tableCellMap->HasMoreThanOneCell(aRowIndex); michael@0: } michael@0: michael@0: void nsTableFrame::AdjustRowIndices(int32_t aRowIndex, michael@0: int32_t aAdjustment) michael@0: { michael@0: // Iterate over the row groups and adjust the row indices of all rows michael@0: // whose index is >= aRowIndex. michael@0: RowGroupArray rowGroups; michael@0: OrderRowGroups(rowGroups); michael@0: michael@0: for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: rowGroups[rgX]->AdjustRowIndices(aRowIndex, aAdjustment); michael@0: } michael@0: } michael@0: michael@0: michael@0: void nsTableFrame::ResetRowIndices(const nsFrameList::Slice& aRowGroupsToExclude) michael@0: { michael@0: // Iterate over the row groups and adjust the row indices of all rows michael@0: // omit the rowgroups that will be inserted later michael@0: RowGroupArray rowGroups; michael@0: OrderRowGroups(rowGroups); michael@0: michael@0: int32_t rowIndex = 0; michael@0: nsTHashtable > excludeRowGroups; michael@0: nsFrameList::Enumerator excludeRowGroupsEnumerator(aRowGroupsToExclude); michael@0: while (!excludeRowGroupsEnumerator.AtEnd()) { michael@0: excludeRowGroups.PutEntry(static_cast(excludeRowGroupsEnumerator.get())); michael@0: excludeRowGroupsEnumerator.Next(); michael@0: } michael@0: michael@0: for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: if (!excludeRowGroups.GetEntry(rgFrame)) { michael@0: const nsFrameList& rowFrames = rgFrame->PrincipalChildList(); michael@0: for (nsFrameList::Enumerator rows(rowFrames); !rows.AtEnd(); rows.Next()) { michael@0: if (NS_STYLE_DISPLAY_TABLE_ROW==rows.get()->StyleDisplay()->mDisplay) { michael@0: ((nsTableRowFrame *)rows.get())->SetRowIndex(rowIndex); michael@0: rowIndex++; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: void nsTableFrame::InsertColGroups(int32_t aStartColIndex, michael@0: const nsFrameList::Slice& aColGroups) michael@0: { michael@0: int32_t colIndex = aStartColIndex; michael@0: nsFrameList::Enumerator colGroups(aColGroups); michael@0: for (; !colGroups.AtEnd(); colGroups.Next()) { michael@0: MOZ_ASSERT(colGroups.get()->GetType() == nsGkAtoms::tableColGroupFrame); michael@0: nsTableColGroupFrame* cgFrame = michael@0: static_cast(colGroups.get()); michael@0: cgFrame->SetStartColumnIndex(colIndex); michael@0: // XXXbz this sucks. AddColsToTable will actually remove colgroups from michael@0: // the list we're traversing! Need to fix things here. :( I guess this is michael@0: // why the old code used pointer-to-last-frame as opposed to michael@0: // pointer-to-frame-after-last.... michael@0: michael@0: // How about dealing with this by storing a const reference to the michael@0: // mNextSibling of the framelist's last frame, instead of storing a pointer michael@0: // to the first-after-next frame? Will involve making nsFrameList friend michael@0: // of nsIFrame, but it's time for that anyway. michael@0: cgFrame->AddColsToTable(colIndex, false, michael@0: colGroups.get()->PrincipalChildList()); michael@0: int32_t numCols = cgFrame->GetColCount(); michael@0: colIndex += numCols; michael@0: } michael@0: michael@0: nsFrameList::Enumerator remainingColgroups = colGroups.GetUnlimitedEnumerator(); michael@0: if (!remainingColgroups.AtEnd()) { michael@0: nsTableColGroupFrame::ResetColIndices( michael@0: static_cast(remainingColgroups.get()), colIndex); michael@0: } michael@0: } michael@0: michael@0: void nsTableFrame::InsertCol(nsTableColFrame& aColFrame, michael@0: int32_t aColIndex) michael@0: { michael@0: mColFrames.InsertElementAt(aColIndex, &aColFrame); michael@0: nsTableColType insertedColType = aColFrame.GetColType(); michael@0: int32_t numCacheCols = mColFrames.Length(); michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: int32_t numMapCols = cellMap->GetColCount(); michael@0: if (numCacheCols > numMapCols) { michael@0: bool removedFromCache = false; michael@0: if (eColAnonymousCell != insertedColType) { michael@0: nsTableColFrame* lastCol = mColFrames.ElementAt(numCacheCols - 1); michael@0: if (lastCol) { michael@0: nsTableColType lastColType = lastCol->GetColType(); michael@0: if (eColAnonymousCell == lastColType) { michael@0: // remove the col from the cache michael@0: mColFrames.RemoveElementAt(numCacheCols - 1); michael@0: // remove the col from the eColGroupAnonymousCell col group michael@0: nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame *)mColGroups.LastChild(); michael@0: if (lastColGroup) { michael@0: lastColGroup->RemoveChild(*lastCol, false); michael@0: michael@0: // remove the col group if it is empty michael@0: if (lastColGroup->GetColCount() <= 0) { michael@0: mColGroups.DestroyFrame((nsIFrame*)lastColGroup); michael@0: } michael@0: } michael@0: removedFromCache = true; michael@0: } michael@0: } michael@0: } michael@0: if (!removedFromCache) { michael@0: cellMap->AddColsAtEnd(1); michael@0: } michael@0: } michael@0: } michael@0: // for now, just bail and recalc all of the collapsing borders michael@0: if (IsBorderCollapse()) { michael@0: nsIntRect damageArea(aColIndex, 0, 1, GetRowCount()); michael@0: AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: michael@0: void nsTableFrame::RemoveCol(nsTableColGroupFrame* aColGroupFrame, michael@0: int32_t aColIndex, michael@0: bool aRemoveFromCache, michael@0: bool aRemoveFromCellMap) michael@0: { michael@0: if (aRemoveFromCache) { michael@0: mColFrames.RemoveElementAt(aColIndex); michael@0: } michael@0: if (aRemoveFromCellMap) { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: AppendAnonymousColFrames(1); michael@0: } michael@0: } michael@0: // for now, just bail and recalc all of the collapsing borders michael@0: if (IsBorderCollapse()) { michael@0: nsIntRect damageArea(0, 0, GetColCount(), GetRowCount()); michael@0: AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: michael@0: /** Get the cell map for this table frame. It is not always mCellMap. michael@0: * Only the first-in-flow has a legit cell map. michael@0: */ michael@0: nsTableCellMap* michael@0: nsTableFrame::GetCellMap() const michael@0: { michael@0: return static_cast(FirstInFlow())->mCellMap; michael@0: } michael@0: michael@0: // XXX this needs to be moved to nsCSSFrameConstructor michael@0: nsTableColGroupFrame* michael@0: nsTableFrame::CreateAnonymousColGroupFrame(nsTableColGroupType aColGroupType) michael@0: { michael@0: nsIContent* colGroupContent = GetContent(); michael@0: nsPresContext* presContext = PresContext(); michael@0: nsIPresShell *shell = presContext->PresShell(); michael@0: michael@0: nsRefPtr colGroupStyle; michael@0: colGroupStyle = shell->StyleSet()-> michael@0: ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableColGroup, mStyleContext); michael@0: // Create a col group frame michael@0: nsIFrame* newFrame = NS_NewTableColGroupFrame(shell, colGroupStyle); michael@0: ((nsTableColGroupFrame *)newFrame)->SetColType(aColGroupType); michael@0: newFrame->Init(colGroupContent, this, nullptr); michael@0: return (nsTableColGroupFrame *)newFrame; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd) michael@0: { michael@0: // get the last col group frame michael@0: nsTableColGroupFrame* colGroupFrame = michael@0: static_cast(mColGroups.LastChild()); michael@0: michael@0: if (!colGroupFrame || michael@0: (colGroupFrame->GetColType() != eColGroupAnonymousCell)) { michael@0: int32_t colIndex = (colGroupFrame) ? michael@0: colGroupFrame->GetStartColumnIndex() + michael@0: colGroupFrame->GetColCount() : 0; michael@0: colGroupFrame = CreateAnonymousColGroupFrame(eColGroupAnonymousCell); michael@0: if (!colGroupFrame) { michael@0: return; michael@0: } michael@0: // add the new frame to the child list michael@0: mColGroups.AppendFrame(this, colGroupFrame); michael@0: colGroupFrame->SetStartColumnIndex(colIndex); michael@0: } michael@0: AppendAnonymousColFrames(colGroupFrame, aNumColsToAdd, eColAnonymousCell, michael@0: true); michael@0: michael@0: } michael@0: michael@0: // XXX this needs to be moved to nsCSSFrameConstructor michael@0: // Right now it only creates the col frames at the end michael@0: void michael@0: nsTableFrame::AppendAnonymousColFrames(nsTableColGroupFrame* aColGroupFrame, michael@0: int32_t aNumColsToAdd, michael@0: nsTableColType aColType, michael@0: bool aAddToTable) michael@0: { michael@0: NS_PRECONDITION(aColGroupFrame, "null frame"); michael@0: NS_PRECONDITION(aColType != eColAnonymousCol, "Shouldn't happen"); michael@0: michael@0: nsIPresShell *shell = PresContext()->PresShell(); michael@0: michael@0: // Get the last col frame michael@0: nsFrameList newColFrames; michael@0: michael@0: int32_t startIndex = mColFrames.Length(); michael@0: int32_t lastIndex = startIndex + aNumColsToAdd - 1; michael@0: michael@0: for (int32_t childX = startIndex; childX <= lastIndex; childX++) { michael@0: nsIContent* iContent; michael@0: nsRefPtr styleContext; michael@0: nsStyleContext* parentStyleContext; michael@0: michael@0: // all anonymous cols that we create here use a pseudo style context of the michael@0: // col group michael@0: iContent = aColGroupFrame->GetContent(); michael@0: parentStyleContext = aColGroupFrame->StyleContext(); michael@0: styleContext = shell->StyleSet()-> michael@0: ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableCol, parentStyleContext); michael@0: // ASSERTION to check for bug 54454 sneaking back in... michael@0: NS_ASSERTION(iContent, "null content in CreateAnonymousColFrames"); michael@0: michael@0: // create the new col frame michael@0: nsIFrame* colFrame = NS_NewTableColFrame(shell, styleContext); michael@0: ((nsTableColFrame *) colFrame)->SetColType(aColType); michael@0: colFrame->Init(iContent, aColGroupFrame, nullptr); michael@0: michael@0: newColFrames.AppendFrame(nullptr, colFrame); michael@0: } michael@0: nsFrameList& cols = aColGroupFrame->GetWritableChildList(); michael@0: nsIFrame* oldLastCol = cols.LastChild(); michael@0: const nsFrameList::Slice& newCols = michael@0: cols.InsertFrames(nullptr, oldLastCol, newColFrames); michael@0: if (aAddToTable) { michael@0: // get the starting col index in the cache michael@0: int32_t startColIndex; michael@0: if (oldLastCol) { michael@0: startColIndex = michael@0: static_cast(oldLastCol)->GetColIndex() + 1; michael@0: } else { michael@0: startColIndex = aColGroupFrame->GetStartColumnIndex(); michael@0: } michael@0: michael@0: aColGroupFrame->AddColsToTable(startColIndex, true, newCols); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::MatchCellMapToColCache(nsTableCellMap* aCellMap) michael@0: { michael@0: int32_t numColsInMap = GetColCount(); michael@0: int32_t numColsInCache = mColFrames.Length(); michael@0: int32_t numColsToAdd = numColsInMap - numColsInCache; michael@0: if (numColsToAdd > 0) { michael@0: // this sets the child list, updates the col cache and cell map michael@0: AppendAnonymousColFrames(numColsToAdd); michael@0: } michael@0: if (numColsToAdd < 0) { michael@0: int32_t numColsNotRemoved = DestroyAnonymousColFrames(-numColsToAdd); michael@0: // if the cell map has fewer cols than the cache, correct it michael@0: if (numColsNotRemoved > 0) { michael@0: aCellMap->AddColsAtEnd(numColsNotRemoved); michael@0: } michael@0: } michael@0: if (numColsToAdd && HasZeroColSpans()) { michael@0: SetNeedColSpanExpansion(true); michael@0: } michael@0: if (NeedColSpanExpansion()) { michael@0: // This flag can be set in two ways -- either by changing michael@0: // the number of columns (that happens in the block above), michael@0: // or by adding a cell with colspan="0" to the cellmap. To michael@0: // handle the latter case we need to explicitly check the michael@0: // flag here -- it may be set even if the number of columns michael@0: // did not change. michael@0: // michael@0: // @see nsCellMap::AppendCell michael@0: michael@0: aCellMap->ExpandZeroColSpans(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::DidResizeColumns() michael@0: { michael@0: NS_PRECONDITION(!GetPrevInFlow(), michael@0: "should only be called on first-in-flow"); michael@0: if (mBits.mResizedColumns) michael@0: return; // already marked michael@0: michael@0: for (nsTableFrame *f = this; f; michael@0: f = static_cast(f->GetNextInFlow())) michael@0: f->mBits.mResizedColumns = true; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::AppendCell(nsTableCellFrame& aCellFrame, michael@0: int32_t aRowIndex) michael@0: { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: nsIntRect damageArea(0,0,0,0); michael@0: cellMap->AppendCell(aCellFrame, aRowIndex, true, damageArea); michael@0: MatchCellMapToColCache(cellMap); michael@0: if (IsBorderCollapse()) { michael@0: AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void nsTableFrame::InsertCells(nsTArray& aCellFrames, michael@0: int32_t aRowIndex, michael@0: int32_t aColIndexBefore) michael@0: { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: nsIntRect damageArea(0,0,0,0); michael@0: cellMap->InsertCells(aCellFrames, aRowIndex, aColIndexBefore, damageArea); michael@0: MatchCellMapToColCache(cellMap); michael@0: if (IsBorderCollapse()) { michael@0: AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // this removes the frames from the col group and table, but not the cell map michael@0: int32_t michael@0: nsTableFrame::DestroyAnonymousColFrames(int32_t aNumFrames) michael@0: { michael@0: // only remove cols that are of type eTypeAnonymous cell (they are at the end) michael@0: int32_t endIndex = mColFrames.Length() - 1; michael@0: int32_t startIndex = (endIndex - aNumFrames) + 1; michael@0: int32_t numColsRemoved = 0; michael@0: for (int32_t colX = endIndex; colX >= startIndex; colX--) { michael@0: nsTableColFrame* colFrame = GetColFrame(colX); michael@0: if (colFrame && (eColAnonymousCell == colFrame->GetColType())) { michael@0: nsTableColGroupFrame* cgFrame = michael@0: static_cast(colFrame->GetParent()); michael@0: // remove the frame from the colgroup michael@0: cgFrame->RemoveChild(*colFrame, false); michael@0: // remove the frame from the cache, but not the cell map michael@0: RemoveCol(nullptr, colX, true, false); michael@0: numColsRemoved++; michael@0: } michael@0: else { michael@0: break; michael@0: } michael@0: } michael@0: return (aNumFrames - numColsRemoved); michael@0: } michael@0: michael@0: void nsTableFrame::RemoveCell(nsTableCellFrame* aCellFrame, michael@0: int32_t aRowIndex) michael@0: { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: nsIntRect damageArea(0,0,0,0); michael@0: cellMap->RemoveCell(aCellFrame, aRowIndex, damageArea); michael@0: MatchCellMapToColCache(cellMap); michael@0: if (IsBorderCollapse()) { michael@0: AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsTableFrame::GetStartRowIndex(nsTableRowGroupFrame* aRowGroupFrame) michael@0: { michael@0: RowGroupArray orderedRowGroups; michael@0: OrderRowGroups(orderedRowGroups); michael@0: michael@0: int32_t rowIndex = 0; michael@0: for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { michael@0: nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; michael@0: if (rgFrame == aRowGroupFrame) { michael@0: break; michael@0: } michael@0: int32_t numRows = rgFrame->GetRowCount(); michael@0: rowIndex += numRows; michael@0: } michael@0: return rowIndex; michael@0: } michael@0: michael@0: // this cannot extend beyond a single row group michael@0: void nsTableFrame::AppendRows(nsTableRowGroupFrame* aRowGroupFrame, michael@0: int32_t aRowIndex, michael@0: nsTArray& aRowFrames) michael@0: { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: int32_t absRowIndex = GetStartRowIndex(aRowGroupFrame) + aRowIndex; michael@0: InsertRows(aRowGroupFrame, aRowFrames, absRowIndex, true); michael@0: } michael@0: } michael@0: michael@0: // this cannot extend beyond a single row group michael@0: int32_t michael@0: nsTableFrame::InsertRows(nsTableRowGroupFrame* aRowGroupFrame, michael@0: nsTArray& aRowFrames, michael@0: int32_t aRowIndex, michael@0: bool aConsiderSpans) michael@0: { michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== insertRowsBefore firstRow=%d \n", aRowIndex); michael@0: Dump(true, false, true); michael@0: #endif michael@0: michael@0: int32_t numColsToAdd = 0; michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: nsIntRect damageArea(0,0,0,0); michael@0: int32_t origNumRows = cellMap->GetRowCount(); michael@0: int32_t numNewRows = aRowFrames.Length(); michael@0: cellMap->InsertRows(aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans, damageArea); michael@0: MatchCellMapToColCache(cellMap); michael@0: if (aRowIndex < origNumRows) { michael@0: AdjustRowIndices(aRowIndex, numNewRows); michael@0: } michael@0: // assign the correct row indices to the new rows. If they were adjusted above michael@0: // it may not have been done correctly because each row is constructed with index 0 michael@0: for (int32_t rowY = 0; rowY < numNewRows; rowY++) { michael@0: nsTableRowFrame* rowFrame = aRowFrames.ElementAt(rowY); michael@0: rowFrame->SetRowIndex(aRowIndex + rowY); michael@0: } michael@0: if (IsBorderCollapse()) { michael@0: AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== insertRowsAfter \n"); michael@0: Dump(true, false, true); michael@0: #endif michael@0: michael@0: return numColsToAdd; michael@0: } michael@0: michael@0: // this cannot extend beyond a single row group michael@0: void nsTableFrame::RemoveRows(nsTableRowFrame& aFirstRowFrame, michael@0: int32_t aNumRowsToRemove, michael@0: bool aConsiderSpans) michael@0: { michael@0: #ifdef TBD_OPTIMIZATION michael@0: // decide if we need to rebalance. we have to do this here because the row group michael@0: // cannot do it when it gets the dirty reflow corresponding to the frame being destroyed michael@0: bool stopTelling = false; michael@0: for (nsIFrame* kidFrame = aFirstFrame.FirstChild(); (kidFrame && !stopAsking); michael@0: kidFrame = kidFrame->GetNextSibling()) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(kidFrame); michael@0: if (cellFrame) { michael@0: stopTelling = tableFrame->CellChangedWidth(*cellFrame, cellFrame->GetPass1MaxElementWidth(), michael@0: cellFrame->GetMaximumWidth(), true); michael@0: } michael@0: } michael@0: // XXX need to consider what happens if there are cells that have rowspans michael@0: // into the deleted row. Need to consider moving rows if a rebalance doesn't happen michael@0: #endif michael@0: michael@0: int32_t firstRowIndex = aFirstRowFrame.GetRowIndex(); michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== removeRowsBefore firstRow=%d numRows=%d\n", firstRowIndex, aNumRowsToRemove); michael@0: Dump(true, false, true); michael@0: #endif michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: nsIntRect damageArea(0,0,0,0); michael@0: cellMap->RemoveRows(firstRowIndex, aNumRowsToRemove, aConsiderSpans, damageArea); michael@0: MatchCellMapToColCache(cellMap); michael@0: if (IsBorderCollapse()) { michael@0: AddBCDamageArea(damageArea); michael@0: } michael@0: } michael@0: AdjustRowIndices(firstRowIndex, -aNumRowsToRemove); michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== removeRowsAfter\n"); michael@0: Dump(true, true, true); michael@0: #endif michael@0: } michael@0: michael@0: // collect the rows ancestors of aFrame michael@0: int32_t michael@0: nsTableFrame::CollectRows(nsIFrame* aFrame, michael@0: nsTArray& aCollection) michael@0: { michael@0: NS_PRECONDITION(aFrame, "null frame"); michael@0: int32_t numRows = 0; michael@0: nsIFrame* childFrame = aFrame->GetFirstPrincipalChild(); michael@0: while (childFrame) { michael@0: aCollection.AppendElement(static_cast(childFrame)); michael@0: numRows++; michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: return numRows; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::InsertRowGroups(const nsFrameList::Slice& aRowGroups) michael@0: { michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== insertRowGroupsBefore\n"); michael@0: Dump(true, false, true); michael@0: #endif michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: RowGroupArray orderedRowGroups; michael@0: OrderRowGroups(orderedRowGroups); michael@0: michael@0: nsAutoTArray rows; michael@0: // Loop over the rowgroups and check if some of them are new, if they are michael@0: // insert cellmaps in the order that is predefined by OrderRowGroups, michael@0: // XXXbz this code is O(N*M) where N is number of new rowgroups michael@0: // and M is number of rowgroups we have! michael@0: uint32_t rgIndex; michael@0: for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { michael@0: for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd(); michael@0: rowgroups.Next()) { michael@0: if (orderedRowGroups[rgIndex] == rowgroups.get()) { michael@0: nsTableRowGroupFrame* priorRG = michael@0: (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; michael@0: // create and add the cell map for the row group michael@0: cellMap->InsertGroupCellMap(orderedRowGroups[rgIndex], priorRG); michael@0: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: cellMap->Synchronize(this); michael@0: ResetRowIndices(aRowGroups); michael@0: michael@0: //now that the cellmaps are reordered too insert the rows michael@0: for (rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { michael@0: for (nsFrameList::Enumerator rowgroups(aRowGroups); !rowgroups.AtEnd(); michael@0: rowgroups.Next()) { michael@0: if (orderedRowGroups[rgIndex] == rowgroups.get()) { michael@0: nsTableRowGroupFrame* priorRG = michael@0: (0 == rgIndex) ? nullptr : orderedRowGroups[rgIndex - 1]; michael@0: // collect the new row frames in an array and add them to the table michael@0: int32_t numRows = CollectRows(rowgroups.get(), rows); michael@0: if (numRows > 0) { michael@0: int32_t rowIndex = 0; michael@0: if (priorRG) { michael@0: int32_t priorNumRows = priorRG->GetRowCount(); michael@0: rowIndex = priorRG->GetStartRowIndex() + priorNumRows; michael@0: } michael@0: InsertRows(orderedRowGroups[rgIndex], rows, rowIndex, true); michael@0: rows.Clear(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: } michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== insertRowGroupsAfter\n"); michael@0: Dump(true, true, true); michael@0: #endif michael@0: } michael@0: michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // Child frame enumeration michael@0: michael@0: const nsFrameList& michael@0: nsTableFrame::GetChildList(ChildListID aListID) const michael@0: { michael@0: if (aListID == kColGroupList) { michael@0: return mColGroups; michael@0: } michael@0: return nsContainerFrame::GetChildList(aListID); michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::GetChildLists(nsTArray* aLists) const michael@0: { michael@0: nsContainerFrame::GetChildLists(aLists); michael@0: mColGroups.AppendIfNonempty(aLists, kColGroupList); michael@0: } michael@0: michael@0: nsRect michael@0: nsDisplayTableItem::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { michael@0: *aSnap = false; michael@0: return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); michael@0: } michael@0: michael@0: bool michael@0: nsDisplayTableItem::IsVaryingRelativeToMovingFrame(nsDisplayListBuilder* aBuilder, michael@0: nsIFrame* aFrame) michael@0: { michael@0: if (!mPartHasFixedBackground) michael@0: return false; michael@0: michael@0: // If aFrame is mFrame or an ancestor in this document, and aFrame is michael@0: // not the viewport frame, then moving aFrame will move mFrame michael@0: // relative to the viewport, so our fixed-pos background will change. michael@0: return mFrame == aFrame || michael@0: nsLayoutUtils::IsProperAncestorFrame(aFrame, mFrame); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsDisplayTableItem::UpdateForFrameBackground(nsIFrame* aFrame) michael@0: { michael@0: nsStyleContext *bgSC; michael@0: if (!nsCSSRendering::FindBackground(aFrame, &bgSC)) michael@0: return; michael@0: if (!bgSC->StyleBackground()->HasFixedBackground()) michael@0: return; michael@0: michael@0: mPartHasFixedBackground = true; michael@0: } michael@0: michael@0: class nsDisplayTableBorderBackground : public nsDisplayTableItem { michael@0: public: michael@0: nsDisplayTableBorderBackground(nsDisplayListBuilder* aBuilder, michael@0: nsTableFrame* aFrame) : michael@0: nsDisplayTableItem(aBuilder, aFrame) { michael@0: MOZ_COUNT_CTOR(nsDisplayTableBorderBackground); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayTableBorderBackground() { michael@0: MOZ_COUNT_DTOR(nsDisplayTableBorderBackground); michael@0: } michael@0: #endif michael@0: michael@0: virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion *aInvalidRegion) MOZ_OVERRIDE; michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: NS_DISPLAY_DECL_NAME("TableBorderBackground", TYPE_TABLE_BORDER_BACKGROUND) michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: static bool michael@0: IsFrameAllowedInTable(nsIAtom* aType) michael@0: { michael@0: return IS_TABLE_CELL(aType) || michael@0: nsGkAtoms::tableRowFrame == aType || michael@0: nsGkAtoms::tableRowGroupFrame == aType || michael@0: nsGkAtoms::scrollFrame == aType || michael@0: nsGkAtoms::tableFrame == aType || michael@0: nsGkAtoms::tableColFrame == aType || michael@0: nsGkAtoms::tableColGroupFrame == aType; michael@0: } michael@0: #endif michael@0: michael@0: /* static */ bool michael@0: nsTableFrame::AnyTablePartHasUndecodedBackgroundImage(nsIFrame* aStart, michael@0: nsIFrame* aEnd) michael@0: { michael@0: for (nsIFrame* f = aStart; f != aEnd; f = f->GetNextSibling()) { michael@0: NS_ASSERTION(IsFrameAllowedInTable(f->GetType()), "unexpected frame type"); michael@0: michael@0: if (!nsCSSRendering::AreAllBackgroundImagesDecodedForFrame(f)) michael@0: return true; michael@0: michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(f); michael@0: if (cellFrame) michael@0: continue; michael@0: michael@0: if (AnyTablePartHasUndecodedBackgroundImage(f->PrincipalChildList().FirstChild(), nullptr)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsDisplayTableBorderBackground::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion *aInvalidRegion) michael@0: { michael@0: if (aBuilder->ShouldSyncDecodeImages()) { michael@0: if (nsTableFrame::AnyTablePartHasUndecodedBackgroundImage(mFrame, mFrame->GetNextSibling()) || michael@0: nsTableFrame::AnyTablePartHasUndecodedBackgroundImage( michael@0: mFrame->GetChildList(nsIFrame::kColGroupList).FirstChild(), nullptr)) { michael@0: bool snap; michael@0: aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); michael@0: } michael@0: } michael@0: michael@0: nsDisplayTableItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); michael@0: } michael@0: michael@0: void michael@0: nsDisplayTableBorderBackground::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: static_cast(mFrame)-> michael@0: PaintTableBorderBackground(*aCtx, mVisibleRect, michael@0: ToReferenceFrame(), michael@0: aBuilder->GetBackgroundPaintFlags()); michael@0: } michael@0: michael@0: static int32_t GetTablePartRank(nsDisplayItem* aItem) michael@0: { michael@0: nsIAtom* type = aItem->Frame()->GetType(); michael@0: if (type == nsGkAtoms::tableFrame) michael@0: return 0; michael@0: if (type == nsGkAtoms::tableRowGroupFrame) michael@0: return 1; michael@0: if (type == nsGkAtoms::tableRowFrame) michael@0: return 2; michael@0: return 3; michael@0: } michael@0: michael@0: static bool CompareByTablePartRank(nsDisplayItem* aItem1, nsDisplayItem* aItem2, michael@0: void* aClosure) michael@0: { michael@0: return GetTablePartRank(aItem1) <= GetTablePartRank(aItem2); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsTableFrame::GenericTraversal(nsDisplayListBuilder* aBuilder, nsFrame* aFrame, michael@0: const nsRect& aDirtyRect, const nsDisplayListSet& aLists) michael@0: { michael@0: // This is similar to what nsContainerFrame::BuildDisplayListForNonBlockChildren michael@0: // does, except that we allow the children's background and borders to go michael@0: // in our BorderBackground list. This doesn't really affect background michael@0: // painting --- the children won't actually draw their own backgrounds michael@0: // because the nsTableFrame already drew them, unless a child has its own michael@0: // stacking context, in which case the child won't use its passed-in michael@0: // BorderBackground list anyway. It does affect cell borders though; this michael@0: // lets us get cell borders into the nsTableFrame's BorderBackground list. michael@0: nsIFrame* kid = aFrame->GetFirstPrincipalChild(); michael@0: while (kid) { michael@0: aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists); michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsTableFrame::DisplayGenericTablePart(nsDisplayListBuilder* aBuilder, michael@0: nsFrame* aFrame, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists, michael@0: nsDisplayTableItem* aDisplayItem, michael@0: DisplayGenericTablePartTraversal aTraversal) michael@0: { michael@0: nsDisplayList eventsBorderBackground; michael@0: // If we need to sort the event backgrounds, then we'll put descendants' michael@0: // display items into their own set of lists. michael@0: bool sortEventBackgrounds = aDisplayItem && aBuilder->IsForEventDelivery(); michael@0: nsDisplayListCollection separatedCollection; michael@0: const nsDisplayListSet* lists = sortEventBackgrounds ? &separatedCollection : &aLists; michael@0: michael@0: nsAutoPushCurrentTableItem pushTableItem; michael@0: if (aDisplayItem) { michael@0: pushTableItem.Push(aBuilder, aDisplayItem); michael@0: } michael@0: michael@0: if (aFrame->IsVisibleForPainting(aBuilder)) { michael@0: nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem(); michael@0: // currentItem may be null, when none of the table parts have a michael@0: // background or border michael@0: if (currentItem) { michael@0: currentItem->UpdateForFrameBackground(aFrame); michael@0: } michael@0: michael@0: // Paint the outset box-shadows for the table frames michael@0: bool hasBoxShadow = aFrame->StyleBorder()->mBoxShadow != nullptr; michael@0: if (hasBoxShadow) { michael@0: lists->BorderBackground()->AppendNewToTop( michael@0: new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, aFrame)); michael@0: } michael@0: michael@0: // Create dedicated background display items per-frame when we're michael@0: // handling events. michael@0: // XXX how to handle collapsed borders? michael@0: if (aBuilder->IsForEventDelivery()) { michael@0: nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, michael@0: lists->BorderBackground()); michael@0: } michael@0: michael@0: // Paint the inset box-shadows for the table frames michael@0: if (hasBoxShadow) { michael@0: lists->BorderBackground()->AppendNewToTop( michael@0: new (aBuilder) nsDisplayBoxShadowInner(aBuilder, aFrame)); michael@0: } michael@0: } michael@0: michael@0: aTraversal(aBuilder, aFrame, aDirtyRect, *lists); michael@0: michael@0: if (sortEventBackgrounds) { michael@0: // Ensure that the table frame event background goes before the michael@0: // table rowgroups event backgrounds, before the table row event backgrounds, michael@0: // before everything else (cells and their blocks) michael@0: separatedCollection.BorderBackground()->Sort(aBuilder, CompareByTablePartRank, nullptr); michael@0: separatedCollection.MoveTo(aLists); michael@0: } michael@0: michael@0: aFrame->DisplayOutline(aBuilder, aLists); michael@0: } michael@0: michael@0: static bool michael@0: AnyTablePartHasBorderOrBackground(nsIFrame* aStart, nsIFrame* aEnd) michael@0: { michael@0: for (nsIFrame* f = aStart; f != aEnd; f = f->GetNextSibling()) { michael@0: NS_ASSERTION(IsFrameAllowedInTable(f->GetType()), "unexpected frame type"); michael@0: michael@0: if (FrameHasBorderOrBackground(f)) michael@0: return true; michael@0: michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(f); michael@0: if (cellFrame) michael@0: continue; michael@0: michael@0: if (AnyTablePartHasBorderOrBackground(f->PrincipalChildList().FirstChild(), nullptr)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // table paint code is concerned primarily with borders and bg color michael@0: // SEC: TODO: adjust the rect for captions michael@0: void michael@0: nsTableFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT_DSP_COLOR("nsTableFrame", NS_RGB(255,128,255)); michael@0: michael@0: nsDisplayTableItem* item = nullptr; michael@0: if (IsVisibleInSelection(aBuilder)) { michael@0: if (StyleVisibility()->IsVisible()) { michael@0: nsMargin deflate = GetDeflationForBackground(PresContext()); michael@0: // If 'deflate' is (0,0,0,0) then we can paint the table background michael@0: // in its own display item, so do that to take advantage of michael@0: // opacity and visibility optimizations michael@0: if (deflate == nsMargin(0, 0, 0, 0)) { michael@0: DisplayBackgroundUnconditional(aBuilder, aLists, false); michael@0: } michael@0: } michael@0: michael@0: // This background is created if any of the table parts are visible, michael@0: // or if we're doing event handling (since DisplayGenericTablePart michael@0: // needs the item for the |sortEventBackgrounds|-dependent code). michael@0: // Specific visibility decisions are delegated to the table background michael@0: // painter, which handles borders and backgrounds for the table. michael@0: if (aBuilder->IsForEventDelivery() || michael@0: AnyTablePartHasBorderOrBackground(this, GetNextSibling()) || michael@0: AnyTablePartHasBorderOrBackground(mColGroups.FirstChild(), nullptr)) { michael@0: item = new (aBuilder) nsDisplayTableBorderBackground(aBuilder, this); michael@0: aLists.BorderBackground()->AppendNewToTop(item); michael@0: } michael@0: } michael@0: DisplayGenericTablePart(aBuilder, this, aDirtyRect, aLists, item); michael@0: } michael@0: michael@0: nsMargin michael@0: nsTableFrame::GetDeflationForBackground(nsPresContext* aPresContext) const michael@0: { michael@0: if (eCompatibility_NavQuirks != aPresContext->CompatibilityMode() || michael@0: !IsBorderCollapse()) michael@0: return nsMargin(0,0,0,0); michael@0: michael@0: return GetOuterBCBorder(); michael@0: } michael@0: michael@0: // XXX We don't put the borders and backgrounds in tree order like we should. michael@0: // That requires some major surgery which we aren't going to do right now. michael@0: void michael@0: nsTableFrame::PaintTableBorderBackground(nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nsPoint aPt, uint32_t aBGPaintFlags) michael@0: { michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: TableBackgroundPainter painter(this, TableBackgroundPainter::eOrigin_Table, michael@0: presContext, aRenderingContext, michael@0: aDirtyRect, aPt, aBGPaintFlags); michael@0: nsMargin deflate = GetDeflationForBackground(presContext); michael@0: // If 'deflate' is (0,0,0,0) then we'll paint the table background michael@0: // in a separate display item, so don't do it here. michael@0: nsresult rv = painter.PaintTable(this, deflate, deflate != nsMargin(0, 0, 0, 0)); michael@0: if (NS_FAILED(rv)) return; michael@0: michael@0: if (StyleVisibility()->IsVisible()) { michael@0: if (!IsBorderCollapse()) { michael@0: int skipSides = GetSkipSides(); michael@0: nsRect rect(aPt, mRect.Size()); michael@0: nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, michael@0: aDirtyRect, rect, mStyleContext, skipSides); michael@0: } michael@0: else { michael@0: // XXX we should probably get rid of this translation at some stage michael@0: // But that would mean modifying PaintBCBorders, ugh michael@0: nsRenderingContext::AutoPushTranslation translate(&aRenderingContext, aPt); michael@0: PaintBCBorders(aRenderingContext, aDirtyRect - aPt); michael@0: } michael@0: } michael@0: } michael@0: michael@0: int michael@0: nsTableFrame::GetLogicalSkipSides(const nsHTMLReflowState* aReflowState) const michael@0: { michael@0: int skip = 0; michael@0: // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto michael@0: // account for pagination michael@0: if (nullptr != GetPrevInFlow()) { michael@0: skip |= LOGICAL_SIDE_B_START; michael@0: } michael@0: if (nullptr != GetNextInFlow()) { michael@0: skip |= LOGICAL_SIDE_B_END; michael@0: } michael@0: return skip; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::SetColumnDimensions(nscoord aHeight, michael@0: const nsMargin& aBorderPadding) michael@0: { michael@0: nscoord cellSpacingX = GetCellSpacingX(); michael@0: nscoord cellSpacingY = GetCellSpacingY(); michael@0: nscoord colHeight = aHeight -= aBorderPadding.top + aBorderPadding.bottom + michael@0: 2* cellSpacingY; michael@0: michael@0: nsTableIterator iter(mColGroups); michael@0: nsIFrame* colGroupFrame = iter.First(); michael@0: bool tableIsLTR = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR; michael@0: int32_t colX =tableIsLTR ? 0 : std::max(0, GetColCount() - 1); michael@0: int32_t tableColIncr = tableIsLTR ? 1 : -1; michael@0: nsPoint colGroupOrigin(aBorderPadding.left + cellSpacingX, michael@0: aBorderPadding.top + cellSpacingY); michael@0: while (colGroupFrame) { michael@0: MOZ_ASSERT(colGroupFrame->GetType() == nsGkAtoms::tableColGroupFrame); michael@0: nscoord colGroupWidth = 0; michael@0: nsTableIterator iterCol(*colGroupFrame); michael@0: nsIFrame* colFrame = iterCol.First(); michael@0: nsPoint colOrigin(0,0); michael@0: while (colFrame) { michael@0: if (NS_STYLE_DISPLAY_TABLE_COLUMN == michael@0: colFrame->StyleDisplay()->mDisplay) { michael@0: NS_ASSERTION(colX < GetColCount(), "invalid number of columns"); michael@0: nscoord colWidth = GetColumnWidth(colX); michael@0: nsRect colRect(colOrigin.x, colOrigin.y, colWidth, colHeight); michael@0: colFrame->SetRect(colRect); michael@0: colOrigin.x += colWidth + cellSpacingX; michael@0: colGroupWidth += colWidth + cellSpacingX; michael@0: colX += tableColIncr; michael@0: } michael@0: colFrame = iterCol.Next(); michael@0: } michael@0: if (colGroupWidth) { michael@0: colGroupWidth -= cellSpacingX; michael@0: } michael@0: michael@0: nsRect colGroupRect(colGroupOrigin.x, colGroupOrigin.y, colGroupWidth, colHeight); michael@0: colGroupFrame->SetRect(colGroupRect); michael@0: colGroupFrame = iter.Next(); michael@0: colGroupOrigin.x += colGroupWidth + cellSpacingX; michael@0: } michael@0: } michael@0: michael@0: // SEC: TODO need to worry about continuing frames prev/next in flow for splitting across pages. michael@0: michael@0: // XXX this could be made more general to handle row modifications that change the michael@0: // table height, but first we need to scrutinize every Invalidate michael@0: void michael@0: nsTableFrame::ProcessRowInserted(nscoord aNewHeight) michael@0: { michael@0: SetRowInserted(false); // reset the bit that got us here michael@0: nsTableFrame::RowGroupArray rowGroups; michael@0: OrderRowGroups(rowGroups); michael@0: // find the row group containing the inserted row michael@0: for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: NS_ASSERTION(rgFrame, "Must have rgFrame here"); michael@0: nsIFrame* childFrame = rgFrame->GetFirstPrincipalChild(); michael@0: // find the row that was inserted first michael@0: while (childFrame) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(childFrame); michael@0: if (rowFrame) { michael@0: if (rowFrame->IsFirstInserted()) { michael@0: rowFrame->SetFirstInserted(false); michael@0: // damage the table from the 1st row inserted to the end of the table michael@0: nsIFrame::InvalidateFrame(); michael@0: // XXXbz didn't we do this up front? Why do we need to do it again? michael@0: SetRowInserted(false); michael@0: return; // found it, so leave michael@0: } michael@0: } michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsTableFrame::MarkIntrinsicWidthsDirty() michael@0: { michael@0: nsITableLayoutStrategy* tls = LayoutStrategy(); michael@0: if (MOZ_UNLIKELY(!tls)) { michael@0: // This is a FrameNeedsReflow() from nsBlockFrame::RemoveFrame() michael@0: // walking up the ancestor chain in a table next-in-flow. In this case michael@0: // our original first-in-flow (which owns the TableLayoutStrategy) has michael@0: // already been destroyed and unhooked from the flow chain and thusly michael@0: // LayoutStrategy() returns null. All the frames in the flow will be michael@0: // destroyed so no need to mark anything dirty here. See bug 595758. michael@0: return; michael@0: } michael@0: tls->MarkIntrinsicWidthsDirty(); michael@0: michael@0: // XXXldb Call SetBCDamageArea? michael@0: michael@0: nsContainerFrame::MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsTableFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: if (NeedToCalcBCBorders()) michael@0: CalcBCBorders(); michael@0: michael@0: ReflowColGroups(aRenderingContext); michael@0: michael@0: return LayoutStrategy()->GetMinWidth(aRenderingContext); michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsTableFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: if (NeedToCalcBCBorders()) michael@0: CalcBCBorders(); michael@0: michael@0: ReflowColGroups(aRenderingContext); michael@0: michael@0: return LayoutStrategy()->GetPrefWidth(aRenderingContext, false); michael@0: } michael@0: michael@0: /* virtual */ nsIFrame::IntrinsicWidthOffsetData michael@0: nsTableFrame::IntrinsicWidthOffsets(nsRenderingContext* aRenderingContext) michael@0: { michael@0: IntrinsicWidthOffsetData result = michael@0: nsContainerFrame::IntrinsicWidthOffsets(aRenderingContext); michael@0: michael@0: result.hMargin = 0; michael@0: result.hPctMargin = 0; michael@0: michael@0: if (IsBorderCollapse()) { michael@0: result.hPadding = 0; michael@0: result.hPctPadding = 0; michael@0: michael@0: nsMargin outerBC = GetIncludedOuterBCBorder(); michael@0: result.hBorder = outerBC.LeftRight(); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsTableFrame::ComputeSize(nsRenderingContext *aRenderingContext, michael@0: nsSize aCBSize, nscoord aAvailableWidth, michael@0: nsSize aMargin, nsSize aBorder, nsSize aPadding, michael@0: uint32_t aFlags) michael@0: { michael@0: nsSize result = michael@0: nsContainerFrame::ComputeSize(aRenderingContext, aCBSize, aAvailableWidth, michael@0: aMargin, aBorder, aPadding, aFlags); michael@0: michael@0: // If we're a container for font size inflation, then shrink michael@0: // wrapping inside of us should not apply font size inflation. michael@0: AutoMaybeDisableFontInflation an(this); michael@0: michael@0: // Tables never shrink below their min width. michael@0: nscoord minWidth = GetMinWidth(aRenderingContext); michael@0: if (minWidth > result.width) michael@0: result.width = minWidth; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nscoord michael@0: nsTableFrame::TableShrinkWidthToFit(nsRenderingContext *aRenderingContext, michael@0: nscoord aWidthInCB) michael@0: { michael@0: // If we're a container for font size inflation, then shrink michael@0: // wrapping inside of us should not apply font size inflation. michael@0: AutoMaybeDisableFontInflation an(this); michael@0: michael@0: nscoord result; michael@0: nscoord minWidth = GetMinWidth(aRenderingContext); michael@0: if (minWidth > aWidthInCB) { michael@0: result = minWidth; michael@0: } else { michael@0: // Tables shrink width to fit with a slightly different algorithm michael@0: // from the one they use for their intrinsic widths (the difference michael@0: // relates to handling of percentage widths on columns). So this michael@0: // function differs from nsFrame::ShrinkWidthToFit by only the michael@0: // following line. michael@0: // Since we've already called GetMinWidth, we don't need to do any michael@0: // of the other stuff GetPrefWidth does. michael@0: nscoord prefWidth = michael@0: LayoutStrategy()->GetPrefWidth(aRenderingContext, true); michael@0: if (prefWidth > aWidthInCB) { michael@0: result = aWidthInCB; michael@0: } else { michael@0: result = prefWidth; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsTableFrame::ComputeAutoSize(nsRenderingContext *aRenderingContext, michael@0: nsSize aCBSize, nscoord aAvailableWidth, michael@0: nsSize aMargin, nsSize aBorder, nsSize aPadding, michael@0: bool aShrinkWrap) michael@0: { michael@0: // Tables always shrink-wrap. michael@0: nscoord cbBased = aAvailableWidth - aMargin.width - aBorder.width - michael@0: aPadding.width; michael@0: return nsSize(TableShrinkWidthToFit(aRenderingContext, cbBased), michael@0: NS_UNCONSTRAINEDSIZE); michael@0: } michael@0: michael@0: // Return true if aParentReflowState.frame or any of its ancestors within michael@0: // the containing table have non-auto height. (e.g. pct or fixed height) michael@0: bool michael@0: nsTableFrame::AncestorsHaveStyleHeight(const nsHTMLReflowState& aParentReflowState) michael@0: { michael@0: for (const nsHTMLReflowState* rs = &aParentReflowState; michael@0: rs && rs->frame; rs = rs->parentReflowState) { michael@0: nsIAtom* frameType = rs->frame->GetType(); michael@0: if (IS_TABLE_CELL(frameType) || michael@0: (nsGkAtoms::tableRowFrame == frameType) || michael@0: (nsGkAtoms::tableRowGroupFrame == frameType)) { michael@0: const nsStyleCoord &height = rs->mStylePosition->mHeight; michael@0: // calc() with percentages treated like 'auto' on internal table elements michael@0: if (height.GetUnit() != eStyleUnit_Auto && michael@0: (!height.IsCalcUnit() || !height.HasPercent())) { michael@0: return true; michael@0: } michael@0: } michael@0: else if (nsGkAtoms::tableFrame == frameType) { michael@0: // we reached the containing table, so always return michael@0: return rs->mStylePosition->mHeight.GetUnit() != eStyleUnit_Auto; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // See if a special height reflow needs to occur and if so, call RequestSpecialHeightReflow michael@0: void michael@0: nsTableFrame::CheckRequestSpecialHeightReflow(const nsHTMLReflowState& aReflowState) michael@0: { michael@0: NS_ASSERTION(IS_TABLE_CELL(aReflowState.frame->GetType()) || michael@0: aReflowState.frame->GetType() == nsGkAtoms::tableRowFrame || michael@0: aReflowState.frame->GetType() == nsGkAtoms::tableRowGroupFrame || michael@0: aReflowState.frame->GetType() == nsGkAtoms::tableFrame, michael@0: "unexpected frame type"); michael@0: if (!aReflowState.frame->GetPrevInFlow() && // 1st in flow michael@0: (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight() || // no computed height michael@0: 0 == aReflowState.ComputedHeight()) && michael@0: eStyleUnit_Percent == aReflowState.mStylePosition->mHeight.GetUnit() && // pct height michael@0: nsTableFrame::AncestorsHaveStyleHeight(*aReflowState.parentReflowState)) { michael@0: nsTableFrame::RequestSpecialHeightReflow(aReflowState); michael@0: } michael@0: } michael@0: michael@0: // Notify the frame and its ancestors (up to the containing table) that a special michael@0: // height reflow will occur. During a special height reflow, a table, row group, michael@0: // row, or cell returns the last size it was reflowed at. However, the table may michael@0: // change the height of row groups, rows, cells in DistributeHeightToRows after. michael@0: // And the row group can change the height of rows, cells in CalculateRowHeights. michael@0: void michael@0: nsTableFrame::RequestSpecialHeightReflow(const nsHTMLReflowState& aReflowState) michael@0: { michael@0: // notify the frame and its ancestors of the special reflow, stopping at the containing table michael@0: for (const nsHTMLReflowState* rs = &aReflowState; rs && rs->frame; rs = rs->parentReflowState) { michael@0: nsIAtom* frameType = rs->frame->GetType(); michael@0: NS_ASSERTION(IS_TABLE_CELL(frameType) || michael@0: nsGkAtoms::tableRowFrame == frameType || michael@0: nsGkAtoms::tableRowGroupFrame == frameType || michael@0: nsGkAtoms::tableFrame == frameType, michael@0: "unexpected frame type"); michael@0: michael@0: rs->frame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT); michael@0: if (nsGkAtoms::tableFrame == frameType) { michael@0: NS_ASSERTION(rs != &aReflowState, michael@0: "should not request special height reflow for table"); michael@0: // always stop when we reach a table michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /****************************************************************************************** michael@0: * Before reflow, intrinsic width calculation is done using GetMinWidth michael@0: * and GetPrefWidth. This used to be known as pass 1 reflow. michael@0: * michael@0: * After the intrinsic width calculation, the table determines the michael@0: * column widths using BalanceColumnWidths() and michael@0: * then reflows each child again with a constrained avail width. This reflow is referred to michael@0: * as the pass 2 reflow. michael@0: * michael@0: * A special height reflow (pass 3 reflow) can occur during an initial or resize reflow michael@0: * if (a) a row group, row, cell, or a frame inside a cell has a percent height but no computed michael@0: * height or (b) in paginated mode, a table has a height. (a) supports percent nested tables michael@0: * contained inside cells whose heights aren't known until after the pass 2 reflow. (b) is michael@0: * necessary because the table cannot split until after the pass 2 reflow. The mechanics of michael@0: * the special height reflow (variety a) are as follows: michael@0: * michael@0: * 1) Each table related frame (table, row group, row, cell) implements NeedsSpecialReflow() michael@0: * to indicate that it should get the reflow. It does this when it has a percent height but michael@0: * no computed height by calling CheckRequestSpecialHeightReflow(). This method calls michael@0: * RequestSpecialHeightReflow() which calls SetNeedSpecialReflow() on its ancestors until michael@0: * it reaches the containing table and calls SetNeedToInitiateSpecialReflow() on it. For michael@0: * percent height frames inside cells, during DidReflow(), the cell's NotifyPercentHeight() michael@0: * is called (the cell is the reflow state's mPercentHeightObserver in this case). michael@0: * NotifyPercentHeight() calls RequestSpecialHeightReflow(). michael@0: * michael@0: * 2) After the pass 2 reflow, if the table's NeedToInitiateSpecialReflow(true) was called, it michael@0: * will do the special height reflow, setting the reflow state's mFlags.mSpecialHeightReflow michael@0: * to true and mSpecialHeightInitiator to itself. It won't do this if IsPrematureSpecialHeightReflow() michael@0: * returns true because in that case another special height reflow will be coming along with the michael@0: * containing table as the mSpecialHeightInitiator. It is only relevant to do the reflow when michael@0: * the mSpecialHeightInitiator is the containing table, because if it is a remote ancestor, then michael@0: * appropriate heights will not be known. michael@0: * michael@0: * 3) Since the heights of the table, row groups, rows, and cells was determined during the pass 2 michael@0: * reflow, they return their last desired sizes during the special height reflow. The reflow only michael@0: * permits percent height frames inside the cells to resize based on the cells height and that height michael@0: * was determined during the pass 2 reflow. michael@0: * michael@0: * So, in the case of deeply nested tables, all of the tables that were told to initiate a special michael@0: * reflow will do so, but if a table is already in a special reflow, it won't inititate the reflow michael@0: * until the current initiator is its containing table. Since these reflows are only received by michael@0: * frames that need them and they don't cause any rebalancing of tables, the extra overhead is minimal. michael@0: * michael@0: * The type of special reflow that occurs during printing (variety b) follows the same mechanism except michael@0: * that all frames will receive the reflow even if they don't really need them. michael@0: * michael@0: * Open issues with the special height reflow: michael@0: * michael@0: * 1) At some point there should be 2 kinds of special height reflows because (a) and (b) above are michael@0: * really quite different. This would avoid unnecessary reflows during printing. michael@0: * 2) When a cell contains frames whose percent heights > 100%, there is data loss (see bug 115245). michael@0: * However, this can also occur if a cell has a fixed height and there is no special height reflow. michael@0: * michael@0: * XXXldb Special height reflow should really be its own method, not michael@0: * part of nsIFrame::Reflow. It should then call nsIFrame::Reflow on michael@0: * the contents of the cells to do the necessary vertical resizing. michael@0: * michael@0: ******************************************************************************************/ michael@0: michael@0: /* Layout the entire inner table. */ michael@0: nsresult nsTableFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsTableFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: bool isPaginated = aPresContext->IsPaginated(); michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: if (!GetPrevInFlow() && !mTableLayoutStrategy) { michael@0: NS_ASSERTION(false, "strategy should have been created in Init"); michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: nsresult rv = NS_OK; michael@0: michael@0: // see if collapsing borders need to be calculated michael@0: if (!GetPrevInFlow() && IsBorderCollapse() && NeedToCalcBCBorders()) { michael@0: CalcBCBorders(); michael@0: } michael@0: michael@0: aDesiredSize.Width() = aReflowState.AvailableWidth(); michael@0: michael@0: // Check for an overflow list, and append any row group frames being pushed michael@0: MoveOverflowToChildList(); michael@0: michael@0: bool haveDesiredHeight = false; michael@0: SetHaveReflowedColGroups(false); michael@0: michael@0: // Reflow the entire table (pass 2 and possibly pass 3). This phase is necessary during a michael@0: // constrained initial reflow and other reflows which require either a strategy init or balance. michael@0: // This isn't done during an unconstrained reflow, because it will occur later when the parent michael@0: // reflows with a constrained width. michael@0: if (NS_SUBTREE_DIRTY(this) || michael@0: aReflowState.ShouldReflowAllKids() || michael@0: IsGeometryDirty() || michael@0: aReflowState.mFlags.mVResize) { michael@0: michael@0: if (aReflowState.ComputedHeight() != NS_UNCONSTRAINEDSIZE || michael@0: // Also check mVResize, to handle the first Reflow preceding a michael@0: // special height Reflow, when we've already had a special height michael@0: // Reflow (where mComputedHeight would not be michael@0: // NS_UNCONSTRAINEDSIZE, but without a style change in between). michael@0: aReflowState.mFlags.mVResize) { michael@0: // XXX Eventually, we should modify DistributeHeightToRows to use michael@0: // nsTableRowFrame::GetHeight instead of nsIFrame::GetSize().height. michael@0: // That way, it will make its calculations based on internal table michael@0: // frame heights as they are before they ever had any extra height michael@0: // distributed to them. In the meantime, this reflows all the michael@0: // internal table frames, which restores them to their state before michael@0: // DistributeHeightToRows was called. michael@0: SetGeometryDirty(); michael@0: } michael@0: michael@0: bool needToInitiateSpecialReflow = michael@0: !!(GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT); michael@0: // see if an extra reflow will be necessary in pagination mode when there is a specified table height michael@0: if (isPaginated && !GetPrevInFlow() && (NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight())) { michael@0: nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState); michael@0: if ((tableSpecifiedHeight > 0) && michael@0: (tableSpecifiedHeight != NS_UNCONSTRAINEDSIZE)) { michael@0: needToInitiateSpecialReflow = true; michael@0: } michael@0: } michael@0: nsIFrame* lastChildReflowed = nullptr; michael@0: michael@0: NS_ASSERTION(!aReflowState.mFlags.mSpecialHeightReflow, michael@0: "Shouldn't be in special height reflow here!"); michael@0: michael@0: // do the pass 2 reflow unless this is a special height reflow and we will be michael@0: // initiating a special height reflow michael@0: // XXXldb I changed this. Should I change it back? michael@0: michael@0: // if we need to initiate a special height reflow, then don't constrain the michael@0: // height of the reflow before that michael@0: nscoord availHeight = needToInitiateSpecialReflow michael@0: ? NS_UNCONSTRAINEDSIZE : aReflowState.AvailableHeight(); michael@0: michael@0: ReflowTable(aDesiredSize, aReflowState, availHeight, michael@0: lastChildReflowed, aStatus); michael@0: michael@0: // reevaluate special height reflow conditions michael@0: if (GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT) michael@0: needToInitiateSpecialReflow = true; michael@0: michael@0: // XXXldb Are all these conditions correct? michael@0: if (needToInitiateSpecialReflow && NS_FRAME_IS_COMPLETE(aStatus)) { michael@0: // XXXldb Do we need to set the mVResize flag on any reflow states? michael@0: michael@0: nsHTMLReflowState &mutable_rs = michael@0: const_cast(aReflowState); michael@0: michael@0: // distribute extra vertical space to rows michael@0: CalcDesiredHeight(aReflowState, aDesiredSize); michael@0: mutable_rs.mFlags.mSpecialHeightReflow = true; michael@0: michael@0: ReflowTable(aDesiredSize, aReflowState, aReflowState.AvailableHeight(), michael@0: lastChildReflowed, aStatus); michael@0: michael@0: if (lastChildReflowed && NS_FRAME_IS_NOT_COMPLETE(aStatus)) { michael@0: // if there is an incomplete child, then set the desired height to include it but not the next one michael@0: nsMargin borderPadding = GetChildAreaOffset(&aReflowState); michael@0: aDesiredSize.Height() = borderPadding.bottom + GetCellSpacingY() + michael@0: lastChildReflowed->GetRect().YMost(); michael@0: } michael@0: haveDesiredHeight = true; michael@0: michael@0: mutable_rs.mFlags.mSpecialHeightReflow = false; michael@0: } michael@0: } michael@0: else { michael@0: // Calculate the overflow area contribution from our children. michael@0: for (nsIFrame* kid = GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) { michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kid); michael@0: } michael@0: } michael@0: michael@0: aDesiredSize.Width() = aReflowState.ComputedWidth() + michael@0: aReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: if (!haveDesiredHeight) { michael@0: CalcDesiredHeight(aReflowState, aDesiredSize); michael@0: } michael@0: if (IsRowInserted()) { michael@0: ProcessRowInserted(aDesiredSize.Height()); michael@0: } michael@0: michael@0: nsMargin borderPadding = GetChildAreaOffset(&aReflowState); michael@0: SetColumnDimensions(aDesiredSize.Height(), borderPadding); michael@0: if (NeedToCollapse() && michael@0: (NS_UNCONSTRAINEDSIZE != aReflowState.AvailableWidth())) { michael@0: AdjustForCollapsingRowsCols(aDesiredSize, borderPadding); michael@0: } michael@0: michael@0: // If there are any relatively-positioned table parts, we need to reflow their michael@0: // absolutely-positioned descendants now that their dimensions are final. michael@0: FixupPositionedTableParts(aPresContext, aDesiredSize, aReflowState); michael@0: michael@0: // make sure the table overflow area does include the table rect. michael@0: nsRect tableRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height()) ; michael@0: michael@0: if (!ShouldApplyOverflowClipping(this, aReflowState.mStyleDisplay)) { michael@0: // collapsed border may leak out michael@0: nsMargin bcMargin = GetExcludedOuterBCBorder(); michael@0: tableRect.Inflate(bcMargin); michael@0: } michael@0: aDesiredSize.mOverflowAreas.UnionAllWith(tableRect); michael@0: michael@0: if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) || michael@0: nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) { michael@0: nsIFrame::InvalidateFrame(); michael@0: } michael@0: michael@0: FinishAndStoreOverflow(&aDesiredSize); michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::FixupPositionedTableParts(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState) michael@0: { michael@0: auto positionedParts = michael@0: static_cast(Properties().Get(PositionedTablePartArray())); michael@0: if (!positionedParts) { michael@0: return; michael@0: } michael@0: michael@0: OverflowChangedTracker overflowTracker; michael@0: overflowTracker.SetSubtreeRoot(this); michael@0: michael@0: for (size_t i = 0; i < positionedParts->Length(); ++i) { michael@0: nsIFrame* positionedPart = positionedParts->ElementAt(i); michael@0: michael@0: // As we've already finished reflow, positionedParts's size and overflow michael@0: // areas have already been assigned, so we just pull them back out. michael@0: nsSize size(positionedPart->GetSize()); michael@0: nsHTMLReflowMetrics desiredSize(aReflowState.GetWritingMode()); michael@0: desiredSize.Width() = size.width; michael@0: desiredSize.Height() = size.height; michael@0: desiredSize.mOverflowAreas = positionedPart->GetOverflowAreasRelativeToSelf(); michael@0: michael@0: // Construct a dummy reflow state and reflow status. michael@0: // XXX(seth): Note that the dummy reflow state doesn't have a correct michael@0: // chain of parent reflow states. It also doesn't necessarily have a michael@0: // correct containing block. michael@0: nsHTMLReflowState reflowState(aPresContext, positionedPart, michael@0: aReflowState.rendContext, michael@0: nsSize(size.width, NS_UNCONSTRAINEDSIZE), michael@0: nsHTMLReflowState::DUMMY_PARENT_REFLOW_STATE); michael@0: nsReflowStatus reflowStatus = NS_FRAME_COMPLETE; michael@0: michael@0: // Reflow absolutely-positioned descendants of the positioned part. michael@0: // FIXME: Unconditionally using NS_UNCONSTRAINEDSIZE for the height and michael@0: // ignoring any change to the reflow status aren't correct. We'll never michael@0: // paginate absolutely positioned frames. michael@0: overflowTracker.AddFrame(positionedPart, michael@0: OverflowChangedTracker::CHILDREN_AND_PARENT_CHANGED); michael@0: nsFrame* positionedFrame = static_cast(positionedPart); michael@0: positionedFrame->FinishReflowWithAbsoluteFrames(PresContext(), michael@0: desiredSize, michael@0: reflowState, michael@0: reflowStatus, michael@0: true); michael@0: } michael@0: michael@0: // Propagate updated overflow areas up the tree. michael@0: overflowTracker.Flush(); michael@0: michael@0: // Update our own overflow areas. (OverflowChangedTracker doesn't update the michael@0: // subtree root itself.) michael@0: aDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: nsLayoutUtils::UnionChildOverflow(this, aDesiredSize.mOverflowAreas); michael@0: } michael@0: michael@0: bool michael@0: nsTableFrame::UpdateOverflow() michael@0: { michael@0: nsRect bounds(nsPoint(0, 0), GetSize()); michael@0: michael@0: // As above in Reflow, make sure the table overflow area includes the table michael@0: // rect, and check for collapsed borders leaking out. michael@0: if (!ShouldApplyOverflowClipping(this, StyleDisplay())) { michael@0: nsMargin bcMargin = GetExcludedOuterBCBorder(); michael@0: bounds.Inflate(bcMargin); michael@0: } michael@0: michael@0: nsOverflowAreas overflowAreas(bounds, bounds); michael@0: nsLayoutUtils::UnionChildOverflow(this, overflowAreas); michael@0: michael@0: return FinishAndStoreOverflow(overflowAreas, GetSize()); michael@0: } michael@0: michael@0: nsresult michael@0: nsTableFrame::ReflowTable(nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nscoord aAvailHeight, michael@0: nsIFrame*& aLastChildReflowed, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: aLastChildReflowed = nullptr; michael@0: michael@0: if (!GetPrevInFlow()) { michael@0: mTableLayoutStrategy->ComputeColumnWidths(aReflowState); michael@0: } michael@0: // Constrain our reflow width to the computed table width (of the 1st in flow). michael@0: // and our reflow height to our avail height minus border, padding, cellspacing michael@0: aDesiredSize.Width() = aReflowState.ComputedWidth() + michael@0: aReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: nsTableReflowState reflowState(*PresContext(), aReflowState, *this, michael@0: aDesiredSize.Width(), aAvailHeight); michael@0: ReflowChildren(reflowState, aStatus, aLastChildReflowed, michael@0: aDesiredSize.mOverflowAreas); michael@0: michael@0: ReflowColGroups(aReflowState.rendContext); michael@0: return rv; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsTableFrame::GetFirstBodyRowGroupFrame() michael@0: { michael@0: nsIFrame* headerFrame = nullptr; michael@0: nsIFrame* footerFrame = nullptr; michael@0: michael@0: for (nsIFrame* kidFrame = mFrames.FirstChild(); nullptr != kidFrame; ) { michael@0: const nsStyleDisplay* childDisplay = kidFrame->StyleDisplay(); michael@0: michael@0: // We expect the header and footer row group frames to be first, and we only michael@0: // allow one header and one footer michael@0: if (NS_STYLE_DISPLAY_TABLE_HEADER_GROUP == childDisplay->mDisplay) { michael@0: if (headerFrame) { michael@0: // We already have a header frame and so this header frame is treated michael@0: // like an ordinary body row group frame michael@0: return kidFrame; michael@0: } michael@0: headerFrame = kidFrame; michael@0: michael@0: } else if (NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP == childDisplay->mDisplay) { michael@0: if (footerFrame) { michael@0: // We already have a footer frame and so this footer frame is treated michael@0: // like an ordinary body row group frame michael@0: return kidFrame; michael@0: } michael@0: footerFrame = kidFrame; michael@0: michael@0: } else if (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == childDisplay->mDisplay) { michael@0: return kidFrame; michael@0: } michael@0: michael@0: // Get the next child michael@0: kidFrame = kidFrame->GetNextSibling(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: // Table specific version that takes into account repeated header and footer michael@0: // frames when continuing table frames michael@0: void michael@0: nsTableFrame::PushChildren(const RowGroupArray& aRowGroups, michael@0: int32_t aPushFrom) michael@0: { michael@0: NS_PRECONDITION(aPushFrom > 0, "pushing first child"); michael@0: michael@0: // extract the frames from the array into a sibling list michael@0: nsFrameList frames; michael@0: uint32_t childX; michael@0: for (childX = aPushFrom; childX < aRowGroups.Length(); ++childX) { michael@0: nsTableRowGroupFrame* rgFrame = aRowGroups[childX]; michael@0: if (!rgFrame->IsRepeatable()) { michael@0: mFrames.RemoveFrame(rgFrame); michael@0: frames.AppendFrame(nullptr, rgFrame); michael@0: } michael@0: } michael@0: michael@0: if (frames.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: nsTableFrame* nextInFlow = static_cast(GetNextInFlow()); michael@0: if (nextInFlow) { michael@0: // Insert the frames after any repeated header and footer frames. michael@0: nsIFrame* firstBodyFrame = nextInFlow->GetFirstBodyRowGroupFrame(); michael@0: nsIFrame* prevSibling = nullptr; michael@0: if (firstBodyFrame) { michael@0: prevSibling = firstBodyFrame->GetPrevSibling(); michael@0: } michael@0: // When pushing and pulling frames we need to check for whether any michael@0: // views need to be reparented. michael@0: ReparentFrameViewList(frames, this, nextInFlow); michael@0: nextInFlow->mFrames.InsertFrames(nextInFlow, prevSibling, michael@0: frames); michael@0: } michael@0: else { michael@0: // Add the frames to our overflow list. michael@0: SetOverflowFrames(frames); michael@0: } michael@0: } michael@0: michael@0: // collapsing row groups, rows, col groups and cols are accounted for after both passes of michael@0: // reflow so that it has no effect on the calculations of reflow. michael@0: void michael@0: nsTableFrame::AdjustForCollapsingRowsCols(nsHTMLReflowMetrics& aDesiredSize, michael@0: nsMargin aBorderPadding) michael@0: { michael@0: nscoord yTotalOffset = 0; // total offset among all rows in all row groups michael@0: michael@0: // reset the bit, it will be set again if row/rowgroup or col/colgroup are michael@0: // collapsed michael@0: SetNeedToCollapse(false); michael@0: michael@0: // collapse the rows and/or row groups as necessary michael@0: // Get the ordered children michael@0: RowGroupArray rowGroups; michael@0: OrderRowGroups(rowGroups); michael@0: michael@0: nsTableFrame* firstInFlow = static_cast(FirstInFlow()); michael@0: nscoord width = firstInFlow->GetCollapsedWidth(aBorderPadding); michael@0: nscoord rgWidth = width - 2 * GetCellSpacingX(); michael@0: nsOverflowAreas overflow; michael@0: // Walk the list of children michael@0: for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[childX]; michael@0: NS_ASSERTION(rgFrame, "Must have row group frame here"); michael@0: yTotalOffset += rgFrame->CollapseRowGroupIfNecessary(yTotalOffset, rgWidth); michael@0: ConsiderChildOverflow(overflow, rgFrame); michael@0: } michael@0: michael@0: aDesiredSize.Height() -= yTotalOffset; michael@0: aDesiredSize.Width() = width; michael@0: overflow.UnionAllWith(nsRect(0, 0, aDesiredSize.Width(), aDesiredSize.Height())); michael@0: FinishAndStoreOverflow(overflow, michael@0: nsSize(aDesiredSize.Width(), aDesiredSize.Height())); michael@0: } michael@0: michael@0: michael@0: nscoord michael@0: nsTableFrame::GetCollapsedWidth(nsMargin aBorderPadding) michael@0: { michael@0: NS_ASSERTION(!GetPrevInFlow(), "GetCollapsedWidth called on next in flow"); michael@0: nscoord cellSpacingX = GetCellSpacingX(); michael@0: nscoord width = cellSpacingX; michael@0: width += aBorderPadding.left + aBorderPadding.right; michael@0: for (nsIFrame* groupFrame = mColGroups.FirstChild(); groupFrame; michael@0: groupFrame = groupFrame->GetNextSibling()) { michael@0: const nsStyleVisibility* groupVis = groupFrame->StyleVisibility(); michael@0: bool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupVis->mVisible); michael@0: nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)groupFrame; michael@0: for (nsTableColFrame* colFrame = cgFrame->GetFirstColumn(); colFrame; michael@0: colFrame = colFrame->GetNextCol()) { michael@0: const nsStyleDisplay* colDisplay = colFrame->StyleDisplay(); michael@0: int32_t colX = colFrame->GetColIndex(); michael@0: if (NS_STYLE_DISPLAY_TABLE_COLUMN == colDisplay->mDisplay) { michael@0: const nsStyleVisibility* colVis = colFrame->StyleVisibility(); michael@0: bool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colVis->mVisible); michael@0: int32_t colWidth = GetColumnWidth(colX); michael@0: if (!collapseGroup && !collapseCol) { michael@0: width += colWidth; michael@0: if (ColumnHasCellSpacingBefore(colX)) michael@0: width += cellSpacingX; michael@0: } michael@0: else { michael@0: SetNeedToCollapse(true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return width; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsTableFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) michael@0: { michael@0: nsContainerFrame::DidSetStyleContext(aOldStyleContext); michael@0: michael@0: if (!aOldStyleContext) //avoid this on init michael@0: return; michael@0: michael@0: if (IsBorderCollapse() && michael@0: BCRecalcNeeded(aOldStyleContext, StyleContext())) { michael@0: SetFullBCDamageArea(); michael@0: } michael@0: michael@0: //avoid this on init or nextinflow michael@0: if (!mTableLayoutStrategy || GetPrevInFlow()) michael@0: return; michael@0: michael@0: bool isAuto = IsAutoLayout(); michael@0: if (isAuto != (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Auto)) { michael@0: nsITableLayoutStrategy* temp; michael@0: if (isAuto) michael@0: temp = new BasicTableLayoutStrategy(this); michael@0: else michael@0: temp = new FixedTableLayoutStrategy(this); michael@0: michael@0: if (temp) { michael@0: delete mTableLayoutStrategy; michael@0: mTableLayoutStrategy = temp; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: michael@0: nsresult michael@0: nsTableFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(aListID == kPrincipalList || aListID == kColGroupList, michael@0: "unexpected child list"); michael@0: michael@0: // Because we actually have two child lists, one for col group frames and one michael@0: // for everything else, we need to look at each frame individually michael@0: // XXX The frame construction code should be separating out child frames michael@0: // based on the type, bug 343048. michael@0: while (!aFrameList.IsEmpty()) { michael@0: nsIFrame* f = aFrameList.FirstChild(); michael@0: aFrameList.RemoveFrame(f); michael@0: michael@0: // See what kind of frame we have michael@0: const nsStyleDisplay* display = f->StyleDisplay(); michael@0: michael@0: if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay) { michael@0: nsTableColGroupFrame* lastColGroup = michael@0: nsTableColGroupFrame::GetLastRealColGroup(this); michael@0: int32_t startColIndex = (lastColGroup) michael@0: ? lastColGroup->GetStartColumnIndex() + lastColGroup->GetColCount() : 0; michael@0: mColGroups.InsertFrame(nullptr, lastColGroup, f); michael@0: // Insert the colgroup and its cols into the table michael@0: InsertColGroups(startColIndex, michael@0: nsFrameList::Slice(mColGroups, f, f->GetNextSibling())); michael@0: } else if (IsRowGroup(display->mDisplay)) { michael@0: // Append the new row group frame to the sibling chain michael@0: mFrames.AppendFrame(nullptr, f); michael@0: michael@0: // insert the row group and its rows into the table michael@0: InsertRowGroups(nsFrameList::Slice(mFrames, f, nullptr)); michael@0: } else { michael@0: // Nothing special to do, just add the frame to our child list michael@0: NS_NOTREACHED("How did we get here? Frame construction screwed up"); michael@0: mFrames.AppendFrame(nullptr, f); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== TableFrame::AppendFrames\n"); michael@0: Dump(true, true, true); michael@0: #endif michael@0: PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: SetGeometryDirty(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Needs to be at file scope or ArrayLength fails to compile. michael@0: struct ChildListInsertions { michael@0: nsIFrame::ChildListID mID; michael@0: nsFrameList mList; michael@0: }; michael@0: michael@0: nsresult michael@0: nsTableFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: // The frames in aFrameList can be a mix of row group frames and col group michael@0: // frames. The problem is that they should go in separate child lists so michael@0: // we need to deal with that here... michael@0: // XXX The frame construction code should be separating out child frames michael@0: // based on the type, bug 343048. michael@0: michael@0: NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, michael@0: "inserting after sibling frame with different parent"); michael@0: michael@0: if ((aPrevFrame && !aPrevFrame->GetNextSibling()) || michael@0: (!aPrevFrame && GetChildList(aListID).IsEmpty())) { michael@0: // Treat this like an append; still a workaround for bug 343048. michael@0: return AppendFrames(aListID, aFrameList); michael@0: } michael@0: michael@0: // Collect ColGroupFrames into a separate list and insert those separately michael@0: // from the other frames (bug 759249). michael@0: ChildListInsertions insertions[2]; // ColGroup, other michael@0: const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay(); michael@0: nsFrameList::FrameLinkEnumerator e(aFrameList); michael@0: for (; !aFrameList.IsEmpty(); e.Next()) { michael@0: nsIFrame* next = e.NextFrame(); michael@0: if (!next || next->StyleDisplay()->mDisplay != display->mDisplay) { michael@0: nsFrameList head = aFrameList.ExtractHead(e); michael@0: if (display->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP) { michael@0: insertions[0].mID = kColGroupList; michael@0: insertions[0].mList.AppendFrames(nullptr, head); michael@0: } else { michael@0: insertions[1].mID = kPrincipalList; michael@0: insertions[1].mList.AppendFrames(nullptr, head); michael@0: } michael@0: if (!next) { michael@0: break; michael@0: } michael@0: display = next->StyleDisplay(); michael@0: } michael@0: } michael@0: for (uint32_t i = 0; i < ArrayLength(insertions); ++i) { michael@0: // We pass aPrevFrame for both ColGroup and other frames since michael@0: // HomogenousInsertFrames will only use it if it's a suitable michael@0: // prev-sibling for the frames in the frame list. michael@0: if (!insertions[i].mList.IsEmpty()) { michael@0: HomogenousInsertFrames(insertions[i].mID, aPrevFrame, michael@0: insertions[i].mList); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::HomogenousInsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: // See what kind of frame we have michael@0: const nsStyleDisplay* display = aFrameList.FirstChild()->StyleDisplay(); michael@0: #ifdef DEBUG michael@0: // Verify that either all siblings have display:table-column-group, or they michael@0: // all have display values different from table-column-group. michael@0: for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { michael@0: const nsStyleDisplay* nextDisplay = e.get()->StyleDisplay(); michael@0: MOZ_ASSERT((display->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP) == michael@0: (nextDisplay->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP), michael@0: "heterogenous childlist"); michael@0: } michael@0: #endif michael@0: if (aPrevFrame) { michael@0: const nsStyleDisplay* prevDisplay = aPrevFrame->StyleDisplay(); michael@0: // Make sure they belong on the same frame list michael@0: if ((display->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP) != michael@0: (prevDisplay->mDisplay == NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP)) { michael@0: // the previous frame is not valid, see comment at ::AppendFrames michael@0: // XXXbz Using content indices here means XBL will get screwed michael@0: // over... Oh, well. michael@0: nsIFrame* pseudoFrame = aFrameList.FirstChild(); michael@0: nsIContent* parentContent = GetContent(); michael@0: nsIContent* content; michael@0: aPrevFrame = nullptr; michael@0: while (pseudoFrame && (parentContent == michael@0: (content = pseudoFrame->GetContent()))) { michael@0: pseudoFrame = pseudoFrame->GetFirstPrincipalChild(); michael@0: } michael@0: nsCOMPtr container = content->GetParent(); michael@0: if (MOZ_LIKELY(container)) { // XXX need this null-check, see bug 411823. michael@0: int32_t newIndex = container->IndexOf(content); michael@0: nsIFrame* kidFrame; michael@0: bool isColGroup = (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == michael@0: display->mDisplay); michael@0: nsTableColGroupFrame* lastColGroup; michael@0: if (isColGroup) { michael@0: kidFrame = mColGroups.FirstChild(); michael@0: lastColGroup = nsTableColGroupFrame::GetLastRealColGroup(this); michael@0: } michael@0: else { michael@0: kidFrame = mFrames.FirstChild(); michael@0: } michael@0: // Important: need to start at a value smaller than all valid indices michael@0: int32_t lastIndex = -1; michael@0: while (kidFrame) { michael@0: if (isColGroup) { michael@0: if (kidFrame == lastColGroup) { michael@0: aPrevFrame = kidFrame; // there is no real colgroup after this one michael@0: break; michael@0: } michael@0: } michael@0: pseudoFrame = kidFrame; michael@0: while (pseudoFrame && (parentContent == michael@0: (content = pseudoFrame->GetContent()))) { michael@0: pseudoFrame = pseudoFrame->GetFirstPrincipalChild(); michael@0: } michael@0: int32_t index = container->IndexOf(content); michael@0: if (index > lastIndex && index < newIndex) { michael@0: lastIndex = index; michael@0: aPrevFrame = kidFrame; michael@0: } michael@0: kidFrame = kidFrame->GetNextSibling(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay) { michael@0: NS_ASSERTION(aListID == kColGroupList, "unexpected child list"); michael@0: // Insert the column group frames michael@0: const nsFrameList::Slice& newColgroups = michael@0: mColGroups.InsertFrames(nullptr, aPrevFrame, aFrameList); michael@0: // find the starting col index for the first new col group michael@0: int32_t startColIndex = 0; michael@0: if (aPrevFrame) { michael@0: nsTableColGroupFrame* prevColGroup = michael@0: (nsTableColGroupFrame*)GetFrameAtOrBefore(this, aPrevFrame, michael@0: nsGkAtoms::tableColGroupFrame); michael@0: if (prevColGroup) { michael@0: startColIndex = prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount(); michael@0: } michael@0: } michael@0: InsertColGroups(startColIndex, newColgroups); michael@0: } else if (IsRowGroup(display->mDisplay)) { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: // Insert the frames in the sibling chain michael@0: const nsFrameList::Slice& newRowGroups = michael@0: mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); michael@0: michael@0: InsertRowGroups(newRowGroups); michael@0: } else { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: NS_NOTREACHED("How did we even get here?"); michael@0: // Just insert the frame and don't worry about reflowing it michael@0: mFrames.InsertFrames(nullptr, aPrevFrame, aFrameList); michael@0: return; michael@0: } michael@0: michael@0: PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: SetGeometryDirty(); michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== TableFrame::InsertFrames\n"); michael@0: Dump(true, true, true); michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::DoRemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: if (aListID == kColGroupList) { michael@0: nsIFrame* nextColGroupFrame = aOldFrame->GetNextSibling(); michael@0: nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame; michael@0: int32_t firstColIndex = colGroup->GetStartColumnIndex(); michael@0: int32_t lastColIndex = firstColIndex + colGroup->GetColCount() - 1; michael@0: mColGroups.DestroyFrame(aOldFrame); michael@0: nsTableColGroupFrame::ResetColIndices(nextColGroupFrame, firstColIndex); michael@0: // remove the cols from the table michael@0: int32_t colX; michael@0: for (colX = lastColIndex; colX >= firstColIndex; colX--) { michael@0: nsTableColFrame* colFrame = mColFrames.SafeElementAt(colX); michael@0: if (colFrame) { michael@0: RemoveCol(colGroup, colX, true, false); michael@0: } michael@0: } michael@0: michael@0: int32_t numAnonymousColsToAdd = GetColCount() - mColFrames.Length(); michael@0: if (numAnonymousColsToAdd > 0) { michael@0: // this sets the child list, updates the col cache and cell map michael@0: AppendAnonymousColFrames(numAnonymousColsToAdd); michael@0: } michael@0: michael@0: } else { michael@0: NS_ASSERTION(aListID == kPrincipalList, "unexpected child list"); michael@0: nsTableRowGroupFrame* rgFrame = michael@0: static_cast(aOldFrame); michael@0: // remove the row group from the cell map michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (cellMap) { michael@0: cellMap->RemoveGroupCellMap(rgFrame); michael@0: } michael@0: michael@0: // remove the row group frame from the sibling chain michael@0: mFrames.DestroyFrame(aOldFrame); michael@0: michael@0: // the removal of a row group changes the cellmap, the columns might change michael@0: if (cellMap) { michael@0: cellMap->Synchronize(this); michael@0: // Create an empty slice michael@0: ResetRowIndices(nsFrameList::Slice(mFrames, nullptr, nullptr)); michael@0: nsIntRect damageArea; michael@0: cellMap->RebuildConsideringCells(nullptr, nullptr, 0, 0, false, damageArea); michael@0: michael@0: static_cast(FirstInFlow())->MatchCellMapToColCache(cellMap); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTableFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: NS_ASSERTION(aListID == kColGroupList || michael@0: NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP != michael@0: aOldFrame->StyleDisplay()->mDisplay, michael@0: "Wrong list name; use kColGroupList iff colgroup"); michael@0: nsIPresShell* shell = PresContext()->PresShell(); michael@0: nsTableFrame* lastParent = nullptr; michael@0: while (aOldFrame) { michael@0: nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation(); michael@0: nsTableFrame* parent = static_cast(aOldFrame->GetParent()); michael@0: if (parent != lastParent) { michael@0: parent->DrainSelfOverflowList(); michael@0: } michael@0: parent->DoRemoveFrame(aListID, aOldFrame); michael@0: aOldFrame = oldFrameNextContinuation; michael@0: if (parent != lastParent) { michael@0: // for now, just bail and recalc all of the collapsing borders michael@0: // as the cellmap changes we need to recalc michael@0: if (parent->IsBorderCollapse()) { michael@0: parent->SetFullBCDamageArea(); michael@0: } michael@0: parent->SetGeometryDirty(); michael@0: shell->FrameNeedsReflow(parent, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: lastParent = parent; michael@0: } michael@0: } michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: printf("=== TableFrame::RemoveFrame\n"); michael@0: Dump(true, true, true); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableFrame::GetUsedBorder() const michael@0: { michael@0: if (!IsBorderCollapse()) michael@0: return nsContainerFrame::GetUsedBorder(); michael@0: michael@0: return GetIncludedOuterBCBorder(); michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableFrame::GetUsedPadding() const michael@0: { michael@0: if (!IsBorderCollapse()) michael@0: return nsContainerFrame::GetUsedPadding(); michael@0: michael@0: return nsMargin(0,0,0,0); michael@0: } michael@0: michael@0: /* virtual */ nsMargin michael@0: nsTableFrame::GetUsedMargin() const michael@0: { michael@0: // The margin is inherited to the outer table frame via michael@0: // the ::-moz-table-outer rule in ua.css. michael@0: return nsMargin(0, 0, 0, 0); michael@0: } michael@0: michael@0: // Destructor function for BCPropertyData properties michael@0: static void michael@0: DestroyBCProperty(void* aPropertyValue) michael@0: { michael@0: delete static_cast(aPropertyValue); michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(TableBCProperty, DestroyBCProperty) michael@0: michael@0: BCPropertyData* michael@0: nsTableFrame::GetBCProperty(bool aCreateIfNecessary) const michael@0: { michael@0: FrameProperties props = Properties(); michael@0: BCPropertyData* value = static_cast michael@0: (props.Get(TableBCProperty())); michael@0: if (!value && aCreateIfNecessary) { michael@0: value = new BCPropertyData(); michael@0: props.Set(TableBCProperty(), value); michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: static void michael@0: DivideBCBorderSize(BCPixelSize aPixelSize, michael@0: BCPixelSize& aSmallHalf, michael@0: BCPixelSize& aLargeHalf) michael@0: { michael@0: aSmallHalf = aPixelSize / 2; michael@0: aLargeHalf = aPixelSize - aSmallHalf; michael@0: } michael@0: michael@0: nsMargin michael@0: nsTableFrame::GetOuterBCBorder() const michael@0: { michael@0: if (NeedToCalcBCBorders()) michael@0: const_cast(this)->CalcBCBorders(); michael@0: michael@0: nsMargin border(0, 0, 0, 0); michael@0: int32_t p2t = nsPresContext::AppUnitsPerCSSPixel(); michael@0: BCPropertyData* propData = GetBCProperty(); michael@0: if (propData) { michael@0: border.top = BC_BORDER_TOP_HALF_COORD(p2t, propData->mTopBorderWidth); michael@0: border.right = BC_BORDER_RIGHT_HALF_COORD(p2t, propData->mRightBorderWidth); michael@0: border.bottom = BC_BORDER_BOTTOM_HALF_COORD(p2t, propData->mBottomBorderWidth); michael@0: border.left = BC_BORDER_LEFT_HALF_COORD(p2t, propData->mLeftBorderWidth); michael@0: } michael@0: return border; michael@0: } michael@0: michael@0: nsMargin michael@0: nsTableFrame::GetIncludedOuterBCBorder() const michael@0: { michael@0: if (NeedToCalcBCBorders()) michael@0: const_cast(this)->CalcBCBorders(); michael@0: michael@0: nsMargin border(0, 0, 0, 0); michael@0: int32_t p2t = nsPresContext::AppUnitsPerCSSPixel(); michael@0: BCPropertyData* propData = GetBCProperty(); michael@0: if (propData) { michael@0: border.top += BC_BORDER_TOP_HALF_COORD(p2t, propData->mTopBorderWidth); michael@0: border.right += BC_BORDER_RIGHT_HALF_COORD(p2t, propData->mRightCellBorderWidth); michael@0: border.bottom += BC_BORDER_BOTTOM_HALF_COORD(p2t, propData->mBottomBorderWidth); michael@0: border.left += BC_BORDER_LEFT_HALF_COORD(p2t, propData->mLeftCellBorderWidth); michael@0: } michael@0: return border; michael@0: } michael@0: michael@0: nsMargin michael@0: nsTableFrame::GetExcludedOuterBCBorder() const michael@0: { michael@0: return GetOuterBCBorder() - GetIncludedOuterBCBorder(); michael@0: } michael@0: michael@0: static michael@0: void GetSeparateModelBorderPadding(const nsHTMLReflowState* aReflowState, michael@0: nsStyleContext& aStyleContext, michael@0: nsMargin& aBorderPadding) michael@0: { michael@0: // XXXbz Either we _do_ have a reflow state and then we can use its michael@0: // mComputedBorderPadding or we don't and then we get the padding michael@0: // wrong! michael@0: const nsStyleBorder* border = aStyleContext.StyleBorder(); michael@0: aBorderPadding = border->GetComputedBorder(); michael@0: if (aReflowState) { michael@0: aBorderPadding += aReflowState->ComputedPhysicalPadding(); michael@0: } michael@0: } michael@0: michael@0: nsMargin michael@0: nsTableFrame::GetChildAreaOffset(const nsHTMLReflowState* aReflowState) const michael@0: { michael@0: nsMargin offset(0,0,0,0); michael@0: if (IsBorderCollapse()) { michael@0: offset = GetIncludedOuterBCBorder(); michael@0: } michael@0: else { michael@0: GetSeparateModelBorderPadding(aReflowState, *mStyleContext, offset); michael@0: } michael@0: return offset; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::InitChildReflowState(nsHTMLReflowState& aReflowState) michael@0: { michael@0: nsMargin collapseBorder; michael@0: nsMargin padding(0,0,0,0); michael@0: nsMargin* pCollapseBorder = nullptr; michael@0: nsPresContext* presContext = PresContext(); michael@0: if (IsBorderCollapse()) { michael@0: nsTableRowGroupFrame* rgFrame = michael@0: static_cast(aReflowState.frame); michael@0: pCollapseBorder = rgFrame->GetBCBorderWidth(collapseBorder); michael@0: } michael@0: aReflowState.Init(presContext, -1, -1, pCollapseBorder, &padding); michael@0: michael@0: NS_ASSERTION(!mBits.mResizedColumns || michael@0: !aReflowState.parentReflowState->mFlags.mSpecialHeightReflow, michael@0: "should not resize columns on special height reflow"); michael@0: if (mBits.mResizedColumns) { michael@0: aReflowState.mFlags.mHResize = true; michael@0: } michael@0: } michael@0: michael@0: // Position and size aKidFrame and update our reflow state. The origin of michael@0: // aKidRect is relative to the upper-left origin of our frame michael@0: void nsTableFrame::PlaceChild(nsTableReflowState& aReflowState, michael@0: nsIFrame* aKidFrame, michael@0: nsHTMLReflowMetrics& aKidDesiredSize, michael@0: const nsRect& aOriginalKidRect, michael@0: const nsRect& aOriginalKidVisualOverflow) michael@0: { michael@0: bool isFirstReflow = michael@0: (aKidFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0; michael@0: michael@0: // Place and size the child michael@0: FinishReflowChild(aKidFrame, PresContext(), aKidDesiredSize, nullptr, michael@0: aReflowState.x, aReflowState.y, 0); michael@0: michael@0: InvalidateTableFrame(aKidFrame, aOriginalKidRect, aOriginalKidVisualOverflow, michael@0: isFirstReflow); michael@0: michael@0: // Adjust the running y-offset michael@0: aReflowState.y += aKidDesiredSize.Height(); michael@0: michael@0: // If our height is constrained, then update the available height michael@0: if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { michael@0: aReflowState.availSize.height -= aKidDesiredSize.Height(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::OrderRowGroups(RowGroupArray& aChildren, michael@0: nsTableRowGroupFrame** aHead, michael@0: nsTableRowGroupFrame** aFoot) const michael@0: { michael@0: aChildren.Clear(); michael@0: nsTableRowGroupFrame* head = nullptr; michael@0: nsTableRowGroupFrame* foot = nullptr; michael@0: michael@0: nsIFrame* kidFrame = mFrames.FirstChild(); michael@0: while (kidFrame) { michael@0: const nsStyleDisplay* kidDisplay = kidFrame->StyleDisplay(); michael@0: nsTableRowGroupFrame* rowGroup = michael@0: static_cast(kidFrame); michael@0: michael@0: switch (kidDisplay->mDisplay) { michael@0: case NS_STYLE_DISPLAY_TABLE_HEADER_GROUP: michael@0: if (head) { // treat additional thead like tbody michael@0: aChildren.AppendElement(rowGroup); michael@0: } michael@0: else { michael@0: head = rowGroup; michael@0: } michael@0: break; michael@0: case NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP: michael@0: if (foot) { // treat additional tfoot like tbody michael@0: aChildren.AppendElement(rowGroup); michael@0: } michael@0: else { michael@0: foot = rowGroup; michael@0: } michael@0: break; michael@0: case NS_STYLE_DISPLAY_TABLE_ROW_GROUP: michael@0: aChildren.AppendElement(rowGroup); michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("How did this produce an nsTableRowGroupFrame?"); michael@0: // Just ignore it michael@0: break; michael@0: } michael@0: // Get the next sibling but skip it if it's also the next-in-flow, since michael@0: // a next-in-flow will not be part of the current table. michael@0: while (kidFrame) { michael@0: nsIFrame* nif = kidFrame->GetNextInFlow(); michael@0: kidFrame = kidFrame->GetNextSibling(); michael@0: if (kidFrame != nif) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // put the thead first michael@0: if (head) { michael@0: aChildren.InsertElementAt(0, head); michael@0: } michael@0: if (aHead) michael@0: *aHead = head; michael@0: // put the tfoot after the last tbody michael@0: if (foot) { michael@0: aChildren.AppendElement(foot); michael@0: } michael@0: if (aFoot) michael@0: *aFoot = foot; michael@0: } michael@0: michael@0: nsTableRowGroupFrame* michael@0: nsTableFrame::GetTHead() const michael@0: { michael@0: nsIFrame* kidFrame = mFrames.FirstChild(); michael@0: while (kidFrame) { michael@0: if (kidFrame->StyleDisplay()->mDisplay == michael@0: NS_STYLE_DISPLAY_TABLE_HEADER_GROUP) { michael@0: return static_cast(kidFrame); michael@0: } michael@0: michael@0: // Get the next sibling but skip it if it's also the next-in-flow, since michael@0: // a next-in-flow will not be part of the current table. michael@0: while (kidFrame) { michael@0: nsIFrame* nif = kidFrame->GetNextInFlow(); michael@0: kidFrame = kidFrame->GetNextSibling(); michael@0: if (kidFrame != nif) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsTableRowGroupFrame* michael@0: nsTableFrame::GetTFoot() const michael@0: { michael@0: nsIFrame* kidFrame = mFrames.FirstChild(); michael@0: while (kidFrame) { michael@0: if (kidFrame->StyleDisplay()->mDisplay == michael@0: NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP) { michael@0: return static_cast(kidFrame); michael@0: } michael@0: michael@0: // Get the next sibling but skip it if it's also the next-in-flow, since michael@0: // a next-in-flow will not be part of the current table. michael@0: while (kidFrame) { michael@0: nsIFrame* nif = kidFrame->GetNextInFlow(); michael@0: kidFrame = kidFrame->GetNextSibling(); michael@0: if (kidFrame != nif) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: static bool michael@0: IsRepeatable(nscoord aFrameHeight, nscoord aPageHeight) michael@0: { michael@0: return aFrameHeight < (aPageHeight / 4); michael@0: } michael@0: michael@0: nsresult michael@0: nsTableFrame::SetupHeaderFooterChild(const nsTableReflowState& aReflowState, michael@0: nsTableRowGroupFrame* aFrame, michael@0: nscoord* aDesiredHeight) michael@0: { michael@0: nsPresContext* presContext = PresContext(); michael@0: nscoord pageHeight = presContext->GetPageSize().height; michael@0: michael@0: // Reflow the child with unconstrainted height michael@0: nsHTMLReflowState kidReflowState(presContext, aReflowState.reflowState, michael@0: aFrame, michael@0: nsSize(aReflowState.availSize.width, NS_UNCONSTRAINEDSIZE), michael@0: -1, -1, nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(kidReflowState); michael@0: kidReflowState.mFlags.mIsTopOfPage = true; michael@0: nsHTMLReflowMetrics desiredSize(aReflowState.reflowState); michael@0: desiredSize.Width() = desiredSize.Height() = 0; michael@0: nsReflowStatus status; michael@0: nsresult rv = ReflowChild(aFrame, presContext, desiredSize, kidReflowState, michael@0: aReflowState.x, aReflowState.y, 0, status); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // The child will be reflowed again "for real" so no need to place it now michael@0: michael@0: aFrame->SetRepeatable(IsRepeatable(desiredSize.Height(), pageHeight)); michael@0: *aDesiredHeight = desiredSize.Height(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::PlaceRepeatedFooter(nsTableReflowState& aReflowState, michael@0: nsTableRowGroupFrame *aTfoot, michael@0: nscoord aFooterHeight) michael@0: { michael@0: nsPresContext* presContext = PresContext(); michael@0: nsSize kidAvailSize(aReflowState.availSize); michael@0: kidAvailSize.height = aFooterHeight; michael@0: nsHTMLReflowState footerReflowState(presContext, michael@0: aReflowState.reflowState, michael@0: aTfoot, kidAvailSize, michael@0: -1, -1, michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(footerReflowState); michael@0: aReflowState.y += GetCellSpacingY(); michael@0: michael@0: nsRect origTfootRect = aTfoot->GetRect(); michael@0: nsRect origTfootVisualOverflow = aTfoot->GetVisualOverflowRect(); michael@0: michael@0: nsReflowStatus footerStatus; michael@0: nsHTMLReflowMetrics desiredSize(aReflowState.reflowState); michael@0: desiredSize.Width() = desiredSize.Height() = 0; michael@0: ReflowChild(aTfoot, presContext, desiredSize, footerReflowState, michael@0: aReflowState.x, aReflowState.y, 0, footerStatus); michael@0: PlaceChild(aReflowState, aTfoot, desiredSize, origTfootRect, michael@0: origTfootVisualOverflow); michael@0: } michael@0: michael@0: // Reflow the children based on the avail size and reason in aReflowState michael@0: // update aReflowMetrics a aStatus michael@0: nsresult michael@0: nsTableFrame::ReflowChildren(nsTableReflowState& aReflowState, michael@0: nsReflowStatus& aStatus, michael@0: nsIFrame*& aLastChildReflowed, michael@0: nsOverflowAreas& aOverflowAreas) michael@0: { michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: aLastChildReflowed = nullptr; michael@0: michael@0: nsIFrame* prevKidFrame = nullptr; michael@0: nsresult rv = NS_OK; michael@0: nscoord cellSpacingY = GetCellSpacingY(); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: // XXXldb Should we be checking constrained height instead? michael@0: // tables are not able to pull back children from its next inflow, so even michael@0: // under paginated contexts tables are should not paginate if they are inside michael@0: // column set michael@0: bool isPaginated = presContext->IsPaginated() && michael@0: NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height && michael@0: aReflowState.reflowState.mFlags.mTableIsSplittable; michael@0: michael@0: aOverflowAreas.Clear(); michael@0: michael@0: bool reflowAllKids = aReflowState.reflowState.ShouldReflowAllKids() || michael@0: mBits.mResizedColumns || michael@0: IsGeometryDirty(); michael@0: michael@0: RowGroupArray rowGroups; michael@0: nsTableRowGroupFrame *thead, *tfoot; michael@0: OrderRowGroups(rowGroups, &thead, &tfoot); michael@0: bool pageBreak = false; michael@0: nscoord footerHeight = 0; michael@0: michael@0: // Determine the repeatablility of headers and footers, and also the desired michael@0: // height of any repeatable footer. michael@0: // The repeatability of headers on continued tables is handled michael@0: // when they are created in nsCSSFrameConstructor::CreateContinuingTableFrame. michael@0: // We handle the repeatability of footers again here because we need to michael@0: // determine the footer's height anyway. We could perhaps optimize by michael@0: // using the footer's prev-in-flow's height instead of reflowing it again, michael@0: // but there's no real need. michael@0: if (isPaginated) { michael@0: if (thead && !GetPrevInFlow()) { michael@0: nscoord desiredHeight; michael@0: rv = SetupHeaderFooterChild(aReflowState, thead, &desiredHeight); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: if (tfoot) { michael@0: rv = SetupHeaderFooterChild(aReflowState, tfoot, &footerHeight); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: // if the child is a tbody in paginated mode reduce the height by a repeated footer michael@0: bool allowRepeatedFooter = false; michael@0: for (uint32_t childX = 0; childX < rowGroups.Length(); childX++) { michael@0: nsIFrame* kidFrame = rowGroups[childX]; michael@0: // Get the frame state bits michael@0: // See if we should only reflow the dirty child frames michael@0: if (reflowAllKids || michael@0: NS_SUBTREE_DIRTY(kidFrame) || michael@0: (aReflowState.reflowState.mFlags.mSpecialHeightReflow && michael@0: (isPaginated || (kidFrame->GetStateBits() & michael@0: NS_FRAME_CONTAINS_RELATIVE_HEIGHT)))) { michael@0: if (pageBreak) { michael@0: if (allowRepeatedFooter) { michael@0: PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); michael@0: } michael@0: else if (tfoot && tfoot->IsRepeatable()) { michael@0: tfoot->SetRepeatable(false); michael@0: } michael@0: PushChildren(rowGroups, childX); michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: break; michael@0: } michael@0: michael@0: nsSize kidAvailSize(aReflowState.availSize); michael@0: allowRepeatedFooter = false; michael@0: if (isPaginated && (NS_UNCONSTRAINEDSIZE != kidAvailSize.height)) { michael@0: nsTableRowGroupFrame* kidRG = michael@0: static_cast(kidFrame); michael@0: if (kidRG != thead && kidRG != tfoot && tfoot && tfoot->IsRepeatable()) { michael@0: // the child is a tbody and there is a repeatable footer michael@0: NS_ASSERTION(tfoot == rowGroups[rowGroups.Length() - 1], "Missing footer!"); michael@0: if (footerHeight + cellSpacingY < kidAvailSize.height) { michael@0: allowRepeatedFooter = true; michael@0: kidAvailSize.height -= footerHeight + cellSpacingY; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsRect oldKidRect = kidFrame->GetRect(); michael@0: nsRect oldKidVisualOverflow = kidFrame->GetVisualOverflowRect(); michael@0: michael@0: nsHTMLReflowMetrics desiredSize(aReflowState.reflowState); michael@0: desiredSize.Width() = desiredSize.Height() = 0; michael@0: michael@0: // Reflow the child into the available space michael@0: nsHTMLReflowState kidReflowState(presContext, aReflowState.reflowState, michael@0: kidFrame, kidAvailSize, michael@0: -1, -1, michael@0: nsHTMLReflowState::CALLER_WILL_INIT); michael@0: InitChildReflowState(kidReflowState); michael@0: michael@0: // If this isn't the first row group, and the previous row group has a michael@0: // nonzero YMost, then we can't be at the top of the page. michael@0: // We ignore a repeated head row group in this check to avoid causing michael@0: // infinite loops in some circumstances - see bug 344883. michael@0: if (childX > ((thead && IsRepeatedFrame(thead)) ? 1u : 0u) && michael@0: (rowGroups[childX - 1]->GetRect().YMost() > 0)) { michael@0: kidReflowState.mFlags.mIsTopOfPage = false; michael@0: } michael@0: aReflowState.y += cellSpacingY; michael@0: if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { michael@0: aReflowState.availSize.height -= cellSpacingY; michael@0: } michael@0: // record the presence of a next in flow, it might get destroyed so we michael@0: // need to reorder the row group array michael@0: bool reorder = false; michael@0: if (kidFrame->GetNextInFlow()) michael@0: reorder = true; michael@0: michael@0: rv = ReflowChild(kidFrame, presContext, desiredSize, kidReflowState, michael@0: aReflowState.x, aReflowState.y, 0, aStatus); michael@0: michael@0: if (reorder) { michael@0: // reorder row groups the reflow may have changed the nextinflows michael@0: OrderRowGroups(rowGroups, &thead, &tfoot); michael@0: childX = rowGroups.IndexOf(kidFrame); michael@0: if (childX == RowGroupArray::NoIndex) { michael@0: // XXXbz can this happen? michael@0: childX = rowGroups.Length(); michael@0: } michael@0: } michael@0: if (isPaginated && !NS_FRAME_IS_FULLY_COMPLETE(aStatus) && michael@0: ShouldAvoidBreakInside(aReflowState.reflowState)) { michael@0: aStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: break; michael@0: } michael@0: // see if the rowgroup did not fit on this page might be pushed on michael@0: // the next page michael@0: if (isPaginated && michael@0: (NS_INLINE_IS_BREAK_BEFORE(aStatus) || michael@0: (NS_FRAME_IS_COMPLETE(aStatus) && michael@0: (NS_UNCONSTRAINEDSIZE != kidReflowState.AvailableHeight()) && michael@0: kidReflowState.AvailableHeight() < desiredSize.Height()))) { michael@0: if (ShouldAvoidBreakInside(aReflowState.reflowState)) { michael@0: aStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: break; michael@0: } michael@0: // if we are on top of the page place with dataloss michael@0: if (kidReflowState.mFlags.mIsTopOfPage) { michael@0: if (childX+1 < rowGroups.Length()) { michael@0: nsIFrame* nextRowGroupFrame = rowGroups[childX + 1]; michael@0: if (nextRowGroupFrame) { michael@0: PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, michael@0: oldKidVisualOverflow); michael@0: if (allowRepeatedFooter) { michael@0: PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); michael@0: } michael@0: else if (tfoot && tfoot->IsRepeatable()) { michael@0: tfoot->SetRepeatable(false); michael@0: } michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: PushChildren(rowGroups, childX + 1); michael@0: aLastChildReflowed = kidFrame; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: else { // we are not on top, push this rowgroup onto the next page michael@0: if (prevKidFrame) { // we had a rowgroup before so push this michael@0: if (allowRepeatedFooter) { michael@0: PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); michael@0: } michael@0: else if (tfoot && tfoot->IsRepeatable()) { michael@0: tfoot->SetRepeatable(false); michael@0: } michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: PushChildren(rowGroups, childX); michael@0: aLastChildReflowed = prevKidFrame; michael@0: break; michael@0: } michael@0: else { // we can't push so lets make clear how much space we need michael@0: PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, michael@0: oldKidVisualOverflow); michael@0: aLastChildReflowed = kidFrame; michael@0: if (allowRepeatedFooter) { michael@0: PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); michael@0: aLastChildReflowed = tfoot; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: aLastChildReflowed = kidFrame; michael@0: michael@0: pageBreak = false; michael@0: // see if there is a page break after this row group or before the next one michael@0: if (NS_FRAME_IS_COMPLETE(aStatus) && isPaginated && michael@0: (NS_UNCONSTRAINEDSIZE != kidReflowState.AvailableHeight())) { michael@0: nsIFrame* nextKid = michael@0: (childX + 1 < rowGroups.Length()) ? rowGroups[childX + 1] : nullptr; michael@0: pageBreak = PageBreakAfter(kidFrame, nextKid); michael@0: } michael@0: michael@0: // Place the child michael@0: PlaceChild(aReflowState, kidFrame, desiredSize, oldKidRect, michael@0: oldKidVisualOverflow); michael@0: michael@0: // Remember where we just were in case we end up pushing children michael@0: prevKidFrame = kidFrame; michael@0: michael@0: // Special handling for incomplete children michael@0: if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { michael@0: nsIFrame* kidNextInFlow = kidFrame->GetNextInFlow(); michael@0: if (!kidNextInFlow) { michael@0: // The child doesn't have a next-in-flow so create a continuing michael@0: // frame. This hooks the child into the flow michael@0: kidNextInFlow = presContext->PresShell()->FrameConstructor()-> michael@0: CreateContinuingFrame(presContext, kidFrame, this); michael@0: michael@0: // Insert the kid's new next-in-flow into our sibling list... michael@0: mFrames.InsertFrame(nullptr, kidFrame, kidNextInFlow); michael@0: // and in rowGroups after childX so that it will get pushed below. michael@0: rowGroups.InsertElementAt(childX + 1, michael@0: static_cast(kidNextInFlow)); michael@0: } else if (kidNextInFlow == kidFrame->GetNextSibling()) { michael@0: // OrderRowGroups excludes NIFs in the child list from 'rowGroups' michael@0: // so we deal with that here to make sure they get pushed. michael@0: MOZ_ASSERT(!rowGroups.Contains(kidNextInFlow), michael@0: "OrderRowGroups must not put our NIF in 'rowGroups'"); michael@0: rowGroups.InsertElementAt(childX + 1, michael@0: static_cast(kidNextInFlow)); michael@0: } michael@0: michael@0: // We've used up all of our available space so push the remaining michael@0: // children. michael@0: if (allowRepeatedFooter) { michael@0: PlaceRepeatedFooter(aReflowState, tfoot, footerHeight); michael@0: } michael@0: else if (tfoot && tfoot->IsRepeatable()) { michael@0: tfoot->SetRepeatable(false); michael@0: } michael@0: michael@0: nsIFrame* nextSibling = kidFrame->GetNextSibling(); michael@0: if (nextSibling) { michael@0: PushChildren(rowGroups, childX + 1); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: else { // it isn't being reflowed michael@0: aReflowState.y += cellSpacingY; michael@0: nsRect kidRect = kidFrame->GetRect(); michael@0: if (kidRect.y != aReflowState.y) { michael@0: // invalidate the old position michael@0: kidFrame->InvalidateFrameSubtree(); michael@0: kidRect.y = aReflowState.y; michael@0: kidFrame->SetRect(kidRect); // move to the new position michael@0: RePositionViews(kidFrame); michael@0: // invalidate the new position michael@0: kidFrame->InvalidateFrameSubtree(); michael@0: } michael@0: aReflowState.y += kidRect.height; michael@0: michael@0: // If our height is constrained then update the available height. michael@0: if (NS_UNCONSTRAINEDSIZE != aReflowState.availSize.height) { michael@0: aReflowState.availSize.height -= cellSpacingY + kidRect.height; michael@0: } michael@0: } michael@0: ConsiderChildOverflow(aOverflowAreas, kidFrame); michael@0: } michael@0: michael@0: // We've now propagated the column resizes and geometry changes to all michael@0: // the children. michael@0: mBits.mResizedColumns = false; michael@0: ClearGeometryDirty(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::ReflowColGroups(nsRenderingContext *aRenderingContext) michael@0: { michael@0: if (!GetPrevInFlow() && !HaveReflowedColGroups()) { michael@0: nsHTMLReflowMetrics kidMet(GetWritingMode()); michael@0: nsPresContext *presContext = PresContext(); michael@0: for (nsIFrame* kidFrame = mColGroups.FirstChild(); kidFrame; michael@0: kidFrame = kidFrame->GetNextSibling()) { michael@0: if (NS_SUBTREE_DIRTY(kidFrame)) { michael@0: // The column groups don't care about dimensions or reflow states. michael@0: nsHTMLReflowState kidReflowState(presContext, kidFrame, michael@0: aRenderingContext, nsSize(0,0)); michael@0: nsReflowStatus cgStatus; michael@0: ReflowChild(kidFrame, presContext, kidMet, kidReflowState, 0, 0, 0, michael@0: cgStatus); michael@0: FinishReflowChild(kidFrame, presContext, kidMet, nullptr, 0, 0, 0); michael@0: } michael@0: } michael@0: SetHaveReflowedColGroups(true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::CalcDesiredHeight(const nsHTMLReflowState& aReflowState, nsHTMLReflowMetrics& aDesiredSize) michael@0: { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (!cellMap) { michael@0: NS_ASSERTION(false, "never ever call me until the cell map is built!"); michael@0: aDesiredSize.Height() = 0; michael@0: return; michael@0: } michael@0: nscoord cellSpacingY = GetCellSpacingY(); michael@0: nsMargin borderPadding = GetChildAreaOffset(&aReflowState); michael@0: michael@0: // get the natural height based on the last child's (row group) rect michael@0: RowGroupArray rowGroups; michael@0: OrderRowGroups(rowGroups); michael@0: if (rowGroups.IsEmpty()) { michael@0: // tables can be used as rectangular items without content michael@0: nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState); michael@0: if ((NS_UNCONSTRAINEDSIZE != tableSpecifiedHeight) && michael@0: (tableSpecifiedHeight > 0) && michael@0: eCompatibility_NavQuirks != PresContext()->CompatibilityMode()) { michael@0: // empty tables should not have a size in quirks mode michael@0: aDesiredSize.Height() = tableSpecifiedHeight; michael@0: } michael@0: else michael@0: aDesiredSize.Height() = 0; michael@0: return; michael@0: } michael@0: int32_t rowCount = cellMap->GetRowCount(); michael@0: int32_t colCount = cellMap->GetColCount(); michael@0: nscoord desiredHeight = borderPadding.top + borderPadding.bottom; michael@0: if (rowCount > 0 && colCount > 0) { michael@0: desiredHeight += cellSpacingY; michael@0: for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: desiredHeight += rowGroups[rgX]->GetSize().height + cellSpacingY; michael@0: } michael@0: } michael@0: michael@0: // see if a specified table height requires dividing additional space to rows michael@0: if (!GetPrevInFlow()) { michael@0: nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState); michael@0: if ((tableSpecifiedHeight > 0) && michael@0: (tableSpecifiedHeight != NS_UNCONSTRAINEDSIZE) && michael@0: (tableSpecifiedHeight > desiredHeight)) { michael@0: // proportionately distribute the excess height to unconstrained rows in each michael@0: // unconstrained row group. michael@0: DistributeHeightToRows(aReflowState, tableSpecifiedHeight - desiredHeight); michael@0: // this might have changed the overflow area incorporate the childframe overflow area. michael@0: for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame; kidFrame = kidFrame->GetNextSibling()) { michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, kidFrame); michael@0: } michael@0: desiredHeight = tableSpecifiedHeight; michael@0: } michael@0: } michael@0: aDesiredSize.Height() = desiredHeight; michael@0: } michael@0: michael@0: static michael@0: void ResizeCells(nsTableFrame& aTableFrame) michael@0: { michael@0: nsTableFrame::RowGroupArray rowGroups; michael@0: aTableFrame.OrderRowGroups(rowGroups); michael@0: nsHTMLReflowMetrics tableDesiredSize(aTableFrame.GetWritingMode()); // ??? michael@0: nsRect tableRect = aTableFrame.GetRect(); michael@0: tableDesiredSize.Width() = tableRect.width; michael@0: tableDesiredSize.Height() = tableRect.height; michael@0: tableDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: michael@0: for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: michael@0: nsRect rowGroupRect = rgFrame->GetRect(); michael@0: nsHTMLReflowMetrics groupDesiredSize(tableDesiredSize.GetWritingMode()); michael@0: groupDesiredSize.Width() = rowGroupRect.width; michael@0: groupDesiredSize.Height() = rowGroupRect.height; michael@0: groupDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: michael@0: nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); michael@0: while (rowFrame) { michael@0: rowFrame->DidResize(); michael@0: rgFrame->ConsiderChildOverflow(groupDesiredSize.mOverflowAreas, rowFrame); michael@0: rowFrame = rowFrame->GetNextRow(); michael@0: } michael@0: rgFrame->FinishAndStoreOverflow(&groupDesiredSize); michael@0: tableDesiredSize.mOverflowAreas.UnionWith(groupDesiredSize.mOverflowAreas + michael@0: rgFrame->GetPosition()); michael@0: } michael@0: aTableFrame.FinishAndStoreOverflow(&tableDesiredSize); michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::DistributeHeightToRows(const nsHTMLReflowState& aReflowState, michael@0: nscoord aAmount) michael@0: { michael@0: nscoord cellSpacingY = GetCellSpacingY(); michael@0: michael@0: nsMargin borderPadding = GetChildAreaOffset(&aReflowState); michael@0: michael@0: RowGroupArray rowGroups; michael@0: OrderRowGroups(rowGroups); michael@0: michael@0: nscoord amountUsed = 0; michael@0: // distribute space to each pct height row whose row group doesn't have a computed michael@0: // height, and base the pct on the table height. If the row group had a computed michael@0: // height, then this was already done in nsTableRowGroupFrame::CalculateRowHeights michael@0: nscoord pctBasis = aReflowState.ComputedHeight() - (GetCellSpacingY() * (GetRowCount() + 1)); michael@0: nscoord yOriginRG = borderPadding.top + GetCellSpacingY(); michael@0: nscoord yEndRG = yOriginRG; michael@0: uint32_t rgX; michael@0: for (rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: nscoord amountUsedByRG = 0; michael@0: nscoord yOriginRow = 0; michael@0: nsRect rgRect = rgFrame->GetRect(); michael@0: if (!rgFrame->HasStyleHeight()) { michael@0: nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); michael@0: while (rowFrame) { michael@0: nsRect rowRect = rowFrame->GetRect(); michael@0: if ((amountUsed < aAmount) && rowFrame->HasPctHeight()) { michael@0: nscoord pctHeight = rowFrame->GetHeight(pctBasis); michael@0: nscoord amountForRow = std::min(aAmount - amountUsed, pctHeight - rowRect.height); michael@0: if (amountForRow > 0) { michael@0: nsRect oldRowRect = rowRect; michael@0: rowRect.height += amountForRow; michael@0: // XXXbz we don't need to change rowRect.y to be yOriginRow? michael@0: rowFrame->SetRect(rowRect); michael@0: yOriginRow += rowRect.height + cellSpacingY; michael@0: yEndRG += rowRect.height + cellSpacingY; michael@0: amountUsed += amountForRow; michael@0: amountUsedByRG += amountForRow; michael@0: //rowFrame->DidResize(); michael@0: nsTableFrame::RePositionViews(rowFrame); michael@0: michael@0: rgFrame->InvalidateFrameWithRect(oldRowRect); michael@0: rgFrame->InvalidateFrame(); michael@0: } michael@0: } michael@0: else { michael@0: if (amountUsed > 0 && yOriginRow != rowRect.y && michael@0: !(GetStateBits() & NS_FRAME_FIRST_REFLOW)) { michael@0: rowFrame->InvalidateFrameSubtree(); michael@0: rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow)); michael@0: nsTableFrame::RePositionViews(rowFrame); michael@0: rowFrame->InvalidateFrameSubtree(); michael@0: } michael@0: yOriginRow += rowRect.height + cellSpacingY; michael@0: yEndRG += rowRect.height + cellSpacingY; michael@0: } michael@0: rowFrame = rowFrame->GetNextRow(); michael@0: } michael@0: if (amountUsed > 0) { michael@0: if (rgRect.y != yOriginRG) { michael@0: rgFrame->InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: nsRect origRgRect = rgRect; michael@0: nsRect origRgVisualOverflow = rgFrame->GetVisualOverflowRect(); michael@0: michael@0: rgRect.y = yOriginRG; michael@0: rgRect.height += amountUsedByRG; michael@0: michael@0: rgFrame->SetRect(rgRect); michael@0: michael@0: nsTableFrame::InvalidateTableFrame(rgFrame, origRgRect, michael@0: origRgVisualOverflow, false); michael@0: } michael@0: } michael@0: else if (amountUsed > 0 && yOriginRG != rgRect.y) { michael@0: rgFrame->InvalidateFrameSubtree(); michael@0: rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG)); michael@0: // Make sure child views are properly positioned michael@0: nsTableFrame::RePositionViews(rgFrame); michael@0: rgFrame->InvalidateFrameSubtree(); michael@0: } michael@0: yOriginRG = yEndRG; michael@0: } michael@0: michael@0: if (amountUsed >= aAmount) { michael@0: ResizeCells(*this); michael@0: return; michael@0: } michael@0: michael@0: // get the first row without a style height where its row group has an michael@0: // unconstrained height michael@0: nsTableRowGroupFrame* firstUnStyledRG = nullptr; michael@0: nsTableRowFrame* firstUnStyledRow = nullptr; michael@0: for (rgX = 0; rgX < rowGroups.Length() && !firstUnStyledRG; rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: if (!rgFrame->HasStyleHeight()) { michael@0: nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); michael@0: while (rowFrame) { michael@0: if (!rowFrame->HasStyleHeight()) { michael@0: firstUnStyledRG = rgFrame; michael@0: firstUnStyledRow = rowFrame; michael@0: break; michael@0: } michael@0: rowFrame = rowFrame->GetNextRow(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsTableRowFrame* lastEligibleRow = nullptr; michael@0: // Accumulate the correct divisor. This will be the total total height of all michael@0: // unstyled rows inside unstyled row groups, unless there are none, in which michael@0: // case, it will be number of all rows. If the unstyled rows don't have a michael@0: // height, divide the space equally among them. michael@0: nscoord divisor = 0; michael@0: int32_t eligibleRows = 0; michael@0: bool expandEmptyRows = false; michael@0: michael@0: if (!firstUnStyledRow) { michael@0: // there is no unstyled row michael@0: divisor = GetRowCount(); michael@0: } michael@0: else { michael@0: for (rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: if (!firstUnStyledRG || !rgFrame->HasStyleHeight()) { michael@0: nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); michael@0: while (rowFrame) { michael@0: if (!firstUnStyledRG || !rowFrame->HasStyleHeight()) { michael@0: NS_ASSERTION(rowFrame->GetSize().height >= 0, michael@0: "negative row frame height"); michael@0: divisor += rowFrame->GetSize().height; michael@0: eligibleRows++; michael@0: lastEligibleRow = rowFrame; michael@0: } michael@0: rowFrame = rowFrame->GetNextRow(); michael@0: } michael@0: } michael@0: } michael@0: if (divisor <= 0) { michael@0: if (eligibleRows > 0) { michael@0: expandEmptyRows = true; michael@0: } michael@0: else { michael@0: NS_ERROR("invalid divisor"); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: // allocate the extra height to the unstyled row groups and rows michael@0: nscoord heightToDistribute = aAmount - amountUsed; michael@0: yOriginRG = borderPadding.top + cellSpacingY; michael@0: yEndRG = yOriginRG; michael@0: for (rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: nscoord amountUsedByRG = 0; michael@0: nscoord yOriginRow = 0; michael@0: nsRect rgRect = rgFrame->GetRect(); michael@0: nsRect rgVisualOverflow = rgFrame->GetVisualOverflowRect(); michael@0: // see if there is an eligible row group or we distribute to all rows michael@0: if (!firstUnStyledRG || !rgFrame->HasStyleHeight() || !eligibleRows) { michael@0: nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); michael@0: while (rowFrame) { michael@0: nsRect rowRect = rowFrame->GetRect(); michael@0: nsRect rowVisualOverflow = rowFrame->GetVisualOverflowRect(); michael@0: // see if there is an eligible row or we distribute to all rows michael@0: if (!firstUnStyledRow || !rowFrame->HasStyleHeight() || !eligibleRows) { michael@0: float ratio; michael@0: if (eligibleRows) { michael@0: if (!expandEmptyRows) { michael@0: // The amount of additional space each row gets is proportional to michael@0: // its height michael@0: ratio = float(rowRect.height) / float(divisor); michael@0: } else { michael@0: // empty rows get all the same additional space michael@0: ratio = 1.0f / float(eligibleRows); michael@0: } michael@0: } michael@0: else { michael@0: // all rows get the same additional space michael@0: ratio = 1.0f / float(divisor); michael@0: } michael@0: // give rows their additional space, except for the last row which michael@0: // gets the remainder michael@0: nscoord amountForRow = (rowFrame == lastEligibleRow) michael@0: ? aAmount - amountUsed : NSToCoordRound(((float)(heightToDistribute)) * ratio); michael@0: amountForRow = std::min(amountForRow, aAmount - amountUsed); michael@0: michael@0: if (yOriginRow != rowRect.y) { michael@0: rowFrame->InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: // update the row height michael@0: nsRect newRowRect(rowRect.x, yOriginRow, rowRect.width, michael@0: rowRect.height + amountForRow); michael@0: rowFrame->SetRect(newRowRect); michael@0: michael@0: yOriginRow += newRowRect.height + cellSpacingY; michael@0: yEndRG += newRowRect.height + cellSpacingY; michael@0: michael@0: amountUsed += amountForRow; michael@0: amountUsedByRG += amountForRow; michael@0: NS_ASSERTION((amountUsed <= aAmount), "invalid row allocation"); michael@0: //rowFrame->DidResize(); michael@0: nsTableFrame::RePositionViews(rowFrame); michael@0: michael@0: nsTableFrame::InvalidateTableFrame(rowFrame, rowRect, rowVisualOverflow, michael@0: false); michael@0: } michael@0: else { michael@0: if (amountUsed > 0 && yOriginRow != rowRect.y) { michael@0: rowFrame->InvalidateFrameSubtree(); michael@0: rowFrame->SetPosition(nsPoint(rowRect.x, yOriginRow)); michael@0: nsTableFrame::RePositionViews(rowFrame); michael@0: rowFrame->InvalidateFrameSubtree(); michael@0: } michael@0: yOriginRow += rowRect.height + cellSpacingY; michael@0: yEndRG += rowRect.height + cellSpacingY; michael@0: } michael@0: rowFrame = rowFrame->GetNextRow(); michael@0: } michael@0: if (amountUsed > 0) { michael@0: if (rgRect.y != yOriginRG) { michael@0: rgFrame->InvalidateFrameSubtree(); michael@0: } michael@0: michael@0: rgFrame->SetRect(nsRect(rgRect.x, yOriginRG, rgRect.width, michael@0: rgRect.height + amountUsedByRG)); michael@0: michael@0: nsTableFrame::InvalidateTableFrame(rgFrame, rgRect, rgVisualOverflow, michael@0: false); michael@0: } michael@0: // Make sure child views are properly positioned michael@0: } michael@0: else if (amountUsed > 0 && yOriginRG != rgRect.y) { michael@0: rgFrame->InvalidateFrameSubtree(); michael@0: rgFrame->SetPosition(nsPoint(rgRect.x, yOriginRG)); michael@0: // Make sure child views are properly positioned michael@0: nsTableFrame::RePositionViews(rgFrame); michael@0: rgFrame->InvalidateFrameSubtree(); michael@0: } michael@0: yOriginRG = yEndRG; michael@0: } michael@0: michael@0: ResizeCells(*this); michael@0: } michael@0: michael@0: int32_t nsTableFrame::GetColumnWidth(int32_t aColIndex) michael@0: { michael@0: nsTableFrame* firstInFlow = static_cast(FirstInFlow()); michael@0: if (this == firstInFlow) { michael@0: nsTableColFrame* colFrame = GetColFrame(aColIndex); michael@0: return colFrame ? colFrame->GetFinalWidth() : 0; michael@0: } michael@0: return firstInFlow->GetColumnWidth(aColIndex); michael@0: } michael@0: michael@0: // XXX: could cache this. But be sure to check style changes if you do! michael@0: nscoord nsTableFrame::GetCellSpacingX() michael@0: { michael@0: if (IsBorderCollapse()) michael@0: return 0; michael@0: michael@0: return StyleTableBorder()->mBorderSpacingX; michael@0: } michael@0: michael@0: // XXX: could cache this. But be sure to check style changes if you do! michael@0: nscoord nsTableFrame::GetCellSpacingY() michael@0: { michael@0: if (IsBorderCollapse()) michael@0: return 0; michael@0: michael@0: return StyleTableBorder()->mBorderSpacingY; michael@0: } michael@0: michael@0: michael@0: /* virtual */ nscoord michael@0: nsTableFrame::GetBaseline() const michael@0: { michael@0: nscoord ascent = 0; michael@0: RowGroupArray orderedRowGroups; michael@0: OrderRowGroups(orderedRowGroups); michael@0: nsTableRowFrame* firstRow = nullptr; michael@0: for (uint32_t rgIndex = 0; rgIndex < orderedRowGroups.Length(); rgIndex++) { michael@0: nsTableRowGroupFrame* rgFrame = orderedRowGroups[rgIndex]; michael@0: if (rgFrame->GetRowCount()) { michael@0: firstRow = rgFrame->GetFirstRow(); michael@0: ascent = rgFrame->GetRect().y + firstRow->GetRect().y + firstRow->GetRowBaseline(); michael@0: break; michael@0: } michael@0: } michael@0: if (!firstRow) michael@0: ascent = GetRect().height; michael@0: return ascent; michael@0: } michael@0: /* ----- global methods ----- */ michael@0: michael@0: nsIFrame* michael@0: NS_NewTableFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsTableFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsTableFrame) michael@0: michael@0: nsTableFrame* michael@0: nsTableFrame::GetTableFrame(nsIFrame* aFrame) michael@0: { michael@0: for (nsIFrame* ancestor = aFrame->GetParent(); ancestor; michael@0: ancestor = ancestor->GetParent()) { michael@0: if (nsGkAtoms::tableFrame == ancestor->GetType()) { michael@0: return static_cast(ancestor); michael@0: } michael@0: } michael@0: NS_RUNTIMEABORT("unable to find table parent"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsTableFrame* michael@0: nsTableFrame::GetTableFramePassingThrough(nsIFrame* aMustPassThrough, michael@0: nsIFrame* aFrame) michael@0: { michael@0: MOZ_ASSERT(aMustPassThrough == aFrame || michael@0: nsLayoutUtils::IsProperAncestorFrame(aMustPassThrough, aFrame), michael@0: "aMustPassThrough should be an ancestor"); michael@0: michael@0: // Retrieve the table frame, and ensure that we hit aMustPassThrough on the michael@0: // way. If we don't, just return null. michael@0: bool hitPassThroughFrame = false; michael@0: nsTableFrame* tableFrame = nullptr; michael@0: for (nsIFrame* ancestor = aFrame; ancestor; ancestor = ancestor->GetParent()) { michael@0: if (ancestor == aMustPassThrough) { michael@0: hitPassThroughFrame = true; michael@0: } michael@0: if (nsGkAtoms::tableFrame == ancestor->GetType()) { michael@0: tableFrame = static_cast(ancestor); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(tableFrame, "Should have a table frame here"); michael@0: return hitPassThroughFrame ? tableFrame : nullptr; michael@0: } michael@0: michael@0: bool michael@0: nsTableFrame::IsAutoHeight() michael@0: { michael@0: const nsStyleCoord &height = StylePosition()->mHeight; michael@0: // Don't consider calc() here like this quirk for percent. michael@0: return height.GetUnit() == eStyleUnit_Auto || michael@0: (height.GetUnit() == eStyleUnit_Percent && michael@0: height.GetPercentValue() <= 0.0f); michael@0: } michael@0: michael@0: nscoord michael@0: nsTableFrame::CalcBorderBoxHeight(const nsHTMLReflowState& aState) michael@0: { michael@0: nscoord height = aState.ComputedHeight(); michael@0: if (NS_AUTOHEIGHT != height) { michael@0: nsMargin borderPadding = GetChildAreaOffset(&aState); michael@0: height += borderPadding.top + borderPadding.bottom; michael@0: } michael@0: height = std::max(0, height); michael@0: michael@0: return height; michael@0: } michael@0: michael@0: bool michael@0: nsTableFrame::IsAutoLayout() michael@0: { michael@0: if (StyleTable()->mLayoutStrategy == NS_STYLE_TABLE_LAYOUT_AUTO) michael@0: return true; michael@0: // a fixed-layout inline-table must have a width michael@0: // and tables with 'width: -moz-max-content' must be auto-layout michael@0: // (at least as long as FixedTableLayoutStrategy::GetPrefWidth returns michael@0: // nscoord_MAX) michael@0: const nsStyleCoord &width = StylePosition()->mWidth; michael@0: return (width.GetUnit() == eStyleUnit_Auto) || michael@0: (width.GetUnit() == eStyleUnit_Enumerated && michael@0: width.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT); michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsTableFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("Table"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: // Find the closet sibling before aPriorChildFrame (including aPriorChildFrame) that michael@0: // is of type aChildType michael@0: nsIFrame* michael@0: nsTableFrame::GetFrameAtOrBefore(nsIFrame* aParentFrame, michael@0: nsIFrame* aPriorChildFrame, michael@0: nsIAtom* aChildType) michael@0: { michael@0: nsIFrame* result = nullptr; michael@0: if (!aPriorChildFrame) { michael@0: return result; michael@0: } michael@0: if (aChildType == aPriorChildFrame->GetType()) { michael@0: return aPriorChildFrame; michael@0: } michael@0: michael@0: // aPriorChildFrame is not of type aChildType, so we need start from michael@0: // the beginnng and find the closest one michael@0: nsIFrame* lastMatchingFrame = nullptr; michael@0: nsIFrame* childFrame = aParentFrame->GetFirstPrincipalChild(); michael@0: while (childFrame && (childFrame != aPriorChildFrame)) { michael@0: if (aChildType == childFrame->GetType()) { michael@0: lastMatchingFrame = childFrame; michael@0: } michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: return lastMatchingFrame; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: nsTableFrame::DumpRowGroup(nsIFrame* aKidFrame) michael@0: { michael@0: if (!aKidFrame) michael@0: return; michael@0: michael@0: nsIFrame* cFrame = aKidFrame->GetFirstPrincipalChild(); michael@0: while (cFrame) { michael@0: nsTableRowFrame *rowFrame = do_QueryFrame(cFrame); michael@0: if (rowFrame) { michael@0: printf("row(%d)=%p ", rowFrame->GetRowIndex(), michael@0: static_cast(rowFrame)); michael@0: nsIFrame* childFrame = cFrame->GetFirstPrincipalChild(); michael@0: while (childFrame) { michael@0: nsTableCellFrame *cellFrame = do_QueryFrame(childFrame); michael@0: if (cellFrame) { michael@0: int32_t colIndex; michael@0: cellFrame->GetColIndex(colIndex); michael@0: printf("cell(%d)=%p ", colIndex, static_cast(childFrame)); michael@0: } michael@0: childFrame = childFrame->GetNextSibling(); michael@0: } michael@0: printf("\n"); michael@0: } michael@0: else { michael@0: DumpRowGroup(rowFrame); michael@0: } michael@0: cFrame = cFrame->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTableFrame::Dump(bool aDumpRows, michael@0: bool aDumpCols, michael@0: bool aDumpCellMap) michael@0: { michael@0: printf("***START TABLE DUMP*** \n"); michael@0: // dump the columns widths array michael@0: printf("mColWidths="); michael@0: int32_t numCols = GetColCount(); michael@0: int32_t colX; michael@0: for (colX = 0; colX < numCols; colX++) { michael@0: printf("%d ", GetColumnWidth(colX)); michael@0: } michael@0: printf("\n"); michael@0: michael@0: if (aDumpRows) { michael@0: nsIFrame* kidFrame = mFrames.FirstChild(); michael@0: while (kidFrame) { michael@0: DumpRowGroup(kidFrame); michael@0: kidFrame = kidFrame->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: if (aDumpCols) { michael@0: // output col frame cache michael@0: printf("\n col frame cache ->"); michael@0: for (colX = 0; colX < numCols; colX++) { michael@0: nsTableColFrame* colFrame = mColFrames.ElementAt(colX); michael@0: if (0 == (colX % 8)) { michael@0: printf("\n"); michael@0: } michael@0: printf ("%d=%p ", colX, static_cast(colFrame)); michael@0: nsTableColType colType = colFrame->GetColType(); michael@0: switch (colType) { michael@0: case eColContent: michael@0: printf(" content "); michael@0: break; michael@0: case eColAnonymousCol: michael@0: printf(" anonymous-column "); michael@0: break; michael@0: case eColAnonymousColGroup: michael@0: printf(" anonymous-colgroup "); michael@0: break; michael@0: case eColAnonymousCell: michael@0: printf(" anonymous-cell "); michael@0: break; michael@0: } michael@0: } michael@0: printf("\n colgroups->"); michael@0: for (nsIFrame* childFrame = mColGroups.FirstChild(); childFrame; michael@0: childFrame = childFrame->GetNextSibling()) { michael@0: if (nsGkAtoms::tableColGroupFrame == childFrame->GetType()) { michael@0: nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame *)childFrame; michael@0: colGroupFrame->Dump(1); michael@0: } michael@0: } michael@0: for (colX = 0; colX < numCols; colX++) { michael@0: printf("\n"); michael@0: nsTableColFrame* colFrame = GetColFrame(colX); michael@0: colFrame->Dump(1); michael@0: } michael@0: } michael@0: if (aDumpCellMap) { michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: cellMap->Dump(); michael@0: } michael@0: printf(" ***END TABLE DUMP*** \n"); michael@0: } michael@0: #endif michael@0: michael@0: // nsTableIterator michael@0: nsTableIterator::nsTableIterator(nsIFrame& aSource) michael@0: { michael@0: nsIFrame* firstChild = aSource.GetFirstPrincipalChild(); michael@0: Init(firstChild); michael@0: } michael@0: michael@0: nsTableIterator::nsTableIterator(nsFrameList& aSource) michael@0: { michael@0: nsIFrame* firstChild = aSource.FirstChild(); michael@0: Init(firstChild); michael@0: } michael@0: michael@0: void nsTableIterator::Init(nsIFrame* aFirstChild) michael@0: { michael@0: mFirstListChild = aFirstChild; michael@0: mFirstChild = aFirstChild; michael@0: mCurrentChild = nullptr; michael@0: mLeftToRight = true; michael@0: mCount = -1; michael@0: michael@0: if (!mFirstChild) { michael@0: return; michael@0: } michael@0: michael@0: nsTableFrame* table = nsTableFrame::GetTableFrame(mFirstChild); michael@0: mLeftToRight = (NS_STYLE_DIRECTION_LTR == michael@0: table->StyleVisibility()->mDirection); michael@0: michael@0: if (!mLeftToRight) { michael@0: mCount = 0; michael@0: nsIFrame* nextChild = mFirstChild->GetNextSibling(); michael@0: while (nullptr != nextChild) { michael@0: mCount++; michael@0: mFirstChild = nextChild; michael@0: nextChild = nextChild->GetNextSibling(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIFrame* nsTableIterator::First() michael@0: { michael@0: mCurrentChild = mFirstChild; michael@0: return mCurrentChild; michael@0: } michael@0: michael@0: nsIFrame* nsTableIterator::Next() michael@0: { michael@0: if (!mCurrentChild) { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (mLeftToRight) { michael@0: mCurrentChild = mCurrentChild->GetNextSibling(); michael@0: return mCurrentChild; michael@0: } michael@0: else { michael@0: nsIFrame* targetChild = mCurrentChild; michael@0: mCurrentChild = nullptr; michael@0: nsIFrame* child = mFirstListChild; michael@0: while (child && (child != targetChild)) { michael@0: mCurrentChild = child; michael@0: child = child->GetNextSibling(); michael@0: } michael@0: return mCurrentChild; michael@0: } michael@0: } michael@0: michael@0: bool nsTableIterator::IsLeftToRight() michael@0: { michael@0: return mLeftToRight; michael@0: } michael@0: michael@0: int32_t nsTableIterator::Count() michael@0: { michael@0: if (-1 == mCount) { michael@0: mCount = 0; michael@0: nsIFrame* child = mFirstListChild; michael@0: while (nullptr != child) { michael@0: mCount++; michael@0: child = child->GetNextSibling(); michael@0: } michael@0: } michael@0: return mCount; michael@0: } michael@0: michael@0: bool michael@0: nsTableFrame::ColumnHasCellSpacingBefore(int32_t aColIndex) const michael@0: { michael@0: // Since fixed-layout tables should not have their column sizes change michael@0: // as they load, we assume that all columns are significant. michael@0: if (LayoutStrategy()->GetType() == nsITableLayoutStrategy::Fixed) michael@0: return true; michael@0: // the first column is always significant michael@0: if (aColIndex == 0) michael@0: return true; michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: if (!cellMap) michael@0: return false; michael@0: return cellMap->GetNumCellsOriginatingInCol(aColIndex) > 0; michael@0: } michael@0: michael@0: /******************************************************************************** michael@0: * Collapsing Borders michael@0: * michael@0: * The CSS spec says to resolve border conflicts in this order: michael@0: * 1) any border with the style HIDDEN wins michael@0: * 2) the widest border with a style that is not NONE wins michael@0: * 3) the border styles are ranked in this order, highest to lowest precedence: michael@0: * double, solid, dashed, dotted, ridge, outset, groove, inset michael@0: * 4) borders that are of equal width and style (differ only in color) have this precedence: michael@0: * cell, row, rowgroup, col, colgroup, table michael@0: * 5) if all border styles are NONE, then that's the computed border style. michael@0: *******************************************************************************/ michael@0: michael@0: #ifdef DEBUG michael@0: #define VerifyNonNegativeDamageRect(r) \ michael@0: NS_ASSERTION((r).x >= 0, "negative col index"); \ michael@0: NS_ASSERTION((r).y >= 0, "negative row index"); \ michael@0: NS_ASSERTION((r).width >= 0, "negative horizontal damage"); \ michael@0: NS_ASSERTION((r).height >= 0, "negative vertical damage"); michael@0: #define VerifyDamageRect(r) \ michael@0: VerifyNonNegativeDamageRect(r); \ michael@0: NS_ASSERTION((r).XMost() <= GetColCount(), \ michael@0: "horizontal damage extends outside table"); \ michael@0: NS_ASSERTION((r).YMost() <= GetRowCount(), \ michael@0: "vertical damage extends outside table"); michael@0: #endif michael@0: michael@0: void michael@0: nsTableFrame::AddBCDamageArea(const nsIntRect& aValue) michael@0: { michael@0: NS_ASSERTION(IsBorderCollapse(), "invalid AddBCDamageArea call"); michael@0: #ifdef DEBUG michael@0: VerifyDamageRect(aValue); michael@0: #endif michael@0: michael@0: SetNeedToCalcBCBorders(true); michael@0: // Get the property michael@0: BCPropertyData* value = GetBCProperty(true); michael@0: if (value) { michael@0: #ifdef DEBUG michael@0: VerifyNonNegativeDamageRect(value->mDamageArea); michael@0: #endif michael@0: // Clamp the old damage area to the current table area in case it shrunk. michael@0: int32_t cols = GetColCount(); michael@0: if (value->mDamageArea.XMost() > cols) { michael@0: if (value->mDamageArea.x > cols) { michael@0: value->mDamageArea.x = cols; michael@0: value->mDamageArea.width = 0; michael@0: } michael@0: else { michael@0: value->mDamageArea.width = cols - value->mDamageArea.x; michael@0: } michael@0: } michael@0: int32_t rows = GetRowCount(); michael@0: if (value->mDamageArea.YMost() > rows) { michael@0: if (value->mDamageArea.y > rows) { michael@0: value->mDamageArea.y = rows; michael@0: value->mDamageArea.height = 0; michael@0: } michael@0: else { michael@0: value->mDamageArea.height = rows - value->mDamageArea.y; michael@0: } michael@0: } michael@0: michael@0: // Construct a union of the new and old damage areas. michael@0: value->mDamageArea.UnionRect(value->mDamageArea, aValue); michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: nsTableFrame::SetFullBCDamageArea() michael@0: { michael@0: NS_ASSERTION(IsBorderCollapse(), "invalid SetFullBCDamageArea call"); michael@0: michael@0: SetNeedToCalcBCBorders(true); michael@0: michael@0: BCPropertyData* value = GetBCProperty(true); michael@0: if (value) { michael@0: value->mDamageArea = nsIntRect(0, 0, GetColCount(), GetRowCount()); michael@0: } michael@0: } michael@0: michael@0: michael@0: /* BCCellBorder represents a border segment which can be either a horizontal michael@0: * or a vertical segment. For each segment we need to know the color, width, michael@0: * style, who owns it and how long it is in cellmap coordinates. michael@0: * Ownership of these segments is important to calculate which corners should michael@0: * be bevelled. This structure has dual use, its used first to compute the michael@0: * dominant border for horizontal and vertical segments and to store the michael@0: * preliminary computed border results in the BCCellBorders structure. michael@0: * This temporary storage is not symmetric with respect to horizontal and michael@0: * vertical border segments, its always column oriented. For each column in michael@0: * the cellmap there is a temporary stored vertical and horizontal segment. michael@0: * XXX_Bernd this asymmetry is the root of those rowspan bc border errors michael@0: */ michael@0: struct BCCellBorder michael@0: { michael@0: BCCellBorder() { Reset(0, 1); } michael@0: void Reset(uint32_t aRowIndex, uint32_t aRowSpan); michael@0: nscolor color; // border segment color michael@0: BCPixelSize width; // border segment width in pixel coordinates !! michael@0: uint8_t style; // border segment style, possible values are defined michael@0: // in nsStyleConsts.h as NS_STYLE_BORDER_STYLE_* michael@0: BCBorderOwner owner; // border segment owner, possible values are defined michael@0: // in celldata.h. In the cellmap for each border michael@0: // segment we store the owner and later when michael@0: // painting we know the owner and can retrieve the michael@0: // style info from the corresponding frame michael@0: int32_t rowIndex; // rowIndex of temporary stored horizontal border michael@0: // segments relative to the table michael@0: int32_t rowSpan; // row span of temporary stored horizontal border michael@0: // segments michael@0: }; michael@0: michael@0: void michael@0: BCCellBorder::Reset(uint32_t aRowIndex, michael@0: uint32_t aRowSpan) michael@0: { michael@0: style = NS_STYLE_BORDER_STYLE_NONE; michael@0: color = 0; michael@0: width = 0; michael@0: owner = eTableOwner; michael@0: rowIndex = aRowIndex; michael@0: rowSpan = aRowSpan; michael@0: } michael@0: michael@0: class BCMapCellIterator; michael@0: michael@0: /***************************************************************** michael@0: * BCMapCellInfo michael@0: * This structure stores information about the cellmap and all involved michael@0: * table related frames that are used during the computation of winning borders michael@0: * in CalcBCBorders so that they do need to be looked up again and again when michael@0: * iterating over the cells. michael@0: ****************************************************************/ michael@0: struct BCMapCellInfo michael@0: { michael@0: BCMapCellInfo(nsTableFrame* aTableFrame); michael@0: void ResetCellInfo(); michael@0: void SetInfo(nsTableRowFrame* aNewRow, michael@0: int32_t aColIndex, michael@0: BCCellData* aCellData, michael@0: BCMapCellIterator* aIter, michael@0: nsCellMap* aCellMap = nullptr); michael@0: // The BCMapCellInfo has functions to set the continous michael@0: // border widths (see nsTablePainter.cpp for a description of the continous michael@0: // borders concept). The widths are computed inside these functions based on michael@0: // the current position inside the table and the cached frames that correspond michael@0: // to this position. The widths are stored in member variables of the internal michael@0: // table frames. michael@0: void SetTableTopLeftContBCBorder(); michael@0: void SetRowGroupLeftContBCBorder(); michael@0: void SetRowGroupRightContBCBorder(); michael@0: void SetRowGroupBottomContBCBorder(); michael@0: void SetRowLeftContBCBorder(); michael@0: void SetRowRightContBCBorder(); michael@0: void SetColumnTopRightContBCBorder(); michael@0: void SetColumnBottomContBCBorder(); michael@0: void SetColGroupBottomContBCBorder(); michael@0: void SetInnerRowGroupBottomContBCBorder(const nsIFrame* aNextRowGroup, michael@0: nsTableRowFrame* aNextRow); michael@0: michael@0: // functions to set the border widths on the table related frames, where the michael@0: // knowledge about the current position in the table is used. michael@0: void SetTableTopBorderWidth(BCPixelSize aWidth); michael@0: void SetTableLeftBorderWidth(int32_t aRowY, BCPixelSize aWidth); michael@0: void SetTableRightBorderWidth(int32_t aRowY, BCPixelSize aWidth); michael@0: void SetTableBottomBorderWidth(BCPixelSize aWidth); michael@0: void SetLeftBorderWidths(BCPixelSize aWidth); michael@0: void SetRightBorderWidths(BCPixelSize aWidth); michael@0: void SetTopBorderWidths(BCPixelSize aWidth); michael@0: void SetBottomBorderWidths(BCPixelSize aWidth); michael@0: michael@0: // functions to compute the borders; they depend on the michael@0: // knowledge about the current position in the table. The edge functions michael@0: // should be called if a table edge is involved, otherwise the internal michael@0: // functions should be called. michael@0: BCCellBorder GetTopEdgeBorder(); michael@0: BCCellBorder GetBottomEdgeBorder(); michael@0: BCCellBorder GetLeftEdgeBorder(); michael@0: BCCellBorder GetRightEdgeBorder(); michael@0: BCCellBorder GetRightInternalBorder(); michael@0: BCCellBorder GetLeftInternalBorder(); michael@0: BCCellBorder GetTopInternalBorder(); michael@0: BCCellBorder GetBottomInternalBorder(); michael@0: michael@0: // functions to set the interal position information michael@0: void SetColumn(int32_t aColX); michael@0: // Increment the row as we loop over the rows of a rowspan michael@0: void IncrementRow(bool aResetToTopRowOfCell = false); michael@0: michael@0: // Helper functions to get extent of the cell michael@0: int32_t GetCellEndRowIndex() const; michael@0: int32_t GetCellEndColIndex() const; michael@0: michael@0: // storage of table information michael@0: nsTableFrame* mTableFrame; michael@0: int32_t mNumTableRows; michael@0: int32_t mNumTableCols; michael@0: BCPropertyData* mTableBCData; michael@0: michael@0: // storage of table ltr information, the border collapse code swaps the sides michael@0: // to account for rtl tables, this is done through mStartSide and mEndSide michael@0: bool mTableIsLTR; michael@0: mozilla::css::Side mStartSide; michael@0: mozilla::css::Side mEndSide; michael@0: michael@0: // a cell can only belong to one rowgroup michael@0: nsTableRowGroupFrame* mRowGroup; michael@0: michael@0: // a cell with a rowspan has a top and a bottom row, and rows in between michael@0: nsTableRowFrame* mTopRow; michael@0: nsTableRowFrame* mBottomRow; michael@0: nsTableRowFrame* mCurrentRowFrame; michael@0: michael@0: // a cell with a colspan has a left and right column and columns in between michael@0: // they can belong to different colgroups michael@0: nsTableColGroupFrame* mColGroup; michael@0: nsTableColGroupFrame* mCurrentColGroupFrame; michael@0: michael@0: nsTableColFrame* mLeftCol; michael@0: nsTableColFrame* mRightCol; michael@0: nsTableColFrame* mCurrentColFrame; michael@0: michael@0: // cell information michael@0: BCCellData* mCellData; michael@0: nsBCTableCellFrame* mCell; michael@0: michael@0: int32_t mRowIndex; michael@0: int32_t mRowSpan; michael@0: int32_t mColIndex; michael@0: int32_t mColSpan; michael@0: michael@0: // flags to describe the position of the cell with respect to the row- and michael@0: // colgroups, for instance mRgAtTop documents that the top cell border hits michael@0: // a rowgroup border michael@0: bool mRgAtTop; michael@0: bool mRgAtBottom; michael@0: bool mCgAtLeft; michael@0: bool mCgAtRight; michael@0: michael@0: }; michael@0: michael@0: michael@0: BCMapCellInfo::BCMapCellInfo(nsTableFrame* aTableFrame) michael@0: { michael@0: mTableFrame = aTableFrame; michael@0: mTableIsLTR = michael@0: aTableFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR; michael@0: if (mTableIsLTR) { michael@0: mStartSide = NS_SIDE_LEFT; michael@0: mEndSide = NS_SIDE_RIGHT; michael@0: } michael@0: else { michael@0: mStartSide = NS_SIDE_RIGHT; michael@0: mEndSide = NS_SIDE_LEFT; michael@0: } michael@0: mNumTableRows = mTableFrame->GetRowCount(); michael@0: mNumTableCols = mTableFrame->GetColCount(); michael@0: mTableBCData = static_cast michael@0: (mTableFrame->Properties().Get(TableBCProperty())); michael@0: michael@0: ResetCellInfo(); michael@0: } michael@0: michael@0: void BCMapCellInfo::ResetCellInfo() michael@0: { michael@0: mCellData = nullptr; michael@0: mRowGroup = nullptr; michael@0: mTopRow = nullptr; michael@0: mBottomRow = nullptr; michael@0: mColGroup = nullptr; michael@0: mLeftCol = nullptr; michael@0: mRightCol = nullptr; michael@0: mCell = nullptr; michael@0: mRowIndex = mRowSpan = mColIndex = mColSpan = 0; michael@0: mRgAtTop = mRgAtBottom = mCgAtLeft = mCgAtRight = false; michael@0: } michael@0: michael@0: inline int32_t BCMapCellInfo::GetCellEndRowIndex() const michael@0: { michael@0: return mRowIndex + mRowSpan - 1; michael@0: } michael@0: michael@0: inline int32_t BCMapCellInfo::GetCellEndColIndex() const michael@0: { michael@0: return mColIndex + mColSpan - 1; michael@0: } michael@0: michael@0: michael@0: class BCMapCellIterator michael@0: { michael@0: public: michael@0: BCMapCellIterator(nsTableFrame* aTableFrame, michael@0: const nsIntRect& aDamageArea); michael@0: michael@0: void First(BCMapCellInfo& aMapCellInfo); michael@0: michael@0: void Next(BCMapCellInfo& aMapCellInfo); michael@0: michael@0: void PeekRight(BCMapCellInfo& aRefInfo, michael@0: uint32_t aRowIndex, michael@0: BCMapCellInfo& aAjaInfo); michael@0: michael@0: void PeekBottom(BCMapCellInfo& aRefInfo, michael@0: uint32_t aColIndex, michael@0: BCMapCellInfo& aAjaInfo); michael@0: michael@0: bool IsNewRow() { return mIsNewRow; } michael@0: michael@0: nsTableRowFrame* GetPrevRow() const { return mPrevRow; } michael@0: nsTableRowFrame* GetCurrentRow() const { return mRow; } michael@0: nsTableRowGroupFrame* GetCurrentRowGroup() const { return mRowGroup;} michael@0: michael@0: int32_t mRowGroupStart; michael@0: int32_t mRowGroupEnd; michael@0: bool mAtEnd; michael@0: nsCellMap* mCellMap; michael@0: michael@0: private: michael@0: bool SetNewRow(nsTableRowFrame* row = nullptr); michael@0: bool SetNewRowGroup(bool aFindFirstDamagedRow); michael@0: michael@0: nsTableFrame* mTableFrame; michael@0: nsTableCellMap* mTableCellMap; michael@0: nsTableFrame::RowGroupArray mRowGroups; michael@0: nsTableRowGroupFrame* mRowGroup; michael@0: int32_t mRowGroupIndex; michael@0: uint32_t mNumTableRows; michael@0: nsTableRowFrame* mRow; michael@0: nsTableRowFrame* mPrevRow; michael@0: bool mIsNewRow; michael@0: int32_t mRowIndex; michael@0: uint32_t mNumTableCols; michael@0: int32_t mColIndex; michael@0: nsPoint mAreaStart; michael@0: nsPoint mAreaEnd; michael@0: }; michael@0: michael@0: BCMapCellIterator::BCMapCellIterator(nsTableFrame* aTableFrame, michael@0: const nsIntRect& aDamageArea) michael@0: :mTableFrame(aTableFrame) michael@0: { michael@0: mTableCellMap = aTableFrame->GetCellMap(); michael@0: michael@0: mAreaStart.x = aDamageArea.x; michael@0: mAreaStart.y = aDamageArea.y; michael@0: mAreaEnd.y = aDamageArea.y + aDamageArea.height - 1; michael@0: mAreaEnd.x = aDamageArea.x + aDamageArea.width - 1; michael@0: michael@0: mNumTableRows = mTableFrame->GetRowCount(); michael@0: mRow = nullptr; michael@0: mRowIndex = 0; michael@0: mNumTableCols = mTableFrame->GetColCount(); michael@0: mColIndex = 0; michael@0: mRowGroupIndex = -1; michael@0: michael@0: // Get the ordered row groups michael@0: aTableFrame->OrderRowGroups(mRowGroups); michael@0: michael@0: mAtEnd = true; // gets reset when First() is called michael@0: } michael@0: michael@0: // fill fields that we need for border collapse computation on a given cell michael@0: void michael@0: BCMapCellInfo::SetInfo(nsTableRowFrame* aNewRow, michael@0: int32_t aColIndex, michael@0: BCCellData* aCellData, michael@0: BCMapCellIterator* aIter, michael@0: nsCellMap* aCellMap) michael@0: { michael@0: // fill the cell information michael@0: mCellData = aCellData; michael@0: mColIndex = aColIndex; michael@0: michael@0: // initialize the row information if it was not previously set for cells in michael@0: // this row michael@0: mRowIndex = 0; michael@0: if (aNewRow) { michael@0: mTopRow = aNewRow; michael@0: mRowIndex = aNewRow->GetRowIndex(); michael@0: } michael@0: michael@0: // fill cell frame info and row information michael@0: mCell = nullptr; michael@0: mRowSpan = 1; michael@0: mColSpan = 1; michael@0: if (aCellData) { michael@0: mCell = static_cast(aCellData->GetCellFrame()); michael@0: if (mCell) { michael@0: if (!mTopRow) { michael@0: mTopRow = static_cast(mCell->GetParent()); michael@0: if (!mTopRow) ABORT0(); michael@0: mRowIndex = mTopRow->GetRowIndex(); michael@0: } michael@0: mColSpan = mTableFrame->GetEffectiveColSpan(*mCell, aCellMap); michael@0: mRowSpan = mTableFrame->GetEffectiveRowSpan(*mCell, aCellMap); michael@0: } michael@0: } michael@0: michael@0: if (!mTopRow) { michael@0: mTopRow = aIter->GetCurrentRow(); michael@0: } michael@0: if (1 == mRowSpan) { michael@0: mBottomRow = mTopRow; michael@0: } michael@0: else { michael@0: mBottomRow = mTopRow->GetNextRow(); michael@0: if (mBottomRow) { michael@0: for (int32_t spanY = 2; mBottomRow && (spanY < mRowSpan); spanY++) { michael@0: mBottomRow = mBottomRow->GetNextRow(); michael@0: } michael@0: NS_ASSERTION(mBottomRow, "spanned row not found"); michael@0: } michael@0: else { michael@0: NS_ASSERTION(false, "error in cell map"); michael@0: mRowSpan = 1; michael@0: mBottomRow = mTopRow; michael@0: } michael@0: } michael@0: // row group frame info michael@0: // try to reuse the rgStart and rgEnd from the iterator as calls to michael@0: // GetRowCount() are computationally expensive and should be avoided if michael@0: // possible michael@0: uint32_t rgStart = aIter->mRowGroupStart; michael@0: uint32_t rgEnd = aIter->mRowGroupEnd; michael@0: mRowGroup = static_cast(mTopRow->GetParent()); michael@0: if (mRowGroup != aIter->GetCurrentRowGroup()) { michael@0: rgStart = mRowGroup->GetStartRowIndex(); michael@0: rgEnd = rgStart + mRowGroup->GetRowCount() - 1; michael@0: } michael@0: uint32_t rowIndex = mTopRow->GetRowIndex(); michael@0: mRgAtTop = (rgStart == rowIndex); michael@0: mRgAtBottom = (rgEnd == rowIndex + mRowSpan - 1); michael@0: michael@0: // col frame info michael@0: mLeftCol = mTableFrame->GetColFrame(aColIndex); michael@0: if (!mLeftCol) ABORT0(); michael@0: michael@0: mRightCol = mLeftCol; michael@0: if (mColSpan > 1) { michael@0: nsTableColFrame* colFrame = mTableFrame->GetColFrame(aColIndex + michael@0: mColSpan -1); michael@0: if (!colFrame) ABORT0(); michael@0: mRightCol = colFrame; michael@0: } michael@0: michael@0: // col group frame info michael@0: mColGroup = static_cast(mLeftCol->GetParent()); michael@0: int32_t cgStart = mColGroup->GetStartColumnIndex(); michael@0: int32_t cgEnd = std::max(0, cgStart + mColGroup->GetColCount() - 1); michael@0: mCgAtLeft = (cgStart == aColIndex); michael@0: mCgAtRight = (cgEnd == aColIndex + mColSpan - 1); michael@0: } michael@0: michael@0: bool michael@0: BCMapCellIterator::SetNewRow(nsTableRowFrame* aRow) michael@0: { michael@0: mAtEnd = true; michael@0: mPrevRow = mRow; michael@0: if (aRow) { michael@0: mRow = aRow; michael@0: } michael@0: else if (mRow) { michael@0: mRow = mRow->GetNextRow(); michael@0: } michael@0: if (mRow) { michael@0: mRowIndex = mRow->GetRowIndex(); michael@0: // get to the first entry with an originating cell michael@0: int32_t rgRowIndex = mRowIndex - mRowGroupStart; michael@0: if (uint32_t(rgRowIndex) >= mCellMap->mRows.Length()) michael@0: ABORT1(false); michael@0: const nsCellMap::CellDataArray& row = mCellMap->mRows[rgRowIndex]; michael@0: michael@0: for (mColIndex = mAreaStart.x; mColIndex <= mAreaEnd.x; mColIndex++) { michael@0: CellData* cellData = row.SafeElementAt(mColIndex); michael@0: if (!cellData) { // add a dead cell data michael@0: nsIntRect damageArea; michael@0: cellData = mCellMap->AppendCell(*mTableCellMap, nullptr, rgRowIndex, michael@0: false, 0, damageArea); michael@0: if (!cellData) ABORT1(false); michael@0: } michael@0: if (cellData && (cellData->IsOrig() || cellData->IsDead())) { michael@0: break; michael@0: } michael@0: } michael@0: mIsNewRow = true; michael@0: mAtEnd = false; michael@0: } michael@0: else ABORT1(false); michael@0: michael@0: return !mAtEnd; michael@0: } michael@0: michael@0: bool michael@0: BCMapCellIterator::SetNewRowGroup(bool aFindFirstDamagedRow) michael@0: { michael@0: mAtEnd = true; michael@0: int32_t numRowGroups = mRowGroups.Length(); michael@0: mCellMap = nullptr; michael@0: for (mRowGroupIndex++; mRowGroupIndex < numRowGroups; mRowGroupIndex++) { michael@0: mRowGroup = mRowGroups[mRowGroupIndex]; michael@0: int32_t rowCount = mRowGroup->GetRowCount(); michael@0: mRowGroupStart = mRowGroup->GetStartRowIndex(); michael@0: mRowGroupEnd = mRowGroupStart + rowCount - 1; michael@0: if (rowCount > 0) { michael@0: mCellMap = mTableCellMap->GetMapFor(mRowGroup, mCellMap); michael@0: if (!mCellMap) ABORT1(false); michael@0: nsTableRowFrame* firstRow = mRowGroup->GetFirstRow(); michael@0: if (aFindFirstDamagedRow) { michael@0: if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) { michael@0: // the damage area starts in the row group michael@0: if (aFindFirstDamagedRow) { michael@0: // find the correct first damaged row michael@0: int32_t numRows = mAreaStart.y - mRowGroupStart; michael@0: for (int32_t i = 0; i < numRows; i++) { michael@0: firstRow = firstRow->GetNextRow(); michael@0: if (!firstRow) ABORT1(false); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: continue; michael@0: } michael@0: } michael@0: if (SetNewRow(firstRow)) { // sets mAtEnd michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return !mAtEnd; michael@0: } michael@0: michael@0: void michael@0: BCMapCellIterator::First(BCMapCellInfo& aMapInfo) michael@0: { michael@0: aMapInfo.ResetCellInfo(); michael@0: michael@0: SetNewRowGroup(true); // sets mAtEnd michael@0: while (!mAtEnd) { michael@0: if ((mAreaStart.y >= mRowGroupStart) && (mAreaStart.y <= mRowGroupEnd)) { michael@0: BCCellData* cellData = michael@0: static_cast(mCellMap->GetDataAt(mAreaStart.y - michael@0: mRowGroupStart, michael@0: mAreaStart.x)); michael@0: if (cellData && (cellData->IsOrig() || cellData->IsDead())) { michael@0: aMapInfo.SetInfo(mRow, mAreaStart.x, cellData, this); michael@0: return; michael@0: } michael@0: else { michael@0: NS_ASSERTION(((0 == mAreaStart.x) && (mRowGroupStart == mAreaStart.y)) , michael@0: "damage area expanded incorrectly"); michael@0: } michael@0: } michael@0: SetNewRowGroup(true); // sets mAtEnd michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellIterator::Next(BCMapCellInfo& aMapInfo) michael@0: { michael@0: if (mAtEnd) ABORT0(); michael@0: aMapInfo.ResetCellInfo(); michael@0: michael@0: mIsNewRow = false; michael@0: mColIndex++; michael@0: while ((mRowIndex <= mAreaEnd.y) && !mAtEnd) { michael@0: for (; mColIndex <= mAreaEnd.x; mColIndex++) { michael@0: int32_t rgRowIndex = mRowIndex - mRowGroupStart; michael@0: BCCellData* cellData = michael@0: static_cast(mCellMap->GetDataAt(rgRowIndex, mColIndex)); michael@0: if (!cellData) { // add a dead cell data michael@0: nsIntRect damageArea; michael@0: cellData = michael@0: static_cast(mCellMap->AppendCell(*mTableCellMap, nullptr, michael@0: rgRowIndex, false, 0, michael@0: damageArea)); michael@0: if (!cellData) ABORT0(); michael@0: } michael@0: if (cellData && (cellData->IsOrig() || cellData->IsDead())) { michael@0: aMapInfo.SetInfo(mRow, mColIndex, cellData, this); michael@0: return; michael@0: } michael@0: } michael@0: if (mRowIndex >= mRowGroupEnd) { michael@0: SetNewRowGroup(false); // could set mAtEnd michael@0: } michael@0: else { michael@0: SetNewRow(); // could set mAtEnd michael@0: } michael@0: } michael@0: mAtEnd = true; michael@0: } michael@0: michael@0: void michael@0: BCMapCellIterator::PeekRight(BCMapCellInfo& aRefInfo, michael@0: uint32_t aRowIndex, michael@0: BCMapCellInfo& aAjaInfo) michael@0: { michael@0: aAjaInfo.ResetCellInfo(); michael@0: int32_t colIndex = aRefInfo.mColIndex + aRefInfo.mColSpan; michael@0: uint32_t rgRowIndex = aRowIndex - mRowGroupStart; michael@0: michael@0: BCCellData* cellData = michael@0: static_cast(mCellMap->GetDataAt(rgRowIndex, colIndex)); michael@0: if (!cellData) { // add a dead cell data michael@0: NS_ASSERTION(colIndex < mTableCellMap->GetColCount(), "program error"); michael@0: nsIntRect damageArea; michael@0: cellData = michael@0: static_cast(mCellMap->AppendCell(*mTableCellMap, nullptr, michael@0: rgRowIndex, false, 0, michael@0: damageArea)); michael@0: if (!cellData) ABORT0(); michael@0: } michael@0: nsTableRowFrame* row = nullptr; michael@0: if (cellData->IsRowSpan()) { michael@0: rgRowIndex -= cellData->GetRowSpanOffset(); michael@0: cellData = michael@0: static_cast(mCellMap->GetDataAt(rgRowIndex, colIndex)); michael@0: if (!cellData) michael@0: ABORT0(); michael@0: } michael@0: else { michael@0: row = mRow; michael@0: } michael@0: aAjaInfo.SetInfo(row, colIndex, cellData, this); michael@0: } michael@0: michael@0: void michael@0: BCMapCellIterator::PeekBottom(BCMapCellInfo& aRefInfo, michael@0: uint32_t aColIndex, michael@0: BCMapCellInfo& aAjaInfo) michael@0: { michael@0: aAjaInfo.ResetCellInfo(); michael@0: int32_t rowIndex = aRefInfo.mRowIndex + aRefInfo.mRowSpan; michael@0: int32_t rgRowIndex = rowIndex - mRowGroupStart; michael@0: nsTableRowGroupFrame* rg = mRowGroup; michael@0: nsCellMap* cellMap = mCellMap; michael@0: nsTableRowFrame* nextRow = nullptr; michael@0: if (rowIndex > mRowGroupEnd) { michael@0: int32_t nextRgIndex = mRowGroupIndex; michael@0: do { michael@0: nextRgIndex++; michael@0: rg = mRowGroups.SafeElementAt(nextRgIndex); michael@0: if (rg) { michael@0: cellMap = mTableCellMap->GetMapFor(rg, cellMap); if (!cellMap) ABORT0(); michael@0: rgRowIndex = 0; michael@0: nextRow = rg->GetFirstRow(); michael@0: } michael@0: } michael@0: while (rg && !nextRow); michael@0: if(!rg) return; michael@0: } michael@0: else { michael@0: // get the row within the same row group michael@0: nextRow = mRow; michael@0: for (int32_t i = 0; i < aRefInfo.mRowSpan; i++) { michael@0: nextRow = nextRow->GetNextRow(); if (!nextRow) ABORT0(); michael@0: } michael@0: } michael@0: michael@0: BCCellData* cellData = michael@0: static_cast(cellMap->GetDataAt(rgRowIndex, aColIndex)); michael@0: if (!cellData) { // add a dead cell data michael@0: NS_ASSERTION(rgRowIndex < cellMap->GetRowCount(), "program error"); michael@0: nsIntRect damageArea; michael@0: cellData = michael@0: static_cast(cellMap->AppendCell(*mTableCellMap, nullptr, michael@0: rgRowIndex, false, 0, michael@0: damageArea)); michael@0: if (!cellData) ABORT0(); michael@0: } michael@0: if (cellData->IsColSpan()) { michael@0: aColIndex -= cellData->GetColSpanOffset(); michael@0: cellData = michael@0: static_cast(cellMap->GetDataAt(rgRowIndex, aColIndex)); michael@0: } michael@0: aAjaInfo.SetInfo(nextRow, aColIndex, cellData, this, cellMap); michael@0: } michael@0: michael@0: // Assign priorities to border styles. For example, styleToPriority(NS_STYLE_BORDER_STYLE_SOLID) michael@0: // will return the priority of NS_STYLE_BORDER_STYLE_SOLID. michael@0: static uint8_t styleToPriority[13] = { 0, // NS_STYLE_BORDER_STYLE_NONE michael@0: 2, // NS_STYLE_BORDER_STYLE_GROOVE michael@0: 4, // NS_STYLE_BORDER_STYLE_RIDGE michael@0: 5, // NS_STYLE_BORDER_STYLE_DOTTED michael@0: 6, // NS_STYLE_BORDER_STYLE_DASHED michael@0: 7, // NS_STYLE_BORDER_STYLE_SOLID michael@0: 8, // NS_STYLE_BORDER_STYLE_DOUBLE michael@0: 1, // NS_STYLE_BORDER_STYLE_INSET michael@0: 3, // NS_STYLE_BORDER_STYLE_OUTSET michael@0: 9 };// NS_STYLE_BORDER_STYLE_HIDDEN michael@0: // priority rules follow CSS 2.1 spec michael@0: // 'hidden', 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', michael@0: // and the lowest: 'inset'. none is even weaker michael@0: #define CELL_CORNER true michael@0: michael@0: /** return the border style, border color for a given frame and side michael@0: * @param aFrame - query the info for this frame michael@0: * @param aSide - the side of the frame michael@0: * @param aStyle - the border style michael@0: * @param aColor - the border color michael@0: * @param aTableIsLTR - table direction is LTR michael@0: */ michael@0: static void michael@0: GetColorAndStyle(const nsIFrame* aFrame, michael@0: mozilla::css::Side aSide, michael@0: uint8_t& aStyle, michael@0: nscolor& aColor, michael@0: bool aTableIsLTR) michael@0: { michael@0: NS_PRECONDITION(aFrame, "null frame"); michael@0: // initialize out arg michael@0: aColor = 0; michael@0: const nsStyleBorder* styleData = aFrame->StyleBorder(); michael@0: if(!aTableIsLTR) { // revert the directions michael@0: if (NS_SIDE_RIGHT == aSide) { michael@0: aSide = NS_SIDE_LEFT; michael@0: } michael@0: else if (NS_SIDE_LEFT == aSide) { michael@0: aSide = NS_SIDE_RIGHT; michael@0: } michael@0: } michael@0: aStyle = styleData->GetBorderStyle(aSide); michael@0: michael@0: if ((NS_STYLE_BORDER_STYLE_NONE == aStyle) || michael@0: (NS_STYLE_BORDER_STYLE_HIDDEN == aStyle)) { michael@0: return; michael@0: } michael@0: aColor = aFrame->StyleContext()->GetVisitedDependentColor( michael@0: nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[aSide]); michael@0: } michael@0: michael@0: /** coerce the paint style as required by CSS2.1 michael@0: * @param aFrame - query the info for this frame michael@0: * @param aSide - the side of the frame michael@0: * @param aStyle - the border style michael@0: * @param aColor - the border color michael@0: * @param aTableIsLTR - table direction is LTR michael@0: */ michael@0: static void michael@0: GetPaintStyleInfo(const nsIFrame* aFrame, michael@0: mozilla::css::Side aSide, michael@0: uint8_t& aStyle, michael@0: nscolor& aColor, michael@0: bool aTableIsLTR) michael@0: { michael@0: GetColorAndStyle(aFrame, aSide, aStyle, aColor, aTableIsLTR); michael@0: if (NS_STYLE_BORDER_STYLE_INSET == aStyle) { michael@0: aStyle = NS_STYLE_BORDER_STYLE_RIDGE; michael@0: } michael@0: else if (NS_STYLE_BORDER_STYLE_OUTSET == aStyle) { michael@0: aStyle = NS_STYLE_BORDER_STYLE_GROOVE; michael@0: } michael@0: } michael@0: michael@0: /** return the border style, border color and the width in pixel for a given michael@0: * frame and side michael@0: * @param aFrame - query the info for this frame michael@0: * @param aSide - the side of the frame michael@0: * @param aStyle - the border style michael@0: * @param aColor - the border color michael@0: * @param aTableIsLTR - table direction is LTR michael@0: * @param aWidth - the border width in px. michael@0: * @param aTwipsToPixels - conversion factor from twips to pixel michael@0: */ michael@0: static void michael@0: GetColorAndStyle(const nsIFrame* aFrame, michael@0: mozilla::css::Side aSide, michael@0: uint8_t& aStyle, michael@0: nscolor& aColor, michael@0: bool aTableIsLTR, michael@0: BCPixelSize& aWidth) michael@0: { michael@0: GetColorAndStyle(aFrame, aSide, aStyle, aColor, aTableIsLTR); michael@0: if ((NS_STYLE_BORDER_STYLE_NONE == aStyle) || michael@0: (NS_STYLE_BORDER_STYLE_HIDDEN == aStyle)) { michael@0: aWidth = 0; michael@0: return; michael@0: } michael@0: const nsStyleBorder* styleData = aFrame->StyleBorder(); michael@0: nscoord width; michael@0: if(!aTableIsLTR) { // revert the directions michael@0: if (NS_SIDE_RIGHT == aSide) { michael@0: aSide = NS_SIDE_LEFT; michael@0: } michael@0: else if (NS_SIDE_LEFT == aSide) { michael@0: aSide = NS_SIDE_RIGHT; michael@0: } michael@0: } michael@0: width = styleData->GetComputedBorderWidth(aSide); michael@0: aWidth = nsPresContext::AppUnitsToIntCSSPixels(width); michael@0: } michael@0: michael@0: class nsDelayedCalcBCBorders : public nsRunnable { michael@0: public: michael@0: nsDelayedCalcBCBorders(nsIFrame* aFrame) : michael@0: mFrame(aFrame) {} michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE { michael@0: if (mFrame) { michael@0: nsTableFrame* tableFrame = static_cast (mFrame.GetFrame()); michael@0: if (tableFrame->NeedToCalcBCBorders()) { michael@0: tableFrame->CalcBCBorders(); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsWeakFrame mFrame; michael@0: }; michael@0: michael@0: bool michael@0: nsTableFrame::BCRecalcNeeded(nsStyleContext* aOldStyleContext, michael@0: nsStyleContext* aNewStyleContext) michael@0: { michael@0: // Attention: the old style context is the one we're forgetting, michael@0: // and hence possibly completely bogus for GetStyle* purposes. michael@0: // We use PeekStyleData instead. michael@0: michael@0: const nsStyleBorder* oldStyleData = aOldStyleContext->PeekStyleBorder(); michael@0: if (!oldStyleData) michael@0: return false; michael@0: michael@0: const nsStyleBorder* newStyleData = aNewStyleContext->StyleBorder(); michael@0: nsChangeHint change = newStyleData->CalcDifference(*oldStyleData); michael@0: if (!change) michael@0: return false; michael@0: if (change & nsChangeHint_NeedReflow) michael@0: return true; // the caller only needs to mark the bc damage area michael@0: if (change & nsChangeHint_RepaintFrame) { michael@0: // we need to recompute the borders and the caller needs to mark michael@0: // the bc damage area michael@0: // XXX In principle this should only be necessary for border style changes michael@0: // However the bc painting code tries to maximize the drawn border segments michael@0: // so it stores in the cellmap where a new border segment starts and this michael@0: // introduces a unwanted cellmap data dependence on color michael@0: nsCOMPtr evt = new nsDelayedCalcBCBorders(this); michael@0: NS_DispatchToCurrentThread(evt); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: michael@0: // Compare two border segments, this comparison depends whether the two michael@0: // segments meet at a corner and whether the second segment is horizontal. michael@0: // The return value is whichever of aBorder1 or aBorder2 dominates. michael@0: static const BCCellBorder& michael@0: CompareBorders(bool aIsCorner, // Pass true for corner calculations michael@0: const BCCellBorder& aBorder1, michael@0: const BCCellBorder& aBorder2, michael@0: bool aSecondIsHorizontal, michael@0: bool* aFirstDominates = nullptr) michael@0: { michael@0: bool firstDominates = true; michael@0: michael@0: if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder1.style) { michael@0: firstDominates = (aIsCorner) ? false : true; michael@0: } michael@0: else if (NS_STYLE_BORDER_STYLE_HIDDEN == aBorder2.style) { michael@0: firstDominates = (aIsCorner) ? true : false; michael@0: } michael@0: else if (aBorder1.width < aBorder2.width) { michael@0: firstDominates = false; michael@0: } michael@0: else if (aBorder1.width == aBorder2.width) { michael@0: if (styleToPriority[aBorder1.style] < styleToPriority[aBorder2.style]) { michael@0: firstDominates = false; michael@0: } michael@0: else if (styleToPriority[aBorder1.style] == styleToPriority[aBorder2.style]) { michael@0: if (aBorder1.owner == aBorder2.owner) { michael@0: firstDominates = !aSecondIsHorizontal; michael@0: } michael@0: else if (aBorder1.owner < aBorder2.owner) { michael@0: firstDominates = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aFirstDominates) michael@0: *aFirstDominates = firstDominates; michael@0: michael@0: if (firstDominates) michael@0: return aBorder1; michael@0: return aBorder2; michael@0: } michael@0: michael@0: /** calc the dominant border by considering the table, row/col group, row/col, michael@0: * cell. michael@0: * Depending on whether the side is vertical or horizontal and whether michael@0: * adjacent frames are taken into account the ownership of a single border michael@0: * segment is defined. The return value is the dominating border michael@0: * The cellmap stores only top and left borders for each cellmap position. michael@0: * If the cell border is owned by the cell that is left of the border michael@0: * it will be an adjacent owner aka eAjaCellOwner. See celldata.h for the other michael@0: * scenarios with a adjacent owner. michael@0: * @param xxxFrame - the frame for style information, might be zero if michael@0: * it should not be considered michael@0: * @param aSide - side of the frames that should be considered michael@0: * @param aAja - the border comparison takes place from the point of michael@0: * a frame that is adjacent to the cellmap entry, for michael@0: * when a cell owns its lower border it will be the michael@0: * adjacent owner as in the cellmap only top and left michael@0: * borders are stored. michael@0: * @param aTwipsToPixels - conversion factor as borders need to be drawn pixel michael@0: * aligned. michael@0: */ michael@0: static BCCellBorder michael@0: CompareBorders(const nsIFrame* aTableFrame, michael@0: const nsIFrame* aColGroupFrame, michael@0: const nsIFrame* aColFrame, michael@0: const nsIFrame* aRowGroupFrame, michael@0: const nsIFrame* aRowFrame, michael@0: const nsIFrame* aCellFrame, michael@0: bool aTableIsLTR, michael@0: mozilla::css::Side aSide, michael@0: bool aAja) michael@0: { michael@0: BCCellBorder border, tempBorder; michael@0: bool horizontal = (NS_SIDE_TOP == aSide) || (NS_SIDE_BOTTOM == aSide); michael@0: michael@0: // start with the table as dominant if present michael@0: if (aTableFrame) { michael@0: GetColorAndStyle(aTableFrame, aSide, border.style, border.color, aTableIsLTR, border.width); michael@0: border.owner = eTableOwner; michael@0: if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { michael@0: return border; michael@0: } michael@0: } michael@0: // see if the colgroup is dominant michael@0: if (aColGroupFrame) { michael@0: GetColorAndStyle(aColGroupFrame, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); michael@0: tempBorder.owner = (aAja && !horizontal) ? eAjaColGroupOwner : eColGroupOwner; michael@0: // pass here and below false for aSecondIsHorizontal as it is only used for corner calculations. michael@0: border = CompareBorders(!CELL_CORNER, border, tempBorder, false); michael@0: if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { michael@0: return border; michael@0: } michael@0: } michael@0: // see if the col is dominant michael@0: if (aColFrame) { michael@0: GetColorAndStyle(aColFrame, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); michael@0: tempBorder.owner = (aAja && !horizontal) ? eAjaColOwner : eColOwner; michael@0: border = CompareBorders(!CELL_CORNER, border, tempBorder, false); michael@0: if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { michael@0: return border; michael@0: } michael@0: } michael@0: // see if the rowgroup is dominant michael@0: if (aRowGroupFrame) { michael@0: GetColorAndStyle(aRowGroupFrame, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); michael@0: tempBorder.owner = (aAja && horizontal) ? eAjaRowGroupOwner : eRowGroupOwner; michael@0: border = CompareBorders(!CELL_CORNER, border, tempBorder, false); michael@0: if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { michael@0: return border; michael@0: } michael@0: } michael@0: // see if the row is dominant michael@0: if (aRowFrame) { michael@0: GetColorAndStyle(aRowFrame, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); michael@0: tempBorder.owner = (aAja && horizontal) ? eAjaRowOwner : eRowOwner; michael@0: border = CompareBorders(!CELL_CORNER, border, tempBorder, false); michael@0: if (NS_STYLE_BORDER_STYLE_HIDDEN == border.style) { michael@0: return border; michael@0: } michael@0: } michael@0: // see if the cell is dominant michael@0: if (aCellFrame) { michael@0: GetColorAndStyle(aCellFrame, aSide, tempBorder.style, tempBorder.color, aTableIsLTR, tempBorder.width); michael@0: tempBorder.owner = (aAja) ? eAjaCellOwner : eCellOwner; michael@0: border = CompareBorders(!CELL_CORNER, border, tempBorder, false); michael@0: } michael@0: return border; michael@0: } michael@0: michael@0: static bool michael@0: Perpendicular(mozilla::css::Side aSide1, michael@0: mozilla::css::Side aSide2) michael@0: { michael@0: switch (aSide1) { michael@0: case NS_SIDE_TOP: michael@0: return (NS_SIDE_BOTTOM != aSide2); michael@0: case NS_SIDE_RIGHT: michael@0: return (NS_SIDE_LEFT != aSide2); michael@0: case NS_SIDE_BOTTOM: michael@0: return (NS_SIDE_TOP != aSide2); michael@0: default: // NS_SIDE_LEFT michael@0: return (NS_SIDE_RIGHT != aSide2); michael@0: } michael@0: } michael@0: michael@0: // XXX allocate this as number-of-cols+1 instead of number-of-cols+1 * number-of-rows+1 michael@0: struct BCCornerInfo michael@0: { michael@0: BCCornerInfo() { ownerColor = 0; ownerWidth = subWidth = ownerElem = subSide = michael@0: subElem = hasDashDot = numSegs = bevel = 0; ownerSide = NS_SIDE_TOP; michael@0: ownerStyle = 0xFF; subStyle = NS_STYLE_BORDER_STYLE_SOLID; } michael@0: void Set(mozilla::css::Side aSide, michael@0: BCCellBorder border); michael@0: michael@0: void Update(mozilla::css::Side aSide, michael@0: BCCellBorder border); michael@0: michael@0: nscolor ownerColor; // color of borderOwner michael@0: uint16_t ownerWidth; // pixel width of borderOwner michael@0: uint16_t subWidth; // pixel width of the largest border intersecting the border perpendicular michael@0: // to ownerSide michael@0: uint32_t ownerSide:2; // mozilla::css::Side (e.g NS_SIDE_TOP, NS_SIDE_RIGHT, etc) of the border michael@0: // owning the corner relative to the corner michael@0: uint32_t ownerElem:3; // elem type (e.g. eTable, eGroup, etc) owning the corner michael@0: uint32_t ownerStyle:8; // border style of ownerElem michael@0: uint32_t subSide:2; // side of border with subWidth relative to the corner michael@0: uint32_t subElem:3; // elem type (e.g. eTable, eGroup, etc) of sub owner michael@0: uint32_t subStyle:8; // border style of subElem michael@0: uint32_t hasDashDot:1; // does a dashed, dotted segment enter the corner, they cannot be beveled michael@0: uint32_t numSegs:3; // number of segments entering corner michael@0: uint32_t bevel:1; // is the corner beveled (uses the above two fields together with subWidth) michael@0: uint32_t unused:1; michael@0: }; michael@0: michael@0: void michael@0: BCCornerInfo::Set(mozilla::css::Side aSide, michael@0: BCCellBorder aBorder) michael@0: { michael@0: ownerElem = aBorder.owner; michael@0: ownerStyle = aBorder.style; michael@0: ownerWidth = aBorder.width; michael@0: ownerColor = aBorder.color; michael@0: ownerSide = aSide; michael@0: hasDashDot = 0; michael@0: numSegs = 0; michael@0: if (aBorder.width > 0) { michael@0: numSegs++; michael@0: hasDashDot = (NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) || michael@0: (NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style); michael@0: } michael@0: bevel = 0; michael@0: subWidth = 0; michael@0: // the following will get set later michael@0: subSide = ((aSide == NS_SIDE_LEFT) || (aSide == NS_SIDE_RIGHT)) ? NS_SIDE_TOP : NS_SIDE_LEFT; michael@0: subElem = eTableOwner; michael@0: subStyle = NS_STYLE_BORDER_STYLE_SOLID; michael@0: } michael@0: michael@0: void michael@0: BCCornerInfo::Update(mozilla::css::Side aSide, michael@0: BCCellBorder aBorder) michael@0: { michael@0: bool existingWins = false; michael@0: if (0xFF == ownerStyle) { // initial value indiating that it hasn't been set yet michael@0: Set(aSide, aBorder); michael@0: } michael@0: else { michael@0: bool horizontal = (NS_SIDE_LEFT == aSide) || (NS_SIDE_RIGHT == aSide); // relative to the corner michael@0: BCCellBorder oldBorder, tempBorder; michael@0: oldBorder.owner = (BCBorderOwner) ownerElem; michael@0: oldBorder.style = ownerStyle; michael@0: oldBorder.width = ownerWidth; michael@0: oldBorder.color = ownerColor; michael@0: michael@0: mozilla::css::Side oldSide = mozilla::css::Side(ownerSide); michael@0: michael@0: tempBorder = CompareBorders(CELL_CORNER, oldBorder, aBorder, horizontal, &existingWins); michael@0: michael@0: ownerElem = tempBorder.owner; michael@0: ownerStyle = tempBorder.style; michael@0: ownerWidth = tempBorder.width; michael@0: ownerColor = tempBorder.color; michael@0: if (existingWins) { // existing corner is dominant michael@0: if (::Perpendicular(mozilla::css::Side(ownerSide), aSide)) { michael@0: // see if the new sub info replaces the old michael@0: BCCellBorder subBorder; michael@0: subBorder.owner = (BCBorderOwner) subElem; michael@0: subBorder.style = subStyle; michael@0: subBorder.width = subWidth; michael@0: subBorder.color = 0; // we are not interested in subBorder color michael@0: bool firstWins; michael@0: michael@0: tempBorder = CompareBorders(CELL_CORNER, subBorder, aBorder, horizontal, &firstWins); michael@0: michael@0: subElem = tempBorder.owner; michael@0: subStyle = tempBorder.style; michael@0: subWidth = tempBorder.width; michael@0: if (!firstWins) { michael@0: subSide = aSide; michael@0: } michael@0: } michael@0: } michael@0: else { // input args are dominant michael@0: ownerSide = aSide; michael@0: if (::Perpendicular(oldSide, mozilla::css::Side(ownerSide))) { michael@0: subElem = oldBorder.owner; michael@0: subStyle = oldBorder.style; michael@0: subWidth = oldBorder.width; michael@0: subSide = oldSide; michael@0: } michael@0: } michael@0: if (aBorder.width > 0) { michael@0: numSegs++; michael@0: if (!hasDashDot && ((NS_STYLE_BORDER_STYLE_DASHED == aBorder.style) || michael@0: (NS_STYLE_BORDER_STYLE_DOTTED == aBorder.style))) { michael@0: hasDashDot = 1; michael@0: } michael@0: } michael@0: michael@0: // bevel the corner if only two perpendicular non dashed/dotted segments enter the corner michael@0: bevel = (2 == numSegs) && (subWidth > 1) && (0 == hasDashDot); michael@0: } michael@0: } michael@0: michael@0: struct BCCorners michael@0: { michael@0: BCCorners(int32_t aNumCorners, michael@0: int32_t aStartIndex); michael@0: michael@0: ~BCCorners() { delete [] corners; } michael@0: michael@0: BCCornerInfo& operator [](int32_t i) const michael@0: { NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error"); michael@0: return corners[clamped(i, startIndex, endIndex) - startIndex]; } michael@0: michael@0: int32_t startIndex; michael@0: int32_t endIndex; michael@0: BCCornerInfo* corners; michael@0: }; michael@0: michael@0: BCCorners::BCCorners(int32_t aNumCorners, michael@0: int32_t aStartIndex) michael@0: { michael@0: NS_ASSERTION((aNumCorners > 0) && (aStartIndex >= 0), "program error"); michael@0: startIndex = aStartIndex; michael@0: endIndex = aStartIndex + aNumCorners - 1; michael@0: corners = new BCCornerInfo[aNumCorners]; michael@0: } michael@0: michael@0: michael@0: struct BCCellBorders michael@0: { michael@0: BCCellBorders(int32_t aNumBorders, michael@0: int32_t aStartIndex); michael@0: michael@0: ~BCCellBorders() { delete [] borders; } michael@0: michael@0: BCCellBorder& operator [](int32_t i) const michael@0: { NS_ASSERTION((i >= startIndex) && (i <= endIndex), "program error"); michael@0: return borders[clamped(i, startIndex, endIndex) - startIndex]; } michael@0: michael@0: int32_t startIndex; michael@0: int32_t endIndex; michael@0: BCCellBorder* borders; michael@0: }; michael@0: michael@0: BCCellBorders::BCCellBorders(int32_t aNumBorders, michael@0: int32_t aStartIndex) michael@0: { michael@0: NS_ASSERTION((aNumBorders > 0) && (aStartIndex >= 0), "program error"); michael@0: startIndex = aStartIndex; michael@0: endIndex = aStartIndex + aNumBorders - 1; michael@0: borders = new BCCellBorder[aNumBorders]; michael@0: } michael@0: michael@0: // this function sets the new border properties and returns true if the border michael@0: // segment will start a new segment and not be accumulated into the previous michael@0: // segment. michael@0: static bool michael@0: SetBorder(const BCCellBorder& aNewBorder, michael@0: BCCellBorder& aBorder) michael@0: { michael@0: bool changed = (aNewBorder.style != aBorder.style) || michael@0: (aNewBorder.width != aBorder.width) || michael@0: (aNewBorder.color != aBorder.color); michael@0: aBorder.color = aNewBorder.color; michael@0: aBorder.width = aNewBorder.width; michael@0: aBorder.style = aNewBorder.style; michael@0: aBorder.owner = aNewBorder.owner; michael@0: michael@0: return changed; michael@0: } michael@0: michael@0: // this function will set the horizontal border. It will return true if the michael@0: // existing segment will not be continued. Having a vertical owner of a corner michael@0: // should also start a new segment. michael@0: static bool michael@0: SetHorBorder(const BCCellBorder& aNewBorder, michael@0: const BCCornerInfo& aCorner, michael@0: BCCellBorder& aBorder) michael@0: { michael@0: bool startSeg = ::SetBorder(aNewBorder, aBorder); michael@0: if (!startSeg) { michael@0: startSeg = ((NS_SIDE_LEFT != aCorner.ownerSide) && (NS_SIDE_RIGHT != aCorner.ownerSide)); michael@0: } michael@0: return startSeg; michael@0: } michael@0: michael@0: // Make the damage area larger on the top and bottom by at least one row and on the left and right michael@0: // at least one column. This is done so that adjacent elements are part of the border calculations. michael@0: // The extra segments and borders outside the actual damage area will not be updated in the cell map, michael@0: // because they in turn would need info from adjacent segments outside the damage area to be accurate. michael@0: void michael@0: nsTableFrame::ExpandBCDamageArea(nsIntRect& aRect) const michael@0: { michael@0: int32_t numRows = GetRowCount(); michael@0: int32_t numCols = GetColCount(); michael@0: michael@0: int32_t dStartX = aRect.x; michael@0: int32_t dEndX = aRect.XMost() - 1; michael@0: int32_t dStartY = aRect.y; michael@0: int32_t dEndY = aRect.YMost() - 1; michael@0: michael@0: // expand the damage area in each direction michael@0: if (dStartX > 0) { michael@0: dStartX--; michael@0: } michael@0: if (dEndX < (numCols - 1)) { michael@0: dEndX++; michael@0: } michael@0: if (dStartY > 0) { michael@0: dStartY--; michael@0: } michael@0: if (dEndY < (numRows - 1)) { michael@0: dEndY++; michael@0: } michael@0: // Check the damage area so that there are no cells spanning in or out. If there are any then michael@0: // make the damage area as big as the table, similarly to the way the cell map decides whether michael@0: // to rebuild versus expand. This could be optimized to expand to the smallest area that contains michael@0: // no spanners, but it may not be worth the effort in general, and it would need to be done in the michael@0: // cell map as well. michael@0: bool haveSpanner = false; michael@0: if ((dStartX > 0) || (dEndX < (numCols - 1)) || (dStartY > 0) || (dEndY < (numRows - 1))) { michael@0: nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT0(); michael@0: // Get the ordered row groups michael@0: RowGroupArray rowGroups; michael@0: OrderRowGroups(rowGroups); michael@0: michael@0: // Scope outside loop to be used as hint. michael@0: nsCellMap* cellMap = nullptr; michael@0: for (uint32_t rgX = 0; rgX < rowGroups.Length(); rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = rowGroups[rgX]; michael@0: int32_t rgStartY = rgFrame->GetStartRowIndex(); michael@0: int32_t rgEndY = rgStartY + rgFrame->GetRowCount() - 1; michael@0: if (dEndY < rgStartY) michael@0: break; michael@0: cellMap = tableCellMap->GetMapFor(rgFrame, cellMap); michael@0: if (!cellMap) ABORT0(); michael@0: // check for spanners from above and below michael@0: if ((dStartY > 0) && (dStartY >= rgStartY) && (dStartY <= rgEndY)) { michael@0: if (uint32_t(dStartY - rgStartY) >= cellMap->mRows.Length()) michael@0: ABORT0(); michael@0: const nsCellMap::CellDataArray& row = michael@0: cellMap->mRows[dStartY - rgStartY]; michael@0: for (int32_t x = dStartX; x <= dEndX; x++) { michael@0: CellData* cellData = row.SafeElementAt(x); michael@0: if (cellData && (cellData->IsRowSpan())) { michael@0: haveSpanner = true; michael@0: break; michael@0: } michael@0: } michael@0: if (dEndY < rgEndY) { michael@0: if (uint32_t(dEndY + 1 - rgStartY) >= cellMap->mRows.Length()) michael@0: ABORT0(); michael@0: const nsCellMap::CellDataArray& row2 = michael@0: cellMap->mRows[dEndY + 1 - rgStartY]; michael@0: for (int32_t x = dStartX; x <= dEndX; x++) { michael@0: CellData* cellData = row2.SafeElementAt(x); michael@0: if (cellData && (cellData->IsRowSpan())) { michael@0: haveSpanner = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: // check for spanners on the left and right michael@0: int32_t iterStartY = -1; michael@0: int32_t iterEndY = -1; michael@0: if ((dStartY >= rgStartY) && (dStartY <= rgEndY)) { michael@0: // the damage area starts in the row group michael@0: iterStartY = dStartY; michael@0: iterEndY = std::min(dEndY, rgEndY); michael@0: } michael@0: else if ((dEndY >= rgStartY) && (dEndY <= rgEndY)) { michael@0: // the damage area ends in the row group michael@0: iterStartY = rgStartY; michael@0: iterEndY = dEndY; michael@0: } michael@0: else if ((rgStartY >= dStartY) && (rgEndY <= dEndY)) { michael@0: // the damage area contains the row group michael@0: iterStartY = rgStartY; michael@0: iterEndY = rgEndY; michael@0: } michael@0: if ((iterStartY >= 0) && (iterEndY >= 0)) { michael@0: for (int32_t y = iterStartY; y <= iterEndY; y++) { michael@0: if (uint32_t(y - rgStartY) >= cellMap->mRows.Length()) michael@0: ABORT0(); michael@0: const nsCellMap::CellDataArray& row = michael@0: cellMap->mRows[y - rgStartY]; michael@0: CellData* cellData = row.SafeElementAt(dStartX); michael@0: if (cellData && (cellData->IsColSpan())) { michael@0: haveSpanner = true; michael@0: break; michael@0: } michael@0: if (dEndX < (numCols - 1)) { michael@0: cellData = row.SafeElementAt(dEndX + 1); michael@0: if (cellData && (cellData->IsColSpan())) { michael@0: haveSpanner = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (haveSpanner) { michael@0: // make the damage area the whole table michael@0: aRect.x = 0; michael@0: aRect.y = 0; michael@0: aRect.width = numCols; michael@0: aRect.height = numRows; michael@0: } michael@0: else { michael@0: aRect.x = dStartX; michael@0: aRect.y = dStartY; michael@0: aRect.width = 1 + dEndX - dStartX; michael@0: aRect.height = 1 + dEndY - dStartY; michael@0: } michael@0: } michael@0: michael@0: michael@0: #define ADJACENT true michael@0: #define HORIZONTAL true michael@0: michael@0: void michael@0: BCMapCellInfo::SetTableTopLeftContBCBorder() michael@0: { michael@0: BCCellBorder currentBorder; michael@0: //calculate continuous top first row & rowgroup border: special case michael@0: //because it must include the table in the collapse michael@0: if (mTopRow) { michael@0: currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, michael@0: mTopRow, nullptr, mTableIsLTR, michael@0: NS_SIDE_TOP, !ADJACENT); michael@0: mTopRow->SetContinuousBCBorderWidth(NS_SIDE_TOP, currentBorder.width); michael@0: } michael@0: if (mCgAtRight && mColGroup) { michael@0: //calculate continuous top colgroup border once per colgroup michael@0: currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, michael@0: mTopRow, nullptr, mTableIsLTR, michael@0: NS_SIDE_TOP, !ADJACENT); michael@0: mColGroup->SetContinuousBCBorderWidth(NS_SIDE_TOP, currentBorder.width); michael@0: } michael@0: if (0 == mColIndex) { michael@0: currentBorder = CompareBorders(mTableFrame, mColGroup, mLeftCol, nullptr, michael@0: nullptr, nullptr, mTableIsLTR, NS_SIDE_LEFT, michael@0: !ADJACENT); michael@0: mTableFrame->SetContinuousLeftBCBorderWidth(currentBorder.width); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetRowGroupLeftContBCBorder() michael@0: { michael@0: BCCellBorder currentBorder; michael@0: //get row group continuous borders michael@0: if (mRgAtBottom && mRowGroup) { //once per row group, so check for bottom michael@0: currentBorder = CompareBorders(mTableFrame, mColGroup, mLeftCol, mRowGroup, michael@0: nullptr, nullptr, mTableIsLTR, NS_SIDE_LEFT, michael@0: !ADJACENT); michael@0: mRowGroup->SetContinuousBCBorderWidth(mStartSide, currentBorder.width); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetRowGroupRightContBCBorder() michael@0: { michael@0: BCCellBorder currentBorder; michael@0: //get row group continuous borders michael@0: if (mRgAtBottom && mRowGroup) { //once per mRowGroup, so check for bottom michael@0: currentBorder = CompareBorders(mTableFrame, mColGroup, mRightCol, mRowGroup, michael@0: nullptr, nullptr, mTableIsLTR, NS_SIDE_RIGHT, michael@0: ADJACENT); michael@0: mRowGroup->SetContinuousBCBorderWidth(mEndSide, currentBorder.width); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetColumnTopRightContBCBorder() michael@0: { michael@0: BCCellBorder currentBorder; michael@0: //calculate column continuous borders michael@0: //we only need to do this once, so we'll do it only on the first row michael@0: currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, michael@0: mCurrentColFrame, mRowGroup, mTopRow, nullptr, michael@0: mTableIsLTR, NS_SIDE_TOP, !ADJACENT); michael@0: ((nsTableColFrame*) mCurrentColFrame)->SetContinuousBCBorderWidth(NS_SIDE_TOP, michael@0: currentBorder.width); michael@0: if (mNumTableCols == GetCellEndColIndex() + 1) { michael@0: currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, michael@0: mCurrentColFrame, nullptr, nullptr, nullptr, michael@0: mTableIsLTR, NS_SIDE_RIGHT, !ADJACENT); michael@0: } michael@0: else { michael@0: currentBorder = CompareBorders(nullptr, mCurrentColGroupFrame, michael@0: mCurrentColFrame, nullptr,nullptr, nullptr, michael@0: mTableIsLTR, NS_SIDE_RIGHT, !ADJACENT); michael@0: } michael@0: mCurrentColFrame->SetContinuousBCBorderWidth(NS_SIDE_RIGHT, michael@0: currentBorder.width); michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetColumnBottomContBCBorder() michael@0: { michael@0: BCCellBorder currentBorder; michael@0: //get col continuous border michael@0: currentBorder = CompareBorders(mTableFrame, mCurrentColGroupFrame, michael@0: mCurrentColFrame, mRowGroup, mBottomRow, michael@0: nullptr, mTableIsLTR, NS_SIDE_BOTTOM, ADJACENT); michael@0: mCurrentColFrame->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, michael@0: currentBorder.width); michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetColGroupBottomContBCBorder() michael@0: { michael@0: BCCellBorder currentBorder; michael@0: if (mColGroup) { michael@0: currentBorder = CompareBorders(mTableFrame, mColGroup, nullptr, mRowGroup, michael@0: mBottomRow, nullptr, mTableIsLTR, michael@0: NS_SIDE_BOTTOM, ADJACENT); michael@0: mColGroup->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, currentBorder.width); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetRowGroupBottomContBCBorder() michael@0: { michael@0: BCCellBorder currentBorder; michael@0: if (mRowGroup) { michael@0: currentBorder = CompareBorders(mTableFrame, nullptr, nullptr, mRowGroup, michael@0: mBottomRow, nullptr, mTableIsLTR, michael@0: NS_SIDE_BOTTOM, ADJACENT); michael@0: mRowGroup->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, currentBorder.width); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetInnerRowGroupBottomContBCBorder(const nsIFrame* aNextRowGroup, michael@0: nsTableRowFrame* aNextRow) michael@0: { michael@0: BCCellBorder currentBorder, adjacentBorder; michael@0: michael@0: const nsIFrame* rowgroup = (mRgAtBottom) ? mRowGroup : nullptr; michael@0: currentBorder = CompareBorders(nullptr, nullptr, nullptr, rowgroup, mBottomRow, michael@0: nullptr, mTableIsLTR, NS_SIDE_BOTTOM, ADJACENT); michael@0: michael@0: adjacentBorder = CompareBorders(nullptr, nullptr, nullptr, aNextRowGroup, michael@0: aNextRow, nullptr, mTableIsLTR, NS_SIDE_TOP, michael@0: !ADJACENT); michael@0: currentBorder = CompareBorders(false, currentBorder, adjacentBorder, michael@0: HORIZONTAL); michael@0: if (aNextRow) { michael@0: aNextRow->SetContinuousBCBorderWidth(NS_SIDE_TOP, currentBorder.width); michael@0: } michael@0: if (mRgAtBottom && mRowGroup) { michael@0: mRowGroup->SetContinuousBCBorderWidth(NS_SIDE_BOTTOM, currentBorder.width); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetRowLeftContBCBorder() michael@0: { michael@0: //get row continuous borders michael@0: if (mCurrentRowFrame) { michael@0: BCCellBorder currentBorder; michael@0: currentBorder = CompareBorders(mTableFrame, mColGroup, mLeftCol, mRowGroup, michael@0: mCurrentRowFrame, nullptr, mTableIsLTR, michael@0: NS_SIDE_LEFT, !ADJACENT); michael@0: mCurrentRowFrame->SetContinuousBCBorderWidth(mStartSide, michael@0: currentBorder.width); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetRowRightContBCBorder() michael@0: { michael@0: if (mCurrentRowFrame) { michael@0: BCCellBorder currentBorder; michael@0: currentBorder = CompareBorders(mTableFrame, mColGroup, mRightCol, mRowGroup, michael@0: mCurrentRowFrame, nullptr, mTableIsLTR, michael@0: NS_SIDE_RIGHT, ADJACENT); michael@0: mCurrentRowFrame->SetContinuousBCBorderWidth(mEndSide, michael@0: currentBorder.width); michael@0: } michael@0: } michael@0: void michael@0: BCMapCellInfo::SetTableTopBorderWidth(BCPixelSize aWidth) michael@0: { michael@0: mTableBCData->mTopBorderWidth = std::max(mTableBCData->mTopBorderWidth, aWidth); michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetTableLeftBorderWidth(int32_t aRowY, BCPixelSize aWidth) michael@0: { michael@0: // update the left/right first cell border michael@0: if (aRowY == 0) { michael@0: if (mTableIsLTR) { michael@0: mTableBCData->mLeftCellBorderWidth = aWidth; michael@0: } michael@0: else { michael@0: mTableBCData->mRightCellBorderWidth = aWidth; michael@0: } michael@0: } michael@0: mTableBCData->mLeftBorderWidth = std::max(mTableBCData->mLeftBorderWidth, michael@0: aWidth); michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetTableRightBorderWidth(int32_t aRowY, BCPixelSize aWidth) michael@0: { michael@0: // update the left/right first cell border michael@0: if (aRowY == 0) { michael@0: if (mTableIsLTR) { michael@0: mTableBCData->mRightCellBorderWidth = aWidth; michael@0: } michael@0: else { michael@0: mTableBCData->mLeftCellBorderWidth = aWidth; michael@0: } michael@0: } michael@0: mTableBCData->mRightBorderWidth = std::max(mTableBCData->mRightBorderWidth, michael@0: aWidth); michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetRightBorderWidths(BCPixelSize aWidth) michael@0: { michael@0: // update the borders of the cells and cols affected michael@0: if (mCell) { michael@0: mCell->SetBorderWidth(mEndSide, std::max(aWidth, michael@0: mCell->GetBorderWidth(mEndSide))); michael@0: } michael@0: if (mRightCol) { michael@0: BCPixelSize half = BC_BORDER_LEFT_HALF(aWidth); michael@0: mRightCol->SetRightBorderWidth(std::max(nscoord(half), michael@0: mRightCol->GetRightBorderWidth())); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetBottomBorderWidths(BCPixelSize aWidth) michael@0: { michael@0: // update the borders of the affected cells and rows michael@0: if (mCell) { michael@0: mCell->SetBorderWidth(NS_SIDE_BOTTOM, std::max(aWidth, michael@0: mCell->GetBorderWidth(NS_SIDE_BOTTOM))); michael@0: } michael@0: if (mBottomRow) { michael@0: BCPixelSize half = BC_BORDER_TOP_HALF(aWidth); michael@0: mBottomRow->SetBottomBCBorderWidth(std::max(nscoord(half), michael@0: mBottomRow->GetBottomBCBorderWidth())); michael@0: } michael@0: } michael@0: void michael@0: BCMapCellInfo::SetTopBorderWidths(BCPixelSize aWidth) michael@0: { michael@0: if (mCell) { michael@0: mCell->SetBorderWidth(NS_SIDE_TOP, std::max(aWidth, michael@0: mCell->GetBorderWidth(NS_SIDE_TOP))); michael@0: } michael@0: if (mTopRow) { michael@0: BCPixelSize half = BC_BORDER_BOTTOM_HALF(aWidth); michael@0: mTopRow->SetTopBCBorderWidth(std::max(nscoord(half), michael@0: mTopRow->GetTopBCBorderWidth())); michael@0: } michael@0: } michael@0: void michael@0: BCMapCellInfo::SetLeftBorderWidths(BCPixelSize aWidth) michael@0: { michael@0: if (mCell) { michael@0: mCell->SetBorderWidth(mStartSide, std::max(aWidth, michael@0: mCell->GetBorderWidth(mStartSide))); michael@0: } michael@0: if (mLeftCol) { michael@0: BCPixelSize half = BC_BORDER_RIGHT_HALF(aWidth); michael@0: mLeftCol->SetLeftBorderWidth(std::max(nscoord(half), michael@0: mLeftCol->GetLeftBorderWidth())); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetTableBottomBorderWidth(BCPixelSize aWidth) michael@0: { michael@0: mTableBCData->mBottomBorderWidth = std::max(mTableBCData->mBottomBorderWidth, michael@0: aWidth); michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::SetColumn(int32_t aColX) michael@0: { michael@0: mCurrentColFrame = mTableFrame->GetColFrame(aColX); michael@0: if (!mCurrentColFrame) { michael@0: NS_ERROR("null mCurrentColFrame"); michael@0: } michael@0: mCurrentColGroupFrame = static_cast michael@0: (mCurrentColFrame->GetParent()); michael@0: if (!mCurrentColGroupFrame) { michael@0: NS_ERROR("null mCurrentColGroupFrame"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: BCMapCellInfo::IncrementRow(bool aResetToTopRowOfCell) michael@0: { michael@0: mCurrentRowFrame = (aResetToTopRowOfCell) ? mTopRow : michael@0: mCurrentRowFrame->GetNextRow(); michael@0: } michael@0: michael@0: BCCellBorder michael@0: BCMapCellInfo::GetTopEdgeBorder() michael@0: { michael@0: return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, michael@0: mRowGroup, mTopRow, mCell, mTableIsLTR, NS_SIDE_TOP, michael@0: !ADJACENT); michael@0: } michael@0: michael@0: BCCellBorder michael@0: BCMapCellInfo::GetBottomEdgeBorder() michael@0: { michael@0: return CompareBorders(mTableFrame, mCurrentColGroupFrame, mCurrentColFrame, michael@0: mRowGroup, mBottomRow, mCell, mTableIsLTR, michael@0: NS_SIDE_BOTTOM, ADJACENT); michael@0: } michael@0: BCCellBorder michael@0: BCMapCellInfo::GetLeftEdgeBorder() michael@0: { michael@0: return CompareBorders(mTableFrame, mColGroup, mLeftCol, mRowGroup, michael@0: mCurrentRowFrame, mCell, mTableIsLTR, NS_SIDE_LEFT, michael@0: !ADJACENT); michael@0: } michael@0: BCCellBorder michael@0: BCMapCellInfo::GetRightEdgeBorder() michael@0: { michael@0: return CompareBorders(mTableFrame, mColGroup, mRightCol, mRowGroup, michael@0: mCurrentRowFrame, mCell, mTableIsLTR, NS_SIDE_RIGHT, michael@0: ADJACENT); michael@0: } michael@0: BCCellBorder michael@0: BCMapCellInfo::GetRightInternalBorder() michael@0: { michael@0: const nsIFrame* cg = (mCgAtRight) ? mColGroup : nullptr; michael@0: return CompareBorders(nullptr, cg, mRightCol, nullptr, nullptr, mCell, michael@0: mTableIsLTR, NS_SIDE_RIGHT, ADJACENT); michael@0: } michael@0: michael@0: BCCellBorder michael@0: BCMapCellInfo::GetLeftInternalBorder() michael@0: { michael@0: const nsIFrame* cg = (mCgAtLeft) ? mColGroup : nullptr; michael@0: return CompareBorders(nullptr, cg, mLeftCol, nullptr, nullptr, mCell, michael@0: mTableIsLTR, NS_SIDE_LEFT, !ADJACENT); michael@0: } michael@0: michael@0: BCCellBorder michael@0: BCMapCellInfo::GetBottomInternalBorder() michael@0: { michael@0: const nsIFrame* rg = (mRgAtBottom) ? mRowGroup : nullptr; michael@0: return CompareBorders(nullptr, nullptr, nullptr, rg, mBottomRow, mCell, michael@0: mTableIsLTR, NS_SIDE_BOTTOM, ADJACENT); michael@0: } michael@0: michael@0: BCCellBorder michael@0: BCMapCellInfo::GetTopInternalBorder() michael@0: { michael@0: const nsIFrame* rg = (mRgAtTop) ? mRowGroup : nullptr; michael@0: return CompareBorders(nullptr, nullptr, nullptr, rg, mTopRow, mCell, michael@0: mTableIsLTR, NS_SIDE_TOP, !ADJACENT); michael@0: } michael@0: michael@0: /* Here is the order for storing border edges in the cell map as a cell is processed. There are michael@0: n=colspan top and bottom border edges per cell and n=rowspan left and right border edges per cell. michael@0: michael@0: 1) On the top edge of the table, store the top edge. Never store the top edge otherwise, since michael@0: a bottom edge from a cell above will take care of it. michael@0: 2) On the left edge of the table, store the left edge. Never store the left edge othewise, since michael@0: a right edge from a cell to the left will take care of it. michael@0: 3) Store the right edge (or edges if a row span) michael@0: 4) Store the bottom edge (or edges if a col span) michael@0: michael@0: Since corners are computed with only an array of BCCornerInfo indexed by the number-of-cols, corner michael@0: calculations are somewhat complicated. Using an array with number-of-rows * number-of-col entries michael@0: would simplify this, but at an extra in memory cost of nearly 12 bytes per cell map entry. Collapsing michael@0: borders already have about an extra 8 byte per cell map entry overhead (this could be michael@0: reduced to 4 bytes if we are willing to not store border widths in nsTableCellFrame), Here are the michael@0: rules in priority order for storing cornes in the cell map as a cell is processed. top-left means the michael@0: left endpoint of the border edge on the top of the cell. There are n=colspan top and bottom border michael@0: edges per cell and n=rowspan left and right border edges per cell. michael@0: michael@0: 1) On the top edge of the table, store the top-left corner, unless on the left edge of the table. michael@0: Never store the top-right corner, since it will get stored as a right-top corner. michael@0: 2) On the left edge of the table, store the left-top corner. Never store the left-bottom corner, michael@0: since it will get stored as a bottom-left corner. michael@0: 3) Store the right-top corner if (a) it is the top right corner of the table or (b) it is not on michael@0: the top edge of the table. Never store the right-bottom corner since it will get stored as a michael@0: bottom-right corner. michael@0: 4) Store the bottom-right corner, if it is the bottom right corner of the table. Never store it michael@0: otherwise, since it will get stored as either a right-top corner by a cell below or michael@0: a bottom-left corner from a cell to the right. michael@0: 5) Store the bottom-left corner, if (a) on the bottom edge of the table or (b) if the left edge hits michael@0: the top side of a colspan in its interior. Never store the corner otherwise, since it will michael@0: get stored as a right-top corner by a cell from below. michael@0: michael@0: XXX the BC-RTL hack - The correct fix would be a rewrite as described in bug 203686. michael@0: In order to draw borders in rtl conditions somehow correct, the existing structure which relies michael@0: heavily on the assumption that the next cell sibling will be on the right side, has been modified. michael@0: We flip the border during painting and during style lookup. Look for tableIsLTR for places where michael@0: the flipping is done. michael@0: */ michael@0: michael@0: michael@0: michael@0: // Calc the dominant border at every cell edge and corner within the current damage area michael@0: void michael@0: nsTableFrame::CalcBCBorders() michael@0: { michael@0: NS_ASSERTION(IsBorderCollapse(), michael@0: "calling CalcBCBorders on separated-border table"); michael@0: nsTableCellMap* tableCellMap = GetCellMap(); if (!tableCellMap) ABORT0(); michael@0: int32_t numRows = GetRowCount(); michael@0: int32_t numCols = GetColCount(); michael@0: if (!numRows || !numCols) michael@0: return; // nothing to do michael@0: michael@0: // Get the property holding the table damage area and border widths michael@0: BCPropertyData* propData = GetBCProperty(); michael@0: if (!propData) ABORT0(); michael@0: michael@0: michael@0: michael@0: // calculate an expanded damage area michael@0: nsIntRect damageArea(propData->mDamageArea); michael@0: ExpandBCDamageArea(damageArea); michael@0: michael@0: // segments that are on the table border edges need michael@0: // to be initialized only once michael@0: bool tableBorderReset[4]; michael@0: for (uint32_t sideX = NS_SIDE_TOP; sideX <= NS_SIDE_LEFT; sideX++) { michael@0: tableBorderReset[sideX] = false; michael@0: } michael@0: michael@0: // vertical borders indexed in x-direction (cols) michael@0: BCCellBorders lastVerBorders(damageArea.width + 1, damageArea.x); michael@0: if (!lastVerBorders.borders) ABORT0(); michael@0: BCCellBorder lastTopBorder, lastBottomBorder; michael@0: // horizontal borders indexed in x-direction (cols) michael@0: BCCellBorders lastBottomBorders(damageArea.width + 1, damageArea.x); michael@0: if (!lastBottomBorders.borders) ABORT0(); michael@0: bool startSeg; michael@0: bool gotRowBorder = false; michael@0: michael@0: BCMapCellInfo info(this), ajaInfo(this); michael@0: michael@0: BCCellBorder currentBorder, adjacentBorder; michael@0: BCCorners topCorners(damageArea.width + 1, damageArea.x); michael@0: if (!topCorners.corners) ABORT0(); michael@0: BCCorners bottomCorners(damageArea.width + 1, damageArea.x); michael@0: if (!bottomCorners.corners) ABORT0(); michael@0: michael@0: BCMapCellIterator iter(this, damageArea); michael@0: for (iter.First(info); !iter.mAtEnd; iter.Next(info)) { michael@0: // see if lastTopBorder, lastBottomBorder need to be reset michael@0: if (iter.IsNewRow()) { michael@0: gotRowBorder = false; michael@0: lastTopBorder.Reset(info.mRowIndex, info.mRowSpan); michael@0: lastBottomBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); michael@0: } michael@0: else if (info.mColIndex > damageArea.x) { michael@0: lastBottomBorder = lastBottomBorders[info.mColIndex - 1]; michael@0: if (info.mRowIndex > michael@0: (lastBottomBorder.rowIndex - lastBottomBorder.rowSpan)) { michael@0: // the top border's left edge butts against the middle of a rowspan michael@0: lastTopBorder.Reset(info.mRowIndex, info.mRowSpan); michael@0: } michael@0: if (lastBottomBorder.rowIndex > (info.GetCellEndRowIndex() + 1)) { michael@0: // the bottom border's left edge butts against the middle of a rowspan michael@0: lastBottomBorder.Reset(info.GetCellEndRowIndex() + 1, info.mRowSpan); michael@0: } michael@0: } michael@0: michael@0: // find the dominant border considering the cell's top border and the table, michael@0: // row group, row if the border is at the top of the table, otherwise it was michael@0: // processed in a previous row michael@0: if (0 == info.mRowIndex) { michael@0: if (!tableBorderReset[NS_SIDE_TOP]) { michael@0: propData->mTopBorderWidth = 0; michael@0: tableBorderReset[NS_SIDE_TOP] = true; michael@0: } michael@0: for (int32_t colX = info.mColIndex; colX <= info.GetCellEndColIndex(); michael@0: colX++) { michael@0: info.SetColumn(colX); michael@0: currentBorder = info.GetTopEdgeBorder(); michael@0: // update/store the top left & top right corners of the seg michael@0: BCCornerInfo& tlCorner = topCorners[colX]; // top left michael@0: if (0 == colX) { michael@0: // we are on right hand side of the corner michael@0: tlCorner.Set(NS_SIDE_RIGHT, currentBorder); michael@0: } michael@0: else { michael@0: tlCorner.Update(NS_SIDE_RIGHT, currentBorder); michael@0: tableCellMap->SetBCBorderCorner(eTopLeft, *iter.mCellMap, 0, 0, colX, michael@0: mozilla::css::Side(tlCorner.ownerSide), michael@0: tlCorner.subWidth, michael@0: tlCorner.bevel); michael@0: } michael@0: topCorners[colX + 1].Set(NS_SIDE_LEFT, currentBorder); // top right michael@0: // update lastTopBorder and see if a new segment starts michael@0: startSeg = SetHorBorder(currentBorder, tlCorner, lastTopBorder); michael@0: // store the border segment in the cell map michael@0: tableCellMap->SetBCBorderEdge(NS_SIDE_TOP, *iter.mCellMap, 0, 0, colX, michael@0: 1, currentBorder.owner, michael@0: currentBorder.width, startSeg); michael@0: michael@0: info.SetTableTopBorderWidth(currentBorder.width); michael@0: info.SetTopBorderWidths(currentBorder.width); michael@0: info.SetColumnTopRightContBCBorder(); michael@0: } michael@0: info.SetTableTopLeftContBCBorder(); michael@0: } michael@0: else { michael@0: // see if the top border needs to be the start of a segment due to a michael@0: // vertical border owning the corner michael@0: if (info.mColIndex > 0) { michael@0: BCData& data = info.mCellData->mData; michael@0: if (!data.IsTopStart()) { michael@0: mozilla::css::Side cornerSide; michael@0: bool bevel; michael@0: data.GetCorner(cornerSide, bevel); michael@0: if ((NS_SIDE_TOP == cornerSide) || (NS_SIDE_BOTTOM == cornerSide)) { michael@0: data.SetTopStart(true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // find the dominant border considering the cell's left border and the michael@0: // table, col group, col if the border is at the left of the table, michael@0: // otherwise it was processed in a previous col michael@0: if (0 == info.mColIndex) { michael@0: if (!tableBorderReset[NS_SIDE_LEFT]) { michael@0: propData->mLeftBorderWidth = 0; michael@0: tableBorderReset[NS_SIDE_LEFT] = true; michael@0: } michael@0: info.mCurrentRowFrame = nullptr; michael@0: for (int32_t rowY = info.mRowIndex; rowY <= info.GetCellEndRowIndex(); michael@0: rowY++) { michael@0: info.IncrementRow(rowY == info.mRowIndex); michael@0: currentBorder = info.GetLeftEdgeBorder(); michael@0: BCCornerInfo& tlCorner = (0 == rowY) ? topCorners[0] : bottomCorners[0]; michael@0: tlCorner.Update(NS_SIDE_BOTTOM, currentBorder); michael@0: tableCellMap->SetBCBorderCorner(eTopLeft, *iter.mCellMap, michael@0: iter.mRowGroupStart, rowY, 0, michael@0: mozilla::css::Side(tlCorner.ownerSide), michael@0: tlCorner.subWidth, michael@0: tlCorner.bevel); michael@0: bottomCorners[0].Set(NS_SIDE_TOP, currentBorder); // bottom left michael@0: michael@0: // update lastVerBordersBorder and see if a new segment starts michael@0: startSeg = SetBorder(currentBorder, lastVerBorders[0]); michael@0: // store the border segment in the cell map michael@0: tableCellMap->SetBCBorderEdge(NS_SIDE_LEFT, *iter.mCellMap, michael@0: iter.mRowGroupStart, rowY, info.mColIndex, michael@0: 1, currentBorder.owner, michael@0: currentBorder.width, startSeg); michael@0: info.SetTableLeftBorderWidth(rowY , currentBorder.width); michael@0: info.SetLeftBorderWidths(currentBorder.width); michael@0: info.SetRowLeftContBCBorder(); michael@0: } michael@0: info.SetRowGroupLeftContBCBorder(); michael@0: } michael@0: michael@0: // find the dominant border considering the cell's right border, adjacent michael@0: // cells and the table, row group, row michael@0: if (info.mNumTableCols == info.GetCellEndColIndex() + 1) { michael@0: // touches right edge of table michael@0: if (!tableBorderReset[NS_SIDE_RIGHT]) { michael@0: propData->mRightBorderWidth = 0; michael@0: tableBorderReset[NS_SIDE_RIGHT] = true; michael@0: } michael@0: info.mCurrentRowFrame = nullptr; michael@0: for (int32_t rowY = info.mRowIndex; rowY <= info.GetCellEndRowIndex(); michael@0: rowY++) { michael@0: info.IncrementRow(rowY == info.mRowIndex); michael@0: currentBorder = info.GetRightEdgeBorder(); michael@0: // update/store the top right & bottom right corners michael@0: BCCornerInfo& trCorner = (0 == rowY) ? michael@0: topCorners[info.GetCellEndColIndex() + 1] : michael@0: bottomCorners[info.GetCellEndColIndex() + 1]; michael@0: trCorner.Update(NS_SIDE_BOTTOM, currentBorder); // top right michael@0: tableCellMap->SetBCBorderCorner(eTopRight, *iter.mCellMap, michael@0: iter.mRowGroupStart, rowY, michael@0: info.GetCellEndColIndex(), michael@0: mozilla::css::Side(trCorner.ownerSide), michael@0: trCorner.subWidth, michael@0: trCorner.bevel); michael@0: BCCornerInfo& brCorner = bottomCorners[info.GetCellEndColIndex() + 1]; michael@0: brCorner.Set(NS_SIDE_TOP, currentBorder); // bottom right michael@0: tableCellMap->SetBCBorderCorner(eBottomRight, *iter.mCellMap, michael@0: iter.mRowGroupStart, rowY, michael@0: info.GetCellEndColIndex(), michael@0: mozilla::css::Side(brCorner.ownerSide), michael@0: brCorner.subWidth, michael@0: brCorner.bevel); michael@0: // update lastVerBorders and see if a new segment starts michael@0: startSeg = SetBorder(currentBorder, michael@0: lastVerBorders[info.GetCellEndColIndex() + 1]); michael@0: // store the border segment in the cell map and update cellBorders michael@0: tableCellMap->SetBCBorderEdge(NS_SIDE_RIGHT, *iter.mCellMap, michael@0: iter.mRowGroupStart, rowY, michael@0: info.GetCellEndColIndex(), 1, michael@0: currentBorder.owner, currentBorder.width, michael@0: startSeg); michael@0: info.SetTableRightBorderWidth(rowY, currentBorder.width); michael@0: info.SetRightBorderWidths(currentBorder.width); michael@0: info.SetRowRightContBCBorder(); michael@0: } michael@0: info.SetRowGroupRightContBCBorder(); michael@0: } michael@0: else { michael@0: int32_t segLength = 0; michael@0: BCMapCellInfo priorAjaInfo(this); michael@0: for (int32_t rowY = info.mRowIndex; rowY <= info.GetCellEndRowIndex(); michael@0: rowY += segLength) { michael@0: iter.PeekRight(info, rowY, ajaInfo); michael@0: currentBorder = info.GetRightInternalBorder(); michael@0: adjacentBorder = ajaInfo.GetLeftInternalBorder(); michael@0: currentBorder = CompareBorders(!CELL_CORNER, currentBorder, michael@0: adjacentBorder, !HORIZONTAL); michael@0: michael@0: segLength = std::max(1, ajaInfo.mRowIndex + ajaInfo.mRowSpan - rowY); michael@0: segLength = std::min(segLength, info.mRowIndex + info.mRowSpan - rowY); michael@0: michael@0: // update lastVerBorders and see if a new segment starts michael@0: startSeg = SetBorder(currentBorder, michael@0: lastVerBorders[info.GetCellEndColIndex() + 1]); michael@0: // store the border segment in the cell map and update cellBorders michael@0: if (info.GetCellEndColIndex() < damageArea.XMost() && michael@0: rowY >= damageArea.y && rowY < damageArea.YMost()) { michael@0: tableCellMap->SetBCBorderEdge(NS_SIDE_RIGHT, *iter.mCellMap, michael@0: iter.mRowGroupStart, rowY, michael@0: info.GetCellEndColIndex(), segLength, michael@0: currentBorder.owner, michael@0: currentBorder.width, startSeg); michael@0: info.SetRightBorderWidths(currentBorder.width); michael@0: ajaInfo.SetLeftBorderWidths(currentBorder.width); michael@0: } michael@0: // update the top right corner michael@0: bool hitsSpanOnRight = (rowY > ajaInfo.mRowIndex) && michael@0: (rowY < ajaInfo.mRowIndex + ajaInfo.mRowSpan); michael@0: BCCornerInfo* trCorner = ((0 == rowY) || hitsSpanOnRight) ? michael@0: &topCorners[info.GetCellEndColIndex() + 1] : michael@0: &bottomCorners[info.GetCellEndColIndex() + 1]; michael@0: trCorner->Update(NS_SIDE_BOTTOM, currentBorder); michael@0: // if this is not the first time through, michael@0: // consider the segment to the right michael@0: if (rowY != info.mRowIndex) { michael@0: currentBorder = priorAjaInfo.GetBottomInternalBorder(); michael@0: adjacentBorder = ajaInfo.GetTopInternalBorder(); michael@0: currentBorder = CompareBorders(!CELL_CORNER, currentBorder, michael@0: adjacentBorder, HORIZONTAL); michael@0: trCorner->Update(NS_SIDE_RIGHT, currentBorder); michael@0: } michael@0: // store the top right corner in the cell map michael@0: if (info.GetCellEndColIndex() < damageArea.XMost() && michael@0: rowY >= damageArea.y) { michael@0: if (0 != rowY) { michael@0: tableCellMap->SetBCBorderCorner(eTopRight, *iter.mCellMap, michael@0: iter.mRowGroupStart, rowY, michael@0: info.GetCellEndColIndex(), michael@0: mozilla::css::Side(trCorner->ownerSide), michael@0: trCorner->subWidth, michael@0: trCorner->bevel); michael@0: } michael@0: // store any corners this cell spans together with the aja cell michael@0: for (int32_t rX = rowY + 1; rX < rowY + segLength; rX++) { michael@0: tableCellMap->SetBCBorderCorner(eBottomRight, *iter.mCellMap, michael@0: iter.mRowGroupStart, rX, michael@0: info.GetCellEndColIndex(), michael@0: mozilla::css::Side(trCorner->ownerSide), michael@0: trCorner->subWidth, false); michael@0: } michael@0: } michael@0: // update bottom right corner, topCorners, bottomCorners michael@0: hitsSpanOnRight = (rowY + segLength < michael@0: ajaInfo.mRowIndex + ajaInfo.mRowSpan); michael@0: BCCornerInfo& brCorner = (hitsSpanOnRight) ? michael@0: topCorners[info.GetCellEndColIndex() + 1] : michael@0: bottomCorners[info.GetCellEndColIndex() + 1]; michael@0: brCorner.Set(NS_SIDE_TOP, currentBorder); michael@0: priorAjaInfo = ajaInfo; michael@0: } michael@0: } michael@0: for (int32_t colX = info.mColIndex + 1; colX <= info.GetCellEndColIndex(); michael@0: colX++) { michael@0: lastVerBorders[colX].Reset(0,1); michael@0: } michael@0: michael@0: // find the dominant border considering the cell's bottom border, adjacent michael@0: // cells and the table, row group, row michael@0: if (info.mNumTableRows == info.GetCellEndRowIndex() + 1) { michael@0: // touches bottom edge of table michael@0: if (!tableBorderReset[NS_SIDE_BOTTOM]) { michael@0: propData->mBottomBorderWidth = 0; michael@0: tableBorderReset[NS_SIDE_BOTTOM] = true; michael@0: } michael@0: for (int32_t colX = info.mColIndex; colX <= info.GetCellEndColIndex(); michael@0: colX++) { michael@0: info.SetColumn(colX); michael@0: currentBorder = info.GetBottomEdgeBorder(); michael@0: // update/store the bottom left & bottom right corners michael@0: BCCornerInfo& blCorner = bottomCorners[colX]; // bottom left michael@0: blCorner.Update(NS_SIDE_RIGHT, currentBorder); michael@0: tableCellMap->SetBCBorderCorner(eBottomLeft, *iter.mCellMap, michael@0: iter.mRowGroupStart, michael@0: info.GetCellEndRowIndex(), michael@0: colX, michael@0: mozilla::css::Side(blCorner.ownerSide), michael@0: blCorner.subWidth, blCorner.bevel); michael@0: BCCornerInfo& brCorner = bottomCorners[colX + 1]; // bottom right michael@0: brCorner.Update(NS_SIDE_LEFT, currentBorder); michael@0: if (info.mNumTableCols == colX + 1) { // lower right corner of the table michael@0: tableCellMap->SetBCBorderCorner(eBottomRight, *iter.mCellMap, michael@0: iter.mRowGroupStart, michael@0: info.GetCellEndRowIndex(),colX, michael@0: mozilla::css::Side(brCorner.ownerSide), michael@0: brCorner.subWidth, michael@0: brCorner.bevel, true); michael@0: } michael@0: // update lastBottomBorder and see if a new segment starts michael@0: startSeg = SetHorBorder(currentBorder, blCorner, lastBottomBorder); michael@0: if (!startSeg) { michael@0: // make sure that we did not compare apples to oranges i.e. the michael@0: // current border should be a continuation of the lastBottomBorder, michael@0: // as it is a bottom border michael@0: // add 1 to the info.GetCellEndRowIndex() michael@0: startSeg = (lastBottomBorder.rowIndex != michael@0: (info.GetCellEndRowIndex() + 1)); michael@0: } michael@0: // store the border segment in the cell map and update cellBorders michael@0: tableCellMap->SetBCBorderEdge(NS_SIDE_BOTTOM, *iter.mCellMap, michael@0: iter.mRowGroupStart, michael@0: info.GetCellEndRowIndex(), michael@0: colX, 1, currentBorder.owner, michael@0: currentBorder.width, startSeg); michael@0: // update lastBottomBorders michael@0: lastBottomBorder.rowIndex = info.GetCellEndRowIndex() + 1; michael@0: lastBottomBorder.rowSpan = info.mRowSpan; michael@0: lastBottomBorders[colX] = lastBottomBorder; michael@0: michael@0: info.SetBottomBorderWidths(currentBorder.width); michael@0: info.SetTableBottomBorderWidth(currentBorder.width); michael@0: info.SetColumnBottomContBCBorder(); michael@0: } michael@0: info.SetRowGroupBottomContBCBorder(); michael@0: info.SetColGroupBottomContBCBorder(); michael@0: } michael@0: else { michael@0: int32_t segLength = 0; michael@0: for (int32_t colX = info.mColIndex; colX <= info.GetCellEndColIndex(); michael@0: colX += segLength) { michael@0: iter.PeekBottom(info, colX, ajaInfo); michael@0: currentBorder = info.GetBottomInternalBorder(); michael@0: adjacentBorder = ajaInfo.GetTopInternalBorder(); michael@0: currentBorder = CompareBorders(!CELL_CORNER, currentBorder, michael@0: adjacentBorder, HORIZONTAL); michael@0: segLength = std::max(1, ajaInfo.mColIndex + ajaInfo.mColSpan - colX); michael@0: segLength = std::min(segLength, info.mColIndex + info.mColSpan - colX); michael@0: michael@0: // update, store the bottom left corner michael@0: BCCornerInfo& blCorner = bottomCorners[colX]; // bottom left michael@0: bool hitsSpanBelow = (colX > ajaInfo.mColIndex) && michael@0: (colX < ajaInfo.mColIndex + ajaInfo.mColSpan); michael@0: bool update = true; michael@0: if ((colX == info.mColIndex) && (colX > damageArea.x)) { michael@0: int32_t prevRowIndex = lastBottomBorders[colX - 1].rowIndex; michael@0: if (prevRowIndex > info.GetCellEndRowIndex() + 1) { michael@0: // hits a rowspan on the right michael@0: update = false; michael@0: // the corner was taken care of during the cell on the left michael@0: } michael@0: else if (prevRowIndex < info.GetCellEndRowIndex() + 1) { michael@0: // spans below the cell to the left michael@0: topCorners[colX] = blCorner; michael@0: blCorner.Set(NS_SIDE_RIGHT, currentBorder); michael@0: update = false; michael@0: } michael@0: } michael@0: if (update) { michael@0: blCorner.Update(NS_SIDE_RIGHT, currentBorder); michael@0: } michael@0: if (info.GetCellEndRowIndex() < damageArea.YMost() && michael@0: (colX >= damageArea.x)) { michael@0: if (hitsSpanBelow) { michael@0: tableCellMap->SetBCBorderCorner(eBottomLeft, *iter.mCellMap, michael@0: iter.mRowGroupStart, michael@0: info.GetCellEndRowIndex(), colX, michael@0: mozilla::css::Side(blCorner.ownerSide), michael@0: blCorner.subWidth, blCorner.bevel); michael@0: } michael@0: // store any corners this cell spans together with the aja cell michael@0: for (int32_t cX = colX + 1; cX < colX + segLength; cX++) { michael@0: BCCornerInfo& corner = bottomCorners[cX]; michael@0: corner.Set(NS_SIDE_RIGHT, currentBorder); michael@0: tableCellMap->SetBCBorderCorner(eBottomLeft, *iter.mCellMap, michael@0: iter.mRowGroupStart, michael@0: info.GetCellEndRowIndex(), cX, michael@0: mozilla::css::Side(corner.ownerSide), michael@0: corner.subWidth, michael@0: false); michael@0: } michael@0: } michael@0: // update lastBottomBorders and see if a new segment starts michael@0: startSeg = SetHorBorder(currentBorder, blCorner, lastBottomBorder); michael@0: if (!startSeg) { michael@0: // make sure that we did not compare apples to oranges i.e. the michael@0: // current border should be a continuation of the lastBottomBorder, michael@0: // as it is a bottom border michael@0: // add 1 to the info.GetCellEndRowIndex() michael@0: startSeg = (lastBottomBorder.rowIndex != michael@0: info.GetCellEndRowIndex() + 1); michael@0: } michael@0: lastBottomBorder.rowIndex = info.GetCellEndRowIndex() + 1; michael@0: lastBottomBorder.rowSpan = info.mRowSpan; michael@0: for (int32_t cX = colX; cX < colX + segLength; cX++) { michael@0: lastBottomBorders[cX] = lastBottomBorder; michael@0: } michael@0: michael@0: // store the border segment the cell map and update cellBorders michael@0: if (info.GetCellEndRowIndex() < damageArea.YMost() && michael@0: (colX >= damageArea.x) && michael@0: (colX < damageArea.XMost())) { michael@0: tableCellMap->SetBCBorderEdge(NS_SIDE_BOTTOM, *iter.mCellMap, michael@0: iter.mRowGroupStart, michael@0: info.GetCellEndRowIndex(), michael@0: colX, segLength, currentBorder.owner, michael@0: currentBorder.width, startSeg); michael@0: info.SetBottomBorderWidths(currentBorder.width); michael@0: ajaInfo.SetTopBorderWidths(currentBorder.width); michael@0: } michael@0: // update bottom right corner michael@0: BCCornerInfo& brCorner = bottomCorners[colX + segLength]; michael@0: brCorner.Update(NS_SIDE_LEFT, currentBorder); michael@0: } michael@0: if (!gotRowBorder && 1 == info.mRowSpan && michael@0: (ajaInfo.mTopRow || info.mRgAtBottom)) { michael@0: //get continuous row/row group border michael@0: //we need to check the row group's bottom border if this is michael@0: //the last row in the row group, but only a cell with rowspan=1 michael@0: //will know whether *this* row is at the bottom michael@0: const nsIFrame* nextRowGroup = (ajaInfo.mRgAtTop) ? ajaInfo.mRowGroup : michael@0: nullptr; michael@0: info.SetInnerRowGroupBottomContBCBorder(nextRowGroup, ajaInfo.mTopRow); michael@0: gotRowBorder = true; michael@0: } michael@0: } michael@0: michael@0: // see if the cell to the right had a rowspan and its lower left border michael@0: // needs be joined with this one's bottom michael@0: // if there is a cell to the right and the cell to right was a rowspan michael@0: if ((info.mNumTableCols != info.GetCellEndColIndex() + 1) && michael@0: (lastBottomBorders[info.GetCellEndColIndex() + 1].rowSpan > 1)) { michael@0: BCCornerInfo& corner = bottomCorners[info.GetCellEndColIndex() + 1]; michael@0: if ((NS_SIDE_TOP != corner.ownerSide) && michael@0: (NS_SIDE_BOTTOM != corner.ownerSide)) { michael@0: // not a vertical owner michael@0: BCCellBorder& thisBorder = lastBottomBorder; michael@0: BCCellBorder& nextBorder = lastBottomBorders[info.mColIndex + 1]; michael@0: if ((thisBorder.color == nextBorder.color) && michael@0: (thisBorder.width == nextBorder.width) && michael@0: (thisBorder.style == nextBorder.style)) { michael@0: // set the flag on the next border indicating it is not the start of a michael@0: // new segment michael@0: if (iter.mCellMap) { michael@0: tableCellMap->ResetTopStart(NS_SIDE_BOTTOM, *iter.mCellMap, michael@0: info.GetCellEndRowIndex(), michael@0: info.GetCellEndColIndex() + 1); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } // for (iter.First(info); info.mCell; iter.Next(info)) { michael@0: // reset the bc flag and damage area michael@0: SetNeedToCalcBCBorders(false); michael@0: propData->mDamageArea = nsIntRect(0,0,0,0); michael@0: #ifdef DEBUG_TABLE_CELLMAP michael@0: mCellMap->Dump(); michael@0: #endif michael@0: } michael@0: michael@0: class BCPaintBorderIterator; michael@0: michael@0: struct BCVerticalSeg michael@0: { michael@0: BCVerticalSeg(); michael@0: michael@0: void Start(BCPaintBorderIterator& aIter, michael@0: BCBorderOwner aBorderOwner, michael@0: BCPixelSize aVerSegWidth, michael@0: BCPixelSize aHorSegHeight); michael@0: michael@0: void Initialize(BCPaintBorderIterator& aIter); michael@0: void GetBottomCorner(BCPaintBorderIterator& aIter, michael@0: BCPixelSize aHorSegHeight); michael@0: michael@0: michael@0: void Paint(BCPaintBorderIterator& aIter, michael@0: nsRenderingContext& aRenderingContext, michael@0: BCPixelSize aHorSegHeight); michael@0: void AdvanceOffsetY(); michael@0: void IncludeCurrentBorder(BCPaintBorderIterator& aIter); michael@0: michael@0: michael@0: union { michael@0: nsTableColFrame* mCol; michael@0: int32_t mColWidth; michael@0: }; michael@0: nscoord mOffsetX; // x-offset with respect to the table edge michael@0: nscoord mOffsetY; // y-offset with respect to the table edge michael@0: nscoord mLength; // vertical length including corners michael@0: BCPixelSize mWidth; // width in pixels michael@0: michael@0: nsTableCellFrame* mAjaCell; // previous sibling to the first cell michael@0: // where the segment starts, it can be michael@0: // the owner of a segment michael@0: nsTableCellFrame* mFirstCell; // cell at the start of the segment michael@0: nsTableRowGroupFrame* mFirstRowGroup; // row group at the start of the segment michael@0: nsTableRowFrame* mFirstRow; // row at the start of the segment michael@0: nsTableCellFrame* mLastCell; // cell at the current end of the michael@0: // segment michael@0: michael@0: michael@0: uint8_t mOwner; // owner of the border, defines the michael@0: // style michael@0: mozilla::css::Side mTopBevelSide; // direction to bevel at the top michael@0: nscoord mTopBevelOffset; // how much to bevel at the top michael@0: BCPixelSize mBottomHorSegHeight; // height of the crossing michael@0: //horizontal border michael@0: nscoord mBottomOffset; // how much longer is the segment due michael@0: // to the horizontal border, by this michael@0: // amount the next segment needs to be michael@0: // shifted. michael@0: bool mIsBottomBevel; // should we bevel at the bottom michael@0: }; michael@0: michael@0: struct BCHorizontalSeg michael@0: { michael@0: BCHorizontalSeg(); michael@0: michael@0: void Start(BCPaintBorderIterator& aIter, michael@0: BCBorderOwner aBorderOwner, michael@0: BCPixelSize aBottomVerSegWidth, michael@0: BCPixelSize aHorSegHeight); michael@0: void GetRightCorner(BCPaintBorderIterator& aIter, michael@0: BCPixelSize aLeftSegWidth); michael@0: void AdvanceOffsetX(int32_t aIncrement); michael@0: void IncludeCurrentBorder(BCPaintBorderIterator& aIter); michael@0: void Paint(BCPaintBorderIterator& aIter, michael@0: nsRenderingContext& aRenderingContext); michael@0: michael@0: nscoord mOffsetX; // x-offset with respect to the table edge michael@0: nscoord mOffsetY; // y-offset with respect to the table edge michael@0: nscoord mLength; // horizontal length including corners michael@0: BCPixelSize mWidth; // border width in pixels michael@0: nscoord mLeftBevelOffset; // how much to bevel at the left michael@0: mozilla::css::Side mLeftBevelSide; // direction to bevel at the left michael@0: bool mIsRightBevel; // should we bevel at the right end michael@0: nscoord mRightBevelOffset; // how much to bevel at the right michael@0: mozilla::css::Side mRightBevelSide; // direction to bevel at the right michael@0: nscoord mEndOffset; // how much longer is the segment due michael@0: // to the vertical border, by this michael@0: // amount the next segment needs to be michael@0: // shifted. michael@0: uint8_t mOwner; // owner of the border, defines the michael@0: // style michael@0: nsTableCellFrame* mFirstCell; // cell at the start of the segment michael@0: nsTableCellFrame* mAjaCell; // neighboring cell to the first cell michael@0: // where the segment starts, it can be michael@0: // the owner of a segment michael@0: }; michael@0: michael@0: // Iterates over borders (left border, corner, top border) in the cell map within a damage area michael@0: // from left to right, top to bottom. All members are in terms of the 1st in flow frames, except michael@0: // where suffixed by InFlow. michael@0: class BCPaintBorderIterator michael@0: { michael@0: public: michael@0: michael@0: michael@0: BCPaintBorderIterator(nsTableFrame* aTable); michael@0: ~BCPaintBorderIterator() { if (mVerInfo) { michael@0: delete [] mVerInfo; michael@0: }} michael@0: void Reset(); michael@0: michael@0: /** michael@0: * Determine the damage area in terms of rows and columns and finalize michael@0: * mInitialOffsetX and mInitialOffsetY. michael@0: * @param aDirtyRect - dirty rect in table coordinates michael@0: * @return - true if we need to paint something given dirty rect michael@0: */ michael@0: bool SetDamageArea(const nsRect& aDamageRect); michael@0: void First(); michael@0: void Next(); michael@0: void AccumulateOrPaintHorizontalSegment(nsRenderingContext& aRenderingContext); michael@0: void AccumulateOrPaintVerticalSegment(nsRenderingContext& aRenderingContext); michael@0: void ResetVerInfo(); michael@0: void StoreColumnWidth(int32_t aIndex); michael@0: bool VerticalSegmentOwnsCorner(); michael@0: michael@0: nsTableFrame* mTable; michael@0: nsTableFrame* mTableFirstInFlow; michael@0: nsTableCellMap* mTableCellMap; michael@0: nsCellMap* mCellMap; michael@0: bool mTableIsLTR; michael@0: int32_t mColInc; // +1 for ltr -1 for rtl michael@0: const nsStyleBackground* mTableBgColor; michael@0: nsTableFrame::RowGroupArray mRowGroups; michael@0: michael@0: nsTableRowGroupFrame* mPrevRg; michael@0: nsTableRowGroupFrame* mRg; michael@0: bool mIsRepeatedHeader; michael@0: bool mIsRepeatedFooter; michael@0: nsTableRowGroupFrame* mStartRg; // first row group in the damagearea michael@0: int32_t mRgIndex; // current row group index in the michael@0: // mRowgroups array michael@0: int32_t mFifRgFirstRowIndex; // start row index of the first in michael@0: // flow of the row group michael@0: int32_t mRgFirstRowIndex; // row index of the first row in the michael@0: // row group michael@0: int32_t mRgLastRowIndex; // row index of the last row in the row michael@0: // group michael@0: int32_t mNumTableRows; // number of rows in the table and all michael@0: // continuations michael@0: int32_t mNumTableCols; // number of columns in the table michael@0: int32_t mColIndex; // with respect to the table michael@0: int32_t mRowIndex; // with respect to the table michael@0: int32_t mRepeatedHeaderRowIndex; // row index in a repeated michael@0: //header, it's equivalent to michael@0: // mRowIndex when we're in a repeated michael@0: // header, and set to the last row michael@0: // index of a repeated header when michael@0: // we're not michael@0: bool mIsNewRow; michael@0: bool mAtEnd; // the iterator cycled over all michael@0: // borders michael@0: nsTableRowFrame* mPrevRow; michael@0: nsTableRowFrame* mRow; michael@0: nsTableRowFrame* mStartRow; //first row in a inside the damagearea michael@0: michael@0: michael@0: // cell properties michael@0: nsTableCellFrame* mPrevCell; michael@0: nsTableCellFrame* mCell; michael@0: BCCellData* mPrevCellData; michael@0: BCCellData* mCellData; michael@0: BCData* mBCData; michael@0: michael@0: bool IsTableTopMost() {return (mRowIndex == 0) && !mTable->GetPrevInFlow();} michael@0: bool IsTableRightMost() {return (mColIndex >= mNumTableCols);} michael@0: bool IsTableBottomMost() {return (mRowIndex >= mNumTableRows) && !mTable->GetNextInFlow();} michael@0: bool IsTableLeftMost() {return (mColIndex == 0);} michael@0: bool IsDamageAreaTopMost() {return (mRowIndex == mDamageArea.y);} michael@0: bool IsDamageAreaRightMost() {return (mColIndex >= mDamageArea.XMost());} michael@0: bool IsDamageAreaBottomMost() {return (mRowIndex >= mDamageArea.YMost());} michael@0: bool IsDamageAreaLeftMost() {return (mColIndex == mDamageArea.x);} michael@0: int32_t GetRelativeColIndex() {return (mColIndex - mDamageArea.x);} michael@0: michael@0: nsIntRect mDamageArea; // damageArea in cellmap coordinates michael@0: bool IsAfterRepeatedHeader() { return !mIsRepeatedHeader && (mRowIndex == (mRepeatedHeaderRowIndex + 1));} michael@0: bool StartRepeatedFooter() {return mIsRepeatedFooter && (mRowIndex == mRgFirstRowIndex) && (mRowIndex != mDamageArea.y);} michael@0: nscoord mInitialOffsetX; // offsetX of the first border with michael@0: // respect to the table michael@0: nscoord mInitialOffsetY; // offsetY of the first border with michael@0: // respect to the table michael@0: nscoord mNextOffsetY; // offsetY of the next segment michael@0: BCVerticalSeg* mVerInfo; // this array is used differently when michael@0: // horizontal and vertical borders are drawn michael@0: // When horizontal border are drawn we cache michael@0: // the column widths and the width of the michael@0: // vertical borders that arrive from top michael@0: // When we draw vertical borders we store michael@0: // lengths and width for vertical borders michael@0: // before they are drawn while we move over michael@0: // the columns in the damage area michael@0: // It has one more elements than columns are michael@0: //in the table. michael@0: BCHorizontalSeg mHorSeg; // the horizontal segment while we michael@0: // move over the colums michael@0: BCPixelSize mPrevHorSegHeight; // the height of the previous michael@0: // horizontal border michael@0: michael@0: private: michael@0: michael@0: bool SetNewRow(nsTableRowFrame* aRow = nullptr); michael@0: bool SetNewRowGroup(); michael@0: void SetNewData(int32_t aRowIndex, int32_t aColIndex); michael@0: michael@0: }; michael@0: michael@0: michael@0: michael@0: BCPaintBorderIterator::BCPaintBorderIterator(nsTableFrame* aTable) michael@0: { michael@0: mTable = aTable; michael@0: mVerInfo = nullptr; michael@0: nsMargin childAreaOffset = mTable->GetChildAreaOffset(nullptr); michael@0: mTableFirstInFlow = static_cast(mTable->FirstInFlow()); michael@0: mTableCellMap = mTable->GetCellMap(); michael@0: // y position of first row in damage area michael@0: mInitialOffsetY = mTable->GetPrevInFlow() ? 0 : childAreaOffset.top; michael@0: mNumTableRows = mTable->GetRowCount(); michael@0: mNumTableCols = mTable->GetColCount(); michael@0: michael@0: // Get the ordered row groups michael@0: mTable->OrderRowGroups(mRowGroups); michael@0: // initialize to a non existing index michael@0: mRepeatedHeaderRowIndex = -99; michael@0: michael@0: mTableIsLTR = mTable->StyleVisibility()->mDirection == michael@0: NS_STYLE_DIRECTION_LTR; michael@0: mColInc = (mTableIsLTR) ? 1 : -1; michael@0: michael@0: nsIFrame* bgFrame = michael@0: nsCSSRendering::FindNonTransparentBackgroundFrame(aTable); michael@0: mTableBgColor = bgFrame->StyleBackground(); michael@0: } michael@0: michael@0: bool michael@0: BCPaintBorderIterator::SetDamageArea(const nsRect& aDirtyRect) michael@0: { michael@0: michael@0: uint32_t startRowIndex, endRowIndex, startColIndex, endColIndex; michael@0: startRowIndex = endRowIndex = startColIndex = endColIndex = 0; michael@0: bool done = false; michael@0: bool haveIntersect = false; michael@0: // find startRowIndex, endRowIndex michael@0: nscoord rowY = mInitialOffsetY; michael@0: for (uint32_t rgX = 0; rgX < mRowGroups.Length() && !done; rgX++) { michael@0: nsTableRowGroupFrame* rgFrame = mRowGroups[rgX]; michael@0: for (nsTableRowFrame* rowFrame = rgFrame->GetFirstRow(); rowFrame; michael@0: rowFrame = rowFrame->GetNextRow()) { michael@0: // conservatively estimate the half border widths outside the row michael@0: nscoord topBorderHalf = (mTable->GetPrevInFlow()) ? 0 : michael@0: nsPresContext::CSSPixelsToAppUnits(rowFrame->GetTopBCBorderWidth() + 1); michael@0: nscoord bottomBorderHalf = (mTable->GetNextInFlow()) ? 0 : michael@0: nsPresContext::CSSPixelsToAppUnits(rowFrame->GetBottomBCBorderWidth() + 1); michael@0: // get the row rect relative to the table rather than the row group michael@0: nsSize rowSize = rowFrame->GetSize(); michael@0: if (haveIntersect) { michael@0: if (aDirtyRect.YMost() >= (rowY - topBorderHalf)) { michael@0: nsTableRowFrame* fifRow = michael@0: static_cast(rowFrame->FirstInFlow()); michael@0: endRowIndex = fifRow->GetRowIndex(); michael@0: } michael@0: else done = true; michael@0: } michael@0: else { michael@0: if ((rowY + rowSize.height + bottomBorderHalf) >= aDirtyRect.y) { michael@0: mStartRg = rgFrame; michael@0: mStartRow = rowFrame; michael@0: nsTableRowFrame* fifRow = michael@0: static_cast(rowFrame->FirstInFlow()); michael@0: startRowIndex = endRowIndex = fifRow->GetRowIndex(); michael@0: haveIntersect = true; michael@0: } michael@0: else { michael@0: mInitialOffsetY += rowSize.height; michael@0: } michael@0: } michael@0: rowY += rowSize.height; michael@0: } michael@0: } michael@0: mNextOffsetY = mInitialOffsetY; michael@0: michael@0: // XXX comment refers to the obsolete NS_FRAME_OUTSIDE_CHILDREN flag michael@0: // XXX but I don't understand it, so not changing it for now michael@0: // outer table borders overflow the table, so the table might be michael@0: // target to other areas as the NS_FRAME_OUTSIDE_CHILDREN is set michael@0: // on the table michael@0: if (!haveIntersect) michael@0: return false; michael@0: // find startColIndex, endColIndex, startColX michael@0: haveIntersect = false; michael@0: if (0 == mNumTableCols) michael@0: return false; michael@0: int32_t leftCol, rightCol; // columns are in the range [leftCol, rightCol) michael@0: michael@0: nsMargin childAreaOffset = mTable->GetChildAreaOffset(nullptr); michael@0: if (mTableIsLTR) { michael@0: mInitialOffsetX = childAreaOffset.left; // x position of first col in michael@0: // damage area michael@0: leftCol = 0; michael@0: rightCol = mNumTableCols; michael@0: } else { michael@0: // x position of first col in damage area michael@0: mInitialOffsetX = mTable->GetRect().width - childAreaOffset.right; michael@0: leftCol = mNumTableCols-1; michael@0: rightCol = -1; michael@0: } michael@0: nscoord x = 0; michael@0: int32_t colX; michael@0: for (colX = leftCol; colX != rightCol; colX += mColInc) { michael@0: nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(colX); michael@0: if (!colFrame) ABORT1(false); michael@0: // get the col rect relative to the table rather than the col group michael@0: nsSize size = colFrame->GetSize(); michael@0: if (haveIntersect) { michael@0: // conservatively estimate the left half border width outside the col michael@0: nscoord leftBorderHalf = michael@0: nsPresContext::CSSPixelsToAppUnits(colFrame->GetLeftBorderWidth() + 1); michael@0: if (aDirtyRect.XMost() >= (x - leftBorderHalf)) { michael@0: endColIndex = colX; michael@0: } michael@0: else break; michael@0: } michael@0: else { michael@0: // conservatively estimate the right half border width outside the col michael@0: nscoord rightBorderHalf = michael@0: nsPresContext::CSSPixelsToAppUnits(colFrame->GetRightBorderWidth() + 1); michael@0: if ((x + size.width + rightBorderHalf) >= aDirtyRect.x) { michael@0: startColIndex = endColIndex = colX; michael@0: haveIntersect = true; michael@0: } michael@0: else { michael@0: mInitialOffsetX += mColInc * size.width; michael@0: } michael@0: } michael@0: x += size.width; michael@0: } michael@0: if (!mTableIsLTR) { michael@0: uint32_t temp; michael@0: mInitialOffsetX = mTable->GetRect().width - childAreaOffset.right; michael@0: temp = startColIndex; startColIndex = endColIndex; endColIndex = temp; michael@0: for (uint32_t column = 0; column < startColIndex; column++) { michael@0: nsTableColFrame* colFrame = mTableFirstInFlow->GetColFrame(column); michael@0: if (!colFrame) ABORT1(false); michael@0: nsSize size = colFrame->GetSize(); michael@0: mInitialOffsetX += mColInc * size.width; michael@0: } michael@0: } michael@0: if (!haveIntersect) michael@0: return false; michael@0: mDamageArea = nsIntRect(startColIndex, startRowIndex, michael@0: 1 + DeprecatedAbs(endColIndex - startColIndex), michael@0: 1 + endRowIndex - startRowIndex); michael@0: michael@0: Reset(); michael@0: mVerInfo = new BCVerticalSeg[mDamageArea.width + 1]; michael@0: if (!mVerInfo) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: BCPaintBorderIterator::Reset() michael@0: { michael@0: mAtEnd = true; // gets reset when First() is called michael@0: mRg = mStartRg; michael@0: mPrevRow = nullptr; michael@0: mRow = mStartRow; michael@0: mRowIndex = 0; michael@0: mColIndex = 0; michael@0: mRgIndex = -1; michael@0: mPrevCell = nullptr; michael@0: mCell = nullptr; michael@0: mPrevCellData = nullptr; michael@0: mCellData = nullptr; michael@0: mBCData = nullptr; michael@0: ResetVerInfo(); michael@0: } michael@0: michael@0: /** michael@0: * Set the iterator data to a new cellmap coordinate michael@0: * @param aRowIndex - the row index michael@0: * @param aColIndex - the col index michael@0: */ michael@0: void michael@0: BCPaintBorderIterator::SetNewData(int32_t aY, michael@0: int32_t aX) michael@0: { michael@0: if (!mTableCellMap || !mTableCellMap->mBCInfo) ABORT0(); michael@0: michael@0: mColIndex = aX; michael@0: mRowIndex = aY; michael@0: mPrevCellData = mCellData; michael@0: if (IsTableRightMost() && IsTableBottomMost()) { michael@0: mCell = nullptr; michael@0: mBCData = &mTableCellMap->mBCInfo->mLowerRightCorner; michael@0: } michael@0: else if (IsTableRightMost()) { michael@0: mCellData = nullptr; michael@0: mBCData = &mTableCellMap->mBCInfo->mRightBorders.ElementAt(aY); michael@0: } michael@0: else if (IsTableBottomMost()) { michael@0: mCellData = nullptr; michael@0: mBCData = &mTableCellMap->mBCInfo->mBottomBorders.ElementAt(aX); michael@0: } michael@0: else { michael@0: if (uint32_t(mRowIndex - mFifRgFirstRowIndex) < mCellMap->mRows.Length()) { michael@0: mBCData = nullptr; michael@0: mCellData = michael@0: (BCCellData*)mCellMap->mRows[mRowIndex - mFifRgFirstRowIndex].SafeElementAt(mColIndex); michael@0: if (mCellData) { michael@0: mBCData = &mCellData->mData; michael@0: if (!mCellData->IsOrig()) { michael@0: if (mCellData->IsRowSpan()) { michael@0: aY -= mCellData->GetRowSpanOffset(); michael@0: } michael@0: if (mCellData->IsColSpan()) { michael@0: aX -= mCellData->GetColSpanOffset(); michael@0: } michael@0: if ((aX >= 0) && (aY >= 0)) { michael@0: mCellData = (BCCellData*)mCellMap->mRows[aY - mFifRgFirstRowIndex][aX]; michael@0: } michael@0: } michael@0: if (mCellData->IsOrig()) { michael@0: mPrevCell = mCell; michael@0: mCell = mCellData->GetCellFrame(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Set the iterator to a new row michael@0: * @param aRow - the new row frame, if null the iterator will advance to the michael@0: * next row michael@0: */ michael@0: bool michael@0: BCPaintBorderIterator::SetNewRow(nsTableRowFrame* aRow) michael@0: { michael@0: mPrevRow = mRow; michael@0: mRow = (aRow) ? aRow : mRow->GetNextRow(); michael@0: if (mRow) { michael@0: mIsNewRow = true; michael@0: mRowIndex = mRow->GetRowIndex(); michael@0: mColIndex = mDamageArea.x; michael@0: mPrevHorSegHeight = 0; michael@0: if (mIsRepeatedHeader) { michael@0: mRepeatedHeaderRowIndex = mRowIndex; michael@0: } michael@0: } michael@0: else { michael@0: mAtEnd = true; michael@0: } michael@0: return !mAtEnd; michael@0: } michael@0: michael@0: /** michael@0: * Advance the iterator to the next row group michael@0: */ michael@0: bool michael@0: BCPaintBorderIterator::SetNewRowGroup() michael@0: { michael@0: michael@0: mRgIndex++; michael@0: michael@0: mIsRepeatedHeader = false; michael@0: mIsRepeatedFooter = false; michael@0: michael@0: NS_ASSERTION(mRgIndex >= 0, "mRgIndex out of bounds"); michael@0: if (uint32_t(mRgIndex) < mRowGroups.Length()) { michael@0: mPrevRg = mRg; michael@0: mRg = mRowGroups[mRgIndex]; michael@0: nsTableRowGroupFrame* fifRg = michael@0: static_cast(mRg->FirstInFlow()); michael@0: mFifRgFirstRowIndex = fifRg->GetStartRowIndex(); michael@0: mRgFirstRowIndex = mRg->GetStartRowIndex(); michael@0: mRgLastRowIndex = mRgFirstRowIndex + mRg->GetRowCount() - 1; michael@0: michael@0: if (SetNewRow(mRg->GetFirstRow())) { michael@0: mCellMap = mTableCellMap->GetMapFor(fifRg, nullptr); michael@0: if (!mCellMap) ABORT1(false); michael@0: } michael@0: if (mRg && mTable->GetPrevInFlow() && !mRg->GetPrevInFlow()) { michael@0: // if mRowGroup doesn't have a prev in flow, then it may be a repeated michael@0: // header or footer michael@0: const nsStyleDisplay* display = mRg->StyleDisplay(); michael@0: if (mRowIndex == mDamageArea.y) { michael@0: mIsRepeatedHeader = (NS_STYLE_DISPLAY_TABLE_HEADER_GROUP == display->mDisplay); michael@0: } michael@0: else { michael@0: mIsRepeatedFooter = (NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP == display->mDisplay); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: mAtEnd = true; michael@0: } michael@0: return !mAtEnd; michael@0: } michael@0: michael@0: /** michael@0: * Move the iterator to the first position in the damageArea michael@0: */ michael@0: void michael@0: BCPaintBorderIterator::First() michael@0: { michael@0: if (!mTable || (mDamageArea.x >= mNumTableCols) || michael@0: (mDamageArea.y >= mNumTableRows)) ABORT0(); michael@0: michael@0: mAtEnd = false; michael@0: michael@0: uint32_t numRowGroups = mRowGroups.Length(); michael@0: for (uint32_t rgY = 0; rgY < numRowGroups; rgY++) { michael@0: nsTableRowGroupFrame* rowG = mRowGroups[rgY]; michael@0: int32_t start = rowG->GetStartRowIndex(); michael@0: int32_t end = start + rowG->GetRowCount() - 1; michael@0: if ((mDamageArea.y >= start) && (mDamageArea.y <= end)) { michael@0: mRgIndex = rgY - 1; // SetNewRowGroup increments rowGroupIndex michael@0: if (SetNewRowGroup()) { michael@0: while ((mRowIndex < mDamageArea.y) && !mAtEnd) { michael@0: SetNewRow(); michael@0: } michael@0: if (!mAtEnd) { michael@0: SetNewData(mDamageArea.y, mDamageArea.x); michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: mAtEnd = true; michael@0: } michael@0: michael@0: /** michael@0: * Advance the iterator to the next position michael@0: */ michael@0: void michael@0: BCPaintBorderIterator::Next() michael@0: { michael@0: if (mAtEnd) ABORT0(); michael@0: mIsNewRow = false; michael@0: michael@0: mColIndex++; michael@0: if (mColIndex > mDamageArea.XMost()) { michael@0: mRowIndex++; michael@0: if (mRowIndex == mDamageArea.YMost()) { michael@0: mColIndex = mDamageArea.x; michael@0: } michael@0: else if (mRowIndex < mDamageArea.YMost()) { michael@0: if (mRowIndex <= mRgLastRowIndex) { michael@0: SetNewRow(); michael@0: } michael@0: else { michael@0: SetNewRowGroup(); michael@0: } michael@0: } michael@0: else { michael@0: mAtEnd = true; michael@0: } michael@0: } michael@0: if (!mAtEnd) { michael@0: SetNewData(mRowIndex, mColIndex); michael@0: } michael@0: } michael@0: michael@0: // XXX if CalcVerCornerOffset and CalcHorCornerOffset remain similar, combine michael@0: // them michael@0: /** Compute the vertical offset of a vertical border segment michael@0: * @param aCornerOwnerSide - which side owns the corner michael@0: * @param aCornerSubWidth - how wide is the nonwinning side of the corner michael@0: * @param aHorWidth - how wide is the horizontal edge of the corner michael@0: * @param aIsStartOfSeg - does this corner start a new segment michael@0: * @param aIsBevel - is this corner beveled michael@0: * @return - offset in twips michael@0: */ michael@0: static nscoord michael@0: CalcVerCornerOffset(mozilla::css::Side aCornerOwnerSide, michael@0: BCPixelSize aCornerSubWidth, michael@0: BCPixelSize aHorWidth, michael@0: bool aIsStartOfSeg, michael@0: bool aIsBevel) michael@0: { michael@0: nscoord offset = 0; michael@0: // XXX These should be replaced with appropriate side-specific macros (which?) michael@0: BCPixelSize smallHalf, largeHalf; michael@0: if ((NS_SIDE_TOP == aCornerOwnerSide) || michael@0: (NS_SIDE_BOTTOM == aCornerOwnerSide)) { michael@0: DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); michael@0: if (aIsBevel) { michael@0: offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; michael@0: } michael@0: else { michael@0: offset = (NS_SIDE_TOP == aCornerOwnerSide) ? smallHalf : -largeHalf; michael@0: } michael@0: } michael@0: else { michael@0: DivideBCBorderSize(aHorWidth, smallHalf, largeHalf); michael@0: if (aIsBevel) { michael@0: offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; michael@0: } michael@0: else { michael@0: offset = (aIsStartOfSeg) ? smallHalf : -largeHalf; michael@0: } michael@0: } michael@0: return nsPresContext::CSSPixelsToAppUnits(offset); michael@0: } michael@0: michael@0: /** Compute the horizontal offset of a horizontal border segment michael@0: * @param aCornerOwnerSide - which side owns the corner michael@0: * @param aCornerSubWidth - how wide is the nonwinning side of the corner michael@0: * @param aVerWidth - how wide is the vertical edge of the corner michael@0: * @param aIsStartOfSeg - does this corner start a new segment michael@0: * @param aIsBevel - is this corner beveled michael@0: * @param aTableIsLTR - direction, the computation depends on ltr or rtl michael@0: * @return - offset in twips michael@0: */ michael@0: static nscoord michael@0: CalcHorCornerOffset(mozilla::css::Side aCornerOwnerSide, michael@0: BCPixelSize aCornerSubWidth, michael@0: BCPixelSize aVerWidth, michael@0: bool aIsStartOfSeg, michael@0: bool aIsBevel, michael@0: bool aTableIsLTR) michael@0: { michael@0: nscoord offset = 0; michael@0: // XXX These should be replaced with appropriate side-specific macros (which?) michael@0: BCPixelSize smallHalf, largeHalf; michael@0: if ((NS_SIDE_LEFT == aCornerOwnerSide) || michael@0: (NS_SIDE_RIGHT == aCornerOwnerSide)) { michael@0: if (aTableIsLTR) { michael@0: DivideBCBorderSize(aCornerSubWidth, smallHalf, largeHalf); michael@0: } michael@0: else { michael@0: DivideBCBorderSize(aCornerSubWidth, largeHalf, smallHalf); michael@0: } michael@0: if (aIsBevel) { michael@0: offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; michael@0: } michael@0: else { michael@0: offset = (NS_SIDE_LEFT == aCornerOwnerSide) ? smallHalf : -largeHalf; michael@0: } michael@0: } michael@0: else { michael@0: if (aTableIsLTR) { michael@0: DivideBCBorderSize(aVerWidth, smallHalf, largeHalf); michael@0: } michael@0: else { michael@0: DivideBCBorderSize(aVerWidth, largeHalf, smallHalf); michael@0: } michael@0: if (aIsBevel) { michael@0: offset = (aIsStartOfSeg) ? -largeHalf : smallHalf; michael@0: } michael@0: else { michael@0: offset = (aIsStartOfSeg) ? smallHalf : -largeHalf; michael@0: } michael@0: } michael@0: return nsPresContext::CSSPixelsToAppUnits(offset); michael@0: } michael@0: michael@0: BCVerticalSeg::BCVerticalSeg() michael@0: { michael@0: mCol = nullptr; michael@0: mFirstCell = mLastCell = mAjaCell = nullptr; michael@0: mOffsetX = mOffsetY = mLength = mWidth = mTopBevelOffset = 0; michael@0: mTopBevelSide = NS_SIDE_TOP; michael@0: mOwner = eCellOwner; michael@0: } michael@0: michael@0: /** michael@0: * Start a new vertical segment michael@0: * @param aIter - iterator containing the structural information michael@0: * @param aBorderOwner - determines the border style michael@0: * @param aVerSegWidth - the width of segment in pixel michael@0: * @param aHorSegHeight - the width of the horizontal segment joining the corner michael@0: * at the start michael@0: */ michael@0: void michael@0: BCVerticalSeg::Start(BCPaintBorderIterator& aIter, michael@0: BCBorderOwner aBorderOwner, michael@0: BCPixelSize aVerSegWidth, michael@0: BCPixelSize aHorSegHeight) michael@0: { michael@0: mozilla::css::Side ownerSide = NS_SIDE_TOP; michael@0: bool bevel = false; michael@0: michael@0: michael@0: nscoord cornerSubWidth = (aIter.mBCData) ? michael@0: aIter.mBCData->GetCorner(ownerSide, bevel) : 0; michael@0: michael@0: bool topBevel = (aVerSegWidth > 0) ? bevel : false; michael@0: BCPixelSize maxHorSegHeight = std::max(aIter.mPrevHorSegHeight, aHorSegHeight); michael@0: nscoord offset = CalcVerCornerOffset(ownerSide, cornerSubWidth, michael@0: maxHorSegHeight, true, michael@0: topBevel); michael@0: michael@0: mTopBevelOffset = topBevel ? michael@0: nsPresContext::CSSPixelsToAppUnits(maxHorSegHeight): 0; michael@0: // XXX this assumes that only corners where 2 segments join can be beveled michael@0: mTopBevelSide = (aHorSegHeight > 0) ? NS_SIDE_RIGHT : NS_SIDE_LEFT; michael@0: mOffsetY += offset; michael@0: mLength = -offset; michael@0: mWidth = aVerSegWidth; michael@0: mOwner = aBorderOwner; michael@0: mFirstCell = aIter.mCell; michael@0: mFirstRowGroup = aIter.mRg; michael@0: mFirstRow = aIter.mRow; michael@0: if (aIter.GetRelativeColIndex() > 0) { michael@0: mAjaCell = aIter.mVerInfo[aIter.GetRelativeColIndex() - 1].mLastCell; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Initialize the vertical segments with information that will persist for any michael@0: * vertical segment in this column michael@0: * @param aIter - iterator containing the structural information michael@0: */ michael@0: void michael@0: BCVerticalSeg::Initialize(BCPaintBorderIterator& aIter) michael@0: { michael@0: int32_t relColIndex = aIter.GetRelativeColIndex(); michael@0: mCol = aIter.IsTableRightMost() ? aIter.mVerInfo[relColIndex - 1].mCol : michael@0: aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex); michael@0: if (!mCol) ABORT0(); michael@0: if (0 == relColIndex) { michael@0: mOffsetX = aIter.mInitialOffsetX; michael@0: } michael@0: // set colX for the next column michael@0: if (!aIter.IsDamageAreaRightMost()) { michael@0: aIter.mVerInfo[relColIndex + 1].mOffsetX = mOffsetX + michael@0: aIter.mColInc * mCol->GetSize().width; michael@0: } michael@0: mOffsetY = aIter.mInitialOffsetY; michael@0: mLastCell = aIter.mCell; michael@0: } michael@0: michael@0: /** michael@0: * Compute the offsets for the bottom corner of a vertical segment michael@0: * @param aIter - iterator containing the structural information michael@0: * @param aHorSegHeight - the width of the horizontal segment joining the corner michael@0: * at the start michael@0: */ michael@0: void michael@0: BCVerticalSeg::GetBottomCorner(BCPaintBorderIterator& aIter, michael@0: BCPixelSize aHorSegHeight) michael@0: { michael@0: mozilla::css::Side ownerSide = NS_SIDE_TOP; michael@0: nscoord cornerSubWidth = 0; michael@0: bool bevel = false; michael@0: if (aIter.mBCData) { michael@0: cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); michael@0: } michael@0: mIsBottomBevel = (mWidth > 0) ? bevel : false; michael@0: mBottomHorSegHeight = std::max(aIter.mPrevHorSegHeight, aHorSegHeight); michael@0: mBottomOffset = CalcVerCornerOffset(ownerSide, cornerSubWidth, michael@0: mBottomHorSegHeight, michael@0: false, mIsBottomBevel); michael@0: mLength += mBottomOffset; michael@0: } michael@0: michael@0: /** michael@0: * Paint the vertical segment michael@0: * @param aIter - iterator containing the structural information michael@0: * @param aRenderingContext - the rendering context michael@0: * @param aHorSegHeight - the width of the horizontal segment joining the corner michael@0: * at the start michael@0: */ michael@0: void michael@0: BCVerticalSeg::Paint(BCPaintBorderIterator& aIter, michael@0: nsRenderingContext& aRenderingContext, michael@0: BCPixelSize aHorSegHeight) michael@0: { michael@0: // get the border style, color and paint the segment michael@0: mozilla::css::Side side = (aIter.IsDamageAreaRightMost()) ? NS_SIDE_RIGHT : michael@0: NS_SIDE_LEFT; michael@0: int32_t relColIndex = aIter.GetRelativeColIndex(); michael@0: nsTableColFrame* col = mCol; if (!col) ABORT0(); michael@0: nsTableCellFrame* cell = mFirstCell; // ??? michael@0: nsIFrame* owner = nullptr; michael@0: uint8_t style = NS_STYLE_BORDER_STYLE_SOLID; michael@0: nscolor color = 0xFFFFFFFF; michael@0: michael@0: switch (mOwner) { michael@0: case eTableOwner: michael@0: owner = aIter.mTable; michael@0: break; michael@0: case eAjaColGroupOwner: michael@0: side = NS_SIDE_RIGHT; michael@0: if (!aIter.IsTableRightMost() && (relColIndex > 0)) { michael@0: col = aIter.mVerInfo[relColIndex - 1].mCol; michael@0: } // and fall through michael@0: case eColGroupOwner: michael@0: if (col) { michael@0: owner = col->GetParent(); michael@0: } michael@0: break; michael@0: case eAjaColOwner: michael@0: side = NS_SIDE_RIGHT; michael@0: if (!aIter.IsTableRightMost() && (relColIndex > 0)) { michael@0: col = aIter.mVerInfo[relColIndex - 1].mCol; michael@0: } // and fall through michael@0: case eColOwner: michael@0: owner = col; michael@0: break; michael@0: case eAjaRowGroupOwner: michael@0: NS_ERROR("a neighboring rowgroup can never own a vertical border"); michael@0: // and fall through michael@0: case eRowGroupOwner: michael@0: NS_ASSERTION(aIter.IsTableLeftMost() || aIter.IsTableRightMost(), michael@0: "row group can own border only at table edge"); michael@0: owner = mFirstRowGroup; michael@0: break; michael@0: case eAjaRowOwner: michael@0: NS_ASSERTION(false, "program error"); // and fall through michael@0: case eRowOwner: michael@0: NS_ASSERTION(aIter.IsTableLeftMost() || aIter.IsTableRightMost(), michael@0: "row can own border only at table edge"); michael@0: owner = mFirstRow; michael@0: break; michael@0: case eAjaCellOwner: michael@0: side = NS_SIDE_RIGHT; michael@0: cell = mAjaCell; // and fall through michael@0: case eCellOwner: michael@0: owner = cell; michael@0: break; michael@0: } michael@0: if (owner) { michael@0: ::GetPaintStyleInfo(owner, side, style, color, aIter.mTableIsLTR); michael@0: } michael@0: BCPixelSize smallHalf, largeHalf; michael@0: DivideBCBorderSize(mWidth, smallHalf, largeHalf); michael@0: nsRect segRect(mOffsetX - nsPresContext::CSSPixelsToAppUnits(largeHalf), michael@0: mOffsetY, michael@0: nsPresContext::CSSPixelsToAppUnits(mWidth), mLength); michael@0: nscoord bottomBevelOffset = (mIsBottomBevel) ? michael@0: nsPresContext::CSSPixelsToAppUnits(mBottomHorSegHeight) : 0; michael@0: mozilla::css::Side bottomBevelSide = ((aHorSegHeight > 0) ^ !aIter.mTableIsLTR) ? michael@0: NS_SIDE_RIGHT : NS_SIDE_LEFT; michael@0: mozilla::css::Side topBevelSide = ((mTopBevelSide == NS_SIDE_RIGHT) ^ !aIter.mTableIsLTR)? michael@0: NS_SIDE_RIGHT : NS_SIDE_LEFT; michael@0: nsCSSRendering::DrawTableBorderSegment(aRenderingContext, style, color, michael@0: aIter.mTableBgColor, segRect, michael@0: nsPresContext::AppUnitsPerCSSPixel(), michael@0: topBevelSide, mTopBevelOffset, michael@0: bottomBevelSide, bottomBevelOffset); michael@0: } michael@0: michael@0: /** michael@0: * Advance the start point of a segment michael@0: */ michael@0: void michael@0: BCVerticalSeg::AdvanceOffsetY() michael@0: { michael@0: mOffsetY += mLength - mBottomOffset; michael@0: } michael@0: michael@0: /** michael@0: * Accumulate the current segment michael@0: */ michael@0: void michael@0: BCVerticalSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) michael@0: { michael@0: mLastCell = aIter.mCell; michael@0: mLength += aIter.mRow->GetRect().height; michael@0: } michael@0: michael@0: BCHorizontalSeg::BCHorizontalSeg() michael@0: { michael@0: mOffsetX = mOffsetY = mLength = mWidth = mLeftBevelOffset = 0; michael@0: mLeftBevelSide = NS_SIDE_TOP; michael@0: mFirstCell = mAjaCell = nullptr; michael@0: } michael@0: michael@0: /** Initialize a horizontal border segment for painting michael@0: * @param aIter - iterator storing the current and adjacent frames michael@0: * @param aBorderOwner - which frame owns the border michael@0: * @param aBottomVerSegWidth - vertical segment width coming from up michael@0: * @param aHorSegHeight - the height of the segment michael@0: + */ michael@0: void michael@0: BCHorizontalSeg::Start(BCPaintBorderIterator& aIter, michael@0: BCBorderOwner aBorderOwner, michael@0: BCPixelSize aBottomVerSegWidth, michael@0: BCPixelSize aHorSegHeight) michael@0: { michael@0: mozilla::css::Side cornerOwnerSide = NS_SIDE_TOP; michael@0: bool bevel = false; michael@0: michael@0: mOwner = aBorderOwner; michael@0: nscoord cornerSubWidth = (aIter.mBCData) ? michael@0: aIter.mBCData->GetCorner(cornerOwnerSide, michael@0: bevel) : 0; michael@0: michael@0: bool leftBevel = (aHorSegHeight > 0) ? bevel : false; michael@0: int32_t relColIndex = aIter.GetRelativeColIndex(); michael@0: nscoord maxVerSegWidth = std::max(aIter.mVerInfo[relColIndex].mWidth, michael@0: aBottomVerSegWidth); michael@0: nscoord offset = CalcHorCornerOffset(cornerOwnerSide, cornerSubWidth, michael@0: maxVerSegWidth, true, leftBevel, michael@0: aIter.mTableIsLTR); michael@0: mLeftBevelOffset = (leftBevel && (aHorSegHeight > 0)) ? maxVerSegWidth : 0; michael@0: // XXX this assumes that only corners where 2 segments join can be beveled michael@0: mLeftBevelSide = (aBottomVerSegWidth > 0) ? NS_SIDE_BOTTOM : NS_SIDE_TOP; michael@0: if (aIter.mTableIsLTR) { michael@0: mOffsetX += offset; michael@0: } michael@0: else { michael@0: mOffsetX -= offset; michael@0: } michael@0: mLength = -offset; michael@0: mWidth = aHorSegHeight; michael@0: mFirstCell = aIter.mCell; michael@0: mAjaCell = (aIter.IsDamageAreaTopMost()) ? nullptr : michael@0: aIter.mVerInfo[relColIndex].mLastCell; michael@0: } michael@0: michael@0: /** michael@0: * Compute the offsets for the right corner of a horizontal segment michael@0: * @param aIter - iterator containing the structural information michael@0: * @param aLeftSegWidth - the width of the vertical segment joining the corner michael@0: * at the start michael@0: */ michael@0: void michael@0: BCHorizontalSeg::GetRightCorner(BCPaintBorderIterator& aIter, michael@0: BCPixelSize aLeftSegWidth) michael@0: { michael@0: mozilla::css::Side ownerSide = NS_SIDE_TOP; michael@0: nscoord cornerSubWidth = 0; michael@0: bool bevel = false; michael@0: if (aIter.mBCData) { michael@0: cornerSubWidth = aIter.mBCData->GetCorner(ownerSide, bevel); michael@0: } michael@0: michael@0: mIsRightBevel = (mWidth > 0) ? bevel : 0; michael@0: int32_t relColIndex = aIter.GetRelativeColIndex(); michael@0: nscoord verWidth = std::max(aIter.mVerInfo[relColIndex].mWidth, aLeftSegWidth); michael@0: mEndOffset = CalcHorCornerOffset(ownerSide, cornerSubWidth, verWidth, michael@0: false, mIsRightBevel, aIter.mTableIsLTR); michael@0: mLength += mEndOffset; michael@0: mRightBevelOffset = (mIsRightBevel) ? michael@0: nsPresContext::CSSPixelsToAppUnits(verWidth) : 0; michael@0: mRightBevelSide = (aLeftSegWidth > 0) ? NS_SIDE_BOTTOM : NS_SIDE_TOP; michael@0: } michael@0: michael@0: /** michael@0: * Paint the horizontal segment michael@0: * @param aIter - iterator containing the structural information michael@0: * @param aRenderingContext - the rendering context michael@0: */ michael@0: void michael@0: BCHorizontalSeg::Paint(BCPaintBorderIterator& aIter, michael@0: nsRenderingContext& aRenderingContext) michael@0: { michael@0: // get the border style, color and paint the segment michael@0: mozilla::css::Side side = (aIter.IsDamageAreaBottomMost()) ? NS_SIDE_BOTTOM : michael@0: NS_SIDE_TOP; michael@0: nsIFrame* rg = aIter.mRg; if (!rg) ABORT0(); michael@0: nsIFrame* row = aIter.mRow; if (!row) ABORT0(); michael@0: nsIFrame* cell = mFirstCell; michael@0: nsIFrame* col; michael@0: nsIFrame* owner = nullptr; michael@0: michael@0: uint8_t style = NS_STYLE_BORDER_STYLE_SOLID; michael@0: nscolor color = 0xFFFFFFFF; michael@0: michael@0: michael@0: switch (mOwner) { michael@0: case eTableOwner: michael@0: owner = aIter.mTable; michael@0: break; michael@0: case eAjaColGroupOwner: michael@0: NS_ERROR("neighboring colgroups can never own a horizontal border"); michael@0: // and fall through michael@0: case eColGroupOwner: michael@0: NS_ASSERTION(aIter.IsTableTopMost() || aIter.IsTableBottomMost(), michael@0: "col group can own border only at the table edge"); michael@0: col = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1); michael@0: if (!col) ABORT0(); michael@0: owner = col->GetParent(); michael@0: break; michael@0: case eAjaColOwner: michael@0: NS_ERROR("neighboring column can never own a horizontal border"); michael@0: // and fall through michael@0: case eColOwner: michael@0: NS_ASSERTION(aIter.IsTableTopMost() || aIter.IsTableBottomMost(), michael@0: "col can own border only at the table edge"); michael@0: owner = aIter.mTableFirstInFlow->GetColFrame(aIter.mColIndex - 1); michael@0: break; michael@0: case eAjaRowGroupOwner: michael@0: side = NS_SIDE_BOTTOM; michael@0: rg = (aIter.IsTableBottomMost()) ? aIter.mRg : aIter.mPrevRg; michael@0: // and fall through michael@0: case eRowGroupOwner: michael@0: owner = rg; michael@0: break; michael@0: case eAjaRowOwner: michael@0: side = NS_SIDE_BOTTOM; michael@0: row = (aIter.IsTableBottomMost()) ? aIter.mRow : aIter.mPrevRow; michael@0: // and fall through michael@0: case eRowOwner: michael@0: owner = row; michael@0: break; michael@0: case eAjaCellOwner: michael@0: side = NS_SIDE_BOTTOM; michael@0: // if this is null due to the damage area origin-y > 0, then the border michael@0: // won't show up anyway michael@0: cell = mAjaCell; michael@0: // and fall through michael@0: case eCellOwner: michael@0: owner = cell; michael@0: break; michael@0: } michael@0: if (owner) { michael@0: ::GetPaintStyleInfo(owner, side, style, color, aIter.mTableIsLTR); michael@0: } michael@0: BCPixelSize smallHalf, largeHalf; michael@0: DivideBCBorderSize(mWidth, smallHalf, largeHalf); michael@0: nsRect segRect(mOffsetX, michael@0: mOffsetY - nsPresContext::CSSPixelsToAppUnits(largeHalf), michael@0: mLength, michael@0: nsPresContext::CSSPixelsToAppUnits(mWidth)); michael@0: if (aIter.mTableIsLTR) { michael@0: nsCSSRendering::DrawTableBorderSegment(aRenderingContext, style, color, michael@0: aIter.mTableBgColor, segRect, michael@0: nsPresContext::AppUnitsPerCSSPixel(), michael@0: mLeftBevelSide, michael@0: nsPresContext::CSSPixelsToAppUnits(mLeftBevelOffset), michael@0: mRightBevelSide, mRightBevelOffset); michael@0: } michael@0: else { michael@0: segRect.x -= segRect.width; michael@0: nsCSSRendering::DrawTableBorderSegment(aRenderingContext, style, color, michael@0: aIter.mTableBgColor, segRect, michael@0: nsPresContext::AppUnitsPerCSSPixel(), michael@0: mRightBevelSide, mRightBevelOffset, michael@0: mLeftBevelSide, michael@0: nsPresContext::CSSPixelsToAppUnits(mLeftBevelOffset)); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Advance the start point of a segment michael@0: */ michael@0: void michael@0: BCHorizontalSeg::AdvanceOffsetX(int32_t aIncrement) michael@0: { michael@0: mOffsetX += aIncrement * (mLength - mEndOffset); michael@0: } michael@0: michael@0: /** michael@0: * Accumulate the current segment michael@0: */ michael@0: void michael@0: BCHorizontalSeg::IncludeCurrentBorder(BCPaintBorderIterator& aIter) michael@0: { michael@0: mLength += aIter.mVerInfo[aIter.GetRelativeColIndex()].mColWidth; michael@0: } michael@0: michael@0: /** michael@0: * store the column width information while painting horizontal segment michael@0: */ michael@0: void michael@0: BCPaintBorderIterator::StoreColumnWidth(int32_t aIndex) michael@0: { michael@0: if (IsTableRightMost()) { michael@0: mVerInfo[aIndex].mColWidth = mVerInfo[aIndex - 1].mColWidth; michael@0: } michael@0: else { michael@0: nsTableColFrame* col = mTableFirstInFlow->GetColFrame(mColIndex); michael@0: if (!col) ABORT0(); michael@0: mVerInfo[aIndex].mColWidth = col->GetSize().width; michael@0: } michael@0: } michael@0: /** michael@0: * Determine if a vertical segment owns the corder michael@0: */ michael@0: bool michael@0: BCPaintBorderIterator::VerticalSegmentOwnsCorner() michael@0: { michael@0: mozilla::css::Side cornerOwnerSide = NS_SIDE_TOP; michael@0: bool bevel = false; michael@0: if (mBCData) { michael@0: mBCData->GetCorner(cornerOwnerSide, bevel); michael@0: } michael@0: // unitialized ownerside, bevel michael@0: return (NS_SIDE_TOP == cornerOwnerSide) || michael@0: (NS_SIDE_BOTTOM == cornerOwnerSide); michael@0: } michael@0: michael@0: /** michael@0: * Paint if necessary a horizontal segment, otherwise accumulate it michael@0: * @param aRenderingContext - the rendering context michael@0: */ michael@0: void michael@0: BCPaintBorderIterator::AccumulateOrPaintHorizontalSegment(nsRenderingContext& aRenderingContext) michael@0: { michael@0: michael@0: int32_t relColIndex = GetRelativeColIndex(); michael@0: // store the current col width if it hasn't been already michael@0: if (mVerInfo[relColIndex].mColWidth < 0) { michael@0: StoreColumnWidth(relColIndex); michael@0: } michael@0: michael@0: BCBorderOwner borderOwner = eCellOwner; michael@0: BCBorderOwner ignoreBorderOwner; michael@0: bool isSegStart = true; michael@0: bool ignoreSegStart; michael@0: michael@0: nscoord leftSegWidth = michael@0: mBCData ? mBCData->GetLeftEdge(ignoreBorderOwner, ignoreSegStart) : 0; michael@0: nscoord topSegHeight = michael@0: mBCData ? mBCData->GetTopEdge(borderOwner, isSegStart) : 0; michael@0: michael@0: if (mIsNewRow || (IsDamageAreaLeftMost() && IsDamageAreaBottomMost())) { michael@0: // reset for every new row and on the bottom of the last row michael@0: mHorSeg.mOffsetY = mNextOffsetY; michael@0: mNextOffsetY = mNextOffsetY + mRow->GetSize().height; michael@0: mHorSeg.mOffsetX = mInitialOffsetX; michael@0: mHorSeg.Start(*this, borderOwner, leftSegWidth, topSegHeight); michael@0: } michael@0: michael@0: if (!IsDamageAreaLeftMost() && (isSegStart || IsDamageAreaRightMost() || michael@0: VerticalSegmentOwnsCorner())) { michael@0: // paint the previous seg or the current one if IsDamageAreaRightMost() michael@0: if (mHorSeg.mLength > 0) { michael@0: mHorSeg.GetRightCorner(*this, leftSegWidth); michael@0: if (mHorSeg.mWidth > 0) { michael@0: mHorSeg.Paint(*this, aRenderingContext); michael@0: } michael@0: mHorSeg.AdvanceOffsetX(mColInc); michael@0: } michael@0: mHorSeg.Start(*this, borderOwner, leftSegWidth, topSegHeight); michael@0: } michael@0: mHorSeg.IncludeCurrentBorder(*this); michael@0: mVerInfo[relColIndex].mWidth = leftSegWidth; michael@0: mVerInfo[relColIndex].mLastCell = mCell; michael@0: } michael@0: /** michael@0: * Paint if necessary a vertical segment, otherwise it michael@0: * @param aRenderingContext - the rendering context michael@0: */ michael@0: void michael@0: BCPaintBorderIterator::AccumulateOrPaintVerticalSegment(nsRenderingContext& aRenderingContext) michael@0: { michael@0: BCBorderOwner borderOwner = eCellOwner; michael@0: BCBorderOwner ignoreBorderOwner; michael@0: bool isSegStart = true; michael@0: bool ignoreSegStart; michael@0: michael@0: nscoord verSegWidth = michael@0: mBCData ? mBCData->GetLeftEdge(borderOwner, isSegStart) : 0; michael@0: nscoord horSegHeight = michael@0: mBCData ? mBCData->GetTopEdge(ignoreBorderOwner, ignoreSegStart) : 0; michael@0: michael@0: int32_t relColIndex = GetRelativeColIndex(); michael@0: BCVerticalSeg& verSeg = mVerInfo[relColIndex]; michael@0: if (!verSeg.mCol) { // on the first damaged row and the first segment in the michael@0: // col michael@0: verSeg.Initialize(*this); michael@0: verSeg.Start(*this, borderOwner, verSegWidth, horSegHeight); michael@0: } michael@0: michael@0: if (!IsDamageAreaTopMost() && (isSegStart || IsDamageAreaBottomMost() || michael@0: IsAfterRepeatedHeader() || michael@0: StartRepeatedFooter())) { michael@0: // paint the previous seg or the current one if IsDamageAreaBottomMost() michael@0: if (verSeg.mLength > 0) { michael@0: verSeg.GetBottomCorner(*this, horSegHeight); michael@0: if (verSeg.mWidth > 0) { michael@0: verSeg.Paint(*this, aRenderingContext, horSegHeight); michael@0: } michael@0: verSeg.AdvanceOffsetY(); michael@0: } michael@0: verSeg.Start(*this, borderOwner, verSegWidth, horSegHeight); michael@0: } michael@0: verSeg.IncludeCurrentBorder(*this); michael@0: mPrevHorSegHeight = horSegHeight; michael@0: } michael@0: michael@0: /** michael@0: * Reset the vertical information cache michael@0: */ michael@0: void michael@0: BCPaintBorderIterator::ResetVerInfo() michael@0: { michael@0: if (mVerInfo) { michael@0: memset(mVerInfo, 0, mDamageArea.width * sizeof(BCVerticalSeg)); michael@0: // XXX reinitialize properly michael@0: for (int32_t xIndex = 0; xIndex < mDamageArea.width; xIndex++) { michael@0: mVerInfo[xIndex].mColWidth = -1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Method to paint BCBorders, this does not use currently display lists although michael@0: * it will do this in future michael@0: * @param aRenderingContext - the rendering context michael@0: * @param aDirtyRect - inside this rectangle the BC Borders will redrawn michael@0: */ michael@0: void michael@0: nsTableFrame::PaintBCBorders(nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: // We first transfer the aDirtyRect into cellmap coordinates to compute which michael@0: // cell borders need to be painted michael@0: BCPaintBorderIterator iter(this); michael@0: if (!iter.SetDamageArea(aDirtyRect)) michael@0: return; michael@0: michael@0: // First, paint all of the vertical borders from top to bottom and left to michael@0: // right as they become complete. They are painted first, since they are less michael@0: // efficient to paint than horizontal segments. They were stored with as few michael@0: // segments as possible (since horizontal borders are painted last and michael@0: // possibly over them). For every cell in a row that fails in the damage are michael@0: // we look up if the current border would start a new segment, if so we paint michael@0: // the previously stored vertical segment and start a new segment. After michael@0: // this we the now active segment with the current border. These michael@0: // segments are stored in mVerInfo to be used on the next row michael@0: for (iter.First(); !iter.mAtEnd; iter.Next()) { michael@0: iter.AccumulateOrPaintVerticalSegment(aRenderingContext); michael@0: } michael@0: michael@0: // Next, paint all of the horizontal border segments from top to bottom reuse michael@0: // the mVerInfo array to keep track of col widths and vertical segments for michael@0: // corner calculations michael@0: iter.Reset(); michael@0: for (iter.First(); !iter.mAtEnd; iter.Next()) { michael@0: iter.AccumulateOrPaintHorizontalSegment(aRenderingContext); michael@0: } michael@0: } michael@0: michael@0: bool nsTableFrame::RowHasSpanningCells(int32_t aRowIndex, int32_t aNumEffCols) michael@0: { michael@0: bool result = false; michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated."); michael@0: if (cellMap) { michael@0: result = cellMap->RowHasSpanningCells(aRowIndex, aNumEffCols); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: bool nsTableFrame::RowIsSpannedInto(int32_t aRowIndex, int32_t aNumEffCols) michael@0: { michael@0: bool result = false; michael@0: nsTableCellMap* cellMap = GetCellMap(); michael@0: NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated."); michael@0: if (cellMap) { michael@0: result = cellMap->RowIsSpannedInto(aRowIndex, aNumEffCols); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsTableFrame::InvalidateTableFrame(nsIFrame* aFrame, michael@0: const nsRect& aOrigRect, michael@0: const nsRect& aOrigVisualOverflow, michael@0: bool aIsFirstReflow) michael@0: { michael@0: nsIFrame* parent = aFrame->GetParent(); michael@0: NS_ASSERTION(parent, "What happened here?"); michael@0: michael@0: if (parent->GetStateBits() & NS_FRAME_FIRST_REFLOW) { michael@0: // Don't bother; we'll invalidate the parent's overflow rect when michael@0: // we finish reflowing it. michael@0: return; michael@0: } michael@0: michael@0: // The part that looks at both the rect and the overflow rect is a michael@0: // bit of a hack. See nsBlockFrame::ReflowLine for an eloquent michael@0: // description of its hackishness. michael@0: // michael@0: // This doesn't really make sense now that we have DLBI. michael@0: // This code can probably be simplified a fair bit. michael@0: nsRect visualOverflow = aFrame->GetVisualOverflowRect(); michael@0: if (aIsFirstReflow || michael@0: aOrigRect.TopLeft() != aFrame->GetPosition() || michael@0: aOrigVisualOverflow.TopLeft() != visualOverflow.TopLeft()) { michael@0: // Invalidate the old and new overflow rects. Note that if the michael@0: // frame moved, we can't just use aOrigVisualOverflow, since it's in michael@0: // coordinates relative to the old position. So invalidate via michael@0: // aFrame's parent, and reposition that overflow rect to the right michael@0: // place. michael@0: // XXXbz this doesn't handle outlines, does it? michael@0: aFrame->InvalidateFrame(); michael@0: parent->InvalidateFrameWithRect(aOrigVisualOverflow + aOrigRect.TopLeft()); michael@0: } else if (aOrigRect.Size() != aFrame->GetSize() || michael@0: aOrigVisualOverflow.Size() != visualOverflow.Size()){ michael@0: aFrame->InvalidateFrameWithRect(aOrigVisualOverflow); michael@0: aFrame->InvalidateFrame(); michael@0: parent->InvalidateFrameWithRect(aOrigRect);; michael@0: parent->InvalidateFrame(); michael@0: } michael@0: }