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