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 "nsTableWrapperFrame.h"
8 #include "LayoutConstants.h"
9 #include "mozilla/ComputedStyle.h"
10 #include "mozilla/PresShell.h"
11 #include "nsFrameManager.h"
12 #include "nsTableFrame.h"
13 #include "nsTableCellFrame.h"
14 #include "nsStyleConsts.h"
15 #include "nsPresContext.h"
16 #include "nsCSSRendering.h"
17 #include "nsIContent.h"
19 #include "nsGkAtoms.h"
20 #include "nsHTMLParts.h"
21 #include "nsDisplayList.h"
22 #include "nsLayoutUtils.h"
23 #include "nsIFrameInlines.h"
26 using namespace mozilla
;
27 using namespace mozilla::layout
;
29 nscoord
nsTableWrapperFrame::SynthesizeFallbackBaseline(
30 mozilla::WritingMode aWM
, BaselineSharingGroup aBaselineGroup
) const {
31 const auto marginBlockEnd
= GetLogicalUsedMargin(aWM
).BEnd(aWM
);
32 if (aWM
.IsCentralBaseline()) {
33 return (BSize(aWM
) + marginBlockEnd
) / 2;
35 // Our fallback baseline is the block-end margin-edge, with respect to the
36 // given writing mode.
37 if (aBaselineGroup
== BaselineSharingGroup::Last
) {
38 return -marginBlockEnd
;
40 return BSize(aWM
) + marginBlockEnd
;
43 Maybe
<nscoord
> nsTableWrapperFrame::GetNaturalBaselineBOffset(
44 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
45 BaselineExportContext aExportContext
) const {
46 // Baseline is determined by row
47 // (https://drafts.csswg.org/css-align-3/#baseline-export). If the row
48 // direction is going to be orthogonal to the parent's writing mode, the
49 // resulting baseline wouldn't be valid, so we use the fallback baseline
51 if (StyleDisplay()->IsContainLayout() ||
52 GetWritingMode().IsOrthogonalTo(aWM
)) {
55 auto* innerTable
= InnerTableFrame();
57 ->GetNaturalBaselineBOffset(aWM
, aBaselineGroup
, aExportContext
)
58 .map([this, aWM
, aBaselineGroup
, innerTable
](nscoord aBaseline
) {
59 auto bStart
= innerTable
->BStart(aWM
, mRect
.Size());
60 if (aBaselineGroup
== BaselineSharingGroup::First
) {
61 return aBaseline
+ bStart
;
63 auto bEnd
= bStart
+ innerTable
->BSize(aWM
);
64 return BSize(aWM
) - (bEnd
- aBaseline
);
68 nsTableWrapperFrame::nsTableWrapperFrame(ComputedStyle
* aStyle
,
69 nsPresContext
* aPresContext
,
71 : nsContainerFrame(aStyle
, aPresContext
, aID
) {}
73 nsTableWrapperFrame::~nsTableWrapperFrame() = default;
75 NS_QUERYFRAME_HEAD(nsTableWrapperFrame
)
76 NS_QUERYFRAME_ENTRY(nsTableWrapperFrame
)
77 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
80 a11y::AccType
nsTableWrapperFrame::AccessibleType() {
81 return a11y::eHTMLTableType
;
85 void nsTableWrapperFrame::Destroy(DestroyContext
& aContext
) {
86 DestroyAbsoluteFrames(aContext
);
87 mCaptionFrames
.DestroyFrames(aContext
);
88 nsContainerFrame::Destroy(aContext
);
91 const nsFrameList
& nsTableWrapperFrame::GetChildList(
92 ChildListID aListID
) const {
93 if (aListID
== FrameChildListID::Caption
) {
94 return mCaptionFrames
;
97 return nsContainerFrame::GetChildList(aListID
);
100 void nsTableWrapperFrame::GetChildLists(nsTArray
<ChildList
>* aLists
) const {
101 nsContainerFrame::GetChildLists(aLists
);
102 mCaptionFrames
.AppendIfNonempty(aLists
, FrameChildListID::Caption
);
105 void nsTableWrapperFrame::SetInitialChildList(ChildListID aListID
,
106 nsFrameList
&& aChildList
) {
107 if (FrameChildListID::Caption
== aListID
) {
109 nsIFrame::VerifyDirtyBitSet(aChildList
);
110 for (nsIFrame
* f
: aChildList
) {
111 MOZ_ASSERT(f
->GetParent() == this, "Unexpected parent");
114 // the frame constructor already checked for table-caption display type
115 MOZ_ASSERT(mCaptionFrames
.IsEmpty(),
116 "already have child frames in CaptionList");
117 mCaptionFrames
= std::move(aChildList
);
119 MOZ_ASSERT(FrameChildListID::Principal
!= aListID
||
120 (aChildList
.FirstChild() &&
121 aChildList
.FirstChild() == aChildList
.LastChild() &&
122 aChildList
.FirstChild()->IsTableFrame()),
123 "expected a single table frame in principal child list");
124 nsContainerFrame::SetInitialChildList(aListID
, std::move(aChildList
));
128 void nsTableWrapperFrame::AppendFrames(ChildListID aListID
,
129 nsFrameList
&& aFrameList
) {
130 // We only have two child frames: the inner table and a caption frame.
131 // The inner frame is provided when we're initialized, and it cannot change
132 MOZ_ASSERT(FrameChildListID::Caption
== aListID
, "unexpected child list");
133 MOZ_ASSERT(aFrameList
.IsEmpty() || aFrameList
.FirstChild()->IsTableCaption(),
134 "appending non-caption frame to captionList");
135 mCaptionFrames
.AppendFrames(nullptr, std::move(aFrameList
));
137 // Reflow the new caption frame. It's already marked dirty, so
138 // just tell the pres shell.
139 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors
,
140 NS_FRAME_HAS_DIRTY_CHILDREN
);
141 // The presence of caption frames makes us sort our display
142 // list differently, so mark us as changed for the new
144 MarkNeedsDisplayItemRebuild();
147 void nsTableWrapperFrame::InsertFrames(
148 ChildListID aListID
, nsIFrame
* aPrevFrame
,
149 const nsLineList::iterator
* aPrevFrameLine
, nsFrameList
&& aFrameList
) {
150 MOZ_ASSERT(FrameChildListID::Caption
== aListID
, "unexpected child list");
151 MOZ_ASSERT(aFrameList
.IsEmpty() || aFrameList
.FirstChild()->IsTableCaption(),
152 "inserting non-caption frame into captionList");
153 MOZ_ASSERT(!aPrevFrame
|| aPrevFrame
->GetParent() == this,
154 "inserting after sibling frame with different parent");
155 mCaptionFrames
.InsertFrames(nullptr, aPrevFrame
, std::move(aFrameList
));
157 // Reflow the new caption frame. It's already marked dirty, so
158 // just tell the pres shell.
159 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors
,
160 NS_FRAME_HAS_DIRTY_CHILDREN
);
161 MarkNeedsDisplayItemRebuild();
164 void nsTableWrapperFrame::RemoveFrame(DestroyContext
& aContext
,
166 nsIFrame
* aOldFrame
) {
167 // We only have two child frames: the inner table and one caption frame.
168 // The inner frame can't be removed so this should be the caption
169 MOZ_ASSERT(FrameChildListID::Caption
== aListID
, "can't remove inner frame");
171 // Remove the frame and destroy it
172 mCaptionFrames
.DestroyFrame(aContext
, aOldFrame
);
174 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors
,
175 NS_FRAME_HAS_DIRTY_CHILDREN
);
176 MarkNeedsDisplayItemRebuild();
179 void nsTableWrapperFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
180 const nsDisplayListSet
& aLists
) {
181 // No border or background is painted because they belong to the inner table.
182 // The outline belongs to the wrapper frame so it can contain the caption.
184 // If there's no caption, take a short cut to avoid having to create
185 // the special display list set and then sort it.
186 if (mCaptionFrames
.IsEmpty()) {
187 BuildDisplayListForInnerTable(aBuilder
, aLists
);
188 DisplayOutline(aBuilder
, aLists
);
192 nsDisplayListCollection
set(aBuilder
);
193 BuildDisplayListForInnerTable(aBuilder
, set
);
195 nsDisplayListSet
captionSet(set
, set
.BlockBorderBackgrounds());
196 BuildDisplayListForChild(aBuilder
, mCaptionFrames
.FirstChild(), captionSet
);
198 // Now we have to sort everything by content order, since the caption
199 // may be somewhere inside the table.
200 // We don't sort BlockBorderBackgrounds and BorderBackgrounds because the
201 // display items in those lists should stay out of content order in order to
202 // follow the rules in https://www.w3.org/TR/CSS21/zindex.html#painting-order
203 // and paint the caption background after all of the rest.
204 set
.Floats()->SortByContentOrder(GetContent());
205 set
.Content()->SortByContentOrder(GetContent());
206 set
.PositionedDescendants()->SortByContentOrder(GetContent());
207 set
.Outlines()->SortByContentOrder(GetContent());
210 DisplayOutline(aBuilder
, aLists
);
213 void nsTableWrapperFrame::BuildDisplayListForInnerTable(
214 nsDisplayListBuilder
* aBuilder
, const nsDisplayListSet
& aLists
) {
215 // Just paint the regular children, but the children's background is our
216 // true background (there should only be one, the real table)
217 nsIFrame
* kid
= mFrames
.FirstChild();
218 // The children should be in content order
220 BuildDisplayListForChild(aBuilder
, kid
, aLists
);
221 kid
= kid
->GetNextSibling();
225 ComputedStyle
* nsTableWrapperFrame::GetParentComputedStyle(
226 nsIFrame
** aProviderFrame
) const {
227 // The table wrapper frame and the (inner) table frame split the style
228 // data by giving the table frame the ComputedStyle associated with
229 // the table content node and creating a ComputedStyle for the wrapper
230 // frame that is a *child* of the table frame's ComputedStyle,
231 // matching the ::-moz-table-wrapper pseudo-element. html.css has a
232 // rule that causes that pseudo-element (and thus the wrapper table)
233 // to inherit *some* style properties from the table frame. The
234 // children of the table inherit directly from the inner table, and
235 // the table wrapper's ComputedStyle is a leaf.
237 return (*aProviderFrame
= InnerTableFrame())->Style();
240 static nsSize
GetContainingBlockSize(const ReflowInput
& aOuterRI
) {
242 const ReflowInput
* containRS
= aOuterRI
.mCBReflowInput
;
245 size
.width
= containRS
->ComputedWidth();
246 if (NS_UNCONSTRAINEDSIZE
== size
.width
) {
249 size
.height
= containRS
->ComputedHeight();
250 if (NS_UNCONSTRAINEDSIZE
== size
.height
) {
258 nscoord
nsTableWrapperFrame::GetMinISize(gfxContext
* aRenderingContext
) {
259 nscoord iSize
= nsLayoutUtils::IntrinsicForContainer(
260 aRenderingContext
, InnerTableFrame(), IntrinsicISizeType::MinISize
);
261 DISPLAY_MIN_INLINE_SIZE(this, iSize
);
262 if (mCaptionFrames
.NotEmpty()) {
263 nscoord capISize
= nsLayoutUtils::IntrinsicForContainer(
264 aRenderingContext
, mCaptionFrames
.FirstChild(),
265 IntrinsicISizeType::MinISize
);
266 if (capISize
> iSize
) {
274 nscoord
nsTableWrapperFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
276 DISPLAY_PREF_INLINE_SIZE(this, maxISize
);
278 maxISize
= nsLayoutUtils::IntrinsicForContainer(
279 aRenderingContext
, InnerTableFrame(), IntrinsicISizeType::PrefISize
);
280 if (mCaptionFrames
.NotEmpty()) {
281 // Don't let the caption's pref isize expand the table's pref isize.
282 const nscoord capMinISize
= nsLayoutUtils::IntrinsicForContainer(
283 aRenderingContext
, mCaptionFrames
.FirstChild(),
284 IntrinsicISizeType::MinISize
);
285 maxISize
= std::max(maxISize
, capMinISize
);
290 LogicalSize
nsTableWrapperFrame::InnerTableShrinkWrapSize(
291 gfxContext
* aRenderingContext
, nsTableFrame
* aTableFrame
, WritingMode aWM
,
292 const LogicalSize
& aCBSize
, nscoord aAvailableISize
,
293 const StyleSizeOverrides
& aSizeOverrides
, ComputeSizeFlags aFlags
) const {
294 MOZ_ASSERT(InnerTableFrame() == aTableFrame
);
296 AutoMaybeDisableFontInflation
an(aTableFrame
);
298 Maybe
<LogicalMargin
> collapseBorder
;
299 Maybe
<LogicalMargin
> collapsePadding
;
300 aTableFrame
->GetCollapsedBorderPadding(collapseBorder
, collapsePadding
);
302 SizeComputationInput
input(aTableFrame
, aRenderingContext
, aWM
,
303 aCBSize
.ISize(aWM
), collapseBorder
,
305 LogicalSize
marginSize(aWM
); // Inner table doesn't have any margin
306 LogicalSize bpSize
= input
.ComputedLogicalBorderPadding(aWM
).Size(aWM
);
308 // Note that we pass an empty caption-area here (rather than the caption's
309 // actual size). This is fine because:
311 // 1) nsTableWrapperFrame::ComputeSize() only uses the size returned by this
312 // method (indirectly via calling nsTableWrapperFrame::ComputeAutoSize())
313 // if it get a aSizeOverrides arg containing any size overrides with
314 // mApplyOverridesVerbatim=true. The aSizeOverrides arg is passed to this
315 // method without any modifications.
317 // 2) With 1), that means the aSizeOverrides passing into this method should
318 // be applied to the inner table directly, so we don't need to subtract
319 // caption-area when preparing innerOverrides for
320 // nsTableFrame::ComputeSize().
321 StyleSizeOverrides innerOverrides
= ComputeSizeOverridesForInnerTable(
322 aTableFrame
, aSizeOverrides
, bpSize
, /* aBSizeOccupiedByCaption = */ 0);
325 ->ComputeSize(aRenderingContext
, aWM
, aCBSize
, aAvailableISize
,
326 marginSize
, bpSize
, innerOverrides
, aFlags
)
328 size
.ISize(aWM
) += bpSize
.ISize(aWM
);
329 if (size
.BSize(aWM
) != NS_UNCONSTRAINEDSIZE
) {
330 size
.BSize(aWM
) += bpSize
.BSize(aWM
);
335 LogicalSize
nsTableWrapperFrame::CaptionShrinkWrapSize(
336 gfxContext
* aRenderingContext
, nsIFrame
* aCaptionFrame
, WritingMode aWM
,
337 const LogicalSize
& aCBSize
, nscoord aAvailableISize
,
338 ComputeSizeFlags aFlags
) const {
339 MOZ_ASSERT(aCaptionFrame
== mCaptionFrames
.FirstChild());
341 AutoMaybeDisableFontInflation
an(aCaptionFrame
);
343 SizeComputationInput
input(aCaptionFrame
, aRenderingContext
, aWM
,
345 LogicalSize marginSize
= input
.ComputedLogicalMargin(aWM
).Size(aWM
);
346 LogicalSize bpSize
= input
.ComputedLogicalBorderPadding(aWM
).Size(aWM
);
348 auto size
= aCaptionFrame
349 ->ComputeSize(aRenderingContext
, aWM
, aCBSize
,
350 aAvailableISize
, marginSize
, bpSize
, {}, aFlags
)
352 size
.ISize(aWM
) += (marginSize
.ISize(aWM
) + bpSize
.ISize(aWM
));
353 if (size
.BSize(aWM
) != NS_UNCONSTRAINEDSIZE
) {
354 size
.BSize(aWM
) += (marginSize
.BSize(aWM
) + bpSize
.BSize(aWM
));
359 StyleSize
nsTableWrapperFrame::ReduceStyleSizeBy(
360 const StyleSize
& aStyleSize
, const nscoord aAmountToReduce
) const {
361 MOZ_ASSERT(aStyleSize
.ConvertsToLength(), "Only handles 'Length' StyleSize!");
362 const nscoord size
= std::max(0, aStyleSize
.ToLength() - aAmountToReduce
);
363 return StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(size
));
366 StyleSizeOverrides
nsTableWrapperFrame::ComputeSizeOverridesForInnerTable(
367 const nsTableFrame
* aTableFrame
,
368 const StyleSizeOverrides
& aWrapperSizeOverrides
,
369 const LogicalSize
& aBorderPadding
, nscoord aBSizeOccupiedByCaption
) const {
370 if (aWrapperSizeOverrides
.mApplyOverridesVerbatim
||
371 !aWrapperSizeOverrides
.HasAnyLengthOverrides()) {
372 // We are asked to apply the size overrides directly to the inner table, or
373 // there's no 'Length' size overrides. No need to tweak the size overrides.
374 return aWrapperSizeOverrides
;
377 const auto wm
= aTableFrame
->GetWritingMode();
378 LogicalSize
areaOccupied(wm
, 0, aBSizeOccupiedByCaption
);
379 if (aTableFrame
->StylePosition()->mBoxSizing
== StyleBoxSizing::Content
) {
380 // If the inner table frame has 'box-sizing: content', enlarge the occupied
381 // area by adding border & padding because they should also be subtracted
382 // from the size overrides.
383 areaOccupied
+= aBorderPadding
;
386 StyleSizeOverrides innerSizeOverrides
;
387 const auto& wrapperISize
= aWrapperSizeOverrides
.mStyleISize
;
389 MOZ_ASSERT(!wrapperISize
->HasPercent(),
390 "Table doesn't support size overrides containing percentages!");
391 innerSizeOverrides
.mStyleISize
.emplace(
392 wrapperISize
->ConvertsToLength()
393 ? ReduceStyleSizeBy(*wrapperISize
, areaOccupied
.ISize(wm
))
397 const auto& wrapperBSize
= aWrapperSizeOverrides
.mStyleBSize
;
399 MOZ_ASSERT(!wrapperBSize
->HasPercent(),
400 "Table doesn't support size overrides containing percentages!");
401 innerSizeOverrides
.mStyleBSize
.emplace(
402 wrapperBSize
->ConvertsToLength()
403 ? ReduceStyleSizeBy(*wrapperBSize
, areaOccupied
.BSize(wm
))
407 return innerSizeOverrides
;
411 nsIFrame::SizeComputationResult
nsTableWrapperFrame::ComputeSize(
412 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
413 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
414 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
415 ComputeSizeFlags aFlags
) {
416 auto result
= nsContainerFrame::ComputeSize(
417 aRenderingContext
, aWM
, aCBSize
, aAvailableISize
, aMargin
, aBorderPadding
,
418 aSizeOverrides
, aFlags
);
420 if (aSizeOverrides
.mApplyOverridesVerbatim
&&
421 aSizeOverrides
.HasAnyOverrides()) {
422 // We are asked to apply the size overrides directly to the inner table, but
423 // we still want ourselves to remain 'auto'-sized and shrink-wrapping our
424 // children's sizes. (Table wrapper frames always have 'auto' inline-size
425 // and block-size, since we don't inherit those properties from inner table,
426 // and authors can't target them with styling.)
428 ComputeAutoSize(aRenderingContext
, aWM
, aCBSize
, aAvailableISize
,
429 aMargin
, aBorderPadding
, aSizeOverrides
, aFlags
);
430 result
.mLogicalSize
= size
;
437 LogicalSize
nsTableWrapperFrame::ComputeAutoSize(
438 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
439 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
440 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
441 ComputeSizeFlags aFlags
) {
442 nscoord kidAvailableISize
= aAvailableISize
- aMargin
.ISize(aWM
);
443 NS_ASSERTION(aBorderPadding
.IsAllZero(),
444 "Table wrapper frames cannot have borders or paddings");
446 // When we're shrink-wrapping, our auto size needs to wrap around the
447 // actual size of the table, which (if it is specified as a percent)
448 // could be something that is not reflected in our GetMinISize and
449 // GetPrefISize. See bug 349457 for an example.
451 // Shrink-wrap aChildFrame by default, except if we're a stretched grid item.
452 ComputeSizeFlags
flags(ComputeSizeFlag::ShrinkWrap
);
453 if (MOZ_UNLIKELY(IsGridItem()) && !StyleMargin()->HasInlineAxisAuto(aWM
)) {
454 const auto* parent
= GetParent();
455 auto inlineAxisAlignment
=
456 aWM
.IsOrthogonalTo(parent
->GetWritingMode())
457 ? StylePosition()->UsedAlignSelf(parent
->Style())._0
458 : StylePosition()->UsedJustifySelf(parent
->Style())._0
;
459 if (inlineAxisAlignment
== StyleAlignFlags::NORMAL
||
460 inlineAxisAlignment
== StyleAlignFlags::STRETCH
) {
461 flags
-= ComputeSizeFlag::ShrinkWrap
;
465 // Match the logic in Reflow() that sets aside space for the caption.
466 Maybe
<StyleCaptionSide
> captionSide
= GetCaptionSide();
468 const LogicalSize innerTableSize
= InnerTableShrinkWrapSize(
469 aRenderingContext
, InnerTableFrame(), aWM
, aCBSize
, kidAvailableISize
,
470 aSizeOverrides
, flags
);
472 return innerTableSize
;
474 const LogicalSize captionSize
=
475 CaptionShrinkWrapSize(aRenderingContext
, mCaptionFrames
.FirstChild(), aWM
,
476 aCBSize
, innerTableSize
.ISize(aWM
), flags
);
477 const nscoord iSize
=
478 std::max(innerTableSize
.ISize(aWM
), captionSize
.ISize(aWM
));
479 nscoord bSize
= NS_UNCONSTRAINEDSIZE
;
480 if (innerTableSize
.BSize(aWM
) != NS_UNCONSTRAINEDSIZE
&&
481 captionSize
.BSize(aWM
) != NS_UNCONSTRAINEDSIZE
) {
482 bSize
= innerTableSize
.BSize(aWM
) + captionSize
.BSize(aWM
);
484 return LogicalSize(aWM
, iSize
, bSize
);
487 Maybe
<StyleCaptionSide
> nsTableWrapperFrame::GetCaptionSide() const {
488 if (mCaptionFrames
.IsEmpty()) {
491 return Some(mCaptionFrames
.FirstChild()->StyleTableBorder()->mCaptionSide
);
494 StyleVerticalAlignKeyword
nsTableWrapperFrame::GetCaptionVerticalAlign() const {
495 const auto& va
= mCaptionFrames
.FirstChild()->StyleDisplay()->mVerticalAlign
;
496 return va
.IsKeyword() ? va
.AsKeyword() : StyleVerticalAlignKeyword::Top
;
499 nscoord
nsTableWrapperFrame::ComputeFinalBSize(
500 const MaybeCaptionSide
& aCaptionSide
, const LogicalSize
& aInnerSize
,
501 const LogicalSize
& aCaptionSize
, const LogicalMargin
& aCaptionMargin
,
502 const WritingMode aWM
) const {
503 // negative sizes can upset overflow-area code
504 return std::max(0, aInnerSize
.BSize(aWM
) +
505 std::max(0, aCaptionSize
.BSize(aWM
) +
506 aCaptionMargin
.BStartEnd(aWM
)));
509 void nsTableWrapperFrame::GetCaptionOrigin(StyleCaptionSide aCaptionSide
,
510 const LogicalSize
& aContainBlockSize
,
511 const LogicalSize
& aInnerSize
,
512 const LogicalSize
& aCaptionSize
,
513 LogicalMargin
& aCaptionMargin
,
514 LogicalPoint
& aOrigin
,
515 WritingMode aWM
) const {
516 aOrigin
.I(aWM
) = aOrigin
.B(aWM
) = 0;
517 if ((NS_UNCONSTRAINEDSIZE
== aInnerSize
.ISize(aWM
)) ||
518 (NS_UNCONSTRAINEDSIZE
== aInnerSize
.BSize(aWM
)) ||
519 (NS_UNCONSTRAINEDSIZE
== aCaptionSize
.ISize(aWM
)) ||
520 (NS_UNCONSTRAINEDSIZE
== aCaptionSize
.BSize(aWM
))) {
523 if (mCaptionFrames
.IsEmpty()) {
527 NS_ASSERTION(NS_AUTOMARGIN
!= aCaptionMargin
.IStart(aWM
) &&
528 NS_AUTOMARGIN
!= aCaptionMargin
.BStart(aWM
) &&
529 NS_AUTOMARGIN
!= aCaptionMargin
.BEnd(aWM
),
530 "The computed caption margin is auto?");
532 aOrigin
.I(aWM
) = aCaptionMargin
.IStart(aWM
);
534 // block-dir computation
535 switch (aCaptionSide
) {
536 case StyleCaptionSide::Bottom
:
537 aOrigin
.B(aWM
) = aInnerSize
.BSize(aWM
) + aCaptionMargin
.BStart(aWM
);
539 case StyleCaptionSide::Top
:
540 aOrigin
.B(aWM
) = aCaptionMargin
.BStart(aWM
);
545 void nsTableWrapperFrame::GetInnerOrigin(const MaybeCaptionSide
& aCaptionSide
,
546 const LogicalSize
& aContainBlockSize
,
547 const LogicalSize
& aCaptionSize
,
548 const LogicalMargin
& aCaptionMargin
,
549 const LogicalSize
& aInnerSize
,
550 LogicalPoint
& aOrigin
,
551 WritingMode aWM
) const {
552 NS_ASSERTION(NS_AUTOMARGIN
!= aCaptionMargin
.IStart(aWM
) &&
553 NS_AUTOMARGIN
!= aCaptionMargin
.IEnd(aWM
),
554 "The computed caption margin is auto?");
556 aOrigin
.I(aWM
) = aOrigin
.B(aWM
) = 0;
557 if ((NS_UNCONSTRAINEDSIZE
== aInnerSize
.ISize(aWM
)) ||
558 (NS_UNCONSTRAINEDSIZE
== aInnerSize
.BSize(aWM
)) ||
559 (NS_UNCONSTRAINEDSIZE
== aCaptionSize
.ISize(aWM
)) ||
560 (NS_UNCONSTRAINEDSIZE
== aCaptionSize
.BSize(aWM
))) {
564 // block-dir computation
566 switch (*aCaptionSide
) {
567 case StyleCaptionSide::Bottom
:
570 case StyleCaptionSide::Top
:
572 aCaptionSize
.BSize(aWM
) + aCaptionMargin
.BStartEnd(aWM
);
578 void nsTableWrapperFrame::CreateReflowInputForInnerTable(
579 nsPresContext
* aPresContext
, nsTableFrame
* aTableFrame
,
580 const ReflowInput
& aOuterRI
, Maybe
<ReflowInput
>& aChildRI
,
581 const nscoord aAvailISize
, nscoord aBSizeOccupiedByCaption
) const {
582 MOZ_ASSERT(InnerTableFrame() == aTableFrame
);
584 const WritingMode wm
= aTableFrame
->GetWritingMode();
585 // If we have a caption occupied our content-box area, reduce the available
586 // block-size by the amount.
587 nscoord availBSize
= aOuterRI
.AvailableBSize();
588 if (availBSize
!= NS_UNCONSTRAINEDSIZE
) {
589 availBSize
= std::max(0, availBSize
- aBSizeOccupiedByCaption
);
591 const LogicalSize
availSize(wm
, aAvailISize
, availBSize
);
593 // For inner table frames, the containing block is the same as for the outer
595 Maybe
<LogicalSize
> cbSize
= Some(aOuterRI
.mContainingBlockSize
);
597 // However, if we are a grid item, the CB size needs to subtract our margins
598 // and the area occupied by the caption.
600 // Note that inner table computed margins are always zero, they're inherited
601 // by the table wrapper, so we need to get our margin from aOuterRI.
603 const LogicalMargin margin
= aOuterRI
.ComputedLogicalMargin(wm
);
604 cbSize
->ISize(wm
) = std::max(0, cbSize
->ISize(wm
) - margin
.IStartEnd(wm
));
605 if (cbSize
->BSize(wm
) != NS_UNCONSTRAINEDSIZE
) {
606 cbSize
->BSize(wm
) = std::max(0, cbSize
->BSize(wm
) - margin
.BStartEnd(wm
) -
607 aBSizeOccupiedByCaption
);
611 if (!aTableFrame
->IsBorderCollapse() &&
612 !aOuterRI
.mStyleSizeOverrides
.HasAnyOverrides()) {
613 // We are not border-collapsed and not given any size overrides. It's
614 // sufficient to call the standard ReflowInput constructor.
615 aChildRI
.emplace(aPresContext
, aOuterRI
, aTableFrame
, availSize
, cbSize
);
619 Maybe
<LogicalMargin
> borderPadding
;
620 Maybe
<LogicalMargin
> padding
;
622 // Compute inner table frame's border & padding because we may need to
623 // reduce the size for inner table's size overrides. We won't waste time if
624 // they are not used, because we can use them directly by passing them into
625 // ReflowInput::Init().
626 Maybe
<LogicalMargin
> collapseBorder
;
627 Maybe
<LogicalMargin
> collapsePadding
;
628 aTableFrame
->GetCollapsedBorderPadding(collapseBorder
, collapsePadding
);
629 SizeComputationInput
input(aTableFrame
, aOuterRI
.mRenderingContext
, wm
,
630 cbSize
->ISize(wm
), collapseBorder
,
632 borderPadding
.emplace(input
.ComputedLogicalBorderPadding(wm
));
633 padding
.emplace(input
.ComputedLogicalPadding(wm
));
636 StyleSizeOverrides innerOverrides
= ComputeSizeOverridesForInnerTable(
637 aTableFrame
, aOuterRI
.mStyleSizeOverrides
, borderPadding
->Size(wm
),
638 aBSizeOccupiedByCaption
);
640 aChildRI
.emplace(aPresContext
, aOuterRI
, aTableFrame
, availSize
, Nothing(),
641 ReflowInput::InitFlag::CallerWillInit
, innerOverrides
);
642 aChildRI
->Init(aPresContext
, cbSize
, Some(*borderPadding
- *padding
),
646 void nsTableWrapperFrame::CreateReflowInputForCaption(
647 nsPresContext
* aPresContext
, nsIFrame
* aCaptionFrame
,
648 const ReflowInput
& aOuterRI
, Maybe
<ReflowInput
>& aChildRI
,
649 const nscoord aAvailISize
) const {
650 MOZ_ASSERT(aCaptionFrame
== mCaptionFrames
.FirstChild());
652 const WritingMode wm
= aCaptionFrame
->GetWritingMode();
654 // Use unconstrained available block-size so that the caption is always
656 const LogicalSize
availSize(wm
, aAvailISize
, NS_UNCONSTRAINEDSIZE
);
657 aChildRI
.emplace(aPresContext
, aOuterRI
, aCaptionFrame
, availSize
);
659 // See if we need to reset mIsTopOfPage flag.
660 if (aChildRI
->mFlags
.mIsTopOfPage
) {
661 if (auto captionSide
= GetCaptionSide()) {
662 if (*captionSide
== StyleCaptionSide::Bottom
) {
663 aChildRI
->mFlags
.mIsTopOfPage
= false;
669 void nsTableWrapperFrame::ReflowChild(nsPresContext
* aPresContext
,
670 nsIFrame
* aChildFrame
,
671 const ReflowInput
& aChildRI
,
672 ReflowOutput
& aMetrics
,
673 nsReflowStatus
& aStatus
) {
674 // Using zero as containerSize here because we want consistency between
675 // the GetLogicalPosition and ReflowChild calls, to avoid unnecessarily
676 // changing the frame's coordinates; but we don't yet know its final
677 // position anyway so the actual value is unimportant.
678 const nsSize zeroCSize
;
679 WritingMode wm
= aChildRI
.GetWritingMode();
681 // Use the current position as a best guess for placement.
682 LogicalPoint childPt
= aChildFrame
->GetLogicalPosition(wm
, zeroCSize
);
683 ReflowChildFlags flags
= ReflowChildFlags::NoMoveFrame
;
685 // We don't want to delete our next-in-flow's child if it's an inner table
686 // frame, because table wrapper frames always assume that their inner table
687 // frames don't go away. If a table wrapper frame is removed because it is
688 // a next-in-flow of an already complete table wrapper frame, then it will
689 // take care of removing it's inner table frame.
690 if (aChildFrame
== InnerTableFrame()) {
691 flags
|= ReflowChildFlags::NoDeleteNextInFlowChild
;
694 nsContainerFrame::ReflowChild(aChildFrame
, aPresContext
, aMetrics
, aChildRI
,
695 wm
, childPt
, zeroCSize
, flags
, aStatus
);
698 void nsTableWrapperFrame::UpdateOverflowAreas(ReflowOutput
& aMet
) {
699 aMet
.SetOverflowAreasToDesiredBounds();
700 ConsiderChildOverflow(aMet
.mOverflowAreas
, InnerTableFrame());
701 if (mCaptionFrames
.NotEmpty()) {
702 ConsiderChildOverflow(aMet
.mOverflowAreas
, mCaptionFrames
.FirstChild());
706 void nsTableWrapperFrame::Reflow(nsPresContext
* aPresContext
,
707 ReflowOutput
& aDesiredSize
,
708 const ReflowInput
& aOuterRI
,
709 nsReflowStatus
& aStatus
) {
711 DO_GLOBAL_REFLOW_COUNT("nsTableWrapperFrame");
712 DISPLAY_REFLOW(aPresContext
, this, aOuterRI
, aDesiredSize
, aStatus
);
713 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
715 // Initialize out parameters
716 aDesiredSize
.ClearSize();
718 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
719 // Set up our kids. They're already present, on an overflow list,
720 // or there are none so we'll create them now
721 MoveOverflowToChildList();
724 Maybe
<ReflowInput
> captionRI
;
725 Maybe
<ReflowInput
> innerRI
;
727 nsRect origCaptionRect
;
728 nsRect origCaptionInkOverflow
;
729 bool captionFirstReflow
= false;
730 if (mCaptionFrames
.NotEmpty()) {
731 origCaptionRect
= mCaptionFrames
.FirstChild()->GetRect();
732 origCaptionInkOverflow
= mCaptionFrames
.FirstChild()->InkOverflowRect();
734 mCaptionFrames
.FirstChild()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
);
737 // ComputeAutoSize has to match this logic.
738 WritingMode wm
= aOuterRI
.GetWritingMode();
739 Maybe
<StyleCaptionSide
> captionSide
= GetCaptionSide();
740 WritingMode captionWM
= wm
; // will be changed below if necessary
741 const nscoord contentBoxISize
= aOuterRI
.ComputedSize(wm
).ISize(wm
);
743 MOZ_ASSERT(mCaptionFrames
.NotEmpty() == captionSide
.isSome());
745 // Compute the table's size first, and then prevent the caption from
746 // being larger in the inline dir unless it has to be.
748 // Note that CSS 2.1 (but not 2.0) says:
749 // The width of the anonymous box is the border-edge width of the
750 // table box inside it
751 // We don't actually make our anonymous box that isize (if we did,
752 // it would break 'auto' margins), but this effectively does that.
753 CreateReflowInputForInnerTable(aPresContext
, InnerTableFrame(), aOuterRI
,
754 innerRI
, contentBoxISize
);
756 // It's good that CSS 2.1 says not to include margins, since we can't, since
757 // they already been converted so they exactly fill the available isize
758 // (ignoring the margin on one side if neither are auto). (We take
759 // advantage of that later when we call GetCaptionOrigin, though.)
760 nscoord innerBorderISize
=
761 innerRI
->ComputedSizeWithBorderPadding(wm
).ISize(wm
);
762 CreateReflowInputForCaption(aPresContext
, mCaptionFrames
.FirstChild(),
763 aOuterRI
, captionRI
, innerBorderISize
);
764 captionWM
= captionRI
->GetWritingMode();
767 // First reflow the caption.
768 Maybe
<ReflowOutput
> captionMet
;
769 LogicalSize
captionSize(wm
);
770 LogicalMargin
captionMargin(wm
);
772 captionMet
.emplace(wm
);
773 // We intentionally don't merge capStatus into aStatus, since we currently
774 // can't handle caption continuations, but we probably should.
775 nsReflowStatus capStatus
;
776 ReflowChild(aPresContext
, mCaptionFrames
.FirstChild(), *captionRI
,
777 *captionMet
, capStatus
);
778 captionSize
= captionMet
->Size(wm
);
779 captionMargin
= captionRI
->ComputedLogicalMargin(wm
);
780 nscoord bSizeOccupiedByCaption
=
781 captionSize
.BSize(wm
) + captionMargin
.BStartEnd(wm
);
782 if (bSizeOccupiedByCaption
) {
783 // Reset the inner table's ReflowInput to reduce various sizes because of
784 // the area occupied by caption.
786 CreateReflowInputForInnerTable(aPresContext
, InnerTableFrame(), aOuterRI
,
787 innerRI
, contentBoxISize
,
788 bSizeOccupiedByCaption
);
792 // Then, now that we know how much to reduce the isize of the inner
793 // table to account for side captions, reflow the inner table.
794 ReflowOutput
innerMet(innerRI
->GetWritingMode());
795 ReflowChild(aPresContext
, InnerTableFrame(), *innerRI
, innerMet
, aStatus
);
796 LogicalSize
innerSize(wm
, innerMet
.ISize(wm
), innerMet
.BSize(wm
));
798 LogicalSize
containSize(wm
, GetContainingBlockSize(aOuterRI
));
800 // Now that we've reflowed both we can place them.
801 // XXXldb Most of the input variables here are now uninitialized!
803 // XXX Need to recompute inner table's auto margins for the case of side
804 // captions. (Caption's are broken too, but that should be fixed earlier.)
806 // Compute the desiredSize so that we can use it as the containerSize
807 // for the FinishReflowChild calls below.
808 LogicalSize
desiredSize(wm
);
810 // We have zero border and padding, so content-box inline-size is our desired
811 // border-box inline-size.
812 desiredSize
.ISize(wm
) = contentBoxISize
;
813 desiredSize
.BSize(wm
) =
814 ComputeFinalBSize(captionSide
, innerSize
, captionSize
, captionMargin
, wm
);
816 aDesiredSize
.SetSize(wm
, desiredSize
);
817 nsSize containerSize
= aDesiredSize
.PhysicalSize();
818 // XXX It's possible for this to be NS_UNCONSTRAINEDSIZE, which will result
819 // in assertions from FinishReflowChild.
821 MOZ_ASSERT(mCaptionFrames
.NotEmpty() == captionSide
.isSome());
822 if (mCaptionFrames
.NotEmpty()) {
823 LogicalPoint
captionOrigin(wm
);
824 GetCaptionOrigin(*captionSide
, containSize
, innerSize
, captionSize
,
825 captionMargin
, captionOrigin
, wm
);
826 FinishReflowChild(mCaptionFrames
.FirstChild(), aPresContext
, *captionMet
,
827 captionRI
.ptr(), wm
, captionOrigin
, containerSize
,
828 ReflowChildFlags::ApplyRelativePositioning
);
831 // XXX If the bsize is constrained then we need to check whether
832 // everything still fits...
834 LogicalPoint
innerOrigin(wm
);
835 GetInnerOrigin(captionSide
, containSize
, captionSize
, captionMargin
,
836 innerSize
, innerOrigin
, wm
);
837 // NOTE: Relative positioning on the table applies to the whole table wrapper.
838 FinishReflowChild(InnerTableFrame(), aPresContext
, innerMet
, innerRI
.ptr(),
839 wm
, innerOrigin
, containerSize
, ReflowChildFlags::Default
);
842 if (mCaptionFrames
.NotEmpty()) {
843 nsTableFrame::InvalidateTableFrame(mCaptionFrames
.FirstChild(),
844 origCaptionRect
, origCaptionInkOverflow
,
848 UpdateOverflowAreas(aDesiredSize
);
850 if (GetPrevInFlow()) {
851 ReflowOverflowContainerChildren(aPresContext
, aOuterRI
,
852 aDesiredSize
.mOverflowAreas
,
853 ReflowChildFlags::Default
, aStatus
);
856 FinishReflowWithAbsoluteFrames(aPresContext
, aDesiredSize
, aOuterRI
, aStatus
);
859 /* ----- global methods ----- */
861 nsIContent
* nsTableWrapperFrame::GetCellAt(uint32_t aRowIdx
,
862 uint32_t aColIdx
) const {
863 nsTableCellMap
* cellMap
= InnerTableFrame()->GetCellMap();
868 nsTableCellFrame
* cell
= cellMap
->GetCellInfoAt(aRowIdx
, aColIdx
);
873 return cell
->GetContent();
876 nsTableWrapperFrame
* NS_NewTableWrapperFrame(PresShell
* aPresShell
,
877 ComputedStyle
* aStyle
) {
878 return new (aPresShell
)
879 nsTableWrapperFrame(aStyle
, aPresShell
->GetPresContext());
882 NS_IMPL_FRAMEARENA_HELPERS(nsTableWrapperFrame
)
884 #ifdef DEBUG_FRAME_DUMP
885 nsresult
nsTableWrapperFrame::GetFrameName(nsAString
& aResult
) const {
886 return MakeFrameName(u
"TableWrapper"_ns
, aResult
);