nsFieldSetFrame.cpp (37689B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsFieldSetFrame.h" 8 9 #include <algorithm> 10 11 #include "gfxContext.h" 12 #include "mozilla/Baseline.h" 13 #include "mozilla/Likely.h" 14 #include "mozilla/Maybe.h" 15 #include "mozilla/PresShell.h" 16 #include "mozilla/ScrollContainerFrame.h" 17 #include "mozilla/dom/HTMLLegendElement.h" 18 #include "mozilla/gfx/2D.h" 19 #include "mozilla/webrender/WebRenderAPI.h" 20 #include "nsBlockFrame.h" 21 #include "nsCSSAnonBoxes.h" 22 #include "nsCSSFrameConstructor.h" 23 #include "nsCSSRendering.h" 24 #include "nsDisplayList.h" 25 #include "nsGkAtoms.h" 26 #include "nsIFrameInlines.h" 27 #include "nsLayoutUtils.h" 28 #include "nsStyleConsts.h" 29 30 using namespace mozilla; 31 using namespace mozilla::gfx; 32 using namespace mozilla::layout; 33 using image::ImgDrawResult; 34 35 nsContainerFrame* NS_NewFieldSetFrame(PresShell* aPresShell, 36 ComputedStyle* aStyle) { 37 return new (aPresShell) nsFieldSetFrame(aStyle, aPresShell->GetPresContext()); 38 } 39 40 NS_IMPL_FRAMEARENA_HELPERS(nsFieldSetFrame) 41 NS_QUERYFRAME_HEAD(nsFieldSetFrame) 42 NS_QUERYFRAME_ENTRY(nsFieldSetFrame) 43 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 44 45 nsFieldSetFrame::nsFieldSetFrame(ComputedStyle* aStyle, 46 nsPresContext* aPresContext) 47 : nsContainerFrame(aStyle, aPresContext, kClassID), 48 mLegendRect(GetWritingMode()) { 49 mLegendSpace = 0; 50 } 51 52 nsRect nsFieldSetFrame::VisualBorderRectRelativeToSelf() const { 53 WritingMode wm = GetWritingMode(); 54 LogicalRect r(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm)); 55 nsSize containerSize = r.Size(wm).GetPhysicalSize(wm); 56 nsIFrame* legend = GetLegend(); 57 if (legend && !GetPrevInFlow()) { 58 nscoord legendSize = legend->GetLogicalSize(wm).BSize(wm); 59 auto legendMargin = legend->GetLogicalUsedMargin(wm); 60 nscoord legendStartMargin = legendMargin.BStart(wm); 61 nscoord legendEndMargin = legendMargin.BEnd(wm); 62 nscoord border = GetUsedBorder().Side(wm.PhysicalSide(LogicalSide::BStart)); 63 // Calculate the offset from the border area block-axis start edge needed to 64 // center-align our border with the legend's border-box (in the block-axis). 65 nscoord off = (legendStartMargin + legendSize / 2) - border / 2; 66 // We don't want to display our border above our border area. 67 if (off > nscoord(0)) { 68 nscoord marginBoxSize = legendStartMargin + legendSize + legendEndMargin; 69 if (marginBoxSize > border) { 70 // We don't want to display our border below the legend's margin-box, 71 // so we align it to the block-axis end if that happens. 72 nscoord overflow = off + border - marginBoxSize; 73 if (overflow > nscoord(0)) { 74 off -= overflow; 75 } 76 r.BStart(wm) += off; 77 r.BSize(wm) -= off; 78 } 79 } 80 } 81 return r.GetPhysicalRect(wm, containerSize); 82 } 83 84 nsContainerFrame* nsFieldSetFrame::GetInner() const { 85 for (nsIFrame* child : mFrames) { 86 if (child->Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) { 87 return static_cast<nsContainerFrame*>(child); 88 } 89 } 90 return nullptr; 91 } 92 93 nsIFrame* nsFieldSetFrame::GetLegend() const { 94 for (nsIFrame* child : mFrames) { 95 if (child->Style()->GetPseudoType() != PseudoStyleType::fieldsetContent) { 96 return child; 97 } 98 } 99 return nullptr; 100 } 101 102 namespace mozilla { 103 104 class nsDisplayFieldSetBorder final : public nsPaintedDisplayItem { 105 public: 106 nsDisplayFieldSetBorder(nsDisplayListBuilder* aBuilder, 107 nsFieldSetFrame* aFrame) 108 : nsPaintedDisplayItem(aBuilder, aFrame) { 109 MOZ_COUNT_CTOR(nsDisplayFieldSetBorder); 110 } 111 112 MOZ_COUNTED_DTOR_FINAL(nsDisplayFieldSetBorder) 113 114 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; 115 bool CreateWebRenderCommands( 116 mozilla::wr::DisplayListBuilder& aBuilder, 117 mozilla::wr::IpcResourceUpdateQueue& aResources, 118 const StackingContextHelper& aSc, 119 mozilla::layers::RenderRootStateManager* aManager, 120 nsDisplayListBuilder* aDisplayListBuilder) override; 121 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, 122 bool* aSnap) const override; 123 NS_DISPLAY_DECL_NAME("FieldSetBorder", TYPE_FIELDSET_BORDER_BACKGROUND) 124 }; 125 126 void nsDisplayFieldSetBorder::Paint(nsDisplayListBuilder* aBuilder, 127 gfxContext* aCtx) { 128 (void)static_cast<nsFieldSetFrame*>(mFrame)->PaintBorder( 129 aBuilder, *aCtx, ToReferenceFrame(), GetPaintRect(aBuilder, aCtx)); 130 } 131 132 nsRect nsDisplayFieldSetBorder::GetBounds(nsDisplayListBuilder* aBuilder, 133 bool* aSnap) const { 134 // Just go ahead and claim our frame's overflow rect as the bounds, because we 135 // may have border-image-outset or other features that cause borders to extend 136 // outside the border rect. We could try to duplicate all the complexity 137 // nsDisplayBorder has here, but keeping things in sync would be a pain, and 138 // this code is not typically performance-sensitive. 139 *aSnap = false; 140 return Frame()->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); 141 } 142 143 bool nsDisplayFieldSetBorder::CreateWebRenderCommands( 144 mozilla::wr::DisplayListBuilder& aBuilder, 145 mozilla::wr::IpcResourceUpdateQueue& aResources, 146 const StackingContextHelper& aSc, 147 mozilla::layers::RenderRootStateManager* aManager, 148 nsDisplayListBuilder* aDisplayListBuilder) { 149 auto frame = static_cast<nsFieldSetFrame*>(mFrame); 150 auto offset = ToReferenceFrame(); 151 Maybe<wr::SpaceAndClipChainHelper> clipOut; 152 153 nsRect rect = frame->VisualBorderRectRelativeToSelf() + offset; 154 nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( 155 aBuilder, aSc, rect, mFrame, rect); 156 157 if (nsIFrame* legend = frame->GetLegend()) { 158 nsRect legendRect = legend->GetNormalRect() + offset; 159 160 // Make sure we clip all of the border in case the legend is smaller. 161 nscoord borderTopWidth = frame->GetUsedBorder().top; 162 if (legendRect.height < borderTopWidth) { 163 legendRect.height = borderTopWidth; 164 legendRect.y = offset.y; 165 } 166 167 if (!legendRect.IsEmpty()) { 168 // We need to clip out the part of the border where the legend would go 169 auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); 170 auto layoutRect = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( 171 frame->InkOverflowRectRelativeToSelf() + offset, 172 appUnitsPerDevPixel)); 173 174 wr::ComplexClipRegion region; 175 region.rect = wr::ToLayoutRect( 176 LayoutDeviceRect::FromAppUnits(legendRect, appUnitsPerDevPixel)); 177 region.mode = wr::ClipMode::ClipOut; 178 region.radii = wr::EmptyBorderRadius(); 179 180 std::array<wr::WrClipId, 2> clips = { 181 aBuilder.DefineRectClip(Nothing(), layoutRect), 182 aBuilder.DefineRoundedRectClip(Nothing(), region), 183 }; 184 auto clipChain = aBuilder.DefineClipChain( 185 clips, aBuilder.CurrentClipChainIdIfNotRoot()); 186 clipOut.emplace(aBuilder, clipChain); 187 } 188 } else { 189 rect = nsRect(offset, frame->GetRect().Size()); 190 } 191 192 ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder( 193 this, mFrame, rect, aBuilder, aResources, aSc, aManager, 194 aDisplayListBuilder); 195 if (drawResult == ImgDrawResult::NOT_SUPPORTED) { 196 return false; 197 } 198 return true; 199 }; 200 201 } // namespace mozilla 202 203 void nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 204 const nsDisplayListSet& aLists) { 205 // Paint our background and border in a special way. 206 // REVIEW: We don't really need to check frame emptiness here; if it's empty, 207 // the background/border display item won't do anything, and if it isn't 208 // empty, we need to paint the outline 209 if (!HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) && 210 IsVisibleForPainting()) { 211 DisplayOutsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground()); 212 213 const nsRect rect = 214 VisualBorderRectRelativeToSelf() + aBuilder->ToReferenceFrame(this); 215 216 nsDisplayBackgroundImage::AppendBackgroundItemsToTop( 217 aBuilder, this, rect, aLists.BorderBackground(), 218 /* aAllowWillPaintBorderOptimization = */ false); 219 220 aLists.BorderBackground()->AppendNewToTop<nsDisplayFieldSetBorder>(aBuilder, 221 this); 222 223 DisplayOutlineUnconditional(aBuilder, aLists); 224 225 DO_GLOBAL_REFLOW_COUNT_DSP("nsFieldSetFrame"); 226 } 227 228 if (HidesContent()) { 229 return; 230 } 231 232 if (GetPrevInFlow()) { 233 DisplayOverflowContainers(aBuilder, aLists); 234 DisplayPushedAbsoluteFrames(aBuilder, aLists); 235 } 236 237 nsDisplayListCollection contentDisplayItems(aBuilder); 238 if (nsIFrame* inner = GetInner()) { 239 // Collect the inner frame's display items into their own collection. 240 // We need to be calling BuildDisplayList on it before the legend in 241 // case it contains out-of-flow frames whose placeholders are in the 242 // legend. However, we want the inner frame's display items to be 243 // after the legend's display items in z-order, so we need to save them 244 // and append them later. 245 BuildDisplayListForChild(aBuilder, inner, contentDisplayItems); 246 } 247 if (nsIFrame* legend = GetLegend()) { 248 // The legend's background goes on our BlockBorderBackgrounds list because 249 // it's a block child. 250 nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds()); 251 BuildDisplayListForChild(aBuilder, legend, set); 252 } 253 // Put the inner frame's display items on the master list. Note that this 254 // moves its border/background display items to our BorderBackground() list, 255 // which isn't really correct, but it's OK because the inner frame is 256 // anonymous and can't have its own border and background. 257 contentDisplayItems.MoveTo(aLists); 258 } 259 260 ImgDrawResult nsFieldSetFrame::PaintBorder(nsDisplayListBuilder* aBuilder, 261 gfxContext& aRenderingContext, 262 nsPoint aPt, 263 const nsRect& aDirtyRect) { 264 // If the border is smaller than the legend, move the border down 265 // to be centered on the legend. We call VisualBorderRectRelativeToSelf() to 266 // compute the border positioning. 267 // FIXME: This means border-radius clamping is incorrect; we should 268 // override nsIFrame::GetBorderRadii. 269 nsRect rect = VisualBorderRectRelativeToSelf() + aPt; 270 nsPresContext* presContext = PresContext(); 271 272 const auto skipSides = GetSkipSides(); 273 PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages() 274 ? PaintBorderFlags::SyncDecodeImages 275 : PaintBorderFlags(); 276 277 ImgDrawResult result = ImgDrawResult::SUCCESS; 278 279 nsCSSRendering::PaintBoxShadowInner(presContext, aRenderingContext, this, 280 rect); 281 282 if (nsIFrame* legend = GetLegend()) { 283 // We want to avoid drawing our border under the legend, so clip out the 284 // legend while drawing our border. We don't want to use mLegendRect here, 285 // because we do want to draw our border under the legend's inline-start and 286 // -end margins. And we use GetNormalRect(), not GetRect(), because we do 287 // not want relative positioning applied to the legend to change how our 288 // border looks. 289 nsRect legendRect = legend->GetNormalRect() + aPt; 290 291 // Make sure we clip all of the border in case the legend is smaller. 292 nscoord borderTopWidth = GetUsedBorder().top; 293 if (legendRect.height < borderTopWidth) { 294 legendRect.height = borderTopWidth; 295 legendRect.y = aPt.y; 296 } 297 298 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); 299 // We set up a clip path which has our rect clockwise and the legend rect 300 // counterclockwise, with FILL_WINDING as the fill rule. That will allow us 301 // to paint within our rect but outside the legend rect. For "our rect" we 302 // use our ink overflow rect (relative to ourselves, so it's not affected 303 // by transforms), because we can have borders sticking outside our border 304 // box (e.g. due to border-image-outset). 305 RefPtr<PathBuilder> pathBuilder = 306 drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); 307 int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); 308 AppendRectToPath(pathBuilder, 309 NSRectToSnappedRect(InkOverflowRectRelativeToSelf() + aPt, 310 appUnitsPerDevPixel, *drawTarget), 311 true); 312 AppendRectToPath( 313 pathBuilder, 314 NSRectToSnappedRect(legendRect, appUnitsPerDevPixel, *drawTarget), 315 false); 316 RefPtr<Path> clipPath = pathBuilder->Finish(); 317 318 aRenderingContext.Save(); 319 aRenderingContext.Clip(clipPath); 320 result &= nsCSSRendering::PaintBorder(presContext, aRenderingContext, this, 321 aDirtyRect, rect, mComputedStyle, 322 borderFlags, skipSides); 323 aRenderingContext.Restore(); 324 } else { 325 result &= nsCSSRendering::PaintBorder( 326 presContext, aRenderingContext, this, aDirtyRect, 327 nsRect(aPt, mRect.Size()), mComputedStyle, borderFlags, skipSides); 328 } 329 330 return result; 331 } 332 333 nscoord nsFieldSetFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 334 IntrinsicISizeType aType) { 335 // Both inner and legend are children, and if the fieldset is 336 // size-contained they should not contribute to the intrinsic size. 337 if (Maybe<nscoord> containISize = ContainIntrinsicISize()) { 338 return *containISize; 339 } 340 341 nscoord legendWidth = 0; 342 if (nsIFrame* legend = GetLegend()) { 343 legendWidth = 344 nsLayoutUtils::IntrinsicForContainer(aInput.mContext, legend, aType); 345 } 346 347 nscoord contentWidth = 0; 348 if (nsIFrame* inner = GetInner()) { 349 // Ignore padding on the inner, since the padding will be applied to the 350 // outer instead, and the padding computed for the inner is wrong 351 // for percentage padding. 352 contentWidth = nsLayoutUtils::IntrinsicForContainer( 353 aInput.mContext, inner, aType, aInput.mPercentageBasisForChildren, 354 nsLayoutUtils::IGNORE_PADDING); 355 } 356 357 return std::max(legendWidth, contentWidth); 358 } 359 360 /* virtual */ 361 void nsFieldSetFrame::Reflow(nsPresContext* aPresContext, 362 ReflowOutput& aDesiredSize, 363 const ReflowInput& aReflowInput, 364 nsReflowStatus& aStatus) { 365 using LegendAlignValue = mozilla::dom::HTMLLegendElement::LegendAlignValue; 366 367 MarkInReflow(); 368 DO_GLOBAL_REFLOW_COUNT("nsFieldSetFrame"); 369 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 370 NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE, 371 "Should have a precomputed inline-size!"); 372 373 OverflowAreas ocBounds; 374 nsReflowStatus ocStatus; 375 auto* prevInFlow = static_cast<nsFieldSetFrame*>(GetPrevInFlow()); 376 if (prevInFlow) { 377 ReflowOverflowContainerChildren(aPresContext, aReflowInput, ocBounds, 378 ReflowChildFlags::Default, ocStatus); 379 380 AutoFrameListPtr prevOverflowFrames(PresContext(), 381 prevInFlow->StealOverflowFrames()); 382 if (prevOverflowFrames) { 383 mFrames.InsertFrames(this, nullptr, std::move(*prevOverflowFrames)); 384 } 385 } 386 387 bool reflowInner; 388 bool reflowLegend; 389 nsIFrame* legend = GetLegend(); 390 nsContainerFrame* inner = GetInner(); 391 if (!legend || !inner) { 392 if (DrainSelfOverflowList()) { 393 legend = GetLegend(); 394 inner = GetInner(); 395 } 396 } 397 if (aReflowInput.ShouldReflowAllKids() || GetNextInFlow() || 398 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) { 399 reflowInner = inner != nullptr; 400 reflowLegend = legend != nullptr; 401 } else { 402 reflowInner = inner && inner->IsSubtreeDirty(); 403 reflowLegend = legend && legend->IsSubtreeDirty(); 404 } 405 406 // @note |this| frame applies borders but not any padding. Our anonymous 407 // inner frame applies the padding (but not borders). 408 const auto wm = GetWritingMode(); 409 auto skipSides = PreReflowBlockLevelLogicalSkipSides(); 410 LogicalMargin border = 411 aReflowInput.ComputedLogicalBorder(wm).ApplySkipSides(skipSides); 412 LogicalSize availSize(wm, aReflowInput.ComputedSize().ISize(wm), 413 aReflowInput.AvailableBSize()); 414 415 // Figure out how big the legend is if there is one. 416 LogicalMargin legendMargin(wm); 417 Maybe<ReflowInput> legendReflowInput; 418 if (legend) { 419 const auto legendWM = legend->GetWritingMode(); 420 LogicalSize legendAvailSize = availSize.ConvertTo(legendWM, wm); 421 ComputeSizeFlags sizeFlags; 422 if (legend->StylePosition() 423 ->ISize(wm, AnchorPosResolutionParams::From(legend)) 424 ->IsAuto()) { 425 sizeFlags = ComputeSizeFlag::ShrinkWrap; 426 } 427 ReflowInput::InitFlags initFlags; // intentionally empty 428 StyleSizeOverrides sizeOverrides; // intentionally empty 429 legendReflowInput.emplace(aPresContext, aReflowInput, legend, 430 legendAvailSize, Nothing(), initFlags, 431 sizeOverrides, sizeFlags); 432 } 433 const bool avoidBreakInside = ShouldAvoidBreakInside(aReflowInput); 434 if (reflowLegend) { 435 ReflowOutput legendDesiredSize(aReflowInput); 436 437 // We'll move the legend to its proper place later, so the position 438 // and containerSize passed here are unimportant. 439 const nsSize dummyContainerSize; 440 ReflowChild(legend, aPresContext, legendDesiredSize, *legendReflowInput, wm, 441 LogicalPoint(wm), dummyContainerSize, 442 ReflowChildFlags::NoMoveFrame, aStatus); 443 444 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 445 !(HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && 446 aReflowInput.mStyleDisplay->IsAbsolutelyPositionedStyle()) && 447 !prevInFlow && !aReflowInput.mFlags.mIsTopOfPage) { 448 // Propagate break-before from the legend to the fieldset. 449 if (legend->StyleDisplay()->BreakBefore() || 450 aStatus.IsInlineBreakBefore()) { 451 aStatus.SetInlineLineBreakBeforeAndReset(); 452 return; 453 } 454 // Honor break-inside:avoid by breaking before instead. 455 if (MOZ_UNLIKELY(avoidBreakInside) && !aStatus.IsFullyComplete()) { 456 aStatus.SetInlineLineBreakBeforeAndReset(); 457 return; 458 } 459 } 460 461 // Calculate the legend's margin-box rectangle. 462 legendMargin = legend->GetLogicalUsedMargin(wm); 463 mLegendRect = LogicalRect( 464 wm, 0, 0, legendDesiredSize.ISize(wm) + legendMargin.IStartEnd(wm), 465 legendDesiredSize.BSize(wm) + legendMargin.BStartEnd(wm)); 466 // We subtract mLegendSpace from inner's content-box block-size below. 467 nscoord oldSpace = mLegendSpace; 468 mLegendSpace = 0; 469 nscoord borderBStart = border.BStart(wm); 470 if (!prevInFlow) { 471 if (mLegendRect.BSize(wm) > borderBStart) { 472 mLegendSpace = mLegendRect.BSize(wm) - borderBStart; 473 } else { 474 // Calculate the border-box position that would center the legend's 475 // border-box within the fieldset border: 476 nscoord off = (borderBStart - legendDesiredSize.BSize(wm)) / 2; 477 off -= legendMargin.BStart(wm); // convert to a margin-box position 478 if (off > nscoord(0)) { 479 // Align the legend to the end if center-aligning it would overflow. 480 nscoord overflow = off + mLegendRect.BSize(wm) - borderBStart; 481 if (overflow > nscoord(0)) { 482 off -= overflow; 483 } 484 mLegendRect.BStart(wm) += off; 485 } 486 } 487 } else { 488 mLegendSpace = mLegendRect.BSize(wm); 489 } 490 491 // If mLegendSpace changes then we need to reflow |inner| as well. 492 if (mLegendSpace != oldSpace && inner) { 493 reflowInner = true; 494 } 495 496 FinishReflowChild(legend, aPresContext, legendDesiredSize, 497 legendReflowInput.ptr(), wm, LogicalPoint(wm), 498 dummyContainerSize, ReflowChildFlags::NoMoveFrame); 499 EnsureChildContinuation(legend, aStatus); 500 if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 501 !legend->GetWritingMode().IsOrthogonalTo(wm) && 502 legend->StyleDisplay()->BreakAfter() && 503 (!legendReflowInput->mFlags.mIsTopOfPage || 504 mLegendRect.BSize(wm) > 0) && 505 aStatus.IsComplete()) { 506 // Pretend that we ran out of space to push children of |inner|. 507 // XXX(mats) perhaps pushing the inner frame would be more correct, 508 // but we don't support that yet. 509 availSize.BSize(wm) = nscoord(0); 510 aStatus.Reset(); 511 aStatus.SetIncomplete(); 512 } 513 } else if (!legend) { 514 mLegendRect.SetEmpty(); 515 mLegendSpace = 0; 516 } else { 517 // mLegendSpace and mLegendRect haven't changed, but we need 518 // the used margin when placing the legend. 519 legendMargin = legend->GetLogicalUsedMargin(wm); 520 } 521 522 // This containerSize is incomplete as yet: it does not include the size 523 // of the |inner| frame itself. 524 nsSize containerSize = 525 (LogicalSize(wm, 0, mLegendSpace) + border.Size(wm)).GetPhysicalSize(wm); 526 if (reflowInner) { 527 LogicalSize innerAvailSize = availSize; 528 innerAvailSize.ISize(wm) = 529 aReflowInput.ComputedSizeWithPadding(wm).ISize(wm); 530 nscoord remainingComputedBSize = aReflowInput.ComputedBSize(); 531 if (prevInFlow && remainingComputedBSize != NS_UNCONSTRAINEDSIZE) { 532 // Subtract the consumed BSize associated with the legend. 533 for (nsIFrame* prev = prevInFlow; prev; prev = prev->GetPrevInFlow()) { 534 auto* prevFieldSet = static_cast<nsFieldSetFrame*>(prev); 535 remainingComputedBSize -= prevFieldSet->mLegendSpace; 536 } 537 remainingComputedBSize = std::max(0, remainingComputedBSize); 538 } 539 if (innerAvailSize.BSize(wm) != NS_UNCONSTRAINEDSIZE) { 540 innerAvailSize.BSize(wm) -= 541 std::max(mLegendRect.BSize(wm), border.BStart(wm)); 542 if (StyleBorder()->mBoxDecorationBreak == 543 StyleBoxDecorationBreak::Clone && 544 (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE || 545 remainingComputedBSize + 546 aReflowInput.ComputedLogicalBorderPadding(wm).BStartEnd( 547 wm) >= 548 availSize.BSize(wm))) { 549 innerAvailSize.BSize(wm) -= border.BEnd(wm); 550 } 551 innerAvailSize.BSize(wm) = std::max(0, innerAvailSize.BSize(wm)); 552 } 553 ReflowInput kidReflowInput(aPresContext, aReflowInput, inner, 554 innerAvailSize, Nothing(), 555 ReflowInput::InitFlag::CallerWillInit); 556 // Override computed padding, in case it's percentage padding 557 kidReflowInput.Init( 558 aPresContext, Nothing(), Nothing(), 559 Some(aReflowInput.ComputedLogicalPadding(inner->GetWritingMode()))); 560 561 // Propagate the aspect-ratio flag to |inner| (i.e. the container frame 562 // wrapped by nsFieldSetFrame), so we can let |inner|'s reflow code handle 563 // automatic content-based minimum. 564 // Note: Init() resets this flag, so we have to copy it again here. 565 if (aReflowInput.mFlags.mIsBSizeSetByAspectRatio) { 566 kidReflowInput.mFlags.mIsBSizeSetByAspectRatio = true; 567 } 568 569 if (kidReflowInput.mFlags.mIsTopOfPage) { 570 // Prevent break-before from |inner| if we have a legend. 571 kidReflowInput.mFlags.mIsTopOfPage = !legend; 572 } 573 // Our child is "height:100%" but we actually want its height to be reduced 574 // by the amount of content-height the legend is eating up, unless our 575 // height is unconstrained (in which case the child's will be too). 576 if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) { 577 kidReflowInput.SetComputedBSize( 578 std::max(0, remainingComputedBSize - mLegendSpace)); 579 } 580 581 if (aReflowInput.ComputedMinBSize() > 0) { 582 kidReflowInput.SetComputedMinBSize( 583 std::max(0, aReflowInput.ComputedMinBSize() - mLegendSpace)); 584 } 585 586 if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { 587 kidReflowInput.SetComputedMaxBSize( 588 std::max(0, aReflowInput.ComputedMaxBSize() - mLegendSpace)); 589 } 590 591 ReflowOutput kidDesiredSize(kidReflowInput); 592 NS_ASSERTION( 593 kidReflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), 594 "Margins on anonymous fieldset child not supported!"); 595 LogicalPoint pt(wm, border.IStart(wm), border.BStart(wm) + mLegendSpace); 596 597 // We don't know the correct containerSize until we have reflowed |inner|, 598 // so we use a dummy value for now; FinishReflowChild will fix the position 599 // if necessary. 600 const nsSize dummyContainerSize; 601 nsReflowStatus status; 602 // If our legend needs a continuation then *this* frame will have 603 // a continuation as well so we should keep our inner frame continuations 604 // too (even if 'inner' ends up being COMPLETE here). This ensures that 605 // our continuation will have a reasonable inline-size. 606 ReflowChildFlags flags = aStatus.IsFullyComplete() 607 ? ReflowChildFlags::Default 608 : ReflowChildFlags::NoDeleteNextInFlowChild; 609 ReflowChild(inner, aPresContext, kidDesiredSize, kidReflowInput, wm, pt, 610 dummyContainerSize, flags, status); 611 612 // Honor break-inside:avoid when possible by returning a BreakBefore status. 613 if (MOZ_UNLIKELY(avoidBreakInside) && !prevInFlow && 614 !aReflowInput.mFlags.mIsTopOfPage && 615 availSize.BSize(wm) != NS_UNCONSTRAINEDSIZE) { 616 if (status.IsInlineBreakBefore() || !status.IsFullyComplete()) { 617 aStatus.SetInlineLineBreakBeforeAndReset(); 618 return; 619 } 620 } 621 622 // Update containerSize to account for size of the inner frame, so that 623 // FinishReflowChild can position it correctly. 624 containerSize += kidDesiredSize.PhysicalSize(); 625 FinishReflowChild(inner, aPresContext, kidDesiredSize, &kidReflowInput, wm, 626 pt, containerSize, ReflowChildFlags::Default); 627 EnsureChildContinuation(inner, status); 628 aStatus.MergeCompletionStatusFrom(status); 629 NS_FRAME_TRACE_REFLOW_OUT("FieldSet::Reflow", aStatus); 630 } else if (inner) { 631 // |inner| didn't need to be reflowed but we do need to include its size 632 // in containerSize. 633 containerSize += inner->GetSize(); 634 } else { 635 // No |inner| means it was already complete in an earlier continuation. 636 MOZ_ASSERT(prevInFlow, "first-in-flow should always have an inner frame"); 637 for (nsIFrame* prev = prevInFlow; prev; prev = prev->GetPrevInFlow()) { 638 auto* prevFieldSet = static_cast<nsFieldSetFrame*>(prev); 639 if (auto* prevInner = prevFieldSet->GetInner()) { 640 containerSize += prevInner->GetSize(); 641 break; 642 } 643 } 644 } 645 646 LogicalRect contentRect(wm); 647 if (inner) { 648 // We don't support margins on inner, so our content rect is just the 649 // inner's border-box. (We don't really care about container size at this 650 // point, as we'll figure out the actual positioning later.) 651 contentRect = inner->GetLogicalRect(wm, containerSize); 652 } else if (prevInFlow) { 653 auto size = prevInFlow->GetPaddingRectRelativeToSelf().Size(); 654 contentRect.ISize(wm) = wm.IsVertical() ? size.height : size.width; 655 } 656 657 if (legend) { 658 // The legend is positioned inline-wards within the inner's content rect 659 // (so that padding on the fieldset affects the legend position). 660 LogicalRect innerContentRect = contentRect; 661 innerContentRect.Deflate(wm, aReflowInput.ComputedLogicalPadding(wm)); 662 // If the inner content rect is larger than the legend, we can align the 663 // legend. 664 if (innerContentRect.ISize(wm) > mLegendRect.ISize(wm)) { 665 // NOTE legend @align values are: left/right/center 666 // GetLogicalAlign converts left/right to start/end for the given WM. 667 // @see HTMLLegendElement::ParseAttribute/LogicalAlign 668 auto* legendElement = 669 dom::HTMLLegendElement::FromNode(legend->GetContent()); 670 switch (legendElement->LogicalAlign(wm)) { 671 case LegendAlignValue::InlineEnd: 672 mLegendRect.IStart(wm) = 673 innerContentRect.IEnd(wm) - mLegendRect.ISize(wm); 674 break; 675 case LegendAlignValue::Center: 676 // Note: rounding removed; there doesn't seem to be any need 677 mLegendRect.IStart(wm) = 678 innerContentRect.IStart(wm) + 679 (innerContentRect.ISize(wm) - mLegendRect.ISize(wm)) / 2; 680 break; 681 case LegendAlignValue::InlineStart: 682 mLegendRect.IStart(wm) = innerContentRect.IStart(wm); 683 break; 684 default: 685 MOZ_ASSERT_UNREACHABLE("unexpected GetLogicalAlign value"); 686 } 687 } else { 688 // otherwise just start-align it. 689 mLegendRect.IStart(wm) = innerContentRect.IStart(wm); 690 } 691 692 // place the legend 693 LogicalRect actualLegendRect = mLegendRect; 694 actualLegendRect.Deflate(wm, legendMargin); 695 LogicalPoint actualLegendPos(actualLegendRect.Origin(wm)); 696 697 // Note that legend's writing mode may be different from the fieldset's, 698 // so we need to convert offsets before applying them to it (bug 1134534). 699 LogicalMargin offsets = legendReflowInput->ComputedLogicalOffsets(wm); 700 ReflowInput::ApplyRelativePositioning(legend, wm, offsets, &actualLegendPos, 701 containerSize); 702 703 legend->SetPosition(wm, actualLegendPos, containerSize); 704 } 705 706 // Skip our block-end border if we're INCOMPLETE. 707 if (!aStatus.IsComplete() && 708 StyleBorder()->mBoxDecorationBreak != StyleBoxDecorationBreak::Clone) { 709 border.BEnd(wm) = nscoord(0); 710 } 711 712 // Return our size and our result. 713 LogicalSize finalSize( 714 wm, contentRect.ISize(wm) + border.IStartEnd(wm), 715 mLegendSpace + border.BStartEnd(wm) + (inner ? inner->BSize(wm) : 0)); 716 if (Maybe<nscoord> containBSize = 717 aReflowInput.mFrame->ContainIntrinsicBSize()) { 718 // If we're size-contained in block axis, then we must set finalSize 719 // according to contain-intrinsic-block-size, disregarding legend and inner. 720 // Note: normally the fieldset's own padding (which we still must honor) 721 // would be accounted for as part of inner's size (see kidReflowInput.Init() 722 // call above). So: since we're disregarding sizing information from 723 // 'inner', we need to account for that padding ourselves here. 724 nscoord contentBoxBSize = 725 aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE 726 ? aReflowInput.ApplyMinMaxBSize(*containBSize) 727 : aReflowInput.ComputedBSize(); 728 finalSize.BSize(wm) = 729 contentBoxBSize + 730 aReflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm); 731 } 732 733 if (aStatus.IsComplete() && 734 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 735 finalSize.BSize(wm) > aReflowInput.AvailableBSize() && 736 border.BEnd(wm) > 0 && aReflowInput.AvailableBSize() > border.BEnd(wm)) { 737 // Our end border doesn't fit but it should fit in the next column/page. 738 if (MOZ_UNLIKELY(avoidBreakInside)) { 739 aStatus.SetInlineLineBreakBeforeAndReset(); 740 return; 741 } else { 742 if (StyleBorder()->mBoxDecorationBreak == 743 StyleBoxDecorationBreak::Slice) { 744 finalSize.BSize(wm) -= border.BEnd(wm); 745 } 746 aStatus.SetIncomplete(); 747 } 748 } 749 750 if (!aStatus.IsComplete()) { 751 MOZ_ASSERT(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, 752 "must be Complete in an unconstrained available block-size"); 753 // Stretch our BSize to fill the fragmentainer. 754 finalSize.BSize(wm) = 755 std::max(finalSize.BSize(wm), aReflowInput.AvailableBSize()); 756 } 757 aDesiredSize.SetSize(wm, finalSize); 758 aDesiredSize.SetOverflowAreasToDesiredBounds(); 759 760 if (legend) { 761 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, legend); 762 } 763 if (inner) { 764 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inner); 765 } 766 767 // Merge overflow container bounds and status. 768 aDesiredSize.mOverflowAreas.UnionWith(ocBounds); 769 aStatus.MergeCompletionStatusFrom(ocStatus); 770 771 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, 772 aStatus); 773 InvalidateFrame(); 774 } 775 776 void nsFieldSetFrame::SetInitialChildList(ChildListID aListID, 777 nsFrameList&& aChildList) { 778 nsContainerFrame::SetInitialChildList(aListID, std::move(aChildList)); 779 MOZ_ASSERT( 780 aListID != FrameChildListID::Principal || GetInner() || GetLegend(), 781 "Setting principal child list should populate our inner frame " 782 "or our rendered legend"); 783 } 784 785 void nsFieldSetFrame::AppendFrames(ChildListID aListID, 786 nsFrameList&& aFrameList) { 787 MOZ_ASSERT(aListID == FrameChildListID::NoReflowPrincipal && 788 HasAnyStateBits(NS_FRAME_FIRST_REFLOW), 789 "AppendFrames should only be used from " 790 "nsCSSFrameConstructor::ConstructFieldSetFrame"); 791 nsContainerFrame::AppendFrames(aListID, std::move(aFrameList)); 792 MOZ_ASSERT(GetInner(), "at this point we should have an inner frame"); 793 } 794 795 void nsFieldSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, 796 const nsLineList::iterator* aPrevFrameLine, 797 nsFrameList&& aFrameList) { 798 MOZ_ASSERT( 799 aListID == FrameChildListID::Principal && !aPrevFrame && !GetLegend(), 800 "InsertFrames should only be used to prepend a rendered legend " 801 "from nsCSSFrameConstructor::ConstructFramesFromItemList"); 802 nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, 803 std::move(aFrameList)); 804 MOZ_ASSERT(GetLegend()); 805 } 806 807 #ifdef DEBUG 808 void nsFieldSetFrame::RemoveFrame(DestroyContext&, ChildListID, nsIFrame*) { 809 MOZ_CRASH("nsFieldSetFrame::RemoveFrame not supported"); 810 } 811 #endif 812 813 #ifdef ACCESSIBILITY 814 a11y::AccType nsFieldSetFrame::AccessibleType() { 815 return a11y::eHTMLGroupboxType; 816 } 817 #endif 818 819 BaselineSharingGroup nsFieldSetFrame::GetDefaultBaselineSharingGroup() const { 820 switch (StyleDisplay()->DisplayInside()) { 821 case mozilla::StyleDisplayInside::Grid: 822 case mozilla::StyleDisplayInside::Flex: 823 return BaselineSharingGroup::First; 824 default: 825 return BaselineSharingGroup::Last; 826 } 827 } 828 829 nscoord nsFieldSetFrame::SynthesizeFallbackBaseline( 830 WritingMode aWM, BaselineSharingGroup aBaselineGroup) const { 831 return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup); 832 } 833 834 Maybe<nscoord> nsFieldSetFrame::GetNaturalBaselineBOffset( 835 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 836 BaselineExportContext aExportContext) const { 837 if (StyleDisplay()->IsContainLayout()) { 838 // If we are layout-contained, our child 'inner' should not 839 // affect how we calculate our baseline. 840 return Nothing{}; 841 } 842 nsIFrame* inner = GetInner(); 843 if (MOZ_UNLIKELY(!inner)) { 844 return Nothing{}; 845 } 846 MOZ_ASSERT(!inner->GetWritingMode().IsOrthogonalTo(aWM)); 847 const auto result = 848 inner->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext); 849 if (!result) { 850 return Nothing{}; 851 } 852 nscoord innerBStart = inner->BStart(aWM, GetSize()); 853 if (aBaselineGroup == BaselineSharingGroup::First) { 854 return Some(*result + innerBStart); 855 } 856 return Some(*result + BSize(aWM) - (innerBStart + inner->BSize(aWM))); 857 } 858 859 ScrollContainerFrame* nsFieldSetFrame::GetScrollTargetFrame() const { 860 return do_QueryFrame(GetInner()); 861 } 862 863 void nsFieldSetFrame::AppendDirectlyOwnedAnonBoxes( 864 nsTArray<OwnedAnonBox>& aResult) { 865 if (nsIFrame* kid = GetInner()) { 866 aResult.AppendElement(OwnedAnonBox(kid)); 867 } 868 } 869 870 void nsFieldSetFrame::EnsureChildContinuation(nsIFrame* aChild, 871 const nsReflowStatus& aStatus) { 872 MOZ_ASSERT(aChild == GetLegend() || aChild == GetInner(), 873 "unexpected child frame"); 874 nsIFrame* nif = aChild->GetNextInFlow(); 875 if (aStatus.IsFullyComplete()) { 876 if (nif) { 877 // NOTE: we want to avoid our DEBUG version of RemoveFrame above. 878 DestroyContext context(PresShell()); 879 nsContainerFrame::RemoveFrame(context, 880 FrameChildListID::NoReflowPrincipal, nif); 881 MOZ_ASSERT(!aChild->GetNextInFlow()); 882 } 883 } else { 884 nsFrameList nifs; 885 if (!nif) { 886 auto* fc = PresShell()->FrameConstructor(); 887 nif = fc->CreateContinuingFrame(aChild, this); 888 if (aStatus.IsOverflowIncomplete()) { 889 nif->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 890 } 891 nifs = nsFrameList(nif, nif); 892 } else { 893 // Steal all nifs and push them again in case they are currently on 894 // the wrong list. 895 for (nsIFrame* n = nif; n; n = n->GetNextInFlow()) { 896 n->GetParent()->StealFrame(n); 897 nifs.AppendFrame(this, n); 898 if (aStatus.IsOverflowIncomplete()) { 899 n->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 900 } else { 901 n->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); 902 } 903 } 904 } 905 if (aStatus.IsOverflowIncomplete()) { 906 if (nsFrameList* eoc = GetExcessOverflowContainers()) { 907 eoc->AppendFrames(nullptr, std::move(nifs)); 908 } else { 909 SetExcessOverflowContainers(std::move(nifs)); 910 } 911 } else { 912 if (nsFrameList* oc = GetOverflowFrames()) { 913 oc->AppendFrames(nullptr, std::move(nifs)); 914 } else { 915 SetOverflowFrames(std::move(nifs)); 916 } 917 } 918 } 919 }