forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			962 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			962 lines
		
	
	
	
		
			39 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsFieldSetFrame.h"
 | |
| #include "mozilla/dom/HTMLLegendElement.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include "gfxContext.h"
 | |
| #include "mozilla/gfx/2D.h"
 | |
| #include "mozilla/Likely.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/Maybe.h"
 | |
| #include "mozilla/webrender/WebRenderAPI.h"
 | |
| #include "nsBlockFrame.h"
 | |
| #include "nsCSSAnonBoxes.h"
 | |
| #include "nsCSSFrameConstructor.h"
 | |
| #include "nsCSSRendering.h"
 | |
| #include "nsDisplayList.h"
 | |
| #include "nsGkAtoms.h"
 | |
| #include "nsIFrameInlines.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsStyleConsts.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::gfx;
 | |
| using namespace mozilla::layout;
 | |
| 
 | |
| nsContainerFrame* NS_NewFieldSetFrame(PresShell* aPresShell,
 | |
|                                       ComputedStyle* aStyle) {
 | |
|   return new (aPresShell) nsFieldSetFrame(aStyle, aPresShell->GetPresContext());
 | |
| }
 | |
| 
 | |
| NS_IMPL_FRAMEARENA_HELPERS(nsFieldSetFrame)
 | |
| NS_QUERYFRAME_HEAD(nsFieldSetFrame)
 | |
|   NS_QUERYFRAME_ENTRY(nsFieldSetFrame)
 | |
| NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
 | |
| 
 | |
| nsFieldSetFrame::nsFieldSetFrame(ComputedStyle* aStyle,
 | |
|                                  nsPresContext* aPresContext)
 | |
|     : nsContainerFrame(aStyle, aPresContext, kClassID),
 | |
|       mLegendRect(GetWritingMode()) {
 | |
|   mLegendSpace = 0;
 | |
| }
 | |
| 
 | |
| nsRect nsFieldSetFrame::VisualBorderRectRelativeToSelf() const {
 | |
|   WritingMode wm = GetWritingMode();
 | |
|   LogicalRect r(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm));
 | |
|   nsSize containerSize = r.Size(wm).GetPhysicalSize(wm);
 | |
|   nsIFrame* legend = GetLegend();
 | |
|   if (legend && !GetPrevInFlow()) {
 | |
|     nscoord legendSize = legend->GetLogicalSize(wm).BSize(wm);
 | |
|     auto legendMargin = legend->GetLogicalUsedMargin(wm);
 | |
|     nscoord legendStartMargin = legendMargin.BStart(wm);
 | |
|     nscoord legendEndMargin = legendMargin.BEnd(wm);
 | |
|     nscoord border = GetUsedBorder().Side(wm.PhysicalSide(eLogicalSideBStart));
 | |
|     // Calculate the offset from the border area block-axis start edge needed to
 | |
|     // center-align our border with the legend's border-box (in the block-axis).
 | |
|     nscoord off = (legendStartMargin + legendSize / 2) - border / 2;
 | |
|     // We don't want to display our border above our border area.
 | |
|     if (off > nscoord(0)) {
 | |
|       nscoord marginBoxSize = legendStartMargin + legendSize + legendEndMargin;
 | |
|       if (marginBoxSize > border) {
 | |
|         // We don't want to display our border below the legend's margin-box,
 | |
|         // so we align it to the block-axis end if that happens.
 | |
|         nscoord overflow = off + border - marginBoxSize;
 | |
|         if (overflow > nscoord(0)) {
 | |
|           off -= overflow;
 | |
|         }
 | |
|         r.BStart(wm) += off;
 | |
|         r.BSize(wm) -= off;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return r.GetPhysicalRect(wm, containerSize);
 | |
| }
 | |
| 
 | |
| nsContainerFrame* nsFieldSetFrame::GetInner() const {
 | |
|   for (nsIFrame* child : mFrames) {
 | |
|     if (child->Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) {
 | |
|       return static_cast<nsContainerFrame*>(child);
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| nsIFrame* nsFieldSetFrame::GetLegend() const {
 | |
|   for (nsIFrame* child : mFrames) {
 | |
|     if (child->Style()->GetPseudoType() != PseudoStyleType::fieldsetContent) {
 | |
|       return child;
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| class nsDisplayFieldSetBorder final : public nsPaintedDisplayItem {
 | |
|  public:
 | |
|   nsDisplayFieldSetBorder(nsDisplayListBuilder* aBuilder,
 | |
|                           nsFieldSetFrame* aFrame)
 | |
|       : nsPaintedDisplayItem(aBuilder, aFrame) {
 | |
|     MOZ_COUNT_CTOR(nsDisplayFieldSetBorder);
 | |
|   }
 | |
|   MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayFieldSetBorder)
 | |
| 
 | |
|   virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
 | |
|   virtual nsDisplayItemGeometry* AllocateGeometry(
 | |
|       nsDisplayListBuilder* aBuilder) override;
 | |
|   virtual void ComputeInvalidationRegion(
 | |
|       nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
 | |
|       nsRegion* aInvalidRegion) const override;
 | |
|   bool CreateWebRenderCommands(
 | |
|       mozilla::wr::DisplayListBuilder& aBuilder,
 | |
|       mozilla::wr::IpcResourceUpdateQueue& aResources,
 | |
|       const StackingContextHelper& aSc,
 | |
|       mozilla::layers::RenderRootStateManager* aManager,
 | |
|       nsDisplayListBuilder* aDisplayListBuilder) override;
 | |
|   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
 | |
|                            bool* aSnap) const override;
 | |
|   NS_DISPLAY_DECL_NAME("FieldSetBorder", TYPE_FIELDSET_BORDER_BACKGROUND)
 | |
| };
 | |
| 
 | |
| void nsDisplayFieldSetBorder::Paint(nsDisplayListBuilder* aBuilder,
 | |
|                                     gfxContext* aCtx) {
 | |
|   image::ImgDrawResult result =
 | |
|       static_cast<nsFieldSetFrame*>(mFrame)->PaintBorder(
 | |
|           aBuilder, *aCtx, ToReferenceFrame(), GetPaintRect());
 | |
| 
 | |
|   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
 | |
| }
 | |
| 
 | |
| nsDisplayItemGeometry* nsDisplayFieldSetBorder::AllocateGeometry(
 | |
|     nsDisplayListBuilder* aBuilder) {
 | |
|   return new nsDisplayItemGenericImageGeometry(this, aBuilder);
 | |
| }
 | |
| 
 | |
| void nsDisplayFieldSetBorder::ComputeInvalidationRegion(
 | |
|     nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
 | |
|     nsRegion* aInvalidRegion) const {
 | |
|   auto geometry =
 | |
|       static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
 | |
| 
 | |
|   if (aBuilder->ShouldSyncDecodeImages() &&
 | |
|       geometry->ShouldInvalidateToSyncDecodeImages()) {
 | |
|     bool snap;
 | |
|     aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
 | |
|   }
 | |
| 
 | |
|   nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
 | |
| }
 | |
| 
 | |
| nsRect nsDisplayFieldSetBorder::GetBounds(nsDisplayListBuilder* aBuilder,
 | |
|                                           bool* aSnap) const {
 | |
|   // Just go ahead and claim our frame's overflow rect as the bounds, because we
 | |
|   // may have border-image-outset or other features that cause borders to extend
 | |
|   // outside the border rect.  We could try to duplicate all the complexity
 | |
|   // nsDisplayBorder has here, but keeping things in sync would be a pain, and
 | |
|   // this code is not typically performance-sensitive.
 | |
|   *aSnap = false;
 | |
|   return Frame()->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
 | |
| }
 | |
| 
 | |
| bool nsDisplayFieldSetBorder::CreateWebRenderCommands(
 | |
|     mozilla::wr::DisplayListBuilder& aBuilder,
 | |
|     mozilla::wr::IpcResourceUpdateQueue& aResources,
 | |
|     const StackingContextHelper& aSc,
 | |
|     mozilla::layers::RenderRootStateManager* aManager,
 | |
|     nsDisplayListBuilder* aDisplayListBuilder) {
 | |
|   auto frame = static_cast<nsFieldSetFrame*>(mFrame);
 | |
|   auto offset = ToReferenceFrame();
 | |
|   nsRect rect;
 | |
|   Maybe<wr::SpaceAndClipChainHelper> clipOut;
 | |
| 
 | |
|   if (nsIFrame* legend = frame->GetLegend()) {
 | |
|     rect = frame->VisualBorderRectRelativeToSelf() + offset;
 | |
| 
 | |
|     nsRect legendRect = legend->GetNormalRect() + offset;
 | |
| 
 | |
|     // Make sure we clip all of the border in case the legend is smaller.
 | |
|     nscoord borderTopWidth = frame->GetUsedBorder().top;
 | |
|     if (legendRect.height < borderTopWidth) {
 | |
|       legendRect.height = borderTopWidth;
 | |
|       legendRect.y = offset.y;
 | |
|     }
 | |
| 
 | |
|     if (!legendRect.IsEmpty()) {
 | |
|       // We need to clip out the part of the border where the legend would go
 | |
|       auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
 | |
|       auto layoutRect = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
 | |
|           frame->InkOverflowRectRelativeToSelf() + offset,
 | |
|           appUnitsPerDevPixel));
 | |
| 
 | |
|       wr::ComplexClipRegion region;
 | |
|       region.rect = wr::ToLayoutRect(
 | |
|           LayoutDeviceRect::FromAppUnits(legendRect, appUnitsPerDevPixel));
 | |
|       region.mode = wr::ClipMode::ClipOut;
 | |
|       region.radii = wr::EmptyBorderRadius();
 | |
|       nsTArray<mozilla::wr::ComplexClipRegion> array{region};
 | |
| 
 | |
|       auto clip = aBuilder.DefineClip(Nothing(), layoutRect, &array);
 | |
|       auto clipChain = aBuilder.DefineClipChain({clip}, true);
 | |
|       clipOut.emplace(aBuilder, clipChain);
 | |
|     }
 | |
|   } else {
 | |
|     rect = nsRect(offset, frame->GetRect().Size());
 | |
|   }
 | |
| 
 | |
|   ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder(
 | |
|       this, mFrame, rect, aBuilder, aResources, aSc, aManager,
 | |
|       aDisplayListBuilder);
 | |
|   if (drawResult == ImgDrawResult::NOT_SUPPORTED) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, drawResult);
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| void nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
 | |
|                                        const nsDisplayListSet& aLists) {
 | |
|   // Paint our background and border in a special way.
 | |
|   // REVIEW: We don't really need to check frame emptiness here; if it's empty,
 | |
|   // the background/border display item won't do anything, and if it isn't
 | |
|   // empty, we need to paint the outline
 | |
|   if (!HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
 | |
|       IsVisibleForPainting()) {
 | |
|     DisplayOutsetBoxShadowUnconditional(aBuilder, aLists.BorderBackground());
 | |
| 
 | |
|     const nsRect rect =
 | |
|         VisualBorderRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
 | |
| 
 | |
|     nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
 | |
|         aBuilder, this, rect, aLists.BorderBackground(),
 | |
|         /* aAllowWillPaintBorderOptimization = */ false);
 | |
| 
 | |
|     aLists.BorderBackground()->AppendNewToTop<nsDisplayFieldSetBorder>(aBuilder,
 | |
|                                                                        this);
 | |
| 
 | |
|     DisplayOutlineUnconditional(aBuilder, aLists);
 | |
| 
 | |
|     DO_GLOBAL_REFLOW_COUNT_DSP("nsFieldSetFrame");
 | |
|   }
 | |
| 
 | |
|   if (GetPrevInFlow()) {
 | |
|     DisplayOverflowContainers(aBuilder, aLists);
 | |
|   }
 | |
| 
 | |
|   nsDisplayListCollection contentDisplayItems(aBuilder);
 | |
|   if (nsIFrame* inner = GetInner()) {
 | |
|     // Collect the inner frame's display items into their own collection.
 | |
|     // We need to be calling BuildDisplayList on it before the legend in
 | |
|     // case it contains out-of-flow frames whose placeholders are in the
 | |
|     // legend. However, we want the inner frame's display items to be
 | |
|     // after the legend's display items in z-order, so we need to save them
 | |
|     // and append them later.
 | |
|     BuildDisplayListForChild(aBuilder, inner, contentDisplayItems);
 | |
|   }
 | |
|   if (nsIFrame* legend = GetLegend()) {
 | |
|     // The legend's background goes on our BlockBorderBackgrounds list because
 | |
|     // it's a block child.
 | |
|     nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds());
 | |
|     BuildDisplayListForChild(aBuilder, legend, set);
 | |
|   }
 | |
|   // Put the inner frame's display items on the master list. Note that this
 | |
|   // moves its border/background display items to our BorderBackground() list,
 | |
|   // which isn't really correct, but it's OK because the inner frame is
 | |
|   // anonymous and can't have its own border and background.
 | |
|   contentDisplayItems.MoveTo(aLists);
 | |
| }
 | |
| 
 | |
| image::ImgDrawResult nsFieldSetFrame::PaintBorder(
 | |
|     nsDisplayListBuilder* aBuilder, gfxContext& aRenderingContext, nsPoint aPt,
 | |
|     const nsRect& aDirtyRect) {
 | |
|   // If the border is smaller than the legend, move the border down
 | |
|   // to be centered on the legend.  We call VisualBorderRectRelativeToSelf() to
 | |
|   // compute the border positioning.
 | |
|   // FIXME: This means border-radius clamping is incorrect; we should
 | |
|   // override nsIFrame::GetBorderRadii.
 | |
|   nsRect rect = VisualBorderRectRelativeToSelf() + aPt;
 | |
|   nsPresContext* presContext = PresContext();
 | |
| 
 | |
|   const auto skipSides = GetSkipSides();
 | |
|   PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
 | |
|                                      ? PaintBorderFlags::SyncDecodeImages
 | |
|                                      : PaintBorderFlags();
 | |
| 
 | |
|   ImgDrawResult result = ImgDrawResult::SUCCESS;
 | |
| 
 | |
|   nsCSSRendering::PaintBoxShadowInner(presContext, aRenderingContext, this,
 | |
|                                       rect);
 | |
| 
 | |
|   if (nsIFrame* legend = GetLegend()) {
 | |
|     // We want to avoid drawing our border under the legend, so clip out the
 | |
|     // legend while drawing our border.  We don't want to use mLegendRect here,
 | |
|     // because we do want to draw our border under the legend's inline-start and
 | |
|     // -end margins.  And we use GetNormalRect(), not GetRect(), because we do
 | |
|     // not want relative positioning applied to the legend to change how our
 | |
|     // border looks.
 | |
|     nsRect legendRect = legend->GetNormalRect() + aPt;
 | |
| 
 | |
|     // Make sure we clip all of the border in case the legend is smaller.
 | |
|     nscoord borderTopWidth = GetUsedBorder().top;
 | |
|     if (legendRect.height < borderTopWidth) {
 | |
|       legendRect.height = borderTopWidth;
 | |
|       legendRect.y = aPt.y;
 | |
|     }
 | |
| 
 | |
|     DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
 | |
|     // We set up a clip path which has our rect clockwise and the legend rect
 | |
|     // counterclockwise, with FILL_WINDING as the fill rule.  That will allow us
 | |
|     // to paint within our rect but outside the legend rect.  For "our rect" we
 | |
|     // use our ink overflow rect (relative to ourselves, so it's not affected
 | |
|     // by transforms), because we can have borders sticking outside our border
 | |
|     // box (e.g. due to border-image-outset).
 | |
|     RefPtr<PathBuilder> pathBuilder =
 | |
|         drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
 | |
|     int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
 | |
|     AppendRectToPath(pathBuilder,
 | |
|                      NSRectToSnappedRect(InkOverflowRectRelativeToSelf() + aPt,
 | |
|                                          appUnitsPerDevPixel, *drawTarget),
 | |
|                      true);
 | |
|     AppendRectToPath(
 | |
|         pathBuilder,
 | |
|         NSRectToSnappedRect(legendRect, appUnitsPerDevPixel, *drawTarget),
 | |
|         false);
 | |
|     RefPtr<Path> clipPath = pathBuilder->Finish();
 | |
| 
 | |
|     aRenderingContext.Save();
 | |
|     aRenderingContext.Clip(clipPath);
 | |
|     result &= nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
 | |
|                                           aDirtyRect, rect, mComputedStyle,
 | |
|                                           borderFlags, skipSides);
 | |
|     aRenderingContext.Restore();
 | |
|   } else {
 | |
|     result &= nsCSSRendering::PaintBorder(
 | |
|         presContext, aRenderingContext, this, aDirtyRect,
 | |
|         nsRect(aPt, mRect.Size()), mComputedStyle, borderFlags, skipSides);
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| nscoord nsFieldSetFrame::GetIntrinsicISize(gfxContext* aRenderingContext,
 | |
|                                            IntrinsicISizeType aType) {
 | |
|   nscoord legendWidth = 0;
 | |
|   nscoord contentWidth = 0;
 | |
|   if (!StyleDisplay()->IsContainSize()) {
 | |
|     // Both inner and legend are children, and if the fieldset is
 | |
|     // size-contained they should not contribute to the intrinsic size.
 | |
|     if (nsIFrame* legend = GetLegend()) {
 | |
|       legendWidth = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
 | |
|                                                          legend, aType);
 | |
|     }
 | |
| 
 | |
|     if (nsIFrame* inner = GetInner()) {
 | |
|       // Ignore padding on the inner, since the padding will be applied to the
 | |
|       // outer instead, and the padding computed for the inner is wrong
 | |
|       // for percentage padding.
 | |
|       contentWidth = nsLayoutUtils::IntrinsicForContainer(
 | |
|           aRenderingContext, inner, aType, nsLayoutUtils::IGNORE_PADDING);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return std::max(legendWidth, contentWidth);
 | |
| }
 | |
| 
 | |
| nscoord nsFieldSetFrame::GetMinISize(gfxContext* aRenderingContext) {
 | |
|   nscoord result = 0;
 | |
|   DISPLAY_MIN_INLINE_SIZE(this, result);
 | |
| 
 | |
|   result = GetIntrinsicISize(aRenderingContext, IntrinsicISizeType::MinISize);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| nscoord nsFieldSetFrame::GetPrefISize(gfxContext* aRenderingContext) {
 | |
|   nscoord result = 0;
 | |
|   DISPLAY_PREF_INLINE_SIZE(this, result);
 | |
| 
 | |
|   result = GetIntrinsicISize(aRenderingContext, IntrinsicISizeType::PrefISize);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* virtual */
 | |
| void nsFieldSetFrame::Reflow(nsPresContext* aPresContext,
 | |
|                              ReflowOutput& aDesiredSize,
 | |
|                              const ReflowInput& aReflowInput,
 | |
|                              nsReflowStatus& aStatus) {
 | |
|   using LegendAlignValue = mozilla::dom::HTMLLegendElement::LegendAlignValue;
 | |
| 
 | |
|   MarkInReflow();
 | |
|   DO_GLOBAL_REFLOW_COUNT("nsFieldSetFrame");
 | |
|   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
 | |
|   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 | |
|   NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE,
 | |
|                        "Should have a precomputed inline-size!");
 | |
| 
 | |
|   OverflowAreas ocBounds;
 | |
|   nsReflowStatus ocStatus;
 | |
|   auto* prevInFlow = static_cast<nsFieldSetFrame*>(GetPrevInFlow());
 | |
|   if (prevInFlow) {
 | |
|     ReflowOverflowContainerChildren(aPresContext, aReflowInput, ocBounds,
 | |
|                                     ReflowChildFlags::Default, ocStatus);
 | |
| 
 | |
|     AutoFrameListPtr prevOverflowFrames(PresContext(),
 | |
|                                         prevInFlow->StealOverflowFrames());
 | |
|     if (prevOverflowFrames) {
 | |
|       nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow,
 | |
|                                               this);
 | |
|       mFrames.InsertFrames(this, nullptr, *prevOverflowFrames);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool reflowInner;
 | |
|   bool reflowLegend;
 | |
|   nsIFrame* legend = GetLegend();
 | |
|   nsContainerFrame* inner = GetInner();
 | |
|   if (!legend || !inner) {
 | |
|     if (DrainSelfOverflowList()) {
 | |
|       legend = GetLegend();
 | |
|       inner = GetInner();
 | |
|     }
 | |
|   }
 | |
|   if (aReflowInput.ShouldReflowAllKids() || GetNextInFlow()) {
 | |
|     reflowInner = inner != nullptr;
 | |
|     reflowLegend = legend != nullptr;
 | |
|   } else {
 | |
|     reflowInner = inner && inner->IsSubtreeDirty();
 | |
|     reflowLegend = legend && legend->IsSubtreeDirty();
 | |
|   }
 | |
| 
 | |
|   // @note |this| frame applies borders but not any padding.  Our anonymous
 | |
|   // inner frame applies the padding (but not borders).
 | |
|   const auto wm = GetWritingMode();
 | |
|   LogicalMargin border = aReflowInput.ComputedLogicalBorderPadding(wm) -
 | |
|                          aReflowInput.ComputedLogicalPadding(wm);
 | |
|   auto skipSides = PreReflowBlockLevelLogicalSkipSides();
 | |
|   border.ApplySkipSides(skipSides);
 | |
|   LogicalSize availSize(wm, aReflowInput.ComputedSize().ISize(wm),
 | |
|                         aReflowInput.AvailableBSize());
 | |
| 
 | |
|   // Figure out how big the legend is if there is one.
 | |
|   LogicalMargin legendMargin(wm);
 | |
|   Maybe<ReflowInput> legendReflowInput;
 | |
|   if (legend) {
 | |
|     const auto legendWM = legend->GetWritingMode();
 | |
|     LogicalSize legendAvailSize = availSize.ConvertTo(legendWM, wm);
 | |
|     ComputeSizeFlags sizeFlags;
 | |
|     if (legend->StylePosition()->ISize(wm).IsAuto()) {
 | |
|       sizeFlags = ComputeSizeFlag::ShrinkWrap;
 | |
|     }
 | |
|     ReflowInput::InitFlags initFlags;  // intentionally empty
 | |
|     StyleSizeOverrides sizeOverrides;  // intentionally empty
 | |
|     legendReflowInput.emplace(aPresContext, aReflowInput, legend,
 | |
|                               legendAvailSize, Nothing(), initFlags,
 | |
|                               sizeOverrides, sizeFlags);
 | |
|   }
 | |
|   const bool avoidBreakInside = ShouldAvoidBreakInside(aReflowInput);
 | |
|   if (reflowLegend) {
 | |
|     ReflowOutput legendDesiredSize(aReflowInput);
 | |
| 
 | |
|     // We'll move the legend to its proper place later, so the position
 | |
|     // and containerSize passed here are unimportant.
 | |
|     const nsSize dummyContainerSize;
 | |
|     ReflowChild(legend, aPresContext, legendDesiredSize, *legendReflowInput, wm,
 | |
|                 LogicalPoint(wm), dummyContainerSize,
 | |
|                 ReflowChildFlags::NoMoveFrame, aStatus);
 | |
| 
 | |
|     if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
 | |
|         !(HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
 | |
|           aReflowInput.mStyleDisplay->IsAbsolutelyPositionedStyle()) &&
 | |
|         !prevInFlow && !aReflowInput.mFlags.mIsTopOfPage) {
 | |
|       // Propagate break-before from the legend to the fieldset.
 | |
|       if (legend->StyleDisplay()->BreakBefore() ||
 | |
|           aStatus.IsInlineBreakBefore()) {
 | |
|         // XXX(mats) setting a desired size shouldn't be necessary: bug 1599159.
 | |
|         aDesiredSize.SetSize(wm, LogicalSize(wm));
 | |
|         aStatus.SetInlineLineBreakBeforeAndReset();
 | |
|         return;
 | |
|       }
 | |
|       // Honor break-inside:avoid by breaking before instead.
 | |
|       if (MOZ_UNLIKELY(avoidBreakInside) && !aStatus.IsFullyComplete()) {
 | |
|         aDesiredSize.SetSize(wm, LogicalSize(wm));
 | |
|         aStatus.SetInlineLineBreakBeforeAndReset();
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Calculate the legend's margin-box rectangle.
 | |
|     legendMargin = legend->GetLogicalUsedMargin(wm);
 | |
|     mLegendRect = LogicalRect(
 | |
|         wm, 0, 0, legendDesiredSize.ISize(wm) + legendMargin.IStartEnd(wm),
 | |
|         legendDesiredSize.BSize(wm) + legendMargin.BStartEnd(wm));
 | |
|     // We subtract mLegendSpace from inner's content-box block-size below.
 | |
|     nscoord oldSpace = mLegendSpace;
 | |
|     mLegendSpace = 0;
 | |
|     nscoord borderBStart = border.BStart(wm);
 | |
|     if (!prevInFlow) {
 | |
|       if (mLegendRect.BSize(wm) > borderBStart) {
 | |
|         mLegendSpace = mLegendRect.BSize(wm) - borderBStart;
 | |
|       } else {
 | |
|         // Calculate the border-box position that would center the legend's
 | |
|         // border-box within the fieldset border:
 | |
|         nscoord off = (borderBStart - legendDesiredSize.BSize(wm)) / 2;
 | |
|         off -= legendMargin.BStart(wm);  // convert to a margin-box position
 | |
|         if (off > nscoord(0)) {
 | |
|           // Align the legend to the end if center-aligning it would overflow.
 | |
|           nscoord overflow = off + mLegendRect.BSize(wm) - borderBStart;
 | |
|           if (overflow > nscoord(0)) {
 | |
|             off -= overflow;
 | |
|           }
 | |
|           mLegendRect.BStart(wm) += off;
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       mLegendSpace = mLegendRect.BSize(wm);
 | |
|     }
 | |
| 
 | |
|     // If mLegendSpace changes then we need to reflow |inner| as well.
 | |
|     if (mLegendSpace != oldSpace && inner) {
 | |
|       reflowInner = true;
 | |
|     }
 | |
| 
 | |
|     FinishReflowChild(legend, aPresContext, legendDesiredSize,
 | |
|                       legendReflowInput.ptr(), wm, LogicalPoint(wm),
 | |
|                       dummyContainerSize, ReflowChildFlags::NoMoveFrame);
 | |
|     EnsureChildContinuation(legend, aStatus);
 | |
|     if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
 | |
|         !legend->GetWritingMode().IsOrthogonalTo(wm) &&
 | |
|         legend->StyleDisplay()->BreakAfter() &&
 | |
|         (!legendReflowInput->mFlags.mIsTopOfPage ||
 | |
|          mLegendRect.BSize(wm) > 0) &&
 | |
|         aStatus.IsComplete()) {
 | |
|       // Pretend that we ran out of space to push children of |inner|.
 | |
|       // XXX(mats) perhaps pushing the inner frame would be more correct,
 | |
|       // but we don't support that yet.
 | |
|       availSize.BSize(wm) = nscoord(0);
 | |
|       aStatus.Reset();
 | |
|       aStatus.SetIncomplete();
 | |
|     }
 | |
|   } else if (!legend) {
 | |
|     mLegendRect.SetEmpty();
 | |
|     mLegendSpace = 0;
 | |
|   } else {
 | |
|     // mLegendSpace and mLegendRect haven't changed, but we need
 | |
|     // the used margin when placing the legend.
 | |
|     legendMargin = legend->GetLogicalUsedMargin(wm);
 | |
|   }
 | |
| 
 | |
|   // This containerSize is incomplete as yet: it does not include the size
 | |
|   // of the |inner| frame itself.
 | |
|   nsSize containerSize =
 | |
|       (LogicalSize(wm, 0, mLegendSpace) + border.Size(wm)).GetPhysicalSize(wm);
 | |
|   if (reflowInner) {
 | |
|     LogicalSize innerAvailSize = availSize;
 | |
|     innerAvailSize.ISize(wm) =
 | |
|         aReflowInput.ComputedSizeWithPadding(wm).ISize(wm);
 | |
|     nscoord remainingComputedBSize = aReflowInput.ComputedBSize();
 | |
|     if (prevInFlow && remainingComputedBSize != NS_UNCONSTRAINEDSIZE) {
 | |
|       // Subtract the consumed BSize associated with the legend.
 | |
|       for (nsIFrame* prev = prevInFlow; prev; prev = prev->GetPrevInFlow()) {
 | |
|         auto* prevFieldSet = static_cast<nsFieldSetFrame*>(prev);
 | |
|         remainingComputedBSize -= prevFieldSet->mLegendSpace;
 | |
|       }
 | |
|       remainingComputedBSize = std::max(0, remainingComputedBSize);
 | |
|     }
 | |
|     if (innerAvailSize.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
 | |
|       innerAvailSize.BSize(wm) -=
 | |
|           std::max(mLegendRect.BSize(wm), border.BStart(wm));
 | |
|       if (StyleBorder()->mBoxDecorationBreak ==
 | |
|               StyleBoxDecorationBreak::Clone &&
 | |
|           (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE ||
 | |
|            remainingComputedBSize +
 | |
|                    aReflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(
 | |
|                        wm) >=
 | |
|                availSize.BSize(wm))) {
 | |
|         innerAvailSize.BSize(wm) -= border.BEnd(wm);
 | |
|       }
 | |
|       innerAvailSize.BSize(wm) = std::max(0, innerAvailSize.BSize(wm));
 | |
|     }
 | |
|     ReflowInput kidReflowInput(aPresContext, aReflowInput, inner,
 | |
|                                innerAvailSize, Nothing(),
 | |
|                                ReflowInput::InitFlag::CallerWillInit);
 | |
|     // Override computed padding, in case it's percentage padding
 | |
|     kidReflowInput.Init(
 | |
|         aPresContext, Nothing(), Nothing(),
 | |
|         Some(aReflowInput.ComputedLogicalPadding(inner->GetWritingMode())));
 | |
|     if (kidReflowInput.mFlags.mIsTopOfPage) {
 | |
|       // Prevent break-before from |inner| if we have a legend.
 | |
|       kidReflowInput.mFlags.mIsTopOfPage = !legend;
 | |
|     }
 | |
|     // Our child is "height:100%" but we actually want its height to be reduced
 | |
|     // by the amount of content-height the legend is eating up, unless our
 | |
|     // height is unconstrained (in which case the child's will be too).
 | |
|     if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
 | |
|       kidReflowInput.SetComputedBSize(
 | |
|           std::max(0, remainingComputedBSize - mLegendSpace));
 | |
|     }
 | |
| 
 | |
|     if (aReflowInput.ComputedMinBSize() > 0) {
 | |
|       kidReflowInput.ComputedMinBSize() =
 | |
|           std::max(0, aReflowInput.ComputedMinBSize() - mLegendSpace);
 | |
|     }
 | |
| 
 | |
|     if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
 | |
|       kidReflowInput.ComputedMaxBSize() =
 | |
|           std::max(0, aReflowInput.ComputedMaxBSize() - mLegendSpace);
 | |
|     }
 | |
| 
 | |
|     ReflowOutput kidDesiredSize(kidReflowInput);
 | |
|     NS_ASSERTION(
 | |
|         kidReflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
 | |
|         "Margins on anonymous fieldset child not supported!");
 | |
|     LogicalPoint pt(wm, border.IStart(wm), border.BStart(wm) + mLegendSpace);
 | |
| 
 | |
|     // We don't know the correct containerSize until we have reflowed |inner|,
 | |
|     // so we use a dummy value for now; FinishReflowChild will fix the position
 | |
|     // if necessary.
 | |
|     const nsSize dummyContainerSize;
 | |
|     nsReflowStatus status;
 | |
|     // If our legend needs a continuation then *this* frame will have
 | |
|     // a continuation as well so we should keep our inner frame continuations
 | |
|     // too (even if 'inner' ends up being COMPLETE here).  This ensures that
 | |
|     // our continuation will have a reasonable inline-size.
 | |
|     ReflowChildFlags flags = aStatus.IsFullyComplete()
 | |
|                                  ? ReflowChildFlags::Default
 | |
|                                  : ReflowChildFlags::NoDeleteNextInFlowChild;
 | |
|     ReflowChild(inner, aPresContext, kidDesiredSize, kidReflowInput, wm, pt,
 | |
|                 dummyContainerSize, flags, status);
 | |
| 
 | |
|     // Honor break-inside:avoid when possible by returning a BreakBefore status.
 | |
|     if (MOZ_UNLIKELY(avoidBreakInside) && !prevInFlow &&
 | |
|         !aReflowInput.mFlags.mIsTopOfPage &&
 | |
|         availSize.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
 | |
|       if (status.IsInlineBreakBefore() || !status.IsFullyComplete()) {
 | |
|         aDesiredSize.SetSize(wm, LogicalSize(wm));
 | |
|         aStatus.SetInlineLineBreakBeforeAndReset();
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Update containerSize to account for size of the inner frame, so that
 | |
|     // FinishReflowChild can position it correctly.
 | |
|     containerSize += kidDesiredSize.PhysicalSize();
 | |
|     FinishReflowChild(inner, aPresContext, kidDesiredSize, &kidReflowInput, wm,
 | |
|                       pt, containerSize, ReflowChildFlags::Default);
 | |
|     EnsureChildContinuation(inner, status);
 | |
|     aStatus.MergeCompletionStatusFrom(status);
 | |
|     NS_FRAME_TRACE_REFLOW_OUT("FieldSet::Reflow", aStatus);
 | |
|   } else if (inner) {
 | |
|     // |inner| didn't need to be reflowed but we do need to include its size
 | |
|     // in containerSize.
 | |
|     containerSize += inner->GetSize();
 | |
|   } else {
 | |
|     // No |inner| means it was already complete in an earlier continuation.
 | |
|     MOZ_ASSERT(prevInFlow, "first-in-flow should always have an inner frame");
 | |
|     for (nsIFrame* prev = prevInFlow; prev; prev = prev->GetPrevInFlow()) {
 | |
|       auto* prevFieldSet = static_cast<nsFieldSetFrame*>(prev);
 | |
|       if (auto* prevInner = prevFieldSet->GetInner()) {
 | |
|         containerSize += prevInner->GetSize();
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LogicalRect contentRect(wm);
 | |
|   if (inner) {
 | |
|     // We don't support margins on inner, so our content rect is just the
 | |
|     // inner's border-box. (We don't really care about container size at this
 | |
|     // point, as we'll figure out the actual positioning later.)
 | |
|     contentRect = inner->GetLogicalRect(wm, containerSize);
 | |
|   } else if (prevInFlow) {
 | |
|     auto size = prevInFlow->GetPaddingRectRelativeToSelf().Size();
 | |
|     contentRect.ISize(wm) = wm.IsVertical() ? size.height : size.width;
 | |
|   }
 | |
| 
 | |
|   if (legend) {
 | |
|     // The legend is positioned inline-wards within the inner's content rect
 | |
|     // (so that padding on the fieldset affects the legend position).
 | |
|     LogicalRect innerContentRect = contentRect;
 | |
|     innerContentRect.Deflate(wm, aReflowInput.ComputedLogicalPadding(wm));
 | |
|     // If the inner content rect is larger than the legend, we can align the
 | |
|     // legend.
 | |
|     if (innerContentRect.ISize(wm) > mLegendRect.ISize(wm)) {
 | |
|       // NOTE legend @align values are: left/right/center
 | |
|       // GetLogicalAlign converts left/right to start/end for the given WM.
 | |
|       // @see HTMLLegendElement::ParseAttribute/LogicalAlign
 | |
|       auto* legendElement =
 | |
|           dom::HTMLLegendElement::FromNode(legend->GetContent());
 | |
|       switch (legendElement->LogicalAlign(wm)) {
 | |
|         case LegendAlignValue::InlineEnd:
 | |
|           mLegendRect.IStart(wm) =
 | |
|               innerContentRect.IEnd(wm) - mLegendRect.ISize(wm);
 | |
|           break;
 | |
|         case LegendAlignValue::Center:
 | |
|           // Note: rounding removed; there doesn't seem to be any need
 | |
|           mLegendRect.IStart(wm) =
 | |
|               innerContentRect.IStart(wm) +
 | |
|               (innerContentRect.ISize(wm) - mLegendRect.ISize(wm)) / 2;
 | |
|           break;
 | |
|         case LegendAlignValue::InlineStart:
 | |
|           mLegendRect.IStart(wm) = innerContentRect.IStart(wm);
 | |
|           break;
 | |
|         default:
 | |
|           MOZ_ASSERT_UNREACHABLE("unexpected GetLogicalAlign value");
 | |
|       }
 | |
|     } else {
 | |
|       // otherwise just start-align it.
 | |
|       mLegendRect.IStart(wm) = innerContentRect.IStart(wm);
 | |
|     }
 | |
| 
 | |
|     // place the legend
 | |
|     LogicalRect actualLegendRect = mLegendRect;
 | |
|     actualLegendRect.Deflate(wm, legendMargin);
 | |
|     LogicalPoint actualLegendPos(actualLegendRect.Origin(wm));
 | |
| 
 | |
|     // Note that legend's writing mode may be different from the fieldset's,
 | |
|     // so we need to convert offsets before applying them to it (bug 1134534).
 | |
|     LogicalMargin offsets = legendReflowInput->ComputedLogicalOffsets(wm);
 | |
|     ReflowInput::ApplyRelativePositioning(legend, wm, offsets, &actualLegendPos,
 | |
|                                           containerSize);
 | |
| 
 | |
|     legend->SetPosition(wm, actualLegendPos, containerSize);
 | |
|     nsContainerFrame::PositionFrameView(legend);
 | |
|     nsContainerFrame::PositionChildViews(legend);
 | |
|   }
 | |
| 
 | |
|   // Skip our block-end border if we're INCOMPLETE.
 | |
|   if (!aStatus.IsComplete() &&
 | |
|       StyleBorder()->mBoxDecorationBreak != StyleBoxDecorationBreak::Clone) {
 | |
|     border.BEnd(wm) = nscoord(0);
 | |
|   }
 | |
| 
 | |
|   // Return our size and our result.
 | |
|   LogicalSize finalSize(
 | |
|       wm, contentRect.ISize(wm) + border.IStartEnd(wm),
 | |
|       mLegendSpace + border.BStartEnd(wm) + (inner ? inner->BSize(wm) : 0));
 | |
|   if (aReflowInput.mStyleDisplay->IsContainSize()) {
 | |
|     // If we're size-contained, then we must set finalSize to be what
 | |
|     // it'd be if we had no children (i.e. if we had no legend and if
 | |
|     // 'inner' were empty).  Note: normally the fieldset's own padding
 | |
|     // (which we still must honor) would be accounted for as part of
 | |
|     // inner's size (see kidReflowInput.Init() call above).  So: since
 | |
|     // we're disregarding sizing information from 'inner', we need to
 | |
|     // account for that padding ourselves here.
 | |
|     nscoord contentBoxBSize =
 | |
|         aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE
 | |
|             ? aReflowInput.ApplyMinMaxBSize(0)
 | |
|             : aReflowInput.ComputedBSize();
 | |
|     finalSize.BSize(wm) =
 | |
|         contentBoxBSize +
 | |
|         aReflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm);
 | |
|   }
 | |
| 
 | |
|   if (aStatus.IsComplete() &&
 | |
|       aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
 | |
|       finalSize.BSize(wm) > aReflowInput.AvailableBSize() &&
 | |
|       border.BEnd(wm) > 0 && aReflowInput.AvailableBSize() > border.BEnd(wm)) {
 | |
|     // Our end border doesn't fit but it should fit in the next column/page.
 | |
|     if (MOZ_UNLIKELY(avoidBreakInside)) {
 | |
|       aDesiredSize.SetSize(wm, LogicalSize(wm));
 | |
|       aStatus.SetInlineLineBreakBeforeAndReset();
 | |
|       return;
 | |
|     } else {
 | |
|       if (StyleBorder()->mBoxDecorationBreak ==
 | |
|           StyleBoxDecorationBreak::Slice) {
 | |
|         finalSize.BSize(wm) -= border.BEnd(wm);
 | |
|       }
 | |
|       aStatus.SetIncomplete();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!aStatus.IsComplete()) {
 | |
|     MOZ_ASSERT(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
 | |
|                "must be Complete in an unconstrained available block-size");
 | |
|     // Stretch our BSize to fill the fragmentainer.
 | |
|     finalSize.BSize(wm) =
 | |
|         std::max(finalSize.BSize(wm), aReflowInput.AvailableBSize());
 | |
|   }
 | |
|   aDesiredSize.SetSize(wm, finalSize);
 | |
|   aDesiredSize.SetOverflowAreasToDesiredBounds();
 | |
| 
 | |
|   if (legend) {
 | |
|     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, legend);
 | |
|   }
 | |
|   if (inner) {
 | |
|     ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inner);
 | |
|   }
 | |
| 
 | |
|   // Merge overflow container bounds and status.
 | |
|   aDesiredSize.mOverflowAreas.UnionWith(ocBounds);
 | |
|   aStatus.MergeCompletionStatusFrom(ocStatus);
 | |
| 
 | |
|   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
 | |
|                                  aStatus);
 | |
|   InvalidateFrame();
 | |
|   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
 | |
| }
 | |
| 
 | |
| void nsFieldSetFrame::SetInitialChildList(ChildListID aListID,
 | |
|                                           nsFrameList& aChildList) {
 | |
|   nsContainerFrame::SetInitialChildList(aListID, aChildList);
 | |
|   if (nsBlockFrame* legend = do_QueryFrame(GetLegend())) {
 | |
|     // A rendered legend always establish a new formatting context.
 | |
|     // https://html.spec.whatwg.org/multipage/rendering.html#rendered-legend
 | |
|     legend->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
 | |
|   }
 | |
|   MOZ_ASSERT(aListID != kPrincipalList || GetInner() || GetLegend(),
 | |
|              "Setting principal child list should populate our inner frame "
 | |
|              "or our rendered legend");
 | |
| }
 | |
| 
 | |
| void nsFieldSetFrame::AppendFrames(ChildListID aListID,
 | |
|                                    nsFrameList& aFrameList) {
 | |
|   MOZ_ASSERT(aListID == kNoReflowPrincipalList &&
 | |
|                  HasAnyStateBits(NS_FRAME_FIRST_REFLOW),
 | |
|              "AppendFrames should only be used from "
 | |
|              "nsCSSFrameConstructor::ConstructFieldSetFrame");
 | |
|   nsContainerFrame::AppendFrames(aListID, aFrameList);
 | |
|   MOZ_ASSERT(GetInner(), "at this point we should have an inner frame");
 | |
| }
 | |
| 
 | |
| void nsFieldSetFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
 | |
|                                    const nsLineList::iterator* aPrevFrameLine,
 | |
|                                    nsFrameList& aFrameList) {
 | |
|   MOZ_ASSERT(aListID == kPrincipalList && !aPrevFrame && !GetLegend(),
 | |
|              "InsertFrames should only be used to prepend a rendered legend "
 | |
|              "from nsCSSFrameConstructor::ConstructFramesFromItemList");
 | |
|   nsContainerFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine,
 | |
|                                  aFrameList);
 | |
|   MOZ_ASSERT(GetLegend());
 | |
|   if (nsBlockFrame* legend = do_QueryFrame(GetLegend())) {
 | |
|     // A rendered legend always establish a new formatting context.
 | |
|     // https://html.spec.whatwg.org/multipage/rendering.html#rendered-legend
 | |
|     legend->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
 | |
|   }
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| void nsFieldSetFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
 | |
|   MOZ_CRASH("nsFieldSetFrame::RemoveFrame not supported");
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #ifdef ACCESSIBILITY
 | |
| a11y::AccType nsFieldSetFrame::AccessibleType() {
 | |
|   return a11y::eHTMLGroupboxType;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| nscoord nsFieldSetFrame::GetLogicalBaseline(WritingMode aWM) const {
 | |
|   switch (StyleDisplay()->DisplayInside()) {
 | |
|     case mozilla::StyleDisplayInside::Grid:
 | |
|     case mozilla::StyleDisplayInside::Flex:
 | |
|       return BaselineBOffset(aWM, BaselineSharingGroup::First,
 | |
|                              AlignmentContext::Inline);
 | |
|     default:
 | |
|       return BSize(aWM) - BaselineBOffset(aWM, BaselineSharingGroup::Last,
 | |
|                                           AlignmentContext::Inline);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsFieldSetFrame::GetVerticalAlignBaseline(WritingMode aWM,
 | |
|                                                nscoord* aBaseline) const {
 | |
|   if (StyleDisplay()->IsContainLayout()) {
 | |
|     // If we are layout-contained, our child 'inner' should not
 | |
|     // affect how we calculate our baseline.
 | |
|     return false;
 | |
|   }
 | |
|   nsIFrame* inner = GetInner();
 | |
|   if (MOZ_UNLIKELY(!inner)) {
 | |
|     return false;
 | |
|   }
 | |
|   MOZ_ASSERT(!inner->GetWritingMode().IsOrthogonalTo(aWM));
 | |
|   if (!inner->GetVerticalAlignBaseline(aWM, aBaseline)) {
 | |
|     return false;
 | |
|   }
 | |
|   nscoord innerBStart = inner->BStart(aWM, GetSize());
 | |
|   *aBaseline += innerBStart;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool nsFieldSetFrame::GetNaturalBaselineBOffset(
 | |
|     WritingMode aWM, BaselineSharingGroup aBaselineGroup,
 | |
|     nscoord* aBaseline) const {
 | |
|   if (StyleDisplay()->IsContainLayout()) {
 | |
|     // If we are layout-contained, our child 'inner' should not
 | |
|     // affect how we calculate our baseline.
 | |
|     return false;
 | |
|   }
 | |
|   nsIFrame* inner = GetInner();
 | |
|   if (MOZ_UNLIKELY(!inner)) {
 | |
|     return false;
 | |
|   }
 | |
|   MOZ_ASSERT(!inner->GetWritingMode().IsOrthogonalTo(aWM));
 | |
|   if (!inner->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aBaseline)) {
 | |
|     return false;
 | |
|   }
 | |
|   nscoord innerBStart = inner->BStart(aWM, GetSize());
 | |
|   if (aBaselineGroup == BaselineSharingGroup::First) {
 | |
|     *aBaseline += innerBStart;
 | |
|   } else {
 | |
|     *aBaseline += BSize(aWM) - (innerBStart + inner->BSize(aWM));
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void nsFieldSetFrame::AppendDirectlyOwnedAnonBoxes(
 | |
|     nsTArray<OwnedAnonBox>& aResult) {
 | |
|   if (nsIFrame* kid = GetInner()) {
 | |
|     aResult.AppendElement(OwnedAnonBox(kid));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsFieldSetFrame::EnsureChildContinuation(nsIFrame* aChild,
 | |
|                                               const nsReflowStatus& aStatus) {
 | |
|   MOZ_ASSERT(aChild == GetLegend() || aChild == GetInner(),
 | |
|              "unexpected child frame");
 | |
|   nsIFrame* nif = aChild->GetNextInFlow();
 | |
|   if (aStatus.IsFullyComplete()) {
 | |
|     if (nif) {
 | |
|       // NOTE: we want to avoid our DEBUG version of RemoveFrame above.
 | |
|       nsContainerFrame::RemoveFrame(kNoReflowPrincipalList, nif);
 | |
|       MOZ_ASSERT(!aChild->GetNextInFlow());
 | |
|     }
 | |
|   } else {
 | |
|     nsFrameList nifs;
 | |
|     if (!nif) {
 | |
|       auto* fc = PresShell()->FrameConstructor();
 | |
|       nif = fc->CreateContinuingFrame(aChild, this);
 | |
|       if (aStatus.IsOverflowIncomplete()) {
 | |
|         nif->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
 | |
|       }
 | |
|       nifs = nsFrameList(nif, nif);
 | |
|     } else {
 | |
|       // Steal all nifs and push them again in case they are currently on
 | |
|       // the wrong list.
 | |
|       for (nsIFrame* n = nif; n; n = n->GetNextInFlow()) {
 | |
|         n->GetParent()->StealFrame(n);
 | |
|         nifs.AppendFrame(this, n);
 | |
|         if (aStatus.IsOverflowIncomplete()) {
 | |
|           n->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
 | |
|         } else {
 | |
|           n->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (aStatus.IsOverflowIncomplete()) {
 | |
|       if (nsFrameList* eoc = GetExcessOverflowContainers()) {
 | |
|         eoc->AppendFrames(nullptr, nifs);
 | |
|       } else {
 | |
|         SetExcessOverflowContainers(std::move(nifs));
 | |
|       }
 | |
|     } else {
 | |
|       if (nsFrameList* oc = GetOverflowFrames()) {
 | |
|         oc->AppendFrames(nullptr, nifs);
 | |
|       } else {
 | |
|         SetOverflowFrames(std::move(nifs));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | 
