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/. */
7 #include "nsFieldSetFrame.h"
8 #include "mozilla/dom/HTMLLegendElement.h"
11 #include "gfxContext.h"
12 #include "mozilla/Baseline.h"
13 #include "mozilla/gfx/2D.h"
14 #include "mozilla/Likely.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/webrender/WebRenderAPI.h"
18 #include "nsBlockFrame.h"
19 #include "nsCSSAnonBoxes.h"
20 #include "nsCSSFrameConstructor.h"
21 #include "nsCSSRendering.h"
22 #include "nsDisplayList.h"
23 #include "nsGkAtoms.h"
24 #include "nsIFrameInlines.h"
25 #include "nsIScrollableFrame.h"
26 #include "nsLayoutUtils.h"
27 #include "nsStyleConsts.h"
29 using namespace mozilla
;
30 using namespace mozilla::gfx
;
31 using namespace mozilla::layout
;
32 using image::ImgDrawResult
;
34 nsContainerFrame
* NS_NewFieldSetFrame(PresShell
* aPresShell
,
35 ComputedStyle
* aStyle
) {
36 return new (aPresShell
) nsFieldSetFrame(aStyle
, aPresShell
->GetPresContext());
39 NS_IMPL_FRAMEARENA_HELPERS(nsFieldSetFrame
)
40 NS_QUERYFRAME_HEAD(nsFieldSetFrame
)
41 NS_QUERYFRAME_ENTRY(nsFieldSetFrame
)
42 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
44 nsFieldSetFrame::nsFieldSetFrame(ComputedStyle
* aStyle
,
45 nsPresContext
* aPresContext
)
46 : nsContainerFrame(aStyle
, aPresContext
, kClassID
),
47 mLegendRect(GetWritingMode()) {
51 nsRect
nsFieldSetFrame::VisualBorderRectRelativeToSelf() const {
52 WritingMode wm
= GetWritingMode();
53 LogicalRect
r(wm
, LogicalPoint(wm
, 0, 0), GetLogicalSize(wm
));
54 nsSize containerSize
= r
.Size(wm
).GetPhysicalSize(wm
);
55 nsIFrame
* legend
= GetLegend();
56 if (legend
&& !GetPrevInFlow()) {
57 nscoord legendSize
= legend
->GetLogicalSize(wm
).BSize(wm
);
58 auto legendMargin
= legend
->GetLogicalUsedMargin(wm
);
59 nscoord legendStartMargin
= legendMargin
.BStart(wm
);
60 nscoord legendEndMargin
= legendMargin
.BEnd(wm
);
61 nscoord border
= GetUsedBorder().Side(wm
.PhysicalSide(eLogicalSideBStart
));
62 // Calculate the offset from the border area block-axis start edge needed to
63 // center-align our border with the legend's border-box (in the block-axis).
64 nscoord off
= (legendStartMargin
+ legendSize
/ 2) - border
/ 2;
65 // We don't want to display our border above our border area.
66 if (off
> nscoord(0)) {
67 nscoord marginBoxSize
= legendStartMargin
+ legendSize
+ legendEndMargin
;
68 if (marginBoxSize
> border
) {
69 // We don't want to display our border below the legend's margin-box,
70 // so we align it to the block-axis end if that happens.
71 nscoord overflow
= off
+ border
- marginBoxSize
;
72 if (overflow
> nscoord(0)) {
80 return r
.GetPhysicalRect(wm
, containerSize
);
83 nsContainerFrame
* nsFieldSetFrame::GetInner() const {
84 for (nsIFrame
* child
: mFrames
) {
85 if (child
->Style()->GetPseudoType() == PseudoStyleType::fieldsetContent
) {
86 return static_cast<nsContainerFrame
*>(child
);
92 nsIFrame
* nsFieldSetFrame::GetLegend() const {
93 for (nsIFrame
* child
: mFrames
) {
94 if (child
->Style()->GetPseudoType() != PseudoStyleType::fieldsetContent
) {
103 class nsDisplayFieldSetBorder final
: public nsPaintedDisplayItem
{
105 nsDisplayFieldSetBorder(nsDisplayListBuilder
* aBuilder
,
106 nsFieldSetFrame
* aFrame
)
107 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
108 MOZ_COUNT_CTOR(nsDisplayFieldSetBorder
);
110 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFieldSetBorder
)
112 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
113 bool CreateWebRenderCommands(
114 mozilla::wr::DisplayListBuilder
& aBuilder
,
115 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
116 const StackingContextHelper
& aSc
,
117 mozilla::layers::RenderRootStateManager
* aManager
,
118 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
119 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
120 bool* aSnap
) const override
;
121 NS_DISPLAY_DECL_NAME("FieldSetBorder", TYPE_FIELDSET_BORDER_BACKGROUND
)
124 void nsDisplayFieldSetBorder::Paint(nsDisplayListBuilder
* aBuilder
,
126 Unused
<< static_cast<nsFieldSetFrame
*>(mFrame
)->PaintBorder(
127 aBuilder
, *aCtx
, ToReferenceFrame(), GetPaintRect(aBuilder
, aCtx
));
130 nsRect
nsDisplayFieldSetBorder::GetBounds(nsDisplayListBuilder
* aBuilder
,
132 // Just go ahead and claim our frame's overflow rect as the bounds, because we
133 // may have border-image-outset or other features that cause borders to extend
134 // outside the border rect. We could try to duplicate all the complexity
135 // nsDisplayBorder has here, but keeping things in sync would be a pain, and
136 // this code is not typically performance-sensitive.
138 return Frame()->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
141 bool nsDisplayFieldSetBorder::CreateWebRenderCommands(
142 mozilla::wr::DisplayListBuilder
& aBuilder
,
143 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
144 const StackingContextHelper
& aSc
,
145 mozilla::layers::RenderRootStateManager
* aManager
,
146 nsDisplayListBuilder
* aDisplayListBuilder
) {
147 auto frame
= static_cast<nsFieldSetFrame
*>(mFrame
);
148 auto offset
= ToReferenceFrame();
149 Maybe
<wr::SpaceAndClipChainHelper
> clipOut
;
151 nsRect rect
= frame
->VisualBorderRectRelativeToSelf() + offset
;
152 nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
153 aBuilder
, aSc
, rect
, mFrame
, rect
);
155 if (nsIFrame
* legend
= frame
->GetLegend()) {
156 nsRect legendRect
= legend
->GetNormalRect() + offset
;
158 // Make sure we clip all of the border in case the legend is smaller.
159 nscoord borderTopWidth
= frame
->GetUsedBorder().top
;
160 if (legendRect
.height
< borderTopWidth
) {
161 legendRect
.height
= borderTopWidth
;
162 legendRect
.y
= offset
.y
;
165 if (!legendRect
.IsEmpty()) {
166 // We need to clip out the part of the border where the legend would go
167 auto appUnitsPerDevPixel
= frame
->PresContext()->AppUnitsPerDevPixel();
168 auto layoutRect
= wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
169 frame
->InkOverflowRectRelativeToSelf() + offset
,
170 appUnitsPerDevPixel
));
172 wr::ComplexClipRegion region
;
173 region
.rect
= wr::ToLayoutRect(
174 LayoutDeviceRect::FromAppUnits(legendRect
, appUnitsPerDevPixel
));
175 region
.mode
= wr::ClipMode::ClipOut
;
176 region
.radii
= wr::EmptyBorderRadius();
178 auto rect_clip
= aBuilder
.DefineRectClip(Nothing(), layoutRect
);
179 auto complex_clip
= aBuilder
.DefineRoundedRectClip(Nothing(), region
);
181 aBuilder
.DefineClipChain({rect_clip
, complex_clip
}, true);
182 clipOut
.emplace(aBuilder
, clipChain
);
185 rect
= nsRect(offset
, frame
->GetRect().Size());
188 ImgDrawResult drawResult
= nsCSSRendering::CreateWebRenderCommandsForBorder(
189 this, mFrame
, rect
, aBuilder
, aResources
, aSc
, aManager
,
190 aDisplayListBuilder
);
191 if (drawResult
== ImgDrawResult::NOT_SUPPORTED
) {
197 } // namespace mozilla
199 void nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
200 const nsDisplayListSet
& aLists
) {
201 // Paint our background and border in a special way.
202 // REVIEW: We don't really need to check frame emptiness here; if it's empty,
203 // the background/border display item won't do anything, and if it isn't
204 // empty, we need to paint the outline
205 if (!HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
) &&
206 IsVisibleForPainting()) {
207 DisplayOutsetBoxShadowUnconditional(aBuilder
, aLists
.BorderBackground());
210 VisualBorderRectRelativeToSelf() + aBuilder
->ToReferenceFrame(this);
212 nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
213 aBuilder
, this, rect
, aLists
.BorderBackground(),
214 /* aAllowWillPaintBorderOptimization = */ false);
216 aLists
.BorderBackground()->AppendNewToTop
<nsDisplayFieldSetBorder
>(aBuilder
,
219 DisplayOutlineUnconditional(aBuilder
, aLists
);
221 DO_GLOBAL_REFLOW_COUNT_DSP("nsFieldSetFrame");
224 if (GetPrevInFlow()) {
225 DisplayOverflowContainers(aBuilder
, aLists
);
228 nsDisplayListCollection
contentDisplayItems(aBuilder
);
229 if (nsIFrame
* inner
= GetInner()) {
230 // Collect the inner frame's display items into their own collection.
231 // We need to be calling BuildDisplayList on it before the legend in
232 // case it contains out-of-flow frames whose placeholders are in the
233 // legend. However, we want the inner frame's display items to be
234 // after the legend's display items in z-order, so we need to save them
235 // and append them later.
236 BuildDisplayListForChild(aBuilder
, inner
, contentDisplayItems
);
238 if (nsIFrame
* legend
= GetLegend()) {
239 // The legend's background goes on our BlockBorderBackgrounds list because
240 // it's a block child.
241 nsDisplayListSet
set(aLists
, aLists
.BlockBorderBackgrounds());
242 BuildDisplayListForChild(aBuilder
, legend
, set
);
244 // Put the inner frame's display items on the master list. Note that this
245 // moves its border/background display items to our BorderBackground() list,
246 // which isn't really correct, but it's OK because the inner frame is
247 // anonymous and can't have its own border and background.
248 contentDisplayItems
.MoveTo(aLists
);
251 ImgDrawResult
nsFieldSetFrame::PaintBorder(nsDisplayListBuilder
* aBuilder
,
252 gfxContext
& aRenderingContext
,
254 const nsRect
& aDirtyRect
) {
255 // If the border is smaller than the legend, move the border down
256 // to be centered on the legend. We call VisualBorderRectRelativeToSelf() to
257 // compute the border positioning.
258 // FIXME: This means border-radius clamping is incorrect; we should
259 // override nsIFrame::GetBorderRadii.
260 nsRect rect
= VisualBorderRectRelativeToSelf() + aPt
;
261 nsPresContext
* presContext
= PresContext();
263 const auto skipSides
= GetSkipSides();
264 PaintBorderFlags borderFlags
= aBuilder
->ShouldSyncDecodeImages()
265 ? PaintBorderFlags::SyncDecodeImages
266 : PaintBorderFlags();
268 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
270 nsCSSRendering::PaintBoxShadowInner(presContext
, aRenderingContext
, this,
273 if (nsIFrame
* legend
= GetLegend()) {
274 // We want to avoid drawing our border under the legend, so clip out the
275 // legend while drawing our border. We don't want to use mLegendRect here,
276 // because we do want to draw our border under the legend's inline-start and
277 // -end margins. And we use GetNormalRect(), not GetRect(), because we do
278 // not want relative positioning applied to the legend to change how our
280 nsRect legendRect
= legend
->GetNormalRect() + aPt
;
282 // Make sure we clip all of the border in case the legend is smaller.
283 nscoord borderTopWidth
= GetUsedBorder().top
;
284 if (legendRect
.height
< borderTopWidth
) {
285 legendRect
.height
= borderTopWidth
;
286 legendRect
.y
= aPt
.y
;
289 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
290 // We set up a clip path which has our rect clockwise and the legend rect
291 // counterclockwise, with FILL_WINDING as the fill rule. That will allow us
292 // to paint within our rect but outside the legend rect. For "our rect" we
293 // use our ink overflow rect (relative to ourselves, so it's not affected
294 // by transforms), because we can have borders sticking outside our border
295 // box (e.g. due to border-image-outset).
296 RefPtr
<PathBuilder
> pathBuilder
=
297 drawTarget
->CreatePathBuilder(FillRule::FILL_WINDING
);
298 int32_t appUnitsPerDevPixel
= presContext
->AppUnitsPerDevPixel();
299 AppendRectToPath(pathBuilder
,
300 NSRectToSnappedRect(InkOverflowRectRelativeToSelf() + aPt
,
301 appUnitsPerDevPixel
, *drawTarget
),
305 NSRectToSnappedRect(legendRect
, appUnitsPerDevPixel
, *drawTarget
),
307 RefPtr
<Path
> clipPath
= pathBuilder
->Finish();
309 aRenderingContext
.Save();
310 aRenderingContext
.Clip(clipPath
);
311 result
&= nsCSSRendering::PaintBorder(presContext
, aRenderingContext
, this,
312 aDirtyRect
, rect
, mComputedStyle
,
313 borderFlags
, skipSides
);
314 aRenderingContext
.Restore();
316 result
&= nsCSSRendering::PaintBorder(
317 presContext
, aRenderingContext
, this, aDirtyRect
,
318 nsRect(aPt
, mRect
.Size()), mComputedStyle
, borderFlags
, skipSides
);
324 nscoord
nsFieldSetFrame::GetIntrinsicISize(gfxContext
* aRenderingContext
,
325 IntrinsicISizeType aType
) {
326 // Both inner and legend are children, and if the fieldset is
327 // size-contained they should not contribute to the intrinsic size.
328 if (Maybe
<nscoord
> containISize
= ContainIntrinsicISize()) {
329 return *containISize
;
332 nscoord legendWidth
= 0;
333 if (nsIFrame
* legend
= GetLegend()) {
335 nsLayoutUtils::IntrinsicForContainer(aRenderingContext
, legend
, aType
);
338 nscoord contentWidth
= 0;
339 if (nsIFrame
* inner
= GetInner()) {
340 // Ignore padding on the inner, since the padding will be applied to the
341 // outer instead, and the padding computed for the inner is wrong
342 // for percentage padding.
343 contentWidth
= nsLayoutUtils::IntrinsicForContainer(
344 aRenderingContext
, inner
, aType
, nsLayoutUtils::IGNORE_PADDING
);
347 return std::max(legendWidth
, contentWidth
);
350 nscoord
nsFieldSetFrame::GetMinISize(gfxContext
* aRenderingContext
) {
352 DISPLAY_MIN_INLINE_SIZE(this, result
);
354 result
= GetIntrinsicISize(aRenderingContext
, IntrinsicISizeType::MinISize
);
358 nscoord
nsFieldSetFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
360 DISPLAY_PREF_INLINE_SIZE(this, result
);
362 result
= GetIntrinsicISize(aRenderingContext
, IntrinsicISizeType::PrefISize
);
367 void nsFieldSetFrame::Reflow(nsPresContext
* aPresContext
,
368 ReflowOutput
& aDesiredSize
,
369 const ReflowInput
& aReflowInput
,
370 nsReflowStatus
& aStatus
) {
371 using LegendAlignValue
= mozilla::dom::HTMLLegendElement::LegendAlignValue
;
374 DO_GLOBAL_REFLOW_COUNT("nsFieldSetFrame");
375 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aDesiredSize
, aStatus
);
376 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
377 NS_WARNING_ASSERTION(aReflowInput
.ComputedISize() != NS_UNCONSTRAINEDSIZE
,
378 "Should have a precomputed inline-size!");
380 OverflowAreas ocBounds
;
381 nsReflowStatus ocStatus
;
382 auto* prevInFlow
= static_cast<nsFieldSetFrame
*>(GetPrevInFlow());
384 ReflowOverflowContainerChildren(aPresContext
, aReflowInput
, ocBounds
,
385 ReflowChildFlags::Default
, ocStatus
);
387 AutoFrameListPtr
prevOverflowFrames(PresContext(),
388 prevInFlow
->StealOverflowFrames());
389 if (prevOverflowFrames
) {
390 nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames
, prevInFlow
,
392 mFrames
.InsertFrames(this, nullptr, std::move(*prevOverflowFrames
));
398 nsIFrame
* legend
= GetLegend();
399 nsContainerFrame
* inner
= GetInner();
400 if (!legend
|| !inner
) {
401 if (DrainSelfOverflowList()) {
402 legend
= GetLegend();
406 if (aReflowInput
.ShouldReflowAllKids() || GetNextInFlow() ||
407 aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
) {
408 reflowInner
= inner
!= nullptr;
409 reflowLegend
= legend
!= nullptr;
411 reflowInner
= inner
&& inner
->IsSubtreeDirty();
412 reflowLegend
= legend
&& legend
->IsSubtreeDirty();
415 // @note |this| frame applies borders but not any padding. Our anonymous
416 // inner frame applies the padding (but not borders).
417 const auto wm
= GetWritingMode();
418 auto skipSides
= PreReflowBlockLevelLogicalSkipSides();
419 LogicalMargin border
=
420 aReflowInput
.ComputedLogicalBorder(wm
).ApplySkipSides(skipSides
);
421 LogicalSize
availSize(wm
, aReflowInput
.ComputedSize().ISize(wm
),
422 aReflowInput
.AvailableBSize());
424 // Figure out how big the legend is if there is one.
425 LogicalMargin
legendMargin(wm
);
426 Maybe
<ReflowInput
> legendReflowInput
;
428 const auto legendWM
= legend
->GetWritingMode();
429 LogicalSize legendAvailSize
= availSize
.ConvertTo(legendWM
, wm
);
430 ComputeSizeFlags sizeFlags
;
431 if (legend
->StylePosition()->ISize(wm
).IsAuto()) {
432 sizeFlags
= ComputeSizeFlag::ShrinkWrap
;
434 ReflowInput::InitFlags initFlags
; // intentionally empty
435 StyleSizeOverrides sizeOverrides
; // intentionally empty
436 legendReflowInput
.emplace(aPresContext
, aReflowInput
, legend
,
437 legendAvailSize
, Nothing(), initFlags
,
438 sizeOverrides
, sizeFlags
);
440 const bool avoidBreakInside
= ShouldAvoidBreakInside(aReflowInput
);
442 ReflowOutput
legendDesiredSize(aReflowInput
);
444 // We'll move the legend to its proper place later, so the position
445 // and containerSize passed here are unimportant.
446 const nsSize dummyContainerSize
;
447 ReflowChild(legend
, aPresContext
, legendDesiredSize
, *legendReflowInput
, wm
,
448 LogicalPoint(wm
), dummyContainerSize
,
449 ReflowChildFlags::NoMoveFrame
, aStatus
);
451 if (aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
452 !(HasAnyStateBits(NS_FRAME_OUT_OF_FLOW
) &&
453 aReflowInput
.mStyleDisplay
->IsAbsolutelyPositionedStyle()) &&
454 !prevInFlow
&& !aReflowInput
.mFlags
.mIsTopOfPage
) {
455 // Propagate break-before from the legend to the fieldset.
456 if (legend
->StyleDisplay()->BreakBefore() ||
457 aStatus
.IsInlineBreakBefore()) {
458 aStatus
.SetInlineLineBreakBeforeAndReset();
461 // Honor break-inside:avoid by breaking before instead.
462 if (MOZ_UNLIKELY(avoidBreakInside
) && !aStatus
.IsFullyComplete()) {
463 aStatus
.SetInlineLineBreakBeforeAndReset();
468 // Calculate the legend's margin-box rectangle.
469 legendMargin
= legend
->GetLogicalUsedMargin(wm
);
470 mLegendRect
= LogicalRect(
471 wm
, 0, 0, legendDesiredSize
.ISize(wm
) + legendMargin
.IStartEnd(wm
),
472 legendDesiredSize
.BSize(wm
) + legendMargin
.BStartEnd(wm
));
473 // We subtract mLegendSpace from inner's content-box block-size below.
474 nscoord oldSpace
= mLegendSpace
;
476 nscoord borderBStart
= border
.BStart(wm
);
478 if (mLegendRect
.BSize(wm
) > borderBStart
) {
479 mLegendSpace
= mLegendRect
.BSize(wm
) - borderBStart
;
481 // Calculate the border-box position that would center the legend's
482 // border-box within the fieldset border:
483 nscoord off
= (borderBStart
- legendDesiredSize
.BSize(wm
)) / 2;
484 off
-= legendMargin
.BStart(wm
); // convert to a margin-box position
485 if (off
> nscoord(0)) {
486 // Align the legend to the end if center-aligning it would overflow.
487 nscoord overflow
= off
+ mLegendRect
.BSize(wm
) - borderBStart
;
488 if (overflow
> nscoord(0)) {
491 mLegendRect
.BStart(wm
) += off
;
495 mLegendSpace
= mLegendRect
.BSize(wm
);
498 // If mLegendSpace changes then we need to reflow |inner| as well.
499 if (mLegendSpace
!= oldSpace
&& inner
) {
503 FinishReflowChild(legend
, aPresContext
, legendDesiredSize
,
504 legendReflowInput
.ptr(), wm
, LogicalPoint(wm
),
505 dummyContainerSize
, ReflowChildFlags::NoMoveFrame
);
506 EnsureChildContinuation(legend
, aStatus
);
507 if (aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
508 !legend
->GetWritingMode().IsOrthogonalTo(wm
) &&
509 legend
->StyleDisplay()->BreakAfter() &&
510 (!legendReflowInput
->mFlags
.mIsTopOfPage
||
511 mLegendRect
.BSize(wm
) > 0) &&
512 aStatus
.IsComplete()) {
513 // Pretend that we ran out of space to push children of |inner|.
514 // XXX(mats) perhaps pushing the inner frame would be more correct,
515 // but we don't support that yet.
516 availSize
.BSize(wm
) = nscoord(0);
518 aStatus
.SetIncomplete();
520 } else if (!legend
) {
521 mLegendRect
.SetEmpty();
524 // mLegendSpace and mLegendRect haven't changed, but we need
525 // the used margin when placing the legend.
526 legendMargin
= legend
->GetLogicalUsedMargin(wm
);
529 // This containerSize is incomplete as yet: it does not include the size
530 // of the |inner| frame itself.
531 nsSize containerSize
=
532 (LogicalSize(wm
, 0, mLegendSpace
) + border
.Size(wm
)).GetPhysicalSize(wm
);
534 LogicalSize innerAvailSize
= availSize
;
535 innerAvailSize
.ISize(wm
) =
536 aReflowInput
.ComputedSizeWithPadding(wm
).ISize(wm
);
537 nscoord remainingComputedBSize
= aReflowInput
.ComputedBSize();
538 if (prevInFlow
&& remainingComputedBSize
!= NS_UNCONSTRAINEDSIZE
) {
539 // Subtract the consumed BSize associated with the legend.
540 for (nsIFrame
* prev
= prevInFlow
; prev
; prev
= prev
->GetPrevInFlow()) {
541 auto* prevFieldSet
= static_cast<nsFieldSetFrame
*>(prev
);
542 remainingComputedBSize
-= prevFieldSet
->mLegendSpace
;
544 remainingComputedBSize
= std::max(0, remainingComputedBSize
);
546 if (innerAvailSize
.BSize(wm
) != NS_UNCONSTRAINEDSIZE
) {
547 innerAvailSize
.BSize(wm
) -=
548 std::max(mLegendRect
.BSize(wm
), border
.BStart(wm
));
549 if (StyleBorder()->mBoxDecorationBreak
==
550 StyleBoxDecorationBreak::Clone
&&
551 (aReflowInput
.ComputedBSize() == NS_UNCONSTRAINEDSIZE
||
552 remainingComputedBSize
+
553 aReflowInput
.ComputedLogicalBorderPadding(wm
).BStartEnd(
555 availSize
.BSize(wm
))) {
556 innerAvailSize
.BSize(wm
) -= border
.BEnd(wm
);
558 innerAvailSize
.BSize(wm
) = std::max(0, innerAvailSize
.BSize(wm
));
560 ReflowInput
kidReflowInput(aPresContext
, aReflowInput
, inner
,
561 innerAvailSize
, Nothing(),
562 ReflowInput::InitFlag::CallerWillInit
);
563 // Override computed padding, in case it's percentage padding
565 aPresContext
, Nothing(), Nothing(),
566 Some(aReflowInput
.ComputedLogicalPadding(inner
->GetWritingMode())));
568 // Propagate the aspect-ratio flag to |inner| (i.e. the container frame
569 // wrapped by nsFieldSetFrame), so we can let |inner|'s reflow code handle
570 // automatic content-based minimum.
571 // Note: Init() resets this flag, so we have to copy it again here.
572 if (aReflowInput
.mFlags
.mIsBSizeSetByAspectRatio
) {
573 kidReflowInput
.mFlags
.mIsBSizeSetByAspectRatio
= true;
576 if (kidReflowInput
.mFlags
.mIsTopOfPage
) {
577 // Prevent break-before from |inner| if we have a legend.
578 kidReflowInput
.mFlags
.mIsTopOfPage
= !legend
;
580 // Our child is "height:100%" but we actually want its height to be reduced
581 // by the amount of content-height the legend is eating up, unless our
582 // height is unconstrained (in which case the child's will be too).
583 if (aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
) {
584 kidReflowInput
.SetComputedBSize(
585 std::max(0, remainingComputedBSize
- mLegendSpace
));
588 if (aReflowInput
.ComputedMinBSize() > 0) {
589 kidReflowInput
.SetComputedMinBSize(
590 std::max(0, aReflowInput
.ComputedMinBSize() - mLegendSpace
));
593 if (aReflowInput
.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE
) {
594 kidReflowInput
.SetComputedMaxBSize(
595 std::max(0, aReflowInput
.ComputedMaxBSize() - mLegendSpace
));
598 ReflowOutput
kidDesiredSize(kidReflowInput
);
600 kidReflowInput
.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
601 "Margins on anonymous fieldset child not supported!");
602 LogicalPoint
pt(wm
, border
.IStart(wm
), border
.BStart(wm
) + mLegendSpace
);
604 // We don't know the correct containerSize until we have reflowed |inner|,
605 // so we use a dummy value for now; FinishReflowChild will fix the position
607 const nsSize dummyContainerSize
;
608 nsReflowStatus status
;
609 // If our legend needs a continuation then *this* frame will have
610 // a continuation as well so we should keep our inner frame continuations
611 // too (even if 'inner' ends up being COMPLETE here). This ensures that
612 // our continuation will have a reasonable inline-size.
613 ReflowChildFlags flags
= aStatus
.IsFullyComplete()
614 ? ReflowChildFlags::Default
615 : ReflowChildFlags::NoDeleteNextInFlowChild
;
616 ReflowChild(inner
, aPresContext
, kidDesiredSize
, kidReflowInput
, wm
, pt
,
617 dummyContainerSize
, flags
, status
);
619 // Honor break-inside:avoid when possible by returning a BreakBefore status.
620 if (MOZ_UNLIKELY(avoidBreakInside
) && !prevInFlow
&&
621 !aReflowInput
.mFlags
.mIsTopOfPage
&&
622 availSize
.BSize(wm
) != NS_UNCONSTRAINEDSIZE
) {
623 if (status
.IsInlineBreakBefore() || !status
.IsFullyComplete()) {
624 aStatus
.SetInlineLineBreakBeforeAndReset();
629 // Update containerSize to account for size of the inner frame, so that
630 // FinishReflowChild can position it correctly.
631 containerSize
+= kidDesiredSize
.PhysicalSize();
632 FinishReflowChild(inner
, aPresContext
, kidDesiredSize
, &kidReflowInput
, wm
,
633 pt
, containerSize
, ReflowChildFlags::Default
);
634 EnsureChildContinuation(inner
, status
);
635 aStatus
.MergeCompletionStatusFrom(status
);
636 NS_FRAME_TRACE_REFLOW_OUT("FieldSet::Reflow", aStatus
);
638 // |inner| didn't need to be reflowed but we do need to include its size
640 containerSize
+= inner
->GetSize();
642 // No |inner| means it was already complete in an earlier continuation.
643 MOZ_ASSERT(prevInFlow
, "first-in-flow should always have an inner frame");
644 for (nsIFrame
* prev
= prevInFlow
; prev
; prev
= prev
->GetPrevInFlow()) {
645 auto* prevFieldSet
= static_cast<nsFieldSetFrame
*>(prev
);
646 if (auto* prevInner
= prevFieldSet
->GetInner()) {
647 containerSize
+= prevInner
->GetSize();
653 LogicalRect
contentRect(wm
);
655 // We don't support margins on inner, so our content rect is just the
656 // inner's border-box. (We don't really care about container size at this
657 // point, as we'll figure out the actual positioning later.)
658 contentRect
= inner
->GetLogicalRect(wm
, containerSize
);
659 } else if (prevInFlow
) {
660 auto size
= prevInFlow
->GetPaddingRectRelativeToSelf().Size();
661 contentRect
.ISize(wm
) = wm
.IsVertical() ? size
.height
: size
.width
;
665 // The legend is positioned inline-wards within the inner's content rect
666 // (so that padding on the fieldset affects the legend position).
667 LogicalRect innerContentRect
= contentRect
;
668 innerContentRect
.Deflate(wm
, aReflowInput
.ComputedLogicalPadding(wm
));
669 // If the inner content rect is larger than the legend, we can align the
671 if (innerContentRect
.ISize(wm
) > mLegendRect
.ISize(wm
)) {
672 // NOTE legend @align values are: left/right/center
673 // GetLogicalAlign converts left/right to start/end for the given WM.
674 // @see HTMLLegendElement::ParseAttribute/LogicalAlign
675 auto* legendElement
=
676 dom::HTMLLegendElement::FromNode(legend
->GetContent());
677 switch (legendElement
->LogicalAlign(wm
)) {
678 case LegendAlignValue::InlineEnd
:
679 mLegendRect
.IStart(wm
) =
680 innerContentRect
.IEnd(wm
) - mLegendRect
.ISize(wm
);
682 case LegendAlignValue::Center
:
683 // Note: rounding removed; there doesn't seem to be any need
684 mLegendRect
.IStart(wm
) =
685 innerContentRect
.IStart(wm
) +
686 (innerContentRect
.ISize(wm
) - mLegendRect
.ISize(wm
)) / 2;
688 case LegendAlignValue::InlineStart
:
689 mLegendRect
.IStart(wm
) = innerContentRect
.IStart(wm
);
692 MOZ_ASSERT_UNREACHABLE("unexpected GetLogicalAlign value");
695 // otherwise just start-align it.
696 mLegendRect
.IStart(wm
) = innerContentRect
.IStart(wm
);
700 LogicalRect actualLegendRect
= mLegendRect
;
701 actualLegendRect
.Deflate(wm
, legendMargin
);
702 LogicalPoint
actualLegendPos(actualLegendRect
.Origin(wm
));
704 // Note that legend's writing mode may be different from the fieldset's,
705 // so we need to convert offsets before applying them to it (bug 1134534).
706 LogicalMargin offsets
= legendReflowInput
->ComputedLogicalOffsets(wm
);
707 ReflowInput::ApplyRelativePositioning(legend
, wm
, offsets
, &actualLegendPos
,
710 legend
->SetPosition(wm
, actualLegendPos
, containerSize
);
711 nsContainerFrame::PositionFrameView(legend
);
712 nsContainerFrame::PositionChildViews(legend
);
715 // Skip our block-end border if we're INCOMPLETE.
716 if (!aStatus
.IsComplete() &&
717 StyleBorder()->mBoxDecorationBreak
!= StyleBoxDecorationBreak::Clone
) {
718 border
.BEnd(wm
) = nscoord(0);
721 // Return our size and our result.
722 LogicalSize
finalSize(
723 wm
, contentRect
.ISize(wm
) + border
.IStartEnd(wm
),
724 mLegendSpace
+ border
.BStartEnd(wm
) + (inner
? inner
->BSize(wm
) : 0));
725 if (Maybe
<nscoord
> containBSize
=
726 aReflowInput
.mFrame
->ContainIntrinsicBSize()) {
727 // If we're size-contained in block axis, then we must set finalSize
728 // according to contain-intrinsic-block-size, disregarding legend and inner.
729 // Note: normally the fieldset's own padding (which we still must honor)
730 // would be accounted for as part of inner's size (see kidReflowInput.Init()
731 // call above). So: since we're disregarding sizing information from
732 // 'inner', we need to account for that padding ourselves here.
733 nscoord contentBoxBSize
=
734 aReflowInput
.ComputedBSize() == NS_UNCONSTRAINEDSIZE
735 ? aReflowInput
.ApplyMinMaxBSize(*containBSize
)
736 : aReflowInput
.ComputedBSize();
737 finalSize
.BSize(wm
) =
739 aReflowInput
.ComputedLogicalBorderPadding(wm
).BStartEnd(wm
);
742 if (aStatus
.IsComplete() &&
743 aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&&
744 finalSize
.BSize(wm
) > aReflowInput
.AvailableBSize() &&
745 border
.BEnd(wm
) > 0 && aReflowInput
.AvailableBSize() > border
.BEnd(wm
)) {
746 // Our end border doesn't fit but it should fit in the next column/page.
747 if (MOZ_UNLIKELY(avoidBreakInside
)) {
748 aStatus
.SetInlineLineBreakBeforeAndReset();
751 if (StyleBorder()->mBoxDecorationBreak
==
752 StyleBoxDecorationBreak::Slice
) {
753 finalSize
.BSize(wm
) -= border
.BEnd(wm
);
755 aStatus
.SetIncomplete();
759 if (!aStatus
.IsComplete()) {
760 MOZ_ASSERT(aReflowInput
.AvailableBSize() != NS_UNCONSTRAINEDSIZE
,
761 "must be Complete in an unconstrained available block-size");
762 // Stretch our BSize to fill the fragmentainer.
763 finalSize
.BSize(wm
) =
764 std::max(finalSize
.BSize(wm
), aReflowInput
.AvailableBSize());
766 aDesiredSize
.SetSize(wm
, finalSize
);
767 aDesiredSize
.SetOverflowAreasToDesiredBounds();
770 ConsiderChildOverflow(aDesiredSize
.mOverflowAreas
, legend
);
773 ConsiderChildOverflow(aDesiredSize
.mOverflowAreas
, inner
);
776 // Merge overflow container bounds and status.
777 aDesiredSize
.mOverflowAreas
.UnionWith(ocBounds
);
778 aStatus
.MergeCompletionStatusFrom(ocStatus
);
780 FinishReflowWithAbsoluteFrames(aPresContext
, aDesiredSize
, aReflowInput
,
785 void nsFieldSetFrame::SetInitialChildList(ChildListID aListID
,
786 nsFrameList
&& aChildList
) {
787 nsContainerFrame::SetInitialChildList(aListID
, std::move(aChildList
));
788 if (nsBlockFrame
* legend
= do_QueryFrame(GetLegend())) {
789 // A rendered legend always establish a new formatting context.
790 // https://html.spec.whatwg.org/multipage/rendering.html#rendered-legend
791 legend
->AddStateBits(NS_BLOCK_STATIC_BFC
);
794 aListID
!= FrameChildListID::Principal
|| GetInner() || GetLegend(),
795 "Setting principal child list should populate our inner frame "
796 "or our rendered legend");
799 void nsFieldSetFrame::AppendFrames(ChildListID aListID
,
800 nsFrameList
&& aFrameList
) {
801 MOZ_ASSERT(aListID
== FrameChildListID::NoReflowPrincipal
&&
802 HasAnyStateBits(NS_FRAME_FIRST_REFLOW
),
803 "AppendFrames should only be used from "
804 "nsCSSFrameConstructor::ConstructFieldSetFrame");
805 nsContainerFrame::AppendFrames(aListID
, std::move(aFrameList
));
806 MOZ_ASSERT(GetInner(), "at this point we should have an inner frame");
809 void nsFieldSetFrame::InsertFrames(ChildListID aListID
, nsIFrame
* aPrevFrame
,
810 const nsLineList::iterator
* aPrevFrameLine
,
811 nsFrameList
&& aFrameList
) {
813 aListID
== FrameChildListID::Principal
&& !aPrevFrame
&& !GetLegend(),
814 "InsertFrames should only be used to prepend a rendered legend "
815 "from nsCSSFrameConstructor::ConstructFramesFromItemList");
816 nsContainerFrame::InsertFrames(aListID
, aPrevFrame
, aPrevFrameLine
,
817 std::move(aFrameList
));
818 MOZ_ASSERT(GetLegend());
819 if (nsBlockFrame
* legend
= do_QueryFrame(GetLegend())) {
820 // A rendered legend always establish a new formatting context.
821 // https://html.spec.whatwg.org/multipage/rendering.html#rendered-legend
822 legend
->AddStateBits(NS_BLOCK_STATIC_BFC
);
827 void nsFieldSetFrame::RemoveFrame(DestroyContext
&, ChildListID
, nsIFrame
*) {
828 MOZ_CRASH("nsFieldSetFrame::RemoveFrame not supported");
833 a11y::AccType
nsFieldSetFrame::AccessibleType() {
834 return a11y::eHTMLGroupboxType
;
838 BaselineSharingGroup
nsFieldSetFrame::GetDefaultBaselineSharingGroup() const {
839 switch (StyleDisplay()->DisplayInside()) {
840 case mozilla::StyleDisplayInside::Grid
:
841 case mozilla::StyleDisplayInside::Flex
:
842 return BaselineSharingGroup::First
;
844 return BaselineSharingGroup::Last
;
848 nscoord
nsFieldSetFrame::SynthesizeFallbackBaseline(
849 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
) const {
850 return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM
, aBaselineGroup
);
853 Maybe
<nscoord
> nsFieldSetFrame::GetNaturalBaselineBOffset(
854 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
855 BaselineExportContext aExportContext
) const {
856 if (StyleDisplay()->IsContainLayout()) {
857 // If we are layout-contained, our child 'inner' should not
858 // affect how we calculate our baseline.
861 nsIFrame
* inner
= GetInner();
862 if (MOZ_UNLIKELY(!inner
)) {
865 MOZ_ASSERT(!inner
->GetWritingMode().IsOrthogonalTo(aWM
));
867 inner
->GetNaturalBaselineBOffset(aWM
, aBaselineGroup
, aExportContext
);
871 nscoord innerBStart
= inner
->BStart(aWM
, GetSize());
872 if (aBaselineGroup
== BaselineSharingGroup::First
) {
873 return Some(*result
+ innerBStart
);
875 return Some(*result
+ BSize(aWM
) - (innerBStart
+ inner
->BSize(aWM
)));
878 nsIScrollableFrame
* nsFieldSetFrame::GetScrollTargetFrame() const {
879 return do_QueryFrame(GetInner());
882 void nsFieldSetFrame::AppendDirectlyOwnedAnonBoxes(
883 nsTArray
<OwnedAnonBox
>& aResult
) {
884 if (nsIFrame
* kid
= GetInner()) {
885 aResult
.AppendElement(OwnedAnonBox(kid
));
889 void nsFieldSetFrame::EnsureChildContinuation(nsIFrame
* aChild
,
890 const nsReflowStatus
& aStatus
) {
891 MOZ_ASSERT(aChild
== GetLegend() || aChild
== GetInner(),
892 "unexpected child frame");
893 nsIFrame
* nif
= aChild
->GetNextInFlow();
894 if (aStatus
.IsFullyComplete()) {
896 // NOTE: we want to avoid our DEBUG version of RemoveFrame above.
897 DestroyContext
context(PresShell());
898 nsContainerFrame::RemoveFrame(context
,
899 FrameChildListID::NoReflowPrincipal
, nif
);
900 MOZ_ASSERT(!aChild
->GetNextInFlow());
905 auto* fc
= PresShell()->FrameConstructor();
906 nif
= fc
->CreateContinuingFrame(aChild
, this);
907 if (aStatus
.IsOverflowIncomplete()) {
908 nif
->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
910 nifs
= nsFrameList(nif
, nif
);
912 // Steal all nifs and push them again in case they are currently on
914 for (nsIFrame
* n
= nif
; n
; n
= n
->GetNextInFlow()) {
915 n
->GetParent()->StealFrame(n
);
916 nifs
.AppendFrame(this, n
);
917 if (aStatus
.IsOverflowIncomplete()) {
918 n
->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
920 n
->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER
);
924 if (aStatus
.IsOverflowIncomplete()) {
925 if (nsFrameList
* eoc
= GetExcessOverflowContainers()) {
926 eoc
->AppendFrames(nullptr, std::move(nifs
));
928 SetExcessOverflowContainers(std::move(nifs
));
931 if (nsFrameList
* oc
= GetOverflowFrames()) {
932 oc
->AppendFrames(nullptr, std::move(nifs
));
934 SetOverflowFrames(std::move(nifs
));