nsTableWrapperFrame.cpp (33220B)
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/. */ 5 6 #include "nsTableWrapperFrame.h" 7 8 #include <algorithm> 9 10 #include "LayoutConstants.h" 11 #include "mozilla/ComputedStyle.h" 12 #include "mozilla/PresShell.h" 13 #include "nsCSSRendering.h" 14 #include "nsDisplayList.h" 15 #include "nsFrameManager.h" 16 #include "nsGkAtoms.h" 17 #include "nsGridContainerFrame.h" 18 #include "nsHTMLParts.h" 19 #include "nsIContent.h" 20 #include "nsIFrameInlines.h" 21 #include "nsLayoutUtils.h" 22 #include "nsPresContext.h" 23 #include "nsStyleConsts.h" 24 #include "nsTableCellFrame.h" 25 #include "nsTableFrame.h" 26 #include "prinrval.h" 27 28 using namespace mozilla; 29 using namespace mozilla::layout; 30 31 nscoord nsTableWrapperFrame::SynthesizeFallbackBaseline( 32 mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { 33 const auto marginBlockEnd = GetLogicalUsedMargin(aWM).BEnd(aWM); 34 if (aWM.IsCentralBaseline()) { 35 return (BSize(aWM) + marginBlockEnd) / 2; 36 } 37 // Our fallback baseline is the block-end margin-edge, with respect to the 38 // given writing mode. 39 if (aBaselineGroup == BaselineSharingGroup::Last) { 40 return -marginBlockEnd; 41 } 42 return BSize(aWM) + marginBlockEnd; 43 } 44 45 Maybe<nscoord> nsTableWrapperFrame::GetNaturalBaselineBOffset( 46 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 47 BaselineExportContext aExportContext) const { 48 // Baseline is determined by row 49 // (https://drafts.csswg.org/css-align-3/#baseline-export). If the row 50 // direction is going to be orthogonal to the parent's writing mode, the 51 // resulting baseline wouldn't be valid, so we use the fallback baseline 52 // instead. 53 if (StyleDisplay()->IsContainLayout() || 54 GetWritingMode().IsOrthogonalTo(aWM)) { 55 return Nothing{}; 56 } 57 auto* innerTable = InnerTableFrame(); 58 return innerTable 59 ->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext) 60 .map([this, aWM, aBaselineGroup, innerTable](nscoord aBaseline) { 61 auto bStart = innerTable->BStart(aWM, mRect.Size()); 62 if (aBaselineGroup == BaselineSharingGroup::First) { 63 return aBaseline + bStart; 64 } 65 auto bEnd = bStart + innerTable->BSize(aWM); 66 return BSize(aWM) - (bEnd - aBaseline); 67 }); 68 } 69 70 nsTableWrapperFrame::nsTableWrapperFrame(ComputedStyle* aStyle, 71 nsPresContext* aPresContext, 72 ClassID aID) 73 : nsContainerFrame(aStyle, aPresContext, aID) {} 74 75 nsTableWrapperFrame::~nsTableWrapperFrame() = default; 76 77 NS_QUERYFRAME_HEAD(nsTableWrapperFrame) 78 NS_QUERYFRAME_ENTRY(nsTableWrapperFrame) 79 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 80 81 #ifdef ACCESSIBILITY 82 a11y::AccType nsTableWrapperFrame::AccessibleType() { 83 return a11y::eHTMLTableType; 84 } 85 #endif 86 87 void nsTableWrapperFrame::Destroy(DestroyContext& aContext) { 88 DestroyAbsoluteFrames(aContext); 89 nsContainerFrame::Destroy(aContext); 90 } 91 92 void nsTableWrapperFrame::AppendFrames(ChildListID aListID, 93 nsFrameList&& aFrameList) { 94 // We only have two child frames: the inner table and a caption frame. 95 // The inner frame is provided when we're initialized, and it cannot change 96 MOZ_ASSERT(FrameChildListID::Principal == aListID, "unexpected child list"); 97 MOZ_ASSERT(aFrameList.IsEmpty() || aFrameList.FirstChild()->IsTableCaption(), 98 "Why are we appending non-caption frames?"); 99 nsContainerFrame::AppendFrames(aListID, std::move(aFrameList)); 100 // The presence of caption frames makes us sort our display list differently, 101 // so mark us as changed for the new ordering. 102 MarkNeedsDisplayItemRebuild(); 103 } 104 105 void nsTableWrapperFrame::InsertFrames( 106 ChildListID aListID, nsIFrame* aPrevFrame, 107 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { 108 MOZ_ASSERT(FrameChildListID::Principal == aListID, "unexpected child list"); 109 MOZ_ASSERT(aFrameList.IsEmpty() || aFrameList.FirstChild()->IsTableCaption(), 110 "Why are we inserting non-caption frames?"); 111 MOZ_ASSERT(!aPrevFrame || aPrevFrame->GetParent() == this, 112 "inserting after sibling frame with different parent"); 113 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, 114 std::move(aFrameList)); 115 MarkNeedsDisplayItemRebuild(); 116 } 117 118 void nsTableWrapperFrame::RemoveFrame(DestroyContext& aContext, 119 ChildListID aListID, 120 nsIFrame* aOldFrame) { 121 // We only have two child frames: the inner table and one caption frame. 122 // The inner frame can't be removed so this should be the caption 123 MOZ_ASSERT(aOldFrame->IsTableCaption(), "can't remove inner frame"); 124 nsContainerFrame::RemoveFrame(aContext, aListID, aOldFrame); 125 MarkNeedsDisplayItemRebuild(); 126 } 127 128 void nsTableWrapperFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 129 const nsDisplayListSet& aLists) { 130 // No border or background is painted because they belong to the inner table. 131 // The outline belongs to the wrapper frame so it can contain the caption. 132 133 // If there's no caption, take a short cut to avoid having to create 134 // the special display list set and then sort it. 135 if (nsIFrame* inner = mFrames.OnlyChild()) { 136 BuildDisplayListForChild(aBuilder, inner, aLists); 137 DisplayOutline(aBuilder, aLists); 138 return; 139 } 140 141 MOZ_ASSERT(mFrames.FirstChild()); 142 MOZ_ASSERT(mFrames.FirstChild()->IsTableFrame()); 143 144 nsDisplayListCollection set(aBuilder); 145 nsDisplayListSet captionSet(set, set.BlockBorderBackgrounds()); 146 for (auto* frame : mFrames) { 147 const bool isTable = frame->IsTableFrame(); 148 auto& setForFrame = isTable ? set : captionSet; 149 BuildDisplayListForChild(aBuilder, frame, setForFrame); 150 if (!isTable) { 151 // FIXME(emilio, bug 144517): Historically we haven't displayed / laid 152 // out multiple captions. This preserves that behavior. 153 break; 154 } 155 } 156 157 // Now we have to sort everything by content order, since the caption 158 // may be somewhere inside the table. 159 // We don't sort BlockBorderBackgrounds and BorderBackgrounds because the 160 // display items in those lists should stay out of content order in order to 161 // follow the rules in https://www.w3.org/TR/CSS21/zindex.html#painting-order 162 // and paint the caption background after all of the rest. 163 set.Floats()->SortByContentOrder(GetContent()); 164 set.Content()->SortByContentOrder(GetContent()); 165 set.PositionedDescendants()->SortByContentOrder(GetContent()); 166 set.Outlines()->SortByContentOrder(GetContent()); 167 set.MoveTo(aLists); 168 169 DisplayOutline(aBuilder, aLists); 170 } 171 172 ComputedStyle* nsTableWrapperFrame::GetParentComputedStyle( 173 nsIFrame** aProviderFrame) const { 174 // The table wrapper frame and the (inner) table frame split the style 175 // data by giving the table frame the ComputedStyle associated with 176 // the table content node and creating a ComputedStyle for the wrapper 177 // frame that is a *child* of the table frame's ComputedStyle, 178 // matching the ::-moz-table-wrapper pseudo-element. html.css has a 179 // rule that causes that pseudo-element (and thus the wrapper table) 180 // to inherit *some* style properties from the table frame. The 181 // children of the table inherit directly from the inner table, and 182 // the table wrapper's ComputedStyle is a leaf. 183 184 return (*aProviderFrame = InnerTableFrame())->Style(); 185 } 186 187 nscoord nsTableWrapperFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 188 IntrinsicISizeType aType) { 189 nscoord iSize = nsLayoutUtils::IntrinsicForContainer( 190 aInput.mContext, InnerTableFrame(), aType); 191 192 { 193 // If aFrame is a container for font size inflation, then shrink 194 // wrapping inside of it should not apply font size inflation. 195 AutoMaybeDisableFontInflation an(this); 196 197 // Tables can't shrink smaller than their intrinsic minimum inline size, 198 // no matter what. 199 const IntrinsicSizeInput input(aInput.mContext, Nothing(), Nothing()); 200 201 // GetMinISize() returns a content-box inline size, but we need the 202 // margin-box inline size as the contribution in the inline axis. 203 const IntrinsicSizeOffsetData offset = 204 InnerTableFrame()->IntrinsicISizeOffsets(); 205 const nscoord innerTableMinISize = 206 InnerTableFrame()->GetMinISize(input) + offset.MarginBorderPadding(); 207 iSize = std::max(iSize, innerTableMinISize); 208 } 209 210 if (nsIFrame* caption = GetCaption()) { 211 // The table wrapper's intrinsic inline size should be as least as large as 212 // caption's min inline size. 213 const nscoord capMinISize = nsLayoutUtils::IntrinsicForContainer( 214 aInput.mContext, caption, IntrinsicISizeType::MinISize); 215 iSize = std::max(iSize, capMinISize); 216 } 217 return iSize; 218 } 219 220 LogicalSize nsTableWrapperFrame::InnerTableShrinkWrapSize( 221 const SizeComputationInput& aSizingInput, nsTableFrame* aTableFrame, 222 WritingMode aWM, const LogicalSize& aCBSize, nscoord aAvailableISize, 223 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) const { 224 MOZ_ASSERT(InnerTableFrame() == aTableFrame); 225 226 AutoMaybeDisableFontInflation an(aTableFrame); 227 228 Maybe<LogicalMargin> collapseBorder; 229 Maybe<LogicalMargin> collapsePadding; 230 aTableFrame->GetCollapsedBorderPadding(collapseBorder, collapsePadding); 231 232 SizeComputationInput input(aTableFrame, aSizingInput.mRenderingContext, aWM, 233 aCBSize.ISize(aWM), collapseBorder, 234 collapsePadding); 235 LogicalSize marginSize(aWM); // Inner table doesn't have any margin 236 LogicalSize bpSize = input.ComputedLogicalBorderPadding(aWM).Size(aWM); 237 238 // Note that we pass an empty caption-area here (rather than the caption's 239 // actual size). This is fine because: 240 // 241 // 1) nsTableWrapperFrame::ComputeSize() only uses the size returned by this 242 // method (indirectly via calling nsTableWrapperFrame::ComputeAutoSize()) 243 // if it get a aSizeOverrides arg containing any size overrides with 244 // mApplyOverridesVerbatim=true. The aSizeOverrides arg is passed to this 245 // method without any modifications. 246 // 247 // 2) With 1), that means the aSizeOverrides passing into this method should 248 // be applied to the inner table directly, so we don't need to subtract 249 // caption-area when preparing innerOverrides for 250 // nsTableFrame::ComputeSize(). 251 StyleSizeOverrides innerOverrides = ComputeSizeOverridesForInnerTable( 252 aTableFrame, aSizeOverrides, bpSize, /* aBSizeOccupiedByCaption = */ 0); 253 auto size = aTableFrame 254 ->ComputeSize(input, aWM, aCBSize, aAvailableISize, 255 marginSize, bpSize, innerOverrides, aFlags) 256 .mLogicalSize; 257 size.ISize(aWM) += bpSize.ISize(aWM); 258 if (size.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { 259 size.BSize(aWM) += bpSize.BSize(aWM); 260 } 261 return size; 262 } 263 264 LogicalSize nsTableWrapperFrame::CaptionShrinkWrapSize( 265 const SizeComputationInput& aSizingInput, nsIFrame* aCaptionFrame, 266 WritingMode aWM, const LogicalSize& aCBSize, nscoord aAvailableISize, 267 ComputeSizeFlags aFlags) const { 268 MOZ_ASSERT(aCaptionFrame != mFrames.FirstChild()); 269 270 AutoMaybeDisableFontInflation an(aCaptionFrame); 271 272 SizeComputationInput input(aCaptionFrame, aSizingInput.mRenderingContext, aWM, 273 aCBSize.ISize(aWM)); 274 LogicalSize marginSize = input.ComputedLogicalMargin(aWM).Size(aWM); 275 LogicalSize bpSize = input.ComputedLogicalBorderPadding(aWM).Size(aWM); 276 277 auto size = aCaptionFrame 278 ->ComputeSize(input, aWM, aCBSize, aAvailableISize, 279 marginSize, bpSize, {}, aFlags) 280 .mLogicalSize; 281 size.ISize(aWM) += (marginSize.ISize(aWM) + bpSize.ISize(aWM)); 282 if (size.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { 283 size.BSize(aWM) += (marginSize.BSize(aWM) + bpSize.BSize(aWM)); 284 } 285 return size; 286 } 287 288 StyleSize nsTableWrapperFrame::ReduceStyleSizeBy( 289 const StyleSize& aStyleSize, const nscoord aAmountToReduce) const { 290 MOZ_ASSERT(aStyleSize.ConvertsToLength(), "Only handles 'Length' StyleSize!"); 291 const nscoord size = std::max(0, aStyleSize.ToLength() - aAmountToReduce); 292 return StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(size)); 293 } 294 295 StyleSizeOverrides nsTableWrapperFrame::ComputeSizeOverridesForInnerTable( 296 const nsTableFrame* aTableFrame, 297 const StyleSizeOverrides& aWrapperSizeOverrides, 298 const LogicalSize& aBorderPadding, nscoord aBSizeOccupiedByCaption) const { 299 if (aWrapperSizeOverrides.mApplyOverridesVerbatim || 300 !aWrapperSizeOverrides.HasAnyLengthOverrides()) { 301 // We are asked to apply the size overrides directly to the inner table, or 302 // there's no 'Length' size overrides. No need to tweak the size overrides. 303 return aWrapperSizeOverrides; 304 } 305 306 const auto wm = aTableFrame->GetWritingMode(); 307 LogicalSize areaOccupied(wm, 0, aBSizeOccupiedByCaption); 308 if (aTableFrame->StylePosition()->mBoxSizing == StyleBoxSizing::Content) { 309 // If the inner table frame has 'box-sizing: content', enlarge the occupied 310 // area by adding border & padding because they should also be subtracted 311 // from the size overrides. 312 areaOccupied += aBorderPadding; 313 } 314 315 StyleSizeOverrides innerSizeOverrides; 316 const auto& wrapperISize = aWrapperSizeOverrides.mStyleISize; 317 if (wrapperISize) { 318 MOZ_ASSERT(!wrapperISize->HasPercent(), 319 "Table doesn't support size overrides containing percentages!"); 320 innerSizeOverrides.mStyleISize.emplace( 321 wrapperISize->ConvertsToLength() 322 ? ReduceStyleSizeBy(*wrapperISize, areaOccupied.ISize(wm)) 323 : *wrapperISize); 324 } 325 326 const auto& wrapperBSize = aWrapperSizeOverrides.mStyleBSize; 327 if (wrapperBSize) { 328 MOZ_ASSERT(!wrapperBSize->HasPercent(), 329 "Table doesn't support size overrides containing percentages!"); 330 innerSizeOverrides.mStyleBSize.emplace( 331 wrapperBSize->ConvertsToLength() 332 ? ReduceStyleSizeBy(*wrapperBSize, areaOccupied.BSize(wm)) 333 : *wrapperBSize); 334 } 335 336 return innerSizeOverrides; 337 } 338 339 /* virtual */ 340 nsIFrame::SizeComputationResult nsTableWrapperFrame::ComputeSize( 341 const SizeComputationInput& aSizingInput, WritingMode aWM, 342 const LogicalSize& aCBSize, nscoord aAvailableISize, 343 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 344 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 345 auto result = nsContainerFrame::ComputeSize( 346 aSizingInput, aWM, aCBSize, aAvailableISize, aMargin, aBorderPadding, 347 aSizeOverrides, aFlags); 348 349 if (aSizeOverrides.mApplyOverridesVerbatim && 350 aSizeOverrides.HasAnyOverrides()) { 351 // We are asked to apply the size overrides directly to the inner table, but 352 // we still want ourselves to remain 'auto'-sized and shrink-wrapping our 353 // children's sizes. (Table wrapper frames always have 'auto' inline-size 354 // and block-size, since we don't inherit those properties from inner table, 355 // and authors can't target them with styling.) 356 auto size = 357 ComputeAutoSize(aSizingInput, aWM, aCBSize, aAvailableISize, aMargin, 358 aBorderPadding, aSizeOverrides, aFlags); 359 result.mLogicalSize = size; 360 } 361 362 return result; 363 } 364 365 /* virtual */ 366 LogicalSize nsTableWrapperFrame::ComputeAutoSize( 367 const SizeComputationInput& aSizingInput, WritingMode aWM, 368 const LogicalSize& aCBSize, nscoord aAvailableISize, 369 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 370 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 371 nscoord kidAvailableISize = aAvailableISize - aMargin.ISize(aWM); 372 NS_ASSERTION(aBorderPadding.IsAllZero(), 373 "Table wrapper frames cannot have borders or paddings"); 374 375 // When we're shrink-wrapping, our auto size needs to wrap around the 376 // actual size of the table, which (if it is specified as a percent) 377 // could be something that is not reflected in our GetMinISize and 378 // GetPrefISize. See bug 349457 for an example. 379 const ComputeSizeFlags flags = CreateComputeSizeFlagsForChild(); 380 381 // Match the logic in Reflow() that sets aside space for the caption. 382 Maybe<StyleCaptionSide> captionSide = GetCaptionSide(); 383 384 const LogicalSize innerTableSize = 385 InnerTableShrinkWrapSize(aSizingInput, InnerTableFrame(), aWM, aCBSize, 386 kidAvailableISize, aSizeOverrides, flags); 387 if (!captionSide) { 388 return innerTableSize; 389 } 390 const LogicalSize captionSize = 391 CaptionShrinkWrapSize(aSizingInput, GetCaption(), aWM, aCBSize, 392 innerTableSize.ISize(aWM), flags); 393 const nscoord iSize = 394 std::max(innerTableSize.ISize(aWM), captionSize.ISize(aWM)); 395 nscoord bSize = NS_UNCONSTRAINEDSIZE; 396 if (innerTableSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE && 397 captionSize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) { 398 bSize = innerTableSize.BSize(aWM) + captionSize.BSize(aWM); 399 } 400 return LogicalSize(aWM, iSize, bSize); 401 } 402 403 Maybe<StyleCaptionSide> nsTableWrapperFrame::GetCaptionSide() const { 404 if (!HasCaption()) { 405 return Nothing(); 406 } 407 return Some(GetCaption()->StyleTableBorder()->mCaptionSide); 408 } 409 410 nscoord nsTableWrapperFrame::ComputeFinalBSize( 411 const LogicalSize& aInnerSize, const LogicalSize& aCaptionSize, 412 const LogicalMargin& aCaptionMargin, const WritingMode aWM) const { 413 // negative sizes can upset overflow-area code 414 return std::max(0, aInnerSize.BSize(aWM) + 415 std::max(0, aCaptionSize.BSize(aWM) + 416 aCaptionMargin.BStartEnd(aWM))); 417 } 418 419 void nsTableWrapperFrame::GetCaptionOrigin(StyleCaptionSide aCaptionSide, 420 const LogicalSize& aInnerSize, 421 const LogicalSize& aCaptionSize, 422 LogicalMargin& aCaptionMargin, 423 LogicalPoint& aOrigin, 424 WritingMode aWM) const { 425 aOrigin.I(aWM) = aOrigin.B(aWM) = 0; 426 if ((NS_UNCONSTRAINEDSIZE == aInnerSize.ISize(aWM)) || 427 (NS_UNCONSTRAINEDSIZE == aInnerSize.BSize(aWM)) || 428 (NS_UNCONSTRAINEDSIZE == aCaptionSize.ISize(aWM)) || 429 (NS_UNCONSTRAINEDSIZE == aCaptionSize.BSize(aWM))) { 430 return; 431 } 432 if (!HasCaption()) { 433 return; 434 } 435 436 NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) && 437 NS_AUTOMARGIN != aCaptionMargin.BStart(aWM) && 438 NS_AUTOMARGIN != aCaptionMargin.BEnd(aWM), 439 "The computed caption margin is auto?"); 440 441 aOrigin.I(aWM) = aCaptionMargin.IStart(aWM); 442 443 // block-dir computation 444 switch (aCaptionSide) { 445 case StyleCaptionSide::Bottom: 446 aOrigin.B(aWM) = aInnerSize.BSize(aWM) + aCaptionMargin.BStart(aWM); 447 break; 448 case StyleCaptionSide::Top: 449 aOrigin.B(aWM) = aCaptionMargin.BStart(aWM); 450 break; 451 } 452 } 453 454 void nsTableWrapperFrame::GetInnerOrigin(const MaybeCaptionSide& aCaptionSide, 455 const LogicalSize& aCaptionSize, 456 const LogicalMargin& aCaptionMargin, 457 const LogicalSize& aInnerSize, 458 LogicalPoint& aOrigin, 459 WritingMode aWM) const { 460 NS_ASSERTION(NS_AUTOMARGIN != aCaptionMargin.IStart(aWM) && 461 NS_AUTOMARGIN != aCaptionMargin.IEnd(aWM), 462 "The computed caption margin is auto?"); 463 464 aOrigin.I(aWM) = aOrigin.B(aWM) = 0; 465 if ((NS_UNCONSTRAINEDSIZE == aInnerSize.ISize(aWM)) || 466 (NS_UNCONSTRAINEDSIZE == aInnerSize.BSize(aWM)) || 467 (NS_UNCONSTRAINEDSIZE == aCaptionSize.ISize(aWM)) || 468 (NS_UNCONSTRAINEDSIZE == aCaptionSize.BSize(aWM))) { 469 return; 470 } 471 472 // block-dir computation 473 if (aCaptionSide) { 474 switch (*aCaptionSide) { 475 case StyleCaptionSide::Bottom: 476 // Leave at zero. 477 break; 478 case StyleCaptionSide::Top: 479 aOrigin.B(aWM) = 480 aCaptionSize.BSize(aWM) + aCaptionMargin.BStartEnd(aWM); 481 break; 482 } 483 } 484 } 485 486 ComputeSizeFlags nsTableWrapperFrame::CreateComputeSizeFlagsForChild() const { 487 // Shrink-wrap child frames by default, except if we're a stretched grid item. 488 if (MOZ_UNLIKELY(IsGridItem())) { 489 auto* gridContainer = static_cast<nsGridContainerFrame*>(GetParent()); 490 if (gridContainer->GridItemShouldStretch(this, LogicalAxis::Inline)) { 491 return {}; 492 } 493 } 494 return {ComputeSizeFlag::ShrinkWrap}; 495 } 496 497 void nsTableWrapperFrame::CreateReflowInputForInnerTable( 498 nsPresContext* aPresContext, nsTableFrame* aTableFrame, 499 const ReflowInput& aOuterRI, Maybe<ReflowInput>& aChildRI, 500 const nscoord aAvailISize, nscoord aBSizeOccupiedByCaption) const { 501 MOZ_ASSERT(InnerTableFrame() == aTableFrame); 502 503 const WritingMode wm = aTableFrame->GetWritingMode(); 504 // If we have a caption occupied our content-box area, reduce the available 505 // block-size by the amount. 506 nscoord availBSize = aOuterRI.AvailableBSize(); 507 if (availBSize != NS_UNCONSTRAINEDSIZE) { 508 availBSize = std::max(0, availBSize - aBSizeOccupiedByCaption); 509 } 510 const LogicalSize availSize(wm, aAvailISize, availBSize); 511 512 // For inner table frames, the containing block is the same as for the outer 513 // table frame. 514 Maybe<LogicalSize> cbSize = Some(aOuterRI.mContainingBlockSize); 515 516 // However, if we are a grid item, the CB size needs to subtract our margins 517 // and the area occupied by the caption. 518 // 519 // Note that inner table computed margins are always zero, they're inherited 520 // by the table wrapper, so we need to get our margin from aOuterRI. 521 if (IsGridItem()) { 522 const LogicalMargin margin = aOuterRI.ComputedLogicalMargin(wm); 523 cbSize->ISize(wm) = std::max(0, cbSize->ISize(wm) - margin.IStartEnd(wm)); 524 if (cbSize->BSize(wm) != NS_UNCONSTRAINEDSIZE) { 525 cbSize->BSize(wm) = std::max(0, cbSize->BSize(wm) - margin.BStartEnd(wm) - 526 aBSizeOccupiedByCaption); 527 } 528 } 529 530 ComputeSizeFlags csFlags = CreateComputeSizeFlagsForChild(); 531 if (!aTableFrame->IsBorderCollapse() && 532 !aOuterRI.mStyleSizeOverrides.HasAnyOverrides()) { 533 // We are not border-collapsed and not given any size overrides. It's 534 // sufficient to call the standard ReflowInput constructor. 535 aChildRI.emplace(aPresContext, aOuterRI, aTableFrame, availSize, cbSize, 536 ReflowInput::InitFlags{}, StyleSizeOverrides{}, csFlags); 537 return; 538 } 539 540 Maybe<LogicalMargin> borderPadding; 541 Maybe<LogicalMargin> padding; 542 { 543 // Compute inner table frame's border & padding because we may need to 544 // reduce the size for inner table's size overrides. We won't waste time if 545 // they are not used, because we can use them directly by passing them into 546 // ReflowInput::Init(). 547 Maybe<LogicalMargin> collapseBorder; 548 Maybe<LogicalMargin> collapsePadding; 549 aTableFrame->GetCollapsedBorderPadding(collapseBorder, collapsePadding); 550 SizeComputationInput input(aTableFrame, aOuterRI.mRenderingContext, wm, 551 cbSize->ISize(wm), collapseBorder, 552 collapsePadding); 553 borderPadding.emplace(input.ComputedLogicalBorderPadding(wm)); 554 padding.emplace(input.ComputedLogicalPadding(wm)); 555 } 556 557 StyleSizeOverrides innerOverrides = ComputeSizeOverridesForInnerTable( 558 aTableFrame, aOuterRI.mStyleSizeOverrides, borderPadding->Size(wm), 559 aBSizeOccupiedByCaption); 560 561 aChildRI.emplace(aPresContext, aOuterRI, aTableFrame, availSize, Nothing(), 562 ReflowInput::InitFlag::CallerWillInit, innerOverrides, 563 csFlags); 564 aChildRI->Init(aPresContext, cbSize, Some(*borderPadding - *padding), 565 padding); 566 } 567 568 void nsTableWrapperFrame::CreateReflowInputForCaption( 569 nsPresContext* aPresContext, nsIFrame* aCaptionFrame, 570 const ReflowInput& aOuterRI, Maybe<ReflowInput>& aChildRI, 571 const nscoord aAvailISize) const { 572 MOZ_ASSERT(aCaptionFrame == GetCaption()); 573 574 const WritingMode wm = aCaptionFrame->GetWritingMode(); 575 576 // Use unconstrained available block-size so that the caption is always 577 // fully-complete. 578 const LogicalSize availSize(wm, aAvailISize, NS_UNCONSTRAINEDSIZE); 579 aChildRI.emplace(aPresContext, aOuterRI, aCaptionFrame, availSize); 580 581 // See if we need to reset mIsTopOfPage flag. 582 if (aChildRI->mFlags.mIsTopOfPage) { 583 if (auto captionSide = GetCaptionSide()) { 584 if (*captionSide == StyleCaptionSide::Bottom) { 585 aChildRI->mFlags.mIsTopOfPage = false; 586 } 587 } 588 } 589 } 590 591 void nsTableWrapperFrame::ReflowChild(nsPresContext* aPresContext, 592 nsIFrame* aChildFrame, 593 const ReflowInput& aChildRI, 594 ReflowOutput& aMetrics, 595 nsReflowStatus& aStatus) { 596 // Using zero as containerSize here because we want consistency between 597 // the GetLogicalPosition and ReflowChild calls, to avoid unnecessarily 598 // changing the frame's coordinates; but we don't yet know its final 599 // position anyway so the actual value is unimportant. 600 const nsSize zeroCSize; 601 WritingMode wm = aChildRI.GetWritingMode(); 602 603 // Use the current position as a best guess for placement. 604 LogicalPoint childPt = aChildFrame->GetLogicalPosition(wm, zeroCSize); 605 ReflowChildFlags flags = ReflowChildFlags::NoMoveFrame; 606 607 // We don't want to delete our next-in-flow's child if it's an inner table 608 // frame, because table wrapper frames always assume that their inner table 609 // frames don't go away. If a table wrapper frame is removed because it is 610 // a next-in-flow of an already complete table wrapper frame, then it will 611 // take care of removing it's inner table frame. 612 if (aChildFrame == InnerTableFrame()) { 613 flags |= ReflowChildFlags::NoDeleteNextInFlowChild; 614 } 615 616 nsContainerFrame::ReflowChild(aChildFrame, aPresContext, aMetrics, aChildRI, 617 wm, childPt, zeroCSize, flags, aStatus); 618 } 619 620 void nsTableWrapperFrame::UpdateOverflowAreas(ReflowOutput& aMet) { 621 aMet.SetOverflowAreasToDesiredBounds(); 622 for (auto* frame : mFrames) { 623 ConsiderChildOverflow(aMet.mOverflowAreas, frame); 624 } 625 } 626 627 void nsTableWrapperFrame::Reflow(nsPresContext* aPresContext, 628 ReflowOutput& aDesiredSize, 629 const ReflowInput& aOuterRI, 630 nsReflowStatus& aStatus) { 631 MarkInReflow(); 632 DO_GLOBAL_REFLOW_COUNT("nsTableWrapperFrame"); 633 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 634 635 // Initialize out parameters 636 aDesiredSize.ClearSize(); 637 638 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 639 // Set up our kids. They're already present, on an overflow list, 640 // or there are none so we'll create them now 641 MoveOverflowToChildList(); 642 } 643 644 Maybe<ReflowInput> captionRI; 645 Maybe<ReflowInput> innerRI; 646 647 nsRect origCaptionRect; 648 nsRect origCaptionInkOverflow; 649 bool captionFirstReflow = false; 650 if (nsIFrame* caption = GetCaption()) { 651 origCaptionRect = caption->GetRect(); 652 origCaptionInkOverflow = caption->InkOverflowRect(); 653 captionFirstReflow = caption->HasAnyStateBits(NS_FRAME_FIRST_REFLOW); 654 } 655 656 // ComputeAutoSize has to match this logic. 657 WritingMode wm = aOuterRI.GetWritingMode(); 658 Maybe<StyleCaptionSide> captionSide = GetCaptionSide(); 659 const nscoord contentBoxISize = aOuterRI.ComputedSize(wm).ISize(wm); 660 661 MOZ_ASSERT(HasCaption() == captionSide.isSome()); 662 663 // Compute the table's size first, and then prevent the caption from 664 // being larger in the inline dir unless it has to be. 665 // 666 // Note that CSS 2.1 (but not 2.0) says: 667 // The width of the anonymous box is the border-edge width of the 668 // table box inside it 669 // We don't actually make our anonymous box that isize (if we did, 670 // it would break 'auto' margins), but this effectively does that. 671 CreateReflowInputForInnerTable(aPresContext, InnerTableFrame(), aOuterRI, 672 innerRI, contentBoxISize); 673 674 // First reflow the caption. 675 ReflowOutput captionMet(wm); 676 LogicalSize captionSize(wm); 677 LogicalMargin captionMargin(wm); 678 if (captionSide) { 679 // It's good that CSS 2.1 says not to include margins, since we can't, since 680 // they already been converted so they exactly fill the available isize 681 // (ignoring the margin on one side if neither are auto). (We take 682 // advantage of that later when we call GetCaptionOrigin, though.) 683 nscoord innerBorderISize = 684 innerRI->ComputedSizeWithBorderPadding(wm).ISize(wm); 685 CreateReflowInputForCaption(aPresContext, GetCaption(), aOuterRI, captionRI, 686 innerBorderISize); 687 688 // We intentionally don't merge capStatus into aStatus, since we currently 689 // can't handle caption continuations, but we probably should. 690 nsReflowStatus capStatus; 691 ReflowChild(aPresContext, GetCaption(), *captionRI, captionMet, capStatus); 692 captionSize = captionMet.Size(wm); 693 captionMargin = captionRI->ComputedLogicalMargin(wm); 694 nscoord bSizeOccupiedByCaption = 695 captionSize.BSize(wm) + captionMargin.BStartEnd(wm); 696 if (bSizeOccupiedByCaption) { 697 // Reset the inner table's ReflowInput to reduce various sizes because of 698 // the area occupied by caption. 699 innerRI.reset(); 700 CreateReflowInputForInnerTable(aPresContext, InnerTableFrame(), aOuterRI, 701 innerRI, contentBoxISize, 702 bSizeOccupiedByCaption); 703 } 704 } 705 706 // Now we know how much to reduce the block-size for the inner table to 707 // account for captions. Reflow the inner table. 708 ReflowOutput innerMet(innerRI->GetWritingMode()); 709 ReflowChild(aPresContext, InnerTableFrame(), *innerRI, innerMet, aStatus); 710 LogicalSize innerSize(wm, innerMet.ISize(wm), innerMet.BSize(wm)); 711 712 // Now that we've reflowed both we can place them. 713 // Compute the desiredSize so that we can use it as the containerSize 714 // for the FinishReflowChild calls below. 715 LogicalSize desiredSize(wm); 716 717 // We have zero border and padding, so content-box inline-size is our desired 718 // border-box inline-size. 719 desiredSize.ISize(wm) = contentBoxISize; 720 desiredSize.BSize(wm) = 721 ComputeFinalBSize(innerSize, captionSize, captionMargin, wm); 722 723 aDesiredSize.SetSize(wm, desiredSize); 724 nsSize containerSize = aDesiredSize.PhysicalSize(); 725 726 MOZ_ASSERT(HasCaption() == captionSide.isSome()); 727 if (nsIFrame* caption = GetCaption()) { 728 LogicalPoint captionOrigin(wm); 729 GetCaptionOrigin(*captionSide, innerSize, captionSize, captionMargin, 730 captionOrigin, wm); 731 FinishReflowChild(caption, aPresContext, captionMet, captionRI.ptr(), wm, 732 captionOrigin, containerSize, 733 ReflowChildFlags::ApplyRelativePositioning); 734 captionRI.reset(); 735 } 736 // XXX If the bsize is constrained then we need to check whether 737 // everything still fits... 738 739 LogicalPoint innerOrigin(wm); 740 GetInnerOrigin(captionSide, captionSize, captionMargin, innerSize, 741 innerOrigin, wm); 742 // NOTE: Relative positioning on the table applies to the whole table wrapper. 743 FinishReflowChild(InnerTableFrame(), aPresContext, innerMet, innerRI.ptr(), 744 wm, innerOrigin, containerSize, ReflowChildFlags::Default); 745 innerRI.reset(); 746 747 if (HasCaption()) { 748 nsTableFrame::InvalidateTableFrame(GetCaption(), origCaptionRect, 749 origCaptionInkOverflow, 750 captionFirstReflow); 751 } 752 753 UpdateOverflowAreas(aDesiredSize); 754 755 if (GetPrevInFlow()) { 756 ReflowOverflowContainerChildren(aPresContext, aOuterRI, 757 aDesiredSize.mOverflowAreas, 758 ReflowChildFlags::Default, aStatus); 759 } 760 761 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aOuterRI, aStatus); 762 } 763 764 /* ----- global methods ----- */ 765 766 nsIContent* nsTableWrapperFrame::GetCellAt(uint32_t aRowIdx, 767 uint32_t aColIdx) const { 768 nsTableCellMap* cellMap = InnerTableFrame()->GetCellMap(); 769 if (!cellMap) { 770 return nullptr; 771 } 772 773 nsTableCellFrame* cell = cellMap->GetCellInfoAt(aRowIdx, aColIdx); 774 if (!cell) { 775 return nullptr; 776 } 777 778 return cell->GetContent(); 779 } 780 781 nsTableWrapperFrame* NS_NewTableWrapperFrame(PresShell* aPresShell, 782 ComputedStyle* aStyle) { 783 return new (aPresShell) 784 nsTableWrapperFrame(aStyle, aPresShell->GetPresContext()); 785 } 786 787 NS_IMPL_FRAMEARENA_HELPERS(nsTableWrapperFrame) 788 789 #ifdef DEBUG_FRAME_DUMP 790 nsresult nsTableWrapperFrame::GetFrameName(nsAString& aResult) const { 791 return MakeFrameName(u"TableWrapper"_ns, aResult); 792 } 793 #endif