Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
/*
* construction of a frame tree that is nearly isomorphic to the content
* tree and updating of that tree in response to dynamic changes
*/
#include "nsCSSFrameConstructor.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/ComputedStyleInlines.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/ManualNAC.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/GeneratedImageContent.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLSharedListElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/Likely.h"
#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/PrintedSheetFrame.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleSetInlines.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_mathml.h"
#include "mozilla/Unused.h"
#include "RetainedDisplayListBuilder.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsCSSPseudoElements.h"
#include "nsCheckboxRadioFrame.h"
#include "nsCRT.h"
#include "nsAtom.h"
#include "nsIFrameInlines.h"
#include "nsGkAtoms.h"
#include "nsPresContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "nsTableFrame.h"
#include "nsTableColFrame.h"
#include "nsTableRowFrame.h"
#include "nsTableCellFrame.h"
#include "nsFileControlFrame.h"
#include "nsHTMLParts.h"
#include "nsUnicharUtils.h"
#include "nsViewManager.h"
#include "nsStyleConsts.h"
#include "nsXULElement.h"
#include "nsContainerFrame.h"
#include "nsNameSpaceManager.h"
#include "nsComboboxControlFrame.h"
#include "nsListControlFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsTableRowGroupFrame.h"
#include "nsIFormControl.h"
#include "nsCSSAnonBoxes.h"
#include "nsTextFragment.h"
#include "nsIAnonymousContentCreator.h"
#include "nsContentUtils.h"
#include "nsIScriptError.h"
#ifdef XP_MACOSX
# include "nsIDocShell.h"
#endif
#include "ChildIterator.h"
#include "nsError.h"
#include "nsLayoutUtils.h"
#include "nsFlexContainerFrame.h"
#include "nsGridContainerFrame.h"
#include "RubyUtils.h"
#include "nsRubyFrame.h"
#include "nsRubyBaseFrame.h"
#include "nsRubyBaseContainerFrame.h"
#include "nsRubyTextFrame.h"
#include "nsRubyTextContainerFrame.h"
#include "nsImageFrame.h"
#include "nsIObjectLoadingContent.h"
#include "nsTArray.h"
#include "mozilla/dom/CharacterData.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "nsAutoLayoutPhase.h"
#include "nsStyleStructInlines.h"
#include "nsPageContentFrame.h"
#include "mozilla/RestyleManager.h"
#include "StickyScrollContainer.h"
#include "nsFieldSetFrame.h"
#include "nsInlineFrame.h"
#include "nsBlockFrame.h"
#include "nsCanvasFrame.h"
#include "nsFirstLetterFrame.h"
#include "nsGfxScrollFrame.h"
#include "nsPageFrame.h"
#include "nsPageSequenceFrame.h"
#include "nsTableWrapperFrame.h"
#include "nsIScrollableFrame.h"
#include "nsBackdropFrame.h"
#include "nsTransitionManager.h"
#include "nsIPopupContainer.h"
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.h"
#endif
#undef NOISY_FIRST_LETTER
#include "nsMathMLParts.h"
#include "mozilla/SVGGradientFrame.h"
#include "nsRefreshDriver.h"
#include "nsTextNode.h"
#include "ActiveLayerTracker.h"
using namespace mozilla;
using namespace mozilla::dom;
nsIFrame* NS_NewHTMLCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewHTMLAudioFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsContainerFrame* NS_NewSVGOuterSVGFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsIFrame* NS_NewSVGInnerSVGFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGGeometryFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGGFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsContainerFrame* NS_NewSVGForeignObjectFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsIFrame* NS_NewSVGAFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGSwitchFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGSymbolFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGTextFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGContainerFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGUseFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGViewFrame(PresShell* aPresShell, ComputedStyle* aStyle);
extern nsIFrame* NS_NewSVGLinearGradientFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
extern nsIFrame* NS_NewSVGRadialGradientFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
extern nsIFrame* NS_NewSVGStopFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsContainerFrame* NS_NewSVGMarkerFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsContainerFrame* NS_NewSVGMarkerAnonChildFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
extern nsIFrame* NS_NewSVGImageFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsIFrame* NS_NewSVGClipPathFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGFilterFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGPatternFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGMaskFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGFEContainerFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsIFrame* NS_NewSVGFELeafFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGFEImageFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSVGFEUnstyledLeafFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsIFrame* NS_NewFileControlLabelFrame(PresShell*, ComputedStyle*);
nsIFrame* NS_NewComboboxLabelFrame(PresShell*, ComputedStyle*);
nsIFrame* NS_NewMiddleCroppingLabelFrame(PresShell*, ComputedStyle*);
#include "mozilla/dom/NodeInfo.h"
#include "prenv.h"
#include "nsNodeInfoManager.h"
#include "nsContentCreatorFunctions.h"
#ifdef DEBUG
// Set the environment variable GECKO_FRAMECTOR_DEBUG_FLAGS to one or
// more of the following flags (comma separated) for handy debug
// output.
static bool gNoisyContentUpdates = false;
static bool gReallyNoisyContentUpdates = false;
static bool gNoisyInlineConstruction = false;
struct FrameCtorDebugFlags {
const char* name;
bool* on;
};
static FrameCtorDebugFlags gFlags[] = {
{"content-updates", &gNoisyContentUpdates},
{"really-noisy-content-updates", &gReallyNoisyContentUpdates},
{"noisy-inline", &gNoisyInlineConstruction}};
# define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
#endif
//------------------------------------------------------------------
nsIFrame* NS_NewLeafBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewRangeFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewMenuPopupFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell,
ComputedStyle* aStyle, bool aIsRoot);
nsIFrame* NS_NewSliderFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewScrollbarButtonFrame(PresShell*, ComputedStyle*);
nsIFrame* NS_NewSimpleXULLeafFrame(PresShell*, ComputedStyle*);
nsIFrame* NS_NewXULImageFrame(PresShell*, ComputedStyle*);
nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*);
nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*);
nsIFrame* NS_NewImageFrameForListStyleImage(PresShell*, ComputedStyle*);
// Returns true if aFrame is an anonymous flex/grid item.
static inline bool IsAnonymousItem(const nsIFrame* aFrame) {
return aFrame->Style()->GetPseudoType() == PseudoStyleType::anonymousItem;
}
// Returns true IFF the given nsIFrame is a nsFlexContainerFrame and represents
// a -webkit-{inline-}box container.
static inline bool IsFlexContainerForLegacyWebKitBox(const nsIFrame* aFrame) {
return aFrame->IsFlexContainerFrame() &&
aFrame->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
}
#if DEBUG
static void AssertAnonymousFlexOrGridItemParent(const nsIFrame* aChild,
const nsIFrame* aParent) {
MOZ_ASSERT(IsAnonymousItem(aChild), "expected an anonymous item child frame");
MOZ_ASSERT(aParent, "expected a parent frame");
MOZ_ASSERT(aParent->IsFlexOrGridContainer(),
"anonymous items should only exist as children of flex/grid "
"container frames");
}
#else
# define AssertAnonymousFlexOrGridItemParent(x, y) PR_BEGIN_MACRO PR_END_MACRO
#endif
#define ToCreationFunc(_func) \
[](PresShell* aPs, ComputedStyle* aStyle) -> nsIFrame* { \
return _func(aPs, aStyle); \
}
/**
* True if aFrame is an actual inline frame in the sense of non-replaced
* display:inline CSS boxes. In other words, it can be affected by {ib}
* splitting and can contain first-letter frames. Basically, this is either an
* inline frame (positioned or otherwise) or an line frame (this last because
* it can contain first-letter and because inserting blocks in the middle of it
* needs to terminate it).
*/
static bool IsInlineFrame(const nsIFrame* aFrame) {
return aFrame->IsLineParticipant();
}
/**
* True for display: contents elements.
*/
static inline bool IsDisplayContents(const Element* aElement) {
return aElement->IsDisplayContents();
}
static inline bool IsDisplayContents(const nsIContent* aContent) {
return aContent->IsElement() && IsDisplayContents(aContent->AsElement());
}
/**
* True if aFrame is an instance of an SVG frame class or is an inline/block
* frame being used for SVG text.
*/
static bool IsFrameForSVG(const nsIFrame* aFrame) {
return aFrame->IsSVGFrame() || aFrame->IsInSVGTextSubtree();
}
static bool IsLastContinuationForColumnContent(const nsIFrame* aFrame) {
MOZ_ASSERT(aFrame);
return aFrame->Style()->GetPseudoType() == PseudoStyleType::columnContent &&
!aFrame->GetNextContinuation();
}
/**
* Returns true iff aFrame explicitly prevents its descendants from floating
* (at least, down to the level of descendants which themselves are
* float-containing blocks -- those will manage the floating status of any
* lower-level descendents inside them, of course).
*/
static bool ShouldSuppressFloatingOfDescendants(nsIFrame* aFrame) {
return aFrame->IsFlexOrGridContainer() || aFrame->IsMathMLFrame();
}
// Return true if column-span descendants should be suppressed under aFrame's
// subtree (until a multi-column container re-establishing a block formatting
// context). Basically, this is testing whether aFrame establishes a new block
// formatting context or not.
static bool ShouldSuppressColumnSpanDescendants(nsIFrame* aFrame) {
if (aFrame->Style()->GetPseudoType() == PseudoStyleType::columnContent) {
// Never suppress column-span under ::-moz-column-content frames.
return false;
}
if (aFrame->IsInlineFrame()) {
// Allow inline frames to have column-span block children.
return false;
}
if (!aFrame->IsBlockFrameOrSubclass() ||
aFrame->HasAnyStateBits(NS_BLOCK_BFC | NS_FRAME_OUT_OF_FLOW) ||
aFrame->IsFixedPosContainingBlock()) {
// Need to suppress column-span if we:
// - Are a different block formatting context,
// - Are an out-of-flow frame, OR
// - Establish a containing block for fixed-position descendants
//
// For example, the children of a column-span never need to be further
// processed even if there is a nested column-span child. Because a
// column-span always creates its own block formatting context, a nested
// column-span child won't be in the same block formatting context with the
// nearest multi-column ancestor. This is the same case as if the
// column-span is outside of a multi-column hierarchy.
return true;
}
return false;
}
// Reparent a frame into a wrapper frame that is a child of its old parent.
static void ReparentFrame(RestyleManager* aRestyleManager,
nsContainerFrame* aNewParentFrame, nsIFrame* aFrame,
bool aForceStyleReparent) {
aFrame->SetParent(aNewParentFrame);
// We reparent frames for two reasons: to put them inside ::first-line, and to
// put them inside some wrapper anonymous boxes.
if (aForceStyleReparent) {
aRestyleManager->ReparentComputedStyleForFirstLine(aFrame);
}
}
static void ReparentFrames(nsCSSFrameConstructor* aFrameConstructor,
nsContainerFrame* aNewParentFrame,
const nsFrameList& aFrameList,
bool aForceStyleReparent) {
RestyleManager* restyleManager = aFrameConstructor->RestyleManager();
for (nsIFrame* f : aFrameList) {
ReparentFrame(restyleManager, aNewParentFrame, f, aForceStyleReparent);
}
}
//----------------------------------------------------------------------
//
// When inline frames get weird and have block frames in them, we
// annotate them to help us respond to incremental content changes
// more easily.
static inline bool IsFramePartOfIBSplit(nsIFrame* aFrame) {
bool result = aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT);
MOZ_ASSERT(!result || static_cast<nsBlockFrame*>(do_QueryFrame(aFrame)) ||
static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)),
"only block/inline frames can have NS_FRAME_PART_OF_IBSPLIT");
return result;
}
static nsContainerFrame* GetIBSplitSibling(nsIFrame* aFrame) {
MOZ_ASSERT(IsFramePartOfIBSplit(aFrame), "Shouldn't call this");
// We only store the "ib-split sibling" annotation with the first
// frame in the continuation chain. Walk back to find that frame now.
return aFrame->FirstContinuation()->GetProperty(nsIFrame::IBSplitSibling());
}
static nsContainerFrame* GetIBSplitPrevSibling(nsIFrame* aFrame) {
MOZ_ASSERT(IsFramePartOfIBSplit(aFrame), "Shouldn't call this");
// We only store the ib-split sibling annotation with the first
// frame in the continuation chain. Walk back to find that frame now.
return aFrame->FirstContinuation()->GetProperty(
nsIFrame::IBSplitPrevSibling());
}
static nsContainerFrame* GetLastIBSplitSibling(nsIFrame* aFrame) {
for (nsIFrame *frame = aFrame, *next;; frame = next) {
next = GetIBSplitSibling(frame);
if (!next) {
return static_cast<nsContainerFrame*>(frame);
}
}
MOZ_ASSERT_UNREACHABLE("unreachable code");
return nullptr;
}
static void SetFrameIsIBSplit(nsContainerFrame* aFrame,
nsContainerFrame* aIBSplitSibling) {
MOZ_ASSERT(aFrame, "bad args!");
// We should be the only continuation
NS_ASSERTION(!aFrame->GetPrevContinuation(),
"assigning ib-split sibling to other than first continuation!");
NS_ASSERTION(!aFrame->GetNextContinuation() ||
IsFramePartOfIBSplit(aFrame->GetNextContinuation()),
"should have no non-ib-split continuations here");
// Mark the frame as ib-split.
aFrame->AddStateBits(NS_FRAME_PART_OF_IBSPLIT);
if (aIBSplitSibling) {
NS_ASSERTION(!aIBSplitSibling->GetPrevContinuation(),
"assigning something other than the first continuation as the "
"ib-split sibling");
// Store the ib-split sibling (if we were given one) with the
// first frame in the flow.
aFrame->SetProperty(nsIFrame::IBSplitSibling(), aIBSplitSibling);
aIBSplitSibling->SetProperty(nsIFrame::IBSplitPrevSibling(), aFrame);
}
}
static nsIFrame* GetIBContainingBlockFor(nsIFrame* aFrame) {
MOZ_ASSERT(
IsFramePartOfIBSplit(aFrame),
"GetIBContainingBlockFor() should only be called on known IB frames");
// Get the first "normal" ancestor of the target frame.
nsIFrame* parentFrame;
do {
parentFrame = aFrame->GetParent();
if (!parentFrame) {
NS_ERROR("no unsplit block frame in IB hierarchy");
return aFrame;
}
// Note that we ignore non-ib-split frames which have a pseudo on their
// ComputedStyle -- they're not the frames we're looking for! In
// particular, they may be hiding a real parent that _is_ in an ib-split.
if (!IsFramePartOfIBSplit(parentFrame) &&
!parentFrame->Style()->IsPseudoOrAnonBox())
break;
aFrame = parentFrame;
} while (1);
// post-conditions
NS_ASSERTION(parentFrame,
"no normal ancestor found for ib-split frame "
"in GetIBContainingBlockFor");
NS_ASSERTION(parentFrame != aFrame,
"parentFrame is actually the child frame - bogus reslt");
return parentFrame;
}
// Find the multicol containing block suitable for reframing.
//
// Note: this function may not return a ColumnSetWrapperFrame. For example, if
// the multicol containing block has "overflow:scroll" style, HTMLScrollFrame is
// returned because ColumnSetWrapperFrame is the scrolled frame which has the
// -moz-scrolled-content pseudo style. We may walk up "too far", but in terms of
// correctness of reframing, it's OK.
static nsContainerFrame* GetMultiColumnContainingBlockFor(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR),
"Should only be called if the frame has a multi-column ancestor!");
nsContainerFrame* current = aFrame->GetParent();
while (current &&
(current->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
current->Style()->IsPseudoOrAnonBox())) {
current = current->GetParent();
}
MOZ_ASSERT(current,
"No multicol containing block in a valid column hierarchy?");
return current;
}
// This is a bit slow, but sometimes we need it.
static bool ParentIsWrapperAnonBox(nsIFrame* aParent) {
nsIFrame* maybeAnonBox = aParent;
if (maybeAnonBox->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
// The thing that would maybe be a wrapper anon box is the cell.
maybeAnonBox = maybeAnonBox->GetParent();
}
return maybeAnonBox->Style()->IsWrapperAnonBox();
}
static bool InsertSeparatorBeforeAccessKey() {
static bool sInitialized = false;
static bool sValue = false;
if (!sInitialized) {
sInitialized = true;
const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
nsAutoString val;
Preferences::GetLocalizedString(prefName, val);
sValue = val.EqualsLiteral("true");
}
return sValue;
}
static bool AlwaysAppendAccessKey() {
static bool sInitialized = false;
static bool sValue = false;
if (!sInitialized) {
sInitialized = true;
const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
nsAutoString val;
Preferences::GetLocalizedString(prefName, val);
sValue = val.EqualsLiteral("true");
}
return sValue;
}
//----------------------------------------------------------------------
// Block/inline frame construction logic. We maintain a few invariants here:
//
// 1. Block frames contain block and inline frames.
//
// 2. Inline frames only contain inline frames. If an inline parent has a block
// child then the block child is migrated upward until it lands in a block
// parent (the inline frames containing block is where it will end up).
inline void SetInitialSingleChild(nsContainerFrame* aParent, nsIFrame* aFrame) {
MOZ_ASSERT(!aFrame->GetNextSibling(), "Should be using a frame list");
aParent->SetInitialChildList(FrameChildListID::Principal,
nsFrameList(aFrame, aFrame));
}
// -----------------------------------------------------------
// Structure used when constructing formatting object trees. Contains
// state information needed for absolutely positioned elements
namespace mozilla {
struct AbsoluteFrameList final : public nsFrameList {
// Containing block for absolutely positioned elements.
nsContainerFrame* mContainingBlock;
explicit AbsoluteFrameList(nsContainerFrame* aContainingBlock = nullptr)
: mContainingBlock(aContainingBlock) {}
// Transfer frames in aOther to this list. aOther becomes empty after this
// operation.
AbsoluteFrameList(AbsoluteFrameList&& aOther) = default;
AbsoluteFrameList& operator=(AbsoluteFrameList&& aOther) = default;
#ifdef DEBUG
// XXXbz Does this need a debug-only assignment operator that nulls out the
// childList in the AbsoluteFrameList we're copying? Introducing a difference
// between debug and non-debug behavior seems bad, so I guess not...
~AbsoluteFrameList() {
NS_ASSERTION(!FirstChild(),
"Dangling child list. Someone forgot to insert it?");
}
#endif
};
} // namespace mozilla
// -----------------------------------------------------------
// Structure for saving the existing state when pushing/poping containing
// blocks. The destructor restores the state to its previous state
class MOZ_STACK_CLASS nsFrameConstructorSaveState {
public:
~nsFrameConstructorSaveState();
private:
// Pointer to struct whose data we save/restore.
AbsoluteFrameList* mList = nullptr;
// The saved pointer to the fixed list.
AbsoluteFrameList* mSavedFixedList = nullptr;
// Copy of original frame list. This can be the original absolute list or a
// float list.
AbsoluteFrameList mSavedList;
// The name of the child list in which our frames would belong.
mozilla::FrameChildListID mChildListID = FrameChildListID::Principal;
nsFrameConstructorState* mState = nullptr;
friend class nsFrameConstructorState;
};
// Structure used for maintaining state information during the
// frame construction process
class MOZ_STACK_CLASS nsFrameConstructorState {
public:
nsPresContext* mPresContext;
PresShell* mPresShell;
nsCSSFrameConstructor* mFrameConstructor;
// Containing block information for out-of-flow frames.
//
// Floats are easy. Whatever is our float CB.
//
// Regular abspos elements are easy too. Its containing block can be the
// nearest abspos element, or the ICB (the canvas frame).
//
// Top layer abspos elements are always children of the ICB, but we can get
// away with having two different lists (mAbsoluteList and
// mTopLayerAbsoluteList), because because top layer frames cause
// non-top-layer frames to be contained inside (so any descendants of a top
// layer abspos can never share containing block with it, unless they're also
// in the top layer).
//
// Regular fixed elements however are trickier. Fixed elements can be
// contained in one of three lists:
//
// * mAbsoluteList, if our abspos cb is also a fixpos cb (e.g., is
// transformed or has a filter).
//
// * mAncestorFixedList, if the fixpos cb is an ancestor element other than
// the viewport frame, (so, a transformed / filtered
// ancestor).
//
// * mRealFixedList, which is also the fixed list used for the top layer
// fixed items, which is the fixed list of the viewport
// frame.
//
// It is important that mRealFixedList is shared between regular and top layer
// fixpos elements, since no-top-layer descendants of top layer fixed elements
// could share ICB and vice versa, so without that there would be no guarantee
// of layout ordering between them.
AbsoluteFrameList mFloatedList;
AbsoluteFrameList mAbsoluteList;
AbsoluteFrameList mTopLayerAbsoluteList;
AbsoluteFrameList mAncestorFixedList;
AbsoluteFrameList mRealFixedList;
// Never null, always pointing to one of the lists documented above.
AbsoluteFrameList* mFixedList;
// What `page: auto` resolves to. This is the used page-name of the parent
// frame. Updated by AutoFrameConstructionPageName.
const nsAtom* mAutoPageNameValue = nullptr;
nsCOMPtr<nsILayoutHistoryState> mFrameState;
// These bits will be added to the state bits of any frame we construct
// using this state.
nsFrameState mAdditionalStateBits{0};
// If false (which is the default) then call SetPrimaryFrame() as needed
// during frame construction. If true, don't make any SetPrimaryFrame()
// calls, except for generated content which doesn't have a primary frame
// yet. The mCreatingExtraFrames == true mode is meant to be used for
// construction of random "extra" frames for elements via normal frame
// construction APIs (e.g. replication of things across pages in paginated
// mode).
bool mCreatingExtraFrames;
// This keeps track of whether we have found a "rendered legend" for
// the current FieldSetFrame.
bool mHasRenderedLegend;
nsTArray<RefPtr<nsIContent>> mGeneratedContentWithInitializer;
#ifdef DEBUG
// Record the float containing block candidate passed into
// MaybePushFloatContainingBlock() to keep track that we've call the method to
// handle the float CB scope before processing the CB's children. It is reset
// in ConstructFramesFromItemList().
nsContainerFrame* mFloatCBCandidate = nullptr;
#endif
// Constructor
// Use the passed-in history state.
nsFrameConstructorState(
PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
nsContainerFrame* aAbsoluteContainingBlock,
nsContainerFrame* aFloatContainingBlock,
already_AddRefed<nsILayoutHistoryState> aHistoryState);
// Get the history state from the pres context's pres shell.
nsFrameConstructorState(PresShell* aPresShell,
nsContainerFrame* aFixedContainingBlock,
nsContainerFrame* aAbsoluteContainingBlock,
nsContainerFrame* aFloatContainingBlock);
~nsFrameConstructorState();
// Process the frame insertions for all the out-of-flow nsAbsoluteItems.
void ProcessFrameInsertionsForAllLists();
// Function to push the existing absolute containing block state and
// create a new scope. Code that uses this function should get matching
// logic in GetAbsoluteContainingBlock.
// Also makes aNewAbsoluteContainingBlock the containing block for
// fixed-pos elements if necessary.
// aPositionedFrame is the frame whose style actually makes
// aNewAbsoluteContainingBlock a containing block. E.g. for a scrollable
// element aPositionedFrame is the element's primary frame and
// aNewAbsoluteContainingBlock is the scrolled frame.
void PushAbsoluteContainingBlock(
nsContainerFrame* aNewAbsoluteContainingBlock, nsIFrame* aPositionedFrame,
nsFrameConstructorSaveState& aSaveState);
// Function to forbid floats descendants under aFloatCBCandidate, or open a
// new float containing block scope for aFloatCBCandidate. The current
// state is saved in aSaveState if a new scope is pushed.
void MaybePushFloatContainingBlock(nsContainerFrame* aFloatCBCandidate,
nsFrameConstructorSaveState& aSaveState);
// Helper function for MaybePushFloatContainingBlock().
void PushFloatContainingBlock(nsContainerFrame* aNewFloatContainingBlock,
nsFrameConstructorSaveState& aSaveState);
// Function to return the proper geometric parent for a frame with display
// struct given by aStyleDisplay and parent's frame given by
// aContentParentFrame.
nsContainerFrame* GetGeometricParent(
const nsStyleDisplay& aStyleDisplay,
nsContainerFrame* aContentParentFrame) const;
// Collect absolute frames in mAbsoluteList which are proper descendants
// of aNewParent, and reparent them to aNewParent.
//
// Note: This function does something unusual that moves absolute items
// after their frames are constructed under a column hierarchy which has
// column-span elements. Do not use this if you're not dealing with
// columns.
void ReparentAbsoluteItems(nsContainerFrame* aNewParent);
// Collect floats in mFloatedList which are proper descendants of aNewParent,
// and reparent them to aNewParent.
//
// Note: This function does something unusual that moves floats after their
// frames are constructed under a column hierarchy which has column-span
// elements. Do not use this if you're not dealing with columns.
void ReparentFloats(nsContainerFrame* aNewParent);
/**
* Function to add a new frame to the right frame list. This MUST be called
* on frames before their children have been processed if the frames might
* conceivably be out-of-flow; otherwise cleanup in error cases won't work
* right. Also, this MUST be called on frames after they have been
* initialized.
* @param aNewFrame the frame to add
* @param aFrameList the list to add in-flow frames to
* @param aContent the content pointer for aNewFrame
* @param aParentFrame the parent frame for the content if it were in-flow
* @param aCanBePositioned pass false if the frame isn't allowed to be
* positioned
* @param aCanBeFloated pass false if the frame isn't allowed to be
* floated
*/
void AddChild(nsIFrame* aNewFrame, nsFrameList& aFrameList,
nsIContent* aContent, nsContainerFrame* aParentFrame,
bool aCanBePositioned = true, bool aCanBeFloated = true,
bool aInsertAfter = false,
nsIFrame* aInsertAfterFrame = nullptr);
/**
* Function to return the fixed-pos element list. Normally this will just
* hand back the fixed-pos element list, but in case we're dealing with a
* transformed element that's acting as an abs-pos and fixed-pos container,
* we'll hand back the abs-pos list. Callers should use this function if they
* want to get the list acting as the fixed-pos item parent.
*/
AbsoluteFrameList& GetFixedList() { return *mFixedList; }
const AbsoluteFrameList& GetFixedList() const { return *mFixedList; }
protected:
friend class nsFrameConstructorSaveState;
/**
* ProcessFrameInsertions takes the frames in aFrameList and adds them as
* kids to the aChildListID child list of |aFrameList.containingBlock|.
*/
void ProcessFrameInsertions(AbsoluteFrameList& aFrameList,
mozilla::FrameChildListID aChildListID);
/**
* GetOutOfFlowFrameList selects the out-of-flow frame list the new
* frame should be added to. If the frame shouldn't be added to any
* out-of-flow list, it returns nullptr. The corresponding type of
* placeholder is also returned via the aPlaceholderType parameter
* if this method doesn't return nullptr. The caller should check
* whether the returned list really has a containing block.
*/
AbsoluteFrameList* GetOutOfFlowFrameList(nsIFrame* aNewFrame,
bool aCanBePositioned,
bool aCanBeFloated,
nsFrameState* aPlaceholderType);
void ConstructBackdropFrameFor(nsIContent* aContent, nsIFrame* aFrame);
};
nsFrameConstructorState::nsFrameConstructorState(
PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
nsContainerFrame* aAbsoluteContainingBlock,
nsContainerFrame* aFloatContainingBlock,
already_AddRefed<nsILayoutHistoryState> aHistoryState)
: mPresContext(aPresShell->GetPresContext()),
mPresShell(aPresShell),
mFrameConstructor(aPresShell->FrameConstructor()),
mFloatedList(aFloatContainingBlock),
mAbsoluteList(aAbsoluteContainingBlock),
mTopLayerAbsoluteList(mFrameConstructor->GetCanvasFrame()),
mAncestorFixedList(aFixedContainingBlock),
mRealFixedList(
static_cast<nsContainerFrame*>(mFrameConstructor->GetRootFrame())),
// See PushAbsoluteContaningBlock below
mFrameState(aHistoryState),
mCreatingExtraFrames(false),
mHasRenderedLegend(false) {
MOZ_COUNT_CTOR(nsFrameConstructorState);
mFixedList = [&] {
if (aFixedContainingBlock == aAbsoluteContainingBlock) {
return &mAbsoluteList;
}
if (aAbsoluteContainingBlock == mRealFixedList.mContainingBlock) {
return &mRealFixedList;
}
return &mAncestorFixedList;
}();
}
nsFrameConstructorState::nsFrameConstructorState(
PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
nsContainerFrame* aAbsoluteContainingBlock,
nsContainerFrame* aFloatContainingBlock)
: nsFrameConstructorState(
aPresShell, aFixedContainingBlock, aAbsoluteContainingBlock,
aFloatContainingBlock,
aPresShell->GetDocument()->GetLayoutHistoryState()) {}
nsFrameConstructorState::~nsFrameConstructorState() {
MOZ_COUNT_DTOR(nsFrameConstructorState);
ProcessFrameInsertionsForAllLists();
for (auto& content : Reversed(mGeneratedContentWithInitializer)) {
content->RemoveProperty(nsGkAtoms::genConInitializerProperty);
}
}
void nsFrameConstructorState::ProcessFrameInsertionsForAllLists() {
ProcessFrameInsertions(mFloatedList, FrameChildListID::Float);
ProcessFrameInsertions(mAbsoluteList, FrameChildListID::Absolute);
ProcessFrameInsertions(mTopLayerAbsoluteList, FrameChildListID::Absolute);
ProcessFrameInsertions(*mFixedList, FrameChildListID::Fixed);
ProcessFrameInsertions(mRealFixedList, FrameChildListID::Fixed);
}
void nsFrameConstructorState::PushAbsoluteContainingBlock(
nsContainerFrame* aNewAbsoluteContainingBlock, nsIFrame* aPositionedFrame,
nsFrameConstructorSaveState& aSaveState) {
MOZ_ASSERT(!!aNewAbsoluteContainingBlock == !!aPositionedFrame,
"We should have both or none");
aSaveState.mList = &mAbsoluteList;
aSaveState.mChildListID = FrameChildListID::Absolute;
aSaveState.mState = this;
aSaveState.mSavedList = std::move(mAbsoluteList);
aSaveState.mSavedFixedList = mFixedList;
mAbsoluteList = AbsoluteFrameList(aNewAbsoluteContainingBlock);
mFixedList = [&] {
if (!aPositionedFrame || aPositionedFrame->IsFixedPosContainingBlock()) {
// See if we need to treat abspos and fixedpos the same. This happens if
// we're a transformed/filtered/etc element, or if we force a null abspos
// containing block (for mathml for example).
return &mAbsoluteList;
}
if (aPositionedFrame->StyleDisplay()->mTopLayer == StyleTopLayer::Top) {
// If our new CB is in the top layer, and isn't a fixed CB itself, we also
// escape the usual containment.
return &mRealFixedList;
}
if (mFixedList == &mAbsoluteList) {
// If we were pointing to our old absolute list, keep pointing to it.
return &aSaveState.mSavedList;
}
// Otherwise keep pointing to the current thing (another ancestor's
// absolute list, or the real fixed list, doesn't matter).
return mFixedList;
}();
if (aNewAbsoluteContainingBlock) {
aNewAbsoluteContainingBlock->MarkAsAbsoluteContainingBlock();
}
}
void nsFrameConstructorState::MaybePushFloatContainingBlock(
nsContainerFrame* aFloatCBCandidate,
nsFrameConstructorSaveState& aSaveState) {
// The logic here needs to match the logic in GetFloatContainingBlock().
if (ShouldSuppressFloatingOfDescendants(aFloatCBCandidate)) {
// Pushing a null float containing block forbids any frames from being
// floated until a new float containing block is pushed. See implementation
// of nsFrameConstructorState::AddChild().
//
// XXX we should get rid of null float containing blocks and teach the
// various frame classes to deal with floats instead.
PushFloatContainingBlock(nullptr, aSaveState);
} else if (aFloatCBCandidate->IsFloatContainingBlock()) {
PushFloatContainingBlock(aFloatCBCandidate, aSaveState);
}
#ifdef DEBUG
mFloatCBCandidate = aFloatCBCandidate;
#endif
}
void nsFrameConstructorState::PushFloatContainingBlock(
nsContainerFrame* aNewFloatContainingBlock,
nsFrameConstructorSaveState& aSaveState) {
MOZ_ASSERT(!aNewFloatContainingBlock ||
aNewFloatContainingBlock->IsFloatContainingBlock(),
"Please push a real float containing block!");
NS_ASSERTION(
!aNewFloatContainingBlock ||
!ShouldSuppressFloatingOfDescendants(aNewFloatContainingBlock),
"We should not push a frame that is supposed to _suppress_ "
"floats as a float containing block!");
aSaveState.mList = &mFloatedList;
aSaveState.mSavedList = std::move(mFloatedList);
aSaveState.mChildListID = FrameChildListID::Float;
aSaveState.mState = this;
mFloatedList = AbsoluteFrameList(aNewFloatContainingBlock);
}
nsContainerFrame* nsFrameConstructorState::GetGeometricParent(
const nsStyleDisplay& aStyleDisplay,
nsContainerFrame* aContentParentFrame) const {
// If there is no container for a fixed, absolute, or floating root
// frame, we will ignore the positioning. This hack is originally
// brought to you by the letter T: tables, since other roots don't
// even call into this code. See bug 178855.
//
// XXX Disabling positioning in this case is a hack. If one was so inclined,
// one could support this either by (1) inserting a dummy block between the
// table and the canvas or (2) teaching the canvas how to reflow positioned
// elements. (1) has the usual problems when multiple frames share the same
// content (notice all the special cases in this file dealing with inner
// tables and table wrappers which share the same content). (2) requires some
// work and possible factoring.
//
// XXXbz couldn't we just force position to "static" on roots and
// float to "none"? That's OK per CSS 2.1, as far as I can tell.
if (aContentParentFrame && aContentParentFrame->IsInSVGTextSubtree()) {
return aContentParentFrame;
}
if (aStyleDisplay.IsFloatingStyle() && mFloatedList.mContainingBlock) {
NS_ASSERTION(!aStyleDisplay.IsAbsolutelyPositionedStyle(),
"Absolutely positioned _and_ floating?");
return mFloatedList.mContainingBlock;
}
if (aStyleDisplay.mTopLayer != StyleTopLayer::None) {
MOZ_ASSERT(aStyleDisplay.mTopLayer == StyleTopLayer::Top,
"-moz-top-layer should be either none or top");
MOZ_ASSERT(aStyleDisplay.IsAbsolutelyPositionedStyle(),
"Top layer items should always be absolutely positioned");
if (aStyleDisplay.mPosition == StylePositionProperty::Fixed) {
MOZ_ASSERT(mRealFixedList.mContainingBlock, "No root frame?");
return mRealFixedList.mContainingBlock;
}
MOZ_ASSERT(aStyleDisplay.mPosition == StylePositionProperty::Absolute);
MOZ_ASSERT(mTopLayerAbsoluteList.mContainingBlock);
return mTopLayerAbsoluteList.mContainingBlock;
}
if (aStyleDisplay.mPosition == StylePositionProperty::Absolute &&
mAbsoluteList.mContainingBlock) {
return mAbsoluteList.mContainingBlock;
}
if (aStyleDisplay.mPosition == StylePositionProperty::Fixed &&
mFixedList->mContainingBlock) {
return mFixedList->mContainingBlock;
}
return aContentParentFrame;
}
void nsFrameConstructorState::ReparentAbsoluteItems(
nsContainerFrame* aNewParent) {
// Bug 1491727: This function might not conform to the spec. See
MOZ_ASSERT(aNewParent->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR),
"Restrict the usage under column hierarchy.");
AbsoluteFrameList newAbsoluteItems(aNewParent);
nsIFrame* current = mAbsoluteList.FirstChild();
while (current) {
nsIFrame* placeholder = current->GetPlaceholderFrame();
if (nsLayoutUtils::IsProperAncestorFrame(aNewParent, placeholder)) {
nsIFrame* next = current->GetNextSibling();
mAbsoluteList.RemoveFrame(current);
newAbsoluteItems.AppendFrame(aNewParent, current);
current = next;
} else {
current = current->GetNextSibling();
}
}
if (newAbsoluteItems.NotEmpty()) {
// ~nsFrameConstructorSaveState() will move newAbsoluteItems to
// aNewParent's absolute child list.
nsFrameConstructorSaveState absoluteSaveState;
// It doesn't matter whether aNewParent has position style or not. Caller
// won't call us if we can't have absolute children.
PushAbsoluteContainingBlock(aNewParent, aNewParent, absoluteSaveState);
mAbsoluteList = std::move(newAbsoluteItems);
}
}
void nsFrameConstructorState::ReparentFloats(nsContainerFrame* aNewParent) {
MOZ_ASSERT(aNewParent->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR),
"Restrict the usage under column hierarchy.");
MOZ_ASSERT(
aNewParent->IsFloatContainingBlock(),
"Why calling this method if aNewParent is not a float containing block?");
// Gather floats that should reparent under aNewParent.
AbsoluteFrameList floats(aNewParent);
nsIFrame* current = mFloatedList.FirstChild();
while (current) {
nsIFrame* placeholder = current->GetPlaceholderFrame();
nsIFrame* next = current->GetNextSibling();
if (nsLayoutUtils::IsProperAncestorFrame(aNewParent, placeholder)) {
mFloatedList.RemoveFrame(current);
floats.AppendFrame(aNewParent, current);
}
current = next;
}
if (floats.NotEmpty()) {
// Make floats move into aNewParent's float child list in
// ~nsFrameConstructorSaveState() when destructing floatSaveState.
nsFrameConstructorSaveState floatSaveState;
PushFloatContainingBlock(aNewParent, floatSaveState);
mFloatedList = std::move(floats);
}
}
AbsoluteFrameList* nsFrameConstructorState::GetOutOfFlowFrameList(
nsIFrame* aNewFrame, bool aCanBePositioned, bool aCanBeFloated,
nsFrameState* aPlaceholderType) {
const nsStyleDisplay* disp = aNewFrame->StyleDisplay();
if (aCanBeFloated && disp->IsFloatingStyle()) {
*aPlaceholderType = PLACEHOLDER_FOR_FLOAT;
return &mFloatedList;
}
if (aCanBePositioned) {
if (disp->mTopLayer != StyleTopLayer::None) {
*aPlaceholderType = PLACEHOLDER_FOR_TOPLAYER;
if (disp->mPosition == StylePositionProperty::Fixed) {
*aPlaceholderType |= PLACEHOLDER_FOR_FIXEDPOS;
return &mRealFixedList;
}
*aPlaceholderType |= PLACEHOLDER_FOR_ABSPOS;
return &mTopLayerAbsoluteList;
}
if (disp->mPosition == StylePositionProperty::Absolute) {
*aPlaceholderType = PLACEHOLDER_FOR_ABSPOS;
return &mAbsoluteList;
}
if (disp->mPosition == StylePositionProperty::Fixed) {
*aPlaceholderType = PLACEHOLDER_FOR_FIXEDPOS;
return mFixedList;
}
}
return nullptr;
}
void nsFrameConstructorState::ConstructBackdropFrameFor(nsIContent* aContent,
nsIFrame* aFrame) {
MOZ_ASSERT(aFrame->StyleDisplay()->mTopLayer == StyleTopLayer::Top);
nsContainerFrame* frame = do_QueryFrame(aFrame);
if (!frame) {
NS_WARNING("Cannot create backdrop frame for non-container frame");
return;
}
ComputedStyle* parentStyle = nsLayoutUtils::GetStyleFrame(aFrame)->Style();
RefPtr<ComputedStyle> style =
mPresShell->StyleSet()->ResolvePseudoElementStyle(
*aContent->AsElement(), PseudoStyleType::backdrop, nullptr,
parentStyle);
MOZ_ASSERT(style->StyleDisplay()->mTopLayer == StyleTopLayer::Top);
nsContainerFrame* parentFrame =
GetGeometricParent(*style->StyleDisplay(), nullptr);
nsBackdropFrame* backdropFrame =
new (mPresShell) nsBackdropFrame(style, mPresShell->GetPresContext());
backdropFrame->Init(aContent, parentFrame, nullptr);
nsFrameState placeholderType;
AbsoluteFrameList* frameList =
GetOutOfFlowFrameList(backdropFrame, true, true, &placeholderType);
MOZ_ASSERT(placeholderType & PLACEHOLDER_FOR_TOPLAYER);
nsIFrame* placeholder = nsCSSFrameConstructor::CreatePlaceholderFrameFor(
mPresShell, aContent, backdropFrame, frame, nullptr, placeholderType);
frame->SetInitialChildList(FrameChildListID::Backdrop,
nsFrameList(placeholder, placeholder));
frameList->AppendFrame(nullptr, backdropFrame);
}
void nsFrameConstructorState::AddChild(
nsIFrame* aNewFrame, nsFrameList& aFrameList, nsIContent* aContent,
nsContainerFrame* aParentFrame, bool aCanBePositioned, bool aCanBeFloated,
bool aInsertAfter, nsIFrame* aInsertAfterFrame) {
MOZ_ASSERT(!aNewFrame->GetNextSibling(), "Shouldn't happen");
nsFrameState placeholderType;
AbsoluteFrameList* outOfFlowFrameList = GetOutOfFlowFrameList(
aNewFrame, aCanBePositioned, aCanBeFloated, &placeholderType);
// The comments in GetGeometricParent regarding root table frames
// all apply here, unfortunately. Thus, we need to check whether
// the returned frame items really has containing block.
nsFrameList* frameList;
if (outOfFlowFrameList && outOfFlowFrameList->mContainingBlock) {
MOZ_ASSERT(aNewFrame->GetParent() == outOfFlowFrameList->mContainingBlock,
"Parent of the frame is not the containing block?");
frameList = outOfFlowFrameList;
} else {
frameList = &aFrameList;
placeholderType = nsFrameState(0);
}
if (placeholderType) {
NS_ASSERTION(frameList != &aFrameList,
"Putting frame in-flow _and_ want a placeholder?");
nsIFrame* placeholderFrame =
nsCSSFrameConstructor::CreatePlaceholderFrameFor(
mPresShell, aContent, aNewFrame, aParentFrame, nullptr,
placeholderType);
placeholderFrame->AddStateBits(mAdditionalStateBits);
// Add the placeholder frame to the flow
aFrameList.AppendFrame(nullptr, placeholderFrame);
if (placeholderType & PLACEHOLDER_FOR_TOPLAYER) {
ConstructBackdropFrameFor(aContent, aNewFrame);
}
}
#ifdef DEBUG
else {
NS_ASSERTION(aNewFrame->GetParent() == aParentFrame,
"In-flow frame has wrong parent");
}
#endif
if (aInsertAfter) {
frameList->InsertFrame(nullptr, aInsertAfterFrame, aNewFrame);
} else {
frameList->AppendFrame(nullptr, aNewFrame);
}
}
// Some of this function's callers recurse 1000 levels deep in crashtests. On
// platforms where stack limits are low, we can't afford to incorporate this
// function's `AutoTArray`s into its callers' stack frames, so disable inlining.
MOZ_NEVER_INLINE void nsFrameConstructorState::ProcessFrameInsertions(
AbsoluteFrameList& aFrameList, FrameChildListID aChildListID) {
MOZ_ASSERT(&aFrameList == &mFloatedList || &aFrameList == &mAbsoluteList ||
&aFrameList == &mTopLayerAbsoluteList ||
&aFrameList == &mAncestorFixedList || &aFrameList == mFixedList ||
&aFrameList == &mRealFixedList);
MOZ_ASSERT_IF(&aFrameList == &mFloatedList,
aChildListID == FrameChildListID::Float);
MOZ_ASSERT_IF(&aFrameList == &mAbsoluteList || &aFrameList == mFixedList,
aChildListID == FrameChildListID::Absolute ||
aChildListID == FrameChildListID::Fixed);
MOZ_ASSERT_IF(&aFrameList == &mTopLayerAbsoluteList,
aChildListID == FrameChildListID::Absolute);
MOZ_ASSERT_IF(&aFrameList == mFixedList && &aFrameList != &mAbsoluteList,
aChildListID == FrameChildListID::Fixed);
MOZ_ASSERT_IF(&aFrameList == &mAncestorFixedList,
aChildListID == FrameChildListID::Fixed);
MOZ_ASSERT_IF(&aFrameList == &mRealFixedList,
aChildListID == FrameChildListID::Fixed);
if (aFrameList.IsEmpty()) {
return;
}
nsContainerFrame* containingBlock = aFrameList.mContainingBlock;
NS_ASSERTION(containingBlock, "Child list without containing block?");
if (aChildListID == FrameChildListID::Fixed) {
// Put this frame on the transformed-frame's abs-pos list instead, if
// it has abs-pos children instead of fixed-pos children.
aChildListID = containingBlock->GetAbsoluteListID();
}
// Insert the frames hanging out in aItems. We can use SetInitialChildList()
// if the containing block hasn't been reflowed yet (so NS_FRAME_FIRST_REFLOW
// is set) and doesn't have any frames in the aChildListID child list yet.
const nsFrameList& childList = containingBlock->GetChildList(aChildListID);
if (childList.IsEmpty() &&
containingBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
// If we're injecting absolutely positioned frames, inject them on the
// absolute containing block
if (aChildListID == containingBlock->GetAbsoluteListID()) {
containingBlock->GetAbsoluteContainingBlock()->SetInitialChildList(
containingBlock, aChildListID, std::move(aFrameList));
} else {
containingBlock->SetInitialChildList(aChildListID, std::move(aFrameList));
}
} else if (aChildListID == FrameChildListID::Fixed ||
aChildListID == FrameChildListID::Absolute) {
// The order is not important for abs-pos/fixed-pos frame list, just
// append the frame items to the list directly.
mFrameConstructor->AppendFrames(containingBlock, aChildListID,
std::move(aFrameList));
} else {
// Note that whether the frame construction context is doing an append or
// not is not helpful here, since it could be appending to some frame in
// the middle of the document, which means we're not necessarily
// appending to the children of the containing block.
//
// We need to make sure the 'append to the end of document' case is fast.
// So first test the last child of the containing block
nsIFrame* lastChild = childList.LastChild();
// CompareTreePosition uses placeholder hierarchy for out of flow frames,
// so this will make out-of-flows respect the ordering of placeholders,
// which is great because it takes care of anonymous content.
nsIFrame* firstNewFrame = aFrameList.FirstChild();
// Cache the ancestor chain so that we can reuse it if needed.
AutoTArray<nsIFrame*, 20> firstNewFrameAncestors;
nsIFrame* notCommonAncestor = nullptr;
if (lastChild) {
notCommonAncestor = nsLayoutUtils::FillAncestors(
firstNewFrame, containingBlock, &firstNewFrameAncestors);
}
if (!lastChild || nsLayoutUtils::CompareTreePosition(
lastChild, firstNewFrame, firstNewFrameAncestors,
notCommonAncestor ? containingBlock : nullptr) < 0) {
// no lastChild, or lastChild comes before the new children, so just
// append
mFrameConstructor->AppendFrames(containingBlock, aChildListID,
std::move(aFrameList));
} else {
// Try the other children. First collect them to an array so that a
// reasonable fast binary search can be used to find the insertion point.
AutoTArray<nsIFrame*, 128> children;
for (nsIFrame* f = childList.FirstChild(); f != lastChild;
f = f->GetNextSibling()) {
children.AppendElement(f);
}
nsIFrame* insertionPoint = nullptr;
int32_t imin = 0;
int32_t max = children.Length();
while (max > imin) {
int32_t imid = imin + ((max - imin) / 2);
nsIFrame* f = children[imid];
int32_t compare = nsLayoutUtils::CompareTreePosition(
f, firstNewFrame, firstNewFrameAncestors,
notCommonAncestor ? containingBlock : nullptr);
if (compare > 0) {
// f is after the new frame.
max = imid;
insertionPoint = imid > 0 ? children[imid - 1] : nullptr;
} else if (compare < 0) {
// f is before the new frame.
imin = imid + 1;
insertionPoint = f;
} else {
// This is for the old behavior. Should be removed once it is
// guaranteed that CompareTreePosition can't return 0!
// See bug 928645.
NS_WARNING("Something odd happening???");
insertionPoint = nullptr;
for (uint32_t i = 0; i < children.Length(); ++i) {
nsIFrame* f = children[i];
if (nsLayoutUtils::CompareTreePosition(
f, firstNewFrame, firstNewFrameAncestors,
notCommonAncestor ? containingBlock : nullptr) > 0) {
break;
}
insertionPoint = f;
}
break;
}
}
mFrameConstructor->InsertFrames(containingBlock, aChildListID,
insertionPoint, std::move(aFrameList));
}
}
MOZ_ASSERT(aFrameList.IsEmpty(), "How did that happen?");
}
nsFrameConstructorSaveState::~nsFrameConstructorSaveState() {
// Restore the state
if (mList) {
MOZ_ASSERT(mState, "Can't have mList set without having a state!");
mState->ProcessFrameInsertions(*mList, mChildListID);
if (mList == &mState->mAbsoluteList) {
mState->mAbsoluteList = std::move(mSavedList);
mState->mFixedList = mSavedFixedList;
} else {
mState->mFloatedList = std::move(mSavedList);
}
MOZ_ASSERT(mSavedList.IsEmpty(),
"Frames in mSavedList should've moved back into mState!");
MOZ_ASSERT(!mList->LastChild() || !mList->LastChild()->GetNextSibling(),
"Something corrupted our list!");
}
}
/**
* Moves aFrameList from aOldParent to aNewParent. This updates the parent
* pointer of the frames in the list, and reparents their views as needed.
* nsIFrame::SetParent sets the NS_FRAME_HAS_VIEW bit on aNewParent and its
* ancestors as needed. Then it sets the list as the initial child list
* on aNewParent, unless aNewParent either already has kids or has been
* reflowed; in that case it appends the new frames. Note that this
* method differs from ReparentFrames in that it doesn't change the kids'
* style.
*/
// XXXbz Since this is only used for {ib} splits, could we just copy the view
// bits from aOldParent to aNewParent and then use the
// nsFrameList::ApplySetParent? That would still leave us doing two passes
// over the list, of course; if we really wanted to we could factor out the
// relevant part of ReparentFrameViewList, I suppose... Or just get rid of
// views, which would make most of this function go away.
static void MoveChildrenTo(nsIFrame* aOldParent, nsContainerFrame* aNewParent,
nsFrameList& aFrameList) {
#ifdef DEBUG
bool sameGrandParent = aOldParent->GetParent() == aNewParent->GetParent();
if (aNewParent->HasView() || aOldParent->HasView() || !sameGrandParent) {
// Move the frames into the new view
nsContainerFrame::ReparentFrameViewList(aFrameList, aOldParent, aNewParent);
}
#endif
aFrameList.ApplySetParent(aNewParent);
if (aNewParent->PrincipalChildList().IsEmpty() &&
aNewParent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
aNewParent->SetInitialChildList(FrameChildListID::Principal,
std::move(aFrameList));
} else {
aNewParent->AppendFrames(FrameChildListID::Principal,
std::move(aFrameList));
}
}
static void EnsureAutoPageName(nsFrameConstructorState& aState,
const nsContainerFrame* const aFrame) {
// Check if we need to figure out our used page name.
// When building the entire document, this should only happen for the
// root, which will mean the loop will immediately end. Either way, this will
// only happen once for each time the frame constructor is run.
if (aState.mAutoPageNameValue) {
return;
}
for (const nsContainerFrame* frame = aFrame; frame;
frame = frame->GetParent()) {
if (const nsAtom* maybePageName = frame->GetStylePageName()) {
aState.mAutoPageNameValue = maybePageName;
return;
}
}
// Ensure that a root with `page: auto` gets an empty page name
aState.mAutoPageNameValue = nsGkAtoms::_empty;
}
nsCSSFrameConstructor::AutoFrameConstructionPageName::
AutoFrameConstructionPageName(nsFrameConstructorState& aState,
nsIFrame* const aFrame)
: mState(aState), mNameToRestore(nullptr) {
if (!aState.mPresContext->IsPaginated()) {
MOZ_ASSERT(!aState.mAutoPageNameValue,
"Page name should not have been set");
return;
}
#ifdef DEBUG
MOZ_ASSERT(!aFrame->mWasVisitedByAutoFrameConstructionPageName,
"Frame should only have been visited once");
aFrame->mWasVisitedByAutoFrameConstructionPageName = true;
#endif
EnsureAutoPageName(aState, aFrame->GetParent());
mNameToRestore = aState.mAutoPageNameValue;
MOZ_ASSERT(mNameToRestore,
"Page name should have been found by EnsureAutoPageName");
if (const nsAtom* maybePageName = aFrame->GetStylePageName()) {
aState.mAutoPageNameValue = maybePageName;
}
aFrame->SetAutoPageValue(aState.mAutoPageNameValue);
}
nsCSSFrameConstructor::AutoFrameConstructionPageName::
~AutoFrameConstructionPageName() {
// This isn't actually useful when not in paginated layout, but it's very
// likely cheaper to unconditionally write this pointer than to test for
// paginated layout and then branch on the result.
mState.mAutoPageNameValue = mNameToRestore;
}
//----------------------------------------------------------------------
nsCSSFrameConstructor::nsCSSFrameConstructor(Document* aDocument,
PresShell* aPresShell)
: nsFrameManager(aPresShell),
mDocument(aDocument),
mFirstFreeFCItem(nullptr),
mFCItemsInUse(0),
mCurrentDepth(0),
mQuotesDirty(false),
mCountersDirty(false),
mAlwaysCreateFramesForIgnorableWhitespace(false) {
#ifdef DEBUG
static bool gFirstTime = true;
if (gFirstTime) {
gFirstTime = false;
char* flags = PR_GetEnv("GECKO_FRAMECTOR_DEBUG_FLAGS");
if (flags) {
bool error = false;
for (;;) {
char* comma = strchr(flags, ',');
if (comma) *comma = '\0';
bool found = false;
FrameCtorDebugFlags* flag = gFlags;
FrameCtorDebugFlags* limit = gFlags + NUM_DEBUG_FLAGS;
while (flag < limit) {
if (nsCRT::strcasecmp(flag->name, flags) == 0) {
*(flag->on) = true;
printf("nsCSSFrameConstructor: setting %s debug flag on\n",
flag->name);
found = true;
break;
}
++flag;
}
if (!found) error = true;
if (!comma) break;
*comma = ',';
flags = comma + 1;
}
if (error) {
printf("Here are the available GECKO_FRAMECTOR_DEBUG_FLAGS:\n");
FrameCtorDebugFlags* flag = gFlags;
FrameCtorDebugFlags* limit = gFlags + NUM_DEBUG_FLAGS;
while (flag < limit) {
printf(" %s\n", flag->name);
++flag;
}
printf(
"Note: GECKO_FRAMECTOR_DEBUG_FLAGS is a comma separated list of "
"flag\n");
printf("names (no whitespace)\n");
}
}
}
#endif
}
void nsCSSFrameConstructor::NotifyDestroyingFrame(nsIFrame* aFrame) {
if (aFrame->StyleDisplay()->IsContainStyle()) {
mContainStyleScopeManager.DestroyScopesFor(aFrame);
}
if (aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) &&
mContainStyleScopeManager.DestroyQuoteNodesFor(aFrame)) {
QuotesDirty();
}
if (aFrame->HasAnyStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE) &&
mContainStyleScopeManager.DestroyCounterNodesFor(aFrame)) {
// Technically we don't need to update anything if we destroyed only
// USE nodes. However, this is unlikely to happen in the real world
// since USE nodes generally go along with INCREMENT nodes.
CountersDirty();
}
RestyleManager()->NotifyDestroyingFrame(aFrame);
}
struct nsGenConInitializer {
UniquePtr<nsGenConNode> mNode;
nsGenConList* mList;
void (nsCSSFrameConstructor::*mDirtyAll)();
nsGenConInitializer(UniquePtr<nsGenConNode> aNode, nsGenConList* aList,
void (nsCSSFrameConstructor::*aDirtyAll)())
: mNode(std::move(aNode)), mList(aList), mDirtyAll(aDirtyAll) {}
};
already_AddRefed<nsIContent> nsCSSFrameConstructor::CreateGenConTextNode(
nsFrameConstructorState& aState, const nsAString& aString,
UniquePtr<nsGenConInitializer> aInitializer) {
RefPtr<nsTextNode> content = new (mDocument->NodeInfoManager())
nsTextNode(mDocument->NodeInfoManager());
content->SetText(aString, false);
if (aInitializer) {
aInitializer->mNode->mText = content;
content->SetProperty(nsGkAtoms::genConInitializerProperty,
aInitializer.release(),
nsINode::DeleteProperty<nsGenConInitializer>);
aState.mGeneratedContentWithInitializer.AppendElement(content);
}
return content.forget();
}
void nsCSSFrameConstructor::CreateGeneratedContent(
nsFrameConstructorState& aState, Element& aOriginatingElement,
ComputedStyle& aPseudoStyle, uint32_t aContentIndex,
const FunctionRef<void(nsIContent*)> aAddChild) {
using Type = StyleContentItem::Tag;
// Get the content value
const auto& item = aPseudoStyle.StyleContent()->ContentAt(aContentIndex);
const Type type = item.tag;
switch (type) {
case Type::Image: {
RefPtr c = GeneratedImageContent::Create(*mDocument, aContentIndex);
aAddChild(c);
return;
}
case Type::String: {
const auto string = item.AsString().AsString();
if (string.IsEmpty()) {
return;
}
RefPtr text =
CreateGenConTextNode(aState, NS_ConvertUTF8toUTF16(string), nullptr);
aAddChild(text);
return;
}
case Type::Attr: {
const auto& attr = item.AsAttr();
RefPtr<nsAtom> attrName = attr.attribute.AsAtom();
int32_t attrNameSpace = kNameSpaceID_None;
RefPtr<nsAtom> ns = attr.namespace_url.AsAtom();
if (!ns->IsEmpty()) {
nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(
ns.forget(), attrNameSpace);
NS_ENSURE_SUCCESS_VOID(rv);
}
if (mDocument->IsHTMLDocument() && aOriginatingElement.IsHTMLElement()) {
ToLowerCaseASCII(attrName);