/* -*- 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/. */ /* base class #1 for rendering objects that have child lists */ #include "nsContainerFrame.h" #include "mozilla/widget/InitData.h" #include "nsContainerFrameInlines.h" #include "mozilla/ComputedStyle.h" #include "mozilla/PresShell.h" #include "mozilla/dom/HTMLSummaryElement.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Types.h" #include "nsAbsoluteContainingBlock.h" #include "nsAttrValue.h" #include "nsAttrValueInlines.h" #include "nsFlexContainerFrame.h" #include "nsFrameSelection.h" #include "mozilla/dom/Document.h" #include "nsPresContext.h" #include "nsRect.h" #include "nsPoint.h" #include "nsStyleConsts.h" #include "nsView.h" #include "nsCOMPtr.h" #include "nsGkAtoms.h" #include "nsViewManager.h" #include "nsIWidget.h" #include "nsCanvasFrame.h" #include "nsCSSRendering.h" #include "nsError.h" #include "nsDisplayList.h" #include "nsIBaseWindow.h" #include "nsCSSFrameConstructor.h" #include "nsBlockFrame.h" #include "nsPlaceholderFrame.h" #include "mozilla/AutoRestore.h" #include "nsIFrameInlines.h" #include "nsPrintfCString.h" #include "mozilla/webrender/WebRenderAPI.h" #include using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::layout; using mozilla::gfx::ColorPattern; using mozilla::gfx::DeviceColor; using mozilla::gfx::DrawTarget; using mozilla::gfx::Rect; using mozilla::gfx::sRGBColor; using mozilla::gfx::ToDeviceColor; nsContainerFrame::~nsContainerFrame() = default; NS_QUERYFRAME_HEAD(nsContainerFrame) NS_QUERYFRAME_ENTRY(nsContainerFrame) NS_QUERYFRAME_TAIL_INHERITING(nsSplittableFrame) void nsContainerFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { nsSplittableFrame::Init(aContent, aParent, aPrevInFlow); if (aPrevInFlow) { // Make sure we copy bits from our prev-in-flow that will affect // us. A continuation for a container frame needs to know if it // has a child with a view so that we'll properly reposition it. if (aPrevInFlow->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) { AddStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW); } } } void nsContainerFrame::SetInitialChildList(ChildListID aListID, nsFrameList&& aChildList) { #ifdef DEBUG nsIFrame::VerifyDirtyBitSet(aChildList); for (nsIFrame* f : aChildList) { MOZ_ASSERT(f->GetParent() == this, "Unexpected parent"); } #endif if (aListID == FrameChildListID::Principal) { MOZ_ASSERT(mFrames.IsEmpty(), "unexpected second call to SetInitialChildList"); mFrames = std::move(aChildList); } else if (aListID == FrameChildListID::Backdrop) { MOZ_ASSERT(StyleDisplay()->mTopLayer != StyleTopLayer::None, "Only top layer frames should have backdrop"); MOZ_ASSERT(HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), "Top layer frames should be out-of-flow"); MOZ_ASSERT(!GetProperty(BackdropProperty()), "We shouldn't have setup backdrop frame list before"); #ifdef DEBUG { nsIFrame* placeholder = aChildList.FirstChild(); MOZ_ASSERT(aChildList.OnlyChild(), "Should have only one backdrop"); MOZ_ASSERT(placeholder->IsPlaceholderFrame(), "The frame to be stored should be a placeholder"); MOZ_ASSERT(static_cast(placeholder) ->GetOutOfFlowFrame() ->IsBackdropFrame(), "The placeholder should points to a backdrop frame"); } #endif nsFrameList* list = new (PresShell()) nsFrameList(std::move(aChildList)); SetProperty(BackdropProperty(), list); } else { MOZ_ASSERT_UNREACHABLE("Unexpected child list"); } } void nsContainerFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) { MOZ_ASSERT(aListID == FrameChildListID::Principal || aListID == FrameChildListID::NoReflowPrincipal, "unexpected child list"); if (MOZ_UNLIKELY(aFrameList.IsEmpty())) { return; } DrainSelfOverflowList(); // ensure the last frame is in mFrames mFrames.AppendFrames(this, std::move(aFrameList)); if (aListID != FrameChildListID::NoReflowPrincipal) { PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, NS_FRAME_HAS_DIRTY_CHILDREN); } } void nsContainerFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame, const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { MOZ_ASSERT(aListID == FrameChildListID::Principal || aListID == FrameChildListID::NoReflowPrincipal, "unexpected child list"); NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, "inserting after sibling frame with different parent"); if (MOZ_UNLIKELY(aFrameList.IsEmpty())) { return; } DrainSelfOverflowList(); // ensure aPrevFrame is in mFrames mFrames.InsertFrames(this, aPrevFrame, std::move(aFrameList)); if (aListID != FrameChildListID::NoReflowPrincipal) { PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, NS_FRAME_HAS_DIRTY_CHILDREN); } } void nsContainerFrame::RemoveFrame(DestroyContext& aContext, ChildListID aListID, nsIFrame* aOldFrame) { MOZ_ASSERT(aListID == FrameChildListID::Principal || aListID == FrameChildListID::NoReflowPrincipal, "unexpected child list"); AutoTArray continuations; { nsIFrame* continuation = aOldFrame; while (continuation) { continuations.AppendElement(continuation); continuation = continuation->GetNextContinuation(); } } mozilla::PresShell* presShell = PresShell(); nsContainerFrame* lastParent = nullptr; // Loop and destroy aOldFrame and all of its continuations. // // Request a reflow on the parent frames involved unless we were explicitly // told not to (FrameChildListID::NoReflowPrincipal). const bool generateReflowCommand = aListID != FrameChildListID::NoReflowPrincipal; for (nsIFrame* continuation : Reversed(continuations)) { nsContainerFrame* parent = continuation->GetParent(); // Please note that 'parent' may not actually be where 'continuation' lives. // We really MUST use StealFrame() and nothing else here. // @see nsInlineFrame::StealFrame for details. parent->StealFrame(continuation); continuation->Destroy(aContext); if (generateReflowCommand && parent != lastParent) { presShell->FrameNeedsReflow(parent, IntrinsicDirty::FrameAndAncestors, NS_FRAME_HAS_DIRTY_CHILDREN); lastParent = parent; } } } void nsContainerFrame::DestroyAbsoluteFrames(DestroyContext& aContext) { if (IsAbsoluteContainer()) { GetAbsoluteContainingBlock()->DestroyFrames(aContext); MarkAsNotAbsoluteContainingBlock(); } } void nsContainerFrame::SafelyDestroyFrameListProp( DestroyContext& aContext, mozilla::PresShell* aPresShell, FrameListPropertyDescriptor aProp) { // Note that the last frame can be removed through another route and thus // delete the property -- that's why we fetch the property again before // removing each frame rather than fetching it once and iterating the list. while (nsFrameList* frameList = GetProperty(aProp)) { nsIFrame* frame = frameList->RemoveFirstChild(); if (MOZ_LIKELY(frame)) { frame->Destroy(aContext); } else { Unused << TakeProperty(aProp); frameList->Delete(aPresShell); return; } } } void nsContainerFrame::Destroy(DestroyContext& aContext) { // Prevent event dispatch during destruction. if (HasView()) { GetView()->SetFrame(nullptr); } DestroyAbsoluteFrames(aContext); // Destroy frames on the principal child list. mFrames.DestroyFrames(aContext); // If we have any IB split siblings, clear their references to us. if (HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { // Delete previous sibling's reference to me. if (nsIFrame* prevSib = GetProperty(nsIFrame::IBSplitPrevSibling())) { NS_WARNING_ASSERTION( this == prevSib->GetProperty(nsIFrame::IBSplitSibling()), "IB sibling chain is inconsistent"); prevSib->RemoveProperty(nsIFrame::IBSplitSibling()); } // Delete next sibling's reference to me. if (nsIFrame* nextSib = GetProperty(nsIFrame::IBSplitSibling())) { NS_WARNING_ASSERTION( this == nextSib->GetProperty(nsIFrame::IBSplitPrevSibling()), "IB sibling chain is inconsistent"); nextSib->RemoveProperty(nsIFrame::IBSplitPrevSibling()); } #ifdef DEBUG // This is just so we can assert it's not set in nsIFrame::DestroyFrom. RemoveStateBits(NS_FRAME_PART_OF_IBSPLIT); #endif } if (MOZ_UNLIKELY(!mProperties.IsEmpty())) { using T = mozilla::FrameProperties::UntypedDescriptor; bool hasO = false, hasOC = false, hasEOC = false, hasBackdrop = false; mProperties.ForEach([&](const T& aProp, uint64_t) { if (aProp == OverflowProperty()) { hasO = true; } else if (aProp == OverflowContainersProperty()) { hasOC = true; } else if (aProp == ExcessOverflowContainersProperty()) { hasEOC = true; } else if (aProp == BackdropProperty()) { hasBackdrop = true; } return true; }); // Destroy frames on the auxiliary frame lists and delete the lists. mozilla::PresShell* presShell = PresShell(); if (hasO) { SafelyDestroyFrameListProp(aContext, presShell, OverflowProperty()); } MOZ_ASSERT(CanContainOverflowContainers() || !(hasOC || hasEOC), "this type of frame shouldn't have overflow containers"); if (hasOC) { SafelyDestroyFrameListProp(aContext, presShell, OverflowContainersProperty()); } if (hasEOC) { SafelyDestroyFrameListProp(aContext, presShell, ExcessOverflowContainersProperty()); } MOZ_ASSERT(!GetProperty(BackdropProperty()) || StyleDisplay()->mTopLayer != StyleTopLayer::None, "only top layer frame may have backdrop"); if (hasBackdrop) { SafelyDestroyFrameListProp(aContext, presShell, BackdropProperty()); } } nsSplittableFrame::Destroy(aContext); } ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration const nsFrameList& nsContainerFrame::GetChildList(ChildListID aListID) const { // We only know about the principal child list, the overflow lists, // and the backdrop list. switch (aListID) { case FrameChildListID::Principal: return mFrames; case FrameChildListID::Overflow: { nsFrameList* list = GetOverflowFrames(); return list ? *list : nsFrameList::EmptyList(); } case FrameChildListID::OverflowContainers: { nsFrameList* list = GetOverflowContainers(); return list ? *list : nsFrameList::EmptyList(); } case FrameChildListID::ExcessOverflowContainers: { nsFrameList* list = GetExcessOverflowContainers(); return list ? *list : nsFrameList::EmptyList(); } case FrameChildListID::Backdrop: { nsFrameList* list = GetProperty(BackdropProperty()); return list ? *list : nsFrameList::EmptyList(); } default: return nsSplittableFrame::GetChildList(aListID); } } void nsContainerFrame::GetChildLists(nsTArray* aLists) const { mFrames.AppendIfNonempty(aLists, FrameChildListID::Principal); using T = mozilla::FrameProperties::UntypedDescriptor; mProperties.ForEach([this, aLists](const T& aProp, uint64_t aValue) { typedef const nsFrameList* L; if (aProp == OverflowProperty()) { reinterpret_cast(aValue)->AppendIfNonempty(aLists, FrameChildListID::Overflow); } else if (aProp == OverflowContainersProperty()) { MOZ_ASSERT(CanContainOverflowContainers(), "found unexpected OverflowContainersProperty"); Unused << this; // silence clang -Wunused-lambda-capture in opt builds reinterpret_cast(aValue)->AppendIfNonempty( aLists, FrameChildListID::OverflowContainers); } else if (aProp == ExcessOverflowContainersProperty()) { MOZ_ASSERT(CanContainOverflowContainers(), "found unexpected ExcessOverflowContainersProperty"); Unused << this; // silence clang -Wunused-lambda-capture in opt builds reinterpret_cast(aValue)->AppendIfNonempty( aLists, FrameChildListID::ExcessOverflowContainers); } else if (aProp == BackdropProperty()) { reinterpret_cast(aValue)->AppendIfNonempty(aLists, FrameChildListID::Backdrop); } return true; }); nsSplittableFrame::GetChildLists(aLists); } ///////////////////////////////////////////////////////////////////////////// // Painting/Events void nsContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { DisplayBorderBackgroundOutline(aBuilder, aLists); BuildDisplayListForNonBlockChildren(aBuilder, aLists); } void nsContainerFrame::BuildDisplayListForNonBlockChildren( nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists, DisplayChildFlags aFlags) { nsIFrame* kid = mFrames.FirstChild(); // Put each child's background directly onto the content list nsDisplayListSet set(aLists, aLists.Content()); // The children should be in content order while (kid) { BuildDisplayListForChild(aBuilder, kid, set, aFlags); kid = kid->GetNextSibling(); } } class nsDisplaySelectionOverlay : public nsPaintedDisplayItem { public: /** * @param aSelectionValue nsISelectionController::getDisplaySelection. */ nsDisplaySelectionOverlay(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, int16_t aSelectionValue) : nsPaintedDisplayItem(aBuilder, aFrame), mSelectionValue(aSelectionValue) { MOZ_COUNT_CTOR(nsDisplaySelectionOverlay); } MOZ_COUNTED_DTOR_OVERRIDE(nsDisplaySelectionOverlay) virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override; bool CreateWebRenderCommands( mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, mozilla::layers::RenderRootStateManager* aManager, nsDisplayListBuilder* aDisplayListBuilder) override; NS_DISPLAY_DECL_NAME("SelectionOverlay", TYPE_SELECTION_OVERLAY) private: DeviceColor ComputeColor() const; static DeviceColor ComputeColorFromSelectionStyle(ComputedStyle&); static DeviceColor ApplyTransparencyIfNecessary(nscolor); // nsISelectionController::getDisplaySelection. int16_t mSelectionValue; }; DeviceColor nsDisplaySelectionOverlay::ApplyTransparencyIfNecessary( nscolor aColor) { // If it has already alpha, leave it like that. if (NS_GET_A(aColor) != 255) { return ToDeviceColor(aColor); } // NOTE(emilio): Blink and WebKit do something slightly different here, and // blend the color with white instead, both for overlays and text backgrounds. auto color = sRGBColor::FromABGR(aColor); color.a = 0.5; return ToDeviceColor(color); } DeviceColor nsDisplaySelectionOverlay::ComputeColorFromSelectionStyle( ComputedStyle& aStyle) { return ApplyTransparencyIfNecessary( aStyle.GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor)); } DeviceColor nsDisplaySelectionOverlay::ComputeColor() const { LookAndFeel::ColorID colorID; if (RefPtr style = mFrame->ComputeSelectionStyle(mSelectionValue)) { return ComputeColorFromSelectionStyle(*style); } if (mSelectionValue == nsISelectionController::SELECTION_ON) { colorID = LookAndFeel::ColorID::Highlight; } else if (mSelectionValue == nsISelectionController::SELECTION_ATTENTION) { colorID = LookAndFeel::ColorID::TextSelectAttentionBackground; } else { colorID = LookAndFeel::ColorID::TextSelectDisabledBackground; } return ApplyTransparencyIfNecessary( LookAndFeel::Color(colorID, mFrame, NS_RGB(255, 255, 255))); } void nsDisplaySelectionOverlay::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { DrawTarget& aDrawTarget = *aCtx->GetDrawTarget(); ColorPattern color(ComputeColor()); nsIntRect pxRect = GetPaintRect(aBuilder, aCtx) .ToOutsidePixels(mFrame->PresContext()->AppUnitsPerDevPixel()); Rect rect(pxRect.x, pxRect.y, pxRect.width, pxRect.height); MaybeSnapToDevicePixels(rect, aDrawTarget, true); aDrawTarget.FillRect(rect, color); } bool nsDisplaySelectionOverlay::CreateWebRenderCommands( mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, mozilla::layers::RenderRootStateManager* aManager, nsDisplayListBuilder* aDisplayListBuilder) { wr::LayoutRect bounds = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( nsRect(ToReferenceFrame(), Frame()->GetSize()), mFrame->PresContext()->AppUnitsPerDevPixel())); aBuilder.PushRect(bounds, bounds, !BackfaceIsHidden(), false, false, wr::ToColorF(ComputeColor())); return true; } void nsContainerFrame::DisplaySelectionOverlay(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, uint16_t aContentType) { if (!IsSelected() || !IsVisibleForPainting()) { return; } int16_t displaySelection = PresShell()->GetSelectionFlags(); if (!(displaySelection & aContentType)) { return; } const nsFrameSelection* frameSelection = GetConstFrameSelection(); int16_t selectionValue = frameSelection->GetDisplaySelection(); if (selectionValue <= nsISelectionController::SELECTION_HIDDEN) { return; // selection is hidden or off } nsIContent* newContent = mContent->GetParent(); // check to see if we are anonymous content // XXXbz there has GOT to be a better way of determining this! int32_t offset = newContent ? newContent->ComputeIndexOf_Deprecated(mContent) : 0; // look up to see what selection(s) are on this frame UniquePtr details = frameSelection->LookUpSelection(newContent, offset, 1, false); if (!details) { return; } bool normal = false; for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) { if (sd->mSelectionType == SelectionType::eNormal) { normal = true; } } if (!normal && aContentType == nsISelectionDisplay::DISPLAY_IMAGES) { // Don't overlay an image if it's not in the primary selection. return; } aList->AppendNewToTop(aBuilder, this, selectionValue); } /* virtual */ void nsContainerFrame::ChildIsDirty(nsIFrame* aChild) { NS_ASSERTION(aChild->IsSubtreeDirty(), "child isn't actually dirty"); AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); } nsIFrame::FrameSearchResult nsContainerFrame::PeekOffsetNoAmount( bool aForward, int32_t* aOffset) { NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); // Don't allow the caret to stay in an empty (leaf) container frame. return CONTINUE_EMPTY; } nsIFrame::FrameSearchResult nsContainerFrame::PeekOffsetCharacter( bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) { NS_ASSERTION(aOffset && *aOffset <= 1, "aOffset out of range"); // Don't allow the caret to stay in an empty (leaf) container frame. return CONTINUE_EMPTY; } ///////////////////////////////////////////////////////////////////////////// // Helper member functions /** * Position the view associated with |aKidFrame|, if there is one. A * container frame should call this method after positioning a frame, * but before |Reflow|. */ void nsContainerFrame::PositionFrameView(nsIFrame* aKidFrame) { nsIFrame* parentFrame = aKidFrame->GetParent(); if (!aKidFrame->HasView() || !parentFrame) return; nsView* view = aKidFrame->GetView(); nsViewManager* vm = view->GetViewManager(); nsPoint pt; nsView* ancestorView = parentFrame->GetClosestView(&pt); if (ancestorView != view->GetParent()) { NS_ASSERTION(ancestorView == view->GetParent()->GetParent(), "Allowed only one anonymous view between frames"); // parentFrame is responsible for positioning aKidFrame's view // explicitly return; } pt += aKidFrame->GetPosition(); vm->MoveViewTo(view, pt.x, pt.y); } void nsContainerFrame::ReparentFrameView(nsIFrame* aChildFrame, nsIFrame* aOldParentFrame, nsIFrame* aNewParentFrame) { #ifdef DEBUG MOZ_ASSERT(aChildFrame, "null child frame pointer"); MOZ_ASSERT(aOldParentFrame, "null old parent frame pointer"); MOZ_ASSERT(aNewParentFrame, "null new parent frame pointer"); MOZ_ASSERT(aOldParentFrame != aNewParentFrame, "same old and new parent frame"); // See if either the old parent frame or the new parent frame have a view while (!aOldParentFrame->HasView() && !aNewParentFrame->HasView()) { // Walk up both the old parent frame and the new parent frame nodes // stopping when we either find a common parent or views for one // or both of the frames. // // This works well in the common case where we push/pull and the old parent // frame and the new parent frame are part of the same flow. They will // typically be the same distance (height wise) from the aOldParentFrame = aOldParentFrame->GetParent(); aNewParentFrame = aNewParentFrame->GetParent(); // We should never walk all the way to the root frame without finding // a view NS_ASSERTION(aOldParentFrame && aNewParentFrame, "didn't find view"); // See if we reached a common ancestor if (aOldParentFrame == aNewParentFrame) { break; } } // See if we found a common parent frame if (aOldParentFrame == aNewParentFrame) { // We found a common parent and there are no views between the old parent // and the common parent or the new parent frame and the common parent. // Because neither the old parent frame nor the new parent frame have views, // then any child views don't need reparenting return; } // We found views for one or both of the ancestor frames before we // found a common ancestor. nsView* oldParentView = aOldParentFrame->GetClosestView(); nsView* newParentView = aNewParentFrame->GetClosestView(); // See if the old parent frame and the new parent frame are in the // same view sub-hierarchy. If they are then we don't have to do // anything if (oldParentView != newParentView) { MOZ_ASSERT_UNREACHABLE("can't move frames between views"); // They're not so we need to reparent any child views aChildFrame->ReparentFrameViewTo(oldParentView->GetViewManager(), newParentView); } #endif } void nsContainerFrame::ReparentFrameViewList(const nsFrameList& aChildFrameList, nsIFrame* aOldParentFrame, nsIFrame* aNewParentFrame) { #ifdef DEBUG MOZ_ASSERT(aChildFrameList.NotEmpty(), "empty child frame list"); MOZ_ASSERT(aOldParentFrame, "null old parent frame pointer"); MOZ_ASSERT(aNewParentFrame, "null new parent frame pointer"); MOZ_ASSERT(aOldParentFrame != aNewParentFrame, "same old and new parent frame"); // See if either the old parent frame or the new parent frame have a view while (!aOldParentFrame->HasView() && !aNewParentFrame->HasView()) { // Walk up both the old parent frame and the new parent frame nodes // stopping when we either find a common parent or views for one // or both of the frames. // // This works well in the common case where we push/pull and the old parent // frame and the new parent frame are part of the same flow. They will // typically be the same distance (height wise) from the aOldParentFrame = aOldParentFrame->GetParent(); aNewParentFrame = aNewParentFrame->GetParent(); // We should never walk all the way to the root frame without finding // a view NS_ASSERTION(aOldParentFrame && aNewParentFrame, "didn't find view"); // See if we reached a common ancestor if (aOldParentFrame == aNewParentFrame) { break; } } // See if we found a common parent frame if (aOldParentFrame == aNewParentFrame) { // We found a common parent and there are no views between the old parent // and the common parent or the new parent frame and the common parent. // Because neither the old parent frame nor the new parent frame have views, // then any child views don't need reparenting return; } // We found views for one or both of the ancestor frames before we // found a common ancestor. nsView* oldParentView = aOldParentFrame->GetClosestView(); nsView* newParentView = aNewParentFrame->GetClosestView(); // See if the old parent frame and the new parent frame are in the // same view sub-hierarchy. If they are then we don't have to do // anything if (oldParentView != newParentView) { MOZ_ASSERT_UNREACHABLE("can't move frames between views"); nsViewManager* viewManager = oldParentView->GetViewManager(); // They're not so we need to reparent any child views for (nsIFrame* f : aChildFrameList) { f->ReparentFrameViewTo(viewManager, newParentView); } } #endif } void nsContainerFrame::ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent, nsContainerFrame* aNewParent) { NS_ASSERTION(aOldParent == aFrame->GetParent(), "Parent not consistent with expectations"); aFrame->SetParent(aNewParent); // When pushing and pulling frames we need to check for whether any // views need to be reparented ReparentFrameView(aFrame, aOldParent, aNewParent); } void nsContainerFrame::ReparentFrames(nsFrameList& aFrameList, nsContainerFrame* aOldParent, nsContainerFrame* aNewParent) { for (auto* f : aFrameList) { ReparentFrame(f, aOldParent, aNewParent); } } void nsContainerFrame::SetSizeConstraints(nsPresContext* aPresContext, nsIWidget* aWidget, const nsSize& aMinSize, const nsSize& aMaxSize) { LayoutDeviceIntSize devMinSize( aPresContext->AppUnitsToDevPixels(aMinSize.width), aPresContext->AppUnitsToDevPixels(aMinSize.height)); LayoutDeviceIntSize devMaxSize( aMaxSize.width == NS_UNCONSTRAINEDSIZE ? NS_MAXSIZE : aPresContext->AppUnitsToDevPixels(aMaxSize.width), aMaxSize.height == NS_UNCONSTRAINEDSIZE ? NS_MAXSIZE : aPresContext->AppUnitsToDevPixels(aMaxSize.height)); // MinSize has a priority over MaxSize if (devMinSize.width > devMaxSize.width) devMaxSize.width = devMinSize.width; if (devMinSize.height > devMaxSize.height) devMaxSize.height = devMinSize.height; nsIWidget* rootWidget = aPresContext->GetNearestWidget(); DesktopToLayoutDeviceScale constraintsScale(MOZ_WIDGET_INVALID_SCALE); if (rootWidget) { constraintsScale = rootWidget->GetDesktopToDeviceScale(); } widget::SizeConstraints constraints(devMinSize, devMaxSize, constraintsScale); // The sizes are in inner window sizes, so convert them into outer window // sizes. Use a size of (200, 200) as only the difference between the inner // and outer size is needed. const LayoutDeviceIntSize sizeDiff = aWidget->ClientToWindowSizeDifference(); if (constraints.mMinSize.width) { constraints.mMinSize.width += sizeDiff.width; } if (constraints.mMinSize.height) { constraints.mMinSize.height += sizeDiff.height; } if (constraints.mMaxSize.width != NS_MAXSIZE) { constraints.mMaxSize.width += sizeDiff.width; } if (constraints.mMaxSize.height != NS_MAXSIZE) { constraints.mMaxSize.height += sizeDiff.height; } aWidget->SetSizeConstraints(constraints); } void nsContainerFrame::SyncFrameViewAfterReflow(nsPresContext* aPresContext, nsIFrame* aFrame, nsView* aView, const nsRect& aInkOverflowArea, ReflowChildFlags aFlags) { if (!aView) { return; } // Make sure the view is sized and positioned correctly if (!(aFlags & ReflowChildFlags::NoMoveView)) { PositionFrameView(aFrame); } if (!(aFlags & ReflowChildFlags::NoSizeView)) { nsViewManager* vm = aView->GetViewManager(); vm->ResizeView(aView, aInkOverflowArea, true); } } void nsContainerFrame::DoInlineMinISize(gfxContext* aRenderingContext, InlineMinISizeData* aData) { auto handleChildren = [aRenderingContext](auto frame, auto data) { for (nsIFrame* kid : frame->mFrames) { kid->AddInlineMinISize(aRenderingContext, data); } }; DoInlineIntrinsicISize(aData, handleChildren); } void nsContainerFrame::DoInlinePrefISize(gfxContext* aRenderingContext, InlinePrefISizeData* aData) { auto handleChildren = [aRenderingContext](auto frame, auto data) { for (nsIFrame* kid : frame->mFrames) { kid->AddInlinePrefISize(aRenderingContext, data); } }; DoInlineIntrinsicISize(aData, handleChildren); aData->mLineIsEmpty = false; } /* virtual */ LogicalSize nsContainerFrame::ComputeAutoSize( gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, nscoord aAvailableISize, const LogicalSize& aMargin, const mozilla::LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { LogicalSize result(aWM, 0xdeadbeef, NS_UNCONSTRAINEDSIZE); nscoord availBased = aAvailableISize - aMargin.ISize(aWM) - aBorderPadding.ISize(aWM); // replaced elements always shrink-wrap if (aFlags.contains(ComputeSizeFlag::ShrinkWrap) || IsReplaced()) { // Only bother computing our 'auto' ISize if the result will be used. const auto& styleISize = aSizeOverrides.mStyleISize ? *aSizeOverrides.mStyleISize : StylePosition()->ISize(aWM); if (styleISize.IsAuto()) { result.ISize(aWM) = ShrinkISizeToFit(aRenderingContext, availBased, aFlags); } } else { result.ISize(aWM) = availBased; } if (IsTableCaption()) { // If we're a container for font size inflation, then shrink // wrapping inside of us should not apply font size inflation. AutoMaybeDisableFontInflation an(this); WritingMode tableWM = GetParent()->GetWritingMode(); if (aWM.IsOrthogonalTo(tableWM)) { // For an orthogonal caption on a block-dir side of the table, shrink-wrap // to min-isize. result.ISize(aWM) = GetMinISize(aRenderingContext); } else { // The outer frame constrains our available isize to the isize of // the table. Grow if our min-isize is bigger than that, but not // larger than the containing block isize. (It would really be nice // to transmit that information another way, so we could grow up to // the table's available isize, but that's harder.) nscoord min = GetMinISize(aRenderingContext); if (min > aCBSize.ISize(aWM)) { min = aCBSize.ISize(aWM); } if (min > result.ISize(aWM)) { result.ISize(aWM) = min; } } } return result; } void nsContainerFrame::ReflowChild( nsIFrame* aKidFrame, nsPresContext* aPresContext, ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput, const WritingMode& aWM, const LogicalPoint& aPos, const nsSize& aContainerSize, ReflowChildFlags aFlags, nsReflowStatus& aStatus, nsOverflowContinuationTracker* aTracker) { MOZ_ASSERT(aReflowInput.mFrame == aKidFrame, "bad reflow input"); if (aWM.IsPhysicalRTL()) { NS_ASSERTION(aContainerSize.width != NS_UNCONSTRAINEDSIZE, "ReflowChild with unconstrained container width!"); } MOZ_ASSERT(aDesiredSize.InkOverflow() == nsRect(0, 0, 0, 0) && aDesiredSize.ScrollableOverflow() == nsRect(0, 0, 0, 0), "please reset the overflow areas before calling ReflowChild"); // Position the child frame and its view if requested. if (ReflowChildFlags::NoMoveFrame != (aFlags & ReflowChildFlags::NoMoveFrame)) { aKidFrame->SetPosition(aWM, aPos, aContainerSize); } if (!(aFlags & ReflowChildFlags::NoMoveView)) { PositionFrameView(aKidFrame); PositionChildViews(aKidFrame); } // Reflow the child frame aKidFrame->Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus); // If the child frame is complete, delete any next-in-flows, // but only if the NoDeleteNextInFlowChild flag isn't set. if (!aStatus.IsInlineBreakBefore() && aStatus.IsFullyComplete() && !(aFlags & ReflowChildFlags::NoDeleteNextInFlowChild)) { if (nsIFrame* kidNextInFlow = aKidFrame->GetNextInFlow()) { // Remove all of the childs next-in-flows. Make sure that we ask // the right parent to do the removal (it's possible that the // parent is not this because we are executing pullup code) nsOverflowContinuationTracker::AutoFinish fini(aTracker, aKidFrame); DestroyContext context(PresShell()); kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, kidNextInFlow, true); } } } // XXX temporary: hold on to a copy of the old physical version of // ReflowChild so that we can convert callers incrementally. void nsContainerFrame::ReflowChild(nsIFrame* aKidFrame, nsPresContext* aPresContext, ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput, nscoord aX, nscoord aY, ReflowChildFlags aFlags, nsReflowStatus& aStatus, nsOverflowContinuationTracker* aTracker) { MOZ_ASSERT(aReflowInput.mFrame == aKidFrame, "bad reflow input"); // Position the child frame and its view if requested. if (ReflowChildFlags::NoMoveFrame != (aFlags & ReflowChildFlags::NoMoveFrame)) { aKidFrame->SetPosition(nsPoint(aX, aY)); } if (!(aFlags & ReflowChildFlags::NoMoveView)) { PositionFrameView(aKidFrame); PositionChildViews(aKidFrame); } // Reflow the child frame aKidFrame->Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus); // If the child frame is complete, delete any next-in-flows, // but only if the NoDeleteNextInFlowChild flag isn't set. if (aStatus.IsFullyComplete() && !(aFlags & ReflowChildFlags::NoDeleteNextInFlowChild)) { if (nsIFrame* kidNextInFlow = aKidFrame->GetNextInFlow()) { // Remove all of the childs next-in-flows. Make sure that we ask // the right parent to do the removal (it's possible that the // parent is not this because we are executing pullup code) nsOverflowContinuationTracker::AutoFinish fini(aTracker, aKidFrame); DestroyContext context(PresShell()); kidNextInFlow->GetParent()->DeleteNextInFlowChild(context, kidNextInFlow, true); } } } /** * Position the views of |aFrame|'s descendants. A container frame * should call this method if it moves a frame after |Reflow|. */ void nsContainerFrame::PositionChildViews(nsIFrame* aFrame) { if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) { return; } // Recursively walk aFrame's child frames. // Process the additional child lists, but skip the popup list as the view for // popups is managed by the parent. // Currently only nsMenuFrame has a popupList and during layout will adjust // the view manually to position the popup. for (const auto& [list, listID] : aFrame->ChildLists()) { if (listID == FrameChildListID::Popup) { continue; } for (nsIFrame* childFrame : list) { // Position the frame's view (if it has one) otherwise recursively // process its children if (childFrame->HasView()) { PositionFrameView(childFrame); } else { PositionChildViews(childFrame); } } } } void nsContainerFrame::FinishReflowChild( nsIFrame* aKidFrame, nsPresContext* aPresContext, const ReflowOutput& aDesiredSize, const ReflowInput* aReflowInput, const WritingMode& aWM, const LogicalPoint& aPos, const nsSize& aContainerSize, nsIFrame::ReflowChildFlags aFlags) { MOZ_ASSERT(!aReflowInput || aReflowInput->mFrame == aKidFrame); MOZ_ASSERT(aReflowInput || aKidFrame->IsMathMLFrame() || aKidFrame->IsTableCellFrame(), "aReflowInput should be passed in almost all cases"); if (aWM.IsPhysicalRTL()) { NS_ASSERTION(aContainerSize.width != NS_UNCONSTRAINEDSIZE, "FinishReflowChild with unconstrained container width!"); } nsPoint curOrigin = aKidFrame->GetPosition(); const LogicalSize convertedSize = aDesiredSize.Size(aWM); LogicalPoint pos(aPos); if (aFlags & ReflowChildFlags::ApplyRelativePositioning) { MOZ_ASSERT(aReflowInput, "caller must have passed reflow input"); // ApplyRelativePositioning in right-to-left writing modes needs to know // the updated frame width to set the normal position correctly. aKidFrame->SetSize(aWM, convertedSize); const LogicalMargin offsets = aReflowInput->ComputedLogicalOffsets(aWM); ReflowInput::ApplyRelativePositioning(aKidFrame, aWM, offsets, &pos, aContainerSize); } if (ReflowChildFlags::NoMoveFrame != (aFlags & ReflowChildFlags::NoMoveFrame)) { aKidFrame->SetRect(aWM, LogicalRect(aWM, pos, convertedSize), aContainerSize); } else { aKidFrame->SetSize(aWM, convertedSize); } if (aKidFrame->HasView()) { nsView* view = aKidFrame->GetView(); // Make sure the frame's view is properly sized and positioned and has // things like opacity correct SyncFrameViewAfterReflow(aPresContext, aKidFrame, view, aDesiredSize.InkOverflow(), aFlags); } nsPoint newOrigin = aKidFrame->GetPosition(); if (!(aFlags & ReflowChildFlags::NoMoveView) && curOrigin != newOrigin) { if (!aKidFrame->HasView()) { // If the frame has moved, then we need to make sure any child views are // correctly positioned PositionChildViews(aKidFrame); } } aKidFrame->DidReflow(aPresContext, aReflowInput); } #if defined(_MSC_VER) && !defined(__clang__) && defined(_M_AMD64) # pragma optimize("", on) #endif // XXX temporary: hold on to a copy of the old physical version of // FinishReflowChild so that we can convert callers incrementally. void nsContainerFrame::FinishReflowChild(nsIFrame* aKidFrame, nsPresContext* aPresContext, const ReflowOutput& aDesiredSize, const ReflowInput* aReflowInput, nscoord aX, nscoord aY, ReflowChildFlags aFlags) { MOZ_ASSERT(!(aFlags & ReflowChildFlags::ApplyRelativePositioning), "only the logical version supports ApplyRelativePositioning " "since ApplyRelativePositioning requires the container size"); nsPoint curOrigin = aKidFrame->GetPosition(); nsPoint pos(aX, aY); nsSize size(aDesiredSize.PhysicalSize()); if (ReflowChildFlags::NoMoveFrame != (aFlags & ReflowChildFlags::NoMoveFrame)) { aKidFrame->SetRect(nsRect(pos, size)); } else { aKidFrame->SetSize(size); } if (aKidFrame->HasView()) { nsView* view = aKidFrame->GetView(); // Make sure the frame's view is properly sized and positioned and has // things like opacity correct SyncFrameViewAfterReflow(aPresContext, aKidFrame, view, aDesiredSize.InkOverflow(), aFlags); } if (!(aFlags & ReflowChildFlags::NoMoveView) && curOrigin != pos) { if (!aKidFrame->HasView()) { // If the frame has moved, then we need to make sure any child views are // correctly positioned PositionChildViews(aKidFrame); } } aKidFrame->DidReflow(aPresContext, aReflowInput); } void nsContainerFrame::ReflowOverflowContainerChildren( nsPresContext* aPresContext, const ReflowInput& aReflowInput, OverflowAreas& aOverflowRects, ReflowChildFlags aFlags, nsReflowStatus& aStatus, ChildFrameMerger aMergeFunc, Maybe aContainerSize) { MOZ_ASSERT(aPresContext, "null pointer"); nsFrameList* overflowContainers = DrainExcessOverflowContainersList(aMergeFunc); if (!overflowContainers) { return; // nothing to reflow } nsOverflowContinuationTracker tracker(this, false, false); bool shouldReflowAllKids = aReflowInput.ShouldReflowAllKids(); for (nsIFrame* frame : *overflowContainers) { if (frame->GetPrevInFlow()->GetParent() != GetPrevInFlow()) { // frame's prevInFlow has moved, skip reflowing this frame; // it will get reflowed once it's been placed if (GetNextInFlow()) { // We report OverflowIncomplete status in this case to avoid our parent // deleting our next-in-flows which might destroy non-empty frames. nsReflowStatus status; status.SetOverflowIncomplete(); aStatus.MergeCompletionStatusFrom(status); } continue; } auto ScrollableOverflowExceedsAvailableBSize = [this, &aReflowInput](nsIFrame* aFrame) { if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { return false; } const auto parentWM = GetWritingMode(); const nscoord scrollableOverflowRectBEnd = LogicalRect(parentWM, aFrame->ScrollableOverflowRectRelativeToParent(), GetSize()) .BEnd(parentWM); return scrollableOverflowRectBEnd > aReflowInput.AvailableBSize(); }; // If the available block-size has changed, or the existing scrollable // overflow's block-end exceeds it, we need to reflow even if the frame // isn't dirty. if (shouldReflowAllKids || frame->IsSubtreeDirty() || ScrollableOverflowExceedsAvailableBSize(frame)) { nsIFrame* prevInFlow = frame->GetPrevInFlow(); NS_ASSERTION(prevInFlow, "overflow container frame must have a prev-in-flow"); NS_ASSERTION( frame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER), "overflow container frame must have overflow container bit set"); WritingMode wm = frame->GetWritingMode(); // Note: aReflowInput's available inline-size is technically wrong for us // to hand off to children here, because it doesn't account for the space // that's been used for the container's margin/border/padding (and some // other space that a concrete container type, e.g. fieldset and grid [1], // might reserve before setting up the available space for their // children). Since we don't have a way to query the specific available // inline-size each container type used, nor do we know how the container // computes its non-overflow-container children's inline-size, we just // unconditionally override the frame's inline-size, so that the available // inline-size for the children doesn't really matter anyway. // // [1] For example, fieldset uses its computed inline-size with padding as // the available inline-size to reflow its inner child frame. // https://searchfox.org/mozilla-central/rev/04f7743d94691fa24212fb43099f9d84c3bfc890/layout/forms/nsFieldSetFrame.cpp#535-536 const LogicalSize availSpace = aReflowInput.AvailableSize(wm); StyleSizeOverrides sizeOverride; // We override current continuation's inline-size by using the // prev-in-flow's inline-size since both should be the same. sizeOverride.mStyleISize.emplace( StyleSize::LengthPercentage(LengthPercentage::FromAppUnits( frame->StylePosition()->mBoxSizing == StyleBoxSizing::Border ? prevInFlow->ISize(wm) : prevInFlow->ContentISize(wm)))); if (frame->IsFlexItem()) { // An overflow container's block-size must be 0. sizeOverride.mStyleBSize.emplace( StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(0))); } ReflowOutput desiredSize(wm); ReflowInput reflowInput(aPresContext, aReflowInput, frame, availSpace, Nothing(), {}, sizeOverride); const nsSize containerSize = aContainerSize ? *aContainerSize : aReflowInput.AvailableSize(wm).GetPhysicalSize(wm); const LogicalPoint pos(wm, prevInFlow->IStart(wm, containerSize), 0); nsReflowStatus frameStatus; ReflowChild(frame, aPresContext, desiredSize, reflowInput, wm, pos, containerSize, aFlags, frameStatus, &tracker); FinishReflowChild(frame, aPresContext, desiredSize, &reflowInput, wm, pos, containerSize, aFlags); // Handle continuations if (!frameStatus.IsFullyComplete()) { if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { // Abspos frames can't cause their parent to be incomplete, // only overflow incomplete. frameStatus.SetOverflowIncomplete(); } else { NS_ASSERTION(frameStatus.IsComplete(), "overflow container frames can't be incomplete, only " "overflow-incomplete"); } // Acquire a next-in-flow, creating it if necessary nsIFrame* nif = frame->GetNextInFlow(); if (!nif) { NS_ASSERTION(frameStatus.NextInFlowNeedsReflow(), "Someone forgot a NextInFlowNeedsReflow flag"); nif = PresShell()->FrameConstructor()->CreateContinuingFrame(frame, this); } else if (!nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { // used to be a normal next-in-flow; steal it from the child list nif->GetParent()->StealFrame(nif); } tracker.Insert(nif, frameStatus); } aStatus.MergeCompletionStatusFrom(frameStatus); // At this point it would be nice to assert // !frame->GetOverflowRect().IsEmpty(), but we have some unsplittable // frames that, when taller than availableHeight will push zero-height // content into a next-in-flow. } else { tracker.Skip(frame, aStatus); if (aReflowInput.mFloatManager) { nsBlockFrame::RecoverFloatsFor(frame, *aReflowInput.mFloatManager, aReflowInput.GetWritingMode(), aReflowInput.ComputedPhysicalSize()); } } ConsiderChildOverflow(aOverflowRects, frame); } } void nsContainerFrame::DisplayOverflowContainers( nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { nsFrameList* overflowconts = GetOverflowContainers(); if (overflowconts) { for (nsIFrame* frame : *overflowconts) { BuildDisplayListForChild(aBuilder, frame, aLists); } } } bool nsContainerFrame::TryRemoveFrame(FrameListPropertyDescriptor aProp, nsIFrame* aChildToRemove) { nsFrameList* list = GetProperty(aProp); if (list && list->StartRemoveFrame(aChildToRemove)) { // aChildToRemove *may* have been removed from this list. if (list->IsEmpty()) { Unused << TakeProperty(aProp); list->Delete(PresShell()); } return true; } return false; } bool nsContainerFrame::MaybeStealOverflowContainerFrame(nsIFrame* aChild) { bool removed = false; if (MOZ_UNLIKELY(aChild->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER))) { // Try removing from the overflow container list. removed = TryRemoveFrame(OverflowContainersProperty(), aChild); if (!removed) { // It might be in the excess overflow container list. removed = TryRemoveFrame(ExcessOverflowContainersProperty(), aChild); } } return removed; } void nsContainerFrame::StealFrame(nsIFrame* aChild) { #ifdef DEBUG if (!mFrames.ContainsFrame(aChild)) { nsFrameList* list = GetOverflowFrames(); if (!list || !list->ContainsFrame(aChild)) { list = GetOverflowContainers(); if (!list || !list->ContainsFrame(aChild)) { list = GetExcessOverflowContainers(); MOZ_ASSERT(list && list->ContainsFrame(aChild), "aChild isn't our child" " or on a frame list not supported by StealFrame"); } } } #endif if (MaybeStealOverflowContainerFrame(aChild)) { return; } // NOTE nsColumnSetFrame and nsCanvasFrame have their overflow containers // on the normal lists so we might get here also if the frame bit // NS_FRAME_IS_OVERFLOW_CONTAINER is set. if (mFrames.StartRemoveFrame(aChild)) { return; } // We didn't find the child in our principal child list. // Maybe it's on the overflow list? nsFrameList* frameList = GetOverflowFrames(); if (frameList && frameList->ContinueRemoveFrame(aChild)) { if (frameList->IsEmpty()) { DestroyOverflowList(); } return; } MOZ_ASSERT_UNREACHABLE("StealFrame: can't find aChild"); } nsFrameList nsContainerFrame::StealFramesAfter(nsIFrame* aChild) { NS_ASSERTION( !aChild || !aChild->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER), "StealFramesAfter doesn't handle overflow containers"); NS_ASSERTION(!IsBlockFrame(), "unexpected call"); if (!aChild) { return std::move(mFrames); } for (nsIFrame* f : mFrames) { if (f == aChild) { return mFrames.TakeFramesAfter(f); } } // We didn't find the child in the principal child list. // Maybe it's on the overflow list? if (nsFrameList* overflowFrames = GetOverflowFrames()) { for (nsIFrame* f : *overflowFrames) { if (f == aChild) { return mFrames.TakeFramesAfter(f); } } } NS_ERROR("StealFramesAfter: can't find aChild"); return nsFrameList(); } /* * Create a next-in-flow for aFrame. Will return the newly created * frame if and only if a new frame is created; otherwise * nullptr is returned. */ nsIFrame* nsContainerFrame::CreateNextInFlow(nsIFrame* aFrame) { MOZ_ASSERT( !IsBlockFrame(), "you should have called nsBlockFrame::CreateContinuationFor instead"); MOZ_ASSERT(mFrames.ContainsFrame(aFrame), "expected an in-flow child frame"); nsIFrame* nextInFlow = aFrame->GetNextInFlow(); if (nullptr == nextInFlow) { // Create a continuation frame for the child frame and insert it // into our child list. nextInFlow = PresShell()->FrameConstructor()->CreateContinuingFrame(aFrame, this); mFrames.InsertFrame(nullptr, aFrame, nextInFlow); NS_FRAME_LOG(NS_FRAME_TRACE_NEW_FRAMES, ("nsContainerFrame::CreateNextInFlow: frame=%p nextInFlow=%p", aFrame, nextInFlow)); return nextInFlow; } return nullptr; } /** * Remove and delete aNextInFlow and its next-in-flows. Updates the sibling and * flow pointers */ void nsContainerFrame::DeleteNextInFlowChild(DestroyContext& aContext, nsIFrame* aNextInFlow, bool aDeletingEmptyFrames) { #ifdef DEBUG nsIFrame* prevInFlow = aNextInFlow->GetPrevInFlow(); #endif MOZ_ASSERT(prevInFlow, "bad prev-in-flow"); // If the next-in-flow has a next-in-flow then delete it, too (and // delete it first). // Do this in a loop so we don't overflow the stack for frames // with very many next-in-flows nsIFrame* nextNextInFlow = aNextInFlow->GetNextInFlow(); if (nextNextInFlow) { AutoTArray frames; for (nsIFrame* f = nextNextInFlow; f; f = f->GetNextInFlow()) { frames.AppendElement(f); } for (nsIFrame* delFrame : Reversed(frames)) { nsContainerFrame* parent = delFrame->GetParent(); parent->DeleteNextInFlowChild(aContext, delFrame, aDeletingEmptyFrames); } } // Take the next-in-flow out of the parent's child list StealFrame(aNextInFlow); #ifdef DEBUG if (aDeletingEmptyFrames) { nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow); } #endif // Delete the next-in-flow frame and its descendants. This will also // remove it from its next-in-flow/prev-in-flow chain. aNextInFlow->Destroy(aContext); MOZ_ASSERT(!prevInFlow->GetNextInFlow(), "non null next-in-flow"); } void nsContainerFrame::PushChildrenToOverflow(nsIFrame* aFromChild, nsIFrame* aPrevSibling) { MOZ_ASSERT(aFromChild, "null pointer"); MOZ_ASSERT(aPrevSibling, "pushing first child"); MOZ_ASSERT(aPrevSibling->GetNextSibling() == aFromChild, "bad prev sibling"); // Add the frames to our overflow list (let our next in flow drain // our overflow list when it is ready) SetOverflowFrames(mFrames.TakeFramesAfter(aPrevSibling)); } bool nsContainerFrame::PushIncompleteChildren( const FrameHashtable& aPushedItems, const FrameHashtable& aIncompleteItems, const FrameHashtable& aOverflowIncompleteItems) { MOZ_ASSERT(IsFlexOrGridContainer(), "Only Grid / Flex containers can call this!"); if (aPushedItems.IsEmpty() && aIncompleteItems.IsEmpty() && aOverflowIncompleteItems.IsEmpty()) { return false; } // Iterate the children in normal document order and append them (or a NIF) // to one of the following frame lists according to their status. nsFrameList pushedList; nsFrameList incompleteList; nsFrameList overflowIncompleteList; auto* fc = PresShell()->FrameConstructor(); for (nsIFrame* child = PrincipalChildList().FirstChild(); child;) { MOZ_ASSERT((aPushedItems.Contains(child) ? 1 : 0) + (aIncompleteItems.Contains(child) ? 1 : 0) + (aOverflowIncompleteItems.Contains(child) ? 1 : 0) <= 1, "child should only be in one of these sets"); // Save the next-sibling so we can continue the loop if |child| is moved. nsIFrame* next = child->GetNextSibling(); if (aPushedItems.Contains(child)) { MOZ_ASSERT(child->GetParent() == this); StealFrame(child); pushedList.AppendFrame(nullptr, child); } else if (aIncompleteItems.Contains(child)) { nsIFrame* childNIF = child->GetNextInFlow(); if (!childNIF) { childNIF = fc->CreateContinuingFrame(child, this); incompleteList.AppendFrame(nullptr, childNIF); } else { auto* parent = childNIF->GetParent(); MOZ_ASSERT(parent != this || !mFrames.ContainsFrame(childNIF), "child's NIF shouldn't be in the same principal list"); // If child's existing NIF is an overflow container, convert it to an // actual NIF, since now |child| has non-overflow stuff to give it. // Or, if it's further away then our next-in-flow, then pull it up. if (childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) || (parent != this && parent != GetNextInFlow())) { parent->StealFrame(childNIF); childNIF->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); if (parent == this) { incompleteList.AppendFrame(nullptr, childNIF); } else { // If childNIF already lives on the next fragment, then we // don't need to reparent it, since we know it's destined to end // up there anyway. Just move it to its parent's overflow list. if (parent == GetNextInFlow()) { nsFrameList toMove(childNIF, childNIF); parent->MergeSortedOverflow(toMove); } else { ReparentFrame(childNIF, parent, this); incompleteList.AppendFrame(nullptr, childNIF); } } } } } else if (aOverflowIncompleteItems.Contains(child)) { nsIFrame* childNIF = child->GetNextInFlow(); if (!childNIF) { childNIF = fc->CreateContinuingFrame(child, this); childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); overflowIncompleteList.AppendFrame(nullptr, childNIF); } else { DebugOnly lastParent = this; auto* nif = static_cast(GetNextInFlow()); // If child has any non-overflow-container NIFs, convert them to // overflow containers, since that's all |child| needs now. while (childNIF && !childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { auto* parent = childNIF->GetParent(); parent->StealFrame(childNIF); childNIF->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); if (parent == this) { overflowIncompleteList.AppendFrame(nullptr, childNIF); } else { if (!nif || parent == nif) { nsFrameList toMove(childNIF, childNIF); parent->MergeSortedExcessOverflowContainers(toMove); } else { ReparentFrame(childNIF, parent, nif); nsFrameList toMove(childNIF, childNIF); nif->MergeSortedExcessOverflowContainers(toMove); } // We only need to reparent the first childNIF (or not at all if // its parent is our NIF). nif = nullptr; } lastParent = parent; childNIF = childNIF->GetNextInFlow(); } } } child = next; } // Merge the results into our respective overflow child lists. if (!pushedList.IsEmpty()) { MergeSortedOverflow(pushedList); } if (!incompleteList.IsEmpty()) { MergeSortedOverflow(incompleteList); } if (!overflowIncompleteList.IsEmpty()) { // If our next-in-flow already has overflow containers list, merge the // overflowIncompleteList into that list. Otherwise, merge it into our // excess overflow containers list, to be drained by our next-in-flow. auto* nif = static_cast(GetNextInFlow()); nsFrameList* oc = nif ? nif->GetOverflowContainers() : nullptr; if (oc) { ReparentFrames(overflowIncompleteList, this, nif); MergeSortedFrameLists(*oc, overflowIncompleteList, GetContent()); } else { MergeSortedExcessOverflowContainers(overflowIncompleteList); } } return true; } void nsContainerFrame::NormalizeChildLists() { MOZ_ASSERT(IsFlexOrGridContainer(), "Only Flex / Grid containers can call this!"); // Note: the following description uses grid container as an example. Flex // container is similar. // // First we gather child frames we should include in our reflow/placement, // i.e. overflowed children from our prev-in-flow, and pushed first-in-flow // children (that might now fit). It's important to note that these children // can be in arbitrary order vis-a-vis the current children in our lists. // E.g. grid items in the document order: A, B, C may be placed in the rows // 3, 2, 1. Assume each row goes in a separate grid container fragment, // and we reflow the second fragment. Now if C (in fragment 1) overflows, // we can't just prepend it to our mFrames like we usually do because that // would violate the document order invariant that other code depends on. // Similarly if we pull up child A (from fragment 3) we can't just append // that for the same reason. Instead, we must sort these children into // our child lists. (The sorting is trivial given that both lists are // already fully sorted individually - it's just a merge.) // // The invariants that we maintain are that each grid container child list // is sorted in the normal document order at all times, but that children // in different grid container continuations may be in arbitrary order. const auto didPushItemsBit = IsFlexContainerFrame() ? NS_STATE_FLEX_DID_PUSH_ITEMS : NS_STATE_GRID_DID_PUSH_ITEMS; const auto hasChildNifBit = IsFlexContainerFrame() ? NS_STATE_FLEX_HAS_CHILD_NIFS : NS_STATE_GRID_HAS_CHILD_NIFS; auto* prevInFlow = static_cast(GetPrevInFlow()); // Merge overflow frames from our prev-in-flow into our principal child list. if (prevInFlow) { AutoFrameListPtr overflow(PresContext(), prevInFlow->StealOverflowFrames()); if (overflow) { ReparentFrames(*overflow, prevInFlow, this); MergeSortedFrameLists(mFrames, *overflow, GetContent()); // Move trailing next-in-flows into our overflow list. nsFrameList continuations; for (nsIFrame* f = mFrames.FirstChild(); f;) { nsIFrame* next = f->GetNextSibling(); nsIFrame* pif = f->GetPrevInFlow(); if (pif && pif->GetParent() == this) { mFrames.RemoveFrame(f); continuations.AppendFrame(nullptr, f); } f = next; } MergeSortedOverflow(continuations); // Move prev-in-flow's excess overflow containers list into our own // overflow containers list. If we already have an excess overflow // containers list, any child in that list which doesn't have a // prev-in-flow in this frame is also merged into our overflow container // list. nsFrameList* overflowContainers = DrainExcessOverflowContainersList(MergeSortedFrameListsFor); // Move trailing OC next-in-flows into our excess overflow containers // list. if (overflowContainers) { nsFrameList moveToEOC; for (nsIFrame* f = overflowContainers->FirstChild(); f;) { nsIFrame* next = f->GetNextSibling(); nsIFrame* pif = f->GetPrevInFlow(); if (pif && pif->GetParent() == this) { overflowContainers->RemoveFrame(f); moveToEOC.AppendFrame(nullptr, f); } f = next; } if (overflowContainers->IsEmpty()) { DestroyOverflowContainers(); } MergeSortedExcessOverflowContainers(moveToEOC); } } } // For each item in aItems, pull up its next-in-flow (if any), and reparent it // to our next-in-flow, unless its parent is already ourselves or our // next-in-flow (to avoid leaving a hole there). auto PullItemsNextInFlow = [this](const nsFrameList& aItems) { auto* firstNIF = static_cast(GetNextInFlow()); if (!firstNIF) { return; } nsFrameList childNIFs; nsFrameList childOCNIFs; for (auto* child : aItems) { if (auto* childNIF = child->GetNextInFlow()) { if (auto* parent = childNIF->GetParent(); parent != this && parent != firstNIF) { parent->StealFrame(childNIF); ReparentFrame(childNIF, parent, firstNIF); if (childNIF->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { childOCNIFs.AppendFrame(nullptr, childNIF); } else { childNIFs.AppendFrame(nullptr, childNIF); } } } } // Merge aItems' NIFs into our NIF's respective overflow child lists. firstNIF->MergeSortedOverflow(childNIFs); firstNIF->MergeSortedExcessOverflowContainers(childOCNIFs); }; // Merge our own overflow frames into our principal child list, // except those that are a next-in-flow for one of our items. DebugOnly foundOwnPushedChild = false; { nsFrameList* ourOverflow = GetOverflowFrames(); if (ourOverflow) { nsFrameList items; for (nsIFrame* f = ourOverflow->FirstChild(); f;) { nsIFrame* next = f->GetNextSibling(); nsIFrame* pif = f->GetPrevInFlow(); if (!pif || pif->GetParent() != this) { MOZ_ASSERT(f->GetParent() == this); ourOverflow->RemoveFrame(f); items.AppendFrame(nullptr, f); if (!pif) { foundOwnPushedChild = true; } } f = next; } if (ourOverflow->IsEmpty()) { DestroyOverflowList(); ourOverflow = nullptr; } if (items.NotEmpty()) { PullItemsNextInFlow(items); } MergeSortedFrameLists(mFrames, items, GetContent()); } } // Push any child next-in-flows in our principal list to OverflowList. if (HasAnyStateBits(hasChildNifBit)) { nsFrameList framesToPush; nsIFrame* firstChild = mFrames.FirstChild(); // Note that we potentially modify our mFrames list as we go. for (auto* child = firstChild; child; child = child->GetNextSibling()) { if (auto* childNIF = child->GetNextInFlow()) { if (childNIF->GetParent() == this) { for (auto* c = child->GetNextSibling(); c; c = c->GetNextSibling()) { if (c == childNIF) { // child's next-in-flow is in our principal child list, push it. mFrames.RemoveFrame(childNIF); framesToPush.AppendFrame(nullptr, childNIF); break; } } } } } if (!framesToPush.IsEmpty()) { MergeSortedOverflow(framesToPush); } RemoveStateBits(hasChildNifBit); } // Pull up any first-in-flow children we might have pushed. if (HasAnyStateBits(didPushItemsBit)) { RemoveStateBits(didPushItemsBit); nsFrameList items; auto* nif = static_cast(GetNextInFlow()); DebugOnly nifNeedPushedItem = false; while (nif) { nsFrameList nifItems; for (nsIFrame* nifChild = nif->PrincipalChildList().FirstChild(); nifChild;) { nsIFrame* next = nifChild->GetNextSibling(); if (!nifChild->GetPrevInFlow()) { nif->StealFrame(nifChild); ReparentFrame(nifChild, nif, this); nifItems.AppendFrame(nullptr, nifChild); nifNeedPushedItem = false; } nifChild = next; } MergeSortedFrameLists(items, nifItems, GetContent()); if (!nif->HasAnyStateBits(didPushItemsBit)) { MOZ_ASSERT(!nifNeedPushedItem || mDidPushItemsBitMayLie, "The state bit stored in didPushItemsBit lied!"); break; } nifNeedPushedItem = true; for (nsIFrame* nifChild = nif->GetChildList(FrameChildListID::Overflow).FirstChild(); nifChild;) { nsIFrame* next = nifChild->GetNextSibling(); if (!nifChild->GetPrevInFlow()) { nif->StealFrame(nifChild); ReparentFrame(nifChild, nif, this); nifItems.AppendFrame(nullptr, nifChild); nifNeedPushedItem = false; } nifChild = next; } MergeSortedFrameLists(items, nifItems, GetContent()); nif->RemoveStateBits(didPushItemsBit); nif = static_cast(nif->GetNextInFlow()); MOZ_ASSERT(nif || !nifNeedPushedItem || mDidPushItemsBitMayLie, "The state bit stored in didPushItemsBit lied!"); } if (!items.IsEmpty()) { PullItemsNextInFlow(items); } MOZ_ASSERT( foundOwnPushedChild || !items.IsEmpty() || mDidPushItemsBitMayLie, "The state bit stored in didPushItemsBit lied!"); MergeSortedFrameLists(mFrames, items, GetContent()); } } void nsContainerFrame::NoteNewChildren(ChildListID aListID, const nsFrameList& aFrameList) { MOZ_ASSERT(aListID == FrameChildListID::Principal, "unexpected child list"); MOZ_ASSERT(IsFlexOrGridContainer(), "Only Flex / Grid containers can call this!"); mozilla::PresShell* presShell = PresShell(); const auto didPushItemsBit = IsFlexContainerFrame() ? NS_STATE_FLEX_DID_PUSH_ITEMS : NS_STATE_GRID_DID_PUSH_ITEMS; for (auto* pif = GetPrevInFlow(); pif; pif = pif->GetPrevInFlow()) { pif->AddStateBits(didPushItemsBit); presShell->FrameNeedsReflow(pif, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY); } } bool nsContainerFrame::MoveOverflowToChildList() { bool result = false; // Check for an overflow list with our prev-in-flow nsContainerFrame* prevInFlow = (nsContainerFrame*)GetPrevInFlow(); if (nullptr != prevInFlow) { AutoFrameListPtr prevOverflowFrames(PresContext(), prevInFlow->StealOverflowFrames()); if (prevOverflowFrames) { // Tables are special; they can have repeated header/footer // frames on mFrames at this point. NS_ASSERTION(mFrames.IsEmpty() || IsTableFrame(), "bad overflow list"); // When pushing and pulling frames we need to check for whether any // views need to be reparented. nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow, this); mFrames.AppendFrames(this, std::move(*prevOverflowFrames)); result = true; } } // It's also possible that we have an overflow list for ourselves. return DrainSelfOverflowList() || result; } void nsContainerFrame::MergeSortedOverflow(nsFrameList& aList) { if (aList.IsEmpty()) { return; } MOZ_ASSERT( !aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER), "this is the wrong list to put this child frame"); MOZ_ASSERT(aList.FirstChild()->GetParent() == this); nsFrameList* overflow = GetOverflowFrames(); if (overflow) { MergeSortedFrameLists(*overflow, aList, GetContent()); } else { SetOverflowFrames(std::move(aList)); } } void nsContainerFrame::MergeSortedExcessOverflowContainers(nsFrameList& aList) { if (aList.IsEmpty()) { return; } MOZ_ASSERT( aList.FirstChild()->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER), "this is the wrong list to put this child frame"); MOZ_ASSERT(aList.FirstChild()->GetParent() == this); if (nsFrameList* eoc = GetExcessOverflowContainers()) { MergeSortedFrameLists(*eoc, aList, GetContent()); } else { SetExcessOverflowContainers(std::move(aList)); } } nsIFrame* nsContainerFrame::GetFirstNonAnonBoxInSubtree(nsIFrame* aFrame) { while (aFrame) { // If aFrame isn't an anonymous container, or it's text or such, then it'll // do. if (!aFrame->Style()->IsAnonBox() || nsCSSAnonBoxes::IsNonElement(aFrame->Style()->GetPseudoType())) { break; } // Otherwise, descend to its first child and repeat. // SPECIAL CASE: if we're dealing with an anonymous table, then it might // be wrapping something non-anonymous in its caption or col-group lists // (instead of its principal child list), so we have to look there. // (Note: For anonymous tables that have a non-anon cell *and* a non-anon // column, we'll always return the column. This is fine; we're really just // looking for a handle to *anything* with a meaningful content node inside // the table, for use in DOM comparisons to things outside of the table.) if (MOZ_UNLIKELY(aFrame->IsTableWrapperFrame())) { nsIFrame* captionDescendant = GetFirstNonAnonBoxInSubtree( aFrame->GetChildList(FrameChildListID::Caption).FirstChild()); if (captionDescendant) { return captionDescendant; } } else if (MOZ_UNLIKELY(aFrame->IsTableFrame())) { nsIFrame* colgroupDescendant = GetFirstNonAnonBoxInSubtree( aFrame->GetChildList(FrameChildListID::ColGroup).FirstChild()); if (colgroupDescendant) { return colgroupDescendant; } } // USUAL CASE: Descend to the first child in principal list. aFrame = aFrame->PrincipalChildList().FirstChild(); } return aFrame; } /** * Is aFrame1 a prev-continuation of aFrame2? */ static bool IsPrevContinuationOf(nsIFrame* aFrame1, nsIFrame* aFrame2) { nsIFrame* prev = aFrame2; while ((prev = prev->GetPrevContinuation())) { if (prev == aFrame1) { return true; } } return false; } void nsContainerFrame::MergeSortedFrameLists(nsFrameList& aDest, nsFrameList& aSrc, nsIContent* aCommonAncestor) { // Returns a frame whose DOM node can be used for the purpose of ordering // aFrame among its sibling frames by DOM position. If aFrame is // non-anonymous, this just returns aFrame itself. Otherwise, this returns the // first non-anonymous descendant in aFrame's continuation chain. auto FrameForDOMPositionComparison = [](nsIFrame* aFrame) { if (!aFrame->Style()->IsAnonBox()) { // The usual case. return aFrame; } // Walk the continuation chain from the start, and return the first // non-anonymous descendant that we find. for (nsIFrame* f = aFrame->FirstContinuation(); f; f = f->GetNextContinuation()) { if (nsIFrame* nonAnonBox = GetFirstNonAnonBoxInSubtree(f)) { return nonAnonBox; } } MOZ_ASSERT_UNREACHABLE( "Why is there no non-anonymous descendants in the continuation chain?"); return aFrame; }; nsIFrame* dest = aDest.FirstChild(); for (nsIFrame* src = aSrc.FirstChild(); src;) { if (!dest) { aDest.AppendFrames(nullptr, std::move(aSrc)); break; } nsIContent* srcContent = FrameForDOMPositionComparison(src)->GetContent(); nsIContent* destContent = FrameForDOMPositionComparison(dest)->GetContent(); int32_t result = nsContentUtils::CompareTreePosition( srcContent, destContent, aCommonAncestor); if (MOZ_UNLIKELY(result == 0)) { // NOTE: we get here when comparing ::before/::after for the same element. if (MOZ_UNLIKELY(srcContent->IsGeneratedContentContainerForBefore())) { if (MOZ_LIKELY(!destContent->IsGeneratedContentContainerForBefore()) || ::IsPrevContinuationOf(src, dest)) { result = -1; } } else if (MOZ_UNLIKELY( srcContent->IsGeneratedContentContainerForAfter())) { if (MOZ_UNLIKELY(destContent->IsGeneratedContentContainerForAfter()) && ::IsPrevContinuationOf(src, dest)) { result = -1; } } else if (::IsPrevContinuationOf(src, dest)) { result = -1; } } if (result < 0) { // src should come before dest nsIFrame* next = src->GetNextSibling(); aSrc.RemoveFrame(src); aDest.InsertFrame(nullptr, dest->GetPrevSibling(), src); src = next; } else { dest = dest->GetNextSibling(); } } MOZ_ASSERT(aSrc.IsEmpty()); } bool nsContainerFrame::MoveInlineOverflowToChildList(nsIFrame* aLineContainer) { MOZ_ASSERT(aLineContainer, "Must have line container for moving inline overflows"); bool result = false; // Check for an overflow list with our prev-in-flow if (auto prevInFlow = static_cast(GetPrevInFlow())) { AutoFrameListPtr prevOverflowFrames(PresContext(), prevInFlow->StealOverflowFrames()); if (prevOverflowFrames) { // We may need to reparent floats from prev-in-flow to our line // container if the container has prev continuation. if (aLineContainer->GetPrevContinuation()) { ReparentFloatsForInlineChild(aLineContainer, prevOverflowFrames->FirstChild(), true); } // When pushing and pulling frames we need to check for whether // any views need to be reparented. nsContainerFrame::ReparentFrameViewList(*prevOverflowFrames, prevInFlow, this); // Prepend overflow frames to the list. mFrames.InsertFrames(this, nullptr, std::move(*prevOverflowFrames)); result = true; } } // It's also possible that we have overflow list for ourselves. return DrainSelfOverflowList() || result; } bool nsContainerFrame::DrainSelfOverflowList() { AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames()); if (overflowFrames) { mFrames.AppendFrames(nullptr, std::move(*overflowFrames)); return true; } return false; } bool nsContainerFrame::DrainAndMergeSelfOverflowList() { MOZ_ASSERT(IsFlexOrGridContainer(), "Only Flex / Grid containers can call this!"); // Unlike nsContainerFrame::DrainSelfOverflowList, flex or grid containers // need to merge these lists so that the resulting mFrames is in document // content order. // NOTE: nsContainerFrame::AppendFrames/InsertFrames calls this method and // there are also direct calls from the fctor (FindAppendPrevSibling). AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames()); if (overflowFrames) { MergeSortedFrameLists(mFrames, *overflowFrames, GetContent()); // We set a frame bit to push them again in Reflow() to avoid creating // multiple flex / grid items per flex / grid container fragment for the // same content. AddStateBits(IsFlexContainerFrame() ? NS_STATE_FLEX_HAS_CHILD_NIFS : NS_STATE_GRID_HAS_CHILD_NIFS); return true; } return false; } nsFrameList* nsContainerFrame::DrainExcessOverflowContainersList( ChildFrameMerger aMergeFunc) { nsFrameList* overflowContainers = GetOverflowContainers(); // Drain excess overflow containers from our prev-in-flow. if (auto* prev = static_cast(GetPrevInFlow())) { AutoFrameListPtr excessFrames(PresContext(), prev->StealExcessOverflowContainers()); if (excessFrames) { excessFrames->ApplySetParent(this); nsContainerFrame::ReparentFrameViewList(*excessFrames, prev, this); if (overflowContainers) { // The default merge function is AppendFrames, so we use excessFrames as // the destination and then assign the result to overflowContainers. aMergeFunc(*excessFrames, *overflowContainers, this); *overflowContainers = std::move(*excessFrames); } else { overflowContainers = SetOverflowContainers(std::move(*excessFrames)); } } } // Our own excess overflow containers from a previous reflow can still be // present if our next-in-flow hasn't been reflown yet. Move any children // from it that don't have a continuation in this frame to the // OverflowContainers list. AutoFrameListPtr selfExcessOCFrames(PresContext(), StealExcessOverflowContainers()); if (selfExcessOCFrames) { nsFrameList toMove; auto child = selfExcessOCFrames->FirstChild(); while (child) { auto next = child->GetNextSibling(); MOZ_ASSERT(child->GetPrevInFlow(), "ExcessOverflowContainers frames must be continuations"); if (child->GetPrevInFlow()->GetParent() != this) { selfExcessOCFrames->RemoveFrame(child); toMove.AppendFrame(nullptr, child); } child = next; } // If there's any remaining excess overflow containers, put them back. if (selfExcessOCFrames->NotEmpty()) { SetExcessOverflowContainers(std::move(*selfExcessOCFrames)); } if (toMove.NotEmpty()) { if (overflowContainers) { aMergeFunc(*overflowContainers, toMove, this); } else { overflowContainers = SetOverflowContainers(std::move(toMove)); } } } return overflowContainers; } nsIFrame* nsContainerFrame::GetNextInFlowChild( ContinuationTraversingState& aState, bool* aIsInOverflow) { nsContainerFrame*& nextInFlow = aState.mNextInFlow; while (nextInFlow) { // See if there is any frame in the container nsIFrame* frame = nextInFlow->mFrames.FirstChild(); if (frame) { if (aIsInOverflow) { *aIsInOverflow = false; } return frame; } // No frames in the principal list, try its overflow list nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames(); if (overflowFrames) { if (aIsInOverflow) { *aIsInOverflow = true; } return overflowFrames->FirstChild(); } nextInFlow = static_cast(nextInFlow->GetNextInFlow()); } return nullptr; } nsIFrame* nsContainerFrame::PullNextInFlowChild( ContinuationTraversingState& aState) { bool isInOverflow; nsIFrame* frame = GetNextInFlowChild(aState, &isInOverflow); if (frame) { nsContainerFrame* nextInFlow = aState.mNextInFlow; if (isInOverflow) { nsFrameList* overflowFrames = nextInFlow->GetOverflowFrames(); overflowFrames->RemoveFirstChild(); if (overflowFrames->IsEmpty()) { nextInFlow->DestroyOverflowList(); } } else { nextInFlow->mFrames.RemoveFirstChild(); } // Move the frame to the principal frame list of this container mFrames.AppendFrame(this, frame); // AppendFrame has reparented the frame, we need // to reparent the frame view then. nsContainerFrame::ReparentFrameView(frame, nextInFlow, this); } return frame; } /* static */ void nsContainerFrame::ReparentFloatsForInlineChild(nsIFrame* aOurLineContainer, nsIFrame* aFrame, bool aReparentSiblings) { // XXXbz this would be better if it took a nsFrameList or a frame // list slice.... NS_ASSERTION(aOurLineContainer->GetNextContinuation() || aOurLineContainer->GetPrevContinuation(), "Don't call this when we have no continuation, it's a waste"); if (!aFrame) { NS_ASSERTION(aReparentSiblings, "Why did we get called?"); return; } nsBlockFrame* frameBlock = nsLayoutUtils::GetFloatContainingBlock(aFrame); if (!frameBlock || frameBlock == aOurLineContainer) { return; } nsBlockFrame* ourBlock = do_QueryFrame(aOurLineContainer); NS_ASSERTION(ourBlock, "Not a block, but broke vertically?"); while (true) { ourBlock->ReparentFloats(aFrame, frameBlock, false); if (!aReparentSiblings) return; nsIFrame* next = aFrame->GetNextSibling(); if (!next) return; if (next->GetParent() == aFrame->GetParent()) { aFrame = next; continue; } // This is paranoid and will hardly ever get hit ... but we can't actually // trust that the frames in the sibling chain all have the same parent, // because lazy reparenting may be going on. If we find a different // parent we need to redo our analysis. ReparentFloatsForInlineChild(aOurLineContainer, next, aReparentSiblings); return; } } bool nsContainerFrame::ResolvedOrientationIsVertical() { StyleOrient orient = StyleDisplay()->mOrient; switch (orient) { case StyleOrient::Horizontal: return false; case StyleOrient::Vertical: return true; case StyleOrient::Inline: return GetWritingMode().IsVertical(); case StyleOrient::Block: return !GetWritingMode().IsVertical(); } MOZ_ASSERT_UNREACHABLE("unexpected -moz-orient value"); return false; } LogicalSize nsContainerFrame::ComputeSizeWithIntrinsicDimensions( gfxContext* aRenderingContext, WritingMode aWM, const IntrinsicSize& aIntrinsicSize, const AspectRatio& aAspectRatio, const LogicalSize& aCBSize, const LogicalSize& aMargin, const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { const nsStylePosition* stylePos = StylePosition(); const auto& styleISize = aSizeOverrides.mStyleISize ? *aSizeOverrides.mStyleISize : stylePos->ISize(aWM); const auto& styleBSize = aSizeOverrides.mStyleBSize ? *aSizeOverrides.mStyleBSize : stylePos->BSize(aWM); const auto& aspectRatio = aSizeOverrides.mAspectRatio ? *aSizeOverrides.mAspectRatio : aAspectRatio; auto* parentFrame = GetParent(); const bool isGridItem = IsGridItem(); const bool isFlexItem = IsFlexItem() && !parentFrame->HasAnyStateBits( NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX); // This variable only gets meaningfully set if isFlexItem is true. It // indicates which axis (in this frame's own WM) corresponds to its // flex container's main axis. LogicalAxis flexMainAxis = eLogicalAxisBlock; if (isFlexItem && nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)) { flexMainAxis = eLogicalAxisInline; } // Handle intrinsic sizes and their interaction with // {min-,max-,}{width,height} according to the rules in // https://www.w3.org/TR/CSS22/visudet.html#min-max-widths and // https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes // Note: throughout the following section of the function, I avoid // a * (b / c) because of its reduced accuracy relative to a * b / c // or (a * b) / c (which are equivalent). const bool isAutoOrMaxContentISize = styleISize.IsAuto() || styleISize.IsMaxContent(); const bool isAutoBSize = nsLayoutUtils::IsAutoBSize(styleBSize, aCBSize.BSize(aWM)); const auto boxSizingAdjust = stylePos->mBoxSizing == StyleBoxSizing::Border ? aBorderPadding : LogicalSize(aWM); const nscoord boxSizingToMarginEdgeISize = aMargin.ISize(aWM) + aBorderPadding.ISize(aWM) - boxSizingAdjust.ISize(aWM); nscoord iSize, minISize, maxISize, bSize, minBSize, maxBSize; enum class Stretch { // stretch to fill the CB (preserving intrinsic ratio) in the relevant axis StretchPreservingRatio, // stretch to fill the CB in the relevant axis Stretch, // no stretching in the relevant axis NoStretch, }; // just to avoid having to type these out everywhere: const auto eStretchPreservingRatio = Stretch::StretchPreservingRatio; const auto eStretch = Stretch::Stretch; const auto eNoStretch = Stretch::NoStretch; Stretch stretchI = eNoStretch; // stretch behavior in the inline axis Stretch stretchB = eNoStretch; // stretch behavior in the block axis const bool isOrthogonal = aWM.IsOrthogonalTo(parentFrame->GetWritingMode()); const bool isVertical = aWM.IsVertical(); const LogicalSize fallbackIntrinsicSize(aWM, kFallbackIntrinsicSize); const auto& isizeCoord = isVertical ? aIntrinsicSize.height : aIntrinsicSize.width; const bool hasIntrinsicISize = isizeCoord.isSome(); nscoord intrinsicISize = std::max(0, isizeCoord.valueOr(0)); const auto& bsizeCoord = isVertical ? aIntrinsicSize.width : aIntrinsicSize.height; const bool hasIntrinsicBSize = bsizeCoord.isSome(); nscoord intrinsicBSize = std::max(0, bsizeCoord.valueOr(0)); if (!isAutoOrMaxContentISize) { iSize = ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust, boxSizingToMarginEdgeISize, styleISize, aSizeOverrides, aFlags) .mISize; } else if (MOZ_UNLIKELY(isGridItem) && !parentFrame->IsMasonry(isOrthogonal ? eLogicalAxisBlock : eLogicalAxisInline)) { MOZ_ASSERT(!IsTrueOverflowContainer()); // 'auto' inline-size for grid-level box - apply 'stretch' as needed: auto cbSize = aCBSize.ISize(aWM); if (cbSize != NS_UNCONSTRAINEDSIZE) { if (!StyleMargin()->HasInlineAxisAuto(aWM)) { auto inlineAxisAlignment = isOrthogonal ? stylePos->UsedAlignSelf(GetParent()->Style())._0 : stylePos->UsedJustifySelf(GetParent()->Style())._0; if (inlineAxisAlignment == StyleAlignFlags::STRETCH) { stretchI = eStretch; } } if (stretchI != eNoStretch || aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) { iSize = std::max(nscoord(0), cbSize - aBorderPadding.ISize(aWM) - aMargin.ISize(aWM)); } } else { // Reset this flag to avoid applying the clamping below. aFlags -= ComputeSizeFlag::IClampMarginBoxMinSize; } } const auto& maxISizeCoord = stylePos->MaxISize(aWM); if (!maxISizeCoord.IsNone() && !(isFlexItem && flexMainAxis == eLogicalAxisInline)) { maxISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust, boxSizingToMarginEdgeISize, maxISizeCoord, aSizeOverrides, aFlags) .mISize; } else { maxISize = nscoord_MAX; } // NOTE: Flex items ignore their min & max sizing properties in their // flex container's main-axis. (Those properties get applied later in // the flexbox algorithm.) const auto& minISizeCoord = stylePos->MinISize(aWM); if (!minISizeCoord.IsAuto() && !(isFlexItem && flexMainAxis == eLogicalAxisInline)) { minISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust, boxSizingToMarginEdgeISize, minISizeCoord, aSizeOverrides, aFlags) .mISize; } else { // Treat "min-width: auto" as 0. // NOTE: Technically, "auto" is supposed to behave like "min-content" on // flex items. However, we don't need to worry about that here, because // flex items' min-sizes are intentionally ignored until the flex // container explicitly considers them during space distribution. minISize = 0; } if (!isAutoBSize) { bSize = nsLayoutUtils::ComputeBSizeValue(aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM), styleBSize.AsLengthPercentage()); } else if (MOZ_UNLIKELY(isGridItem) && !parentFrame->IsMasonry(isOrthogonal ? eLogicalAxisInline : eLogicalAxisBlock)) { MOZ_ASSERT(!IsTrueOverflowContainer()); // 'auto' block-size for grid-level box - apply 'stretch' as needed: auto cbSize = aCBSize.BSize(aWM); if (cbSize != NS_UNCONSTRAINEDSIZE) { if (!StyleMargin()->HasBlockAxisAuto(aWM)) { auto blockAxisAlignment = !isOrthogonal ? stylePos->UsedAlignSelf(GetParent()->Style())._0 : stylePos->UsedJustifySelf(GetParent()->Style())._0; if (blockAxisAlignment == StyleAlignFlags::STRETCH) { stretchB = eStretch; } } if (stretchB != eNoStretch || aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) { bSize = std::max(nscoord(0), cbSize - aBorderPadding.BSize(aWM) - aMargin.BSize(aWM)); } } else { // Reset this flag to avoid applying the clamping below. aFlags -= ComputeSizeFlag::BClampMarginBoxMinSize; } } const auto& maxBSizeCoord = stylePos->MaxBSize(aWM); if (!nsLayoutUtils::IsAutoBSize(maxBSizeCoord, aCBSize.BSize(aWM)) && !(isFlexItem && flexMainAxis == eLogicalAxisBlock)) { maxBSize = nsLayoutUtils::ComputeBSizeValue( aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM), maxBSizeCoord.AsLengthPercentage()); } else { maxBSize = nscoord_MAX; } const auto& minBSizeCoord = stylePos->MinBSize(aWM); if (!nsLayoutUtils::IsAutoBSize(minBSizeCoord, aCBSize.BSize(aWM)) && !(isFlexItem && flexMainAxis == eLogicalAxisBlock)) { minBSize = nsLayoutUtils::ComputeBSizeValue( aCBSize.BSize(aWM), boxSizingAdjust.BSize(aWM), minBSizeCoord.AsLengthPercentage()); } else { minBSize = 0; } NS_ASSERTION(aCBSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE, "Our containing block must not have unconstrained inline-size!"); // Now calculate the used values for iSize and bSize: if (isAutoOrMaxContentISize) { if (isAutoBSize) { // 'auto' iSize, 'auto' bSize // Get tentative values - CSS 2.1 sections 10.3.2 and 10.6.2: nscoord tentISize, tentBSize; if (hasIntrinsicISize) { tentISize = intrinsicISize; } else if (hasIntrinsicBSize && aspectRatio) { tentISize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisInline, aWM, intrinsicBSize, boxSizingAdjust); } else if (aspectRatio) { tentISize = aCBSize.ISize(aWM) - boxSizingToMarginEdgeISize; // XXX scrollbar? if (tentISize < 0) { tentISize = 0; } } else { tentISize = fallbackIntrinsicSize.ISize(aWM); } // If we need to clamp the inline size to fit the CB, we use the 'stretch' // or 'normal' codepath. We use the ratio-preserving 'normal' codepath // unless we have 'stretch' in the other axis. if (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize) && stretchI != eStretch && tentISize > iSize) { stretchI = (stretchB == eStretch ? eStretch : eStretchPreservingRatio); } if (hasIntrinsicBSize) { tentBSize = intrinsicBSize; } else if (aspectRatio) { tentBSize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisBlock, aWM, tentISize, boxSizingAdjust); } else { tentBSize = fallbackIntrinsicSize.BSize(aWM); } // (ditto the comment about clamping the inline size above) if (aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize) && stretchB != eStretch && tentBSize > bSize) { stretchB = (stretchI == eStretch ? eStretch : eStretchPreservingRatio); } if (stretchI == eStretch) { tentISize = iSize; // * / 'stretch' if (stretchB == eStretch) { tentBSize = bSize; // 'stretch' / 'stretch' } else if (stretchB == eStretchPreservingRatio && aspectRatio) { // 'normal' / 'stretch' tentBSize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisBlock, aWM, iSize, boxSizingAdjust); } } else if (stretchB == eStretch) { tentBSize = bSize; // 'stretch' / * (except 'stretch') if (stretchI == eStretchPreservingRatio && aspectRatio) { // 'stretch' / 'normal' tentISize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust); } } else if (stretchI == eStretchPreservingRatio && aspectRatio) { tentISize = iSize; // * (except 'stretch') / 'normal' tentBSize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisBlock, aWM, iSize, boxSizingAdjust); if (stretchB == eStretchPreservingRatio && tentBSize > bSize) { // Stretch within the CB size with preserved intrinsic ratio. tentBSize = bSize; // 'normal' / 'normal' tentISize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust); } } else if (stretchB == eStretchPreservingRatio && aspectRatio) { tentBSize = bSize; // 'normal' / * (except 'normal' and 'stretch') tentISize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust); } // ComputeAutoSizeWithIntrinsicDimensions preserves the ratio when // applying the min/max-size. We don't want that when we have 'stretch' // in either axis because tentISize/tentBSize is likely not according to // ratio now. if (aspectRatio && stretchI != eStretch && stretchB != eStretch) { nsSize autoSize = nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions( minISize, minBSize, maxISize, maxBSize, tentISize, tentBSize); // The nsSize that ComputeAutoSizeWithIntrinsicDimensions returns will // actually contain logical values if the parameters passed to it were // logical coordinates, so we do NOT perform a physical-to-logical // conversion here, but just assign the fields directly to our result. iSize = autoSize.width; bSize = autoSize.height; } else { // Not honoring an intrinsic ratio: clamp the dimensions independently. iSize = NS_CSS_MINMAX(tentISize, minISize, maxISize); bSize = NS_CSS_MINMAX(tentBSize, minBSize, maxBSize); } } else { // 'auto' iSize, non-'auto' bSize bSize = NS_CSS_MINMAX(bSize, minBSize, maxBSize); if (stretchI != eStretch) { if (aspectRatio) { iSize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisInline, aWM, bSize, boxSizingAdjust); } else if (hasIntrinsicISize) { if (!(aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize) && intrinsicISize > iSize)) { iSize = intrinsicISize; } // else - leave iSize as is to fill the CB } else { iSize = fallbackIntrinsicSize.ISize(aWM); } } // else - leave iSize as is to fill the CB iSize = NS_CSS_MINMAX(iSize, minISize, maxISize); } } else { if (isAutoBSize) { // non-'auto' iSize, 'auto' bSize iSize = NS_CSS_MINMAX(iSize, minISize, maxISize); if (stretchB != eStretch) { if (aspectRatio) { bSize = aspectRatio.ComputeRatioDependentSize( LogicalAxis::eLogicalAxisBlock, aWM, iSize, boxSizingAdjust); } else if (hasIntrinsicBSize) { if (!(aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize) && intrinsicBSize > bSize)) { bSize = intrinsicBSize; } // else - leave bSize as is to fill the CB } else { bSize = fallbackIntrinsicSize.BSize(aWM); } } // else - leave bSize as is to fill the CB bSize = NS_CSS_MINMAX(bSize, minBSize, maxBSize); } else { // non-'auto' iSize, non-'auto' bSize iSize = NS_CSS_MINMAX(iSize, minISize, maxISize); bSize = NS_CSS_MINMAX(bSize, minBSize, maxBSize); } } return LogicalSize(aWM, iSize, bSize); } nsRect nsContainerFrame::ComputeSimpleTightBounds( DrawTarget* aDrawTarget) const { if (StyleOutline()->ShouldPaintOutline() || StyleBorder()->HasBorder() || !StyleBackground()->IsTransparent(this) || StyleDisplay()->HasAppearance()) { // Not necessarily tight, due to clipping, negative // outline-offset, and lots of other issues, but that's OK return InkOverflowRect(); } nsRect r(0, 0, 0, 0); for (const auto& childLists : ChildLists()) { for (nsIFrame* child : childLists.mList) { r.UnionRect( r, child->ComputeTightBounds(aDrawTarget) + child->GetPosition()); } } return r; } void nsContainerFrame::PushDirtyBitToAbsoluteFrames() { if (!HasAnyStateBits(NS_FRAME_IS_DIRTY)) { return; // No dirty bit to push. } if (!HasAbsolutelyPositionedChildren()) { return; // No absolute children to push to. } GetAbsoluteContainingBlock()->MarkAllFramesDirty(); } // Define the MAX_FRAME_DEPTH to be the ContentSink's MAX_REFLOW_DEPTH plus // 4 for the frames above the document's frames: // the Viewport, GFXScroll, ScrollPort, and Canvas #define MAX_FRAME_DEPTH (MAX_REFLOW_DEPTH + 4) bool nsContainerFrame::IsFrameTreeTooDeep(const ReflowInput& aReflowInput, ReflowOutput& aMetrics, nsReflowStatus& aStatus) { if (aReflowInput.mReflowDepth > MAX_FRAME_DEPTH) { NS_WARNING("frame tree too deep; setting zero size and returning"); AddStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE); ClearOverflowRects(); aMetrics.ClearSize(); aMetrics.SetBlockStartAscent(0); aMetrics.mCarriedOutBEndMargin.Zero(); aMetrics.mOverflowAreas.Clear(); aStatus.Reset(); if (GetNextInFlow()) { // Reflow depth might vary between reflows, so we might have // successfully reflowed and split this frame before. If so, we // shouldn't delete its continuations. aStatus.SetIncomplete(); } return true; } RemoveStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE); return false; } bool nsContainerFrame::ShouldAvoidBreakInside( const ReflowInput& aReflowInput) const { MOZ_ASSERT(this == aReflowInput.mFrame, "Caller should pass a ReflowInput for this frame!"); const auto* disp = StyleDisplay(); const bool mayAvoidBreak = [&] { switch (disp->mBreakInside) { case StyleBreakWithin::Auto: return false; case StyleBreakWithin::Avoid: return true; case StyleBreakWithin::AvoidPage: return aReflowInput.mBreakType == ReflowInput::BreakType::Page; case StyleBreakWithin::AvoidColumn: return aReflowInput.mBreakType == ReflowInput::BreakType::Column; } MOZ_ASSERT_UNREACHABLE("Unknown break-inside value"); return false; }(); if (!mayAvoidBreak) { return false; } if (aReflowInput.mFlags.mIsTopOfPage) { return false; } if (IsAbsolutelyPositioned(disp)) { return false; } if (GetPrevInFlow()) { return false; } return true; } void nsContainerFrame::ConsiderChildOverflow(OverflowAreas& aOverflowAreas, nsIFrame* aChildFrame) { if (StyleDisplay()->IsContainLayout() && SupportsContainLayoutAndPaint()) { // If we have layout containment and are not a non-atomic, inline-level // principal box, we should only consider our child's ink overflow, // leaving the scrollable regions of the parent unaffected. // Note: scrollable overflow is a subset of ink overflow, // so this has the same affect as unioning the child's ink and // scrollable overflow with the parent's ink overflow. const OverflowAreas childOverflows(aChildFrame->InkOverflowRect(), nsRect()); aOverflowAreas.UnionWith(childOverflows + aChildFrame->GetPosition()); } else { aOverflowAreas.UnionWith( aChildFrame->GetActualAndNormalOverflowAreasRelativeToParent()); } } StyleAlignFlags nsContainerFrame::CSSAlignmentForAbsPosChild( const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const { MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(), "This method should only be called for abspos children"); NS_ERROR( "Child classes that use css box alignment for abspos children " "should provide their own implementation of this method!"); // In the unexpected/unlikely event that this implementation gets invoked, // just use "start" alignment. return StyleAlignFlags::START; } nsOverflowContinuationTracker::nsOverflowContinuationTracker( nsContainerFrame* aFrame, bool aWalkOOFFrames, bool aSkipOverflowContainerChildren) : mOverflowContList(nullptr), mPrevOverflowCont(nullptr), mSentry(nullptr), mParent(aFrame), mSkipOverflowContainerChildren(aSkipOverflowContainerChildren), mWalkOOFFrames(aWalkOOFFrames) { MOZ_ASSERT(aFrame, "null frame pointer"); SetupOverflowContList(); } void nsOverflowContinuationTracker::SetupOverflowContList() { MOZ_ASSERT(mParent, "null frame pointer"); MOZ_ASSERT(!mOverflowContList, "already have list"); nsContainerFrame* nif = static_cast(mParent->GetNextInFlow()); if (nif) { mOverflowContList = nif->GetOverflowContainers(); if (mOverflowContList) { mParent = nif; SetUpListWalker(); } } if (!mOverflowContList) { mOverflowContList = mParent->GetExcessOverflowContainers(); if (mOverflowContList) { SetUpListWalker(); } } } /** * Helper function to walk past overflow continuations whose prev-in-flow * isn't a normal child and to set mSentry and mPrevOverflowCont correctly. */ void nsOverflowContinuationTracker::SetUpListWalker() { NS_ASSERTION(!mSentry && !mPrevOverflowCont, "forgot to reset mSentry or mPrevOverflowCont"); if (mOverflowContList) { nsIFrame* cur = mOverflowContList->FirstChild(); if (mSkipOverflowContainerChildren) { while (cur && cur->GetPrevInFlow()->HasAnyStateBits( NS_FRAME_IS_OVERFLOW_CONTAINER)) { mPrevOverflowCont = cur; cur = cur->GetNextSibling(); } while (cur && (cur->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) != mWalkOOFFrames)) { mPrevOverflowCont = cur; cur = cur->GetNextSibling(); } } if (cur) { mSentry = cur->GetPrevInFlow(); } } } /** * Helper function to step forward through the overflow continuations list. * Sets mSentry and mPrevOverflowCont, skipping over OOF or non-OOF frames * as appropriate. May only be called when we have already set up an * mOverflowContList; mOverflowContList cannot be null. */ void nsOverflowContinuationTracker::StepForward() { MOZ_ASSERT(mOverflowContList, "null list"); // Step forward if (mPrevOverflowCont) { mPrevOverflowCont = mPrevOverflowCont->GetNextSibling(); } else { mPrevOverflowCont = mOverflowContList->FirstChild(); } // Skip over oof or non-oof frames as appropriate if (mSkipOverflowContainerChildren) { nsIFrame* cur = mPrevOverflowCont->GetNextSibling(); while (cur && (cur->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) != mWalkOOFFrames)) { mPrevOverflowCont = cur; cur = cur->GetNextSibling(); } } // Set up the sentry mSentry = (mPrevOverflowCont->GetNextSibling()) ? mPrevOverflowCont->GetNextSibling()->GetPrevInFlow() : nullptr; } nsresult nsOverflowContinuationTracker::Insert(nsIFrame* aOverflowCont, nsReflowStatus& aReflowStatus) { MOZ_ASSERT(aOverflowCont, "null frame pointer"); MOZ_ASSERT(!mSkipOverflowContainerChildren || mWalkOOFFrames == aOverflowCont->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), "shouldn't insert frame that doesn't match walker type"); MOZ_ASSERT(aOverflowCont->GetPrevInFlow(), "overflow containers must have a prev-in-flow"); nsresult rv = NS_OK; bool reparented = false; nsPresContext* presContext = aOverflowCont->PresContext(); bool addToList = !mSentry || aOverflowCont != mSentry->GetNextInFlow(); // If we have a list and aOverflowCont is already in it then don't try to // add it again. if (addToList && aOverflowCont->GetParent() == mParent && aOverflowCont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) && mOverflowContList && mOverflowContList->ContainsFrame(aOverflowCont)) { addToList = false; mPrevOverflowCont = aOverflowCont->GetPrevSibling(); } if (addToList) { if (aOverflowCont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { // aOverflowCont is in some other overflow container list, // steal it first NS_ASSERTION(!(mOverflowContList && mOverflowContList->ContainsFrame(aOverflowCont)), "overflow containers out of order"); aOverflowCont->GetParent()->StealFrame(aOverflowCont); } else { aOverflowCont->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); } if (!mOverflowContList) { // Note: We don't use SetExcessOverflowContainers() since it requires // setting a non-empty list. It's OK to manually set an empty list to // ExcessOverflowContainersProperty() because we are going to insert // aOverflowCont to mOverflowContList below, which guarantees an nonempty // list in ExcessOverflowContainersProperty(). mOverflowContList = new (presContext->PresShell()) nsFrameList(); mParent->SetProperty(nsContainerFrame::ExcessOverflowContainersProperty(), mOverflowContList); SetUpListWalker(); } if (aOverflowCont->GetParent() != mParent) { nsContainerFrame::ReparentFrameView(aOverflowCont, aOverflowCont->GetParent(), mParent); reparented = true; } // If aOverflowCont has a prev/next-in-flow that might be in // mOverflowContList we need to find it and insert after/before it to // maintain the order amongst next-in-flows in this list. nsIFrame* pif = aOverflowCont->GetPrevInFlow(); nsIFrame* nif = aOverflowCont->GetNextInFlow(); if ((pif && pif->GetParent() == mParent && pif != mPrevOverflowCont) || (nif && nif->GetParent() == mParent && mPrevOverflowCont)) { for (nsIFrame* f : *mOverflowContList) { if (f == pif) { mPrevOverflowCont = pif; break; } if (f == nif) { mPrevOverflowCont = f->GetPrevSibling(); break; } } } mOverflowContList->InsertFrame(mParent, mPrevOverflowCont, aOverflowCont); aReflowStatus.SetNextInFlowNeedsReflow(); } // If we need to reflow it, mark it dirty if (aReflowStatus.NextInFlowNeedsReflow()) { aOverflowCont->MarkSubtreeDirty(); } // It's in our list, just step forward StepForward(); NS_ASSERTION(mPrevOverflowCont == aOverflowCont || (mSkipOverflowContainerChildren && mPrevOverflowCont->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) != aOverflowCont->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)), "OverflowContTracker in unexpected state"); if (addToList) { // Convert all non-overflow-container next-in-flows of aOverflowCont // into overflow containers and move them to our overflow // tracker. This preserves the invariant that the next-in-flows // of an overflow container are also overflow containers. nsIFrame* f = aOverflowCont->GetNextInFlow(); if (f && (!f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) || (!reparented && f->GetParent() == mParent) || (reparented && f->GetParent() != mParent))) { if (!f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { f->GetParent()->StealFrame(f); } Insert(f, aReflowStatus); } } return rv; } void nsOverflowContinuationTracker::BeginFinish(nsIFrame* aChild) { MOZ_ASSERT(aChild, "null ptr"); MOZ_ASSERT(aChild->GetNextInFlow(), "supposed to call Finish *before* deleting next-in-flow!"); for (nsIFrame* f = aChild; f; f = f->GetNextInFlow()) { // We'll update these in EndFinish after the next-in-flows are gone. if (f == mPrevOverflowCont) { mSentry = nullptr; mPrevOverflowCont = nullptr; break; } if (f == mSentry) { mSentry = nullptr; break; } } } void nsOverflowContinuationTracker::EndFinish(nsIFrame* aChild) { if (!mOverflowContList) { return; } // Forget mOverflowContList if it was deleted. nsFrameList* eoc = mParent->GetExcessOverflowContainers(); if (eoc != mOverflowContList) { nsFrameList* oc = mParent->GetOverflowContainers(); if (oc != mOverflowContList) { // mOverflowContList was deleted mPrevOverflowCont = nullptr; mSentry = nullptr; mParent = aChild->GetParent(); mOverflowContList = nullptr; SetupOverflowContList(); return; } } // The list survived, update mSentry if needed. if (!mSentry) { if (!mPrevOverflowCont) { SetUpListWalker(); } else { mozilla::AutoRestore saved(mPrevOverflowCont); // step backward to make StepForward() use our current mPrevOverflowCont mPrevOverflowCont = mPrevOverflowCont->GetPrevSibling(); StepForward(); } } } ///////////////////////////////////////////////////////////////////////////// // Debugging #ifdef DEBUG void nsContainerFrame::SanityCheckChildListsBeforeReflow() const { MOZ_ASSERT(IsFlexOrGridContainer(), "Only Flex / Grid containers can call this!"); const auto didPushItemsBit = IsFlexContainerFrame() ? NS_STATE_FLEX_DID_PUSH_ITEMS : NS_STATE_GRID_DID_PUSH_ITEMS; ChildListIDs absLists = {FrameChildListID::Absolute, FrameChildListID::Fixed, FrameChildListID::OverflowContainers, FrameChildListID::ExcessOverflowContainers}; ChildListIDs itemLists = {FrameChildListID::Principal, FrameChildListID::Overflow}; for (const nsIFrame* f = this; f; f = f->GetNextInFlow()) { MOZ_ASSERT(!f->HasAnyStateBits(didPushItemsBit), "At start of reflow, we should've pulled items back from all " "NIFs and cleared the state bit stored in didPushItemsBit in " "the process."); for (const auto& [list, listID] : f->ChildLists()) { if (!itemLists.contains(listID)) { MOZ_ASSERT( absLists.contains(listID) || listID == FrameChildListID::Backdrop, "unexpected non-empty child list"); continue; } for (const auto* child : list) { MOZ_ASSERT(f == this || child->GetPrevInFlow(), "all pushed items must be pulled up before reflow"); } } } // If we have a prev-in-flow, each of its children's next-in-flow // should be one of our children or be null. const auto* pif = static_cast(GetPrevInFlow()); if (pif) { const nsFrameList* oc = GetOverflowContainers(); const nsFrameList* eoc = GetExcessOverflowContainers(); const nsFrameList* pifEOC = pif->GetExcessOverflowContainers(); for (const nsIFrame* child : pif->PrincipalChildList()) { const nsIFrame* childNIF = child->GetNextInFlow(); MOZ_ASSERT(!childNIF || mFrames.ContainsFrame(childNIF) || (pifEOC && pifEOC->ContainsFrame(childNIF)) || (oc && oc->ContainsFrame(childNIF)) || (eoc && eoc->ContainsFrame(childNIF))); } } } void nsContainerFrame::SetDidPushItemsBitIfNeeded(ChildListID aListID, nsIFrame* aOldFrame) { MOZ_ASSERT(IsFlexOrGridContainer(), "Only Flex / Grid containers can call this!"); // Note that FrameChildListID::Principal doesn't mean aOldFrame must be on // that list. It can also be on FrameChildListID::Overflow, in which case it // might be a pushed item, and if it's the only pushed item our DID_PUSH_ITEMS // bit will lie. if (aListID == FrameChildListID::Principal && !aOldFrame->GetPrevInFlow()) { // Since the bit may lie, set the mDidPushItemsBitMayLie value to true for // ourself and for all our prev-in-flows. nsContainerFrame* frameThatMayLie = this; do { frameThatMayLie->mDidPushItemsBitMayLie = true; frameThatMayLie = static_cast(frameThatMayLie->GetPrevInFlow()); } while (frameThatMayLie); } } #endif #ifdef DEBUG_FRAME_DUMP void nsContainerFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const { nsCString str; ListGeneric(str, aPrefix, aFlags); ExtraContainerFrameInfo(str); // Output the frame name and various fields. fprintf_stderr(out, "%s <\n", str.get()); const nsCString pfx = nsCString(aPrefix) + " "_ns; // Output principal child list separately since we want to omit its // name and address. for (nsIFrame* kid : PrincipalChildList()) { kid->List(out, pfx.get(), aFlags); } // Output rest of the child lists. const ChildListIDs skippedListIDs = {FrameChildListID::Principal}; ListChildLists(out, pfx.get(), aFlags, skippedListIDs); fprintf_stderr(out, "%s>\n", aPrefix); } void nsContainerFrame::ListWithMatchedRules(FILE* out, const char* aPrefix) const { fprintf_stderr(out, "%s%s\n", aPrefix, ListTag().get()); nsCString rulePrefix; rulePrefix += aPrefix; rulePrefix += " "; ListMatchedRules(out, rulePrefix.get()); nsCString childPrefix; childPrefix += aPrefix; childPrefix += " "; for (const auto& childList : ChildLists()) { for (const nsIFrame* kid : childList.mList) { kid->ListWithMatchedRules(out, childPrefix.get()); } } } void nsContainerFrame::ListChildLists(FILE* aOut, const char* aPrefix, ListFlags aFlags, ChildListIDs aSkippedListIDs) const { const nsCString nestedPfx = nsCString(aPrefix) + " "_ns; for (const auto& [list, listID] : ChildLists()) { if (aSkippedListIDs.contains(listID)) { continue; } // Use nsPrintfCString so that %p don't output prefix "0x". This is // consistent with nsIFrame::ListTag(). const nsPrintfCString str("%s%s@%p <\n", aPrefix, ChildListName(listID), &GetChildList(listID)); fprintf_stderr(aOut, "%s", str.get()); for (nsIFrame* kid : list) { // Verify the child frame's parent frame pointer is correct. NS_ASSERTION(kid->GetParent() == this, "Bad parent frame pointer!"); kid->List(aOut, nestedPfx.get(), aFlags); } fprintf_stderr(aOut, "%s>\n", aPrefix); } } void nsContainerFrame::ExtraContainerFrameInfo(nsACString& aTo) const { (void)aTo; } #endif