Source code

Revision control

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 */
* 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/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLSharedListElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/EventStates.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/ServoBindings.h"
#include "mozilla/ServoStyleSetInlines.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 "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 "nsHTMLParts.h"
#include "nsUnicharUtils.h"
#include "nsViewManager.h"
#include "nsStyleConsts.h"
#ifdef MOZ_XUL
# include "nsXULElement.h"
#endif // MOZ_XUL
#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"
#include "ChildIterator.h"
#include "nsError.h"
#include "nsLayoutUtils.h"
#include "nsBoxFrame.h"
#include "nsBoxLayout.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 "DetailsFrame.h"
#ifdef MOZ_XUL
# include "nsIPopupContainer.h"
# include "nsAccessibilityService.h"
#include "nsMathMLParts.h"
#include "mozilla/dom/SVGAnimationElement.h"
#include "mozilla/dom/SVGFilters.h"
#include "mozilla/dom/SVGTests.h"
#include "mozilla/SVGGradientFrame.h"
#include "mozilla/SVGUtils.h"
#include "nsRefreshDriver.h"
#include "nsTextNode.h"
#include "ActiveLayerTracker.h"
using namespace mozilla;
using namespace mozilla::dom;
// An alias for convenience.
static const nsIFrame::ChildListID kPrincipalList = nsIFrame::kPrincipalList;
nsIFrame* NS_NewHTMLCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewHTMLVideoFrame(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);
#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]))
#ifdef MOZ_XUL
# include "nsMenuFrame.h"
# include "nsPopupSetFrame.h"
# include "nsTreeColFrame.h"
# include "nsXULLabelFrame.h"
nsContainerFrame* NS_NewRootBoxFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsContainerFrame* NS_NewDocElementBoxFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsIFrame* NS_NewDeckFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewLeafBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewStackFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewRangeFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewImageBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewGroupBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewButtonBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewMenuPopupFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewPopupSetFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewMenuFrame(PresShell* aPresShell, ComputedStyle* aStyle,
uint32_t aFlags);
nsIFrame* NS_NewMenuBarFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle);
// grid
nsresult NS_NewGridLayout2(nsBoxLayout** aNewLayout);
nsIFrame* NS_NewGridRowLeafFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewGridRowGroupFrame(PresShell* aPresShell, ComputedStyle* aStyle);
// end grid
nsIFrame* NS_NewTitleBarFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewResizerFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell,
ComputedStyle* aStyle, bool aIsRoot);
nsXULScrollFrame* NS_NewXULScrollFrame(PresShell* aPresShell,
ComputedStyle* aStyle, bool aIsRoot,
bool aClipAllDescendants);
nsIFrame* NS_NewSliderFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewScrollbarButtonFrame(PresShell* aPresShell,
ComputedStyle* aStyle);
nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*);
nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*);
// Returns true if aFrame is an anonymous flex/grid item.
static inline bool IsAnonymousFlexOrGridItem(const nsIFrame* aFrame) {
auto pseudoType = aFrame->Style()->GetPseudoType();
return pseudoType == PseudoStyleType::anonymousFlexItem ||
pseudoType == PseudoStyleType::anonymousGridItem;
// Returns true IFF the given nsIFrame is a nsFlexContainerFrame and
// represents a -webkit-{inline-}box or -moz-{inline-}box container.
static inline bool IsFlexContainerForLegacyBox(const nsIFrame* aFrame) {
return aFrame->IsFlexContainerFrame() &&
static void AssertAnonymousFlexOrGridItemParent(const nsIFrame* aChild,
const nsIFrame* aParent) {
"expected an anonymous flex or grid item child frame");
MOZ_ASSERT(aParent, "expected a parent frame");
auto pseudoType = aChild->Style()->GetPseudoType();
if (pseudoType == PseudoStyleType::anonymousFlexItem) {
"anonymous flex items should only exist as children "
"of flex container frames");
} else {
"anonymous grid items should only exist as children "
"of grid container frames");
# define AssertAnonymousFlexOrGridItemParent(x, y) PR_BEGIN_MACRO PR_END_MACRO
#define FCDATA_DECL(_flags, _func) \
{ _flags, {(FrameCreationFunc)_func}, nullptr, PseudoStyleType::NotPseudo }
#define FCDATA_WITH_WRAPPING_BLOCK(_flags, _func, _anon_box) \
{ \
{(FrameCreationFunc)_func}, nullptr, _anon_box \
#define SIMPLE_FCDATA(_func) FCDATA_DECL(0, _func)
#define FULL_CTOR_FCDATA(_flags, _func) \
{ \
_flags | FCDATA_FUNC_IS_FULL_CTOR, {nullptr}, _func, \
PseudoStyleType::NotPseudo \
* 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->IsFrameOfType(nsIFrame::eLineParticipant);
* 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->IsFrameOfType(nsIFrame::eSVG) ||
static bool IsLastContinuationForColumnContent(const nsIFrame* aFrame) {
return aFrame->Style()->GetPseudoType() == PseudoStyleType::columnContent &&
* 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->IsXULBoxFrame() ||
// 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() ||
// Need to suppress column-span under a different block formatting
// context or an out-of-flow frame.
// 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;
* If any children require a block parent, return the first such child.
* Otherwise return null.
static nsIContent* AnyKidsNeedBlockParent(nsIFrame* aFrameList) {
for (nsIFrame* k = aFrameList; k; k = k->GetNextSibling()) {
// Line participants, such as text and inline frames, can't be
// directly inside a XUL box; they must be wrapped in an
// intermediate block.
if (k->IsFrameOfType(nsIFrame::eLineParticipant)) {
return k->GetContent();
return nullptr;
// 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) {
// We reparent frames for two reasons: to put them inside ::first-line, and to
// put them inside some wrapper anonymous boxes.
if (aForceStyleReparent) {
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)) ||
"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(
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
"assigning ib-split sibling to other than first continuation!");
NS_ASSERTION(!aFrame->GetNextContinuation() ||
"should have no non-ib-split continuations here");
// Mark the frame as ib-split.
if (aIBSplitSibling) {
"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) {
"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) &&
aFrame = parentFrame;
} while (1);
// post-conditions
"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) {
"Should only be called if the frame has a multi-column ancestor!");
nsContainerFrame* current = aFrame->GetParent();
while (current &&
current->Style()->IsPseudoOrAnonBox())) {
current = current->GetParent();
"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();
// 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");
nsFrameList temp(aFrame, aFrame);
aParent->SetInitialChildList(kPrincipalList, temp);
// -----------------------------------------------------------
// Structure used when constructing formatting object trees. Contains
// state information needed for absolutely positioned elements
namespace mozilla {
struct AbsoluteFrameList : public nsFrameList {
// containing block for absolutely positioned elements
nsContainerFrame* containingBlock;
explicit AbsoluteFrameList(nsContainerFrame* aContainingBlock)
: containingBlock(aContainingBlock) {}
#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() {
"Dangling child list. Someone forgot to insert it?");
} // 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 {
typedef nsIFrame::ChildListID ChildListID;
AbsoluteFrameList* mList; // pointer to struct whose data we save/restore
AbsoluteFrameList mSavedList; // copy of original data
// The name of the child list in which our frames would belong
ChildListID mChildListID;
nsFrameConstructorState* mState;
// State used only when we're saving the abs-pos state for a transformed
// element.
AbsoluteFrameList mSavedFixedList;
bool mSavedFixedPosIsAbsPos;
friend class nsFrameConstructorState;
// Structure used for maintaining state information during the
// frame construction process
class MOZ_STACK_CLASS nsFrameConstructorState {
typedef nsIFrame::ChildListID ChildListID;
nsPresContext* mPresContext;
PresShell* mPresShell;
nsFrameManager* mFrameManager;
#ifdef MOZ_XUL
// Frames destined for the kPopupList.
AbsoluteFrameList mPopupList;
// Containing block information for out-of-flow frames.
AbsoluteFrameList mFixedList;
AbsoluteFrameList mAbsoluteList;
AbsoluteFrameList mFloatedList;
// The containing block of a frame in the top layer is defined by the
// spec: fixed-positioned frames are children of the viewport frame,
// and absolutely-positioned frames are children of the initial
// containing block. They would not be caught by any other containing
// block, e.g. frames with transform or filter.
AbsoluteFrameList mTopLayerFixedList;
AbsoluteFrameList mTopLayerAbsoluteList;
nsCOMPtr<nsILayoutHistoryState> mFrameState;
// These bits will be added to the state bits of any frame we construct
// using this state.
nsFrameState mAdditionalStateBits;
// When working with the transform and filter properties, we want to hook
// the abs-pos and fixed-pos lists together, since such
// elements are fixed-pos containing blocks. This flag determines
// whether or not we want to wire the fixed-pos and abs-pos lists
// together.
bool mFixedPosIsAbsPos;
// A boolean to indicate whether we have a "pending" popupgroup. That is, we
// have already created the FrameConstructionItem for the root popupgroup but
// we have not yet created the relevant frame.
bool mHavePendingPopupgroup;
// 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;
nsTArray<RefPtr<nsIContent>> mGeneratedContentWithInitializer;
// Constructor
// Use the passed-in history state.
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);
// 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 push the existing float containing block state and
// create a new scope. Code that uses this function should get matching
// logic in GetFloatContainingBlock.
// Pushing a null float containing block forbids any frames from being
// floated until a new float containing block is pushed.
// XXX we should get rid of null float containing blocks and teach the
// various frame classes to deal with floats instead.
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);
* 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
* @param aIsOutOfFlowPopup pass true if the frame is an out-of-flow popup
* (XUL-only)
void AddChild(nsIFrame* aNewFrame, nsFrameList& aFrameList,
nsIContent* aContent, nsContainerFrame* aParentFrame,
bool aCanBePositioned = true, bool aCanBeFloated = true,
bool aIsOutOfFlowPopup = false, 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 mFixedPosIsAbsPos ? mAbsoluteList : mFixedList;
const AbsoluteFrameList& GetFixedList() const {
return mFixedPosIsAbsPos ? mAbsoluteList : mFixedList;
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,
ChildListID 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,
bool aIsOutOfFlowPopup,
nsFrameState* aPlaceholderType);
void ConstructBackdropFrameFor(nsIContent* aContent, nsIFrame* aFrame);
PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
nsContainerFrame* aAbsoluteContainingBlock,
nsContainerFrame* aFloatContainingBlock,
already_AddRefed<nsILayoutHistoryState> aHistoryState)
: mPresContext(aPresShell->GetPresContext()),
#ifdef MOZ_XUL
// See PushAbsoluteContaningBlock below
// If the fixed-pos containing block is equal to the abs-pos containing
// block, use the abs-pos containing block's abs-pos list for fixed-pos
// frames.
mFixedPosIsAbsPos(aFixedContainingBlock == aAbsoluteContainingBlock),
mCreatingExtraFrames(false) {
#ifdef MOZ_XUL
nsIPopupContainer* popupContainer =
if (popupContainer) {
mPopupList.containingBlock = popupContainer->GetPopupSetFrame();
PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
nsContainerFrame* aAbsoluteContainingBlock,
nsContainerFrame* aFloatContainingBlock)
: nsFrameConstructorState(
aPresShell, aFixedContainingBlock, aAbsoluteContainingBlock,
aPresShell->GetDocument()->GetLayoutHistoryState()) {}
nsFrameConstructorState::~nsFrameConstructorState() {
for (auto& content : Reversed(mGeneratedContentWithInitializer)) {
void nsFrameConstructorState::ProcessFrameInsertionsForAllLists() {
ProcessFrameInsertions(mTopLayerFixedList, nsIFrame::kFixedList);
ProcessFrameInsertions(mTopLayerAbsoluteList, nsIFrame::kAbsoluteList);
ProcessFrameInsertions(mFloatedList, nsIFrame::kFloatList);
ProcessFrameInsertions(mAbsoluteList, nsIFrame::kAbsoluteList);
ProcessFrameInsertions(mFixedList, nsIFrame::kFixedList);
#ifdef MOZ_XUL
ProcessFrameInsertions(mPopupList, nsIFrame::kPopupList);
void nsFrameConstructorState::PushAbsoluteContainingBlock(
nsContainerFrame* aNewAbsoluteContainingBlock, nsIFrame* aPositionedFrame,
nsFrameConstructorSaveState& aSaveState) {
aSaveState.mList = &mAbsoluteList;
aSaveState.mSavedList = mAbsoluteList;
aSaveState.mChildListID = nsIFrame::kAbsoluteList;
aSaveState.mState = this;
aSaveState.mSavedFixedPosIsAbsPos = mFixedPosIsAbsPos;
if (mFixedPosIsAbsPos) {
// Since we're going to replace mAbsoluteList, we need to save it into
// mFixedList now (and save the current value of mFixedList).
aSaveState.mSavedFixedList = mFixedList;
mFixedList = mAbsoluteList;
mAbsoluteList = AbsoluteFrameList(aNewAbsoluteContainingBlock);
/* See if we're wiring the fixed-pos and abs-pos lists together. This happens
* iff we're a transformed element.
mFixedPosIsAbsPos =
aPositionedFrame && aPositionedFrame->IsFixedPosContainingBlock();
if (aNewAbsoluteContainingBlock) {
void nsFrameConstructorState::PushFloatContainingBlock(
nsContainerFrame* aNewFloatContainingBlock,
nsFrameConstructorSaveState& aSaveState) {
MOZ_ASSERT(!aNewFloatContainingBlock ||
"Please push a real float containing block!");
!aNewFloatContainingBlock ||
"We should not push a frame that is supposed to _suppress_ "
"floats as a float containing block!");
aSaveState.mList = &mFloatedList;
aSaveState.mSavedList = mFloatedList;
aSaveState.mChildListID = nsIFrame::kFloatList;
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 &&
SVGUtils::IsInSVGTextSubtree(aContentParentFrame)) {
return aContentParentFrame;
if (aStyleDisplay.IsFloatingStyle() && mFloatedList.containingBlock) {
"Absolutely positioned _and_ floating?");
return mFloatedList.containingBlock;
if (aStyleDisplay.mTopLayer != StyleTopLayer::None) {
MOZ_ASSERT(aStyleDisplay.mTopLayer == StyleTopLayer::Top,
"-moz-top-layer should be either none or top");
"Top layer items should always be absolutely positioned");
if (aStyleDisplay.mPosition == StylePositionProperty::Fixed) {
MOZ_ASSERT(mTopLayerFixedList.containingBlock, "No root frame?");
return mTopLayerFixedList.containingBlock;
MOZ_ASSERT(aStyleDisplay.mPosition == StylePositionProperty::Absolute);
return mTopLayerAbsoluteList.containingBlock;
if (aStyleDisplay.mPosition == StylePositionProperty::Absolute &&
mAbsoluteList.containingBlock) {
return mAbsoluteList.containingBlock;
if (aStyleDisplay.mPosition == StylePositionProperty::Fixed &&
GetFixedList().containingBlock) {
return GetFixedList().containingBlock;
return aContentParentFrame;
void nsFrameConstructorState::ReparentAbsoluteItems(
nsContainerFrame* aNewParent) {
// Bug 1491727: This function might not conform to the spec. See
"Restrict the usage under column hierarchy.");
nsFrameList newAbsoluteItems;
nsIFrame* current = mAbsoluteList.FirstChild();
while (current) {
nsIFrame* placeholder = current->GetPlaceholderFrame();
if (nsLayoutUtils::IsProperAncestorFrame(aNewParent, placeholder)) {
nsIFrame* next = current->GetNextSibling();
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);
AbsoluteFrameList* nsFrameConstructorState::GetOutOfFlowFrameList(
nsIFrame* aNewFrame, bool aCanBePositioned, bool aCanBeFloated,
bool aIsOutOfFlowPopup, nsFrameState* aPlaceholderType) {
#ifdef MOZ_XUL
if (MOZ_UNLIKELY(aIsOutOfFlowPopup)) {
MOZ_ASSERT(mPopupList.containingBlock, "Must have a popup set frame!");
*aPlaceholderType = PLACEHOLDER_FOR_POPUP;
return &mPopupList;
#endif // MOZ_XUL
if (aCanBeFloated && aNewFrame->IsFloating()) {
*aPlaceholderType = PLACEHOLDER_FOR_FLOAT;
return &mFloatedList;
if (aCanBePositioned) {
const nsStyleDisplay* disp = aNewFrame->StyleDisplay();
if (disp->mTopLayer != StyleTopLayer::None) {
if (disp->mPosition == StylePositionProperty::Fixed) {
return &mTopLayerFixedList;
*aPlaceholderType |= PLACEHOLDER_FOR_ABSPOS;
return &mTopLayerAbsoluteList;
if (disp->mPosition == StylePositionProperty::Absolute) {
return &mAbsoluteList;
if (disp->mPosition == StylePositionProperty::Fixed) {
return &GetFixedList();
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");
RefPtr<ComputedStyle> style =
*aContent->AsElement(), PseudoStyleType::backdrop,
/* aParentStyle */ nullptr);
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, false, &placeholderType);
nsIFrame* placeholder = nsCSSFrameConstructor::CreatePlaceholderFrameFor(
mPresShell, aContent, backdropFrame, frame, nullptr, placeholderType);
nsFrameList temp(placeholder, placeholder);
frame->SetInitialChildList(nsIFrame::kBackdropList, temp);
frameList->AppendFrame(nullptr, backdropFrame);
void nsFrameConstructorState::AddChild(
nsIFrame* aNewFrame, nsFrameList& aFrameList, nsIContent* aContent,
nsContainerFrame* aParentFrame, bool aCanBePositioned, bool aCanBeFloated,
bool aIsOutOfFlowPopup, bool aInsertAfter, nsIFrame* aInsertAfterFrame) {
MOZ_ASSERT(!aNewFrame->GetNextSibling(), "Shouldn't happen");
nsFrameState placeholderType;
AbsoluteFrameList* outOfFlowFrameList =
GetOutOfFlowFrameList(aNewFrame, aCanBePositioned, aCanBeFloated,
aIsOutOfFlowPopup, &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->containingBlock) {
MOZ_ASSERT(aNewFrame->GetParent() == outOfFlowFrameList->containingBlock,
"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 =
mPresShell, aContent, aNewFrame, aParentFrame, nullptr,
// 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");
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, ChildListID aChildListID) {
(&aFrameList == &mFloatedList && aChildListID == nsIFrame::kFloatList) || \
((&aFrameList == &mAbsoluteList || \
&aFrameList == &mTopLayerAbsoluteList) && \
aChildListID == nsIFrame::kAbsoluteList) || \
((&aFrameList == &mFixedList || &aFrameList == &mTopLayerFixedList) && \
aChildListID == nsIFrame::kFixedList)
#ifdef MOZ_XUL
MOZ_ASSERT(NS_NONXUL_LIST_TEST || (&aFrameList == &mPopupList &&
aChildListID == nsIFrame::kPopupList),
"Unexpected aFrameList/aChildListID combination");
"Unexpected aFrameList/aChildListID combination");
if (aFrameList.IsEmpty()) {
nsContainerFrame* containingBlock = aFrameList.containingBlock;
NS_ASSERTION(containingBlock, "Child list without containing block?");
if (aChildListID == nsIFrame::kFixedList) {
// 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, aChildListID, aFrameList);
} else {
containingBlock->SetInitialChildList(aChildListID, aFrameList);
} else if (aChildListID == nsIFrame::kFixedList ||
aChildListID == nsIFrame::kAbsoluteList) {
// The order is not important for abs-pos/fixed-pos frame list, just
// append the frame items to the list directly.
mFrameManager->AppendFrames(containingBlock, aChildListID, 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
mFrameManager->AppendFrames(containingBlock, aChildListID, 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()) {
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) {
insertionPoint = f;
mFrameManager->InsertFrames(containingBlock, aChildListID, insertionPoint,
MOZ_ASSERT(aFrameList.IsEmpty(), "How did that happen?");
: mList(nullptr),
mSavedFixedPosIsAbsPos(false) {}
nsFrameConstructorSaveState::~nsFrameConstructorSaveState() {
// Restore the state
if (mList) {
NS_ASSERTION(mState, "Can't have mList set without having a state!");
mState->ProcessFrameInsertions(*mList, mChildListID);
*mList = mSavedList;
#ifdef DEBUG
// We've transferred the child list, so drop the pointer we held to it.
// Note that this only matters for the assert in ~AbsoluteFrameList.
if (mList == &mState->mAbsoluteList) {
mState->mFixedPosIsAbsPos = mSavedFixedPosIsAbsPos;
if (mSavedFixedPosIsAbsPos) {
// mAbsoluteList was moved to mFixedList, so move mFixedList back
// and repair the old mFixedList now.
mState->mAbsoluteList = mState->mFixedList;
mState->mFixedList = mSavedFixedList;
#ifdef DEBUG
NS_ASSERTION(!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) {
bool sameGrandParent = aOldParent->GetParent() == aNewParent->GetParent();
if (aNewParent->HasView() || aOldParent->HasView() || !sameGrandParent) {
// Move the frames into the new view
nsContainerFrame::ReparentFrameViewList(aFrameList, aOldParent, aNewParent);
for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
if (aNewParent->PrincipalChildList().IsEmpty() &&
aNewParent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
aNewParent->SetInitialChildList(kPrincipalList, aFrameList);
} else {
aNewParent->AppendFrames(kPrincipalList, aFrameList);
static bool ShouldCreateImageFrameForContent(const Element& aElement,
ComputedStyle& aStyle) {
if (aElement.IsRootOfNativeAnonymousSubtree()) {
return false;
auto& content = aStyle.StyleContent()->mContent;
if (!content.IsItems()) {
return false;
Span<const StyleContentItem> items = content.AsItems().AsSpan();
return items.Length() == 1 && items[0].IsUrl();
nsCSSFrameConstructor::nsCSSFrameConstructor(Document* aDocument,
PresShell* aPresShell)
: nsFrameManager(aPresShell),
mAlwaysCreateFramesForIgnorableWhitespace(false) {
#ifdef DEBUG
static bool gFirstTime = true;
if (gFirstTime) {
gFirstTime = false;
if (flags) {
bool error = false;
for (;;) {
char* comma = PL_strchr(flags, ',');
if (comma) *comma = '\0';
bool found = false;
FrameCtorDebugFlags* flag = gFlags;
FrameCtorDebugFlags* limit = gFlags + NUM_DEBUG_FLAGS;
while (flag < limit) {
if (PL_strcasecmp(flag->name, flags) == 0) {
*(flag->on) = true;
printf("nsCSSFrameConstructor: setting %s debug flag on\n",
found = true;
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);
"Note: GECKO_FRAMECTOR_DEBUG_FLAGS is a comma separated list of "
printf("names (no whitespace)\n");
void nsCSSFrameConstructor::NotifyDestroyingFrame(nsIFrame* aFrame) {
if (aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) {
if (mQuoteList.DestroyNodesFor(aFrame)) QuotesDirty();
if (aFrame->HasAnyStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE) &&
mCounterManager.DestroyNodesFor(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.
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 nsString& aString,
UniquePtr<nsGenConInitializer> aInitializer) {
RefPtr<nsTextNode> content = new (mDocument->NodeInfoManager())
content->SetText(aString, false);
if (aInitializer) {
aInitializer->mNode->mText = content;
return content.forget();
already_AddRefed<nsIContent> nsCSSFrameConstructor::CreateGeneratedContent(
nsFrameConstructorState& aState, const Element& aOriginatingElement,
ComputedStyle& aPseudoStyle, uint32_t aContentIndex) {
using Type = StyleContentItem::Tag;
// Get the content value
const auto& item = aPseudoStyle.StyleContent()->ContentAt(aContentIndex);
const Type type = item.tag;
switch (type) {
case Type::Url:
return GeneratedImageContent::Create(*mDocument, aContentIndex);
case Type::String:
return CreateGenConTextNode(
aState, NS_ConvertUTF8toUTF16(item.AsString().AsString()), nullptr);
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 = nsContentUtils::NameSpaceManager()->RegisterNameSpace(
ns.forget(), attrNameSpace);
NS_ENSURE_SUCCESS(rv, nullptr);
if (mDocument->IsHTMLDocument() && aOriginatingElement.IsHTMLElement()) {
nsCOMPtr<nsIContent> content;
NS_NewAttributeContent(mDocument->NodeInfoManager(), attrNameSpace,
attrName, getter_AddRefs(content));
return content.forget();
case Type::Counter:
case Type::Counters: {
RefPtr<nsAtom> name;
CounterStylePtr ptr;
nsString separator;
if (type == Type::Counter) {
auto& counter = item.AsCounter();
name = counter._0.AsAtom();
ptr = CounterStylePtr::FromStyle(counter._1);
} else {
auto& counters = item.AsCounters();
name = counters._0.AsAtom();
CopyUTF8toUTF16(counters._1.AsString(), separator);
ptr = CounterStylePtr::FromStyle(counters._2);
nsCounterList* counterList = mCounterManager.CounterListFor(name);
auto node = MakeUnique<nsCounterUseNode>(
std::move(ptr), std::move(separator), aContentIndex,
/* aAllCounters = */ type == Type::Counters);
auto initializer = MakeUnique<nsGenConInitializer>(
std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty);
return CreateGenConTextNode(aState, u""_ns, std::move(initializer));
case Type::OpenQuote:
case Type::CloseQuote:
case Type::NoOpenQuote:
case Type::NoCloseQuote: {
auto node = MakeUnique<nsQuoteNode>(type, aContentIndex);
auto initializer = MakeUnique<nsGenConInitializer>(
std::move(node), &mQuoteList, &nsCSSFrameConstructor::QuotesDirty);
return CreateGenConTextNode(aState, u""_ns, std::move(initializer));
case Type::MozAltContent: {
// Use the "alt" attribute; if that fails and the node is an HTML
// <input>, try the value attribute and then fall back to some default
// localized text we have.
// XXX what if the 'alt' attribute is added later, how will we
// detect that and do the right thing here?
if (aOriginatingElement.HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
nsCOMPtr<nsIContent> content;
NS_NewAttributeContent(mDocument->NodeInfoManager(), kNameSpaceID_None,
nsGkAtoms::alt, getter_AddRefs(content));
return content.forget();
if (aOriginatingElement.IsHTMLElement(nsGkAtoms::input)) {
if (aOriginatingElement.HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
nsCOMPtr<nsIContent> content;
kNameSpaceID_None, nsGkAtoms::value,
return content.forget();
nsAutoString temp;
nsContentUtils::eFORMS_PROPERTIES, "Submit", mDocument, temp);
return CreateGenConTextNode(aState, temp, nullptr);
return nullptr;
* aParentFrame - the frame that should be the parent of the generated
* content. This is the frame for the corresponding content node,
* which must not be a leaf frame.
* Any items created are added to aItems.
* We create an XML element (tag _moz_generated_content_before/after/marker)
* representing the pseudoelement. We create a DOM node for each 'content'
* item and make those nodes the children of the XML element. Then we create
* a frame subtree for the XML element as if it were a regular child of
* aParentFrame/aParentContent, giving the XML element the ::before, ::after
* or ::marker style.
void nsCSSFrameConstructor::CreateGeneratedContentItem(
nsFrameConstructorState& aState, nsContainerFrame* aParentFrame,
Element& aOriginatingElement, ComputedStyle& aStyle,
PseudoStyleType aPseudoElement, FrameConstructionItemList& aItems) {
MOZ_ASSERT(aPseudoElement == PseudoStyleType::before ||
aPseudoElement == PseudoStyleType::after ||
aPseudoElement == PseudoStyleType::marker,
"unexpected aPseudoElement");
if (aParentFrame && (aParentFrame->IsHTMLVideoFrame() ||
aParentFrame->IsDateTimeControlFrame())) {
// Video frames and date time control frames may not be leafs when backed by
// an UA widget, but we still don't want to expose generated content.
ServoStyleSet* styleSet = mPresShell->StyleSet();
// Probe for the existence of the pseudo-element
RefPtr<ComputedStyle> pseudoStyle = styleSet->ProbePseudoElementStyle(
aOriginatingElement, aPseudoElement, &aStyle);
if (!pseudoStyle) {
nsAtom* elemName = nullptr;
nsAtom* property = nullptr;
switch (aPseudoElement) {
case PseudoStyleType::before:
elemName = nsGkAtoms::mozgeneratedcontentbefore;
property = nsGkAtoms::beforePseudoProperty;
case PseudoStyleType::after:
elemName = nsGkAtoms::mozgeneratedcontentafter;
property = nsGkAtoms::afterPseudoProperty;
case PseudoStyleType::marker:
// We want to get a marker style even if we match no rules, but we still
// want to check the result of GeneratedContentPseudoExists.
elemName = nsGkAtoms::mozgeneratedcontentmarker;
property = nsGkAtoms::markerPseudoProperty;
MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement");
// |ProbePseudoStyleFor| checked the 'display' property and the
// |ContentCount()| of the 'content' property for us.
RefPtr<NodeInfo> nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(
elemName, nullptr, kNameSpaceID_None, nsINode::ELEMENT_NODE);
RefPtr<Element> container;
nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget());
if (NS_FAILED(rv)) {
// Cleared when the pseudo is unbound from the tree, so no need to store a
// strong reference, nor a destructor.
aOriginatingElement.SetProperty(property, container.get());
BindContext context(aOriginatingElement, BindContext::ForNativeAnonymous);
rv = container->BindToTree(context, aOriginatingElement);
if (NS_FAILED(rv)) {
// Servo has already eagerly computed the style for the container, so we can
// just stick the style on the element and avoid an additional traversal.
// We don't do this for pseudos that may trigger animations or transitions,
// since those need to be kicked off by the traversal machinery.
// Note that when a pseudo-element animates, we flag the originating element,
// so we check that flag, but we could also a more expensive (but exhaustive)
// check using EffectSet::GetEffectSet, for example.
if (!Servo_ComputedValues_SpecifiesAnimationsOrTransitions(pseudoStyle) &&
!aOriginatingElement.MayHaveAnimations()) {
Servo_SetExplicitStyle(container, pseudoStyle);
} else {
// If animations are involved, we avoid the SetExplicitStyle optimization
// above. We need to grab style with animations from the pseudo element and
// replace old one.
pseudoStyle = ServoStyleSet::ResolveServoStyle(*container);
uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount();
for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) {
nsCOMPtr<nsIContent> content = CreateGeneratedContent(
aState, aOriginatingElement, *pseudoStyle, contentIndex);
if (!content) {
// We don't strictly have to set NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE
// here; it would get set under AppendChildTo. But AppendChildTo might
// think that we're going from not being anonymous to being anonymous and
// do some extra work; setting the flag here avoids that.
container->AppendChildTo(content, false);
if (auto* element = Element::FromNode(content)) {
// If we created any children elements, Servo needs to traverse them, but
// the root is already set up.
AddFrameConstructionItemsInternal(aState, container, aParentFrame, true,
pseudoStyle, {ItemFlag::IsGeneratedContent},
// The term pseudo frame is being used instead of anonymous frame, since
// anonymous frame has been used elsewhere to refer to frames that have
// generated content
// Return whether the given frame is a table pseudo-frame. Note that
// cell-content and table-outer frames have pseudo-types, but are always
// created, even for non-anonymous cells and tables respectively. So for those
// we have to examine the cell or table frame to see whether it's a pseudo
// frame. In particular, a lone table caption will have a table wrapper as its
// parent, but will also trigger construction of an empty inner table, which
// will be the one we can examine to see whether the wrapper was a pseudo-frame.
static bool IsTablePseudo(nsIFrame* aFrame) {
auto pseudoType = aFrame->Style()->GetPseudoType();
return pseudoType != PseudoStyleType::NotPseudo &&
(pseudoType == PseudoStyleType::table ||
pseudoType == PseudoStyleType::inlineTable ||
pseudoType == PseudoStyleType::tableColGroup ||
pseudoType == PseudoStyleType::tableRowGroup ||
pseudoType == PseudoStyleType::tableRow ||
pseudoType == PseudoStyleType::tableCell ||
(pseudoType == PseudoStyleType::cellContent &&
aFrame->GetParent()->Style()->GetPseudoType() ==
PseudoStyleType::tableCell) ||
(pseudoType == PseudoStyleType::tableWrapper &&
->GetPseudoType() == PseudoStyleType::table ||
->GetPseudoType() == PseudoStyleType::inlineTable)));
static bool IsRubyPseudo(nsIFrame* aFrame) {
return RubyUtils::IsRubyPseudo(aFrame->Style()->GetPseudoType());
static bool IsTableOrRubyPseudo(nsIFrame* aFrame) {
return IsTablePseudo(aFrame) || IsRubyPseudo(aFrame);
/* static */
nsCSSFrameConstructor::ParentType nsCSSFrameConstructor::GetParentType(
LayoutFrameType aFrameType) {
if (aFrameType == LayoutFrameType::Table) {
return eTypeTable;
if (aFrameType == LayoutFrameType::TableRowGroup) {
return eTypeRowGroup;
if (aFrameType == LayoutFrameType::TableRow) {
return eTypeRow;
if (aFrameType == LayoutFrameType::TableColGroup) {
return eTypeColGroup;
if (aFrameType == LayoutFrameType::RubyBaseContainer) {
return eTypeRubyBaseContainer;
if (aFrameType == LayoutFrameType::RubyTextContainer) {
return eTypeRubyTextContainer;
if (aFrameType == LayoutFrameType::Ruby) {
return eTypeRuby;
return eTypeBlock;
// Pull all the captions present in aItems out into aCaptions.
static void PullOutCaptionFrames(nsFrameList& aList, nsFrameList& aCaptions) {
nsIFrame* child = aList.FirstChild();
while (child) {
nsIFrame* nextSibling = child->GetNextSibling();
if (child->StyleDisplay()->mDisplay == StyleDisplay::TableCaption) {
aCaptions.AppendFrame(nullptr, child);
child = nextSibling;
// Construct the outer, inner table frames and the children frames for the
// table.
// XXX Page break frames for pseudo table frames are not constructed to avoid
// the risk associated with revising the pseudo frame mechanism. The long term
// solution of having frames handle page-break-before/after will solve the
// problem.
nsIFrame* nsCSSFrameConstructor::ConstructTable(nsFrameConstructorState& aState,
FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame,
const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::Table ||
aDisplay->mDisplay == StyleDisplay::InlineTable,
"Unexpected call");
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
const bool isMathMLContent = content->IsMathMLElement();
// create the pseudo SC for the table wrapper as a child of the inner SC
RefPtr<ComputedStyle> outerComputedStyle;
outerComputedStyle =
PseudoStyleType::tableWrapper, computedStyle);
// Create the table wrapper frame which holds the caption and inner table
// frame
nsContainerFrame* newFrame;
if (isMathMLContent)
newFrame = NS_NewMathMLmtableOuterFrame(mPresShell, outerComputedStyle);
newFrame = NS_NewTableWrapperFrame(mPresShell, outerComputedStyle);
nsContainerFrame* geometricParent = aState.GetGeometricParent(
*outerComputedStyle->StyleDisplay(), aParentFrame);
// Init the table wrapper frame
InitAndRestoreFrame(aState, content, geometricParent, newFrame);
// Create the inner table frame
nsContainerFrame* innerFrame;
if (isMathMLContent)
innerFrame = NS_NewMathMLmtableFrame(mPresShell, computedStyle);
innerFrame = NS_NewTableFrame(mPresShell, computedStyle);
InitAndRestoreFrame(aState, content, newFrame, innerFrame);
// Put the newly created frames into the right child list
SetInitialSingleChild(newFrame, innerFrame);
aState.AddChild(newFrame, aFrameList, content, aParentFrame);
if (!mRootElementFrame) {
// The frame we're constructing will be the root element frame.
SetRootElementFrameAndConstructCanvasAnonContent(newFrame, aState,
nsFrameList childList;
// Process children
nsFrameConstructorSaveState absoluteSaveState;
const nsStyleDisplay* display = outerComputedStyle->StyleDisplay();
// Mark the table frame as an absolute container if needed
if (display->IsAbsPosContainingBlock(newFrame)) {
aState.PushAbsoluteContainingBlock(newFrame, newFrame, absoluteSaveState);
if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
aState, aItem.mChildItems, innerFrame,
aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
} else {
ProcessChildren(aState, content, computedStyle, innerFrame, true, childList,
nsFrameList captionList;
PullOutCaptionFrames(childList, captionList);
// Set the inner table frame's initial primary list
innerFrame->SetInitialChildList(kPrincipalList, childList);
// Set the table wrapper frame's secondary childlist lists
if (captionList.NotEmpty()) {
newFrame->SetInitialChildList(nsIFrame::kCaptionList, captionList);
return newFrame;
static void MakeTablePartAbsoluteContainingBlockIfNeeded(
nsFrameConstructorState& aState, const nsStyleDisplay* aDisplay,
nsFrameConstructorSaveState& aAbsSaveState, nsContainerFrame* aFrame) {
// If we're positioned, then we need to become an absolute containing block
// for any absolutely positioned children and register for post-reflow fixup.
// Note that usually if a frame type can be an absolute containing block, we
// always set NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN, whether it actually is or
// not. However, in this case flag serves the additional purpose of indicating
// that the frame was registered with its table frame. This allows us to avoid
// the overhead of unregistering the frame in most cases.
if (aDisplay->IsAbsPosContainingBlock(aFrame)) {
aState.PushAbsoluteContainingBlock(aFrame, aFrame, aAbsSaveState);