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
/*
* 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 "ActiveLayerTracker.h"
#include "ChildIterator.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/ComputedStyleInlines.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CharacterData.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/GeneratedImageContent.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "mozilla/dom/HTMLSharedListElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Likely.h"
#include "mozilla/LinkedList.h"
#include "mozilla/ManualNAC.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/RestyleManager.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScrollContainerFrame.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/SVGGradientFrame.h"
#include "mozilla/Unused.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsAtom.h"
#include "nsAutoLayoutPhase.h"
#include "nsBackdropFrame.h"
#include "nsBlockFrame.h"
#include "nsCanvasFrame.h"
#include "nsCheckboxRadioFrame.h"
#include "nsComboboxControlFrame.h"
#include "nsContainerFrame.h"
#include "nsContentUtils.h"
#include "nsCRT.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSPseudoElements.h"
#include "nsError.h"
#include "nsFieldSetFrame.h"
#include "nsFirstLetterFrame.h"
#include "nsFlexContainerFrame.h"
#include "nsGkAtoms.h"
#include "nsGridContainerFrame.h"
#include "nsHTMLParts.h"
#include "nsIAnonymousContentCreator.h"
#include "nsIFormControl.h"
#include "nsIFrameInlines.h"
#include "nsImageFrame.h"
#include "nsInlineFrame.h"
#include "nsIObjectLoadingContent.h"
#include "nsIPopupContainer.h"
#include "nsIScriptError.h"
#include "nsLayoutUtils.h"
#include "nsListControlFrame.h"
#include "nsMathMLParts.h"
#include "nsNameSpaceManager.h"
#include "nsPageContentFrame.h"
#include "nsPageFrame.h"
#include "nsPageSequenceFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
#include "nsRefreshDriver.h"
#include "nsRubyBaseContainerFrame.h"
#include "nsRubyBaseFrame.h"
#include "nsRubyFrame.h"
#include "nsRubyTextContainerFrame.h"
#include "nsRubyTextFrame.h"
#include "nsStyleConsts.h"
#include "nsStyleStructInlines.h"
#include "nsTableCellFrame.h"
#include "nsTableColFrame.h"
#include "nsTableFrame.h"
#include "nsTableRowFrame.h"
#include "nsTableRowGroupFrame.h"
#include "nsTableWrapperFrame.h"
#include "nsTArray.h"
#include "nsTextFragment.h"
#include "nsTextNode.h"
#include "nsTransitionManager.h"
#include "nsUnicharUtils.h"
#include "nsViewManager.h"
#include "nsXULElement.h"
#include "RetainedDisplayListBuilder.h"
#include "RubyUtils.h"
#include "StickyScrollContainer.h"
#ifdef XP_MACOSX
# include "nsIDocShell.h"
#endif
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.h"
#endif
#undef NOISY_FIRST_LETTER
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);
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*);
nsIFrame* NS_NewImageFrameForViewTransition(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->IsLegacyWebkitBox();
}
#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 (true);
// 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,
// ScrollContainerFrame 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;
}
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::Auto) {
// 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
//
// 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::Auto,
"-moz-top-layer should be either none or auto");
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) {
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::Auto);
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::Auto);
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!
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),
mRemovingContent(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, const StyleContentItem& aItem,
size_t aContentIndex, const FunctionRef<void(nsIContent*)> aAddChild) {
using Type = StyleContentItem::Tag;
// Get the content value
const Type type = aItem.tag;
switch (type) {
case Type::Image: {
RefPtr c = GeneratedImageContent::Create(*mDocument, aContentIndex);
aAddChild(c);
return;
}
case Type::String: {
const auto string = aItem.AsString().AsString();
if (string.IsEmpty()) {
return;
}
RefPtr text =
CreateGenConTextNode(aState, NS_ConvertUTF8toUTF16(string), nullptr);
aAddChild(text);
return;
}
case Type::Attr: {
const auto& attr = aItem.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);
}
RefPtr<nsAtom> fallback = attr.fallback.AsAtom();
nsCOMPtr<nsIContent> content;
NS_NewAttributeContent(mDocument->NodeInfoManager(), attrNameSpace,
attrName, fallback, getter_AddRefs(content));
aAddChild(content);
return;
}
case Type::Counter:
case Type::Counters: {
RefPtr<nsAtom> name;
const StyleCounterStyle* style;
nsString separator;
if (type == Type::Counter) {
const auto& counter = aItem.AsCounter();
name = counter._0.AsAtom();
style = &counter._1;
} else {
const auto& counters = aItem.AsCounters();
name = counters._0.AsAtom();
CopyUTF8toUTF16(counters._1.AsString(), separator);
style = &counters._2;
}
auto* counterList = mContainStyleScopeManager.GetOrCreateCounterList(
aOriginatingElement, name);
auto node = MakeUnique<nsCounterUseNode>(
*style, std::move(separator), aContentIndex,
/* aAllCounters = */ type == Type::Counters);
auto initializer = MakeUnique<nsGenConInitializer>(
std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty);
RefPtr c = CreateGenConTextNode(aState, u""_ns, std::move(initializer));
aAddChild(c);
return;
}
case Type::OpenQuote:
case Type::CloseQuote:
case Type::NoOpenQuote:
case Type::NoCloseQuote: {
auto node = MakeUnique<nsQuoteNode>(type, aContentIndex);
auto* quoteList =
mContainStyleScopeManager.QuoteListFor(aOriginatingElement);
auto initializer = MakeUnique<nsGenConInitializer>(
std::move(node), quoteList, &nsCSSFrameConstructor::QuotesDirty);
RefPtr c = CreateGenConTextNode(aState, u""_ns, std::move(initializer));
aAddChild(c);
return;
}
case Type::MozLabelContent: {
nsAutoString accesskey;
if (!aOriginatingElement.GetAttr(nsGkAtoms::accesskey, accesskey) ||
accesskey.IsEmpty() || !LookAndFeel::GetMenuAccessKey()) {
// Easy path: just return a regular value attribute content.
nsCOMPtr<nsIContent> content;
NS_NewAttributeContent(mDocument->NodeInfoManager(), kNameSpaceID_None,
nsGkAtoms::value, nsGkAtoms::_empty,
getter_AddRefs(content));
aAddChild(content);
return;
}
nsAutoString value;
aOriginatingElement.GetAttr(nsGkAtoms::value, value);
auto AppendAccessKeyLabel = [&] {
ToUpperCase(accesskey);
nsAutoString accessKeyLabel = u"("_ns + accesskey + u")"_ns;
if (!StringEndsWith(value, accessKeyLabel)) {
if (InsertSeparatorBeforeAccessKey() && !value.IsEmpty() &&
!NS_IS_SPACE(value.Last())) {
value.Append(' ');
}
value.Append(accessKeyLabel);
}
};
if (AlwaysAppendAccessKey()) {
AppendAccessKeyLabel();
RefPtr c = CreateGenConTextNode(aState, value, nullptr);
aAddChild(c);
return;
}
const auto accessKeyStart = [&]() -> Maybe<size_t> {
nsAString::const_iterator start, end;
value.BeginReading(start);
value.EndReading(end);
const auto originalStart = start;
// not appending access key - do case-sensitive search
// first
bool found = true;
if (!FindInReadable(accesskey, start, end)) {
start = originalStart;
// didn't find it - perform a case-insensitive search
found = FindInReadable(accesskey, start, end,
nsCaseInsensitiveStringComparator);
}
if (!found) {
return Nothing();
}
return Some(Distance(originalStart, start));
}();
if (accessKeyStart.isNothing()) {
AppendAccessKeyLabel();
RefPtr c = CreateGenConTextNode(aState, value, nullptr);
aAddChild(c);
return;
}
if (*accessKeyStart != 0) {
RefPtr beginning = CreateGenConTextNode(
aState, Substring(value, 0, *accessKeyStart), nullptr);
aAddChild(beginning);
}
{
RefPtr accessKeyText = CreateGenConTextNode(
aState, Substring(value, *accessKeyStart, accesskey.Length()),
nullptr);
RefPtr<nsIContent> underline =
mDocument->CreateHTMLElement(nsGkAtoms::u);
underline->AppendChildTo(accessKeyText, /* aNotify = */ false,
IgnoreErrors());
aAddChild(underline);
}
size_t accessKeyEnd = *accessKeyStart + accesskey.Length();
if (accessKeyEnd != value.Length()) {
RefPtr valueEnd = CreateGenConTextNode(
aState, Substring(value, *accessKeyStart + accesskey.Length()),
nullptr);
aAddChild(valueEnd);
}
break;
}
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(nsGkAtoms::alt)) {
nsCOMPtr<nsIContent> content;
NS_NewAttributeContent(mDocument->NodeInfoManager(), kNameSpaceID_None,
nsGkAtoms::alt, nsGkAtoms::_empty,
getter_AddRefs(content));
aAddChild(content);
return;
}
if (aOriginatingElement.IsHTMLElement(nsGkAtoms::input)) {
if (aOriginatingElement.HasAttr(nsGkAtoms::value)) {
nsCOMPtr<nsIContent> content;
NS_NewAttributeContent(mDocument->NodeInfoManager(),
kNameSpaceID_None, nsGkAtoms::value,
nsGkAtoms::_empty, getter_AddRefs(content));
aAddChild(content);
return;
}
nsAutoString temp;
nsContentUtils::GetMaybeLocalizedString(
nsContentUtils::eFORMS_PROPERTIES, "Submit", mDocument, temp);
RefPtr c = CreateGenConTextNode(aState, temp, nullptr);
aAddChild(c);
return;
}
break;
}
}
}
void nsCSSFrameConstructor::CreateGeneratedContentFromListStyle(
nsFrameConstructorState& aState, Element& aOriginatingElement,
const ComputedStyle& aPseudoStyle,
const FunctionRef<void(nsIContent*)> aAddChild) {
const nsStyleList* styleList = aPseudoStyle.StyleList();
if (!styleList->mListStyleImage.IsNone()) {
RefPtr<nsIContent> child =
GeneratedImageContent::CreateForListStyleImage(*mDocument);
aAddChild(child);
child = CreateGenConTextNode(aState, u" "_ns, nullptr);
aAddChild(child);
return;
}
CreateGeneratedContentFromListStyleType(aState, aOriginatingElement,
aPseudoStyle, aAddChild);
}
void nsCSSFrameConstructor::CreateGeneratedContentFromListStyleType(
nsFrameConstructorState& aState, Element& aOriginatingElement,
const ComputedStyle& aPseudoStyle,
const FunctionRef<void(nsIContent*)> aAddChild) {
using Tag = StyleCounterStyle::Tag;
const auto& styleType = aPseudoStyle.StyleList()->mListStyleType;
switch (styleType.tag) {
case Tag::None:
return;
case Tag::String: {
nsDependentAtomString string(styleType.AsString().AsAtom());
RefPtr<nsIContent> child = CreateGenConTextNode(aState, string, nullptr);
aAddChild(child);
return;
}
case Tag::Name:
case Tag::Symbols:
break;
}
auto node = MakeUnique<nsCounterUseNode>(nsCounterUseNode::ForLegacyBullet,
styleType);
if (styleType.IsName()) {
nsAtom* name = styleType.AsName().AsAtom();
if (name == nsGkAtoms::disc || name == nsGkAtoms::circle ||
name == nsGkAtoms::square || name == nsGkAtoms::disclosure_closed ||
name == nsGkAtoms::disclosure_open) {
// We don't need a use node inserted for these.
CounterStyle* counterStyle = mPresShell->GetPresContext()
->CounterStyleManager()
->ResolveCounterStyle(name);
nsAutoString text;
node->GetText(WritingMode(&aPseudoStyle), counterStyle, text);
// Note that we're done with 'node' in this case. It's not inserted into
// any list so it's deleted when we return.
RefPtr<nsIContent> child = CreateGenConTextNode(aState, text, nullptr);
aAddChild(child);
return;
}
}
auto* counterList = mContainStyleScopeManager.GetOrCreateCounterList(
aOriginatingElement, nsGkAtoms::list_item);
auto initializer = MakeUnique<nsGenConInitializer>(
std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty);
RefPtr<nsIContent> child =
CreateGenConTextNode(aState, EmptyString(), std::move(initializer));
aAddChild(child);
}
// Frames for these may not be leaves in the proper sense, but we still don't
// want to expose generated content on them. For the purposes of the page they
// should be leaves.
static bool HasUAWidget(const Element& aOriginatingElement) {
const ShadowRoot* sr = aOriginatingElement.GetShadowRoot();
return sr && sr->IsUAWidget();
}
/*
* 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,
ItemFlags aExtraFlags) {
MOZ_ASSERT(aPseudoElement == PseudoStyleType::before ||
aPseudoElement == PseudoStyleType::after ||
aPseudoElement == PseudoStyleType::marker,
"unexpected aPseudoElement");
if (HasUAWidget(aOriginatingElement) &&
!aOriginatingElement.IsHTMLElement(nsGkAtoms::details)) {
return;
}
ServoStyleSet* styleSet = mPresShell->StyleSet();
// Probe for the existence of the pseudo-element.
// |ProbePseudoElementStyle| checks the relevant properties for the pseudo.
// It only returns a non-null value if the pseudo should exist.
RefPtr<ComputedStyle> pseudoStyle = styleSet->ProbePseudoElementStyle(
aOriginatingElement, aPseudoElement, nullptr, &aStyle);
if (!pseudoStyle) {
return;
}
nsAtom* elemName = nullptr;
nsAtom* property = nullptr;
switch (aPseudoElement) {
case PseudoStyleType::before:
elemName = nsGkAtoms::mozgeneratedcontentbefore;
property = nsGkAtoms::beforePseudoProperty;
break;
case PseudoStyleType::after:
elemName = nsGkAtoms::mozgeneratedcontentafter;
property = nsGkAtoms::afterPseudoProperty;
break;
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;
break;
default:
MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement");
}
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)) {
return;
}
// 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());
container->SetIsNativeAnonymousRoot();
container->SetPseudoElementType(aPseudoElement);
BindContext context(aOriginatingElement, BindContext::ForNativeAnonymous);
rv = container->BindToTree(context, aOriginatingElement);
if (NS_FAILED(rv)) {
container->UnbindFromTree();
return;
}
if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) {
container->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ false);
}
// 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.
mPresShell->StyleSet()->StyleNewSubtree(container);
pseudoStyle = ServoStyleSet::ResolveServoStyle(*container);
}
auto AppendChild = [&container, this](nsIContent* aChild) {
// 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.
aChild->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
container->AppendChildTo(aChild, false, IgnoreErrors());
if (auto* childElement = Element::FromNode(aChild)) {
// If we created any children elements, Servo needs to traverse them, but
// the root is already set up.
mPresShell->StyleSet()->StyleNewSubtree(childElement);
}
};
auto items = pseudoStyle->StyleContent()->NonAltContentItems();
size_t index = 0;
for (const auto& item : items) {
CreateGeneratedContent(aState, aOriginatingElement, *pseudoStyle, item,
index++, AppendChild);
}
// If a ::marker has no 'content' then generate it from its 'list-style-*'.
if (index == 0 && aPseudoElement == PseudoStyleType::marker) {
CreateGeneratedContentFromListStyle(aState, aOriginatingElement,
*pseudoStyle, AppendChild);
}
auto flags = ItemFlags{ItemFlag::IsGeneratedContent} + aExtraFlags;
AddFrameConstructionItemsInternal(aState, container, aParentFrame, true,
pseudoStyle, flags, aItems);
}
/****************************************************
** BEGIN TABLE SECTION
****************************************************/
// 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();
if (pseudoType == PseudoStyleType::NotPseudo) {
return false;
}
return 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 &&
aFrame->PrincipalChildList()
.FirstChild()
->Style()
->IsPseudoOrAnonBox());
}
static bool IsRubyPseudo(nsIFrame* aFrame) {
return RubyUtils::IsRubyPseudo(aFrame->Style()->GetPseudoType());
}
// Note that this is (subtly) different to ParentIsWrapperAnonBox, since
// ParentIsWrapperAnonBox is really just about restyles, but there are wrapper
// anon boxes that don't need to return true for that...
// FIXME(emilio): This should be less complicated, ParentIsWrapperAnonBox should
// probably be renamed to something else, and this should just use
// IsWrapperAnonBox or similar...
static bool IsWrapperPseudo(nsIFrame* aFrame) {
auto pseudoType = aFrame->Style()->GetPseudoType();
if (!PseudoStyle::IsAnonBox(pseudoType)) {
return false;
}
return PseudoStyle::IsWrapperAnonBox(pseudoType) || IsTablePseudo(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) {
aList.RemoveFrame(child);
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 =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
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);
} else {
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);
} else {
innerFrame = NS_NewTableFrame(mPresShell, computedStyle);
}
InitAndRestoreFrame(aState, content, newFrame, innerFrame);
innerFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
// Put the newly created frames into the right child list
SetInitialSingleChild(newFrame, innerFrame);
aState.AddChild(newFrame, aFrameList, content, aParentFrame);
if (!mRootElementFrame) {
mRootElementFrame = newFrame;
}
nsFrameList childList;
// Process children
nsFrameConstructorSaveState absoluteSaveState;
// Mark the table frame as an absolute container if needed
newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
if (newFrame->IsAbsPosContainingBlock()) {
aState.PushAbsoluteContainingBlock(newFrame, newFrame, absoluteSaveState);
}
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(innerFrame, floatSaveState);
if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
ConstructFramesFromItemList(
aState, aItem.mChildItems, innerFrame,
aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
} else {
ProcessChildren(aState, content, computedStyle, innerFrame, true, childList,
false);
}
nsFrameList captionList;
PullOutCaptionFrames(childList, captionList);
// Set the inner table frame's principal child list.
innerFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
// Append caption frames to the table wrapper frame's principal child list.
if (captionList.NotEmpty()) {
captionList.ApplySetParent(newFrame);
newFrame->AppendFrames(FrameChildListID::Principal, std::move(captionList));
}
return newFrame;
}
static void MakeTablePartAbsoluteContainingBlock(
nsFrameConstructorState& aState, nsFrameConstructorSaveState& aAbsSaveState,
nsContainerFrame* aFrame) {
// If we're positioned, then we need to become an absolute containing block
// for any absolutely positioned children.
aFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
if (aFrame->IsAbsPosContainingBlock()) {
aState.PushAbsoluteContainingBlock(aFrame, aFrame, aAbsSaveState);
}
}
nsIFrame* nsCSSFrameConstructor::ConstructTableRowOrRowGroup(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::TableRow ||
aDisplay->mDisplay == StyleDisplay::TableRowGroup ||
aDisplay->mDisplay == StyleDisplay::TableFooterGroup ||
aDisplay->mDisplay == StyleDisplay::TableHeaderGroup,
"Not a row or row group");
MOZ_ASSERT(aItem.mComputedStyle->StyleDisplay() == aDisplay,
"Display style doesn't match style");
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
nsContainerFrame* newFrame;
if (aDisplay->mDisplay == StyleDisplay::TableRow) {
if (content->IsMathMLElement()) {
newFrame = NS_NewMathMLmtrFrame(mPresShell, computedStyle);
} else {
newFrame = NS_NewTableRowFrame(mPresShell, computedStyle);
}
} else {
newFrame = NS_NewTableRowGroupFrame(mPresShell, computedStyle);
}
InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
nsFrameConstructorSaveState absoluteSaveState;
MakeTablePartAbsoluteContainingBlock(aState, absoluteSaveState, newFrame);
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(newFrame, floatSaveState);
nsFrameList childList;
if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
ConstructFramesFromItemList(
aState, aItem.mChildItems, newFrame,
aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
} else {
ProcessChildren(aState, content, computedStyle, newFrame, true, childList,
false);
}
newFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
aFrameList.AppendFrame(nullptr, newFrame);
return newFrame;
}
nsIFrame* nsCSSFrameConstructor::ConstructTableCol(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList) {
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
nsTableColFrame* colFrame = NS_NewTableColFrame(mPresShell, computedStyle);
InitAndRestoreFrame(aState, content, aParentFrame, colFrame);
NS_ASSERTION(colFrame->Style() == computedStyle, "Unexpected style");
aFrameList.AppendFrame(nullptr, colFrame);
// construct additional col frames if the col frame has a span > 1
int32_t span = colFrame->GetSpan();
for (int32_t spanX = 1; spanX < span; spanX++) {
nsTableColFrame* newCol = NS_NewTableColFrame(mPresShell, computedStyle);
InitAndRestoreFrame(aState, content, aParentFrame, newCol, false);
aFrameList.LastChild()->SetNextContinuation(newCol);
newCol->SetPrevContinuation(aFrameList.LastChild());
aFrameList.AppendFrame(nullptr, newCol);
newCol->SetColType(eColAnonymousCol);
}
return colFrame;
}
nsIFrame* nsCSSFrameConstructor::ConstructTableCell(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::TableCell, "Unexpected call");
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
const bool isMathMLContent = content->IsMathMLElement();
nsTableFrame* tableFrame =
static_cast<nsTableRowFrame*>(aParentFrame)->GetTableFrame();
nsContainerFrame* cellFrame;
// <mtable> is border separate in mathml.css and the MathML code doesn't
// implement border collapse. For those users who style <mtable> with border
// collapse, give them the default non-MathML table frames that understand
// border collapse. This won't break us because MathML table frames are all
// subclasses of the default table code, and so we can freely mix <mtable>
// with <mtr> or <tr>, <mtd> or <td>. What will happen is just that non-MathML
// frames won't understand MathML attributes and will therefore miss the
// special handling that the MathML code does.
if (isMathMLContent && !tableFrame->IsBorderCollapse()) {
cellFrame = NS_NewMathMLmtdFrame(mPresShell, computedStyle, tableFrame);
} else {
// Warning: If you change this and add a wrapper frame around table cell
// See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
cellFrame = NS_NewTableCellFrame(mPresShell, computedStyle, tableFrame);
}
// Initialize the table cell frame
InitAndRestoreFrame(aState, content, aParentFrame, cellFrame);
cellFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
// Resolve pseudo style and initialize the body cell frame
RefPtr<ComputedStyle> innerPseudoStyle =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::cellContent, computedStyle);
nsContainerFrame* cellInnerFrame;
nsContainerFrame* scrollFrame = nullptr;
bool isScrollable = false;
// Create a block frame that will format the cell's content
if (isMathMLContent) {
cellInnerFrame = NS_NewMathMLmtdInnerFrame(mPresShell, innerPseudoStyle);
} else {
isScrollable = innerPseudoStyle->StyleDisplay()->IsScrollableOverflow() &&
!aState.mPresContext->IsPaginated() &&
StaticPrefs::layout_tables_scrollable_cells();
if (isScrollable) {
innerPseudoStyle = BeginBuildingScrollContainerFrame(
aState, content, innerPseudoStyle, cellFrame,
PseudoStyleType::scrolledContent, false, scrollFrame);
}
cellInnerFrame = NS_NewBlockFrame(mPresShell, innerPseudoStyle);
}
auto* parent = scrollFrame ? scrollFrame : cellFrame;
InitAndRestoreFrame(aState, content, parent, cellInnerFrame);
nsFrameConstructorSaveState absoluteSaveState;
MakeTablePartAbsoluteContainingBlock(aState, absoluteSaveState, cellFrame);
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(cellInnerFrame, floatSaveState);
nsFrameList childList;
if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
AutoFrameConstructionPageName pageNameTracker(aState, cellInnerFrame);
ConstructFramesFromItemList(
aState, aItem.mChildItems, cellInnerFrame,
aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
} else {
// Process the child content
ProcessChildren(aState, content, computedStyle, cellInnerFrame, true,
childList, !isMathMLContent);
}
cellInnerFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
if (isScrollable) {
FinishBuildingScrollContainerFrame(scrollFrame, cellInnerFrame);
}
SetInitialSingleChild(cellFrame, scrollFrame ? scrollFrame : cellInnerFrame);
aFrameList.AppendFrame(nullptr, cellFrame);
return cellFrame;
}
static inline bool NeedFrameFor(const nsFrameConstructorState& aState,
nsContainerFrame* aParentFrame,
nsIContent* aChildContent) {
// Remove it once that's fixed.
MOZ_ASSERT(
!aChildContent->GetPrimaryFrame() || aState.mCreatingExtraFrames ||
aChildContent->GetPrimaryFrame()->GetContent() != aChildContent,
"Why did we get called?");
// don't create a whitespace frame if aParentFrame doesn't want it.
// always create frames for children in generated content. counter(),
// quotes, and attr() content can easily change dynamically and we don't
// want to be reconstructing frames. It's not even clear that these
// should be considered ignorable just because they evaluate to
// whitespace.
// We could handle all this in CreateNeededPseudoContainers or some other
// place after we build our frame construction items, but that would involve
// creating frame construction items for whitespace kids that ignores
// white-space, where we know we'll be dropping them all anyway, and involve
// an extra walk down the frame construction item list.
auto excludesIgnorableWhitespace = [](nsIFrame* aParentFrame) {
return aParentFrame->IsMathMLFrame();
};
if (!aParentFrame || !excludesIgnorableWhitespace(aParentFrame) ||
aParentFrame->IsGeneratedContentFrame() || !aChildContent->IsText()) {
return true;
}
aChildContent->SetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
NS_REFRAME_IF_WHITESPACE);
return !aChildContent->TextIsOnlyWhitespace();
}
/***********************************************
* END TABLE SECTION
***********************************************/
nsIFrame* nsCSSFrameConstructor::ConstructDocElementFrame(
Element* aDocElement) {
MOZ_ASSERT(GetRootFrame(),
"No viewport? Someone forgot to call ConstructRootFrame!");
MOZ_ASSERT(!mDocElementContainingBlock,
"Shouldn't have a doc element containing block here");
// Resolve a new style for the viewport since it may be affected by a new root
// element style (e.g. a propagated 'direction').
//
// @see ComputedStyle::ApplyStyleFixups
{
RefPtr<ComputedStyle> sc =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::viewport, nullptr);
GetRootFrame()->SetComputedStyleWithoutNotification(sc);
}
// Ensure the document element is styled at this point.
if (!aDocElement->HasServoData()) {
mPresShell->StyleSet()->StyleNewSubtree(aDocElement);
}
aDocElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
// Make sure to call UpdateViewportScrollStylesOverride before
// SetUpDocElementContainingBlock, since it sets up our scrollbar state
// properly.
DebugOnly<nsIContent*> propagatedScrollFrom;
if (nsPresContext* presContext = mPresShell->GetPresContext()) {
propagatedScrollFrom = presContext->UpdateViewportScrollStylesOverride();
}
SetUpDocElementContainingBlock(aDocElement);
// This has the side-effect of getting `mFrameTreeState` from our docshell.
//
// FIXME(emilio): There may be a more sensible time to do this.
if (!mFrameTreeState) {
mPresShell->CaptureHistoryState(getter_AddRefs(mFrameTreeState));
}
NS_ASSERTION(mDocElementContainingBlock, "Should have parent by now");
nsFrameConstructorState state(
mPresShell,
GetAbsoluteContainingBlock(mDocElementContainingBlock, FIXED_POS),
nullptr, nullptr, do_AddRef(mFrameTreeState));
RefPtr<ComputedStyle> computedStyle =
ServoStyleSet::ResolveServoStyle(*aDocElement);
const nsStyleDisplay* display = computedStyle->StyleDisplay();
// --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
NS_ASSERTION(!display->IsScrollableOverflow() ||
state.mPresContext->IsPaginated() ||
propagatedScrollFrom == aDocElement,
"Scrollbars should have been propagated to the viewport");
if (MOZ_UNLIKELY(display->mDisplay == StyleDisplay::None)) {
return nullptr;
}
// This implements "The Principal Writing Mode".
//
// If there's a <body> element in an HTML document, its writing-mode,
// direction, and text-orientation override the root element's used value.
//
// We need to copy <body>'s WritingMode to mDocElementContainingBlock before
// construct mRootElementFrame so that anonymous internal frames such as
// <html> with table style can copy their parent frame's mWritingMode in
// nsIFrame::Init().
MOZ_ASSERT(!mRootElementFrame,
"We need to copy <body>'s principal writing-mode before "
"constructing mRootElementFrame.");
const WritingMode propagatedWM = [&] {
const WritingMode rootWM(computedStyle);
if (computedStyle->StyleDisplay()->IsContainAny()) {
return rootWM;
}
Element* body = mDocument->GetBodyElement();
if (!body) {
return rootWM;
}
RefPtr<ComputedStyle> bodyStyle = ResolveComputedStyle(body);
if (bodyStyle->StyleDisplay()->IsContainAny()) {
return rootWM;
}
const WritingMode bodyWM(bodyStyle);
if (bodyWM != rootWM) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
mDocument,
nsContentUtils::eLAYOUT_PROPERTIES,
"PrincipalWritingModePropagationWarning");
}
return bodyWM;
}();
mDocElementContainingBlock->PropagateWritingModeToSelfAndAncestors(
propagatedWM);
// Push the absolute containing block now so we can absolutely position the
// root element
nsFrameConstructorSaveState canvasCbSaveState;
mCanvasFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
state.PushAbsoluteContainingBlock(mCanvasFrame, mCanvasFrame,
canvasCbSaveState);
nsFrameConstructorSaveState docElementCbSaveState;
if (mCanvasFrame != mDocElementContainingBlock) {
mDocElementContainingBlock->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
state.PushAbsoluteContainingBlock(mDocElementContainingBlock,
mDocElementContainingBlock,
docElementCbSaveState);
}
// The rules from CSS 2.1, section 9.2.4, have already been applied
// by the style system, so we can assume that display->mDisplay is
// either NONE, BLOCK, or TABLE.
// contentFrame is the primary frame for the root element. frameList contains
// the children of the initial containing block.
//
// The first of those frames is usually `contentFrame`, but it can be
// different, in particular if the root frame is positioned, in which case
// contentFrame is the out-of-flow frame and frameList.FirstChild() is the
// placeholder.
//
// The rest of the frames in frameList are the anonymous content of the canvas
// frame.
nsContainerFrame* contentFrame;
nsFrameList frameList;
bool processChildren = false;
nsFrameConstructorSaveState absoluteSaveState;
if (aDocElement->IsSVGElement()) {
if (!aDocElement->IsSVGElement(nsGkAtoms::svg)) {
return nullptr;
}
// We're going to call the right function ourselves, so no need to give a
// function to this FrameConstructionData.
// XXXbz on the other hand, if we converted this whole function to
// FrameConstructionData/Item, then we'd need the right function
// here... but would probably be able to get away with less code in this
// function in general.
static constexpr FrameConstructionData rootSVGData;
AutoFrameConstructionItem item(this, &rootSVGData, aDocElement,
do_AddRef(computedStyle), true);
contentFrame = static_cast<nsContainerFrame*>(ConstructOuterSVG(
state, item, mDocElementContainingBlock, display, frameList));
} else if (display->mDisplay == StyleDisplay::Flex ||
display->mDisplay == StyleDisplay::WebkitBox ||
display->mDisplay == StyleDisplay::Grid) {
auto func = [&] {
if (display->mDisplay == StyleDisplay::Grid) {
return NS_NewGridContainerFrame;
}
return NS_NewFlexContainerFrame;
}();
contentFrame = func(mPresShell, computedStyle);
InitAndRestoreFrame(
state, aDocElement,
state.GetGeometricParent(*display, mDocElementContainingBlock),
contentFrame);
state.AddChild(contentFrame, frameList, aDocElement,
mDocElementContainingBlock);
processChildren = true;
contentFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
if (contentFrame->IsAbsPosContainingBlock()) {
state.PushAbsoluteContainingBlock(contentFrame, contentFrame,
absoluteSaveState);
}
} else if (display->mDisplay == StyleDisplay::Table) {
// We're going to call the right function ourselves, so no need to give a
// function to this FrameConstructionData.
// XXXbz on the other hand, if we converted this whole function to
// FrameConstructionData/Item, then we'd need the right function
// here... but would probably be able to get away with less code in this
// function in general.
static constexpr FrameConstructionData rootTableData;
AutoFrameConstructionItem item(this, &rootTableData, aDocElement,
do_AddRef(computedStyle), true);
// if the document is a table then just populate it.
contentFrame = static_cast<nsContainerFrame*>(ConstructTable(
state, item, mDocElementContainingBlock, display, frameList));
} else if (display->DisplayInside() == StyleDisplayInside::Ruby) {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructBlockRubyFrame);
AutoFrameConstructionItem item(this, &data, aDocElement,
do_AddRef(computedStyle), true);
contentFrame = static_cast<nsContainerFrame*>(ConstructBlockRubyFrame(
state, item,
state.GetGeometricParent(*display, mDocElementContainingBlock), display,
frameList));
} else {
MOZ_ASSERT(display->mDisplay == StyleDisplay::Block ||
display->mDisplay == StyleDisplay::FlowRoot,
"Unhandled display type for root element");
contentFrame = NS_NewBlockFrame(mPresShell, computedStyle);
ConstructBlock(
state, aDocElement,
state.GetGeometricParent(*display, mDocElementContainingBlock),
mDocElementContainingBlock, computedStyle, &contentFrame, frameList,
contentFrame->IsAbsPosContainingBlock() ? contentFrame : nullptr);
}
MOZ_ASSERT(frameList.FirstChild());
MOZ_ASSERT(frameList.FirstChild()->GetContent() == aDocElement);
MOZ_ASSERT(contentFrame);
MOZ_ASSERT(
processChildren ? !mRootElementFrame : mRootElementFrame == contentFrame,
"unexpected mRootElementFrame");
if (processChildren) {
mRootElementFrame = contentFrame;
}
// Figure out which frame has the main style for the document element,
// assigning it to mRootElementStyleFrame.
// Backgrounds should be propagated from that frame to the viewport.
contentFrame->GetParentComputedStyle(&mRootElementStyleFrame);
bool isChild = mRootElementStyleFrame &&
mRootElementStyleFrame->GetParent() == contentFrame;
if (!isChild) {
mRootElementStyleFrame = mRootElementFrame;
}
if (processChildren) {
// Still need to process the child content
nsFrameList childList;
NS_ASSERTION(
!contentFrame->IsBlockFrameOrSubclass() && !contentFrame->IsSVGFrame(),
"Only XUL frames should reach here");
nsFrameConstructorSaveState floatSaveState;
state.MaybePushFloatContainingBlock(contentFrame, floatSaveState);
ProcessChildren(state, aDocElement, computedStyle, contentFrame, true,
childList, false);
// Set the initial child lists
contentFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
}
nsIFrame* newFrame = frameList.FirstChild();
// set the primary frame
aDocElement->SetPrimaryFrame(contentFrame);
mDocElementContainingBlock->AppendFrames(FrameChildListID::Principal,
std::move(frameList));
// NOTE(emilio): This is in the reverse order compared to normal anonymous
// children. We usually generate anonymous kids first, then non-anonymous,
// but we generate the doc element frame the other way around. This is fine
// either way, but generating anonymous children in a different order requires
// changing nsCanvasFrame (and a whole lot of other potentially unknown code)
// to look at the last child to find the root frame rather than the first
// child.
ConstructAnonymousContentForCanvas(
state, mCanvasFrame, mRootElementFrame->GetContent(), frameList);
mCanvasFrame->AppendFrames(FrameChildListID::Principal, std::move(frameList));
return newFrame;
}
RestyleManager* nsCSSFrameConstructor::RestyleManager() const {
return mPresShell->GetPresContext()->RestyleManager();
}
ViewportFrame* nsCSSFrameConstructor::ConstructRootFrame() {
AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::ConstructRootFrame",
LAYOUT_FrameConstruction);
AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
ServoStyleSet* styleSet = mPresShell->StyleSet();
// --------- BUILD VIEWPORT -----------
RefPtr<ComputedStyle> viewportPseudoStyle =
styleSet->ResolveInheritingAnonymousBoxStyle(PseudoStyleType::viewport,
nullptr);
ViewportFrame* viewportFrame =
NS_NewViewportFrame(mPresShell, viewportPseudoStyle);
// XXXbz do we _have_ to pass a null content pointer to that frame?
// Would it really kill us to pass in the root element or something?
// What would that break?
viewportFrame->Init(nullptr, nullptr, nullptr);
viewportFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
// Bind the viewport frame to the root view
if (nsView* rootView = mPresShell->GetViewManager()->GetRootView()) {
viewportFrame->SetView(rootView);
viewportFrame->SyncFrameViewProperties(rootView);
rootView->SetNeedsWindowPropertiesSync();
}
// Make it an absolute container for fixed-pos elements
viewportFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
viewportFrame->MarkAsAbsoluteContainingBlock();
return viewportFrame;
}
void nsCSSFrameConstructor::SetUpDocElementContainingBlock(
nsIContent* aDocElement) {
MOZ_ASSERT(aDocElement, "No element?");
MOZ_ASSERT(!aDocElement->GetParent(), "Not root content?");
MOZ_ASSERT(aDocElement->GetUncomposedDoc(), "Not in a document?");
MOZ_ASSERT(aDocElement->GetUncomposedDoc()->GetRootElement() == aDocElement,
"Not the root of the document?");
/*
how the root frame hierarchy should look
Galley presentation, with scrolling:
ViewportFrame [fixed-cb]
ScrollContainerFrame (if needed)
nsCanvasFrame [abs-cb]
root element frame (nsBlockFrame, SVGOuterSVGFrame,
nsTableWrapperFrame, nsPlaceholderFrame,
nsFlexContainerFrame, nsGridContainerFrame)
Print presentation, non-XUL
ViewportFrame
nsCanvasFrame
nsPageSequenceFrame
PrintedSheetFrame
nsPageFrame
nsPageContentFrame [fixed-cb]
nsCanvasFrame [abs-cb]
root element frame (nsBlockFrame, SVGOuterSVGFrame,
nsTableWrapperFrame, nsPlaceholderFrame,
nsFlexContainerFrame,
nsGridContainerFrame)
Print-preview presentation, non-XUL
ViewportFrame
ScrollContainerFrame
nsCanvasFrame
nsPageSequenceFrame
PrintedSheetFrame
nsPageFrame
nsPageContentFrame [fixed-cb]
nsCanvasFrame [abs-cb]
root element frame (nsBlockFrame, SVGOuterSVGFrame,
nsTableWrapperFrame,
nsPlaceholderFrame,
nsFlexContainerFrame,
nsGridContainerFrame)
Print/print preview of XUL is not supported.
[fixed-cb]: the default containing block for fixed-pos content
[abs-cb]: the default containing block for abs-pos content
Meaning of nsCSSFrameConstructor fields:
mRootElementFrame is "root element frame". This is the primary frame for
the root element.
mDocElementContainingBlock is the parent of mRootElementFrame
(i.e. nsCanvasFrame)
mPageSequenceFrame is the nsPageSequenceFrame, or null if there isn't
one
*/
// --------- CREATE ROOT FRAME -------
// Create the root frame. The document element's frame is a child of the
// root frame.
//
// The root frame serves two purposes:
// - reserves space for any margins needed for the document element's frame
// - renders the document element's background. This ensures the background
// covers the entire canvas as specified by the CSS2 spec
nsPresContext* presContext = mPresShell->GetPresContext();
const bool isPaginated = presContext->IsRootPaginatedDocument();
const bool isHTML = aDocElement->IsHTMLElement();
const bool isXUL = !isHTML && aDocElement->IsXULElement();
const bool isScrollable = [&] {
if (isPaginated) {
return presContext->HasPaginatedScrolling();
}
// Never create scrollbars for XUL documents or top level XHTML documents
// that disable scrolling.
if (isXUL) {
return false;
}
if (aDocElement->OwnerDoc()->ChromeRulesEnabled() &&
aDocElement->AsElement()->AttrValueIs(
kNameSpaceID_None, nsGkAtoms::scrolling, nsGkAtoms::_false,
eCaseMatters)) {
return false;
}
return true;
}();
nsContainerFrame* viewportFrame =
static_cast<nsContainerFrame*>(GetRootFrame());
ComputedStyle* viewportPseudoStyle = viewportFrame->Style();
nsCanvasFrame* rootCanvasFrame =
NS_NewCanvasFrame(mPresShell, viewportPseudoStyle);
PseudoStyleType rootPseudo = PseudoStyleType::canvas;
mCanvasFrame = rootCanvasFrame;
mDocElementContainingBlock = rootCanvasFrame;
// --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
// If the device supports scrolling (e.g., in galley mode on the screen and
// for print-preview, but not when printing), then create a scroll frame that
// will act as the scrolling mechanism for the viewport.
// XXX Do we even need a viewport when printing to a printer?
// We no longer need to do overflow propagation here. It's taken care of
// when we construct frames for the element whose overflow might be
// propagated
NS_ASSERTION(!isScrollable || !isXUL,
"XUL documents should never be scrollable - see above");
nsContainerFrame* newFrame = rootCanvasFrame;
RefPtr<ComputedStyle> rootPseudoStyle;
// we must create a state because if the scrollbars are GFX it needs the
// state to build the scrollbar frames.
nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
// Start off with the viewport as parent; we'll adjust it as needed.
nsContainerFrame* parentFrame = viewportFrame;
ServoStyleSet* styleSet = mPresShell->StyleSet();
// If paginated, make sure we don't put scrollbars in
if (!isScrollable) {
rootPseudoStyle = styleSet->ResolveInheritingAnonymousBoxStyle(
rootPseudo, viewportPseudoStyle);
} else {
rootPseudo = PseudoStyleType::scrolledCanvas;
// Build the frame. We give it the content we are wrapping which is the
// document element, the root frame, the parent view port frame, and we
// should get back the new frame and the scrollable view if one was
// created.
// resolve a context for the scrollframe
RefPtr<ComputedStyle> computedStyle =
styleSet->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::viewportScroll, viewportPseudoStyle);
// Note that the viewport scrollframe is always built with
// overflow:auto style. This forces the scroll frame to create
// anonymous content for both scrollbars. This is necessary even
// if the HTML or BODY elements are overriding the viewport
// scroll style to 'hidden' --- dynamic style changes might put
// scrollbars back on the viewport and we don't want to have to
// reframe the viewport to create the scrollbar content.
newFrame = nullptr;
rootPseudoStyle = BeginBuildingScrollContainerFrame(
state, aDocElement, computedStyle, viewportFrame, rootPseudo, true,
newFrame);
parentFrame = newFrame;
}
rootCanvasFrame->SetComputedStyleWithoutNotification(rootPseudoStyle);
rootCanvasFrame->Init(aDocElement, parentFrame, nullptr);
if (isScrollable) {
FinishBuildingScrollContainerFrame(parentFrame, rootCanvasFrame);
}
if (isPaginated) {
// Create a page sequence frame
{
RefPtr<ComputedStyle> pageSequenceStyle =
styleSet->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::pageSequence, viewportPseudoStyle);
mPageSequenceFrame =
NS_NewPageSequenceFrame(mPresShell, pageSequenceStyle);
mPageSequenceFrame->Init(aDocElement, rootCanvasFrame, nullptr);
SetInitialSingleChild(rootCanvasFrame, mPageSequenceFrame);
}
// Create the first printed sheet frame, as the sole child (for now) of our
// page sequence frame (mPageSequenceFrame).
auto* printedSheetFrame =
ConstructPrintedSheetFrame(mPresShell, mPageSequenceFrame, nullptr);
SetInitialSingleChild(mPageSequenceFrame, printedSheetFrame);
MOZ_ASSERT(!mNextPageContentFramePageName,
"Next page name should not have been set.");
// Create the first page, as the sole child (for now) of the printed sheet
// frame that we just created.
nsCanvasFrame* canvasFrame;
nsContainerFrame* pageFrame =
ConstructPageFrame(mPresShell, printedSheetFrame, nullptr, canvasFrame);
pageFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
SetInitialSingleChild(printedSheetFrame, pageFrame);
// The eventual parent of the document element frame.
// XXX should this be set for every new page (in ConstructPageFrame)?
mDocElementContainingBlock = canvasFrame;
}
if (viewportFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
SetInitialSingleChild(viewportFrame, newFrame);
} else {
viewportFrame->AppendFrames(FrameChildListID::Principal,
nsFrameList(newFrame, newFrame));
}
}
void nsCSSFrameConstructor::ConstructAnonymousContentForCanvas(
nsFrameConstructorState& aState, nsContainerFrame* aFrame,
nsIContent* aDocElement, nsFrameList& aFrameList) {
NS_ASSERTION(aFrame->IsCanvasFrame(), "aFrame should be canvas frame!");
MOZ_ASSERT(mRootElementFrame->GetContent() == aDocElement);
AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> anonymousItems;
GetAnonymousContent(aDocElement, aFrame, anonymousItems);
if (anonymousItems.IsEmpty()) {
return;
}
AutoFrameConstructionItemList itemsToConstruct(this);
AutoFrameConstructionPageName pageNameTracker(aState, aFrame);
AddFCItemsForAnonymousContent(aState, aFrame, anonymousItems,
itemsToConstruct, pageNameTracker);
ConstructFramesFromItemList(aState, itemsToConstruct, aFrame,
/* aParentIsWrapperAnonBox = */ false,
aFrameList);
}
PrintedSheetFrame* nsCSSFrameConstructor::ConstructPrintedSheetFrame(
PresShell* aPresShell, nsContainerFrame* aParentFrame,
nsIFrame* aPrevSheetFrame) {
RefPtr<ComputedStyle> printedSheetPseudoStyle =
aPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::printedSheet);
auto* printedSheetFrame =
NS_NewPrintedSheetFrame(aPresShell, printedSheetPseudoStyle);
printedSheetFrame->Init(nullptr, aParentFrame, aPrevSheetFrame);
return printedSheetFrame;
}
nsContainerFrame* nsCSSFrameConstructor::ConstructPageFrame(
PresShell* aPresShell, nsContainerFrame* aParentFrame,
nsIFrame* aPrevPageFrame, nsCanvasFrame*& aCanvasFrame) {
ServoStyleSet* styleSet = aPresShell->StyleSet();
RefPtr<ComputedStyle> pagePseudoStyle =
styleSet->ResolveNonInheritingAnonymousBoxStyle(PseudoStyleType::page);
nsContainerFrame* pageFrame = NS_NewPageFrame(aPresShell, pagePseudoStyle);
// Initialize the page frame and force it to have a view. This makes printing
// of the pages easier and faster.
pageFrame->Init(nullptr, aParentFrame, aPrevPageFrame);
RefPtr<const nsAtom> pageName;
if (mNextPageContentFramePageName) {
pageName = mNextPageContentFramePageName.forget();
} else if (aPrevPageFrame) {
pageName = aPrevPageFrame->ComputePageValue();
MOZ_ASSERT(pageName,
"Page name from prev-in-flow should not have been null");
}
RefPtr<ComputedStyle> pageContentPseudoStyle =
styleSet->ResolvePageContentStyle(pageName,
StylePagePseudoClassFlags::NONE);
nsContainerFrame* pageContentFrame = NS_NewPageContentFrame(
aPresShell, pageContentPseudoStyle, pageName.forget());
nsPageContentFrame* prevPageContentFrame = nullptr;
if (aPrevPageFrame) {
MOZ_ASSERT(aPrevPageFrame->IsPageFrame());
prevPageContentFrame =
static_cast<nsPageFrame*>(aPrevPageFrame)->PageContentFrame();
}
pageContentFrame->Init(nullptr, pageFrame, prevPageContentFrame);
if (!prevPageContentFrame) {
// The canvas is an inheriting anon box, so needs to be "owned" by the page
// content.
pageContentFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
}
SetInitialSingleChild(pageFrame, pageContentFrame);
// Make it an absolute container for fixed-pos elements
pageContentFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
pageContentFrame->MarkAsAbsoluteContainingBlock();
RefPtr<ComputedStyle> canvasPseudoStyle =
styleSet->ResolveInheritingAnonymousBoxStyle(PseudoStyleType::canvas,
pageContentPseudoStyle);
aCanvasFrame = NS_NewCanvasFrame(aPresShell, canvasPseudoStyle);
nsIFrame* prevCanvasFrame = nullptr;
if (prevPageContentFrame) {
prevCanvasFrame = prevPageContentFrame->PrincipalChildList().FirstChild();
NS_ASSERTION(prevCanvasFrame, "missing canvas frame");
}
aCanvasFrame->Init(nullptr, pageContentFrame, prevCanvasFrame);
SetInitialSingleChild(pageContentFrame, aCanvasFrame);
return pageFrame;
}
/* static */
nsIFrame* nsCSSFrameConstructor::CreatePlaceholderFrameFor(
PresShell* aPresShell, nsIContent* aContent, nsIFrame* aFrame,
nsContainerFrame* aParentFrame, nsIFrame* aPrevInFlow,
nsFrameState aTypeBit) {
RefPtr<ComputedStyle> placeholderStyle =
aPresShell->StyleSet()->ResolveStyleForPlaceholder();
// The placeholder frame gets a pseudo style.
nsPlaceholderFrame* placeholderFrame =
NS_NewPlaceholderFrame(aPresShell, placeholderStyle, aTypeBit);
placeholderFrame->Init(aContent, aParentFrame, aPrevInFlow);
// Associate the placeholder/out-of-flow with each other.
placeholderFrame->SetOutOfFlowFrame(aFrame);
aFrame->SetProperty(nsIFrame::PlaceholderFrameProperty(), placeholderFrame);
aFrame->AddStateBits(NS_FRAME_OUT_OF_FLOW);
return placeholderFrame;
}
// Clears any lazy bits set in the range [aStartContent, aEndContent). If
// aEndContent is null, that means to clear bits in all siblings starting with
// aStartContent. aStartContent must not be null unless aEndContent is also
// null. We do this so that when new children are inserted under elements whose
// frame is a leaf the new children don't cause us to try to construct frames
// for the existing children again.
static inline void ClearLazyBits(nsIContent* aStartContent,
nsIContent* aEndContent) {
MOZ_ASSERT(aStartContent || !aEndContent,
"Must have start child if we have an end child");
for (nsIContent* cur = aStartContent; cur != aEndContent;
cur = cur->GetNextSibling()) {
cur->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
}
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindSelectData(const Element& aElement,
ComputedStyle& aStyle) {
// Construct a frame-based listbox or combobox
const auto* sel = dom::HTMLSelectElement::FromNode(aElement);
MOZ_ASSERT(sel);
if (sel->IsCombobox()) {
static constexpr FrameConstructionData sComboboxData{
ToCreationFunc(NS_NewComboboxControlFrame), 0,
PseudoStyleType::buttonContent};
return &sComboboxData;
}
// FIXME: Can we simplify this to avoid needing ConstructListboxSelectFrame,
// and reuse ConstructScrollableBlock or so?
static constexpr FrameConstructionData sListBoxData{
&nsCSSFrameConstructor::ConstructListBoxSelectFrame};
return &sListBoxData;
}
nsIFrame* nsCSSFrameConstructor::ConstructListBoxSelectFrame(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList) {
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
// Listbox, not combobox
nsContainerFrame* listFrame =
NS_NewListControlFrame(mPresShell, computedStyle);
nsContainerFrame* scrolledFrame =
NS_NewSelectsAreaFrame(mPresShell, computedStyle);
// ******* this code stolen from Initialze ScrollFrame ********
// please adjust this code to use BuildScrollFrame.
InitializeListboxSelect(aState, listFrame, scrolledFrame, content,
aParentFrame, computedStyle, aFrameList);
return listFrame;
}
void nsCSSFrameConstructor::InitializeListboxSelect(
nsFrameConstructorState& aState, nsContainerFrame* scrollFrame,
nsContainerFrame* scrolledFrame, nsIContent* aContent,
nsContainerFrame* aParentFrame, ComputedStyle* aComputedStyle,
nsFrameList& aFrameList) {
// Initialize it
nsContainerFrame* geometricParent =
aState.GetGeometricParent(*aComputedStyle->StyleDisplay(), aParentFrame);
// We don't call InitAndRestoreFrame for scrollFrame because we can only
// restore the frame state after its parts have been created (in particular,
// the scrollable view). So we have to split Init and Restore.
scrollFrame->Init(aContent, geometricParent, nullptr);
aState.AddChild(scrollFrame, aFrameList, aContent, aParentFrame);
BuildScrollContainerFrame(aState, aContent, aComputedStyle, scrolledFrame,
geometricParent, scrollFrame);
if (aState.mFrameState) {
// Restore frame state for the scroll frame
RestoreFrameStateFor(scrollFrame, aState.mFrameState);
}
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(scrolledFrame, floatSaveState);
// Process children
nsFrameList childList;
ProcessChildren(aState, aContent, aComputedStyle, scrolledFrame, false,
childList, false);
// Set the scrolled frame's initial child lists
scrolledFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
}
nsIFrame* nsCSSFrameConstructor::ConstructFieldSetFrame(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList) {
AutoRestore<bool> savedHasRenderedLegend(aState.mHasRenderedLegend);
aState.mHasRenderedLegend = false;
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
nsContainerFrame* fieldsetFrame =
NS_NewFieldSetFrame(mPresShell, computedStyle);
// Initialize it
InitAndRestoreFrame(aState, content,
aState.GetGeometricParent(*aStyleDisplay, aParentFrame),
fieldsetFrame);
fieldsetFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
// Resolve style and initialize the frame
RefPtr<ComputedStyle> fieldsetContentStyle =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::fieldsetContent, computedStyle);
const nsStyleDisplay* fieldsetContentDisplay =
fieldsetContentStyle->StyleDisplay();
const bool isScrollable = fieldsetContentDisplay->IsScrollableOverflow();
nsContainerFrame* scrollFrame = nullptr;
if (isScrollable) {
fieldsetContentStyle = BeginBuildingScrollContainerFrame(
aState, content, fieldsetContentStyle, fieldsetFrame,
PseudoStyleType::scrolledContent, false, scrollFrame);
}
// Create the inner ::-moz-fieldset-content frame.
nsContainerFrame* contentFrameTop;
nsContainerFrame* contentFrame;
auto* parent = scrollFrame ? scrollFrame : fieldsetFrame;
MOZ_ASSERT(fieldsetContentDisplay->DisplayOutside() ==
StyleDisplayOutside::Block);
switch (fieldsetContentDisplay->DisplayInside()) {
case StyleDisplayInside::Flex:
contentFrame = NS_NewFlexContainerFrame(mPresShell, fieldsetContentStyle);
InitAndRestoreFrame(aState, content, parent, contentFrame);
contentFrameTop = contentFrame;
break;
case StyleDisplayInside::Grid:
contentFrame = NS_NewGridContainerFrame(mPresShell, fieldsetContentStyle);
InitAndRestoreFrame(aState, content, parent, contentFrame);
contentFrameTop = contentFrame;
break;
default: {
MOZ_ASSERT(fieldsetContentDisplay->mDisplay == StyleDisplay::Block,
"bug in StyleAdjuster::adjust_for_fieldset_content?");
contentFrame = NS_NewBlockFrame(mPresShell, fieldsetContentStyle);
if (fieldsetContentStyle->StyleColumn()->IsColumnContainerStyle()) {
contentFrameTop = BeginBuildingColumns(
aState, content, parent, contentFrame, fieldsetContentStyle);
} else {
// No need to create column container. Initialize content frame.
InitAndRestoreFrame(aState, content, parent, contentFrame);
contentFrameTop = contentFrame;
}
break;
}
}
aState.AddChild(fieldsetFrame, aFrameList, content, aParentFrame);
// Process children
nsFrameConstructorSaveState absoluteSaveState;
nsFrameList childList;
contentFrameTop->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
if (fieldsetFrame->IsAbsPosContainingBlock()) {
aState.PushAbsoluteContainingBlock(contentFrameTop, fieldsetFrame,
absoluteSaveState);
}
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(contentFrame, floatSaveState);
ProcessChildren(aState, content, computedStyle, contentFrame, true, childList,
true);
nsFrameList fieldsetKids;
fieldsetKids.AppendFrame(nullptr,
scrollFrame ? scrollFrame : contentFrameTop);
if (!MayNeedToCreateColumnSpanSiblings(contentFrame, childList)) {
// Set the inner frame's initial child lists.
contentFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
} else {
// Extract any initial non-column-span kids, and put them in inner frame's
// child list.
nsFrameList initialNonColumnSpanKids =
childList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
contentFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(initialNonColumnSpanKids));
if (childList.NotEmpty()) {
nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
aState, contentFrame, childList,
// Column content should never be a absolute/fixed positioned
// containing block. Pass nullptr as aPositionedFrame.
nullptr);
FinishBuildingColumns(aState, contentFrameTop, contentFrame,
columnSpanSiblings);
}
}
if (isScrollable) {
FinishBuildingScrollContainerFrame(scrollFrame, contentFrameTop);
}
// We use AppendFrames here because the rendered legend will already
// be present in the principal child list if it exists.
fieldsetFrame->AppendFrames(FrameChildListID::NoReflowPrincipal,
std::move(fieldsetKids));
return fieldsetFrame;
}
// We always obey display for h1, but this is a convenient place for our
// counters.
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindH1Data(const Element& aElement,
ComputedStyle& aStyle) {
constexpr auto kCounter =
UseCounter::eUseCounter_custom_SectioningH1WithNoFontSizeOrMargins;
if (aStyle.HasAuthorSpecifiedMarginAndFontSize()) {
return nullptr;
}
auto* doc = aElement.OwnerDoc();
if (doc->HasUseCounter(kCounter)) {
return nullptr;
}
for (auto* ancestor = aElement.GetParent(); ancestor;
ancestor = ancestor->GetParent()) {
if (ancestor->IsAnyOfHTMLElements(nsGkAtoms::section, nsGkAtoms::aside,
nsGkAtoms::article, nsGkAtoms::nav)) {
doc->SetUseCounter(kCounter);
nsContentUtils::ReportToConsole(
nsIScriptError::warningFlag, "DOM"_ns, doc,
nsContentUtils::eDOM_PROPERTIES,
"SectioningH1WithNoFontSizeOrMargins",
}
}
return nullptr;
}
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindDetailsData(const Element& aElement,
ComputedStyle& aStyle) {
if (!StaticPrefs::layout_details_force_block_layout()) {
return nullptr;
}
static constexpr FrameConstructionData sBlockData[2] = {
{&nsCSSFrameConstructor::ConstructNonScrollableBlock},
{&nsCSSFrameConstructor::ConstructScrollableBlock},
};
return &sBlockData[aStyle.StyleDisplay()->IsScrollableOverflow()];
}
nsIFrame* nsCSSFrameConstructor::ConstructBlockRubyFrame(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
nsFrameList& aFrameList) {
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
nsBlockFrame* blockFrame = NS_NewBlockFrame(mPresShell, computedStyle);
nsContainerFrame* newFrame = blockFrame;
nsContainerFrame* geometricParent =
aState.GetGeometricParent(*aStyleDisplay, aParentFrame);
AutoFrameConstructionPageName pageNameTracker(aState, blockFrame);
if ((aItem.mFCData->mBits & FCDATA_MAY_NEED_SCROLLFRAME) &&
aStyleDisplay->IsScrollableOverflow()) {
nsContainerFrame* scrollframe = nullptr;
BuildScrollContainerFrame(aState, content, computedStyle, blockFrame,
geometricParent, scrollframe);
newFrame = scrollframe;
} else {
InitAndRestoreFrame(aState, content, geometricParent, blockFrame);
}
RefPtr<ComputedStyle> rubyStyle =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::blockRubyContent, computedStyle);
nsContainerFrame* rubyFrame = NS_NewRubyFrame(mPresShell, rubyStyle);
InitAndRestoreFrame(aState, content, blockFrame, rubyFrame);
SetInitialSingleChild(blockFrame, rubyFrame);
blockFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
aState.AddChild(newFrame, aFrameList, content, aParentFrame);
if (!mRootElementFrame) {
mRootElementFrame = newFrame;
}
nsFrameConstructorSaveState absoluteSaveState;
blockFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
if (newFrame->IsAbsPosContainingBlock()) {
aState.PushAbsoluteContainingBlock(blockFrame, blockFrame,
absoluteSaveState);
}
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(blockFrame, floatSaveState);
nsFrameList childList;
ProcessChildren(aState, content, rubyStyle, rubyFrame, true, childList, false,
nullptr);
rubyFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
return newFrame;
}
static nsIFrame* FindAncestorWithGeneratedContentPseudo(nsIFrame* aFrame) {
for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
NS_ASSERTION(f->IsGeneratedContentFrame(),
"should not have exited generated content");
auto pseudo = f->Style()->GetPseudoType();
if (pseudo == PseudoStyleType::before || pseudo == PseudoStyleType::after ||
pseudo == PseudoStyleType::marker) {
return f;
}
}
return nullptr;
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindTextData(const Text& aTextContent,
nsIFrame* aParentFrame) {
if (aParentFrame && IsFrameForSVG(aParentFrame)) {
if (!aParentFrame->IsInSVGTextSubtree()) {
return nullptr;
}
// subtrees, because TextCorrespondenceRecorder in the SVG text code doesn't
// really know how to deal with it. This kinda sucks. :(
if (aParentFrame->GetContent() != aTextContent.GetParent()) {
return nullptr;
}
static constexpr FrameConstructionData sSVGTextData(
NS_NewTextFrame, FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_SVG_TEXT);
return &sSVGTextData;
}
static constexpr FrameConstructionData sTextData(NS_NewTextFrame,
FCDATA_IS_LINE_PARTICIPANT);
return &sTextData;
}
void nsCSSFrameConstructor::ConstructTextFrame(
const FrameConstructionData* aData, nsFrameConstructorState& aState,
nsIContent* aContent, nsContainerFrame* aParentFrame,
ComputedStyle* aComputedStyle, nsFrameList& aFrameList) {
MOZ_ASSERT(aData, "Must have frame construction data");
nsIFrame* newFrame =
(*aData->mFunc.mCreationFunc)(mPresShell, aComputedStyle);
InitAndRestoreFrame(aState, aContent, aParentFrame, newFrame);
// We never need to create a view for a text frame.
if (newFrame->IsGeneratedContentFrame()) {
UniquePtr<nsGenConInitializer> initializer(
static_cast<nsGenConInitializer*>(
aContent->TakeProperty(nsGkAtoms::genConInitializerProperty)));
if (initializer) {
if (initializer->mNode.release()->InitTextFrame(
initializer->mList,
FindAncestorWithGeneratedContentPseudo(newFrame), newFrame)) {
(this->*(initializer->mDirtyAll))();
}
}
}
// Add the newly constructed frame to the flow
aFrameList.AppendFrame(nullptr, newFrame);
if (!aState.mCreatingExtraFrames || (aContent->IsInNativeAnonymousSubtree() &&
!aContent->GetPrimaryFrame())) {
aContent->SetPrimaryFrame(newFrame);
}
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindDataByInt(int32_t aInt, const Element& aElement,
ComputedStyle& aComputedStyle,
const FrameConstructionDataByInt* aDataPtr,
uint32_t aDataLength) {
for (const FrameConstructionDataByInt *curData = aDataPtr,
*endData = aDataPtr + aDataLength;
curData != endData; ++curData) {
if (curData->mInt == aInt) {
const FrameConstructionData* data = &curData->mData;
if (data->mBits & FCDATA_FUNC_IS_DATA_GETTER) {
return data->mFunc.mDataGetter(aElement, aComputedStyle);
}
return data;
}
}
return nullptr;
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindDataByTag(const Element& aElement,
ComputedStyle& aStyle,
const FrameConstructionDataByTag* aDataPtr,
uint32_t aDataLength) {
const nsAtom* tag = aElement.NodeInfo()->NameAtom();
for (const FrameConstructionDataByTag *curData = aDataPtr,
*endData = aDataPtr + aDataLength;
curData != endData; ++curData) {
if (curData->mTag == tag) {
const FrameConstructionData* data = &curData->mData;
if (data->mBits & FCDATA_FUNC_IS_DATA_GETTER) {
return data->mFunc.mDataGetter(aElement, aStyle);
}
return data;
}
}
return nullptr;
}
#define SUPPRESS_FCDATA() FrameConstructionData(nullptr, FCDATA_SUPPRESS_FRAME)
#define SIMPLE_INT_CREATE(_int, _func) \
{int32_t(_int), FrameConstructionData(_func)}
#define SIMPLE_INT_CHAIN(_int, _func) \
{int32_t(_int), FrameConstructionData(_func)}
#define COMPLEX_INT_CREATE(_int, _func) \
{int32_t(_int), FrameConstructionData(_func)}
#define SIMPLE_TAG_CREATE(_tag, _func) \
{nsGkAtoms::_tag, FrameConstructionData(_func)}
#define SIMPLE_TAG_CHAIN(_tag, _func) \
{nsGkAtoms::_tag, FrameConstructionData(_func)}
#define COMPLEX_TAG_CREATE(_tag, _func) \
{nsGkAtoms::_tag, FrameConstructionData(_func)}
static nsFieldSetFrame* GetFieldSetFrameFor(nsIFrame* aFrame) {
auto pseudo = aFrame->Style()->GetPseudoType();
if (pseudo == PseudoStyleType::fieldsetContent ||
pseudo == PseudoStyleType::scrolledContent ||
pseudo == PseudoStyleType::columnSet ||
pseudo == PseudoStyleType::columnContent) {
return GetFieldSetFrameFor(aFrame->GetParent());
}
return do_QueryFrame(aFrame);
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindHTMLData(const Element& aElement,
nsIFrame* aParentFrame,
ComputedStyle& aStyle) {
MOZ_ASSERT(aElement.IsHTMLElement());
NS_ASSERTION(!aParentFrame ||
aParentFrame->Style()->GetPseudoType() !=
PseudoStyleType::fieldsetContent ||
aParentFrame->GetParent()->IsFieldSetFrame(),
"Unexpected parent for fieldset content anon box");
if (aElement.IsInNativeAnonymousSubtree()) {
if (aElement.NodeInfo()->NameAtom() == nsGkAtoms::label && aParentFrame) {
if (aParentFrame->IsFileControlFrame()) {
static constexpr FrameConstructionData sFileLabelData(
NS_NewFileControlLabelFrame);
return &sFileLabelData;
}
if (aParentFrame->GetParent() &&
aParentFrame->GetParent()->IsComboboxControlFrame()) {
static constexpr FrameConstructionData sComboboxLabelData(
NS_NewComboboxLabelFrame);
return &sComboboxLabelData;
}
}
if (aStyle.GetPseudoType() == PseudoStyleType::viewTransitionOld ||
aStyle.GetPseudoType() == PseudoStyleType::viewTransitionNew) {
static constexpr FrameConstructionData sViewTransitionData(
NS_NewImageFrameForViewTransition);
return &sViewTransitionData;
}
}
static constexpr FrameConstructionDataByTag sHTMLData[] = {
SIMPLE_TAG_CHAIN(img, nsCSSFrameConstructor::FindImgData),
SIMPLE_TAG_CHAIN(mozgeneratedcontentimage,
nsCSSFrameConstructor::FindGeneratedImageData),
{nsGkAtoms::br,
{NS_NewBRFrame, FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_LINE_BREAK}},
SIMPLE_TAG_CREATE(wbr, NS_NewWBRFrame),
SIMPLE_TAG_CHAIN(input, nsCSSFrameConstructor::FindInputData),
SIMPLE_TAG_CREATE(textarea, NS_NewTextControlFrame),
SIMPLE_TAG_CHAIN(select, nsCSSFrameConstructor::FindSelectData),
SIMPLE_TAG_CHAIN(object, nsCSSFrameConstructor::FindObjectData),
SIMPLE_TAG_CHAIN(embed, nsCSSFrameConstructor::FindObjectData),
COMPLEX_TAG_CREATE(fieldset,
&nsCSSFrameConstructor::ConstructFieldSetFrame),
SIMPLE_TAG_CREATE(frameset, NS_NewHTMLFramesetFrame),
SIMPLE_TAG_CREATE(iframe, NS_NewSubDocumentFrame),
{nsGkAtoms::button,
{ToCreationFunc(NS_NewHTMLButtonControlFrame),
FCDATA_ALLOW_BLOCK_STYLES | FCDATA_ALLOW_GRID_FLEX_COLUMN,
PseudoStyleType::buttonContent}},
SIMPLE_TAG_CHAIN(canvas, nsCSSFrameConstructor::FindCanvasData),
SIMPLE_TAG_CREATE(video, NS_NewHTMLVideoFrame),
SIMPLE_TAG_CREATE(audio, NS_NewHTMLAudioFrame),
SIMPLE_TAG_CREATE(progress, NS_NewProgressFrame),
SIMPLE_TAG_CREATE(meter, NS_NewMeterFrame),
SIMPLE_TAG_CHAIN(details, nsCSSFrameConstructor::FindDetailsData),
SIMPLE_TAG_CHAIN(h1, nsCSSFrameConstructor::FindH1Data),
};
return FindDataByTag(aElement, aStyle, sHTMLData, std::size(sHTMLData));
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindGeneratedImageData(const Element& aElement,
ComputedStyle&) {
if (!aElement.IsInNativeAnonymousSubtree()) {
return nullptr;
}
auto& generatedContent = static_cast<const GeneratedImageContent&>(aElement);
if (generatedContent.IsForListStyleImageMarker()) {
static constexpr FrameConstructionData sImgData(
NS_NewImageFrameForListStyleImage);
return &sImgData;
}
static constexpr FrameConstructionData sImgData(
NS_NewImageFrameForGeneratedContentIndex);
return &sImgData;
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindImgData(const Element& aElement,
ComputedStyle& aStyle) {
if (nsImageFrame::ImageFrameTypeFor(aElement, aStyle) !=
nsImageFrame::ImageFrameType::ForElementRequest) {
// content: url gets handled by the generic code-path.
return nullptr;
}
static constexpr FrameConstructionData sImgData(NS_NewImageFrame);
return &sImgData;
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindImgControlData(const Element& aElement,
ComputedStyle& aStyle) {
if (nsImageFrame::ImageFrameTypeFor(aElement, aStyle) !=
nsImageFrame::ImageFrameType::ForElementRequest) {
return nullptr;
}
static constexpr FrameConstructionData sImgControlData(
NS_NewImageControlFrame);
return &sImgControlData;
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindSearchControlData(const Element& aElement,
ComputedStyle& aStyle) {
if (StaticPrefs::layout_forms_input_type_search_enabled()) {
static constexpr FrameConstructionData sSearchControlData(
NS_NewSearchControlFrame);
return &sSearchControlData;
}
static constexpr FrameConstructionData sTextControlData(
NS_NewTextControlFrame);
return &sTextControlData;
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindInputData(const Element& aElement,
ComputedStyle& aStyle) {
static constexpr FrameConstructionDataByInt sInputData[] = {
SIMPLE_INT_CREATE(FormControlType::InputCheckbox,
ToCreationFunc(NS_NewCheckboxRadioFrame)),
SIMPLE_INT_CREATE(FormControlType::InputRadio,
ToCreationFunc(NS_NewCheckboxRadioFrame)),
SIMPLE_INT_CREATE(FormControlType::InputFile, NS_NewFileControlFrame),
SIMPLE_INT_CHAIN(FormControlType::InputImage,
nsCSSFrameConstructor::FindImgControlData),
SIMPLE_INT_CREATE(FormControlType::InputEmail, NS_NewTextControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputText, NS_NewTextControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputTel, NS_NewTextControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputUrl, NS_NewTextControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputRange, NS_NewRangeFrame),
SIMPLE_INT_CREATE(FormControlType::InputPassword, NS_NewTextControlFrame),
{int32_t(FormControlType::InputColor),
{NS_NewColorControlFrame, 0, PseudoStyleType::buttonContent}},
SIMPLE_INT_CHAIN(FormControlType::InputSearch,
nsCSSFrameConstructor::FindSearchControlData),
SIMPLE_INT_CREATE(FormControlType::InputNumber, NS_NewNumberControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputTime, NS_NewDateTimeControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputDate, NS_NewDateTimeControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputDatetimeLocal,
NS_NewDateTimeControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputMonth, NS_NewTextControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputWeek, NS_NewTextControlFrame),
{int32_t(FormControlType::InputSubmit),
{ToCreationFunc(NS_NewGfxButtonControlFrame), 0,
PseudoStyleType::buttonContent}},
{int32_t(FormControlType::InputReset),
{ToCreationFunc(NS_NewGfxButtonControlFrame), 0,
PseudoStyleType::buttonContent}},
{int32_t(FormControlType::InputButton),
{ToCreationFunc(NS_NewGfxButtonControlFrame), 0,
PseudoStyleType::buttonContent}}
// Keeping hidden inputs out of here on purpose for so they get frames by
// display (in practice, none).
};
auto controlType = HTMLInputElement::FromNode(aElement)->ControlType();
// radio and checkbox inputs with appearance:none should be constructed
// by display type. (Note that we're not checking that appearance is
// not (respectively) StyleAppearance::Radio and StyleAppearance::Checkbox.)
if ((controlType == FormControlType::InputCheckbox ||
controlType == FormControlType::InputRadio) &&
!aStyle.StyleDisplay()->HasAppearance()) {
return nullptr;
}
return FindDataByInt(int32_t(controlType), aElement, aStyle, sInputData,
std::size(sInputData));
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindObjectData(const Element& aElement,
ComputedStyle& aStyle) {
uint32_t type;
nsCOMPtr<nsIObjectLoadingContent> objContent =
do_QueryInterface(const_cast<Element*>(&aElement));
NS_ASSERTION(objContent,
"embed and object must implement "
"nsIObjectLoadingContent!");
objContent->GetDisplayedType(&type);
static constexpr FrameConstructionDataByInt sObjectData[] = {
// TODO(emilio): Can we remove the NS_NewEmptyFrame case and just use a
// subdocument frame here?
SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_LOADING,
NS_NewEmptyFrame),
SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_DOCUMENT,
NS_NewSubDocumentFrame),
// Nothing for TYPE_FALLBACK so we'll construct frames by display there
};
return FindDataByInt((int32_t)type, aElement, aStyle, sObjectData,
std::size(sObjectData));
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindCanvasData(const Element& aElement,
ComputedStyle& aStyle) {
// We want to check whether script is enabled on the document that
// could be painting to the canvas. That's the owner document of
// the canvas, except when the owner document is a static document,
// in which case it's the original document it was cloned from.
Document* doc = aElement.OwnerDoc();
if (doc->IsStaticDocument()) {
doc = doc->GetOriginalDocument();
}
if (!doc->IsScriptEnabled()) {
return nullptr;
}
static constexpr FrameConstructionData sCanvasData(
NS_NewHTMLCanvasFrame, 0, PseudoStyleType::htmlCanvasContent);
return &sCanvasData;
}
static MOZ_NEVER_INLINE void DestroyFramesInList(PresShell* aPs,
nsFrameList& aList) {
nsIFrame::DestroyContext context(aPs);
aList.DestroyFrames(context);
}
void nsCSSFrameConstructor::ConstructFrameFromItemInternal(
FrameConstructionItem& aItem, nsFrameConstructorState& aState,
nsContainerFrame* aParentFrame, nsFrameList& aFrameList) {
const FrameConstructionData* data = aItem.mFCData;
NS_ASSERTION(data, "Must have frame construction data");
uint32_t bits = data->mBits;
NS_ASSERTION(!(bits & FCDATA_FUNC_IS_DATA_GETTER),
"Should have dealt with this inside the data finder");
// Some sets of bits are not compatible with each other
#define CHECK_ONLY_ONE_BIT(_bit1, _bit2) \
NS_ASSERTION(!(bits & _bit1) || !(bits & _bit2), \
"Only one of these bits should be set")
CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
FCDATA_FORCE_NULL_ABSPOS_CONTAINER);
CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_WRAP_KIDS_IN_BLOCKS);
CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_IS_POPUP);
CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_SKIP_ABSPOS_PUSH);
CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
FCDATA_DISALLOW_GENERATED_CONTENT);
CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_ALLOW_BLOCK_STYLES);
CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS);
CHECK_ONLY_ONE_BIT(FCDATA_WRAP_KIDS_IN_BLOCKS,
FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS);
#undef CHECK_ONLY_ONE_BIT
MOZ_ASSERT(
!(bits & FCDATA_IS_WRAPPER_ANON_BOX) || (bits & FCDATA_USE_CHILD_ITEMS),
"Wrapper anon boxes should always have FCDATA_USE_CHILD_ITEMS");
MOZ_ASSERT(!(bits & FCDATA_ALLOW_GRID_FLEX_COLUMN) ||
(bits & FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS),
"Need the block wrapper bit to create grid/flex/column.");
// Don't create a subdocument frame for iframes if we're creating extra frames
if (aState.mCreatingExtraFrames &&
aItem.mContent->IsHTMLElement(nsGkAtoms::iframe)) {
return;
}
nsIContent* const content = aItem.mContent;
nsIFrame* newFrame;
nsIFrame* primaryFrame;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
const nsStyleDisplay* display = computedStyle->StyleDisplay();
if (bits & FCDATA_FUNC_IS_FULL_CTOR) {
newFrame = (this->*(data->mFunc.mFullConstructor))(
aState, aItem, aParentFrame, display, aFrameList);
MOZ_ASSERT(newFrame, "Full constructor failed");
primaryFrame = newFrame;
} else {
newFrame = (*data->mFunc.mCreationFunc)(mPresShell, computedStyle);
const bool allowOutOfFlow = !(bits & FCDATA_DISALLOW_OUT_OF_FLOW);
const bool isPopup = aItem.mIsPopup;
nsContainerFrame* geometricParent =
(isPopup || allowOutOfFlow)
? aState.GetGeometricParent(*display, aParentFrame)
: aParentFrame;
// In the non-scrollframe case, primaryFrame and newFrame are equal; in the
// scrollframe case, newFrame is the scrolled frame while primaryFrame is
// the scrollframe.
if ((bits & FCDATA_MAY_NEED_SCROLLFRAME) &&
display->IsScrollableOverflow()) {
nsContainerFrame* scrollframe = nullptr;
BuildScrollContainerFrame(aState, content, computedStyle, newFrame,
geometricParent, scrollframe);
primaryFrame = scrollframe;
} else {
InitAndRestoreFrame(aState, content, geometricParent, newFrame);
primaryFrame = newFrame;
}
// If we need to create a block formatting context to wrap our
// kids, do it now.
nsIFrame* maybeAbsoluteContainingBlockStyleFrame = primaryFrame;
nsIFrame* maybeAbsoluteContainingBlock = newFrame;
nsIFrame* possiblyLeafFrame = newFrame;
nsContainerFrame* outerFrame = nullptr;
if (bits & FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS) {
RefPtr<ComputedStyle> outerStyle =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
data->mAnonBoxPseudo, computedStyle);
#ifdef DEBUG
nsContainerFrame* containerFrame = do_QueryFrame(newFrame);
MOZ_ASSERT(containerFrame);
#endif
nsContainerFrame* container = static_cast<nsContainerFrame*>(newFrame);
nsContainerFrame* innerFrame;
if (bits & FCDATA_ALLOW_GRID_FLEX_COLUMN) {
switch (display->DisplayInside()) {
case StyleDisplayInside::Flex:
outerFrame = NS_NewFlexContainerFrame(mPresShell, outerStyle);
InitAndRestoreFrame(aState, content, container, outerFrame);
innerFrame = outerFrame;
break;
case StyleDisplayInside::Grid:
outerFrame = NS_NewGridContainerFrame(mPresShell, outerStyle);
InitAndRestoreFrame(aState, content, container, outerFrame);
innerFrame = outerFrame;
break;
default: {
innerFrame = NS_NewBlockFrame(mPresShell, outerStyle);
if (outerStyle->StyleColumn()->IsColumnContainerStyle()) {
outerFrame = BeginBuildingColumns(aState, content, container,
innerFrame, outerStyle);
} else {
// No need to create column container. Initialize innerFrame.
InitAndRestoreFrame(aState, content, container, innerFrame);
outerFrame = innerFrame;
}
break;
}
}
} else {
innerFrame = NS_NewBlockFrame(mPresShell, outerStyle);
InitAndRestoreFrame(aState, content, container, innerFrame);
outerFrame = innerFrame;
}
SetInitialSingleChild(container, outerFrame);
container->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
// Now figure out whether newFrame or outerFrame should be the
// absolute container.
if (outerFrame->IsAbsPosContainingBlock()) {
maybeAbsoluteContainingBlock = outerFrame;
maybeAbsoluteContainingBlockStyleFrame = outerFrame;
innerFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
}
// Our kids should go into the innerFrame.
newFrame = innerFrame;
}
aState.AddChild(primaryFrame, aFrameList, content, aParentFrame,
allowOutOfFlow, allowOutOfFlow);
nsContainerFrame* newFrameAsContainer = do_QueryFrame(newFrame);
if (newFrameAsContainer) {
// Process the child content if requested
nsFrameList childList;
nsFrameConstructorSaveState absoluteSaveState;
if (bits & FCDATA_FORCE_NULL_ABSPOS_CONTAINER) {
aState.PushAbsoluteContainingBlock(nullptr, nullptr, absoluteSaveState);
} else if (!(bits & FCDATA_SKIP_ABSPOS_PUSH)) {
maybeAbsoluteContainingBlock->AddStateBits(
NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
if (maybeAbsoluteContainingBlockStyleFrame->IsAbsPosContainingBlock()) {
auto* cf =
static_cast<nsContainerFrame*>(maybeAbsoluteContainingBlock);
aState.PushAbsoluteContainingBlock(
cf, maybeAbsoluteContainingBlockStyleFrame, absoluteSaveState);
}
}
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(newFrameAsContainer, floatSaveState);
if (bits & FCDATA_USE_CHILD_ITEMS) {
// At this point, we have not set up the auto value for this frame, and
// no caller will have set it so it is not redundant and therefor will
// not assert.
AutoFrameConstructionPageName pageNameTracker(aState,
newFrameAsContainer);
ConstructFramesFromItemList(
aState, aItem.mChildItems, newFrameAsContainer,
bits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
} else {
// Process the child frames.
ProcessChildren(aState, content, computedStyle, newFrameAsContainer,
!(bits & FCDATA_DISALLOW_GENERATED_CONTENT), childList,
(bits & FCDATA_ALLOW_BLOCK_STYLES) != 0,
possiblyLeafFrame);
}
if (bits & FCDATA_WRAP_KIDS_IN_BLOCKS) {
nsFrameList newList;
nsFrameList currentBlockList;
nsIFrame* f;
while ((f = childList.FirstChild()) != nullptr) {
bool wrapFrame = IsInlineFrame(f) || IsFramePartOfIBSplit(f);
if (!wrapFrame) {
FlushAccumulatedBlock(aState, content, newFrameAsContainer,
currentBlockList, newList);
}
childList.RemoveFrame(f);
if (wrapFrame) {
currentBlockList.AppendFrame(nullptr, f);
} else {
newList.AppendFrame(nullptr, f);
}
}
FlushAccumulatedBlock(aState, content, newFrameAsContainer,
currentBlockList, newList);
if (childList.NotEmpty()) {
// an error must have occurred, delete unprocessed frames
DestroyFramesInList(mPresShell, childList);
}
childList = std::move(newList);
}
if (!(bits & FCDATA_ALLOW_GRID_FLEX_COLUMN) ||
!MayNeedToCreateColumnSpanSiblings(newFrameAsContainer, childList)) {
// Set the frame's initial child list. Note that MathML depends on this
// being called even if childList is empty!
newFrameAsContainer->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
} else {
// Extract any initial non-column-span kids, and put them in inner
// frame's child list.
nsFrameList initialNonColumnSpanKids =
childList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
newFrameAsContainer->SetInitialChildList(
FrameChildListID::Principal, std::move(initialNonColumnSpanKids));
if (childList.NotEmpty()) {
nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
aState, newFrameAsContainer, childList,
// Column content should never be a absolute/fixed positioned
// containing block. Pass nullptr as aPositionedFrame.
nullptr);
MOZ_ASSERT(outerFrame,
"outerFrame should be non-null if multi-column container "
"is created.");
FinishBuildingColumns(aState, outerFrame, newFrameAsContainer,
columnSpanSiblings);
}
}
}
}
NS_ASSERTION(newFrame->IsLineParticipant() ==
((bits & FCDATA_IS_LINE_PARTICIPANT) != 0),
"Incorrectly set FCDATA_IS_LINE_PARTICIPANT bits");
// Even if mCreatingExtraFrames is set, we may need to SetPrimaryFrame for
// generated content that doesn't have one yet. Note that we have to examine
// the frame bit, because by this point mIsGeneratedContent has been cleared
// on aItem.
if ((!aState.mCreatingExtraFrames ||
(aItem.mContent->IsRootOfNativeAnonymousSubtree() &&
!aItem.mContent->GetPrimaryFrame())) &&
!(bits & FCDATA_SKIP_FRAMESET)) {
aItem.mContent->SetPrimaryFrame(primaryFrame);
ActiveLayerTracker::TransferActivityToFrame(aItem.mContent, primaryFrame);
}
}
static void GatherSubtreeElements(Element* aElement,
nsTArray<Element*>& aElements) {
aElements.AppendElement(aElement);
StyleChildrenIterator iter(aElement);
for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
if (!c->IsElement()) {
continue;
}
GatherSubtreeElements(c->AsElement(), aElements);
}
}
nsresult nsCSSFrameConstructor::GetAnonymousContent(
nsIContent* aParent, nsIFrame* aParentFrame,
nsTArray<nsIAnonymousContentCreator::ContentInfo>& aContent) {
nsIAnonymousContentCreator* creator = do_QueryFrame(aParentFrame);
if (!creator) {
return NS_OK;
}
nsresult rv = creator->CreateAnonymousContent(aContent);
if (NS_FAILED(rv)) {
// CreateAnonymousContent failed, e.g. because the page has a <use> loop.
return rv;
}
if (aContent.IsEmpty()) {
return NS_OK;
}
const bool devtoolsEventsEnabled =
mDocument->DevToolsAnonymousAndShadowEventsEnabled();
MOZ_ASSERT(aParent->IsElement());
for (const auto& info : aContent) {
// get our child's content and set its parent to our content
nsIContent* content = info.mContent;
content->SetIsNativeAnonymousRoot();
BindContext context(*aParent->AsElement(), BindContext::ForNativeAnonymous);
rv = content->BindToTree(context, *aParent);
if (NS_FAILED(rv)) {
content->UnbindFromTree();
return rv;
}
if (devtoolsEventsEnabled) {
content->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ false);
}
}
// Some situations where we don't cache anonymous content styles:
//
// * when visibility or pointer-events is anything other than the initial
// value; we rely on visibility and pointer-events inheriting into anonymous
// content, but don't bother adding this state to the AnonymousContentKey,
// since it's not so common. Note that with overlay scrollbars, scrollbars
// always start off with pointer-events: none so we don't need to check for
// that in that case.
//
// * when the medium is anything other than screen; some UA style sheet rules
// apply in e.g. print medium, and will give different results from the
// cached styles
Maybe<bool> computedAllowStyleCaching;
auto ComputeAllowStyleCaching = [&] {
if (!StaticPrefs::layout_css_cached_scrollbar_styles_enabled()) {
return false;
}
if (aParentFrame->StyleVisibility()->mVisible != StyleVisibility::Visible) {
return false;
}
nsPresContext* pc = mPresShell->GetPresContext();
if (!pc->UseOverlayScrollbars() &&
aParentFrame->StyleUI()->ComputedPointerEvents() !=
StylePointerEvents::Auto) {
return false;
}
if (pc->Medium() != nsGkAtoms::screen) {
return false;
}
return true;
};
auto AllowStyleCaching = [&] {
if (computedAllowStyleCaching.isNothing()) {
computedAllowStyleCaching.emplace(ComputeAllowStyleCaching());
}
return computedAllowStyleCaching.value();
};
// Compute styles for the anonymous content tree.
ServoStyleSet* styleSet = mPresShell->StyleSet();
for (auto& info : aContent) {
Element* e = Element::FromNode(info.mContent);
if (!e) {
continue;
}
if (info.mKey == AnonymousContentKey::None || !AllowStyleCaching()) {
// Most NAC subtrees do not use caching of computed styles. Just go
// ahead and eagerly style the subtree.
styleSet->StyleNewSubtree(e);
continue;
}
// We have a NAC subtree for which we can use cached styles.
AutoTArray<RefPtr<ComputedStyle>, 2> cachedStyles;
AutoTArray<Element*, 2> elements;
GatherSubtreeElements(e, elements);
styleSet->GetCachedAnonymousContentStyles(info.mKey, cachedStyles);
if (cachedStyles.IsEmpty()) {
// We haven't stored cached styles for this kind of NAC subtree yet.
// Eagerly compute those styles, then cache them for later.
styleSet->StyleNewSubtree(e);
for (Element* e : elements) {
if (e->HasServoData()) {
cachedStyles.AppendElement(ServoStyleSet::ResolveServoStyle(*e));
} else {
cachedStyles.AppendElement(nullptr);
}
}
styleSet->PutCachedAnonymousContentStyles(info.mKey,
std::move(cachedStyles));
continue;
}
// We previously stored cached styles for this kind of NAC subtree.
// Iterate over them and set them on the subtree's elements.
MOZ_ASSERT(cachedStyles.Length() == elements.Length(),
"should always produce the same size NAC subtree");
for (size_t i = 0, len = cachedStyles.Length(); i != len; ++i) {
if (cachedStyles[i]) {
#ifdef DEBUG
// Assert that our cached style is the same as one we could compute.
RefPtr<ComputedStyle> cs = styleSet->ResolveStyleLazily(*elements[i]);
MOZ_ASSERT(
cachedStyles[i]->EqualForCachedAnonymousContentStyle(*cs),
"cached anonymous content styles should be identical to those we "
"would compute normally");
// All overlay scrollbars start off as inactive, so we can rely on their
// pointer-events value being always none.
MOZ_ASSERT(!mPresShell->GetPresContext()->UseOverlayScrollbars() ||
cs->StyleUI()->ComputedPointerEvents() ==
StylePointerEvents::None);
#endif
Servo_SetExplicitStyle(elements[i], cachedStyles[i]);
}
}
}
return NS_OK;
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindXULTagData(const Element& aElement,
ComputedStyle& aStyle) {
MOZ_ASSERT(aElement.IsXULElement());
static constexpr FrameConstructionData kPopupData(NS_NewMenuPopupFrame,
FCDATA_IS_POPUP);
static constexpr FrameConstructionDataByTag sXULTagData[] = {
SIMPLE_TAG_CREATE(image, NS_NewXULImageFrame),
SIMPLE_TAG_CREATE(treechildren, NS_NewTreeBodyFrame),
SIMPLE_TAG_CHAIN(label,
nsCSSFrameConstructor::FindXULLabelOrDescriptionData),
SIMPLE_TAG_CHAIN(description,
nsCSSFrameConstructor::FindXULLabelOrDescriptionData),
#ifdef XP_MACOSX
SIMPLE_TAG_CHAIN(menubar, nsCSSFrameConstructor::FindXULMenubarData),
#endif /* XP_MACOSX */
SIMPLE_TAG_CREATE(iframe, NS_NewSubDocumentFrame),
SIMPLE_TAG_CREATE(editor, NS_NewSubDocumentFrame),
SIMPLE_TAG_CREATE(browser, NS_NewSubDocumentFrame),
SIMPLE_TAG_CREATE(splitter, NS_NewSplitterFrame),
SIMPLE_TAG_CREATE(scrollbar, NS_NewScrollbarFrame),
SIMPLE_TAG_CREATE(slider, NS_NewSliderFrame),
SIMPLE_TAG_CREATE(thumb, NS_NewSimpleXULLeafFrame),
SIMPLE_TAG_CREATE(scrollcorner, NS_NewSimpleXULLeafFrame),
SIMPLE_TAG_CREATE(resizer, NS_NewSimpleXULLeafFrame),
SIMPLE_TAG_CREATE(scrollbarbutton, NS_NewScrollbarButtonFrame),
{nsGkAtoms::panel, kPopupData},
{nsGkAtoms::menupopup, kPopupData},
{nsGkAtoms::tooltip, kPopupData},
};
return FindDataByTag(aElement, aStyle, sXULTagData, std::size(sXULTagData));
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindXULLabelOrDescriptionData(const Element& aElement,
ComputedStyle&) {
// Follow CSS display value if no value attribute
if (!aElement.HasAttr(nsGkAtoms::value)) {
return nullptr;
}
// Follow CSS display if there's no crop="center".
if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::crop,
nsGkAtoms::center, eCaseMatters)) {
return nullptr;
}
static constexpr FrameConstructionData sMiddleCroppingData(
NS_NewMiddleCroppingLabelFrame);
return &sMiddleCroppingData;
}
#ifdef XP_MACOSX
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindXULMenubarData(const Element& aElement,
ComputedStyle&) {
if (aElement.OwnerDoc()->IsInChromeDocShell()) {
BrowsingContext* bc = aElement.OwnerDoc()->GetBrowsingContext();
bool isRoot = bc && !bc->GetParent();
if (isRoot) {
// This is the root. Suppress the menubar, since on Mac
// window menus are not attached to the window.
static constexpr FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
return &sSuppressData;
}
}
return nullptr;
}
#endif /* XP_MACOSX */
already_AddRefed<ComputedStyle>
nsCSSFrameConstructor::BeginBuildingScrollContainerFrame(
nsFrameConstructorState& aState, nsIContent* aContent,
ComputedStyle* aContentStyle, nsContainerFrame* aParentFrame,
PseudoStyleType aScrolledPseudo, bool aIsRoot,
nsContainerFrame*& aNewFrame) {
nsContainerFrame* scrollContainerFrame = aNewFrame;
if (!scrollContainerFrame) {
scrollContainerFrame =
NS_NewScrollContainerFrame(mPresShell, aContentStyle, aIsRoot);
InitAndRestoreFrame(aState, aContent, aParentFrame, scrollContainerFrame);
}
MOZ_ASSERT(scrollContainerFrame);
// if there are any anonymous children for the scroll frame, create
// frames for them.
//
// We can't take the normal ProcessChildren path, because the NAC needs to
// be parented to the scrollframe, and everything else needs to be parented
// to the scrolledframe.
AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> scrollNAC;
DebugOnly<nsresult> rv =
GetAnonymousContent(aContent, scrollContainerFrame, scrollNAC);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsFrameList anonymousList;
if (!scrollNAC.IsEmpty()) {
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(scrollContainerFrame, floatSaveState);
AutoFrameConstructionItemList items(this);
AutoFrameConstructionPageName pageNameTracker(aState, scrollContainerFrame);
AddFCItemsForAnonymousContent(aState, scrollContainerFrame, scrollNAC,
items, pageNameTracker);
ConstructFramesFromItemList(aState, items, scrollContainerFrame,
/* aParentIsWrapperAnonBox = */ false,
anonymousList);
}
aNewFrame = scrollContainerFrame;
scrollContainerFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
// we used the style that was passed in. So resolve another one.
ServoStyleSet* styleSet = mPresShell->StyleSet();
RefPtr<ComputedStyle> scrolledChildStyle =
styleSet->ResolveInheritingAnonymousBoxStyle(aScrolledPseudo,
aContentStyle);
scrollContainerFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(anonymousList));
return scrolledChildStyle.forget();
}
void nsCSSFrameConstructor::FinishBuildingScrollContainerFrame(
nsContainerFrame* aScrollContainerFrame, nsIFrame* aScrolledFrame) {
aScrollContainerFrame->AppendFrames(
FrameChildListID::Principal, nsFrameList(aScrolledFrame, aScrolledFrame));
}
void nsCSSFrameConstructor::BuildScrollContainerFrame(
nsFrameConstructorState& aState, nsIContent* aContent,
ComputedStyle* aContentStyle, nsIFrame* aScrolledFrame,
nsContainerFrame* aParentFrame, nsContainerFrame*& aNewFrame) {
RefPtr<ComputedStyle> scrolledContentStyle =
BeginBuildingScrollContainerFrame(
aState, aContent, aContentStyle, aParentFrame,
PseudoStyleType::scrolledContent, false, aNewFrame);
aScrolledFrame->SetComputedStyleWithoutNotification(scrolledContentStyle);
InitAndRestoreFrame(aState, aContent, aNewFrame, aScrolledFrame);
FinishBuildingScrollContainerFrame(aNewFrame, aScrolledFrame);
}
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindDisplayData(const nsStyleDisplay& aDisplay,
const Element& aElement) {
static_assert(eParentTypeCount < (1 << (32 - FCDATA_PARENT_TYPE_OFFSET)),
"Check eParentTypeCount should not overflow");
// The style system ensures that floated and positioned frames are
// block-level.
NS_ASSERTION(
!(aDisplay.IsFloatingStyle() || aDisplay.IsAbsolutelyPositionedStyle()) ||
aDisplay.IsBlockOutsideStyle(),
"Style system did not apply CSS2.1 section 9.7 fixups");
// If this is "body", try propagating its scroll style to the viewport
// Note that we need to do this even if the body is NOT scrollable;
// it might have dynamically changed from scrollable to not scrollable,
// and that might need to be propagated.
// XXXbz is this the right place to do this? If this code moves,
// make this function static.
bool propagatedScrollToViewport = false;
if (aElement.IsHTMLElement(nsGkAtoms::body)) {
if (nsPresContext* presContext = mPresShell->GetPresContext()) {
propagatedScrollToViewport =
presContext->UpdateViewportScrollStylesOverride() == &aElement;
MOZ_ASSERT(!propagatedScrollToViewport ||
!mPresShell->GetPresContext()->IsPaginated(),
"Shouldn't propagate scroll in paginated contexts");
}
}
switch (aDisplay.DisplayInside()) {
case StyleDisplayInside::Flow:
case StyleDisplayInside::FlowRoot: {
if (aDisplay.IsInlineFlow()) {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructInline,
FCDATA_IS_INLINE | FCDATA_IS_LINE_PARTICIPANT);
return &data;
}
// If the frame is a block-level frame and is scrollable, then wrap it in
// a scroll frame. Except we don't want to do that for paginated contexts
// for frames that are block-outside and aren't frames for native
// anonymous stuff.
// XXX Ignore tables for the time being (except caption)
const uint32_t kCaptionCtorFlags =
FCDATA_IS_TABLE_PART | FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable);
const bool caption = aDisplay.mDisplay == StyleDisplay::TableCaption;
const bool needScrollFrame =
aDisplay.IsScrollableOverflow() && !propagatedScrollToViewport;
if (needScrollFrame) {
const bool suppressScrollFrame =
mPresShell->GetPresContext()->IsPaginated() &&
aDisplay.IsBlockOutsideStyle() &&
!aElement.IsInNativeAnonymousSubtree();
if (!suppressScrollFrame) {
static constexpr FrameConstructionData sScrollableBlockData[2] = {
{&nsCSSFrameConstructor::ConstructScrollableBlock},
{&nsCSSFrameConstructor::ConstructScrollableBlock,
kCaptionCtorFlags}};
return &sScrollableBlockData[caption];
}
}
// Handle various non-scrollable blocks.
static constexpr FrameConstructionData sNonScrollableBlockData[2] = {
{&nsCSSFrameConstructor::ConstructNonScrollableBlock},
{&nsCSSFrameConstructor::ConstructNonScrollableBlock,
kCaptionCtorFlags}};
return &sNonScrollableBlockData[caption];
}
case StyleDisplayInside::Table: {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructTable);
return &data;
}
// NOTE: In the unlikely event that we add another table-part here that
// has a desired-parent-type (& hence triggers table fixup), we'll need to
// also update the flexbox chunk in ComputedStyle::ApplyStyleFixups().
case StyleDisplayInside::TableRowGroup: {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
FCDATA_IS_TABLE_PART |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
return &data;
}
case StyleDisplayInside::TableColumn: {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructTableCol,
FCDATA_IS_TABLE_PART |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeColGroup));
return &data;
}
case StyleDisplayInside::TableColumnGroup: {
static constexpr FrameConstructionData data(
ToCreationFunc(NS_NewTableColGroupFrame),
FCDATA_IS_TABLE_PART | FCDATA_DISALLOW_OUT_OF_FLOW |
FCDATA_SKIP_ABSPOS_PUSH |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
return &data;
}
case StyleDisplayInside::TableHeaderGroup: {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
FCDATA_IS_TABLE_PART |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
return &data;
}
case StyleDisplayInside::TableFooterGroup: {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
FCDATA_IS_TABLE_PART |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
return &data;
}
case StyleDisplayInside::TableRow: {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
FCDATA_IS_TABLE_PART |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRowGroup));
return &data;
}
case StyleDisplayInside::TableCell: {
static constexpr FrameConstructionData data(
&nsCSSFrameConstructor::ConstructTableCell,
FCDATA_IS_TABLE_PART | FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRow));
return &data;
}
case StyleDisplayInside::Flex:
case StyleDisplayInside::WebkitBox: {
static constexpr FrameConstructionData nonScrollableData(
ToCreationFunc(NS_NewFlexContainerFrame));
static constexpr FrameConstructionData data(
ToCreationFunc(NS_NewFlexContainerFrame),
FCDATA_MAY_NEED_SCROLLFRAME);
return MOZ_UNLIKELY(propagatedScrollToViewport) ? &nonScrollableData
: &data;
}
case StyleDisplayInside::Grid: {
static constexpr FrameConstructionData nonScrollableData(
ToCreationFunc(NS_NewGridContainerFrame));
static constexpr FrameConstructionData data(
ToCreationFunc(NS_NewGridContainerFrame),
FCDATA_MAY_NEED_SCROLLFRAME);
return MOZ_UNLIKELY(propagatedScrollToViewport) ? &nonScrollableData
: &data;
}
case StyleDisplayInside::Ruby: {
static constexpr FrameConstructionData data[] = {
{&nsCSSFrameConstructor::ConstructBlockRubyFrame,
FCDATA_MAY_NEED_SCROLLFRAME},
{ToCreationFunc(NS_NewRubyFrame), FCDATA_IS_LINE_PARTICIPANT}};
bool isInline = aDisplay.DisplayOutside() == StyleDisplayOutside::Inline;
return &data[isInline];
}
case StyleDisplayInside::RubyBase: {
static constexpr FrameConstructionData data(
ToCreationFunc(NS_NewRubyBaseFrame),
FCDATA_IS_LINE_PARTICIPANT |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyBaseContainer));
return &data;
}
case StyleDisplayInside::RubyBaseContainer: {
static constexpr FrameConstructionData data(
ToCreationFunc(NS_NewRubyBaseContainerFrame),
FCDATA_IS_LINE_PARTICIPANT |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby));
return &data;
}
case StyleDisplayInside::RubyText: {
static constexpr FrameConstructionData data(
ToCreationFunc(NS_NewRubyTextFrame),
FCDATA_IS_LINE_PARTICIPANT |
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyTextContainer));
return &data;
}
case StyleDisplayInside::RubyTextContainer: {
static constexpr FrameConstructionData data(
ToCreationFunc(NS_NewRubyTextContainerFrame),
FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby));
return &data;
}
default:
MOZ_ASSERT_UNREACHABLE("unknown 'display' value");
return nullptr;
}
}
nsIFrame* nsCSSFrameConstructor::ConstructScrollableBlock(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
nsContainerFrame* newFrame = nullptr;
RefPtr<ComputedStyle> scrolledContentStyle =
BeginBuildingScrollContainerFrame(
aState, content, computedStyle,
aState.GetGeometricParent(*aDisplay, aParentFrame),
PseudoStyleType::scrolledContent, false, newFrame);
// Create our block frame
// pass a temporary stylecontext, the correct one will be set later
nsContainerFrame* scrolledFrame = NS_NewBlockFrame(mPresShell, computedStyle);
// Make sure to AddChild before we call ConstructBlock so that we
// end up before our descendants in fixed-pos lists as needed.
aState.AddChild(newFrame, aFrameList, content, aParentFrame);
nsFrameList blockList;
ConstructBlock(aState, content, newFrame, newFrame, scrolledContentStyle,
&scrolledFrame, blockList,
newFrame->IsAbsPosContainingBlock() ? newFrame : nullptr);
MOZ_ASSERT(blockList.OnlyChild() == scrolledFrame,
"Scrollframe's frameList should be exactly the scrolled frame!");
FinishBuildingScrollContainerFrame(newFrame, scrolledFrame);
return newFrame;
}
nsIFrame* nsCSSFrameConstructor::ConstructNonScrollableBlock(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
ComputedStyle* const computedStyle = aItem.mComputedStyle;
nsContainerFrame* newFrame = NS_NewBlockFrame(mPresShell, computedStyle);
ConstructBlock(aState, aItem.mContent,
aState.GetGeometricParent(*aDisplay, aParentFrame),
aParentFrame, computedStyle, &newFrame, aFrameList,
newFrame->IsAbsPosContainingBlock() ? newFrame : nullptr);
return newFrame;
}
void nsCSSFrameConstructor::InitAndRestoreFrame(
const nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame, nsIFrame* aNewFrame, bool aAllowCounters) {
MOZ_ASSERT(aNewFrame, "Null frame cannot be initialized");
// Initialize the frame
aNewFrame->Init(aContent, aParentFrame, nullptr);
aNewFrame->AddStateBits(aState.mAdditionalStateBits);
if (aState.mFrameState) {
// Restore frame state for just the newly created frame.
RestoreFrameStateFor(aNewFrame, aState.mFrameState);
}
if (aAllowCounters &&
mContainStyleScopeManager.AddCounterChanges(aNewFrame)) {
CountersDirty();
}
}
already_AddRefed<ComputedStyle> nsCSSFrameConstructor::ResolveComputedStyle(
nsIContent* aContent) {
if (auto* element = Element::FromNode(aContent)) {
return ServoStyleSet::ResolveServoStyle(*element);
}
MOZ_ASSERT(aContent->IsText(),
"shouldn't waste time creating ComputedStyles for "
"comments and processing instructions");
Element* parent = aContent->GetFlattenedTreeParentElement();
MOZ_ASSERT(parent, "Text out of the flattened tree?");
// FIXME(emilio): The const_cast is unfortunate, but it's not worse than what
// we did before.
//
// We could use ResolveServoStyle, but that would involve extra unnecessary
// refcount traffic...
auto* parentStyle =
const_cast<ComputedStyle*>(Servo_Element_GetMaybeOutOfDateStyle(parent));
MOZ_ASSERT(parentStyle,
"How are we inserting text frames in an unstyled element?");
return mPresShell->StyleSet()->ResolveStyleForText(aContent, parentStyle);
}
// MathML Mod - RBS
void nsCSSFrameConstructor::FlushAccumulatedBlock(
nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame, nsFrameList& aBlockList,
nsFrameList& aNewList) {
if (aBlockList.IsEmpty()) {
// Nothing to do
return;
}
auto anonPseudo = PseudoStyleType::mozMathMLAnonymousBlock;
ComputedStyle* parentContext =
nsIFrame::CorrectStyleParentFrame(aParentFrame, anonPseudo)->Style();
ServoStyleSet* styleSet = mPresShell->StyleSet();
RefPtr<ComputedStyle> blockContext =
styleSet->ResolveInheritingAnonymousBoxStyle(anonPseudo, parentContext);
// then, create a block frame that will wrap the child frames. Make it a
// MathML frame so that Get(Absolute/Float)ContainingBlockFor know that this
// is not a suitable block.
nsContainerFrame* blockFrame =
NS_NewMathMLmathBlockFrame(mPresShell, blockContext);
InitAndRestoreFrame(aState, aContent, aParentFrame, blockFrame);
ReparentFrames(this, blockFrame, aBlockList, false);
// We have to walk over aBlockList before we hand it over to blockFrame.
for (nsIFrame* f : aBlockList) {
f->SetParentIsWrapperAnonBox();
}
// abs-pos and floats are disabled in MathML children so we don't have to
// worry about messing up those.
blockFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(aBlockList));
aNewList.AppendFrame(nullptr, blockFrame);
}
// Only <math> elements can be floated or positioned. All other MathML
// should be in-flow.
#define SIMPLE_MATHML_CREATE(_tag, _func) \
{ \
nsGkAtoms::_tag, { \
_func, FCDATA_DISALLOW_OUT_OF_FLOW | \
FCDATA_FORCE_NULL_ABSPOS_CONTAINER | \
FCDATA_WRAP_KIDS_IN_BLOCKS \
} \
}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindMathMLData(const Element& aElement,
ComputedStyle& aStyle) {
MOZ_ASSERT(aElement.IsMathMLElement());
nsAtom* tag = aElement.NodeInfo()->NameAtom();
// Handle <math> specially, because it sometimes produces inlines
if (tag == nsGkAtoms::math) {
// The IsBlockOutsideStyle() check must match what
// specified::Display::equivalent_block_display is checking for
// already-block-outside things. Though the behavior here for the
// display:table case is pretty weird...
if (aStyle.StyleDisplay()->IsBlockOutsideStyle()) {
static constexpr FrameConstructionData sBlockMathData(
ToCreationFunc(NS_NewMathMLmathBlockFrame),
FCDATA_FORCE_NULL_ABSPOS_CONTAINER | FCDATA_WRAP_KIDS_IN_BLOCKS);
return &sBlockMathData;
}
static constexpr FrameConstructionData sInlineMathData(
ToCreationFunc(NS_NewMathMLmathInlineFrame),
FCDATA_FORCE_NULL_ABSPOS_CONTAINER | FCDATA_IS_LINE_PARTICIPANT |
FCDATA_WRAP_KIDS_IN_BLOCKS);
return &sInlineMathData;
}
static constexpr FrameConstructionDataByTag sMathMLData[] = {
SIMPLE_MATHML_CREATE(annotation, NS_NewMathMLTokenFrame),
SIMPLE_MATHML_CREATE(annotation_xml, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(mi, NS_NewMathMLTokenFrame),
SIMPLE_MATHML_CREATE(mn, NS_NewMathMLTokenFrame),
SIMPLE_MATHML_CREATE(ms, NS_NewMathMLTokenFrame),
SIMPLE_MATHML_CREATE(mtext, NS_NewMathMLTokenFrame),
SIMPLE_MATHML_CREATE(mo, NS_NewMathMLmoFrame),
SIMPLE_MATHML_CREATE(mfrac, NS_NewMathMLmfracFrame),
SIMPLE_MATHML_CREATE(msup, NS_NewMathMLmmultiscriptsFrame),
SIMPLE_MATHML_CREATE(msub, NS_NewMathMLmmultiscriptsFrame),
SIMPLE_MATHML_CREATE(msubsup, NS_NewMathMLmmultiscriptsFrame),
SIMPLE_MATHML_CREATE(munder, NS_NewMathMLmunderoverFrame),
SIMPLE_MATHML_CREATE(mover, NS_NewMathMLmunderoverFrame),
SIMPLE_MATHML_CREATE(munderover, NS_NewMathMLmunderoverFrame),
SIMPLE_MATHML_CREATE(mphantom, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(mpadded, NS_NewMathMLmpaddedFrame),
SIMPLE_MATHML_CREATE(mspace, NS_NewMathMLmspaceFrame),
SIMPLE_MATHML_CREATE(none, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(mprescripts, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(mfenced, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(mmultiscripts, NS_NewMathMLmmultiscriptsFrame),
SIMPLE_MATHML_CREATE(mstyle, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(msqrt, NS_NewMathMLmrootFrame),
SIMPLE_MATHML_CREATE(mroot, NS_NewMathMLmrootFrame),
SIMPLE_MATHML_CREATE(maction, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(mrow, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(merror, NS_NewMathMLmrowFrame),
SIMPLE_MATHML_CREATE(menclose, NS_NewMathMLmencloseFrame),
SIMPLE_MATHML_CREATE(semantics, NS_NewMathMLmrowFrame)};
return FindDataByTag(aElement, aStyle, sMathMLData, std::size(sMathMLData));
}
nsContainerFrame* nsCSSFrameConstructor::ConstructFrameWithAnonymousChild(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, nsFrameList& aFrameList,
ContainerFrameCreationFunc aConstructor,
ContainerFrameCreationFunc aInnerConstructor, PseudoStyleType aInnerPseudo,
bool aCandidateRootFrame) {
nsIContent* const content = aItem.mContent;
ComputedStyle* const computedStyle = aItem.mComputedStyle;
// Create the outer frame:
nsContainerFrame* newFrame = aConstructor(mPresShell, computedStyle);
InitAndRestoreFrame(aState, content,
aCandidateRootFrame
? aState.GetGeometricParent(
*computedStyle->StyleDisplay(), aParentFrame)
: aParentFrame,
newFrame);
newFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
// Create the pseudo SC for the anonymous wrapper child as a child of the SC:
RefPtr<ComputedStyle> scForAnon =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(aInnerPseudo,
computedStyle);
// Create the anonymous inner wrapper frame
nsContainerFrame* innerFrame = aInnerConstructor(mPresShell, scForAnon);
InitAndRestoreFrame(aState, content, newFrame, innerFrame);
// Put the newly created frames into the right child list
SetInitialSingleChild(newFrame, innerFrame);
aState.AddChild(newFrame, aFrameList, content, aParentFrame,
aCandidateRootFrame, aCandidateRootFrame);
if (!mRootElementFrame && aCandidateRootFrame) {
mRootElementFrame = newFrame;
}
nsFrameConstructorSaveState floatSaveState;
aState.MaybePushFloatContainingBlock(innerFrame, floatSaveState);
nsFrameList childList;
// Process children
if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
ConstructFramesFromItemList(
aState, aItem.mChildItems, innerFrame,
aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
} else {
ProcessChildren(aState, content, computedStyle, innerFrame, true, childList,
false);
}
// Set the inner wrapper frame's initial primary list
innerFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
return newFrame;
}
nsIFrame* nsCSSFrameConstructor::ConstructOuterSVG(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
return ConstructFrameWithAnonymousChild(
aState, aItem, aParentFrame, aFrameList, NS_NewSVGOuterSVGFrame,
NS_NewSVGOuterSVGAnonChildFrame, PseudoStyleType::mozSVGOuterSVGAnonChild,
true);
}
nsIFrame* nsCSSFrameConstructor::ConstructMarker(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) {
return ConstructFrameWithAnonymousChild(
aState, aItem, aParentFrame, aFrameList, NS_NewSVGMarkerFrame,
NS_NewSVGMarkerAnonChildFrame, PseudoStyleType::mozSVGMarkerAnonChild,
false);
}
// Only outer <svg> elements can be floated or positioned. All other SVG
// should be in-flow.
#define SIMPLE_SVG_FCDATA(_func) \
FrameConstructionData(ToCreationFunc(_func), \
FCDATA_DISALLOW_OUT_OF_FLOW | \
FCDATA_SKIP_ABSPOS_PUSH | \
FCDATA_DISALLOW_GENERATED_CONTENT)
#define SIMPLE_SVG_CREATE(_tag, _func) \
{nsGkAtoms::_tag, SIMPLE_SVG_FCDATA(_func)}
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindSVGData(const Element& aElement,
nsIFrame* aParentFrame,
bool aIsWithinSVGText,
bool aAllowsTextPathChild,
ComputedStyle& aStyle) {
MOZ_ASSERT(aElement.IsSVGElement());
static constexpr FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
static constexpr FrameConstructionData sContainerData =
SIMPLE_SVG_FCDATA(NS_NewSVGContainerFrame);
bool parentIsSVG = aIsWithinSVGText;
nsIContent* parentContent =
aParentFrame ? aParentFrame->GetContent() : nullptr;
nsAtom* tag = aElement.NodeInfo()->NameAtom();
// XXXbz should this really be based on the tag of the parent frame's content?
// Should it not be based on the type of the parent frame (e.g. whether it's
// an SVG frame)?
if (parentContent) {
// It's not clear whether the SVG spec intends to allow any SVG
// content within svg:foreignObject at all (SVG 1.1, section
// 23.2), but if it does, it better be svg:svg. So given that
// we're allowing it, treat it as a non-SVG parent.
parentIsSVG =
parentContent->IsSVGElement() &&
parentContent->NodeInfo()->NameAtom() != nsGkAtoms::foreignObject;
}
if ((tag != nsGkAtoms::svg && !parentIsSVG) ||
(tag == nsGkAtoms::desc || tag == nsGkAtoms::title ||
tag == nsGkAtoms::metadata)) {
// Sections 5.1 and G.4 of SVG 1.1 say that SVG elements other than
// svg:svg not contained within svg:svg are incorrect, although they
// don't seem to specify error handling. Ignore them, since many of
// our frame classes can't deal. It *may* be that the document
// should at that point be considered in error according to F.2, but
// it's hard to tell.
//
// Style mutation can't change this situation, so don't bother
// adding to the undisplayed content map.
//
// We don't currently handle any UI for desc/title/metadata
return &sSuppressData;
}
// We don't need frames for animation elements
if (aElement.IsSVGAnimationElement()) {
return &sSuppressData;
}
if (tag == nsGkAtoms::svg && !parentIsSVG) {
// We need outer <svg> elements to have an SVGOuterSVGFrame regardless
// of whether they fail conditional processing attributes, since various
// SVG frames assume that one exists. We handle the non-rendering
// of failing outer <svg> element contents like <switch> statements,
// and do the PassesConditionalProcessingTests call in
// SVGOuterSVGFrame::Init.
static constexpr FrameConstructionData sOuterSVGData(
&nsCSSFrameConstructor::ConstructOuterSVG);
return &sOuterSVGData;
}
if (tag == nsGkAtoms::marker) {
static constexpr FrameConstructionData sMarkerSVGData(
&nsCSSFrameConstructor::ConstructMarker);
return &sMarkerSVGData;
}
if (!aElement.PassesConditionalProcessingTests()) {
// Elements with failing conditional processing attributes never get
// rendered. Note that this is not where we select which frame in a
// <switch> to render! That happens in SVGSwitchFrame::PaintSVG.
if (aIsWithinSVGText) {
// SVGTextFrame doesn't handle conditional processing attributes,
// so don't create frames for descendants of <text> with failing
// attributes. We need frames not to be created so that text layout
// is correct.
return &sSuppressData;
}
// If we're not inside <text>, create an SVGContainerFrame (which is a
// frame that doesn't render) so that paint servers can still be referenced,
// even if they live inside an element with failing conditional processing
// attributes.
return &sContainerData;
}
// Ensure that a stop frame is a child of a gradient and that gradients
// can only have stop children.
bool parentIsGradient = aParentFrame && static_cast<SVGGradientFrame*>(
do_QueryFrame(aParentFrame));
bool stop = (tag == nsGkAtoms::stop);
if ((parentIsGradient && !stop) || (!parentIsGradient && stop)) {
return &sSuppressData;
}
// Prevent bad frame types being children of filters or parents of filter
// primitives. If aParentFrame is null, we know that the frame that will
// be created will be an nsInlineFrame, so it can never be a filter.
bool parentIsFilter = aParentFrame && aParentFrame->IsSVGFilterFrame();
if ((parentIsFilter && !aElement.IsSVGFilterPrimitiveElement()) ||
(!parentIsFilter && aElement.IsSVGFilterPrimitiveElement())) {
return &sSuppressData;
}
// Prevent bad frame types being children of filter primitives or parents of
// filter primitive children. If aParentFrame is null, we know that the frame
// that will be created will be an nsInlineFrame, so it can never be a filter
// primitive.
bool parentIsFEContainerFrame =
aParentFrame && aParentFrame->IsSVGFEContainerFrame();
if ((parentIsFEContainerFrame &&
!aElement.IsSVGFilterPrimitiveChildElement()) ||
(!parentIsFEContainerFrame &&
aElement.IsSVGFilterPrimitiveChildElement())) {
return &sSuppressData;
}
// Special cases for text/tspan/textPath, because the kind of frame
// they get depends on the parent frame. We ignore 'a' elements when
// determining the parent, however.
if (aIsWithinSVGText) {
// If aIsWithinSVGText is true, then we know that the "SVG text uses
// CSS frames" pref was true when this SVG fragment was first constructed.
//
// subtrees, because TextCorrespondenceRecorder in the SVG text code doesn't
// really know how to deal with it. This kinda sucks. :(
if (aParentFrame && aParentFrame->GetContent() != aElement.GetParent()) {
return &sSuppressData;
}
// We don't use ConstructInline because we want different behavior
// for generated content.
static constexpr FrameConstructionData sTSpanData(
ToCreationFunc(NS_NewInlineFrame),
FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_SKIP_ABSPOS_PUSH |
FCDATA_DISALLOW_GENERATED_CONTENT | FCDATA_IS_LINE_PARTICIPANT |
FCDATA_IS_INLINE | FCDATA_USE_CHILD_ITEMS);
if (tag == nsGkAtoms::textPath) {
if (aAllowsTextPathChild) {
return &sTSpanData;
}
} else if (tag == nsGkAtoms::tspan || tag == nsGkAtoms::a) {
return &sTSpanData;
}
return &sSuppressData;
} else if (tag == nsGkAtoms::tspan || tag == nsGkAtoms::textPath) {
return &sSuppressData;
}
static constexpr FrameConstructionDataByTag sSVGData[] = {
SIMPLE_SVG_CREATE(svg, NS_NewSVGInnerSVGFrame),
SIMPLE_SVG_CREATE(g, NS_NewSVGGFrame),
SIMPLE_SVG_CREATE(svgSwitch, NS_NewSVGSwitchFrame),
SIMPLE_SVG_CREATE(symbol, NS_NewSVGSymbolFrame),
SIMPLE_SVG_CREATE(polygon, NS_NewSVGGeometryFrame),
SIMPLE_SVG_CREATE(polyline, NS_NewSVGGeometryFrame),
SIMPLE_SVG_CREATE(circle, NS_NewSVGGeometryFrame),
SIMPLE_SVG_CREATE(ellipse, NS_NewSVGGeometryFrame),
SIMPLE_SVG_CREATE(line, NS_NewSVGGeometryFrame),
SIMPLE_SVG_CREATE(rect, NS_NewSVGGeometryFrame),
SIMPLE_SVG_CREATE(path, NS_NewSVGGeometryFrame),
SIMPLE_SVG_CREATE(defs, NS_NewSVGContainerFrame),
{nsGkAtoms::text,
{NS_NewSVGTextFrame,
FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_ALLOW_BLOCK_STYLES,
PseudoStyleType::mozSVGText}},
{nsGkAtoms::foreignObject,
{ToCreationFunc(NS_NewSVGForeignObjectFrame),
FCDATA_DISALLOW_OUT_OF_FLOW, PseudoStyleType::mozSVGForeignContent}},
SIMPLE_SVG_CREATE(a, NS_NewSVGAFrame),
SIMPLE_SVG_CREATE(linearGradient, NS_NewSVGLinearGradientFrame),
SIMPLE_SVG_CREATE(radialGradient, NS_NewSVGRadialGradientFrame),
SIMPLE_SVG_CREATE(stop, NS_NewSVGStopFrame),
SIMPLE_SVG_CREATE(use, NS_NewSVGUseFrame),
SIMPLE_SVG_CREATE(view, NS_NewSVGViewFrame),
SIMPLE_SVG_CREATE(image, NS_NewSVGImageFrame),
SIMPLE_SVG_CREATE(clipPath, NS_NewSVGClipPathFrame),
SIMPLE_SVG_CREATE(filter, NS_NewSVGFilterFrame),
SIMPLE_SVG_CREATE(pattern, NS_NewSVGPatternFrame),
SIMPLE_SVG_CREATE(mask, NS_NewSVGMaskFrame),
SIMPLE_SVG_CREATE(feDistantLight, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(fePointLight, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(feSpotLight, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(feBlend, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feColorMatrix, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feFuncR, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(feFuncG, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(feFuncB, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(feFuncA, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(feComposite, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feComponentTransfer, NS_NewSVGFEContainerFrame),
SIMPLE_SVG_CREATE(feConvolveMatrix, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feDiffuseLighting, NS_NewSVGFEContainerFrame),
SIMPLE_SVG_CREATE(feDisplacementMap, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feDropShadow, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feFlood, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feGaussianBlur, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feImage, NS_NewSVGFEImageFrame),
SIMPLE_SVG_CREATE(feMerge, NS_NewSVGFEContainerFrame),
SIMPLE_SVG_CREATE(feMergeNode, NS_NewSVGFEUnstyledLeafFrame),
SIMPLE_SVG_CREATE(feMorphology, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feOffset, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feSpecularLighting, NS_NewSVGFEContainerFrame),
SIMPLE_SVG_CREATE(feTile, NS_NewSVGFELeafFrame),
SIMPLE_SVG_CREATE(feTurbulence, NS_NewSVGFELeafFrame)};
const FrameConstructionData* data =
FindDataByTag(aElement, aStyle, sSVGData, std::size(sSVGData));
if (!data) {
data = &sContainerData;
}
return data;
}
void nsCSSFrameConstructor::AppendPageBreakItem(
nsIContent* aContent, FrameConstructionItemList& aItems) {
RefPtr<ComputedStyle> pseudoStyle =
mPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::pageBreak);
MOZ_ASSERT(pseudoStyle->StyleDisplay()->mDisplay == StyleDisplay::Block,
"Unexpected display");
static constexpr FrameConstructionData sPageBreakData(NS_NewPageBreakFrame,
FCDATA_SKIP_FRAMESET);
aItems.AppendItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
true);
}
bool nsCSSFrameConstructor::ShouldCreateItemsForChild(
nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame) {
aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
// Remove it once that's fixed.
if (aContent->GetPrimaryFrame() &&
aContent->GetPrimaryFrame()->GetContent() == aContent &&
!aState.mCreatingExtraFrames) {
MOZ_ASSERT(false,
"asked to create frame construction item for a node that "
"already has a frame");
return false;
}
// don't create a whitespace frame if aParent doesn't want it
if (!NeedFrameFor(aState, aParentFrame, aContent)) {
return false;
}
// never create frames for comments or PIs
if (aContent->IsComment() || aContent->IsProcessingInstruction()) {
return false;
}
return true;
}
void nsCSSFrameConstructor::AddFrameConstructionItems(
nsFrameConstructorState& aState, nsIContent* aContent,
bool aSuppressWhiteSpaceOptimizations, const ComputedStyle& aParentStyle,
const InsertionPoint& aInsertion, FrameConstructionItemList& aItems,
ItemFlags aFlags) {
nsContainerFrame* parentFrame = aInsertion.mParentFrame;
if (!ShouldCreateItemsForChild(aState, aContent, parentFrame)) {
return;
}
if (MOZ_UNLIKELY(aParentStyle.StyleContent()->mContent.IsNone()) &&
StaticPrefs::layout_css_element_content_none_enabled()) {
return;
}
RefPtr<ComputedStyle> computedStyle = ResolveComputedStyle(aContent);
auto flags = aFlags + ItemFlag::AllowPageBreak;
if (parentFrame) {
if (parentFrame->IsInSVGTextSubtree()) {
flags += ItemFlag::IsWithinSVGText;
}
if (parentFrame->IsBlockFrame() && parentFrame->GetParent() &&
parentFrame->GetParent()->IsSVGTextFrame()) {
flags += ItemFlag::AllowTextPathChild;
}
}
AddFrameConstructionItemsInternal(aState, aContent, parentFrame,
aSuppressWhiteSpaceOptimizations,
computedStyle, flags, aItems);
}
// Whether we should suppress frames for a child under a <select> frame.
//
// Never create frames for non-option/optgroup kids of <select> and non-option
// kids of <optgroup> inside a <select>.
static bool ShouldSuppressFrameInSelect(const nsIContent* aParent,
const nsIContent& aChild) {
if (!aParent ||
!aParent->IsAnyOfHTMLElements(nsGkAtoms::select, nsGkAtoms::optgroup,
nsGkAtoms::option)) {
return false;
}
// Allow native anonymous content no matter what.
if (aChild.IsRootOfNativeAnonymousSubtree()) {
return false;
}
// Options with labels have their label text added in ::before by forms.css.
// Suppress frames for their child text.
if (aParent->IsHTMLElement(nsGkAtoms::option)) {
return aParent->AsElement()->HasNonEmptyAttr(nsGkAtoms::label);
}
// If we're in any display: contents subtree, just suppress the frame.
//
// We can't be regular NAC, since display: contents has no frame to generate
// them off.
if (aChild.GetParent() != aParent) {
return true;
}
// Option is always fine.
if (aChild.IsHTMLElement(nsGkAtoms::option)) {
return false;
}
// <optgroup> is OK in <select> but not in <optgroup>.
if (aChild.IsHTMLElement(nsGkAtoms::optgroup) &&
aParent->IsHTMLElement(nsGkAtoms::select)) {
return false;
}
// Anything else is not ok.
return true;
}
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindDataForContent(nsIContent& aContent,
ComputedStyle& aStyle,
nsIFrame* aParentFrame,
ItemFlags aFlags) {
MOZ_ASSERT(aStyle.StyleDisplay()->mDisplay != StyleDisplay::None &&
aStyle.StyleDisplay()->mDisplay != StyleDisplay::Contents,
"These two special display values should be handled earlier");
if (auto* text = Text::FromNode(aContent)) {
return FindTextData(*text, aParentFrame);
}
return FindElementData(*aContent.AsElement(), aStyle, aParentFrame, aFlags);
}
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindElementData(const Element& aElement,
ComputedStyle& aStyle,
nsIFrame* aParentFrame,
ItemFlags aFlags) {
// Don't create frames for non-SVG element children of SVG elements.
if (!aElement.IsSVGElement()) {
if (aParentFrame && IsFrameForSVG(aParentFrame) &&
!aParentFrame->IsSVGForeignObjectFrame()) {
return nullptr;
}
if (aFlags.contains(ItemFlag::IsWithinSVGText)) {
return nullptr;
}
}
if (auto* data = FindElementTagData(aElement, aStyle, aParentFrame, aFlags)) {
return data;
}
// Check for 'content: <image-url>' on the element (which makes us ignore
// 'display' values other than 'none' or 'contents').
if (nsImageFrame::ShouldCreateImageFrameForContentProperty(aElement,
aStyle)) {
static constexpr FrameConstructionData sImgData(
NS_NewImageFrameForContentProperty);
return &sImgData;
}
const bool shouldBlockify = aFlags.contains(ItemFlag::IsForRenderedLegend) ||
aFlags.contains(ItemFlag::IsForOutsideMarker);
if (shouldBlockify && !aStyle.StyleDisplay()->IsBlockOutsideStyle()) {
// Make a temp copy of StyleDisplay and blockify its mDisplay value.
auto display = *aStyle.StyleDisplay();
bool isRootElement = false;
uint16_t rawDisplayValue =
Servo_ComputedValues_BlockifiedDisplay(&aStyle, isRootElement);
display.mDisplay = StyleDisplay{rawDisplayValue};
return FindDisplayData(display, aElement);
}
const auto& display = *aStyle.StyleDisplay();
return FindDisplayData(display, aElement);
}
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindElementTagData(const Element& aElement,
ComputedStyle& aStyle,
nsIFrame* aParentFrame,
ItemFlags aFlags) {
switch (aElement.GetNameSpaceID()) {
case kNameSpaceID_XHTML:
return FindHTMLData(aElement, aParentFrame, aStyle);
case kNameSpaceID_MathML:
return FindMathMLData(aElement, aStyle);
case kNameSpaceID_SVG:
return FindSVGData(aElement, aParentFrame,
aFlags.contains(ItemFlag::IsWithinSVGText),
aFlags.contains(ItemFlag::AllowTextPathChild), aStyle);
case kNameSpaceID_XUL:
return FindXULTagData(aElement, aStyle);
default:
return nullptr;
}
}
void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame, bool aSuppressWhiteSpaceOptimizations,
ComputedStyle* aComputedStyle, ItemFlags aFlags,
FrameConstructionItemList& aItems) {
MOZ_ASSERT(aContent->IsText() || aContent->IsElement(),
"Shouldn't get anything else here!");
MOZ_ASSERT(aContent->IsInComposedDoc());
MOZ_ASSERT(!aContent->GetPrimaryFrame() || aState.mCreatingExtraFrames ||
aContent->NodeInfo()->NameAtom() == nsGkAtoms::area);
const bool withinSVGText = aFlags.contains(ItemFlag::IsWithinSVGText);
const bool isGeneratedContent = aFlags.contains(ItemFlag::IsGeneratedContent);
MOZ_ASSERT(!isGeneratedContent || aComputedStyle->IsPseudoElement(),
"Generated content should be a pseudo-element");
FrameConstructionItem* item = nullptr;
auto cleanupGeneratedContent = mozilla::MakeScopeExit([&]() {
if (isGeneratedContent && !item) {
MOZ_ASSERT(!IsDisplayContents(aContent),
"This would need to change if we support display: contents "
"in generated content");
aContent->UnbindFromTree();
}
});
// 'display:none' elements never creates any frames at all.
const nsStyleDisplay& display = *aComputedStyle->StyleDisplay();
if (display.mDisplay == StyleDisplay::None) {
return;
}
if (display.mDisplay == StyleDisplay::Contents) {
// See the mDisplay fixup code in StyleAdjuster::adjust.
MOZ_ASSERT(!aContent->AsElement()->IsRootOfNativeAnonymousSubtree(),
"display:contents on anonymous content is unsupported");
// trouble with everything that looks like display: contents.
if (withinSVGText) {
return;
}
CreateGeneratedContentItem(aState, aParentFrame, *aContent->AsElement(),
*aComputedStyle, PseudoStyleType::before,
aItems);
FlattenedChildIterator iter(aContent);
InsertionPoint insertion(aParentFrame, aContent);
for (nsIContent* child = iter.GetNextChild(); child;
child = iter.GetNextChild()) {
AddFrameConstructionItems(aState, child, aSuppressWhiteSpaceOptimizations,
*aComputedStyle, insertion, aItems, aFlags);
}
aItems.SetParentHasNoShadowDOM(!iter.ShadowDOMInvolved());
CreateGeneratedContentItem(aState, aParentFrame, *aContent->AsElement(),
*aComputedStyle, PseudoStyleType::after, aItems);
return;
}
nsIContent* parent = aParentFrame ? aParentFrame->GetContent() : nullptr;
if (ShouldSuppressFrameInSelect(parent, *aContent)) {
return;
}
if (aContent->IsHTMLElement(nsGkAtoms::legend) && aParentFrame) {
const nsFieldSetFrame* const fs = GetFieldSetFrameFor(aParentFrame);
if (fs && !fs->GetLegend() && !aState.mHasRenderedLegend &&
!aComputedStyle->StyleDisplay()->IsFloatingStyle() &&
!aComputedStyle->StyleDisplay()->IsAbsolutelyPositionedStyle()) {
aState.mHasRenderedLegend = true;
aFlags += ItemFlag::IsForRenderedLegend;
}
}
const FrameConstructionData* const data =
FindDataForContent(*aContent, *aComputedStyle, aParentFrame, aFlags);
if (!data || data->mBits & FCDATA_SUPPRESS_FRAME) {
return;
}
const bool isPopup = data->mBits & FCDATA_IS_POPUP;
const uint32_t bits = data->mBits;
// Inside colgroups, suppress everything except columns.
if (aParentFrame && aParentFrame->IsTableColGroupFrame() &&
(!(bits & FCDATA_IS_TABLE_PART) ||
display.mDisplay != StyleDisplay::TableColumn)) {
return;
}
const bool canHavePageBreak =
aFlags.contains(ItemFlag::AllowPageBreak) &&
aState.mPresContext->IsPaginated() &&
!display.IsAbsolutelyPositionedStyle() &&
!(aParentFrame && aParentFrame->IsFlexOrGridContainer()) &&
!(bits & FCDATA_IS_TABLE_PART) && !(bits & FCDATA_IS_SVG_TEXT);
if (canHavePageBreak && display.BreakBefore()) {
AppendPageBreakItem(aContent, aItems);
}
if (!item) {
item = aItems.AppendItem(this, data, aContent, do_AddRef(aComputedStyle),
aSuppressWhiteSpaceOptimizations);
if (aFlags.contains(ItemFlag::IsForRenderedLegend)) {
item->mIsRenderedLegend = true;
}
}
item->mIsText = !aContent->IsElement();
item->mIsGeneratedContent = isGeneratedContent;
if (isGeneratedContent) {
// We need to keep this alive until the frame takes ownership.
// This corresponds to the Release in ConstructFramesFromItem.
item->mContent->AddRef();
}
item->mIsPopup = isPopup;
if (canHavePageBreak && display.BreakAfter()) {
AppendPageBreakItem(aContent, aItems);
}
if (bits & FCDATA_IS_INLINE) {
// To correctly set item->mIsAllInline we need to build up our child items
// right now.
BuildInlineChildItems(aState, *item,
aFlags.contains(ItemFlag::IsWithinSVGText),
aFlags.contains(ItemFlag::AllowTextPathChild));
item->mIsBlock = false;
} else {
// Compute a boolean isInline which is guaranteed to be false for blocks
// (but may also be false for some inlines).
const bool isInline =
// Table-internal things are inline-outside if and only if they're kids
// of inlines, since they'll trigger construction of inline-table
// pseudos.
((bits & FCDATA_IS_TABLE_PART) &&
(!aParentFrame || // No aParentFrame means inline
aParentFrame->StyleDisplay()->IsInlineFlow())) ||
// Things that are inline-outside but aren't inline frames are inline
display.IsInlineOutsideStyle() ||
// Popups that are certainly out of flow.
isPopup;
// Set mIsAllInline conservatively. It just might be that even an inline
// that has mIsAllInline false doesn't need an {ib} split. So this is just
// an optimization to keep from doing too much work in cases when we can
// show that mIsAllInline is true..
item->mIsAllInline =
isInline ||
// Figure out whether we're guaranteed this item will be out of flow.
// This is not a precise test, since one of our ancestor inlines might
// add an absolute containing block (if it's relatively positioned) when
// there wasn't such a containing block before. But it's conservative
// in the sense that anything that will really end up as an in-flow
// non-inline will test false here. In other words, if this test is
// true we're guaranteed to be inline; if it's false we don't know what
// we'll end up as.
//
// If we make this test precise, we can remove some of the code dealing
// with the imprecision in ConstructInline and adjust the comments on
// mIsAllInline and mIsBlock in the header.
(!(bits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
aState.GetGeometricParent(display, nullptr));
// Set mIsBlock conservatively. It's OK to set it false for some real
// blocks, but not OK to set it true for things that aren't blocks. Since
// isOutOfFlow might be false even in cases when the frame will end up
// out-of-flow, we can't use it here. But we _can_ say that the frame will
// for sure end up in-flow if it's not floated or absolutely positioned.
item->mIsBlock = !isInline && !display.IsAbsolutelyPositionedStyle() &&
!display.IsFloatingStyle() && !(bits & FCDATA_IS_SVG_TEXT);
}
if (item->mIsAllInline) {
aItems.InlineItemAdded();
} else if (item->mIsBlock) {
aItems.BlockItemAdded();
}
}
/**
* Return true if the frame construction item pointed to by aIter will
* create a frame adjacent to a line boundary in the frame tree, and that
* line boundary is induced by a content node adjacent to the frame's
* content node in the content tree. The latter condition is necessary so
* that ContentAppended/ContentInserted/ContentWillBeRemoved can easily find any
* text nodes that were suppressed here.
*/
bool nsCSSFrameConstructor::AtLineBoundary(FCItemIterator& aIter) {
if (aIter.item().mSuppressWhiteSpaceOptimizations) {
return false;
}
if (aIter.AtStart()) {
if (aIter.List()->HasLineBoundaryAtStart() &&
!aIter.item().mContent->GetPreviousSibling()) {
return true;
}
} else {
FCItemIterator prev = aIter;
prev.Prev();
if (prev.item().IsLineBoundary() &&
!prev.item().mSuppressWhiteSpaceOptimizations &&
aIter.item().mContent->GetPreviousSibling() == prev.item().mContent) {
return true;
}
}
FCItemIterator next = aIter;
next.Next();
if (next.IsDone()) {
if (aIter.List()->HasLineBoundaryAtEnd() &&
!aIter.item().mContent->GetNextSibling()) {
return true;
}
} else {
if (next.item().IsLineBoundary() &&
!next.item().mSuppressWhiteSpaceOptimizations &&
aIter.item().mContent->GetNextSibling() == next.item().mContent) {
return true;
}
}
return false;
}
void nsCSSFrameConstructor::ConstructFramesFromItem(
nsFrameConstructorState& aState, FCItemIterator& aIter,
nsContainerFrame* aParentFrame, nsFrameList& aFrameList) {
FrameConstructionItem& item = aIter.item();
ComputedStyle* computedStyle = item.mComputedStyle;
if (item.mIsText) {
// If this is collapsible whitespace next to a line boundary,
// don't create a frame. item.IsWhitespace() also sets the
// NS_CREATE_FRAME_IF_NON_WHITESPACE flag in the text node. (If we
// end up creating a frame, nsTextFrame::Init will clear the flag.)
// We don't do this for generated content, because some generated
// text content is empty text nodes that are about to be initialized.
// (We check mAdditionalStateBits because only the generated content
// container's frame construction item is marked with
// mIsGeneratedContent, and we might not have an aParentFrame.)
// We don't do it for content that may have Shadow DOM siblings / insertion
// points, because they make it difficult to correctly create the frame due
// to dynamic changes.
// We don't do it for SVG text, since we might need to position and
// measure the white space glyphs due to x/y/dx/dy attributes.
if (AtLineBoundary(aIter) &&
!computedStyle->StyleText()->WhiteSpaceOrNewlineIsSignificant() &&
aIter.List()->ParentHasNoShadowDOM() &&
!(aState.mAdditionalStateBits & NS_FRAME_GENERATED_CONTENT) &&
(item.mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) &&
!(item.mFCData->mBits & FCDATA_IS_SVG_TEXT) &&
!mAlwaysCreateFramesForIgnorableWhitespace &&
item.IsWhitespace(aState)) {
return;
}
ConstructTextFrame(item.mFCData, aState, item.mContent, aParentFrame,
computedStyle, aFrameList);
return;
}
AutoRestore<nsFrameState> savedStateBits(aState.mAdditionalStateBits);
if (item.mIsGeneratedContent) {
// Ensure that frames created here are all tagged with
// NS_FRAME_GENERATED_CONTENT.
aState.mAdditionalStateBits |= NS_FRAME_GENERATED_CONTENT;
}
// XXXbz maybe just inline ConstructFrameFromItemInternal here or something?
ConstructFrameFromItemInternal(item, aState, aParentFrame, aFrameList);
if (item.mIsGeneratedContent) {
// This corresponds to the AddRef in AddFrameConstructionItemsInternal.
// The frame owns the generated content now.
item.mContent->Release();
// Now that we've passed ownership of item.mContent to the frame, unset
// our generated content flag so we don't release or unbind it ourselves.
item.mIsGeneratedContent = false;
}
}
nsContainerFrame* nsCSSFrameConstructor::GetAbsoluteContainingBlock(
nsIFrame* aFrame, ContainingBlockType aType) {
// Starting with aFrame, look for a frame that is absolutely positioned or
// relatively positioned (and transformed, if aType is FIXED)
for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
if (frame->IsMathMLFrame()) {
// If it's mathml, bail out -- no absolute positioning out from inside
// mathml frames. Note that we don't make this part of the loop
// condition because of the stuff at the end of this method...
return nullptr;
}
// Look for the ICB.
if (aType == FIXED_POS) {
LayoutFrameType t = frame->Type();
if (t == LayoutFrameType::Viewport || t == LayoutFrameType::PageContent) {
return static_cast<nsContainerFrame*>(frame);
}
}
// If the frame is positioned, we will probably return it as the containing
// block (see the exceptions below). Otherwise, we'll start looking at the
// parent frame, unless we're dealing with a scrollframe.
// Scrollframes are special since they're not positioned, but their
// scrolledframe might be. So, we need to check this special case to return
// the correct containing block (the scrolledframe) in that case.
// If we're looking for a fixed-pos containing block and the frame is
// not transformed, skip it.
if (!frame->IsAbsPosContainingBlock()) {
continue;
}
if (aType == FIXED_POS && !frame->IsFixedPosContainingBlock()) {
continue;
}
nsIFrame* absPosCBCandidate = frame;
LayoutFrameType type = absPosCBCandidate->Type();
if (type == LayoutFrameType::FieldSet) {
absPosCBCandidate =
static_cast<nsFieldSetFrame*>(absPosCBCandidate)->GetInner();
if (!absPosCBCandidate) {
continue;
}
type = absPosCBCandidate->Type();
}
if (type == LayoutFrameType::ScrollContainer) {
ScrollContainerFrame* scrollContainerFrame =
do_QueryFrame(absPosCBCandidate);
absPosCBCandidate = scrollContainerFrame->GetScrolledFrame();
if (!absPosCBCandidate) {
continue;
}
type = absPosCBCandidate->Type();
}
// Only first continuations can be containing blocks.
absPosCBCandidate = absPosCBCandidate->FirstContinuation();
// Is the frame really an absolute container?
if (!absPosCBCandidate->IsAbsoluteContainer()) {
continue;
}
// For tables, skip the inner frame and consider the table wrapper frame.
if (type == LayoutFrameType::Table) {
continue;
}
// For table wrapper frames, we can just return absPosCBCandidate.
MOZ_ASSERT((nsContainerFrame*)do_QueryFrame(absPosCBCandidate),
"abs.pos. containing block must be nsContainerFrame sub-class");
return static_cast<nsContainerFrame*>(absPosCBCandidate);
}
MOZ_ASSERT(aType != FIXED_POS, "no ICB in this frame tree?");
// It is possible for the search for the containing block to fail, because
// no absolute container can be found in the parent chain. In those cases,
// we fall back to the document element's containing block.
return mDocElementContainingBlock;
}
nsContainerFrame* nsCSSFrameConstructor::GetFloatContainingBlock(
nsIFrame* aFrame) {
// Starting with aFrame, look for a frame that is a float containing block.
// If we hit a frame which prevents its descendants from floating, bail out.
// The logic here needs to match the logic in MaybePushFloatContainingBlock().
for (nsIFrame* containingBlock = aFrame;
containingBlock && !ShouldSuppressFloatingOfDescendants(containingBlock);
containingBlock = containingBlock->GetParent()) {
if (containingBlock->IsFloatContainingBlock()) {
MOZ_ASSERT((nsContainerFrame*)do_QueryFrame(containingBlock),
"float containing block must be nsContainerFrame sub-class");
return static_cast<nsContainerFrame*>(containingBlock);
}
}
// If we didn't find a containing block, then there just isn't
// one.... return null
return nullptr;
}
/**
* This function will get the previous sibling to use for an append operation.
*
* It takes a parent frame (must not be null) and the next insertion sibling, if
* the parent content is display: contents or has ::after content (may be null).
*/
static nsIFrame* FindAppendPrevSibling(nsIFrame* aParentFrame,
nsIFrame* aNextSibling) {
aParentFrame->DrainSelfOverflowList();
if (aNextSibling) {
MOZ_ASSERT(
aNextSibling->GetParent()->GetContentInsertionFrame() == aParentFrame,
"Wrong parent");
return aNextSibling->GetPrevSibling();
}
return aParentFrame->PrincipalChildList().LastChild();
}
/**
* Finds the right parent frame to append content to aParentFrame.
*
* Cannot return or receive null.
*/
static nsContainerFrame* ContinuationToAppendTo(
nsContainerFrame* aParentFrame) {
MOZ_ASSERT(aParentFrame);
if (IsFramePartOfIBSplit(aParentFrame)) {
// If the frame we are manipulating is a ib-split frame (that is, one that's
// been created as a result of a block-in-inline situation) then we need to
// append to the last ib-split sibling, not to the frame itself.
//
// Always make sure to look at the last continuation of the frame for the
// {ib} case, even if that continuation is empty.
//
// We don't do this for the non-ib-split-frame case, since in the other
// cases appending to the last nonempty continuation is fine and in fact not
// doing that can confuse code that doesn't know to pull kids from
// continuations other than its next one.
return static_cast<nsContainerFrame*>(
GetLastIBSplitSibling(aParentFrame)->LastContinuation());
}
return nsLayoutUtils::LastContinuationWithChild(aParentFrame);
}
/**
* This function will get the next sibling for a frame insert operation given
* the parent and previous sibling. aPrevSibling may be null.
*/
static nsIFrame* GetInsertNextSibling(nsIFrame* aParentFrame,
nsIFrame* aPrevSibling) {
if (aPrevSibling) {
return aPrevSibling->GetNextSibling();
}
return aParentFrame->PrincipalChildList().FirstChild();
}
void nsCSSFrameConstructor::AppendFramesToParent(
nsFrameConstructorState& aState, nsContainerFrame* aParentFrame,
nsFrameList& aFrameList, nsIFrame* aPrevSibling, bool aIsRecursiveCall) {
MOZ_ASSERT(
!IsFramePartOfIBSplit(aParentFrame) || !GetIBSplitSibling(aParentFrame) ||
!GetIBSplitSibling(aParentFrame)->PrincipalChildList().FirstChild(),
"aParentFrame has a ib-split sibling with kids?");
MOZ_ASSERT(!aPrevSibling || aPrevSibling->GetParent() == aParentFrame,
"Parent and prevsibling don't match");
MOZ_ASSERT(
!aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
!IsFramePartOfIBSplit(aParentFrame),
"We should have wiped aParentFrame in WipeContainingBlock() "
"if it's part of an IB split!");
nsIFrame* nextSibling = ::GetInsertNextSibling(aParentFrame, aPrevSibling);
NS_ASSERTION(nextSibling || !aParentFrame->GetNextContinuation() ||
!aParentFrame->GetNextContinuation()
->PrincipalChildList()
.FirstChild() ||