forked from mirrors/gecko-dev
Extend the per-frame-class bit we have to devirtualize IsLeaf to also devirtualize IsFrameOfType. That is, move this data to FrameClasses.py. This was done by going through all the frame classes, trying to preserve behavior. The only quirky thing is that I had to add two more trivial frame classes, `nsAudioFrame` for audio elements, and `nsFloatingFirstLetterFrame`. That's because these frame classes were returning different answers at runtime, but they do this only on conditions that trigger frame reconstruction (floating, and being an audio element, respectively). Differential Revision: https://phabricator.services.mozilla.com/D194703
3049 lines
115 KiB
C++
3049 lines
115 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* 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 <algorithm>
|
|
|
|
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<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");
|
|
}
|
|
}
|
|
|
|
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<nsIFrame*, 10> 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.
|
|
nsPresContext* pc = PresContext();
|
|
mozilla::PresShell* presShell = pc->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<ChildList>* 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<L>(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<L>(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<L>(aValue)->AppendIfNonempty(
|
|
aLists, FrameChildListID::ExcessOverflowContainers);
|
|
} else if (aProp == BackdropProperty()) {
|
|
reinterpret_cast<L>(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<ComputedStyle> 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<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;
|
|
}
|
|
|
|
aList->AppendNewToTop<nsDisplaySelectionOverlay>(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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* De-optimize function to work around a VC2017 15.5+ compiler bug:
|
|
* https://bugzil.la/1424281#c12
|
|
*/
|
|
#if defined(_MSC_VER) && !defined(__clang__) && defined(_M_AMD64)
|
|
# pragma optimize("g", off)
|
|
#endif
|
|
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<nsSize> 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)) {
|
|
// Get prev-in-flow
|
|
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();
|
|
nsSize containerSize =
|
|
aContainerSize ? *aContainerSize
|
|
: aReflowInput.AvailableSize(wm).GetPhysicalSize(wm);
|
|
LogicalRect prevRect = prevInFlow->GetLogicalRect(wm, containerSize);
|
|
|
|
// Initialize reflow params
|
|
LogicalSize availSpace(wm, prevRect.ISize(wm),
|
|
aReflowInput.AvailableSize(wm).BSize(wm));
|
|
ReflowOutput desiredSize(aReflowInput);
|
|
|
|
StyleSizeOverrides sizeOverride;
|
|
if (frame->IsFlexItem()) {
|
|
// A flex item's size is determined by the flex algorithm, not solely by
|
|
// its style. Thus, the following overrides are necessary.
|
|
//
|
|
// Use the overflow container flex item's prev-in-flow inline-size since
|
|
// this continuation's inline-size is the same.
|
|
sizeOverride.mStyleISize.emplace(
|
|
StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(
|
|
frame->StylePosition()->mBoxSizing == StyleBoxSizing::Border
|
|
? prevRect.ISize(wm)
|
|
: prevInFlow->ContentISize(wm))));
|
|
|
|
// An overflow container's block-size must be 0.
|
|
sizeOverride.mStyleBSize.emplace(
|
|
StyleSize::LengthPercentage(LengthPercentage::FromAppUnits(0)));
|
|
}
|
|
ReflowInput reflowInput(aPresContext, aReflowInput, frame, availSpace,
|
|
Nothing(), {}, sizeOverride);
|
|
|
|
LogicalPoint pos(wm, prevRect.IStart(wm), 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 <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);
|
|
|
|
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<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);
|
|
|
|
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<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);
|
|
}
|
|
}
|
|
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<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;
|
|
|
|
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<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());
|
|
}
|
|
}
|
|
|
|
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 = nsLayoutUtils::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<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;
|
|
}
|
|
|
|
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<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;
|
|
}
|
|
|
|
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<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");
|
|
|
|
// 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<nsIFrame*> 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<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)));
|
|
}
|
|
}
|
|
}
|
|
|
|
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<nsContainerFrame*>(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
|