tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsFieldSetFrame.cpp (37689B)


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