summaryrefslogtreecommitdiffstats
path: root/layout/tables/nsTableCellFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/tables/nsTableCellFrame.cpp')
-rw-r--r--layout/tables/nsTableCellFrame.cpp1175
1 files changed, 1175 insertions, 0 deletions
diff --git a/layout/tables/nsTableCellFrame.cpp b/layout/tables/nsTableCellFrame.cpp
new file mode 100644
index 0000000000..70556d5e28
--- /dev/null
+++ b/layout/tables/nsTableCellFrame.cpp
@@ -0,0 +1,1175 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTableCellFrame.h"
+
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "nsTableFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsCSSRendering.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsHTMLParts.h"
+#include "nsGkAtoms.h"
+#include "nsDisplayList.h"
+#include "nsLayoutUtils.h"
+#include "nsTextFrame.h"
+#include <algorithm>
+
+// TABLECELL SELECTION
+#include "nsFrameSelection.h"
+#include "mozilla/LookAndFeel.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+nsTableCellFrame::nsTableCellFrame(ComputedStyle* aStyle,
+ nsTableFrame* aTableFrame, ClassID aID)
+ : nsContainerFrame(aStyle, aTableFrame->PresContext(), aID),
+ mDesiredSize(aTableFrame->GetWritingMode()) {
+ mColIndex = 0;
+ mPriorAvailISize = 0;
+
+ SetContentEmpty(false);
+}
+
+nsTableCellFrame::~nsTableCellFrame() = default;
+
+NS_IMPL_FRAMEARENA_HELPERS(nsTableCellFrame)
+
+void nsTableCellFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ // Let the base class do its initialization
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+
+ if (aPrevInFlow) {
+ // Set the column index
+ nsTableCellFrame* cellFrame = (nsTableCellFrame*)aPrevInFlow;
+ uint32_t colIndex = cellFrame->ColIndex();
+ SetColIndex(colIndex);
+ } else {
+ // Although the spec doesn't say that writing-mode is not applied to
+ // table-cells, we still override style value here because we want to
+ // make effective writing mode of table structure frames consistent
+ // within a table. The content inside table cells is reflowed by an
+ // anonymous block, hence their writing mode is not affected.
+ mWritingMode = GetTableFrame()->GetWritingMode();
+ }
+}
+
+void nsTableCellFrame::Destroy(DestroyContext& aContext) {
+ nsTableFrame::MaybeUnregisterPositionedTablePart(this);
+ nsContainerFrame::Destroy(aContext);
+}
+
+// nsIPercentBSizeObserver methods
+
+void nsTableCellFrame::NotifyPercentBSize(const ReflowInput& aReflowInput) {
+ // ReflowInput ensures the mCBReflowInput of blocks inside a
+ // cell is the cell frame, not the inner-cell block, and that the
+ // containing block of an inner table is the containing block of its
+ // table wrapper.
+ // XXXldb Given the now-stricter |NeedsToObserve|, many if not all of
+ // these tests are probably unnecessary.
+
+ // Maybe the cell reflow input; we sure if we're inside the |if|.
+ const ReflowInput* cellRI = aReflowInput.mCBReflowInput;
+
+ if (cellRI && cellRI->mFrame == this &&
+ (cellRI->ComputedBSize() == NS_UNCONSTRAINEDSIZE ||
+ cellRI->ComputedBSize() == 0)) { // XXXldb Why 0?
+ // This is a percentage bsize on a frame whose percentage bsizes
+ // are based on the bsize of the cell, since its containing block
+ // is the inner cell frame.
+
+ // We'll only honor the percent bsize if sibling-cells/ancestors
+ // have specified/pct bsize. (Also, siblings only count for this if
+ // both this cell and the sibling cell span exactly 1 row.)
+
+ if (nsTableFrame::AncestorsHaveStyleBSize(*cellRI) ||
+ (GetTableFrame()->GetEffectiveRowSpan(*this) == 1 &&
+ cellRI->mParentReflowInput->mFrame->HasAnyStateBits(
+ NS_ROW_HAS_CELL_WITH_STYLE_BSIZE))) {
+ for (const ReflowInput* rs = aReflowInput.mParentReflowInput;
+ rs != cellRI; rs = rs->mParentReflowInput) {
+ rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
+ }
+
+ nsTableFrame::RequestSpecialBSizeReflow(*cellRI);
+ }
+ }
+}
+
+// The cell needs to observe its block and things inside its block but nothing
+// below that
+bool nsTableCellFrame::NeedsToObserve(const ReflowInput& aReflowInput) {
+ const ReflowInput* rs = aReflowInput.mParentReflowInput;
+ if (!rs) return false;
+ if (rs->mFrame == this) {
+ // We always observe the child block. It will never send any
+ // notifications, but we need this so that the observer gets
+ // propagated to its kids.
+ return true;
+ }
+ rs = rs->mParentReflowInput;
+ if (!rs) {
+ return false;
+ }
+
+ // We always need to let the percent bsize observer be propagated
+ // from a table wrapper frame to an inner table frame.
+ LayoutFrameType fType = aReflowInput.mFrame->Type();
+ if (fType == LayoutFrameType::Table) {
+ return true;
+ }
+
+ // We need the observer to be propagated to all children of the cell
+ // (i.e., children of the child block) in quirks mode, but only to
+ // tables in standards mode.
+ // XXX This may not be true in the case of orthogonal flows within
+ // the cell (bug 1174711 comment 8); we may need to observe isizes
+ // instead of bsizes for orthogonal children.
+ return rs->mFrame == this &&
+ (PresContext()->CompatibilityMode() == eCompatibility_NavQuirks ||
+ fType == LayoutFrameType::TableWrapper);
+}
+
+nsresult nsTableCellFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType) {
+ // We need to recalculate in this case because of the nowrap quirk in
+ // BasicTableLayoutStrategy
+ if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::nowrap &&
+ PresContext()->CompatibilityMode() == eCompatibility_NavQuirks) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_IS_DIRTY);
+ }
+
+ const nsAtom* colSpanAttribute =
+ MOZ_UNLIKELY(mContent->AsElement()->IsMathMLElement())
+ ? nsGkAtoms::columnspan_
+ : nsGkAtoms::colspan;
+ if (aAttribute == nsGkAtoms::rowspan || aAttribute == colSpanAttribute) {
+ nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0},
+ nsChangeHint_UpdateTableCellSpans);
+ }
+ return NS_OK;
+}
+
+/* virtual */
+void nsTableCellFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
+ nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle);
+
+ if (!aOldComputedStyle) {
+ return; // avoid the following on init
+ }
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ if (StyleBorder()->GetComputedBorder() !=
+ aOldComputedStyle->StyleBorder()->GetComputedBorder()) {
+ // If a table cell's computed border changes, it can change whether or
+ // not its parent table is classified as a layout or data table. We
+ // send a notification here to invalidate the a11y cache on the table
+ // so the next fetch of IsProbablyLayoutTable() is accurate.
+ accService->TableLayoutGuessMaybeChanged(PresShell(), mContent);
+ }
+ }
+#endif
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse() &&
+ tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) {
+ uint32_t colIndex = ColIndex();
+ uint32_t rowIndex = RowIndex();
+ // row span needs to be clamped as we do not create rows in the cellmap
+ // which do not have cells originating in them
+ TableArea damageArea(colIndex, rowIndex, GetColSpan(),
+ std::min(static_cast<uint32_t>(GetRowSpan()),
+ tableFrame->GetRowCount() - rowIndex));
+ tableFrame->AddBCDamageArea(damageArea);
+ }
+}
+
+#ifdef DEBUG
+void nsTableCellFrame::AppendFrames(ChildListID aListID,
+ nsFrameList&& aFrameList) {
+ MOZ_CRASH("unsupported operation");
+}
+
+void nsTableCellFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList&& aFrameList) {
+ MOZ_CRASH("unsupported operation");
+}
+
+void nsTableCellFrame::RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) {
+ MOZ_CRASH("unsupported operation");
+}
+#endif
+
+void nsTableCellFrame::SetColIndex(int32_t aColIndex) { mColIndex = aColIndex; }
+
+/* virtual */
+nsMargin nsTableCellFrame::GetUsedMargin() const {
+ return nsMargin(0, 0, 0, 0);
+}
+
+// ASSURE DIFFERENT COLORS for selection
+inline nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB) {
+ if (colorA == colorB) {
+ nscolor res;
+ res = NS_RGB(NS_GET_R(colorA) ^ 0xff, NS_GET_G(colorA) ^ 0xff,
+ NS_GET_B(colorA) ^ 0xff);
+ return res;
+ }
+ return colorA;
+}
+
+void nsTableCellFrame::DecorateForSelection(DrawTarget* aDrawTarget,
+ nsPoint aPt) {
+ NS_ASSERTION(IsSelected(), "Should only be called for selected cells");
+ int16_t displaySelection;
+ displaySelection = DetermineDisplaySelection();
+ if (displaySelection) {
+ RefPtr<nsFrameSelection> frameSelection = PresShell()->FrameSelection();
+
+ if (frameSelection->IsInTableSelectionMode()) {
+ nscolor bordercolor;
+ if (displaySelection == nsISelectionController::SELECTION_DISABLED) {
+ bordercolor = NS_RGB(176, 176, 176); // disabled color
+ } else {
+ bordercolor = LookAndFeel::Color(LookAndFeel::ColorID::Highlight, this);
+ }
+ nscoord threePx = nsPresContext::CSSPixelsToAppUnits(3);
+ if ((mRect.width > threePx) && (mRect.height > threePx)) {
+ // compare bordercolor to background-color
+ bordercolor = EnsureDifferentColors(
+ bordercolor, StyleBackground()->BackgroundColor(this));
+
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+ Point devPixelOffset = NSPointToPoint(aPt, appUnitsPerDevPixel);
+
+ AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ aDrawTarget->SetTransform(
+ aDrawTarget->GetTransform().PreTranslate(devPixelOffset));
+
+ ColorPattern color(ToDeviceColor(bordercolor));
+
+ nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
+
+ StrokeLineWithSnapping(nsPoint(onePixel, 0), nsPoint(mRect.width, 0),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(nsPoint(0, onePixel), nsPoint(0, mRect.height),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(nsPoint(onePixel, mRect.height),
+ nsPoint(mRect.width, mRect.height),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(nsPoint(mRect.width, onePixel),
+ nsPoint(mRect.width, mRect.height),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ // middle
+ nsRect r(onePixel, onePixel, mRect.width - onePixel,
+ mRect.height - onePixel);
+ Rect devPixelRect =
+ NSRectToSnappedRect(r, appUnitsPerDevPixel, *aDrawTarget);
+ aDrawTarget->StrokeRect(devPixelRect, color);
+ // shading
+ StrokeLineWithSnapping(
+ nsPoint(2 * onePixel, mRect.height - 2 * onePixel),
+ nsPoint(mRect.width - onePixel, mRect.height - (2 * onePixel)),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ StrokeLineWithSnapping(
+ nsPoint(mRect.width - (2 * onePixel), 2 * onePixel),
+ nsPoint(mRect.width - (2 * onePixel), mRect.height - onePixel),
+ appUnitsPerDevPixel, *aDrawTarget, color);
+ }
+ }
+ }
+}
+
+void nsTableCellFrame::ProcessBorders(nsTableFrame* aFrame,
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ const nsStyleBorder* borderStyle = StyleBorder();
+ if (aFrame->IsBorderCollapse() || !borderStyle->HasBorder()) {
+ return;
+ }
+
+ if (!GetContentEmpty() ||
+ StyleTableBorder()->mEmptyCells == StyleEmptyCells::Show) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayBorder>(aBuilder, this);
+ }
+}
+
+void nsTableCellFrame::InvalidateFrame(uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
+ if (GetTableFrame()->IsBorderCollapse()) {
+ const bool rebuild = StaticPrefs::layout_display_list_retain_sc();
+ GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(),
+ aDisplayItemKey, rebuild);
+ }
+}
+
+void nsTableCellFrame::InvalidateFrameWithRect(const nsRect& aRect,
+ uint32_t aDisplayItemKey,
+ bool aRebuildDisplayItems) {
+ nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
+ aRebuildDisplayItems);
+ // If we have filters applied that would affects our bounds, then
+ // we get an inactive layer created and this is computed
+ // within FrameLayerBuilder
+ GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey,
+ aRebuildDisplayItems);
+}
+
+bool nsTableCellFrame::ShouldPaintBordersAndBackgrounds() const {
+ // If we're not visible, we don't paint.
+ if (!StyleVisibility()->IsVisible()) {
+ return false;
+ }
+
+ // Consider 'empty-cells', but only in separated borders mode.
+ if (!GetContentEmpty()) {
+ return true;
+ }
+
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse()) {
+ return true;
+ }
+
+ return StyleTableBorder()->mEmptyCells == StyleEmptyCells::Show;
+}
+
+bool nsTableCellFrame::ShouldPaintBackground(nsDisplayListBuilder* aBuilder) {
+ return ShouldPaintBordersAndBackgrounds();
+}
+
+LogicalSides nsTableCellFrame::GetLogicalSkipSides() const {
+ LogicalSides skip(mWritingMode);
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return skip;
+ }
+
+ if (GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+
+/* virtual */
+nsMargin nsTableCellFrame::GetBorderOverflow() { return nsMargin(0, 0, 0, 0); }
+
+// Align the cell's child frame within the cell
+
+void nsTableCellFrame::BlockDirAlignChild(WritingMode aWM, nscoord aMaxAscent) {
+ /* It's the 'border-collapse' on the table that matters */
+ const LogicalMargin border = GetLogicalUsedBorder(GetWritingMode())
+ .ApplySkipSides(GetLogicalSkipSides())
+ .ConvertTo(aWM, GetWritingMode());
+
+ nscoord bStartInset = border.BStart(aWM);
+ nscoord bEndInset = border.BEnd(aWM);
+
+ nscoord bSize = BSize(aWM);
+ nsIFrame* firstKid = mFrames.FirstChild();
+ nsSize containerSize = mRect.Size();
+ NS_ASSERTION(firstKid,
+ "Frame construction error, a table cell always has "
+ "an inner cell frame");
+ LogicalRect kidRect = firstKid->GetLogicalRect(aWM, containerSize);
+ nscoord childBSize = kidRect.BSize(aWM);
+
+ // Vertically align the child
+ nscoord kidBStart = 0;
+ switch (GetVerticalAlign()) {
+ case StyleVerticalAlignKeyword::Baseline:
+ if (!GetContentEmpty()) {
+ // Align the baselines of the child frame with the baselines of
+ // other children in the same row which have 'vertical-align: baseline'
+ kidBStart = bStartInset + aMaxAscent - GetCellBaseline();
+ break;
+ }
+ // Empty cells don't participate in baseline alignment -
+ // fallback to start alignment.
+ [[fallthrough]];
+ case StyleVerticalAlignKeyword::Top:
+ // Align the top of the child frame with the top of the content area,
+ kidBStart = bStartInset;
+ break;
+
+ case StyleVerticalAlignKeyword::Bottom:
+ // Align the bottom of the child frame with the bottom of the content
+ // area,
+ kidBStart = bSize - childBSize - bEndInset;
+ break;
+
+ default:
+ case StyleVerticalAlignKeyword::Middle:
+ // Align the middle of the child frame with the middle of the content
+ // area,
+ kidBStart = (bSize - childBSize - bEndInset + bStartInset) / 2;
+ }
+ // If the content is larger than the cell bsize, align from bStartInset
+ // (cell's content-box bstart edge).
+ kidBStart = std::max(bStartInset, kidBStart);
+
+ if (kidBStart != kidRect.BStart(aWM)) {
+ // Invalidate at the old position first
+ firstKid->InvalidateFrameSubtree();
+ }
+
+ firstKid->SetPosition(aWM, LogicalPoint(aWM, kidRect.IStart(aWM), kidBStart),
+ containerSize);
+ ReflowOutput desiredSize(aWM);
+ desiredSize.SetSize(aWM, GetLogicalSize(aWM));
+
+ nsRect overflow(nsPoint(0, 0), GetSize());
+ overflow.Inflate(GetBorderOverflow());
+ desiredSize.mOverflowAreas.SetAllTo(overflow);
+ ConsiderChildOverflow(desiredSize.mOverflowAreas, firstKid);
+ FinishAndStoreOverflow(&desiredSize);
+ if (kidBStart != kidRect.BStart(aWM)) {
+ // Make sure any child views are correctly positioned. We know the inner
+ // table cell won't have a view
+ nsContainerFrame::PositionChildViews(firstKid);
+
+ // Invalidate new overflow rect
+ firstKid->InvalidateFrameSubtree();
+ }
+ if (HasView()) {
+ nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this, GetView(),
+ desiredSize.InkOverflow(),
+ ReflowChildFlags::Default);
+ }
+}
+
+bool nsTableCellFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
+ nsRect bounds(nsPoint(0, 0), GetSize());
+ bounds.Inflate(GetBorderOverflow());
+
+ aOverflowAreas.UnionAllWith(bounds);
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+// Per CSS 2.1, we map 'sub', 'super', 'text-top', 'text-bottom',
+// length, percentage, and calc() values to 'baseline'.
+StyleVerticalAlignKeyword nsTableCellFrame::GetVerticalAlign() const {
+ const StyleVerticalAlign& verticalAlign = StyleDisplay()->mVerticalAlign;
+ if (verticalAlign.IsKeyword()) {
+ auto value = verticalAlign.AsKeyword();
+ if (value == StyleVerticalAlignKeyword::Top ||
+ value == StyleVerticalAlignKeyword::Middle ||
+ value == StyleVerticalAlignKeyword::Bottom) {
+ return value;
+ }
+ }
+ return StyleVerticalAlignKeyword::Baseline;
+}
+
+static bool CellHasVisibleContent(nsTableFrame* aTableFrame,
+ nsTableCellFrame* aCell) {
+ // see http://www.w3.org/TR/CSS21/tables.html#empty-cells
+ nsIFrame* content = aCell->CellContentFrame();
+ if (content->GetContentRect().Height() > 0) {
+ return true;
+ }
+ if (aTableFrame->IsBorderCollapse()) {
+ return true;
+ }
+ for (nsIFrame* innerFrame : content->PrincipalChildList()) {
+ LayoutFrameType frameType = innerFrame->Type();
+ if (LayoutFrameType::Text == frameType) {
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(innerFrame);
+ if (textFrame->HasNoncollapsedCharacters()) {
+ return true;
+ }
+ } else if (LayoutFrameType::Placeholder != frameType) {
+ return true;
+ } else if (nsLayoutUtils::GetFloatFromPlaceholder(innerFrame)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsIFrame* nsTableCellFrame::CellContentFrame() const {
+ nsIFrame* inner = mFrames.FirstChild();
+ if (nsIScrollableFrame* sf = do_QueryFrame(inner)) {
+ return sf->GetScrolledFrame();
+ }
+ return inner;
+}
+
+nscoord nsTableCellFrame::GetCellBaseline() const {
+ // Ignore the position of the inner frame relative to the cell frame
+ // since we want the position as though the inner were top-aligned.
+ nsIFrame* inner = mFrames.FirstChild();
+ const auto wm = GetWritingMode();
+ nscoord result;
+ if (!StyleDisplay()->IsContainLayout() &&
+ nsLayoutUtils::GetFirstLineBaseline(wm, inner, &result)) {
+ // `result` already includes the padding-start from the inner frame.
+ return result + GetLogicalUsedBorder(wm).BStart(wm);
+ }
+ return CellContentFrame()->ContentBSize(wm) +
+ GetLogicalUsedBorderAndPadding(wm).BStart(wm);
+}
+
+int32_t nsTableCellFrame::GetRowSpan() {
+ int32_t rowSpan = 1;
+
+ // Don't look at the content's rowspan if we're a pseudo cell
+ if (!Style()->IsPseudoOrAnonBox()) {
+ dom::Element* elem = mContent->AsElement();
+ const nsAttrValue* attr = elem->GetParsedAttr(nsGkAtoms::rowspan);
+ // Note that we don't need to check the tag name, because only table cells
+ // (including MathML <mtd>) and table headers parse the "rowspan" attribute
+ // into an integer.
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ rowSpan = attr->GetIntegerValue();
+ }
+ }
+ return rowSpan;
+}
+
+int32_t nsTableCellFrame::GetColSpan() {
+ int32_t colSpan = 1;
+
+ // Don't look at the content's colspan if we're a pseudo cell
+ if (!Style()->IsPseudoOrAnonBox()) {
+ dom::Element* elem = mContent->AsElement();
+ const nsAttrValue* attr = elem->GetParsedAttr(
+ MOZ_UNLIKELY(elem->IsMathMLElement()) ? nsGkAtoms::columnspan_
+ : nsGkAtoms::colspan);
+ // Note that we don't need to check the tag name, because only table cells
+ // (including MathML <mtd>) and table headers parse the "colspan" attribute
+ // into an integer.
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ colSpan = attr->GetIntegerValue();
+ }
+ }
+ return colSpan;
+}
+
+nsIScrollableFrame* nsTableCellFrame::GetScrollTargetFrame() const {
+ return do_QueryFrame(mFrames.FirstChild());
+}
+
+/* virtual */
+nscoord nsTableCellFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result = 0;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+
+ nsIFrame* inner = mFrames.FirstChild();
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner,
+ IntrinsicISizeType::MinISize,
+ nsLayoutUtils::IGNORE_PADDING);
+ return result;
+}
+
+/* virtual */
+nscoord nsTableCellFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result = 0;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+
+ nsIFrame* inner = mFrames.FirstChild();
+ result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner,
+ IntrinsicISizeType::PrefISize,
+ nsLayoutUtils::IGNORE_PADDING);
+ return result;
+}
+
+/* virtual */ nsIFrame::IntrinsicSizeOffsetData
+nsTableCellFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis) {
+ IntrinsicSizeOffsetData result =
+ nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis);
+
+ result.margin = 0;
+
+ WritingMode wm = GetWritingMode();
+ result.border = GetBorderWidth(wm).IStartEnd(wm);
+
+ return result;
+}
+
+#ifdef DEBUG
+# define PROBABLY_TOO_LARGE 1000000
+static void DebugCheckChildSize(nsIFrame* aChild, ReflowOutput& aMet) {
+ WritingMode wm = aMet.GetWritingMode();
+ if ((aMet.ISize(wm) < 0) || (aMet.ISize(wm) > PROBABLY_TOO_LARGE)) {
+ printf("WARNING: cell content %p has large inline size %d \n",
+ static_cast<void*>(aChild), int32_t(aMet.ISize(wm)));
+ }
+}
+#endif
+
+// the computed bsize for the cell, which descendants use for percent bsize
+// calculations it is the bsize (minus border, padding) of the cell's first in
+// flow during its final reflow without an unconstrained bsize.
+static nscoord CalcUnpaginatedBSize(nsTableCellFrame& aCellFrame,
+ nsTableFrame& aTableFrame,
+ nscoord aBlockDirBorderPadding) {
+ const nsTableCellFrame* firstCellInFlow =
+ static_cast<nsTableCellFrame*>(aCellFrame.FirstInFlow());
+ nsTableFrame* firstTableInFlow =
+ static_cast<nsTableFrame*>(aTableFrame.FirstInFlow());
+ nsTableRowFrame* row =
+ static_cast<nsTableRowFrame*>(firstCellInFlow->GetParent());
+ nsTableRowGroupFrame* firstRGInFlow =
+ static_cast<nsTableRowGroupFrame*>(row->GetParent());
+
+ uint32_t rowIndex = firstCellInFlow->RowIndex();
+ int32_t rowSpan = aTableFrame.GetEffectiveRowSpan(*firstCellInFlow);
+
+ nscoord computedBSize =
+ firstTableInFlow->GetRowSpacing(rowIndex, rowIndex + rowSpan - 1);
+ computedBSize -= aBlockDirBorderPadding;
+ uint32_t rowX;
+ for (row = firstRGInFlow->GetFirstRow(), rowX = 0; row;
+ row = row->GetNextRow(), rowX++) {
+ if (rowX > rowIndex + rowSpan - 1) {
+ break;
+ } else if (rowX >= rowIndex) {
+ computedBSize += row->GetUnpaginatedBSize();
+ }
+ }
+ return computedBSize;
+}
+
+void nsTableCellFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsTableCellFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) {
+ FirstInFlow()->AddStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW);
+ }
+
+ // see if a special bsize reflow needs to occur due to having a pct height
+ nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput);
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize availSize = aReflowInput.AvailableSize();
+
+ // @note |this| frame applies borders but not any padding. Our anonymous
+ // inner frame applies the padding (but not borders).
+ LogicalMargin border = GetBorderWidth(wm);
+
+ ReflowOutput kidSize(wm);
+ SetPriorAvailISize(aReflowInput.AvailableISize());
+ nsIFrame* firstKid = mFrames.FirstChild();
+ NS_ASSERTION(
+ firstKid,
+ "Frame construction error, a table cell always has an inner cell frame");
+ nsTableFrame* tableFrame = GetTableFrame();
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow || aPresContext->IsPaginated()) {
+ // Here, we're changing our own reflow input, so we need to account for our
+ // padding, even though we don't apply it anywhere else, to get the correct
+ // percentage resolution on children.
+ const LogicalMargin bp = border + aReflowInput.ComputedLogicalPadding(wm);
+ if (aReflowInput.mFlags.mSpecialBSizeReflow) {
+ const_cast<ReflowInput&>(aReflowInput)
+ .SetComputedBSize(BSize(wm) - bp.BStartEnd(wm));
+ DISPLAY_REFLOW_CHANGE();
+ } else {
+ const nscoord computedUnpaginatedBSize =
+ CalcUnpaginatedBSize(*this, *tableFrame, bp.BStartEnd(wm));
+ if (computedUnpaginatedBSize > 0) {
+ const_cast<ReflowInput&>(aReflowInput)
+ .SetComputedBSize(computedUnpaginatedBSize);
+ DISPLAY_REFLOW_CHANGE();
+ }
+ }
+ }
+
+ // We need to apply the skip sides for current fragmentainer's border after
+ // we finish calculating the special block-size or unpaginated block-size to
+ // prevent the skip sides from affecting the results.
+ //
+ // We assume we are the last fragment by using
+ // PreReflowBlockLevelLogicalSkipSides(), i.e. the block-end border and
+ // padding is not skipped.
+ border.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides());
+
+ availSize.ISize(wm) -= border.IStartEnd(wm);
+
+ // If we have a constrained available block-size, shrink it by subtracting our
+ // block-direction border and padding for our children.
+ if (NS_UNCONSTRAINEDSIZE != availSize.BSize(wm)) {
+ availSize.BSize(wm) -= border.BStart(wm);
+
+ if (aReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone) {
+ // We have box-decoration-break:clone. Subtract block-end border from the
+ // available block-size as well.
+ availSize.BSize(wm) -= border.BEnd(wm);
+ }
+ }
+
+ // Available block-size can became negative after subtracting block-direction
+ // border and padding. Per spec, to guarantee progress, fragmentainers are
+ // assumed to have a minimum block size of 1px regardless of their used size.
+ // https://drafts.csswg.org/css-break/#breaking-rules
+ availSize.BSize(wm) =
+ std::max(availSize.BSize(wm), nsPresContext::CSSPixelsToAppUnits(1));
+
+ WritingMode kidWM = firstKid->GetWritingMode();
+ ReflowInput kidReflowInput(aPresContext, aReflowInput, firstKid,
+ availSize.ConvertTo(kidWM, wm), Nothing(),
+ ReflowInput::InitFlag::CallerWillInit);
+ // Override computed padding, in case it's percentage padding
+ {
+ const auto padding = aReflowInput.ComputedLogicalPadding(kidWM);
+ kidReflowInput.Init(aPresContext, Nothing(), Nothing(), Some(padding));
+ if (firstKid->IsScrollFrame()) {
+ // Propagate explicit block sizes to our inner frame, if it's a scroll
+ // frame.
+ // Table cells don't respect box-sizing, so we need to remove the
+ // padding, so that the scroll-frame sizes properly (since the
+ // scrollbars also add to the padding area).
+ auto ToScrolledBSize = [&](const nscoord aBSize) {
+ return std::max(0, aBSize - padding.BStartEnd(kidWM));
+ };
+ if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
+ kidReflowInput.SetComputedBSize(
+ ToScrolledBSize(aReflowInput.ComputedBSize()));
+ }
+ if (aReflowInput.ComputedMinBSize() > 0) {
+ kidReflowInput.SetComputedMinBSize(
+ ToScrolledBSize(aReflowInput.ComputedMinBSize()));
+ }
+ if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
+ kidReflowInput.SetComputedMaxBSize(
+ ToScrolledBSize(aReflowInput.ComputedMaxBSize()));
+ }
+ }
+ }
+
+ // Don't be a percent height observer if we're in the middle of
+ // special-bsize reflow, in case we get an accidental NotifyPercentBSize()
+ // call (which we shouldn't honor during special-bsize reflow)
+ if (!aReflowInput.mFlags.mSpecialBSizeReflow) {
+ // mPercentBSizeObserver is for children of cells in quirks mode,
+ // but only those than are tables in standards mode. NeedsToObserve
+ // will determine how far this is propagated to descendants.
+ kidReflowInput.mPercentBSizeObserver = this;
+ }
+ // Don't propagate special bsize reflow input to our kids
+ kidReflowInput.mFlags.mSpecialBSizeReflow = false;
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow ||
+ FirstInFlow()->HasAnyStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) {
+ // We need to force the kid to have mBResize set if we've had a
+ // special reflow in the past, since the non-special reflow needs to
+ // resize back to what it was without the special bsize reflow.
+ kidReflowInput.SetBResize(true);
+ }
+
+ nsSize containerSize = aReflowInput.ComputedSizeAsContainerIfConstrained();
+
+ const LogicalPoint kidOrigin = border.StartOffset(wm);
+ const nsRect origRect = firstKid->GetRect();
+ const nsRect origInkOverflow = firstKid->InkOverflowRect();
+ const bool firstReflow = firstKid->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
+
+ ReflowChild(firstKid, aPresContext, kidSize, kidReflowInput, wm, kidOrigin,
+ containerSize, ReflowChildFlags::Default, aStatus);
+ if (aStatus.IsOverflowIncomplete()) {
+ // Don't pass OVERFLOW_INCOMPLETE through tables until they can actually
+ // handle it
+ // XXX should paginate overflow as overflow, but not in this patch (bug
+ // 379349)
+ aStatus.SetIncomplete();
+ NS_WARNING(nsPrintfCString("Set table cell incomplete %p", this).get());
+ }
+
+ // XXXbz is this invalidate actually needed, really?
+ if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
+ InvalidateFrameSubtree();
+ }
+
+#ifdef DEBUG
+ DebugCheckChildSize(firstKid, kidSize);
+#endif
+
+ // Place the child
+ FinishReflowChild(firstKid, aPresContext, kidSize, &kidReflowInput, wm,
+ kidOrigin, containerSize, ReflowChildFlags::Default);
+
+ {
+ nsIFrame* prevInFlow = GetPrevInFlow();
+ const bool isEmpty =
+ prevInFlow
+ ? static_cast<nsTableCellFrame*>(prevInFlow)->GetContentEmpty()
+ : !CellHasVisibleContent(tableFrame, this);
+ SetContentEmpty(isEmpty);
+ }
+
+ if (tableFrame->IsBorderCollapse()) {
+ nsTableFrame::InvalidateTableFrame(firstKid, origRect, origInkOverflow,
+ firstReflow);
+ }
+ // first, compute the bsize which can be set w/o being restricted by
+ // available bsize
+ LogicalSize cellSize(wm);
+ cellSize.BSize(wm) = kidSize.BSize(wm);
+
+ if (NS_UNCONSTRAINEDSIZE != cellSize.BSize(wm)) {
+ cellSize.BSize(wm) += border.BStart(wm);
+
+ if (aStatus.IsComplete() ||
+ aReflowInput.mStyleBorder->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone) {
+ cellSize.BSize(wm) += border.BEnd(wm);
+ }
+ }
+
+ // next determine the cell's isize. At this point, we've factored in the
+ // cell's style attributes.
+ cellSize.ISize(wm) = kidSize.ISize(wm);
+
+ // factor in border (and disregard padding, which is handled by our child).
+ if (NS_UNCONSTRAINEDSIZE != cellSize.ISize(wm)) {
+ cellSize.ISize(wm) += border.IStartEnd(wm);
+ }
+
+ // set the cell's desired size and max element size
+ aDesiredSize.SetSize(wm, cellSize);
+
+ // the overflow area will be computed when BlockDirAlignChild() gets called
+
+ if (aReflowInput.mFlags.mSpecialBSizeReflow &&
+ NS_UNCONSTRAINEDSIZE == aReflowInput.AvailableBSize()) {
+ aDesiredSize.BSize(wm) = BSize(wm);
+ }
+
+ // If our parent is in initial reflow, it'll handle invalidating our
+ // entire overflow rect.
+ if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW) &&
+ nsSize(aDesiredSize.Width(), aDesiredSize.Height()) != mRect.Size()) {
+ InvalidateFrame();
+ }
+
+ // remember the desired size for this reflow
+ SetDesiredSize(aDesiredSize);
+
+ // Any absolutely-positioned children will get reflowed in
+ // nsIFrame::FixupPositionedTableParts in another pass, so propagate our
+ // dirtiness to them before our parent clears our dirty bits.
+ PushDirtyBitToAbsoluteFrames();
+}
+
+/* ----- global methods ----- */
+
+NS_QUERYFRAME_HEAD(nsTableCellFrame)
+ NS_QUERYFRAME_ENTRY(nsTableCellFrame)
+ NS_QUERYFRAME_ENTRY(nsITableCellLayout)
+ NS_QUERYFRAME_ENTRY(nsIPercentBSizeObserver)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsTableCellFrame::AccessibleType() {
+ return a11y::eHTMLTableCellType;
+}
+#endif
+
+/* This is primarily for editor access via nsITableLayout */
+NS_IMETHODIMP
+nsTableCellFrame::GetCellIndexes(int32_t& aRowIndex, int32_t& aColIndex) {
+ aRowIndex = RowIndex();
+ aColIndex = mColIndex;
+ return NS_OK;
+}
+
+nsTableCellFrame* NS_NewTableCellFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle,
+ nsTableFrame* aTableFrame) {
+ if (aTableFrame->IsBorderCollapse())
+ return new (aPresShell) nsBCTableCellFrame(aStyle, aTableFrame);
+ else
+ return new (aPresShell) nsTableCellFrame(aStyle, aTableFrame);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsBCTableCellFrame)
+
+LogicalMargin nsTableCellFrame::GetBorderWidth(WritingMode aWM) const {
+ return LogicalMargin(aWM, StyleBorder()->GetComputedBorder());
+}
+
+void nsTableCellFrame::AppendDirectlyOwnedAnonBoxes(
+ nsTArray<OwnedAnonBox>& aResult) {
+ nsIFrame* kid = mFrames.FirstChild();
+ MOZ_ASSERT(kid && !kid->GetNextSibling(),
+ "Table cells should have just one child");
+ aResult.AppendElement(OwnedAnonBox(kid));
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsTableCellFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"TableCell"_ns, aResult);
+}
+#endif
+
+// nsBCTableCellFrame
+
+nsBCTableCellFrame::nsBCTableCellFrame(ComputedStyle* aStyle,
+ nsTableFrame* aTableFrame)
+ : nsTableCellFrame(aStyle, aTableFrame, kClassID) {
+ mBStartBorder = mIEndBorder = mBEndBorder = mIStartBorder = 0;
+}
+
+nsBCTableCellFrame::~nsBCTableCellFrame() = default;
+
+/* virtual */
+nsMargin nsBCTableCellFrame::GetUsedBorder() const {
+ WritingMode wm = GetWritingMode();
+ return GetBorderWidth(wm).GetPhysicalMargin(wm);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsBCTableCellFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"BCTableCell"_ns, aResult);
+}
+#endif
+
+LogicalMargin nsBCTableCellFrame::GetBorderWidth(WritingMode aWM) const {
+ int32_t d2a = PresContext()->AppUnitsPerDevPixel();
+ return LogicalMargin(aWM, BC_BORDER_END_HALF_COORD(d2a, mBStartBorder),
+ BC_BORDER_START_HALF_COORD(d2a, mIEndBorder),
+ BC_BORDER_START_HALF_COORD(d2a, mBEndBorder),
+ BC_BORDER_END_HALF_COORD(d2a, mIStartBorder));
+}
+
+BCPixelSize nsBCTableCellFrame::GetBorderWidth(LogicalSide aSide) const {
+ switch (aSide) {
+ case eLogicalSideBStart:
+ return BC_BORDER_END_HALF(mBStartBorder);
+ case eLogicalSideIEnd:
+ return BC_BORDER_START_HALF(mIEndBorder);
+ case eLogicalSideBEnd:
+ return BC_BORDER_START_HALF(mBEndBorder);
+ default:
+ return BC_BORDER_END_HALF(mIStartBorder);
+ }
+}
+
+void nsBCTableCellFrame::SetBorderWidth(LogicalSide aSide, BCPixelSize aValue) {
+ switch (aSide) {
+ case eLogicalSideBStart:
+ mBStartBorder = aValue;
+ break;
+ case eLogicalSideIEnd:
+ mIEndBorder = aValue;
+ break;
+ case eLogicalSideBEnd:
+ mBEndBorder = aValue;
+ break;
+ default:
+ mIStartBorder = aValue;
+ }
+}
+
+/* virtual */
+nsMargin nsBCTableCellFrame::GetBorderOverflow() {
+ WritingMode wm = GetWritingMode();
+ int32_t d2a = PresContext()->AppUnitsPerDevPixel();
+ LogicalMargin halfBorder(wm, BC_BORDER_START_HALF_COORD(d2a, mBStartBorder),
+ BC_BORDER_END_HALF_COORD(d2a, mIEndBorder),
+ BC_BORDER_END_HALF_COORD(d2a, mBEndBorder),
+ BC_BORDER_START_HALF_COORD(d2a, mIStartBorder));
+ return halfBorder.GetPhysicalMargin(wm);
+}
+
+namespace mozilla {
+
+class nsDisplayTableCellSelection final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayTableCellSelection(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayTableCellSelection);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTableCellSelection)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override {
+ static_cast<nsTableCellFrame*>(mFrame)->DecorateForSelection(
+ aCtx->GetDrawTarget(), ToReferenceFrame());
+ }
+ NS_DISPLAY_DECL_NAME("TableCellSelection", TYPE_TABLE_CELL_SELECTION)
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override {
+ RefPtr<nsFrameSelection> frameSelection =
+ mFrame->PresShell()->FrameSelection();
+ return !frameSelection->IsInTableSelectionMode();
+ }
+};
+
+} // namespace mozilla
+
+void nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame");
+ if (ShouldPaintBordersAndBackgrounds()) {
+ // display outset box-shadows if we need to.
+ bool hasBoxShadow = !StyleEffects()->mBoxShadow.IsEmpty();
+ if (hasBoxShadow) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowOuter>(
+ aBuilder, this);
+ }
+
+ nsRect bgRect = GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
+ nsRect bgRectInsideBorder = bgRect;
+
+ // If we're doing collapsed borders, and this element forms a new stacking
+ // context or has position:relative (which paints as though it did), inset
+ // the background rect so that we don't overpaint the inset part of our
+ // borders.
+ nsTableFrame* tableFrame = GetTableFrame();
+ if (tableFrame->IsBorderCollapse() &&
+ (IsStackingContext() ||
+ StyleDisplay()->mPosition == StylePositionProperty::Relative)) {
+ bgRectInsideBorder.Deflate(GetUsedBorder());
+ }
+
+ // display background if we need to.
+ const AppendedBackgroundType result =
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, this, bgRectInsideBorder, aLists.BorderBackground(), true,
+ bgRect);
+ if (result == AppendedBackgroundType::None) {
+ aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
+ aLists.BorderBackground());
+ }
+
+ // display inset box-shadows if we need to.
+ if (hasBoxShadow) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayBoxShadowInner>(
+ aBuilder, this);
+ }
+
+ // display borders if we need to
+ ProcessBorders(tableFrame, aBuilder, aLists);
+
+ // and display the selection border if we need to
+ if (IsSelected()) {
+ aLists.BorderBackground()->AppendNewToTop<nsDisplayTableCellSelection>(
+ aBuilder, this);
+ }
+
+ // This can be null if display list building initiated in the middle
+ // of the table, which can happen with background-clip:text and
+ // -moz-element.
+ nsDisplayTableBackgroundSet* backgrounds =
+ aBuilder->GetTableBackgroundSet();
+ if (backgrounds) {
+ // Compute bgRect relative to reference frame, but using the
+ // normal (without position:relative offsets) positions for the
+ // cell, row and row group.
+ bgRect = GetRectRelativeToSelf() + GetNormalPosition();
+
+ nsTableRowFrame* row = GetTableRowFrame();
+ bgRect += row->GetNormalPosition();
+
+ nsTableRowGroupFrame* rowGroup = row->GetTableRowGroupFrame();
+ bgRect += rowGroup->GetNormalPosition();
+
+ bgRect += backgrounds->TableToReferenceFrame();
+
+ DisplayListClipState::AutoSaveRestore clipState(aBuilder);
+ nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
+ aBuilder);
+ if (IsStackingContext() || row->IsStackingContext() ||
+ rowGroup->IsStackingContext() || tableFrame->IsStackingContext()) {
+ // The col/colgroup items we create below will be inserted directly into
+ // the BorderBackgrounds list of the table frame. That means that
+ // they'll be moved *outside* of any wrapper items created for any
+ // frames between this table cell frame and the table wrapper frame, and
+ // will not participate in those frames's opacity / transform / filter /
+ // mask effects. If one of those frames is a stacking context, then we
+ // may have one or more of those wrapper items, and one of them may have
+ // captured a clip. In order to ensure correct clipping and scrolling of
+ // the col/colgroup items, restore the clip and ASR that we observed
+ // when we entered the table frame. If that frame is a stacking context
+ // but doesn't have any clip capturing wrapper items, then we'll
+ // double-apply the clip. That's ok.
+ clipState.SetClipChainForContainingBlockDescendants(
+ backgrounds->GetTableClipChain());
+ asrSetter.SetCurrentActiveScrolledRoot(backgrounds->GetTableASR());
+ }
+
+ // Create backgrounds items as needed for the column and column
+ // group that this cell occupies.
+ nsTableColFrame* col = backgrounds->GetColForIndex(ColIndex());
+ nsTableColGroupFrame* colGroup = col->GetTableColGroupFrame();
+
+ Maybe<nsDisplayListBuilder::AutoBuildingDisplayList> buildingForColGroup;
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, colGroup, bgRect, backgrounds->ColGroupBackgrounds(), false,
+ colGroup->GetRect() + backgrounds->TableToReferenceFrame(), this,
+ &buildingForColGroup);
+
+ Maybe<nsDisplayListBuilder::AutoBuildingDisplayList> buildingForCol;
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, col, bgRect, backgrounds->ColBackgrounds(), false,
+ col->GetRect() + colGroup->GetPosition() +
+ backgrounds->TableToReferenceFrame(),
+ this, &buildingForCol);
+ }
+ }
+
+ // the 'empty-cells' property has no effect on 'outline'
+ DisplayOutline(aBuilder, aLists);
+
+ nsIFrame* kid = mFrames.FirstChild();
+ NS_ASSERTION(kid && !kid->GetNextSibling(),
+ "Table cells should have just one child");
+ // The child's background will go in our BorderBackground() list.
+ // This isn't a problem since it won't have a real background except for
+ // event handling. We do not call BuildDisplayListForNonBlockChildren
+ // because that/ would put the child's background in the Content() list
+ // which isn't right (e.g., would end up on top of our child floats for
+ // event handling).
+ BuildDisplayListForChild(aBuilder, kid, aLists);
+}