/* -*- 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 */
using mozilla::gfx::ColorPattern; using mozilla::gfx::DeviceColor; using mozilla::gfx::DrawTarget; using mozilla::gfx::Rect; using mozilla::gfx::sRGBColor; using mozilla::gfx::ToDeviceColor;
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);
} elseif (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<nsPlaceholderFrame*>(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");
}
}
// 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). constbool 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::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)) { // Note: Similar to nsFrameList::DestroyFrames(), we remove the frames in // reverse order to avoid unnecessary updates to the first-continuation and // first-in-flow cache. If we delete them from front to back, updating the // cache has a O(n^2) time complexity.
nsIFrame* frame = frameList->RemoveLastChild(); 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
}
// 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());
}
}
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::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 final : public nsPaintedDisplayItem { public: /** * @param aSelectionValue nsISelectionController::getDisplaySelection.
*/
nsDisplaySelectionOverlay(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
int16_t aSelectionValue)
: nsPaintedDisplayItem(aBuilder, aFrame),
mSelectionValue(aSelectionValue) {
MOZ_COUNT_CTOR(nsDisplaySelectionOverlay);
}
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);
}
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<SelectionDetails> 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;
}
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;
}
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;
}
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);
}
}
// MinSize has a priority over MaxSize if (devMinSize.width > devMaxSize.width) {
devMaxSize.width = devMinSize.width;
} if (devMinSize.height > devMaxSize.height) {
devMaxSize.height = devMinSize.height;
}
// 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;
}
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(); const IntrinsicSizeInput input(
aRenderingContext, Some(aCBSize.ConvertTo(GetWritingMode(), aWM)),
Nothing()); 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(input);
} 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(input); if (min > aCBSize.ISize(aWM)) {
min = aCBSize.ISize(aWM);
} if (min > result.ISize(aWM)) {
result.ISize(aWM) = min;
}
}
} return result;
}
// 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 (constauto& [list, listID] : aFrame->ChildLists()) { 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!");
}
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);
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);
}
}
// 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");
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);
}
}
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;
}
// 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))));
// 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);
} elseif (!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, /* aAsIfScrolled = */ false);
}
}
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());
} returntrue;
} returnfalse;
}
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;
}
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);
}
}
}
/* * Create a next-in-flow for aFrame. Will return the newly created * frame <b>if and only if</b> 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);
/** * 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<nsIFrame*, 8> 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);
// Add the frames to our overflow list (let our next in flow drain // our overflow list when it is ready)
SetOverflowFrames(mFrames.TakeFramesAfter(aPrevSibling));
}
if (aPushedItems.IsEmpty() && aIncompleteItems.IsEmpty() &&
aOverflowIncompleteItems.IsEmpty()) { returnfalse;
}
// 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);
} elseif (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);
}
}
}
}
} elseif (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<nsContainerFrame*> lastParent = this; auto* nif = static_cast<nsContainerFrame*>(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<nsContainerFrame*>(GetNextInFlow());
nsFrameList* oc = nif ? nif->GetOverflowContainers() : nullptr; if (oc) {
ReparentFrames(overflowIncompleteList, this, nif);
MergeSortedFrameLists(*oc, overflowIncompleteList, GetContent());
} else {
MergeSortedExcessOverflowContainers(overflowIncompleteList);
}
} returntrue;
}
// 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.
auto* prevInFlow = static_cast<nsContainerFrame*>(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<nsContainerFrame*>(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<bool> 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<nsContainerFrame*>(GetNextInFlow());
DebugOnly<bool> 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;
nif->RemoveStateBits(didPushItemsBit);
nif = static_cast<nsContainerFrame*>(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());
}
}
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;
}
} elseif (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?
*/ staticbool IsPrevContinuationOf(nsIFrame* aFrame1, nsIFrame* aFrame2) {
nsIFrame* prev = aFrame2; while ((prev = prev->GetPrevContinuation())) { if (prev == aFrame1) { returntrue;
}
} returnfalse;
}
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<TreeKind::Flat>(
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;
}
} elseif (MOZ_UNLIKELY(
srcContent->IsGeneratedContentContainerForAfter())) { if (MOZ_UNLIKELY(destContent->IsGeneratedContentContainerForAfter()) &&
::IsPrevContinuationOf(src, dest)) {
result = -1;
}
} elseif (::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<nsContainerFrame*>(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;
}
// 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); returntrue;
} returnfalse;
}
// Drain excess overflow containers from our prev-in-flow. if (auto* prev = static_cast<nsContainerFrame*>(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<nsContainerFrame*>(nextInFlow->GetNextInFlow());
} return nullptr;
}
// 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* 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: returnfalse; case StyleOrient::Vertical: returntrue; case StyleOrient::Inline: return GetWritingMode().IsVertical(); case StyleOrient::Block: return !GetWritingMode().IsVertical();
}
MOZ_ASSERT_UNREACHABLE("unexpected -moz-orient value"); returnfalse;
}
// TODO(dholbert): if styleBSize is 'stretch' here, we should probably // resolve it like we do in nsIFrame::ComputeSize. See bug 1937275. constauto& styleBSize = aSizeOverrides.mStyleBSize
? *aSizeOverrides.mStyleBSize
: stylePos->BSize(aWM); constauto& aspectRatio =
aSizeOverrides.mAspectRatio ? *aSizeOverrides.mAspectRatio : aAspectRatio;
// flexItemMainAxis is set if this frame is a flex item in a modern flexbox // layout. It indicates which logical axis (in this frame's own WM) // corresponds to its flex container's main axis.
Maybe<LogicalAxis> flexItemMainAxis; if (IsFlexItem() && !parentFrame->HasAnyStateBits(
NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX)) {
flexItemMainAxis = Some(nsFlexContainerFrame::IsItemInlineAxisMainAxis(this)
? LogicalAxis::Inline
: LogicalAxis::Block);
}
// 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).
nscoord iSize, minISize, maxISize, bSize, minBSize, maxBSize; enumclass FillCB { // No stretching or clamping in the relevant axis.
No, // Stretch to fill the containing block size in the relevant axis while // preserving the aspect-ratio. If both axes are stretched, the aspect-ratio // will be disregarded.
Stretch, // Clamp to fill the containing block size in the relevant axis while // preserving the aspect-ratio. If both axes are clamped, the aspect-ratio // is respected, and the dimensions do not overflow the containing block // size.
Clamp,
};
FillCB inlineFillCB = FillCB::No; // fill CB behavior in the inline axis
FillCB blockFillCB = FillCB::No; // fill CB behavior in the block axis
Maybe<nscoord> iSizeToFillCB;
Maybe<nscoord> bSizeToFillCB; if (!isAutoOrMaxContentISize) {
iSize = ComputeISizeValue(aRenderingContext, aWM, aCBSize, boxSizingAdjust,
boxSizingToMarginEdgeISize, styleISize,
styleBSize, aspectRatio, aFlags)
.mISize;
} elseif (MOZ_UNLIKELY(isGridItem) &&
!parentFrame->IsMasonry(isOrthogonal ? LogicalAxis::Block
: LogicalAxis::Inline)) {
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) {
inlineFillCB = FillCB::Stretch;
}
} if (inlineFillCB != FillCB::No ||
aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
iSizeToFillCB.emplace(std::max(
0, cbSize - aBorderPadding.ISize(aWM) - aMargin.ISize(aWM)));
}
} else { // Reset this flag to avoid applying the clamping below.
aFlags -= ComputeSizeFlag::IClampMarginBoxMinSize;
}
}
// Flex items ignore their min & max sizing properties in their flex // container's main-axis. (Those properties get applied later in the flexbox // algorithm.) constbool isFlexItemInlineAxisMainAxis =
flexItemMainAxis && *flexItemMainAxis == LogicalAxis::Inline; constauto& maxISizeCoord = stylePos->MaxISize(aWM); if (!maxISizeCoord.IsNone() && !isFlexItemInlineAxisMainAxis) {
maxISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
boxSizingAdjust, boxSizingToMarginEdgeISize,
maxISizeCoord, styleBSize, aspectRatio, aFlags)
.mISize;
} else {
maxISize = nscoord_MAX;
}
constauto& minISizeCoord = stylePos->MinISize(aWM); if (!minISizeCoord.IsAuto() && !isFlexItemInlineAxisMainAxis) {
minISize = ComputeISizeValue(aRenderingContext, aWM, aCBSize,
boxSizingAdjust, boxSizingToMarginEdgeISize,
minISizeCoord, styleBSize, aspectRatio, 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::ComputeBSizeValueHandlingStretch(
aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM),
boxSizingAdjust.BSize(aWM), styleBSize);
} elseif (MOZ_UNLIKELY(isGridItem) &&
!parentFrame->IsMasonry(isOrthogonal ? LogicalAxis::Inline
: LogicalAxis::Block)) {
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) {
blockFillCB = FillCB::Stretch;
}
} if (blockFillCB != FillCB::No ||
aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) {
bSizeToFillCB.emplace(std::max(
0, cbSize - aBorderPadding.BSize(aWM) - aMargin.BSize(aWM)));
}
} else { // Reset this flag to avoid applying the clamping below.
aFlags -= ComputeSizeFlag::BClampMarginBoxMinSize;
}
}
// Flex items ignore their min & max sizing properties in their flex // container's main-axis. (Those properties get applied later in the flexbox // algorithm.) constbool isFlexItemBlockAxisMainAxis =
flexItemMainAxis && *flexItemMainAxis == LogicalAxis::Block; constauto& maxBSizeCoord = stylePos->MaxBSize(aWM); if (!nsLayoutUtils::IsAutoBSize(maxBSizeCoord, aCBSize.BSize(aWM)) &&
!isFlexItemBlockAxisMainAxis) {
maxBSize = nsLayoutUtils::ComputeBSizeValueHandlingStretch(
aCBSize.BSize(aWM), aMargin.BSize(aWM), aBorderPadding.BSize(aWM),
boxSizingAdjust.BSize(aWM), maxBSizeCoord);
} else {
maxBSize = nscoord_MAX;
}
NS_ASSERTION(aCBSize.ISize(aWM) != NS_UNCONSTRAINEDSIZE, "Our containing block must not have unconstrained inline-size!");
MOZ_ASSERT(!(inlineFillCB != FillCB::No ||
aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) ||
iSizeToFillCB, "iSizeToFillCB must be valid when stretching or clamping in the " "inline axis!");
MOZ_ASSERT(!(blockFillCB != FillCB::No ||
aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize)) ||
bSizeToFillCB, "bSizeToFillCB must be valid when stretching or clamping in the " "block axis!");
// 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:
// 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) &&
inlineFillCB != FillCB::Stretch && tentISize > *iSizeToFillCB) {
inlineFillCB =
(blockFillCB == FillCB::Stretch ? FillCB::Stretch : FillCB::Clamp);
}
// 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 && inlineFillCB != FillCB::Stretch &&
blockFillCB != FillCB::Stretch) {
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 = CSSMinMax(tentISize, minISize, maxISize);
bSize = CSSMinMax(tentBSize, minBSize, maxBSize);
}
} else { // 'auto' iSize, non-'auto' bSize
bSize = CSSMinMax(bSize, minBSize, maxBSize); if (inlineFillCB == FillCB::Stretch) {
iSize = *iSizeToFillCB;
} elseif (aspectRatio) {
iSize = aspectRatio.ComputeRatioDependentSize(LogicalAxis::Inline, aWM,
bSize, boxSizingAdjust);
} elseif (hasIntrinsicISize) {
iSize = aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize) &&
intrinsicISize > *iSizeToFillCB
? *iSizeToFillCB
: intrinsicISize;
} else {
iSize = fallbackIntrinsicSize.ISize(aWM);
}
iSize = CSSMinMax(iSize, minISize, maxISize);
}
} else { if (isAutoBSize) { // non-'auto' iSize, 'auto' bSize
iSize = CSSMinMax(iSize, minISize, maxISize); if (blockFillCB == FillCB::Stretch) {
bSize = *bSizeToFillCB;
} elseif (aspectRatio) {
bSize = aspectRatio.ComputeRatioDependentSize(LogicalAxis::Block, aWM,
iSize, boxSizingAdjust);
} elseif (hasIntrinsicBSize) {
bSize = aFlags.contains(ComputeSizeFlag::BClampMarginBoxMinSize) &&
intrinsicBSize > *bSizeToFillCB
? *bSizeToFillCB
: intrinsicBSize;
} else {
bSize = fallbackIntrinsicSize.BSize(aWM);
}
bSize = CSSMinMax(bSize, minBSize, maxBSize);
} else { // non-'auto' iSize, non-'auto' bSize
iSize = CSSMinMax(iSize, minISize, maxISize);
bSize = CSSMinMax(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();
}
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();
}
bool nsContainerFrame::ShouldAvoidBreakInside( const ReflowInput& aReflowInput) const {
MOZ_ASSERT(this == aReflowInput.mFrame, "Caller should pass a ReflowInput for this frame!");
constauto* disp = StyleDisplay(); constbool mayAvoidBreak = [&] { switch (disp->mBreakInside) { case StyleBreakWithin::Auto: returnfalse; case StyleBreakWithin::Avoid: returntrue; 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"); returnfalse;
}();
if (!mayAvoidBreak) { returnfalse;
} if (aReflowInput.mFlags.mIsTopOfPage) { returnfalse;
} if (IsAbsolutelyPositioned(disp)) { returnfalse;
} if (GetPrevInFlow()) { returnfalse;
} returntrue;
}
void nsContainerFrame::ConsiderChildOverflow(OverflowAreas& aOverflowAreas,
nsIFrame* aChildFrame, bool aAsIfScrolled) { if (StyleDisplay()->IsContainLayout() && SupportsContainLayoutAndPaint() &&
!aAsIfScrolled) { // 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());
}
}
// Map a raw StyleAlignFlags value to the used one. static StyleAlignFlags MapCSSAlignment(StyleAlignFlags aFlags, const ReflowInput& aChildRI,
LogicalAxis aLogicalAxis,
WritingMode aWM) { // Extract and strip the flag bits
StyleAlignFlags alignmentFlags = aFlags & StyleAlignFlags::FLAG_BITS;
aFlags &= ~StyleAlignFlags::FLAG_BITS;
StyleAlignFlags nsContainerFrame::CSSAlignmentForAbsPosChild( const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(), "This method should only be called for abspos children"); // For computing the static position of an absolutely positioned box, // `auto` takes from parent's `align-items`.
StyleAlignFlags alignment =
(aLogicalAxis == LogicalAxis::Inline)
? aChildRI.mStylePosition->UsedJustifySelf(Style())._0
: aChildRI.mStylePosition->UsedAlignSelf(Style())._0;
StyleAlignFlags
nsContainerFrame::CSSAlignmentForAbsPosChildWithinContainingBlock( const ReflowInput& aChildRI, LogicalAxis aLogicalAxis) const {
MOZ_ASSERT(aChildRI.mFrame->IsAbsolutelyPositioned(), "This method should only be called for abspos children"); // When determining the position of absolutely-positioned boxes, // `auto` behaves as `normal`.
StyleAlignFlags alignment =
(aLogicalAxis == LogicalAxis::Inline)
? aChildRI.mStylePosition->UsedJustifySelf(nullptr)._0
: aChildRI.mStylePosition->UsedAlignSelf(nullptr)._0;
void nsOverflowContinuationTracker::SetupOverflowContList() {
MOZ_ASSERT(mParent, "null frame pointer");
MOZ_ASSERT(!mOverflowContList, "already have list");
nsContainerFrame* nif = static_cast<nsContainerFrame*>(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");
// 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");
// 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;
}
}
}
// 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;
}
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<nsIFrame*> saved(mPrevOverflowCont); // step backward to make StepForward() use our current mPrevOverflowCont
mPrevOverflowCont = mPrevOverflowCont->GetPrevSibling();
StepForward();
}
}
}
constauto 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 (constauto& [list, listID] : f->ChildLists()) { if (!itemLists.contains(listID)) {
MOZ_ASSERT(
absLists.contains(listID) || listID == FrameChildListID::Backdrop, "unexpected non-empty child list"); continue;
} for (constauto* 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. constauto* pif = static_cast<nsContainerFrame*>(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)));
}
}
}
// 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<nsContainerFrame*>(frameThatMayLie->GetPrevInFlow());
} while (frameThatMayLie);
}
} #endif
// 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);
for (constauto& [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());
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.43Bemerkung:
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.