1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsFieldSetFrame.h"
8 
9 #include <algorithm>
10 #include "gfxContext.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/Likely.h"
13 #include "mozilla/Maybe.h"
14 #include "nsCSSAnonBoxes.h"
15 #include "nsCSSRendering.h"
16 #include "nsDisplayList.h"
17 #include "nsGkAtoms.h"
18 #include "nsIFrameInlines.h"
19 #include "nsLayoutUtils.h"
20 #include "nsLegendFrame.h"
21 #include "nsStyleConsts.h"
22 
23 using namespace mozilla;
24 using namespace mozilla::gfx;
25 using namespace mozilla::layout;
26 
NS_NewFieldSetFrame(nsIPresShell * aPresShell,nsStyleContext * aContext)27 nsContainerFrame* NS_NewFieldSetFrame(nsIPresShell* aPresShell,
28                                       nsStyleContext* aContext) {
29   return new (aPresShell) nsFieldSetFrame(aContext);
30 }
31 
NS_IMPL_FRAMEARENA_HELPERS(nsFieldSetFrame)32 NS_IMPL_FRAMEARENA_HELPERS(nsFieldSetFrame)
33 
34 nsFieldSetFrame::nsFieldSetFrame(nsStyleContext* aContext)
35     : nsContainerFrame(aContext, kClassID), mLegendRect(GetWritingMode()) {
36   mLegendSpace = 0;
37 }
38 
VisualBorderRectRelativeToSelf() const39 nsRect nsFieldSetFrame::VisualBorderRectRelativeToSelf() const {
40   WritingMode wm = GetWritingMode();
41   Side legendSide = wm.PhysicalSide(eLogicalSideBStart);
42   nscoord legendBorder = StyleBorder()->GetComputedBorderWidth(legendSide);
43   LogicalRect r(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm));
44   nsSize containerSize = r.Size(wm).GetPhysicalSize(wm);
45   if (legendBorder < mLegendRect.BSize(wm)) {
46     nscoord off = (mLegendRect.BSize(wm) - legendBorder) / 2;
47     r.BStart(wm) += off;
48     r.BSize(wm) -= off;
49   }
50   return r.GetPhysicalRect(wm, containerSize);
51 }
52 
GetInner() const53 nsIFrame* nsFieldSetFrame::GetInner() const {
54   nsIFrame* last = mFrames.LastChild();
55   if (last &&
56       last->StyleContext()->GetPseudo() == nsCSSAnonBoxes::fieldsetContent) {
57     return last;
58   }
59   MOZ_ASSERT(mFrames.LastChild() == mFrames.FirstChild());
60   return nullptr;
61 }
62 
GetLegend() const63 nsIFrame* nsFieldSetFrame::GetLegend() const {
64   if (mFrames.FirstChild() == GetInner()) {
65     MOZ_ASSERT(mFrames.LastChild() == mFrames.FirstChild());
66     return nullptr;
67   }
68   MOZ_ASSERT(mFrames.FirstChild() &&
69              mFrames.FirstChild()->GetContentInsertionFrame()->IsLegendFrame());
70   return mFrames.FirstChild();
71 }
72 
73 class nsDisplayFieldSetBorder : public nsDisplayItem {
74  public:
nsDisplayFieldSetBorder(nsDisplayListBuilder * aBuilder,nsFieldSetFrame * aFrame)75   nsDisplayFieldSetBorder(nsDisplayListBuilder* aBuilder,
76                           nsFieldSetFrame* aFrame)
77       : nsDisplayItem(aBuilder, aFrame) {
78     MOZ_COUNT_CTOR(nsDisplayFieldSetBorder);
79   }
80 #ifdef NS_BUILD_REFCNT_LOGGING
~nsDisplayFieldSetBorder()81   virtual ~nsDisplayFieldSetBorder() {
82     MOZ_COUNT_DTOR(nsDisplayFieldSetBorder);
83   }
84 #endif
85   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
86   virtual nsDisplayItemGeometry* AllocateGeometry(
87       nsDisplayListBuilder* aBuilder) override;
88   virtual void ComputeInvalidationRegion(
89       nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
90       nsRegion* aInvalidRegion) const override;
91   bool CreateWebRenderCommands(
92       mozilla::wr::DisplayListBuilder& aBuilder,
93       mozilla::wr::IpcResourceUpdateQueue& aResources,
94       const StackingContextHelper& aSc,
95       mozilla::layers::WebRenderLayerManager* aManager,
96       nsDisplayListBuilder* aDisplayListBuilder) override;
97   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
98                            bool* aSnap) const override;
99   NS_DISPLAY_DECL_NAME("FieldSetBorder", TYPE_FIELDSET_BORDER_BACKGROUND)
100 };
101 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)102 void nsDisplayFieldSetBorder::Paint(nsDisplayListBuilder* aBuilder,
103                                     gfxContext* aCtx) {
104   image::ImgDrawResult result =
105       static_cast<nsFieldSetFrame*>(mFrame)->PaintBorder(
106           aBuilder, *aCtx, ToReferenceFrame(), mVisibleRect);
107 
108   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
109 }
110 
AllocateGeometry(nsDisplayListBuilder * aBuilder)111 nsDisplayItemGeometry* nsDisplayFieldSetBorder::AllocateGeometry(
112     nsDisplayListBuilder* aBuilder) {
113   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
114 }
115 
ComputeInvalidationRegion(nsDisplayListBuilder * aBuilder,const nsDisplayItemGeometry * aGeometry,nsRegion * aInvalidRegion) const116 void nsDisplayFieldSetBorder::ComputeInvalidationRegion(
117     nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
118     nsRegion* aInvalidRegion) const {
119   auto geometry =
120       static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
121 
122   if (aBuilder->ShouldSyncDecodeImages() &&
123       geometry->ShouldInvalidateToSyncDecodeImages()) {
124     bool snap;
125     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
126   }
127 
128   nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
129 }
130 
GetBounds(nsDisplayListBuilder * aBuilder,bool * aSnap) const131 nsRect nsDisplayFieldSetBorder::GetBounds(nsDisplayListBuilder* aBuilder,
132                                           bool* aSnap) const {
133   // Just go ahead and claim our frame's overflow rect as the bounds, because we
134   // may have border-image-outset or other features that cause borders to extend
135   // outside the border rect.  We could try to duplicate all the complexity
136   // nsDisplayBorder has here, but keeping things in sync would be a pain, and
137   // this code is not typically performance-sensitive.
138   *aSnap = false;
139   return Frame()->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
140 }
141 
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,mozilla::layers::WebRenderLayerManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)142 bool nsDisplayFieldSetBorder::CreateWebRenderCommands(
143     mozilla::wr::DisplayListBuilder& aBuilder,
144     mozilla::wr::IpcResourceUpdateQueue& aResources,
145     const StackingContextHelper& aSc,
146     mozilla::layers::WebRenderLayerManager* aManager,
147     nsDisplayListBuilder* aDisplayListBuilder) {
148   auto frame = static_cast<nsFieldSetFrame*>(mFrame);
149   auto offset = ToReferenceFrame();
150   nsRect rect;
151 
152   if (nsIFrame* legend = frame->GetLegend()) {
153     rect = frame->VisualBorderRectRelativeToSelf() + offset;
154 
155     // Legends require a "negative" clip around the text, which WR doesn't
156     // support yet.
157     nsRect legendRect = legend->GetNormalRect() + offset;
158     if (!legendRect.IsEmpty()) {
159       return false;
160     }
161   } else {
162     rect = nsRect(offset, frame->GetRect().Size());
163   }
164 
165   return nsCSSRendering::CreateWebRenderCommandsForBorder(
166       this, mFrame, rect, aBuilder, aResources, aSc, aManager,
167       aDisplayListBuilder);
168 };
169 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)170 void nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
171                                        const nsDisplayListSet& aLists) {
172   // Paint our background and border in a special way.
173   // REVIEW: We don't really need to check frame emptiness here; if it's empty,
174   // the background/border display item won't do anything, and if it isn't
175   // empty, we need to paint the outline
176   if (!(GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) &&
177       IsVisibleForPainting(aBuilder)) {
178     if (StyleEffects()->mBoxShadow) {
179       aLists.BorderBackground()->AppendToTop(
180           MakeDisplayItem<nsDisplayBoxShadowOuter>(aBuilder, this));
181     }
182 
183     nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
184         aBuilder, this, VisualBorderRectRelativeToSelf(),
185         aLists.BorderBackground(),
186         /* aAllowWillPaintBorderOptimization = */ false);
187 
188     aLists.BorderBackground()->AppendToTop(
189         MakeDisplayItem<nsDisplayFieldSetBorder>(aBuilder, this));
190 
191     DisplayOutlineUnconditional(aBuilder, aLists);
192 
193     DO_GLOBAL_REFLOW_COUNT_DSP("nsFieldSetFrame");
194   }
195 
196   if (GetPrevInFlow()) {
197     DisplayOverflowContainers(aBuilder, aLists);
198   }
199 
200   nsDisplayListCollection contentDisplayItems(aBuilder);
201   if (nsIFrame* inner = GetInner()) {
202     // Collect the inner frame's display items into their own collection.
203     // We need to be calling BuildDisplayList on it before the legend in
204     // case it contains out-of-flow frames whose placeholders are in the
205     // legend. However, we want the inner frame's display items to be
206     // after the legend's display items in z-order, so we need to save them
207     // and append them later.
208     BuildDisplayListForChild(aBuilder, inner, contentDisplayItems);
209   }
210   if (nsIFrame* legend = GetLegend()) {
211     // The legend's background goes on our BlockBorderBackgrounds list because
212     // it's a block child.
213     nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds());
214     BuildDisplayListForChild(aBuilder, legend, set);
215   }
216   // Put the inner frame's display items on the master list. Note that this
217   // moves its border/background display items to our BorderBackground() list,
218   // which isn't really correct, but it's OK because the inner frame is
219   // anonymous and can't have its own border and background.
220   contentDisplayItems.MoveTo(aLists);
221 }
222 
PaintBorder(nsDisplayListBuilder * aBuilder,gfxContext & aRenderingContext,nsPoint aPt,const nsRect & aDirtyRect)223 image::ImgDrawResult nsFieldSetFrame::PaintBorder(
224     nsDisplayListBuilder* aBuilder, gfxContext& aRenderingContext, nsPoint aPt,
225     const nsRect& aDirtyRect) {
226   // If the border is smaller than the legend, move the border down
227   // to be centered on the legend.  We call VisualBorderRectRelativeToSelf() to
228   // compute the border positioning.
229   // FIXME: This means border-radius clamping is incorrect; we should
230   // override nsIFrame::GetBorderRadii.
231   nsRect rect = VisualBorderRectRelativeToSelf() + aPt;
232   nsPresContext* presContext = PresContext();
233 
234   PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
235                                      ? PaintBorderFlags::SYNC_DECODE_IMAGES
236                                      : PaintBorderFlags();
237 
238   ImgDrawResult result = ImgDrawResult::SUCCESS;
239 
240   nsCSSRendering::PaintBoxShadowInner(presContext, aRenderingContext, this,
241                                       rect);
242 
243   if (nsIFrame* legend = GetLegend()) {
244     // We want to avoid drawing our border under the legend, so clip out the
245     // legend while drawing our border.  We don't want to use mLegendRect here,
246     // because we do want to draw our border under the legend's inline-start and
247     // -end margins.  And we use GetNormalRect(), not GetRect(), because we do
248     // not want relative positioning applied to the legend to change how our
249     // border looks.
250     nsRect legendRect = legend->GetNormalRect() + aPt;
251 
252     DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
253     // We set up a clip path which has our rect clockwise and the legend rect
254     // counterclockwise, with FILL_WINDING as the fill rule.  That will allow us
255     // to paint within our rect but outside the legend rect.  For "our rect" we
256     // use our visual overflow rect (relative to ourselves, so it's not affected
257     // by transforms), because we can have borders sticking outside our border
258     // box (e.g. due to border-image-outset).
259     RefPtr<PathBuilder> pathBuilder =
260         drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
261     int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
262     AppendRectToPath(
263         pathBuilder,
264         NSRectToSnappedRect(GetVisualOverflowRectRelativeToSelf() + aPt,
265                             appUnitsPerDevPixel, *drawTarget),
266         true);
267     AppendRectToPath(
268         pathBuilder,
269         NSRectToSnappedRect(legendRect, appUnitsPerDevPixel, *drawTarget),
270         false);
271     RefPtr<Path> clipPath = pathBuilder->Finish();
272 
273     aRenderingContext.Save();
274     aRenderingContext.Clip(clipPath);
275     result &= nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
276                                           aDirtyRect, rect, mStyleContext,
277                                           borderFlags);
278     aRenderingContext.Restore();
279   } else {
280     result &= nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
281                                           aDirtyRect, nsRect(aPt, mRect.Size()),
282                                           mStyleContext, borderFlags);
283   }
284 
285   return result;
286 }
287 
GetIntrinsicISize(gfxContext * aRenderingContext,nsLayoutUtils::IntrinsicISizeType aType)288 nscoord nsFieldSetFrame::GetIntrinsicISize(
289     gfxContext* aRenderingContext, nsLayoutUtils::IntrinsicISizeType aType) {
290   nscoord legendWidth = 0;
291   nscoord contentWidth = 0;
292   if (nsIFrame* legend = GetLegend()) {
293     legendWidth =
294         nsLayoutUtils::IntrinsicForContainer(aRenderingContext, legend, aType);
295   }
296 
297   if (nsIFrame* inner = GetInner()) {
298     // Ignore padding on the inner, since the padding will be applied to the
299     // outer instead, and the padding computed for the inner is wrong
300     // for percentage padding.
301     contentWidth = nsLayoutUtils::IntrinsicForContainer(
302         aRenderingContext, inner, aType, nsLayoutUtils::IGNORE_PADDING);
303   }
304 
305   return std::max(legendWidth, contentWidth);
306 }
307 
GetMinISize(gfxContext * aRenderingContext)308 nscoord nsFieldSetFrame::GetMinISize(gfxContext* aRenderingContext) {
309   nscoord result = 0;
310   DISPLAY_MIN_WIDTH(this, result);
311 
312   result = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::MIN_ISIZE);
313   return result;
314 }
315 
GetPrefISize(gfxContext * aRenderingContext)316 nscoord nsFieldSetFrame::GetPrefISize(gfxContext* aRenderingContext) {
317   nscoord result = 0;
318   DISPLAY_PREF_WIDTH(this, result);
319 
320   result = GetIntrinsicISize(aRenderingContext, nsLayoutUtils::PREF_ISIZE);
321   return result;
322 }
323 
324 /* virtual */
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)325 void nsFieldSetFrame::Reflow(nsPresContext* aPresContext,
326                              ReflowOutput& aDesiredSize,
327                              const ReflowInput& aReflowInput,
328                              nsReflowStatus& aStatus) {
329   MarkInReflow();
330   DO_GLOBAL_REFLOW_COUNT("nsFieldSetFrame");
331   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
332   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
333   NS_PRECONDITION(aReflowInput.ComputedISize() != NS_INTRINSICSIZE,
334                   "Should have a precomputed inline-size!");
335 
336   nsOverflowAreas ocBounds;
337   nsReflowStatus ocStatus;
338   if (GetPrevInFlow()) {
339     ReflowOverflowContainerChildren(aPresContext, aReflowInput, ocBounds, 0,
340                                     ocStatus);
341   }
342 
343   //------------ Handle Incremental Reflow -----------------
344   bool reflowInner;
345   bool reflowLegend;
346   nsIFrame* legend = GetLegend();
347   nsIFrame* inner = GetInner();
348   if (aReflowInput.ShouldReflowAllKids()) {
349     reflowInner = inner != nullptr;
350     reflowLegend = legend != nullptr;
351   } else {
352     reflowInner = inner && NS_SUBTREE_DIRTY(inner);
353     reflowLegend = legend && NS_SUBTREE_DIRTY(legend);
354   }
355 
356   // We don't allow fieldsets to break vertically. If we did, we'd
357   // need logic here to push and pull overflow frames.
358   // Since we're not applying our padding in this frame, we need to add it here
359   // to compute the available width for our children.
360   WritingMode wm = GetWritingMode();
361   WritingMode innerWM = inner ? inner->GetWritingMode() : wm;
362   WritingMode legendWM = legend ? legend->GetWritingMode() : wm;
363   LogicalSize innerAvailSize = aReflowInput.ComputedSizeWithPadding(innerWM);
364   LogicalSize legendAvailSize = aReflowInput.ComputedSizeWithPadding(legendWM);
365   innerAvailSize.BSize(innerWM) = legendAvailSize.BSize(legendWM) =
366       NS_UNCONSTRAINEDSIZE;
367 
368   // get our border and padding
369   LogicalMargin border = aReflowInput.ComputedLogicalBorderPadding() -
370                          aReflowInput.ComputedLogicalPadding();
371 
372   // Figure out how big the legend is if there is one.
373   // get the legend's margin
374   LogicalMargin legendMargin(wm);
375   // reflow the legend only if needed
376   Maybe<ReflowInput> legendReflowInput;
377   if (legend) {
378     legendReflowInput.emplace(aPresContext, aReflowInput, legend,
379                               legendAvailSize);
380   }
381   if (reflowLegend) {
382     ReflowOutput legendDesiredSize(aReflowInput);
383 
384     // We'll move the legend to its proper place later, so the position
385     // and containerSize passed here are unimportant.
386     const nsSize dummyContainerSize;
387     ReflowChild(legend, aPresContext, legendDesiredSize, *legendReflowInput, wm,
388                 LogicalPoint(wm), dummyContainerSize, NS_FRAME_NO_MOVE_FRAME,
389                 aStatus);
390 #ifdef NOISY_REFLOW
391     printf("  returned (%d, %d)\n", legendDesiredSize.Width(),
392            legendDesiredSize.Height());
393 #endif
394     // figure out the legend's rectangle
395     legendMargin = legend->GetLogicalUsedMargin(wm);
396     mLegendRect = LogicalRect(
397         wm, 0, 0, legendDesiredSize.ISize(wm) + legendMargin.IStartEnd(wm),
398         legendDesiredSize.BSize(wm) + legendMargin.BStartEnd(wm));
399     nscoord oldSpace = mLegendSpace;
400     mLegendSpace = 0;
401     if (mLegendRect.BSize(wm) > border.BStart(wm)) {
402       // center the border on the legend
403       mLegendSpace = mLegendRect.BSize(wm) - border.BStart(wm);
404     } else {
405       mLegendRect.BStart(wm) = (border.BStart(wm) - mLegendRect.BSize(wm)) / 2;
406     }
407 
408     // if the legend space changes then we need to reflow the
409     // content area as well.
410     if (mLegendSpace != oldSpace && inner) {
411       reflowInner = true;
412     }
413 
414     FinishReflowChild(legend, aPresContext, legendDesiredSize,
415                       legendReflowInput.ptr(), wm, LogicalPoint(wm),
416                       dummyContainerSize, NS_FRAME_NO_MOVE_FRAME);
417   } else if (!legend) {
418     mLegendRect.SetEmpty();
419     mLegendSpace = 0;
420   } else {
421     // mLegendSpace and mLegendRect haven't changed, but we need
422     // the used margin when placing the legend.
423     legendMargin = legend->GetLogicalUsedMargin(wm);
424   }
425 
426   // This containerSize is incomplete as yet: it does not include the size
427   // of the |inner| frame itself.
428   nsSize containerSize =
429       (LogicalSize(wm, 0, mLegendSpace) + border.Size(wm)).GetPhysicalSize(wm);
430   // reflow the content frame only if needed
431   if (reflowInner) {
432     ReflowInput kidReflowInput(aPresContext, aReflowInput, inner,
433                                innerAvailSize, nullptr,
434                                ReflowInput::CALLER_WILL_INIT);
435     // Override computed padding, in case it's percentage padding
436     kidReflowInput.Init(aPresContext, nullptr, nullptr,
437                         &aReflowInput.ComputedPhysicalPadding());
438     // Our child is "height:100%" but we actually want its height to be reduced
439     // by the amount of content-height the legend is eating up, unless our
440     // height is unconstrained (in which case the child's will be too).
441     if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
442       kidReflowInput.SetComputedBSize(
443           std::max(0, aReflowInput.ComputedBSize() - mLegendSpace));
444     }
445 
446     if (aReflowInput.ComputedMinBSize() > 0) {
447       kidReflowInput.ComputedMinBSize() =
448           std::max(0, aReflowInput.ComputedMinBSize() - mLegendSpace);
449     }
450 
451     if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
452       kidReflowInput.ComputedMaxBSize() =
453           std::max(0, aReflowInput.ComputedMaxBSize() - mLegendSpace);
454     }
455 
456     ReflowOutput kidDesiredSize(kidReflowInput, aDesiredSize.mFlags);
457     // Reflow the frame
458     NS_ASSERTION(
459         kidReflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
460         "Margins on anonymous fieldset child not supported!");
461     LogicalPoint pt(wm, border.IStart(wm), border.BStart(wm) + mLegendSpace);
462 
463     // We don't know the correct containerSize until we have reflowed |inner|,
464     // so we use a dummy value for now; FinishReflowChild will fix the position
465     // if necessary.
466     const nsSize dummyContainerSize;
467     ReflowChild(inner, aPresContext, kidDesiredSize, kidReflowInput, wm, pt,
468                 dummyContainerSize, 0, aStatus);
469 
470     // Update containerSize to account for size of the inner frame, so that
471     // FinishReflowChild can position it correctly.
472     containerSize += kidDesiredSize.PhysicalSize();
473     FinishReflowChild(inner, aPresContext, kidDesiredSize, &kidReflowInput, wm,
474                       pt, containerSize, 0);
475     NS_FRAME_TRACE_REFLOW_OUT("FieldSet::Reflow", aStatus);
476   } else if (inner) {
477     // |inner| didn't need to be reflowed but we do need to include its size
478     // in containerSize.
479     containerSize += inner->GetSize();
480   }
481 
482   LogicalRect contentRect(wm);
483   if (inner) {
484     // We don't support margins on inner, so our content rect is just the
485     // inner's border-box. (We don't really care about container size at this
486     // point, as we'll figure out the actual positioning later.)
487     contentRect = inner->GetLogicalRect(wm, containerSize);
488   }
489 
490   // Our content rect must fill up the available width
491   LogicalSize availSize = aReflowInput.ComputedSizeWithPadding(wm);
492   if (availSize.ISize(wm) > contentRect.ISize(wm)) {
493     contentRect.ISize(wm) = innerAvailSize.ISize(wm);
494   }
495 
496   if (legend) {
497     // The legend is positioned inline-wards within the inner's content rect
498     // (so that padding on the fieldset affects the legend position).
499     LogicalRect innerContentRect = contentRect;
500     innerContentRect.Deflate(wm, aReflowInput.ComputedLogicalPadding());
501     // If the inner content rect is larger than the legend, we can align the
502     // legend.
503     if (innerContentRect.ISize(wm) > mLegendRect.ISize(wm)) {
504       // NOTE legend @align values are: left/right/center/top/bottom.
505       // GetLogicalAlign converts left/right to start/end for the given WM.
506       // @see HTMLLegendElement::ParseAttribute, nsLegendFrame::GetLogicalAlign
507       int32_t align =
508           static_cast<nsLegendFrame*>(legend->GetContentInsertionFrame())
509               ->GetLogicalAlign(wm);
510       switch (align) {
511         case NS_STYLE_TEXT_ALIGN_END:
512           mLegendRect.IStart(wm) =
513               innerContentRect.IEnd(wm) - mLegendRect.ISize(wm);
514           break;
515         case NS_STYLE_TEXT_ALIGN_CENTER:
516           // Note: rounding removed; there doesn't seem to be any need
517           mLegendRect.IStart(wm) =
518               innerContentRect.IStart(wm) +
519               (innerContentRect.ISize(wm) - mLegendRect.ISize(wm)) / 2;
520           break;
521         case NS_STYLE_TEXT_ALIGN_START:
522         case NS_STYLE_VERTICAL_ALIGN_TOP:
523         case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
524           mLegendRect.IStart(wm) = innerContentRect.IStart(wm);
525           break;
526         default:
527           MOZ_ASSERT_UNREACHABLE("unexpected GetLogicalAlign value");
528       }
529     } else {
530       // otherwise just start-align it.
531       mLegendRect.IStart(wm) = innerContentRect.IStart(wm);
532     }
533 
534     // place the legend
535     LogicalRect actualLegendRect = mLegendRect;
536     actualLegendRect.Deflate(wm, legendMargin);
537     LogicalPoint actualLegendPos(actualLegendRect.Origin(wm));
538 
539     // Note that legend's writing mode may be different from the fieldset's,
540     // so we need to convert offsets before applying them to it (bug 1134534).
541     LogicalMargin offsets =
542         legendReflowInput->ComputedLogicalOffsets().ConvertTo(
543             wm, legendReflowInput->GetWritingMode());
544     ReflowInput::ApplyRelativePositioning(legend, wm, offsets, &actualLegendPos,
545                                           containerSize);
546 
547     legend->SetPosition(wm, actualLegendPos, containerSize);
548     nsContainerFrame::PositionFrameView(legend);
549     nsContainerFrame::PositionChildViews(legend);
550   }
551 
552   // Return our size and our result.
553   LogicalSize finalSize(
554       wm, contentRect.ISize(wm) + border.IStartEnd(wm),
555       mLegendSpace + border.BStartEnd(wm) + (inner ? inner->BSize(wm) : 0));
556   aDesiredSize.SetSize(wm, finalSize);
557   aDesiredSize.SetOverflowAreasToDesiredBounds();
558 
559   if (legend) {
560     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, legend);
561   }
562   if (inner) {
563     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inner);
564   }
565 
566   // Merge overflow container bounds and status.
567   aDesiredSize.mOverflowAreas.UnionWith(ocBounds);
568   aStatus.MergeCompletionStatusFrom(ocStatus);
569 
570   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
571                                  aStatus);
572 
573   InvalidateFrame();
574 
575   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
576 }
577 
578 #ifdef DEBUG
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)579 void nsFieldSetFrame::SetInitialChildList(ChildListID aListID,
580                                           nsFrameList& aChildList) {
581   nsContainerFrame::SetInitialChildList(aListID, aChildList);
582   MOZ_ASSERT(aListID != kPrincipalList || GetInner(),
583              "Setting principal child list should populate our inner frame");
584 }
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)585 void nsFieldSetFrame::AppendFrames(ChildListID aListID,
586                                    nsFrameList& aFrameList) {
587   MOZ_CRASH("nsFieldSetFrame::AppendFrames not supported");
588 }
589 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,nsFrameList & aFrameList)590 void nsFieldSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
591                                    nsFrameList& aFrameList) {
592   MOZ_CRASH("nsFieldSetFrame::InsertFrames not supported");
593 }
594 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)595 void nsFieldSetFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
596   MOZ_CRASH("nsFieldSetFrame::RemoveFrame not supported");
597 }
598 #endif
599 
600 #ifdef ACCESSIBILITY
AccessibleType()601 a11y::AccType nsFieldSetFrame::AccessibleType() {
602   return a11y::eHTMLGroupboxType;
603 }
604 #endif
605 
GetLogicalBaseline(WritingMode aWM) const606 nscoord nsFieldSetFrame::GetLogicalBaseline(WritingMode aWM) const {
607   switch (StyleDisplay()->mDisplay) {
608     case mozilla::StyleDisplay::Grid:
609     case mozilla::StyleDisplay::InlineGrid:
610     case mozilla::StyleDisplay::Flex:
611     case mozilla::StyleDisplay::InlineFlex:
612       return BaselineBOffset(aWM, BaselineSharingGroup::eFirst,
613                              AlignmentContext::eInline);
614     default:
615       return BSize(aWM) - BaselineBOffset(aWM, BaselineSharingGroup::eLast,
616                                           AlignmentContext::eInline);
617   }
618 }
619 
GetVerticalAlignBaseline(WritingMode aWM,nscoord * aBaseline) const620 bool nsFieldSetFrame::GetVerticalAlignBaseline(WritingMode aWM,
621                                                nscoord* aBaseline) const {
622   nsIFrame* inner = GetInner();
623   MOZ_ASSERT(!inner->GetWritingMode().IsOrthogonalTo(aWM));
624   if (!inner->GetVerticalAlignBaseline(aWM, aBaseline)) {
625     return false;
626   }
627   nscoord innerBStart = inner->BStart(aWM, GetSize());
628   *aBaseline += innerBStart;
629   return true;
630 }
631 
GetNaturalBaselineBOffset(WritingMode aWM,BaselineSharingGroup aBaselineGroup,nscoord * aBaseline) const632 bool nsFieldSetFrame::GetNaturalBaselineBOffset(
633     WritingMode aWM, BaselineSharingGroup aBaselineGroup,
634     nscoord* aBaseline) const {
635   nsIFrame* inner = GetInner();
636   MOZ_ASSERT(!inner->GetWritingMode().IsOrthogonalTo(aWM));
637   if (!inner->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aBaseline)) {
638     return false;
639   }
640   nscoord innerBStart = inner->BStart(aWM, GetSize());
641   if (aBaselineGroup == BaselineSharingGroup::eFirst) {
642     *aBaseline += innerBStart;
643   } else {
644     *aBaseline += BSize(aWM) - (innerBStart + inner->BSize(aWM));
645   }
646   return true;
647 }
648 
AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox> & aResult)649 void nsFieldSetFrame::AppendDirectlyOwnedAnonBoxes(
650     nsTArray<OwnedAnonBox>& aResult) {
651   if (nsIFrame* kid = GetInner()) {
652     aResult.AppendElement(OwnedAnonBox(kid));
653   }
654 }
655