1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsTableCellFrame.h"
8 #include "gfxContext.h"
10 #include "mozilla/ComputedStyle.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/StaticPrefs_layout.h"
13 #include "mozilla/gfx/2D.h"
14 #include "mozilla/gfx/Helpers.h"
15 #include "nsTableFrame.h"
16 #include "nsTableColFrame.h"
17 #include "nsTableRowFrame.h"
18 #include "nsTableRowGroupFrame.h"
19 #include "nsStyleConsts.h"
20 #include "nsPresContext.h"
21 #include "nsCSSRendering.h"
22 #include "nsIContent.h"
24 #include "nsIFrameInlines.h"
25 #include "nsGenericHTMLElement.h"
26 #include "nsAttrValueInlines.h"
27 #include "nsHTMLParts.h"
28 #include "nsGkAtoms.h"
29 #include "nsDisplayList.h"
30 #include "nsLayoutUtils.h"
31 #include "nsTextFrame.h"
34 // TABLECELL SELECTION
35 #include "nsFrameSelection.h"
36 #include "mozilla/LookAndFeel.h"
39 # include "nsAccessibilityService.h"
42 using namespace mozilla
;
43 using namespace mozilla::gfx
;
44 using namespace mozilla::image
;
46 nsTableCellFrame::nsTableCellFrame(ComputedStyle
* aStyle
,
47 nsTableFrame
* aTableFrame
, ClassID aID
)
48 : nsContainerFrame(aStyle
, aTableFrame
->PresContext(), aID
),
49 mDesiredSize(aTableFrame
->GetWritingMode()) {
53 SetContentEmpty(false);
56 nsTableCellFrame::~nsTableCellFrame() = default;
58 NS_IMPL_FRAMEARENA_HELPERS(nsTableCellFrame
)
60 void nsTableCellFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
61 nsIFrame
* aPrevInFlow
) {
62 // Let the base class do its initialization
63 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
65 if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER
)) {
66 AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
);
70 // Set the column index
71 nsTableCellFrame
* cellFrame
= (nsTableCellFrame
*)aPrevInFlow
;
72 uint32_t colIndex
= cellFrame
->ColIndex();
73 SetColIndex(colIndex
);
75 // Although the spec doesn't say that writing-mode is not applied to
76 // table-cells, we still override style value here because we want to
77 // make effective writing mode of table structure frames consistent
78 // within a table. The content inside table cells is reflowed by an
79 // anonymous block, hence their writing mode is not affected.
80 mWritingMode
= GetTableFrame()->GetWritingMode();
84 void nsTableCellFrame::Destroy(DestroyContext
& aContext
) {
85 nsTableFrame::MaybeUnregisterPositionedTablePart(this);
86 nsContainerFrame::Destroy(aContext
);
89 // nsIPercentBSizeObserver methods
91 void nsTableCellFrame::NotifyPercentBSize(const ReflowInput
& aReflowInput
) {
92 // ReflowInput ensures the mCBReflowInput of blocks inside a
93 // cell is the cell frame, not the inner-cell block, and that the
94 // containing block of an inner table is the containing block of its
96 // XXXldb Given the now-stricter |NeedsToObserve|, many if not all of
97 // these tests are probably unnecessary.
99 // Maybe the cell reflow input; we sure if we're inside the |if|.
100 const ReflowInput
* cellRI
= aReflowInput
.mCBReflowInput
;
102 if (cellRI
&& cellRI
->mFrame
== this &&
103 (cellRI
->ComputedBSize() == NS_UNCONSTRAINEDSIZE
||
104 cellRI
->ComputedBSize() == 0)) { // XXXldb Why 0?
105 // This is a percentage bsize on a frame whose percentage bsizes
106 // are based on the bsize of the cell, since its containing block
107 // is the inner cell frame.
109 // We'll only honor the percent bsize if sibling-cells/ancestors
110 // have specified/pct bsize. (Also, siblings only count for this if
111 // both this cell and the sibling cell span exactly 1 row.)
113 if (nsTableFrame::AncestorsHaveStyleBSize(*cellRI
) ||
114 (GetTableFrame()->GetEffectiveRowSpan(*this) == 1 &&
115 cellRI
->mParentReflowInput
->mFrame
->HasAnyStateBits(
116 NS_ROW_HAS_CELL_WITH_STYLE_BSIZE
))) {
117 for (const ReflowInput
* rs
= aReflowInput
.mParentReflowInput
;
118 rs
!= cellRI
; rs
= rs
->mParentReflowInput
) {
119 rs
->mFrame
->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE
);
122 nsTableFrame::RequestSpecialBSizeReflow(*cellRI
);
127 // The cell needs to observe its block and things inside its block but nothing
129 bool nsTableCellFrame::NeedsToObserve(const ReflowInput
& aReflowInput
) {
130 const ReflowInput
* rs
= aReflowInput
.mParentReflowInput
;
131 if (!rs
) return false;
132 if (rs
->mFrame
== this) {
133 // We always observe the child block. It will never send any
134 // notifications, but we need this so that the observer gets
135 // propagated to its kids.
138 rs
= rs
->mParentReflowInput
;
143 // We always need to let the percent bsize observer be propagated
144 // from a table wrapper frame to an inner table frame.
145 LayoutFrameType fType
= aReflowInput
.mFrame
->Type();
146 if (fType
== LayoutFrameType::Table
) {
150 // We need the observer to be propagated to all children of the cell
151 // (i.e., children of the child block) in quirks mode, but only to
152 // tables in standards mode.
153 // XXX This may not be true in the case of orthogonal flows within
154 // the cell (bug 1174711 comment 8); we may need to observe isizes
155 // instead of bsizes for orthogonal children.
156 return rs
->mFrame
== this &&
157 (PresContext()->CompatibilityMode() == eCompatibility_NavQuirks
||
158 fType
== LayoutFrameType::TableWrapper
);
161 nsresult
nsTableCellFrame::AttributeChanged(int32_t aNameSpaceID
,
164 // We need to recalculate in this case because of the nowrap quirk in
165 // BasicTableLayoutStrategy
166 if (aNameSpaceID
== kNameSpaceID_None
&& aAttribute
== nsGkAtoms::nowrap
&&
167 PresContext()->CompatibilityMode() == eCompatibility_NavQuirks
) {
168 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors
,
172 if (aAttribute
== nsGkAtoms::rowspan
|| aAttribute
== nsGkAtoms::colspan
) {
173 nsLayoutUtils::PostRestyleEvent(mContent
->AsElement(), RestyleHint
{0},
174 nsChangeHint_UpdateTableCellSpans
);
180 void nsTableCellFrame::DidSetComputedStyle(ComputedStyle
* aOldComputedStyle
) {
181 nsContainerFrame::DidSetComputedStyle(aOldComputedStyle
);
182 nsTableFrame::PositionedTablePartMaybeChanged(this, aOldComputedStyle
);
184 if (!aOldComputedStyle
) {
185 return; // avoid the following on init
189 if (nsAccessibilityService
* accService
= GetAccService()) {
190 if (StyleBorder()->GetComputedBorder() !=
191 aOldComputedStyle
->StyleBorder()->GetComputedBorder()) {
192 // If a table cell's computed border changes, it can change whether or
193 // not its parent table is classified as a layout or data table. We
194 // send a notification here to invalidate the a11y cache on the table
195 // so the next fetch of IsProbablyLayoutTable() is accurate.
196 accService
->TableLayoutGuessMaybeChanged(PresShell(), mContent
);
201 nsTableFrame
* tableFrame
= GetTableFrame();
202 if (tableFrame
->IsBorderCollapse() &&
203 tableFrame
->BCRecalcNeeded(aOldComputedStyle
, Style())) {
204 uint32_t colIndex
= ColIndex();
205 uint32_t rowIndex
= RowIndex();
206 // row span needs to be clamped as we do not create rows in the cellmap
207 // which do not have cells originating in them
208 TableArea
damageArea(colIndex
, rowIndex
, GetColSpan(),
209 std::min(static_cast<uint32_t>(GetRowSpan()),
210 tableFrame
->GetRowCount() - rowIndex
));
211 tableFrame
->AddBCDamageArea(damageArea
);
216 void nsTableCellFrame::AppendFrames(ChildListID aListID
,
217 nsFrameList
&& aFrameList
) {
218 MOZ_CRASH("unsupported operation");
221 void nsTableCellFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
222 const nsLineList::iterator
* aPrevFrameLine
,
223 nsFrameList
&& aFrameList
) {
224 MOZ_CRASH("unsupported operation");
227 void nsTableCellFrame::RemoveFrame(DestroyContext
&, ChildListID
, nsIFrame
*) {
228 MOZ_CRASH("unsupported operation");
232 void nsTableCellFrame::SetColIndex(int32_t aColIndex
) { mColIndex
= aColIndex
; }
235 nsMargin
nsTableCellFrame::GetUsedMargin() const {
236 return nsMargin(0, 0, 0, 0);
239 // ASSURE DIFFERENT COLORS for selection
240 inline nscolor
EnsureDifferentColors(nscolor colorA
, nscolor colorB
) {
241 if (colorA
== colorB
) {
243 res
= NS_RGB(NS_GET_R(colorA
) ^ 0xff, NS_GET_G(colorA
) ^ 0xff,
244 NS_GET_B(colorA
) ^ 0xff);
250 void nsTableCellFrame::DecorateForSelection(DrawTarget
* aDrawTarget
,
252 NS_ASSERTION(IsSelected(), "Should only be called for selected cells");
253 int16_t displaySelection
;
254 nsPresContext
* presContext
= PresContext();
255 displaySelection
= DetermineDisplaySelection();
256 if (displaySelection
) {
257 RefPtr
<nsFrameSelection
> frameSelection
=
258 presContext
->PresShell()->FrameSelection();
260 if (frameSelection
->IsInTableSelectionMode()) {
262 if (displaySelection
== nsISelectionController::SELECTION_DISABLED
) {
263 bordercolor
= NS_RGB(176, 176, 176); // disabled color
265 bordercolor
= LookAndFeel::Color(LookAndFeel::ColorID::Highlight
, this);
267 nscoord threePx
= nsPresContext::CSSPixelsToAppUnits(3);
268 if ((mRect
.width
> threePx
) && (mRect
.height
> threePx
)) {
269 // compare bordercolor to background-color
270 bordercolor
= EnsureDifferentColors(
271 bordercolor
, StyleBackground()->BackgroundColor(this));
273 int32_t appUnitsPerDevPixel
= PresContext()->AppUnitsPerDevPixel();
274 Point devPixelOffset
= NSPointToPoint(aPt
, appUnitsPerDevPixel
);
276 AutoRestoreTransform
autoRestoreTransform(aDrawTarget
);
277 aDrawTarget
->SetTransform(
278 aDrawTarget
->GetTransform().PreTranslate(devPixelOffset
));
280 ColorPattern
color(ToDeviceColor(bordercolor
));
282 nscoord onePixel
= nsPresContext::CSSPixelsToAppUnits(1);
284 StrokeLineWithSnapping(nsPoint(onePixel
, 0), nsPoint(mRect
.width
, 0),
285 appUnitsPerDevPixel
, *aDrawTarget
, color
);
286 StrokeLineWithSnapping(nsPoint(0, onePixel
), nsPoint(0, mRect
.height
),
287 appUnitsPerDevPixel
, *aDrawTarget
, color
);
288 StrokeLineWithSnapping(nsPoint(onePixel
, mRect
.height
),
289 nsPoint(mRect
.width
, mRect
.height
),
290 appUnitsPerDevPixel
, *aDrawTarget
, color
);
291 StrokeLineWithSnapping(nsPoint(mRect
.width
, onePixel
),
292 nsPoint(mRect
.width
, mRect
.height
),
293 appUnitsPerDevPixel
, *aDrawTarget
, color
);
295 nsRect
r(onePixel
, onePixel
, mRect
.width
- onePixel
,
296 mRect
.height
- onePixel
);
298 NSRectToSnappedRect(r
, appUnitsPerDevPixel
, *aDrawTarget
);
299 aDrawTarget
->StrokeRect(devPixelRect
, color
);
301 StrokeLineWithSnapping(
302 nsPoint(2 * onePixel
, mRect
.height
- 2 * onePixel
),
303 nsPoint(mRect
.width
- onePixel
, mRect
.height
- (2 * onePixel
)),
304 appUnitsPerDevPixel
, *aDrawTarget
, color
);
305 StrokeLineWithSnapping(
306 nsPoint(mRect
.width
- (2 * onePixel
), 2 * onePixel
),
307 nsPoint(mRect
.width
- (2 * onePixel
), mRect
.height
- onePixel
),
308 appUnitsPerDevPixel
, *aDrawTarget
, color
);
314 void nsTableCellFrame::ProcessBorders(nsTableFrame
* aFrame
,
315 nsDisplayListBuilder
* aBuilder
,
316 const nsDisplayListSet
& aLists
) {
317 const nsStyleBorder
* borderStyle
= StyleBorder();
318 if (aFrame
->IsBorderCollapse() || !borderStyle
->HasBorder()) {
322 if (!GetContentEmpty() ||
323 StyleTableBorder()->mEmptyCells
== StyleEmptyCells::Show
) {
324 aLists
.BorderBackground()->AppendNewToTop
<nsDisplayBorder
>(aBuilder
, this);
328 void nsTableCellFrame::InvalidateFrame(uint32_t aDisplayItemKey
,
329 bool aRebuildDisplayItems
) {
330 nsIFrame::InvalidateFrame(aDisplayItemKey
, aRebuildDisplayItems
);
331 if (GetTableFrame()->IsBorderCollapse()) {
332 const bool rebuild
= StaticPrefs::layout_display_list_retain_sc();
333 GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(),
334 aDisplayItemKey
, rebuild
);
338 void nsTableCellFrame::InvalidateFrameWithRect(const nsRect
& aRect
,
339 uint32_t aDisplayItemKey
,
340 bool aRebuildDisplayItems
) {
341 nsIFrame::InvalidateFrameWithRect(aRect
, aDisplayItemKey
,
342 aRebuildDisplayItems
);
343 // If we have filters applied that would affects our bounds, then
344 // we get an inactive layer created and this is computed
345 // within FrameLayerBuilder
346 GetParent()->InvalidateFrameWithRect(aRect
+ GetPosition(), aDisplayItemKey
,
347 aRebuildDisplayItems
);
350 bool nsTableCellFrame::ShouldPaintBordersAndBackgrounds() const {
351 // If we're not visible, we don't paint.
352 if (!StyleVisibility()->IsVisible()) {
356 // Consider 'empty-cells', but only in separated borders mode.
357 if (!GetContentEmpty()) {
361 nsTableFrame
* tableFrame
= GetTableFrame();
362 if (tableFrame
->IsBorderCollapse()) {
366 return StyleTableBorder()->mEmptyCells
== StyleEmptyCells::Show
;
369 bool nsTableCellFrame::ShouldPaintBackground(nsDisplayListBuilder
* aBuilder
) {
370 return ShouldPaintBordersAndBackgrounds();
373 LogicalSides
nsTableCellFrame::GetLogicalSkipSides() const {
374 LogicalSides
skip(mWritingMode
);
375 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak
==
376 StyleBoxDecorationBreak::Clone
)) {
380 if (GetPrevInFlow()) {
381 skip
|= eLogicalSideBitsBStart
;
383 if (GetNextInFlow()) {
384 skip
|= eLogicalSideBitsBEnd
;
390 nsMargin
nsTableCellFrame::GetBorderOverflow() { return nsMargin(0, 0, 0, 0); }
392 // Align the cell's child frame within the cell
394 void nsTableCellFrame::BlockDirAlignChild(WritingMode aWM
, nscoord aMaxAscent
) {
395 /* It's the 'border-collapse' on the table that matters */
396 const LogicalMargin borderPadding
=
397 GetLogicalUsedBorderAndPadding(GetWritingMode())
398 .ApplySkipSides(GetLogicalSkipSides())
399 .ConvertTo(aWM
, GetWritingMode());
401 nscoord bStartInset
= borderPadding
.BStart(aWM
);
402 nscoord bEndInset
= borderPadding
.BEnd(aWM
);
404 nscoord bSize
= BSize(aWM
);
405 nsIFrame
* firstKid
= mFrames
.FirstChild();
406 nsSize containerSize
= mRect
.Size();
407 NS_ASSERTION(firstKid
,
408 "Frame construction error, a table cell always has "
409 "an inner cell frame");
410 LogicalRect kidRect
= firstKid
->GetLogicalRect(aWM
, containerSize
);
411 nscoord childBSize
= kidRect
.BSize(aWM
);
413 // Vertically align the child
414 nscoord kidBStart
= 0;
415 switch (GetVerticalAlign()) {
416 case StyleVerticalAlignKeyword::Baseline
:
417 if (!GetContentEmpty()) {
418 // Align the baselines of the child frame with the baselines of
419 // other children in the same row which have 'vertical-align: baseline'
420 kidBStart
= bStartInset
+ aMaxAscent
- GetCellBaseline();
423 // Empty cells don't participate in baseline alignment -
424 // fallback to start alignment.
426 case StyleVerticalAlignKeyword::Top
:
427 // Align the top of the child frame with the top of the content area,
428 kidBStart
= bStartInset
;
431 case StyleVerticalAlignKeyword::Bottom
:
432 // Align the bottom of the child frame with the bottom of the content
434 kidBStart
= bSize
- childBSize
- bEndInset
;
438 case StyleVerticalAlignKeyword::Middle
:
439 // Align the middle of the child frame with the middle of the content
441 kidBStart
= (bSize
- childBSize
- bEndInset
+ bStartInset
) / 2;
443 // If the content is larger than the cell bsize, align from bStartInset
444 // (cell's content-box bstart edge).
445 kidBStart
= std::max(bStartInset
, kidBStart
);
447 if (kidBStart
!= kidRect
.BStart(aWM
)) {
448 // Invalidate at the old position first
449 firstKid
->InvalidateFrameSubtree();
452 firstKid
->SetPosition(aWM
, LogicalPoint(aWM
, kidRect
.IStart(aWM
), kidBStart
),
454 ReflowOutput
desiredSize(aWM
);
455 desiredSize
.SetSize(aWM
, GetLogicalSize(aWM
));
457 nsRect
overflow(nsPoint(0, 0), GetSize());
458 overflow
.Inflate(GetBorderOverflow());
459 desiredSize
.mOverflowAreas
.SetAllTo(overflow
);
460 ConsiderChildOverflow(desiredSize
.mOverflowAreas
, firstKid
);
461 FinishAndStoreOverflow(&desiredSize
);
462 if (kidBStart
!= kidRect
.BStart(aWM
)) {
463 // Make sure any child views are correctly positioned. We know the inner
464 // table cell won't have a view
465 nsContainerFrame::PositionChildViews(firstKid
);
467 // Invalidate new overflow rect
468 firstKid
->InvalidateFrameSubtree();
471 nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this, GetView(),
472 desiredSize
.InkOverflow(),
473 ReflowChildFlags::Default
);
477 bool nsTableCellFrame::ComputeCustomOverflow(OverflowAreas
& aOverflowAreas
) {
478 nsRect
bounds(nsPoint(0, 0), GetSize());
479 bounds
.Inflate(GetBorderOverflow());
481 aOverflowAreas
.UnionAllWith(bounds
);
482 return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas
);
485 // Per CSS 2.1, we map 'sub', 'super', 'text-top', 'text-bottom',
486 // length, percentage, and calc() values to 'baseline'.
487 StyleVerticalAlignKeyword
nsTableCellFrame::GetVerticalAlign() const {
488 const StyleVerticalAlign
& verticalAlign
= StyleDisplay()->mVerticalAlign
;
489 if (verticalAlign
.IsKeyword()) {
490 auto value
= verticalAlign
.AsKeyword();
491 if (value
== StyleVerticalAlignKeyword::Top
||
492 value
== StyleVerticalAlignKeyword::Middle
||
493 value
== StyleVerticalAlignKeyword::Bottom
) {
497 return StyleVerticalAlignKeyword::Baseline
;
500 bool nsTableCellFrame::CellHasVisibleContent(nscoord height
,
501 nsTableFrame
* tableFrame
,
502 nsIFrame
* kidFrame
) {
503 // see http://www.w3.org/TR/CSS21/tables.html#empty-cells
504 if (height
> 0) return true;
505 if (tableFrame
->IsBorderCollapse()) return true;
506 for (nsIFrame
* innerFrame
: kidFrame
->PrincipalChildList()) {
507 LayoutFrameType frameType
= innerFrame
->Type();
508 if (LayoutFrameType::Text
== frameType
) {
509 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(innerFrame
);
510 if (textFrame
->HasNoncollapsedCharacters()) return true;
511 } else if (LayoutFrameType::Placeholder
!= frameType
) {
514 nsIFrame
* floatFrame
= nsLayoutUtils::GetFloatFromPlaceholder(innerFrame
);
515 if (floatFrame
) return true;
521 nscoord
nsTableCellFrame::GetCellBaseline() const {
522 // Ignore the position of the inner frame relative to the cell frame
523 // since we want the position as though the inner were top-aligned.
524 nsIFrame
* inner
= mFrames
.FirstChild();
525 const auto wm
= GetWritingMode();
526 const auto borderPadding
= GetLogicalUsedBorderAndPadding(wm
);
528 if (!StyleDisplay()->IsContainLayout() &&
529 nsLayoutUtils::GetFirstLineBaseline(wm
, inner
, &result
)) {
530 return result
+ borderPadding
.BStart(wm
);
532 const auto logicalSize
= inner
->GetLogicalSize(wm
);
533 // ::-moz-cell-content shouldn't have any border/padding.
534 return logicalSize
.BSize(wm
) + borderPadding
.BStart(wm
);
537 int32_t nsTableCellFrame::GetRowSpan() {
540 // Don't look at the content's rowspan if we're a pseudo cell
541 if (!Style()->IsPseudoOrAnonBox()) {
542 dom::Element
* elem
= mContent
->AsElement();
543 const nsAttrValue
* attr
= elem
->GetParsedAttr(nsGkAtoms::rowspan
);
544 // Note that we don't need to check the tag name, because only table cells
545 // (including MathML <mtd>) and table headers parse the "rowspan" attribute
547 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
548 rowSpan
= attr
->GetIntegerValue();
554 int32_t nsTableCellFrame::GetColSpan() {
557 // Don't look at the content's colspan if we're a pseudo cell
558 if (!Style()->IsPseudoOrAnonBox()) {
559 dom::Element
* elem
= mContent
->AsElement();
560 const nsAttrValue
* attr
= elem
->GetParsedAttr(
561 MOZ_UNLIKELY(elem
->IsMathMLElement()) ? nsGkAtoms::columnspan_
562 : nsGkAtoms::colspan
);
563 // Note that we don't need to check the tag name, because only table cells
564 // (including MathML <mtd>) and table headers parse the "colspan" attribute
566 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
567 colSpan
= attr
->GetIntegerValue();
574 nscoord
nsTableCellFrame::GetMinISize(gfxContext
* aRenderingContext
) {
576 DISPLAY_MIN_INLINE_SIZE(this, result
);
578 nsIFrame
* inner
= mFrames
.FirstChild();
579 result
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
, inner
,
580 IntrinsicISizeType::MinISize
);
585 nscoord
nsTableCellFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
587 DISPLAY_PREF_INLINE_SIZE(this, result
);
589 nsIFrame
* inner
= mFrames
.FirstChild();
590 result
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
, inner
,
591 IntrinsicISizeType::PrefISize
);
595 /* virtual */ nsIFrame::IntrinsicSizeOffsetData
596 nsTableCellFrame::IntrinsicISizeOffsets(nscoord aPercentageBasis
) {
597 IntrinsicSizeOffsetData result
=
598 nsContainerFrame::IntrinsicISizeOffsets(aPercentageBasis
);
602 WritingMode wm
= GetWritingMode();
603 result
.border
= GetBorderWidth(wm
).IStartEnd(wm
);
609 # define PROBABLY_TOO_LARGE 1000000
610 static void DebugCheckChildSize(nsIFrame
* aChild
, ReflowOutput
& aMet
) {
611 WritingMode wm
= aMet
.GetWritingMode();
612 if ((aMet
.ISize(wm
) < 0) || (aMet
.ISize(wm
) > PROBABLY_TOO_LARGE
)) {
613 printf("WARNING: cell content %p has large inline size %d \n",
614 static_cast<void*>(aChild
), int32_t(aMet
.ISize(wm
)));
619 // the computed bsize for the cell, which descendants use for percent bsize
620 // calculations it is the bsize (minus border, padding) of the cell's first in
621 // flow during its final reflow without an unconstrained bsize.
622 static nscoord
CalcUnpaginatedBSize(nsTableCellFrame
& aCellFrame
,
623 nsTableFrame
& aTableFrame
,
624 nscoord aBlockDirBorderPadding
) {
625 const nsTableCellFrame
* firstCellInFlow
=
626 static_cast<nsTableCellFrame
*>(aCellFrame
.FirstInFlow());
627 nsTableFrame
* firstTableInFlow
=
628 static_cast<nsTableFrame
*>(aTableFrame
.FirstInFlow());
629 nsTableRowFrame
* row
=
630 static_cast<nsTableRowFrame
*>(firstCellInFlow
->GetParent());
631 nsTableRowGroupFrame
* firstRGInFlow
=
632 static_cast<nsTableRowGroupFrame
*>(row
->GetParent());
634 uint32_t rowIndex
= firstCellInFlow
->RowIndex();
635 int32_t rowSpan
= aTableFrame
.GetEffectiveRowSpan(*firstCellInFlow
);
637 nscoord computedBSize
=
638 firstTableInFlow
->GetRowSpacing(rowIndex
, rowIndex
+ rowSpan
- 1);
639 computedBSize
-= aBlockDirBorderPadding
;
641 for (row
= firstRGInFlow
->GetFirstRow(), rowX
= 0; row
;
642 row
= row
->GetNextRow(), rowX
++) {
643 if (rowX
> rowIndex
+ rowSpan
- 1) {
645 } else if (rowX
>= rowIndex
) {
646 computedBSize
+= row
->GetUnpaginatedBSize();
649 return computedBSize
;
652 void nsTableCellFrame::Reflow(nsPresContext
* aPresContext
,
653 ReflowOutput
& aDesiredSize
,
654 const ReflowInput
& aReflowInput
,
655 nsReflowStatus
& aStatus
) {
657 DO_GLOBAL_REFLOW_COUNT("nsTableCellFrame");
658 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aDesiredSize
, aStatus
);
659 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
661 if (aReflowInput
.mFlags
.mSpecialBSizeReflow
) {
662 FirstInFlow()->AddStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW
);
665 // see if a special bsize reflow needs to occur due to having a pct height
666 nsTableFrame::CheckRequestSpecialBSizeReflow(aReflowInput
);
668 WritingMode wm
= aReflowInput
.GetWritingMode();
669 LogicalSize availSize
= aReflowInput
.AvailableSize();
671 LogicalMargin borderPadding
=
672 aReflowInput
.ComputedLogicalPadding(wm
) + GetBorderWidth(wm
);
674 ReflowOutput
kidSize(wm
);
675 SetPriorAvailISize(aReflowInput
.AvailableISize());
676 nsIFrame
* firstKid
= mFrames
.FirstChild();
679 "Frame construction error, a table cell always has an inner cell frame");
680 nsTableFrame
* tableFrame
= GetTableFrame();
682 if (aReflowInput
.mFlags
.mSpecialBSizeReflow
) {
683 const_cast<ReflowInput
&>(aReflowInput
)
684 .SetComputedBSize(BSize(wm
) - borderPadding
.BStartEnd(wm
));
685 DISPLAY_REFLOW_CHANGE();
686 } else if (aPresContext
->IsPaginated()) {
687 nscoord computedUnpaginatedBSize
= CalcUnpaginatedBSize(
688 (nsTableCellFrame
&)*this, *tableFrame
, borderPadding
.BStartEnd(wm
));
689 if (computedUnpaginatedBSize
> 0) {
690 const_cast<ReflowInput
&>(aReflowInput
)
691 .SetComputedBSize(computedUnpaginatedBSize
);
692 DISPLAY_REFLOW_CHANGE();
696 // We need to apply the skip sides for current fragmentainer's border and
697 // padding after we finish calculating the special block-size or unpaginated
698 // block-size to prevent the skip sides from affecting the results.
700 // We assume we are the last fragment by using
701 // PreReflowBlockLevelLogicalSkipSides(), i.e. the block-end border and
702 // padding is not skipped.
703 borderPadding
.ApplySkipSides(PreReflowBlockLevelLogicalSkipSides());
705 availSize
.ISize(wm
) -= borderPadding
.IStartEnd(wm
);
707 // If we have a constrained available block-size, shrink it by subtracting our
708 // block-direction border and padding for our children.
709 if (NS_UNCONSTRAINEDSIZE
!= availSize
.BSize(wm
)) {
710 availSize
.BSize(wm
) -= borderPadding
.BStart(wm
);
712 if (aReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
713 StyleBoxDecorationBreak::Clone
) {
714 // We have box-decoration-break:clone. Subtract block-end border and
715 // padding from the available block-size as well.
716 availSize
.BSize(wm
) -= borderPadding
.BEnd(wm
);
720 // Available block-size can became negative after subtracting block-direction
721 // border and padding. Per spec, to guarantee progress, fragmentainers are
722 // assumed to have a minimum block size of 1px regardless of their used size.
723 // https://drafts.csswg.org/css-break/#breaking-rules
724 availSize
.BSize(wm
) =
725 std::max(availSize
.BSize(wm
), nsPresContext::CSSPixelsToAppUnits(1));
727 WritingMode kidWM
= firstKid
->GetWritingMode();
728 ReflowInput
kidReflowInput(aPresContext
, aReflowInput
, firstKid
,
729 availSize
.ConvertTo(kidWM
, wm
));
731 // Don't be a percent height observer if we're in the middle of
732 // special-bsize reflow, in case we get an accidental NotifyPercentBSize()
733 // call (which we shouldn't honor during special-bsize reflow)
734 if (!aReflowInput
.mFlags
.mSpecialBSizeReflow
) {
735 // mPercentBSizeObserver is for children of cells in quirks mode,
736 // but only those than are tables in standards mode. NeedsToObserve
737 // will determine how far this is propagated to descendants.
738 kidReflowInput
.mPercentBSizeObserver
= this;
740 // Don't propagate special bsize reflow input to our kids
741 kidReflowInput
.mFlags
.mSpecialBSizeReflow
= false;
743 if (aReflowInput
.mFlags
.mSpecialBSizeReflow
||
744 FirstInFlow()->HasAnyStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW
)) {
745 // We need to force the kid to have mBResize set if we've had a
746 // special reflow in the past, since the non-special reflow needs to
747 // resize back to what it was without the special bsize reflow.
748 kidReflowInput
.SetBResize(true);
751 nsSize containerSize
= aReflowInput
.ComputedSizeAsContainerIfConstrained();
753 LogicalPoint
kidOrigin(wm
, borderPadding
.IStart(wm
),
754 borderPadding
.BStart(wm
));
755 nsRect origRect
= firstKid
->GetRect();
756 nsRect origInkOverflow
= firstKid
->InkOverflowRect();
757 bool firstReflow
= firstKid
->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
);
759 ReflowChild(firstKid
, aPresContext
, kidSize
, kidReflowInput
, wm
, kidOrigin
,
760 containerSize
, ReflowChildFlags::Default
, aStatus
);
761 if (aStatus
.IsOverflowIncomplete()) {
762 // Don't pass OVERFLOW_INCOMPLETE through tables until they can actually
764 // XXX should paginate overflow as overflow, but not in this patch (bug
766 aStatus
.SetIncomplete();
767 printf("Set table cell incomplete %p\n", static_cast<void*>(this));
770 // XXXbz is this invalidate actually needed, really?
771 if (HasAnyStateBits(NS_FRAME_IS_DIRTY
)) {
772 InvalidateFrameSubtree();
776 DebugCheckChildSize(firstKid
, kidSize
);
779 // 0 dimensioned cells need to be treated specially in Standard/NavQuirks mode
780 // see testcase "emptyCells.html"
781 nsIFrame
* prevInFlow
= GetPrevInFlow();
784 isEmpty
= static_cast<nsTableCellFrame
*>(prevInFlow
)->GetContentEmpty();
786 isEmpty
= !CellHasVisibleContent(kidSize
.Height(), tableFrame
, firstKid
);
788 SetContentEmpty(isEmpty
);
791 FinishReflowChild(firstKid
, aPresContext
, kidSize
, &kidReflowInput
, wm
,
792 kidOrigin
, containerSize
, ReflowChildFlags::Default
);
794 if (tableFrame
->IsBorderCollapse()) {
795 nsTableFrame::InvalidateTableFrame(firstKid
, origRect
, origInkOverflow
,
798 // first, compute the bsize which can be set w/o being restricted by
800 LogicalSize
cellSize(wm
);
801 cellSize
.BSize(wm
) = kidSize
.BSize(wm
);
803 if (NS_UNCONSTRAINEDSIZE
!= cellSize
.BSize(wm
)) {
804 cellSize
.BSize(wm
) += borderPadding
.BStart(wm
);
806 if (aStatus
.IsComplete() ||
807 aReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
808 StyleBoxDecorationBreak::Clone
) {
809 cellSize
.BSize(wm
) += borderPadding
.BEnd(wm
);
813 // next determine the cell's isize
814 cellSize
.ISize(wm
) = kidSize
.ISize(
815 wm
); // at this point, we've factored in the cell's style attributes
817 // factor in border and padding
818 if (NS_UNCONSTRAINEDSIZE
!= cellSize
.ISize(wm
)) {
819 cellSize
.ISize(wm
) += borderPadding
.IStartEnd(wm
);
822 // set the cell's desired size and max element size
823 aDesiredSize
.SetSize(wm
, cellSize
);
825 // the overflow area will be computed when BlockDirAlignChild() gets called
827 if (aReflowInput
.mFlags
.mSpecialBSizeReflow
&&
828 NS_UNCONSTRAINEDSIZE
== aReflowInput
.AvailableBSize()) {
829 aDesiredSize
.BSize(wm
) = BSize(wm
);
832 // If our parent is in initial reflow, it'll handle invalidating our
833 // entire overflow rect.
834 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
) &&
835 nsSize(aDesiredSize
.Width(), aDesiredSize
.Height()) != mRect
.Size()) {
839 // remember the desired size for this reflow
840 SetDesiredSize(aDesiredSize
);
842 // Any absolutely-positioned children will get reflowed in
843 // nsIFrame::FixupPositionedTableParts in another pass, so propagate our
844 // dirtiness to them before our parent clears our dirty bits.
845 PushDirtyBitToAbsoluteFrames();
848 /* ----- global methods ----- */
850 NS_QUERYFRAME_HEAD(nsTableCellFrame
)
851 NS_QUERYFRAME_ENTRY(nsTableCellFrame
)
852 NS_QUERYFRAME_ENTRY(nsITableCellLayout
)
853 NS_QUERYFRAME_ENTRY(nsIPercentBSizeObserver
)
854 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
857 a11y::AccType
nsTableCellFrame::AccessibleType() {
858 return a11y::eHTMLTableCellType
;
862 /* This is primarily for editor access via nsITableLayout */
864 nsTableCellFrame::GetCellIndexes(int32_t& aRowIndex
, int32_t& aColIndex
) {
865 aRowIndex
= RowIndex();
866 aColIndex
= mColIndex
;
870 nsTableCellFrame
* NS_NewTableCellFrame(PresShell
* aPresShell
,
871 ComputedStyle
* aStyle
,
872 nsTableFrame
* aTableFrame
) {
873 if (aTableFrame
->IsBorderCollapse())
874 return new (aPresShell
) nsBCTableCellFrame(aStyle
, aTableFrame
);
876 return new (aPresShell
) nsTableCellFrame(aStyle
, aTableFrame
);
879 NS_IMPL_FRAMEARENA_HELPERS(nsBCTableCellFrame
)
881 LogicalMargin
nsTableCellFrame::GetBorderWidth(WritingMode aWM
) const {
882 return LogicalMargin(aWM
, StyleBorder()->GetComputedBorder());
885 void nsTableCellFrame::AppendDirectlyOwnedAnonBoxes(
886 nsTArray
<OwnedAnonBox
>& aResult
) {
887 nsIFrame
* kid
= mFrames
.FirstChild();
888 MOZ_ASSERT(kid
&& !kid
->GetNextSibling(),
889 "Table cells should have just one child");
890 aResult
.AppendElement(OwnedAnonBox(kid
));
893 #ifdef DEBUG_FRAME_DUMP
894 nsresult
nsTableCellFrame::GetFrameName(nsAString
& aResult
) const {
895 return MakeFrameName(u
"TableCell"_ns
, aResult
);
899 // nsBCTableCellFrame
901 nsBCTableCellFrame::nsBCTableCellFrame(ComputedStyle
* aStyle
,
902 nsTableFrame
* aTableFrame
)
903 : nsTableCellFrame(aStyle
, aTableFrame
, kClassID
) {
904 mBStartBorder
= mIEndBorder
= mBEndBorder
= mIStartBorder
= 0;
907 nsBCTableCellFrame::~nsBCTableCellFrame() = default;
910 nsMargin
nsBCTableCellFrame::GetUsedBorder() const {
911 WritingMode wm
= GetWritingMode();
912 return GetBorderWidth(wm
).GetPhysicalMargin(wm
);
915 #ifdef DEBUG_FRAME_DUMP
916 nsresult
nsBCTableCellFrame::GetFrameName(nsAString
& aResult
) const {
917 return MakeFrameName(u
"BCTableCell"_ns
, aResult
);
921 LogicalMargin
nsBCTableCellFrame::GetBorderWidth(WritingMode aWM
) const {
922 int32_t d2a
= PresContext()->AppUnitsPerDevPixel();
923 return LogicalMargin(aWM
, BC_BORDER_END_HALF_COORD(d2a
, mBStartBorder
),
924 BC_BORDER_START_HALF_COORD(d2a
, mIEndBorder
),
925 BC_BORDER_START_HALF_COORD(d2a
, mBEndBorder
),
926 BC_BORDER_END_HALF_COORD(d2a
, mIStartBorder
));
929 BCPixelSize
nsBCTableCellFrame::GetBorderWidth(LogicalSide aSide
) const {
931 case eLogicalSideBStart
:
932 return BC_BORDER_END_HALF(mBStartBorder
);
933 case eLogicalSideIEnd
:
934 return BC_BORDER_START_HALF(mIEndBorder
);
935 case eLogicalSideBEnd
:
936 return BC_BORDER_START_HALF(mBEndBorder
);
938 return BC_BORDER_END_HALF(mIStartBorder
);
942 void nsBCTableCellFrame::SetBorderWidth(LogicalSide aSide
, BCPixelSize aValue
) {
944 case eLogicalSideBStart
:
945 mBStartBorder
= aValue
;
947 case eLogicalSideIEnd
:
948 mIEndBorder
= aValue
;
950 case eLogicalSideBEnd
:
951 mBEndBorder
= aValue
;
954 mIStartBorder
= aValue
;
959 nsMargin
nsBCTableCellFrame::GetBorderOverflow() {
960 WritingMode wm
= GetWritingMode();
961 int32_t d2a
= PresContext()->AppUnitsPerDevPixel();
962 LogicalMargin
halfBorder(wm
, BC_BORDER_START_HALF_COORD(d2a
, mBStartBorder
),
963 BC_BORDER_END_HALF_COORD(d2a
, mIEndBorder
),
964 BC_BORDER_END_HALF_COORD(d2a
, mBEndBorder
),
965 BC_BORDER_START_HALF_COORD(d2a
, mIStartBorder
));
966 return halfBorder
.GetPhysicalMargin(wm
);
971 class nsDisplayTableCellSelection final
: public nsPaintedDisplayItem
{
973 nsDisplayTableCellSelection(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
)
974 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
975 MOZ_COUNT_CTOR(nsDisplayTableCellSelection
);
977 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayTableCellSelection
)
979 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
{
980 static_cast<nsTableCellFrame
*>(mFrame
)->DecorateForSelection(
981 aCtx
->GetDrawTarget(), ToReferenceFrame());
983 NS_DISPLAY_DECL_NAME("TableCellSelection", TYPE_TABLE_CELL_SELECTION
)
985 bool CreateWebRenderCommands(
986 mozilla::wr::DisplayListBuilder
& aBuilder
,
987 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
988 const StackingContextHelper
& aSc
,
989 mozilla::layers::RenderRootStateManager
* aManager
,
990 nsDisplayListBuilder
* aDisplayListBuilder
) override
{
991 RefPtr
<nsFrameSelection
> frameSelection
=
992 mFrame
->PresShell()->FrameSelection();
993 return !frameSelection
->IsInTableSelectionMode();
997 } // namespace mozilla
999 void nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
1000 const nsDisplayListSet
& aLists
) {
1001 DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame");
1002 if (ShouldPaintBordersAndBackgrounds()) {
1003 // display outset box-shadows if we need to.
1004 bool hasBoxShadow
= !StyleEffects()->mBoxShadow
.IsEmpty();
1006 aLists
.BorderBackground()->AppendNewToTop
<nsDisplayBoxShadowOuter
>(
1010 nsRect bgRect
= GetRectRelativeToSelf() + aBuilder
->ToReferenceFrame(this);
1011 nsRect bgRectInsideBorder
= bgRect
;
1013 // If we're doing collapsed borders, and this element forms a new stacking
1014 // context or has position:relative (which paints as though it did), inset
1015 // the background rect so that we don't overpaint the inset part of our
1017 nsTableFrame
* tableFrame
= GetTableFrame();
1018 if (tableFrame
->IsBorderCollapse() &&
1019 (IsStackingContext() ||
1020 StyleDisplay()->mPosition
== StylePositionProperty::Relative
)) {
1021 bgRectInsideBorder
.Deflate(GetUsedBorder());
1024 // display background if we need to.
1025 const AppendedBackgroundType result
=
1026 nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
1027 aBuilder
, this, bgRectInsideBorder
, aLists
.BorderBackground(), true,
1029 if (result
== AppendedBackgroundType::None
) {
1030 aBuilder
->BuildCompositorHitTestInfoIfNeeded(this,
1031 aLists
.BorderBackground());
1034 // display inset box-shadows if we need to.
1036 aLists
.BorderBackground()->AppendNewToTop
<nsDisplayBoxShadowInner
>(
1040 // display borders if we need to
1041 ProcessBorders(tableFrame
, aBuilder
, aLists
);
1043 // and display the selection border if we need to
1045 aLists
.BorderBackground()->AppendNewToTop
<nsDisplayTableCellSelection
>(
1049 // This can be null if display list building initiated in the middle
1050 // of the table, which can happen with background-clip:text and
1052 nsDisplayTableBackgroundSet
* backgrounds
=
1053 aBuilder
->GetTableBackgroundSet();
1055 // Compute bgRect relative to reference frame, but using the
1056 // normal (without position:relative offsets) positions for the
1057 // cell, row and row group.
1058 bgRect
= GetRectRelativeToSelf() + GetNormalPosition();
1060 nsTableRowFrame
* row
= GetTableRowFrame();
1061 bgRect
+= row
->GetNormalPosition();
1063 nsTableRowGroupFrame
* rowGroup
= row
->GetTableRowGroupFrame();
1064 bgRect
+= rowGroup
->GetNormalPosition();
1066 bgRect
+= backgrounds
->TableToReferenceFrame();
1068 DisplayListClipState::AutoSaveRestore
clipState(aBuilder
);
1069 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter
asrSetter(
1071 if (IsStackingContext() || row
->IsStackingContext() ||
1072 rowGroup
->IsStackingContext() || tableFrame
->IsStackingContext()) {
1073 // The col/colgroup items we create below will be inserted directly into
1074 // the BorderBackgrounds list of the table frame. That means that
1075 // they'll be moved *outside* of any wrapper items created for any
1076 // frames between this table cell frame and the table wrapper frame, and
1077 // will not participate in those frames's opacity / transform / filter /
1078 // mask effects. If one of those frames is a stacking context, then we
1079 // may have one or more of those wrapper items, and one of them may have
1080 // captured a clip. In order to ensure correct clipping and scrolling of
1081 // the col/colgroup items, restore the clip and ASR that we observed
1082 // when we entered the table frame. If that frame is a stacking context
1083 // but doesn't have any clip capturing wrapper items, then we'll
1084 // double-apply the clip. That's ok.
1085 clipState
.SetClipChainForContainingBlockDescendants(
1086 backgrounds
->GetTableClipChain());
1087 asrSetter
.SetCurrentActiveScrolledRoot(backgrounds
->GetTableASR());
1090 // Create backgrounds items as needed for the column and column
1091 // group that this cell occupies.
1092 nsTableColFrame
* col
= backgrounds
->GetColForIndex(ColIndex());
1093 nsTableColGroupFrame
* colGroup
= col
->GetTableColGroupFrame();
1095 Maybe
<nsDisplayListBuilder::AutoBuildingDisplayList
> buildingForColGroup
;
1096 nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
1097 aBuilder
, colGroup
, bgRect
, backgrounds
->ColGroupBackgrounds(), false,
1098 colGroup
->GetRect() + backgrounds
->TableToReferenceFrame(), this,
1099 &buildingForColGroup
);
1101 Maybe
<nsDisplayListBuilder::AutoBuildingDisplayList
> buildingForCol
;
1102 nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
1103 aBuilder
, col
, bgRect
, backgrounds
->ColBackgrounds(), false,
1104 col
->GetRect() + colGroup
->GetPosition() +
1105 backgrounds
->TableToReferenceFrame(),
1106 this, &buildingForCol
);
1110 // the 'empty-cells' property has no effect on 'outline'
1111 DisplayOutline(aBuilder
, aLists
);
1113 nsIFrame
* kid
= mFrames
.FirstChild();
1114 NS_ASSERTION(kid
&& !kid
->GetNextSibling(),
1115 "Table cells should have just one child");
1116 // The child's background will go in our BorderBackground() list.
1117 // This isn't a problem since it won't have a real background except for
1118 // event handling. We do not call BuildDisplayListForNonBlockChildren
1119 // because that/ would put the child's background in the Content() list
1120 // which isn't right (e.g., would end up on top of our child floats for
1122 BuildDisplayListForChild(aBuilder
, kid
, aLists
);