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