Quellcode-Bibliothek nsCSSFrameConstructor.cpp
Sprache: C
/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef DEBUG // Set the environment variable GECKO_FRAMECTOR_DEBUG_FLAGS to one or // more of the following flags (comma separated) for handy debug // output. staticbool gNoisyContentUpdates = false; staticbool gReallyNoisyContentUpdates = false; staticbool gNoisyInlineConstruction = false;
// Returns true if aFrame is an anonymous flex/grid item. staticinlinebool IsAnonymousItem(const nsIFrame* aFrame) { return aFrame->Style()->GetPseudoType() == PseudoStyleType::MozAnonymousItem;
}
// Returns true IFF the given nsIFrame is a nsFlexContainerFrame and represents // a -webkit-{inline-}box container. staticinlinebool IsFlexContainerForLegacyWebKitBox(const nsIFrame* aFrame) { return aFrame->IsFlexContainerFrame() && aFrame->IsLegacyWebkitBox();
}
static MOZ_ALWAYS_INLINE 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");
}
// 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. staticbool ShouldSuppressColumnSpanDescendants(nsIFrame* aFrame) { if (aFrame->Style()->GetPseudoType() == PseudoStyleType::MozColumnContent) { // Never suppress column-span under ::-moz-column-content frames. returnfalse;
}
if (aFrame->IsInlineFrame()) { // Allow inline frames to have column-span block children. returnfalse;
}
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. returntrue;
}
returnfalse;
}
// Reparent a frame into a wrapper frame that is a child of its old parent. staticvoid 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);
}
}
//---------------------------------------------------------------------- // // When inline frames get weird and have block frames in them, we // annotate them to help us respond to incremental content changes // more easily.
staticinlinebool 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;
}
// 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());
}
// 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) { returnstatic_cast<nsContainerFrame*>(frame);
}
}
MOZ_ASSERT_UNREACHABLE("unreachable code"); return nullptr;
}
// 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?");
// 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).
inlinevoid 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;
// 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;
friendclass 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;
#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);
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->IsAbsoluteContainer()) {
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);
} elseif (aFloatCBCandidate->IsFloatContainingBlock()) {
PushFloatContainingBlock(aFloatCBCandidate, aSaveState);
}
void nsFrameConstructorState::PushFloatContainingBlock(
nsContainerFrame* aNewFloatContainingBlock,
nsFrameConstructorSaveState& aSaveState) {
MOZ_ASSERT(!aNewFloatContainingBlock ||
aNewFloatContainingBlock->IsFloatContainingBlock(), "Please push a real float containing block!");
NS_ASSERTION(
!aNewFloatContainingBlock ||
!ShouldSuppressFloatingOfDescendants(aNewFloatContainingBlock), "We should not push a frame that is supposed to _suppress_ " "floats as a float containing block!");
aSaveState.mList = &mFloatedList;
aSaveState.mSavedList = std::move(mFloatedList);
aSaveState.mChildListID = FrameChildListID::Float;
aSaveState.mState = this;
mFloatedList = AbsoluteFrameList(aNewFloatContainingBlock);
}
nsContainerFrame* nsFrameConstructorState::GetGeometricParent( const nsStyleDisplay& aStyleDisplay,
nsContainerFrame* aContentParentFrame) const { // If there is no container for a fixed, absolute, or floating root // frame, we will ignore the positioning. This hack is originally // brought to you by the letter T: tables, since other roots don't // even call into this code. See bug 178855. // // XXX Disabling positioning in this case is a hack. If one was so inclined, // one could support this either by (1) inserting a dummy block between the // table and the canvas or (2) teaching the canvas how to reflow positioned // elements. (1) has the usual problems when multiple frames share the same // content (notice all the special cases in this file dealing with inner // tables and table wrappers which share the same content). (2) requires some // work and possible factoring. // // XXXbz couldn't we just force position to "static" on roots and // float to "none"? That's OK per CSS 2.1, as far as I can tell.
if (aContentParentFrame && aContentParentFrame->IsInSVGTextSubtree()) { return aContentParentFrame;
}
if (aStyleDisplay.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) { // Bug 1491727: This function might not conform to the spec. See // https://github.com/w3c/csswg-drafts/issues/1894.
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);
}
}
// 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);
}
NS_ASSERTION(containingBlock, "Child list without containing block?");
// 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 == FrameChildListID::Absolute) {
containingBlock->GetAbsoluteContainingBlock()->SetInitialChildList(
containingBlock, aChildListID, std::move(aFrameList));
} else {
containingBlock->SetInitialChildList(aChildListID, std::move(aFrameList));
}
} elseif (childList.IsEmpty() ||
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 {
MOZ_ASSERT(aChildListID == FrameChildListID::Float); // 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();
lastChild = lastChild->FirstContinuation()->GetPlaceholderFrame();
// 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();
firstNewFrame = firstNewFrame->GetPlaceholderFrame();
// Cache the ancestor chain so that we can reuse it if needed.
AutoTArray<const nsIFrame*, 20> firstNewFrameAncestors; const nsIFrame* notCommonAncestor = nsLayoutUtils::FillAncestors(
firstNewFrame, containingBlock, &firstNewFrameAncestors);
if (nsLayoutUtils::CompareTreePosition(
lastChild, firstNewFrame, firstNewFrameAncestors,
notCommonAncestor ? containingBlock : nullptr) < 0) { // 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<std::pair<nsIFrame*, nsPlaceholderFrame*>, 128> children; for (nsIFrame* f : childList) {
children.AppendElement(
std::make_pair(f, f->FirstContinuation()->GetPlaceholderFrame()));
}
nsIFrame* insertionPoint = nullptr;
int32_t imin = 0;
int32_t max = children.Length(); while (max > imin) {
int32_t imid = imin + ((max - imin) / 2); constauto& pair = children[imid];
int32_t compare = nsLayoutUtils::CompareTreePosition(
pair.second, firstNewFrame, firstNewFrameAncestors,
notCommonAncestor ? containingBlock : nullptr); if (compare > 0) { // f is after the new frame.
max = imid;
insertionPoint = imid > 0 ? children[imid - 1].first : nullptr;
} elseif (compare < 0) { // f is before the new frame.
imin = imid + 1;
insertionPoint = pair.first;
} else { // This is for the old behavior. Should be removed once it is // guaranteed that CompareTreePosition can't return 0! // See bug 928645.
NS_WARNING("Something odd happening???");
insertionPoint = nullptr; for (auto [frame, placeholder] : children) { if (nsLayoutUtils::CompareTreePosition(
placeholder, firstNewFrame, firstNewFrameAncestors,
notCommonAncestor ? containingBlock : nullptr) > 0) { break;
}
insertionPoint = frame;
} 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);
MOZ_ASSERT(mSavedList.IsEmpty(), "Frames in mSavedList should've moved back into mState!");
MOZ_ASSERT(!mList->LastChild() || !mList->LastChild()->GetNextSibling(), "Something corrupted our list!");
}
}
/** *MovesaFrameListfromaOldParenttoaNewParent.Thisupdatestheparent *pointeroftheframesinthelist,andreparentstheirviewsasneeded. *nsIFrame::SetParentsetstheNS_FRAME_HAS_VIEWbitonaNewParentandits *ancestorsasneeded.Thenitsetsthelistastheinitialchildlist *onaNewParent,unlessaNewParenteitheralreadyhaskidsorhasbeen *reflowed;inthatcaseitappendsthenewframes.Notethatthis *methoddiffersfromReparentFramesinthatitdoesn'tchangethekids' *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. staticvoid MoveChildrenTo(nsIFrame* aOldParent, nsContainerFrame* aNewParent,
nsFrameList& aFrameList) {
aFrameList.ApplySetParent(aNewParent);
staticvoid 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 // https://drafts.csswg.org/css-page-3/#using-named-pages
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
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;
}
bool found = false;
FrameCtorDebugFlags* flag = gFrameCtorDebugFlags;
FrameCtorDebugFlags* limit = gFrameCtorDebugFlags + 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 = gFrameCtorDebugFlags;
FrameCtorDebugFlags* limit = gFrameCtorDebugFlags + 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();
}
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: { constauto string = aItem.AsString().AsString(); if (string.IsEmpty()) { return;
}
RefPtr text =
CreateGenConTextNode(aState, NS_ConvertUTF8toUTF16(string), nullptr);
aAddChild(text); return;
}
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;
}
void nsCSSFrameConstructor::CreateGeneratedContentFromListStyleType(
nsFrameConstructorState& aState, Element& aOriginatingElement, const ComputedStyle& aPseudoStyle, const FunctionRef<void(nsIContent*)> aAddChild) { using Tag = StyleCounterStyle::Tag; constauto& 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;
}
}
// 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. staticbool HasUAWidget(const Element& aOriginatingElement) { const ShadowRoot* sr = aOriginatingElement.GetShadowRoot(); return sr && sr->IsUAWidget();
}
if (aPseudoElement != PseudoStyleType::Backdrop &&
aPseudoElement != PseudoStyleType::PickerIcon &&
HasUAWidget(aOriginatingElement) &&
!aOriginatingElement.IsHTMLElement(nsGkAtoms::details)) { // ::before / ::after / ::marker shouldn't work on <video> / <input>. 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; case PseudoStyleType::Backdrop:
elemName = nsGkAtoms::mozgeneratedcontentbackdrop;
property = nsGkAtoms::backdropPseudoProperty; break; case PseudoStyleType::Checkmark:
elemName = nsGkAtoms::mozgeneratedcontentcheckmark;
property = nsGkAtoms::checkmarkPseudoProperty; break; case PseudoStyleType::PickerIcon:
elemName = nsGkAtoms::mozgeneratedcontentpickericon;
property = nsGkAtoms::pickerIconPseudoProperty; break; default:
MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement");
}
// 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());
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);
} if (aPseudoElement != PseudoStyleType::Backdrop) { 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 (constauto& 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);
}
// 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. staticbool IsTablePseudo(nsIFrame* aFrame) { auto pseudoType = aFrame->Style()->GetPseudoType(); if (pseudoType == PseudoStyleType::NotPseudo) { returnfalse;
} return pseudoType == PseudoStyleType::MozTable ||
pseudoType == PseudoStyleType::MozInlineTable ||
pseudoType == PseudoStyleType::MozTableColumnGroup ||
pseudoType == PseudoStyleType::MozTableRowGroup ||
pseudoType == PseudoStyleType::MozTableRow ||
pseudoType == PseudoStyleType::MozTableCell ||
(pseudoType == PseudoStyleType::MozCellContent &&
aFrame->GetParent()->Style()->GetPseudoType() ==
PseudoStyleType::MozTableCell) ||
(pseudoType == PseudoStyleType::MozTableWrapper && static_cast<nsTableWrapperFrame*>(aFrame)
->InnerTableFrame()
->Style()
->IsPseudoOrAnonBox());
}
// 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... staticbool IsWrapperPseudo(nsIFrame* aFrame) { auto pseudoType = aFrame->Style()->GetPseudoType(); if (!PseudoStyle::IsAnonBox(pseudoType)) { returnfalse;
} return PseudoStyle::IsWrapperAnonBox(pseudoType) || IsTablePseudo(aFrame);
}
staticbool IsInAnonymousTable(nsIFrame* aFrame) { for (nsIFrame* f = aFrame; f; f = f->GetParent()) { if (!IsWrapperPseudo(f)) { returnfalse;
} if (f->IsTableWrapperFrame()) { returntrue;
}
}
MOZ_ASSERT_UNREACHABLE("Expected to be called inside tables"); returnfalse;
}
/* 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. staticvoid 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");
// create the pseudo SC for the table wrapper as a child of the inner SC
RefPtr<ComputedStyle> outerComputedStyle =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::MozTableWrapper, 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);
}
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);
}
// 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;
}
staticvoid 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);
}
}
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 // frames, make sure Bug 368554 doesn't regress! // See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
cellFrame = NS_NewTableCellFrame(mPresShell, computedStyle, tableFrame);
}
staticinlinebool NeedFrameFor(const nsFrameConstructorState& aState,
nsContainerFrame* aParentFrame,
nsIContent* aChildContent) { // XXX the GetContent() != aChildContent check is needed due to bug 135040. // 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()) { returntrue;
}
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");
// Ensure the document element is styled at this point. // FIXME(emilio, bug 1852735): This is only needed because of the sync frame // construction from PresShell::Initialize. 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));
// --------- 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". // https://drafts.csswg.org/css-writing-modes-3/#principal-flow // // 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.");
// Push the absolute containing block now so we can absolutely position the // root element
nsFrameConstructorSaveState canvasCbSaveState;
mCanvasFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
// 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->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN); if (contentFrame->IsAbsPosContainingBlock()) {
state.PushAbsoluteContainingBlock(contentFrame, contentFrame,
absoluteSaveState);
}
} elseif (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));
} elseif (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);
}
// 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");
// 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.
ConstructAnonymousContentForRoot(state, mCanvasFrame,
mRootElementFrame->GetContent(), frameList);
mCanvasFrame->AppendFrames(FrameChildListID::Principal, std::move(frameList));
// 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);
// 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?");
MeaningofnsCSSFrameConstructorfields: mRootElementFrameis"rootelementframe".Thisistheprimaryframefor therootelement. mDocElementContainingBlockistheparentofmRootElementFrame (i.e.nsCanvasFrame) mPageSequenceFrameisthensPageSequenceFrame,ornullifthereisn'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
// --------- 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->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::MozCanvas);
} else { // 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 style for the scrollframe
RefPtr<ComputedStyle> computedStyle =
styleSet->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::MozViewportScroll);
// 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 we get here, we are rebuilding the anonymous content of the root // element. In this case, we also need to deal with the custom content // container. if (auto* container =
aState.mPresContext->Document()->GetCustomContentContainer()) { // FIXME(emilio, bug 1852735): This is only needed because of the sync frame // construction from PresShell::Initialize. See the similar code-path in // ConstructDocElementFrame. if (!container->HasServoData()) {
mPresShell->StyleSet()->StyleNewSubtree(container);
}
anonymousItems.AppendElement(container);
}
// 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();
} elseif (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);
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 |
NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN); // Make it an absolute container for fixed-pos elements
pageContentFrame->MarkAsAbsoluteContainingBlock();
} else {
MOZ_ASSERT(
pageContentFrame->HasAllStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN), "This bit should've been carried over from the previous continuation " "in nsIFrame::Init().");
MOZ_ASSERT(pageContentFrame->GetAbsoluteContainingBlock(), "nsIFrame::Init() should've constructed AbsoluteContainingBlock " "for continuations!");
}
SetInitialSingleChild(pageFrame, pageContentFrame);
// 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. staticinlinevoid 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 constauto* sel = dom::HTMLSelectElement::FromNode(aElement);
MOZ_ASSERT(sel); switch (aStyle.StyleDisplay()->EffectiveAppearance()) { case StyleAppearance::Base: case StyleAppearance::BaseSelect: return nullptr; default: break;
} if (sel->IsCombobox()) { static constexpr FrameConstructionData sComboboxData{
ToCreationFunc(NS_NewComboboxControlFrame)}; return &sComboboxData;
} // FIXME: Can we simplify this to avoid needing ConstructListboxSelectFrame, // and reuse ConstructScrollableBlock or so? static constexpr FrameConstructionData sListBoxData{
&nsCSSFrameConstructor::ConstructListBoxSelectFrame}; return &sListBoxData;
}
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));
// FIXME(bug 1588477) Don't render stuff in display: contents / Shadow DOM // 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;
}
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindHTMLButtonData(const Element&,
ComputedStyle& aStyle) { // Buttons force a (maybe inline) block unless their display is flex or grid. // TODO(emilio): It'd be good to remove this restriction more broadly. // There are some tests that expect block baselines on e.g. a `display: table` // button, but seems like it would be doable. constauto* disp = aStyle.StyleDisplay(); constbool respectDisplay = [&] { if (disp->IsInlineFlow()) { // For compat, `display: inline` and co need to create an inline-block. returnfalse;
} switch (disp->DisplayInside()) { case StyleDisplayInside::Flex: case StyleDisplayInside::Grid: case StyleDisplayInside::FlowRoot: returntrue; default: returnfalse;
}
}(); if (respectDisplay) { return nullptr;
} static constexpr FrameConstructionData sBlockData[2] = {
{&nsCSSFrameConstructor::ConstructNonScrollableBlock},
{&nsCSSFrameConstructor::ConstructScrollableBlock},
}; return &sBlockData[disp->IsScrollableOverflow()];
}
/* 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 */ 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,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputText,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputTel,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputUrl,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputRange, NS_NewRangeFrame),
SIMPLE_INT_CREATE(FormControlType::InputPassword,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputColor, NS_NewColorControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputSearch,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputNumber,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputTime, NS_NewDateTimeControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputDate, NS_NewDateTimeControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputDatetimeLocal,
NS_NewDateTimeControlFrame), // TODO: this is temporary until a frame is written: bug 888320
SIMPLE_INT_CREATE(FormControlType::InputMonth,
&nsCSSFrameConstructor::ConstructTextControl), // TODO: this is temporary until a frame is written: bug 888320
SIMPLE_INT_CREATE(FormControlType::InputWeek,
&nsCSSFrameConstructor::ConstructTextControl),
SIMPLE_INT_CREATE(FormControlType::InputSubmit,
NS_NewInputButtonControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputReset,
NS_NewInputButtonControlFrame),
SIMPLE_INT_CREATE(FormControlType::InputButton,
NS_NewInputButtonControlFrame), // 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()->HasNativeAppearance()) { return nullptr;
}
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
};
/* 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;
}
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_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");
// Don't create a subdocument frame for iframes if we're creating extra frames if (aState.mCreatingExtraFrames &&
aItem.mContent->IsHTMLElement(nsGkAtoms::iframe)) { return;
}
// 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 = NS_NewBlockFrame(mPresShell, outerStyle);
InitAndRestoreFrame(aState, content, container, innerFrame);
outerFrame = innerFrame;
// 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;
}
nsContainerFrame* newFrameAsContainer = do_QueryFrame(newFrame); if (newFrameAsContainer) { // Process the child content if requested
nsFrameList childList;
nsFrameConstructorSaveState absoluteSaveState;
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 (childList.NotEmpty()) { // an error must have occurred, delete unprocessed frames
DestroyFramesInList(mPresShell, childList);
}
childList = std::move(newList);
}
// 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));
}
}
// 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);
}
}
staticvoid 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 rv = creator->CreateAnonymousContent(aContent); if (NS_FAILED(rv)) { // CreateAnonymousContent failed, e.g. because the page has a <use> loop. return rv;
}
MOZ_ASSERT(aParent->IsElement()); for (constauto& info : aContent) { // get our child's content and set its parent to our content
nsIContent* content = info.mContent;
content->SetIsNativeAnonymousRoot();
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()) { returnfalse;
} if (aParentFrame->StyleVisibility()->mVisible != StyleVisibility::Visible) { returnfalse;
}
nsPresContext* pc = mPresShell->GetPresContext(); if (!pc->UseOverlayScrollbars() &&
aParentFrame->StyleUI()->ComputedPointerEvents() !=
StylePointerEvents::Auto) { returnfalse;
} if (pc->Medium() != nsGkAtoms::screen) { returnfalse;
} returntrue;
};
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;
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]);
}
}
}
/* 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;
}
// 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);
// we used the style that was passed in. So resolve another one.
ServoStyleSet* styleSet = mPresShell->StyleSet();
RefPtr<ComputedStyle> scrolledChildStyle =
styleSet->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::MozScrolledContent, aContentStyle);
// 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); constbool caption = aDisplay.mDisplay == StyleDisplay::TableCaption; constbool needScrollFrame =
aDisplay.IsScrollableOverflow() && !propagatedScrollToViewport; if (needScrollFrame) { constbool 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;
}
}
// 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(aNewFrame, aFrameList, content, aParentFrame);
MOZ_ASSERT(blockList.OnlyChild() == scrolledFrame, "Scrollframe's frameList should be exactly the scrolled frame!");
FinishBuildingScrollContainerFrame(aNewFrame, scrolledFrame);
}
// 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 == AllowCounters::Yes &&
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;
// 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 MATHML_DATA(_func) \
FrameConstructionData { \
_func, FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_FORCE_NULL_ABSPOS_CONTAINER | \
FCDATA_WRAP_KIDS_IN_BLOCKS \
}
// 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;
}
// Special case for elements with a display value other than none // specified in mathml.css that are not handled by this function. // These shouldn't be rendered as an mrow. if (tag == nsGkAtoms::mtable || tag == nsGkAtoms::mtr ||
tag == nsGkAtoms::mlabeledtr || tag == nsGkAtoms::mtd) { return nullptr;
}
// Create the pseudo SC for the anonymous wrapper child as a child of the SC:
RefPtr<ComputedStyle> scForAnon =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(aInnerPseudo,
computedStyle);
// 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)}
if (aElement.OwnerDoc()->IsSVGGlyphsDocument()) { // SVG elements that are explicitly not supported in svg-glyphs documents. // (Partially applies the restrictions mentioned at // https://learn.microsoft.com/en-us/typography/opentype/spec/svg#svg-capability-requirements-and-restrictions if (tag == nsGkAtoms::text || tag == nsGkAtoms::tspan ||
tag == nsGkAtoms::textPath || tag == nsGkAtoms::a ||
tag == nsGkAtoms::foreignObject || tag == nsGkAtoms::svgSwitch ||
tag == nsGkAtoms::view) { return &sSuppressData;
}
}
// 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. // // 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 (!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. // // FIXME(bug 1588477) Don't render stuff in display: contents / Shadow DOM // 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;
}
} elseif (tag == nsGkAtoms::tspan || tag == nsGkAtoms::a) { return &sTSpanData;
} return &sSuppressData;
} elseif (tag == nsGkAtoms::tspan || tag == nsGkAtoms::textPath) { return &sSuppressData;
}
bool nsCSSFrameConstructor::ShouldCreateItemsForChild(
nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame) {
aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME); // XXX the GetContent() != aContent check is needed due to bug 135040. // 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"); returnfalse;
}
// don't create a whitespace frame if aParent doesn't want it if (!NeedFrameFor(aState, aParentFrame, aContent)) { returnfalse;
}
// never create frames for comments or PIs if (aContent->IsComment() || aContent->IsProcessingInstruction()) { returnfalse;
}
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>. staticbool ShouldSuppressFrameInListboxSelect(const nsIContent* aParent, const nsIContent& aChild) { if (!aParent ||
!aParent->IsAnyOfHTMLElements(nsGkAtoms::select, nsGkAtoms::optgroup,
nsGkAtoms::option)) { returnfalse;
}
// Allow native anonymous content no matter what. if (aChild.IsRootOfNativeAnonymousSubtree()) { returnfalse;
}
// 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) { returntrue;
}
// <option> and <hr> are always fine. if (aChild.IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::hr)) { returnfalse;
}
// <optgroup> is OK in <select> but not in <optgroup>. if (aChild.IsHTMLElement(nsGkAtoms::optgroup) &&
aParent->IsHTMLElement(nsGkAtoms::select)) { returnfalse;
}
// Anything else is not ok. returntrue;
}
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);
}
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()) { // NOTE: ::backdrop is explicitly allowed because it's out of flow, but we // get here with other generated content and drop it here. We have // mechanisms to drop this at the caller instead, which we should probably // use. if (aParentFrame && IsFrameForSVG(aParentFrame) &&
!aParentFrame->IsSVGForeignObjectFrame() &&
aStyle.GetPseudoType() != PseudoStyleType::Backdrop) { 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;
}
constbool 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);
}
constbool withinSVGText = aFlags.contains(ItemFlag::IsWithinSVGText); constbool 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");
// FIXME(bug 1588477): <svg:text>'s TextNodeCorrespondenceRecorder has // trouble with everything that looks like display: contents. if (withinSVGText) { return;
}
const FrameConstructionData* const data =
FindDataForContent(*aContent, *aComputedStyle, aParentFrame, aFlags); if (!data) { return;
} const uint32_t bits = data->mBits; if (bits & FCDATA_SUPPRESS_FRAME) { return;
} // Create our shadow tree lazily if needed. // NOTE(emilio): This is rather hacky, we should ideally remove this and make // shadow tree creation faster, see bug 2017005. if (auto* input = HTMLInputElement::FromNode(aContent)) { if (auto* sr = input->CreateShadowTreeFromLayoutIfNeeded()) {
StyleNewChildRange(sr->GetFirstChild(), nullptr);
}
}
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();
}
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). constbool 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();
// 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 (aIter.AtStart()) { if (aIter.List()->HasLineBoundaryAtStart() &&
!aIter.item().mContent->GetPreviousSibling()) { returntrue;
}
} else {
FCItemIterator prev = aIter;
prev.Prev(); if (prev.item().IsLineBoundary() &&
!prev.item().mSuppressWhiteSpaceOptimizations &&
aIter.item().mContent->GetPreviousSibling() == prev.item().mContent) { returntrue;
}
}
FCItemIterator next = aIter;
next.Next(); if (next.IsDone()) { if (aIter.List()->HasLineBoundaryAtEnd() &&
!aIter.item().mContent->GetNextSibling()) { returntrue;
}
} else { if (next.item().IsLineBoundary() &&
!next.item().mSuppressWhiteSpaceOptimizations &&
aIter.item().mContent->GetNextSibling() == next.item().mContent) { returntrue;
}
}
returnfalse;
}
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;
}
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 &&
(frame->IsViewportFrame() || frame->IsPageContentFrame())) { returnstatic_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; if (absPosCBCandidate->IsFieldSetFrame()) {
absPosCBCandidate = static_cast<nsFieldSetFrame*>(absPosCBCandidate)->GetInner(); if (!absPosCBCandidate) { continue;
}
} if (absPosCBCandidate->IsScrollContainerFrame()) {
ScrollContainerFrame* scrollContainerFrame =
do_QueryFrame(absPosCBCandidate);
absPosCBCandidate = scrollContainerFrame->GetScrolledFrame(); if (!absPosCBCandidate) { continue;
}
} // Only first continuations or first IB-split siblings can be containing // blocks.
absPosCBCandidate =
nsLayoutUtils::FirstContinuationOrIBSplitSibling(absPosCBCandidate); // Is the frame really an absolute container? if (!absPosCBCandidate->IsAbsoluteContainer()) { continue;
}
// For tables, skip the inner frame and consider the table wrapper frame. if (absPosCBCandidate->IsTableFrame()) { 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"); returnstatic_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"); returnstatic_cast<nsContainerFrame*>(containingBlock);
}
}
// If we didn't find a containing block, then there just isn't // one.... return null return nullptr;
}
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. returnstatic_cast<nsContainerFrame*>(
GetLastIBSplitSibling(aParentFrame)->LastContinuation());
}
NS_ASSERTION(nextSibling || !aParentFrame->GetNextContinuation() ||
!aParentFrame->GetNextContinuation()
->PrincipalChildList()
.FirstChild() ||
aIsRecursiveCall, "aParentFrame has later continuations with kids?");
NS_ASSERTION(
nextSibling || !IsFramePartOfIBSplit(aParentFrame) ||
(IsInlineFrame(aParentFrame) && !GetIBSplitSibling(aParentFrame) &&
!aParentFrame->GetNextContinuation()) ||
aIsRecursiveCall, "aParentFrame is not last?");
// If we're inserting a list of frames at the end of the trailing inline // of an {ib} split, we may need to create additional {ib} siblings to parent // them. if (!nextSibling && IsFramePartOfIBSplit(aParentFrame)) { // When we get here, our frame list might start with a block. If it does // so, and aParentFrame is an inline, and it and all its previous // continuations have no siblings, then put the initial blocks from the // frame list into the previous block of the {ib} split. Note that we // didn't want to stop at the block part of the split when figuring out // initial parent, because that could screw up float parenting; it's easier // to do this little fixup here instead. if (aFrameList.NotEmpty() && aFrameList.FirstChild()->IsBlockOutside()) { // See whether our trailing inline is empty
nsIFrame* firstContinuation = aParentFrame->FirstContinuation(); if (firstContinuation->PrincipalChildList().IsEmpty()) { // Our trailing inline is empty. Collect our starting blocks from // aFrameList, get the right parent frame for them, and put them in.
nsFrameList blockKids =
aFrameList.Split([](nsIFrame* f) { return !f->IsBlockOutside(); });
NS_ASSERTION(blockKids.NotEmpty(), "No blocks?");
// We want to put some of the frames into this inline frame.
nsFrameList inlineKids =
aFrameList.Split([](nsIFrame* f) { return f->IsBlockOutside(); });
if (!inlineKids.IsEmpty()) {
AppendFrames(aParentFrame, FrameChildListID::Principal,
std::move(inlineKids));
}
if (!aFrameList.IsEmpty()) {
nsFrameList ibSiblings;
CreateIBSiblings(aState, aParentFrame,
aParentFrame->IsAbsPosContainingBlock(), aFrameList,
ibSiblings);
// Make sure to trigger reflow of the inline that used to be our // last one and now isn't anymore, since its GetSkipSides() has // changed.
mPresShell->FrameNeedsReflow(aParentFrame,
IntrinsicDirty::FrameAndAncestors,
NS_FRAME_HAS_DIRTY_CHILDREN);
// Recurse so we create new ib siblings as needed for aParentFrame's // parent return AppendFramesToParent(aState, aParentFrame->GetParent(), ibSiblings,
aParentFrame, true);
} return;
}
// If we're appending a list of frames to the last continuations of a // ::-moz-column-content, we may need to create column-span siblings for them. if (!nextSibling && IsLastContinuationForColumnContent(aParentFrame)) { // Extract any initial non-column-span kids, and append them to // ::-moz-column-content's child list.
nsFrameList initialNonColumnSpanKids =
aFrameList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
AppendFrames(aParentFrame, FrameChildListID::Principal,
std::move(initialNonColumnSpanKids));
if (aFrameList.IsEmpty()) { // No more kids to process (there weren't any column-span kids). return;
}
nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
aState, aParentFrame, aFrameList, // Column content should never be a absolute/fixed positioned containing // block. Pass nullptr as aPositionedFrame.
nullptr);
nsContainerFrame* columnSetWrapper = aParentFrame->GetParent(); while (!columnSetWrapper->IsColumnSetWrapperFrame()) {
columnSetWrapper = columnSetWrapper->GetParent();
}
MOZ_ASSERT(columnSetWrapper, "No ColumnSetWrapperFrame ancestor for -moz-column-content?");
auto getNearPseudo = [&](const nsIContent* aContent) -> nsIFrame* { if (aDirection == SiblingDirection::Forward) { if (auto* backdrop = nsLayoutUtils::GetBackdropFrame(aContent)) { return backdrop;
} if (auto* marker = getInsideMarkerFrame(aContent)) { return marker;
} return nsLayoutUtils::GetBeforeFrame(aContent);
} return nsLayoutUtils::GetAfterFrame(aContent);
};
auto getFarPseudo = [&](const nsIContent* aContent) -> nsIFrame* { if (aDirection == SiblingDirection::Forward) { if (auto* pickerIcon = nsLayoutUtils::GetPickerIconFrame(aContent)) { return pickerIcon;
} return nsLayoutUtils::GetAfterFrame(aContent);
} if (auto* before = nsLayoutUtils::GetBeforeFrame(aContent)) { return before;
} if (auto* marker = getInsideMarkerFrame(aContent)) { return marker;
} return nsLayoutUtils::GetBackdropFrame(aContent);
};
while (nsIContent* sibling = nextDomSibling(aIter)) { // NOTE(emilio): It's important to check GetPrimaryFrame() before // IsDisplayContents to get the correct insertion point when multiple // siblings go from display: non-none to display: contents. if (nsIFrame* primaryFrame = sibling->GetPrimaryFrame()) { // XXX the GetContent() == sibling check is needed due to bug 135040. // Remove it once that's fixed. // The rendered legend check is needed because it is conceptually out of // flow. There can only be one rendered legend at a time, and we reframe // when that changes, so this is ok. Table captions are a similar case, // but there can be multiple of them and they instead get pulled out (and // the sibling fixed up) after building the frame list. if (primaryFrame->GetContent() == sibling &&
!primaryFrame->IsRenderedLegend()) [[likely]] { return primaryFrame;
}
}
if (IsDisplayContents(sibling)) { if (nsIFrame* frame = getNearPseudo(sibling)) { return frame;
}
if (aSibling->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
aSibling = aSibling->GetPlaceholderFrame();
MOZ_ASSERT(aSibling);
}
MOZ_ASSERT(!aSibling->GetPrevContinuation(), "How?"); if (aDirection == SiblingDirection::Backward) { // The frame may be a ib-split frame (a split inline frame that contains a // block). Get the last part of that split. if (IsFramePartOfIBSplit(aSibling)) {
aSibling = GetLastIBSplitSibling(aSibling);
}
// The frame may have a continuation. If so, we want the last // non-overflow-container continuation as our previous sibling.
aSibling = aSibling->GetTailContinuation();
}
// Our siblings (if any) do not have a frame to guide us. The frame for the // target content should be inserted whereever a frame for the container would // be inserted. This is needed when inserting into display: contents nodes. const nsIContent* current = aIter.Parent(); while (IsDisplayContents(current)) { const nsIContent* parent = current->GetFlattenedTreeParent();
MOZ_ASSERT(parent, "No display: contents on the root");
nsIFrame* nsCSSFrameConstructor::GetInsertionPrevSibling(
InsertionPoint* aInsertion, nsIContent* aChild, bool* aIsAppend) {
MOZ_ASSERT(aInsertion->mParentFrame, "Must have parent frame to start with");
*aIsAppend = false;
// Find the frame that precedes the insertion point.
FlattenedChildIterator iter(aInsertion->mContainer); if (!aChild->IsRootOfNativeAnonymousSubtree()) { // The check for IsRootOfNativeAnonymousSubtree() is because editor is // severely broken and calls us directly for native anonymous // nodes that it creates.
iter.Seek(aChild);
} else { // Prime the iterator for the call to FindPreviousSibling.
iter.GetNextChild();
MOZ_ASSERT(aChild->GetProperty(nsGkAtoms::restylableAnonymousNode), "Someone passed native anonymous content directly into frame " "construction. Stop doing that!");
}
// Note that FindPreviousSibling is passed the iterator by value, so that // the later usage of the iterator starts from the same place.
nsIFrame* prevSibling = FindPreviousSibling(iter);
// Now, find the geometric parent so that we can handle continuations // properly. Use the prev sibling if we have it; otherwise use the next // sibling. if (prevSibling) {
aInsertion->mParentFrame =
prevSibling->GetParent()->GetContentInsertionFrame();
if (IsAnonymousItem(aInsertion->mParentFrame)) { // Special-case anonymous flex / grid items: Elements are blockified // inside those containers, so the anonymous item is unlikely to be the // right parent unless we're inserting a non-whitespace text-node. // // If we guess wrong, we catch this in WipeContainingBlock, but with a // performance penalty (and sometimes correctness too, see bug 2014986). // // TODO(emilio): Are there other situations where this is worth doing // (some table anon boxes or something?). Seems hard to do at this stage // where we still don't _quite_ know the details of what aChild is (in or // out of flow, display inside or outside...). Maybe WipeContainingBlock // should be able to fix-up the insertion point at the last minute? But // that seems more sketchy since some code does rely on looking at the // insertion point to decide what to construct.
AssertAnonymousFlexOrGridItemParent(aInsertion->mParentFrame); if (!prevSibling->GetNextSibling() &&
(!aChild->IsText() || aChild->TextIsOnlyWhitespace())) {
prevSibling = aInsertion->mParentFrame->GetTailContinuation();
aInsertion->mParentFrame = prevSibling->GetParent();
}
}
*aIsAppend =
!::GetInsertNextSibling(aInsertion->mParentFrame, prevSibling) &&
!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(
aInsertion->mParentFrame) &&
!IsWrapperPseudo(aInsertion->mParentFrame);
} elseif (nsIFrame* nextSibling = FindNextSibling(iter)) { // If there is no previous sibling, then find the frame that follows
aInsertion->mParentFrame =
nextSibling->GetParent()->GetContentInsertionFrame(); if (IsAnonymousItem(aInsertion->mParentFrame)) { // See the prevSibling special-case above.
AssertAnonymousFlexOrGridItemParent(aInsertion->mParentFrame); if (!nextSibling->GetPrevSibling() &&
(!aChild->IsText() || aChild->TextIsOnlyWhitespace())) {
aInsertion->mParentFrame =
aInsertion->mParentFrame->FirstContinuation()->GetParent();
}
}
} else { // No previous or next sibling, so treat this like an appended frame.
*aIsAppend = true;
nsContainerFrame* nsCSSFrameConstructor::GetContentInsertionFrameFor(
nsIContent* aContent) {
nsIFrame* frame; while (!(frame = aContent->GetPrimaryFrame())) { if (!IsDisplayContents(aContent)) { return nullptr;
}
aContent = aContent->GetFlattenedTreeParent(); if (!aContent) { return nullptr;
}
}
// If the content of the frame is not the desired content then this is not // really a frame for the desired content. // XXX This check is needed due to bug 135040. Remove it once that's fixed. if (frame->GetContent() != aContent) { return nullptr;
}
NS_ASSERTION(!insertionFrame || insertionFrame == frame || !frame->IsLeaf(), "The insertion frame is the primary frame or the primary frame " "isn't a leaf");
return insertionFrame;
}
staticbool IsSpecialFramesetChild(nsIContent* aContent) { // IMPORTANT: This must match the conditions in nsHTMLFramesetFrame::Init. return aContent->IsAnyOfHTMLElements(nsGkAtoms::frameset, nsGkAtoms::frame);
}
void nsCSSFrameConstructor::AddTextItemIfNeeded(
nsFrameConstructorState& aState, const ComputedStyle& aParentStyle, const InsertionPoint& aInsertion, nsIContent* aPossibleTextContent,
FrameConstructionItemList& aItems) {
MOZ_ASSERT(aPossibleTextContent, "Must have node"); if (!aPossibleTextContent->IsText() ||
!aPossibleTextContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) ||
aPossibleTextContent->HasFlag(NODE_NEEDS_FRAME)) { // Not text, or not suppressed due to being all-whitespace (if it were being // suppressed, it would have the NS_CREATE_FRAME_IF_NON_WHITESPACE flag), or // going to be reframed anyway. return;
}
MOZ_ASSERT(!aPossibleTextContent->GetPrimaryFrame(), "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
AddFrameConstructionItems(aState, aPossibleTextContent, false, aParentStyle,
aInsertion, aItems);
}
void nsCSSFrameConstructor::ReframeTextIfNeeded(nsIContent* aContent) { if (!aContent->IsText() ||
!aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) ||
aContent->HasFlag(NODE_NEEDS_FRAME)) { // Not text, or not suppressed due to being all-whitespace (if it were being // suppressed, it would have the NS_CREATE_FRAME_IF_NON_WHITESPACE flag), or // going to be reframed anyway. return;
}
MOZ_ASSERT(!aContent->GetPrimaryFrame(), "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
ContentInserted(aContent, InsertionKind::Async);
}
#ifdef DEBUG void nsCSSFrameConstructor::CheckBitsForLazyFrameConstruction(
nsIContent* aParent) { // If we hit a node with no primary frame, or the NODE_NEEDS_FRAME bit set // we want to assert, but leaf frames that process their own children and may // ignore anonymous children (eg framesets) make this complicated. So we set // these two booleans if we encounter these situations and unset them if we // hit a node with a leaf frame. // // It's fine if one of node without primary frame is in a display:none // subtree. // // Also, it's fine if one of the nodes without primary frame is a display: // contents node. bool noPrimaryFrame = false; bool needsFrameBitSet = false;
nsIContent* content = aParent; while (content && !content->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) { if (content->GetPrimaryFrame() && content->GetPrimaryFrame()->IsLeaf()) {
noPrimaryFrame = needsFrameBitSet = false;
} if (!noPrimaryFrame && !content->GetPrimaryFrame()) {
noPrimaryFrame = !IsDisplayContents(content);
} if (!needsFrameBitSet && content->HasFlag(NODE_NEEDS_FRAME)) {
needsFrameBitSet = true;
}
content = content->GetFlattenedTreeParent();
} if (content && content->GetPrimaryFrame() &&
content->GetPrimaryFrame()->IsLeaf()) {
noPrimaryFrame = needsFrameBitSet = false;
}
MOZ_ASSERT(!noPrimaryFrame, "Ancestors of nodes with frames to be " "constructed lazily should have frames");
MOZ_ASSERT(!needsFrameBitSet, "Ancestors of nodes with frames to be " "constructed lazily should not have NEEDS_FRAME bit set");
} #endif
// Returns true if this operation can be lazy, false if not. // // NOTE(emilio): This function assumes that the flattened tree // parent of all the appended children is the same, which holds because this // gets called after GetRangeInsertionPoint which notifies individually. void nsCSSFrameConstructor::ConstructLazily(nsIContent* aStartChild,
nsIContent* aEndChild) {
MOZ_ASSERT(aStartChild->GetParent());
// We can construct lazily; just need to set suitable bits in the content // tree.
Element* parent = aStartChild->GetFlattenedTreeParentElement(); if (!parent) { // Not part of the flat tree, nothing to do. return;
}
if (Servo_Element_IsDisplayNone(parent)) { // Nothing to do either. // // FIXME(emilio): This should be an assert, except for weird <frameset> // stuff that does its own frame construction. Such an assert would fire in // layout/style/crashtests/1411478.html, for example. return;
}
// Set NODE_NEEDS_FRAME on the new nodes. for (nsIContent* child = aStartChild; child != aEndChild;
child = child->GetNextSibling()) {
NS_ASSERTION(!child->GetPrimaryFrame() ||
child->GetPrimaryFrame()->GetContent() != child, // XXX the child->GetPrimaryFrame()->GetContent() != child // check is needed due to bug 135040. Remove it once that's // fixed. "setting NEEDS_FRAME on a node that already has a frame?");
child->SetFlags(NODE_NEEDS_FRAME);
}
// If the children of the container may be distributed to different insertion // points, insert them separately and bail out, letting ContentInserted handle // the mess. if (aStartChild->GetParentNode()->GetShadowRoot()) {
IssueSingleInsertNofications(aStartChild, aEndChild, aInsertionKind); return {};
}
// Now the flattened tree parent of all the siblings is the same, just use the // same insertion point. return GetInsertionPoint(aStartChild);
}
bool nsCSSFrameConstructor::MaybeRecreateForFrameset(nsIFrame* aParentFrame,
nsIContent* aStartChild,
nsIContent* aEndChild) { if (aParentFrame->IsFrameSetFrame()) { // Check whether we have any kids we care about. for (nsIContent* cur = aStartChild; cur != aEndChild;
cur = cur->GetNextSibling()) { if (IsSpecialFramesetChild(cur)) { // Just reframe the parent, since framesets are weird like that.
RecreateFramesForContent(aParentFrame->GetContent(),
InsertionKind::Async); returntrue;
}
}
} returnfalse;
}
for (nsIContent* child = aStartChild; child != aEndChild;
child = child->GetNextSibling()) { if (!child->IsElement()) { continue;
}
Element* childElement = child->AsElement();
// We only come in here from non-lazy frame construction, so the children // should be unstyled.
MOZ_ASSERT(!childElement->HasServoData());
#ifdef DEBUG
{ // Furthermore, all of them should have the same flattened tree parent // (GetRangeInsertionPoint ensures it). And that parent should be styled, // otherwise we would've never found an insertion point at all.
Element* parent = childElement->GetFlattenedTreeParentElement();
MOZ_ASSERT(parent);
MOZ_ASSERT(parent->HasServoData());
MOZ_ASSERT(
IsFlattenedTreeChild(parent, child), "GetFlattenedTreeParent and ChildIterator don't agree, fix this!");
} #endif
styleSet->StyleNewSubtree(childElement);
}
}
// This is a bit slow, but sometimes we need it. staticbool ParentIsWrapperAnonBox(nsIFrame* aParent) {
nsIFrame* maybeAnonBox = aParent; if (maybeAnonBox->Style()->GetPseudoType() ==
PseudoStyleType::MozCellContent) { // The thing that would maybe be a wrapper anon box is the cell.
maybeAnonBox = maybeAnonBox->GetParent();
} return maybeAnonBox->Style()->IsWrapperAnonBox();
}
// It's very likely that we don't have any existing captions (because we only // render one of them, see bug 144517). We also don't fragment table captions. // So, prefer just walking the existing captions and searching it, rather than // adding a special GetInsertionPrevSibling() version that skips everything but // table captions. static nsIFrame* FindCaptionPrevSibling(nsTableWrapperFrame* aTable,
nsIContent* aCaptionContent) {
nsIFrame* prevSibling = aTable->InnerTableFrame(); if (nsIFrame* firstCaption = prevSibling->GetNextSibling()) {
nsContentUtils::NodeIndexCache cache; for (auto* caption = firstCaption; caption;
caption = caption->GetNextSibling()) { if (nsContentUtils::CompareTreePosition<TreeKind::Flat>(
caption->GetContent(), aCaptionContent, nullptr, &cache) >= 0) { break;
}
prevSibling = caption;
}
} return prevSibling;
}
// ContentRangeInserted handles creating frames for a range of nodes that // might not be at the end of their childlist. ContentRangeInserted isn't a real // content notification, but rather it handles regular ContentInserted calls // for a single node as well as the lazy construction of frames for a range of // nodes when called from CreateNeededFrames. For a range of nodes to be // suitable to have its frames constructed all at once they must meet the same // conditions that ContentAppended imposes (GetRangeInsertionPoint checks // these), plus more. void nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aStartChild,
nsIContent* aEndChild,
InsertionKind aInsertionKind) {
AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::ContentRangeInserted",
LAYOUT_FrameConstruction);
AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
MOZ_ASSERT(aStartChild, "must always pass a child");
for (nsIContent* child = aStartChild; child != aEndChild;
child = child->GetNextSibling()) { // XXX the GetContent() != child check is needed due to bug 135040. // Remove it once that's fixed.
MOZ_ASSERT(
!child->GetPrimaryFrame() ||
child->GetPrimaryFrame()->GetContent() != child, "asked to construct a frame for a node that already has a frame");
} #endif
// If we have a null parent, then this must be the document element being // inserted, or some other child of the document in the DOM (might be a // processing instruction or comment). if (!aStartChild->GetParent()) {
Element* docElement = mDocument->GetRootElement(); constbool foundRoot = [&] { for (nsIContent* cur = aStartChild; cur != aEndChild;
cur = cur->GetNextSibling()) { if (cur == docElement) { returntrue;
}
} returnfalse;
}();
if (!foundRoot) { // Not the root element (could be e.g. a comment), just bail out return;
}
MOZ_ASSERT(!mRootElementFrame, "root element frame already created"); if (aInsertionKind == InsertionKind::Async) {
docElement->SetFlags(NODE_NEEDS_FRAME);
LazilyStyleNewChildRange(docElement, nullptr); return;
}
// Create frames for the document element and its child elements if (ConstructDocElementFrame(docElement)) {
InvalidateCanvasIfNeeded(mPresShell, docElement); #ifdef DEBUG if (gReallyNoisyContentUpdates) {
printf( "nsCSSFrameConstructor::ContentRangeInserted: resulting frame " "model:\n");
mRootElementFrame->List(stdout);
} #endif
}
constbool isSingleInsert = aStartChild->GetNextSibling() == aEndChild;
InsertionPoint insertion; if (isSingleInsert) { // See if we have a Shadow DOM insertion point. If so, then that's our real // parent frame; if not, then the frame hasn't been built yet and we just // bail.
insertion = GetInsertionPoint(aStartChild);
} else { // Get our insertion point. If we need to issue single ContentInserteds // GetRangeInsertionPoint will take care of that for us.
LAYOUT_PHASE_TEMP_EXIT();
insertion = GetRangeInsertionPoint(aStartChild, aEndChild, aInsertionKind);
LAYOUT_PHASE_TEMP_REENTER();
}
if (!insertion.mParentFrame) { // We're punting on frame construction because there's no container frame. // The Servo-backed style system handles this case like the lazy frame // construction case, except when we're already constructing frames, in // which case we shouldn't need to do anything else. if (aInsertionKind == InsertionKind::Async) {
LazilyStyleNewChildRange(aStartChild, aEndChild);
} return;
}
// Don't construct kids of leaves if (insertion.mParentFrame->IsLeaf()) { // Clear lazy bits so we don't try to construct again.
ClearLazyBits(aStartChild, aEndChild); return;
}
LAYOUT_PHASE_TEMP_EXIT(); if (WipeInsertionParent(insertion.mParentFrame)) {
LAYOUT_PHASE_TEMP_REENTER(); return;
}
LAYOUT_PHASE_TEMP_REENTER();
// Recover state for the containing block - we need to know if // it has :first-letter or :first-line style applied to it. The // reason we care is that the internal structure in these cases // is not the normal structure and requires custom updating // logic.
nsContainerFrame* containingBlock = state.mFloatedList.mContainingBlock; bool haveFirstLetterStyle = false; bool haveFirstLineStyle = false;
// In order to shave off some cycles, we only dig up the // containing block haveFirst* flags if the parent frame where // the insertion/append is occurring is an inline or block // container. For other types of containers this isn't relevant.
StyleDisplayInside parentDisplayInside =
insertion.mParentFrame->StyleDisplay()->DisplayInside();
// Examine the insertion.mParentFrame where the insertion is taking // place. If it's a certain kind of container then some special // processing is done. if (StyleDisplayInside::Flow == parentDisplayInside) { // Recover the special style flags for the containing block if (containingBlock) {
haveFirstLetterStyle = HasFirstLetterStyle(containingBlock);
haveFirstLineStyle = ShouldHaveFirstLineStyle(
containingBlock->GetContent(), containingBlock->Style());
}
if (haveFirstLetterStyle) { // If our current insertion.mParentFrame is a Letter frame, use its parent // as our new parent hint if (insertion.mParentFrame->IsLetterFrame()) { // If insertion.mParentFrame is out of flow, then we actually want the // parent of the placeholder frame. if (insertion.mParentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
nsPlaceholderFrame* placeholderFrame =
insertion.mParentFrame->GetPlaceholderFrame();
NS_ASSERTION(placeholderFrame, "No placeholder for out-of-flow?");
insertion.mParentFrame = placeholderFrame->GetParent();
} else {
insertion.mParentFrame = insertion.mParentFrame->GetParent();
}
}
// Remove the old letter frames before doing the insertion
RemoveLetterFrames(mPresShell, state.mFloatedList.mContainingBlock);
// Removing the letterframes messes around with the frame tree, removing // and creating frames. We need to reget our prevsibling, parent frame, // etc.
prevSibling = GetInsertionPrevSibling(&insertion, aStartChild, &isAppend);
frameType = insertion.mParentFrame->Type();
}
}
// This handles fallback to 'list-style-type' when a 'list-style-image' fails // to load. if (aStartChild->IsInNativeAnonymousSubtree() &&
aStartChild->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)) {
MOZ_ASSERT(isSingleInsert);
MOZ_ASSERT(insertion.mParentFrame->Style()->GetPseudoType() ==
PseudoStyleType::Marker, "we can only handle ::marker fallback for now");
nsIContent* const nextSibling = aStartChild->GetNextSibling();
MOZ_ASSERT(nextSibling && nextSibling->IsText(), "expected a text node after the list-style-image image");
DestroyContext context(mPresShell);
RemoveFrame(context, FrameChildListID::Principal,
nextSibling->GetPrimaryFrame()); auto* const container = aStartChild->GetParent()->AsElement();
nsIContent* firstNewChild = nullptr; auto InsertChild = [this, container, nextSibling,
&firstNewChild](RefPtr<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->InsertChildBefore(aChild, nextSibling, 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);
} if (!firstNewChild) {
firstNewChild = aChild;
}
};
CreateGeneratedContentFromListStyleType(
state, *insertion.mContainer->AsElement(),
*insertion.mParentFrame->Style(), InsertChild); if (!firstNewChild) { // No fallback content - we're done. return;
}
aStartChild = firstNewChild;
MOZ_ASSERT(firstNewChild->GetNextSibling() == nextSibling, "list-style-type should only create one child");
}
AutoFrameConstructionItemList items(this);
RefPtr<ComputedStyle> parentStyle =
ResolveComputedStyle(insertion.mContainer);
ParentType parentType = GetParentType(frameType);
FlattenedChildIterator iter(insertion.mContainer); constbool haveNoShadowDOM =
!iter.ShadowDOMInvolved() || !iter.GetNextChild(); if (aStartChild->GetPreviousSibling() && parentType == eTypeBlock &&
haveNoShadowDOM) { // If there's a text node in the normal content list just before the // new nodes, and it has no frame, make a frame construction item for // it, because it might need a frame now. No need to do this if our // parent type is not block, though, since WipeContainingBlock // already handles that situation.
AddTextItemIfNeeded(state, *parentStyle, insertion,
aStartChild->GetPreviousSibling(), items);
}
if (aEndChild && parentType == eTypeBlock && haveNoShadowDOM) { // If there's a text node in the normal content list just after the // new nodes, and it has no frame, make a frame construction item for // it, because it might need a frame now. No need to do this if our // parent type is not block, though, since WipeContainingBlock // already handles that situation.
AddTextItemIfNeeded(state, *parentStyle, insertion, aEndChild, items);
}
// Perform special check for diddling around with the frames in a special // inline frame. If we're appending before :after content, then we're not // really appending, so let WipeContainingBlock know that.
LAYOUT_PHASE_TEMP_EXIT(); if (WipeContainingBlock(state, containingBlock, insertion.mParentFrame, items,
isAppend, prevSibling)) {
LAYOUT_PHASE_TEMP_REENTER(); return;
}
LAYOUT_PHASE_TEMP_REENTER();
// If the parent is a block frame, and we're not in a special case // where frames can be moved around, determine if the list is for the // start or end of the block. if (insertion.mParentFrame->IsBlockFrameOrSubclass() &&
!haveFirstLetterStyle && !haveFirstLineStyle &&
!IsFramePartOfIBSplit(insertion.mParentFrame)) {
items.SetLineBoundaryAtStart(!prevSibling ||
!prevSibling->IsInlineOutside() ||
prevSibling->IsBrFrame()); auto* nextSibling =
::GetInsertNextSibling(insertion.mParentFrame, prevSibling);
items.SetLineBoundaryAtEnd(!nextSibling ||
!nextSibling->IsInlineOutside() ||
nextSibling->IsBrFrame());
} // To suppress whitespace-only text frames, we have to verify that // our container's DOM child list matches its flattened tree child list.
items.SetParentHasNoShadowDOM(haveNoShadowDOM);
if (state.mPresContext->IsPaginated()) { // Because this function can be called outside frame construction, we need // to set state.mAutoPageNameValue based on what the parent frame's auto // value is. // Calling this from outside the frame constructor can violate many of the // expectations in AutoFrameConstructionPageName, and unlike during frame // construction we already have an auto value from parentFrame, so we do // not use AutoFrameConstructionPageName here.
state.mAutoPageNameValue = insertion.mParentFrame->GetAutoPageValue(); #ifdef DEBUG
insertion.mParentFrame->mWasVisitedByAutoFrameConstructionPageName = true; #endif
}
// If the container is a table and a caption will be appended, it needs to be // put in the table wrapper frame's additional child list. // We make no attempt here to set flags to indicate whether the list // will be at the start or end of a block. It doesn't seem worthwhile.
nsFrameList frameList, captionList;
ConstructFramesFromItemList(state, items, insertion.mParentFrame,
ParentIsWrapperAnonBox(insertion.mParentFrame),
frameList);
if (frameList.NotEmpty()) { for (nsIContent* child = aStartChild; child != aEndChild;
child = child->GetNextSibling()) {
InvalidateCanvasIfNeeded(mPresShell, child);
}
if (LayoutFrameType::Table == frameType ||
LayoutFrameType::TableWrapper == frameType) {
PullOutCaptionFrames(frameList, captionList); if (prevSibling && prevSibling->IsTableCaption()) { // This can happen, but only if the table is empty (otherwise // SafeToInsertPseudoNeedingChildren bails).
prevSibling = nullptr;
}
}
}
if (haveFirstLineStyle && insertion.mParentFrame == containingBlock &&
isAppend) { // It's possible that the new frame goes into a first-line // frame. Look at it and see...
AppendFirstLineFrames(state, containingBlock->GetContent(), containingBlock,
frameList);
} elseif (insertion.mParentFrame->Style()->IsInFirstLineSubtree()) {
CheckForFirstLineInsertion(insertion.mParentFrame, frameList);
CheckForFirstLineInsertion(insertion.mParentFrame, captionList);
}
// We might have captions; put them into the principal child list of the table // wrapper frame. if (captionList.NotEmpty()) {
NS_ASSERTION(LayoutFrameType::Table == frameType ||
LayoutFrameType::TableWrapper == frameType, "parent for caption is not table?"); // We need to determine where to put the caption items; start with the // the parent frame that has already been determined and get the insertion // prevsibling of the first caption item.
nsContainerFrame* outerTable = insertion.mParentFrame->IsTableFrame()
? insertion.mParentFrame->GetParent()
: insertion.mParentFrame;
// If the parent is not a table wrapper frame we will try to add frames // to a named child list that the parent does not honor and the frames // will get lost.
MOZ_ASSERT(outerTable->IsTableWrapperFrame(), "Pseudo frame construction failure; " "a caption can be only a child of a table wrapper frame");
if (frameList.NotEmpty()) { // Notify the parent frame if (isAppend) {
AppendFramesToParent(state, insertion.mParentFrame, frameList,
prevSibling);
} else {
InsertFrames(insertion.mParentFrame, FrameChildListID::Principal,
prevSibling, std::move(frameList));
}
}
if (haveFirstLetterStyle) { // Recover the letter frames for the containing block when // it has first-letter style.
RecoverLetterFrames(state.mFloatedList.mContainingBlock);
}
staticbool IsOnlyNonWhitespaceFrameInList( const nsFrameList& aFrameList, const nsIFrame* aFrame, const nsIFrame* aIgnoreFrame = nullptr) { for (const nsIFrame* f : aFrameList) { if (f == aIgnoreFrame) { continue;
} if (f == aFrame) { // If we have continuations, ignore them too.
aFrame = aFrame->GetNextContinuation();
} elseif (!IsWhitespaceFrame(f) && !IsSyntheticColGroup(f)) { // Synthetic colgroups get created unconditionally, so let's not consider // them as giving us a non-whitespace frame. returnfalse;
}
} returntrue;
}
staticbool AllChildListsAreEffectivelyEmpty(nsIFrame* aFrame) { for (auto& [list, listID] : aFrame->ChildLists()) { if (list.IsEmpty()) { continue;
} // We have some existing frame, usually that would be considered as making // this list nonempty. But let's make an exception for the synthetic // colgroup that tables have, since that gets created unconditionally. if (listID == FrameChildListID::Principal && aFrame->IsTableFrame()) { if (nsIFrame* f = list.OnlyChild(); f && IsSyntheticColGroup(f)) { continue;
}
} returnfalse;
} returntrue;
}
// Returns true if aFrame is the only meaningful child of aParent (which is // known to be a wrapper-pseudo). This lets us determine whether aParent can be // removed, as a result of aFrame being removed. staticbool IsOnlyMeaningfulChildOfWrapperPseudo(nsIFrame* aFrame,
nsIFrame* aParent) {
MOZ_ASSERT(IsWrapperPseudo(aParent)); // Handle a few special cases with tables and colgroups / captions. if (aParent->IsTableFrame()) { auto* wrapper = aParent->GetParent();
MOZ_ASSERT(wrapper);
MOZ_ASSERT(wrapper->IsTableWrapperFrame());
MOZ_ASSERT(!aFrame->IsTableCaption(), "Caption parent should be the wrapper"); // We can't remove the table if there are any captions present (captions are // never anonymous themselves), because table wrapper always relies on // having a table frame. if (!wrapper->PrincipalChildList().OnlyChild()) { returnfalse;
}
} if (aFrame->IsTableCaption()) {
MOZ_ASSERT(aParent->IsTableWrapperFrame()); auto* table = static_cast<nsTableWrapperFrame*>(aParent)->InnerTableFrame();
MOZ_ASSERT(table); return IsOnlyNonWhitespaceFrameInList(aParent->PrincipalChildList(), aFrame, /* aIgnoreFrame = */ table) && // This checks for both colgroups and the principal list of the table // frame.
AllChildListsAreEffectivelyEmpty(table);
} return IsOnlyNonWhitespaceFrameInList(aParent->PrincipalChildList(), aFrame);
}
staticbool CanRemoveWrapperPseudoForChildRemoval(nsIFrame* aFrame,
nsIFrame* aParent) { if (!IsOnlyMeaningfulChildOfWrapperPseudo(aFrame, aParent)) { returnfalse;
} if (aParent->IsRubyBaseContainerFrame()) { // We can't remove the first ruby base container of a ruby frame unless // it has no siblings. See CreateNeededPseudoSiblings. return aParent->GetPrevSibling() || !aParent->GetNextSibling();
} returntrue;
}
bool nsCSSFrameConstructor::ContentWillBeRemoved(nsIContent* aChild,
RemovalKind aKind) {
MOZ_ASSERT(aChild);
MOZ_ASSERT(
!aChild->IsRootOfNativeAnonymousSubtree() || !aChild->GetNextSibling(), "Anonymous roots don't have siblings");
AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::ContentWillBeRemoved",
LAYOUT_FrameConstruction);
AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
nsPresContext* presContext = mPresShell->GetPresContext();
MOZ_ASSERT(presContext, "Our presShell should have a valid presContext");
// We want to detect when the viewport override element stored in the // prescontext is in the subtree being removed. Except in fullscreen cases // (which are handled in Element::UnbindFromTree and do not get stored on the // prescontext), the override element is always either the root element or a // <body> child of the root element. So we can only be removing the stored // override element if the thing being removed is either the override element // itself or the root element (which can be a parent of the override element). // // The !wasRemovingContent check makes sure that we don't re-enter here from // other ContentWillBeRemoved calls, as that'd be useless work, and we don't // want to incorrectly pick aChild again as our viewport scroll style element // if it's getting removed from the DOM. if ((aChild == presContext->GetViewportScrollStylesOverrideElement() ||
aChild->IsRootElement()) &&
!wasRemovingContent) { // We might be removing the element that we propagated viewport scrollbar // styles from. Recompute those. (This clause covers two of the three // possible scrollbar-propagation sources: the <body> [as aChild or a // descendant] and the root node. The other possible scrollbar-propagation // source is a fullscreen element, and we have code elsewhere to update // scrollbars after fullscreen elements are removed -- specifically, it's // part of the fullscreen cleanup code called by Element::UnbindFromTree. // We don't handle the fullscreen case here, because it doesn't change the // scrollbar styles override element stored on the prescontext.) const Element* removingElement =
aKind == RemovalKind::Dom ? aChild->AsElement() : nullptr;
Element* newOverrideElement =
presContext->UpdateViewportScrollStylesOverride(removingElement);
// If aChild is the root, then we don't need to do any reframing of // newOverrideElement, because we're about to tear down the whole frame tree // anyway. And we need to make sure we don't do any such reframing, because // reframing the <body> can trigger a reframe of the <html> and then reenter // here. // // But if aChild is not the root, and if newOverrideElement is not the root // and isn't aChild (which it could be if all we're doing here is reframing // the current override element), it needs reframing. In particular, it // used to have a scrollframe (because its overflow was not "visible"), but // now it will propagate its overflow to the viewport, so it should not need // a scrollframe anymore. if (aChild->GetParent() && newOverrideElement &&
newOverrideElement->GetParent() && newOverrideElement != aChild) {
LAYOUT_PHASE_TEMP_EXIT();
RecreateFramesForContent(newOverrideElement, InsertionKind::Async);
LAYOUT_PHASE_TEMP_REENTER();
}
}
#ifdef DEBUG if (gNoisyContentUpdates) {
printf( "nsCSSFrameConstructor::ContentWillBeRemoved container=%p child=%p\n",
aChild->GetParent(), aChild); if (gReallyNoisyContentUpdates) {
aChild->GetParent()->List(stdout, 0);
}
} #endif
nsIFrame* childFrame = aChild->GetPrimaryFrame(); if (!childFrame || childFrame->GetContent() != aChild) { // XXXbz the GetContent() != aChild check is needed due to bug 135040. // Remove it once that's fixed.
childFrame = nullptr;
}
// If we're removing the root, then make sure to remove things starting at // the viewport's child instead of the primary frame (which might even be // null if the root was display:none, even though the frames above it got // created). Detecting removal of a root is a little exciting; in particular, // having no parent is necessary but NOT sufficient. // // Due to how we process reframes, the content node might not even be in our // document by now. So explicitly check whether the viewport's first kid's // content node is aChild. // // FIXME(emilio): I think the "might not be in our document" bit is impossible // now. bool isRoot = false; if (!aChild->GetParent()) { if (nsIFrame* viewport = GetRootFrame()) {
nsIFrame* firstChild = viewport->PrincipalChildList().FirstChild(); if (firstChild && firstChild->GetContent() == aChild) {
isRoot = true;
childFrame = firstChild;
NS_ASSERTION(!childFrame->GetNextSibling(), "How did that happen?");
}
}
}
// We need to be conservative about when to determine whether something has // display: contents or not because at this point our actual display may be // different. // // Consider the case of: // // <div id="A" style="display: contents"><div id="B"></div></div> // // If we reconstruct A because its display changed to "none", we still need to // cleanup the frame on B, but A's display is now "none", so we can't poke at // the style of it. // // FIXME(emilio, bug 1450366): We can make this faster without adding much // complexity for the display: none -> other case, which right now // unnecessarily walks the content tree down. auto CouldHaveBeenDisplayContents = [aKind](nsIContent* aContent) -> bool { return aContent->IsElement() && (aKind != RemovalKind::Dom ||
IsDisplayContents(aContent->AsElement()));
};
if (!childFrame) { if (CouldHaveBeenDisplayContents(aChild)) { // NOTE(emilio): We may iterate through ::before and ::after here and they // may be gone after the respective ContentWillBeRemoved call. Right now // StyleChildrenIterator handles that properly, so it's not an issue.
StyleChildrenIterator iter(aChild); for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) { if (c->GetPrimaryFrame() || CouldHaveBeenDisplayContents(c)) {
LAYOUT_PHASE_TEMP_EXIT(); bool didReconstruct = ContentWillBeRemoved(c, aKind);
LAYOUT_PHASE_TEMP_REENTER(); if (didReconstruct) { returntrue;
}
}
}
} returnfalse;
}
if (aKind != RemovalKind::Dom) { // Before removing the frames associated with the content object, // ask them to save their state onto our state object.
CaptureStateForFramesOf(aChild, mFrameTreeState);
}
InvalidateCanvasIfNeeded(mPresShell, aChild);
// See whether we need to remove more than just childFrame
LAYOUT_PHASE_TEMP_EXIT(); if (MaybeRecreateContainerForFrameRemoval(childFrame)) {
LAYOUT_PHASE_TEMP_REENTER(); returntrue;
}
LAYOUT_PHASE_TEMP_REENTER();
// Get the childFrame's parent frame
nsIFrame* parentFrame = childFrame->GetParent();
LayoutFrameType parentType = parentFrame->Type();
if (parentType == LayoutFrameType::FrameSet &&
IsSpecialFramesetChild(aChild)) { // Just reframe the parent, since framesets are weird like that.
LAYOUT_PHASE_TEMP_EXIT();
RecreateFramesForContent(parentFrame->GetContent(), InsertionKind::Async);
LAYOUT_PHASE_TEMP_REENTER(); returntrue;
}
// If we're a child of MathML, then we should reframe the MathML content. // If we're non-MathML, then we would be wrapped in a block so we need to // check our grandparent in that case.
nsIFrame* possibleMathMLAncestor = parentType == LayoutFrameType::Block
? parentFrame->GetParent()
: parentFrame; if (possibleMathMLAncestor->IsMathMLFrame()) {
LAYOUT_PHASE_TEMP_EXIT();
RecreateFramesForContent(parentFrame->GetContent(), InsertionKind::Async);
LAYOUT_PHASE_TEMP_REENTER(); returntrue;
}
#ifdef ACCESSIBILITY if (aKind != RemovalKind::ForReconstruction) { if (nsAccessibilityService* accService = GetAccService()) {
accService->ContentRemoved(mPresShell, aChild);
}
} #endif
// Examine the containing-block for the removed content and see if // :first-letter style applies.
nsIFrame* inflowChild = childFrame; if (childFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
inflowChild = childFrame->GetPlaceholderFrame();
NS_ASSERTION(inflowChild, "No placeholder for out-of-flow?");
}
nsContainerFrame* containingBlock =
GetFloatContainingBlock(inflowChild->GetParent()); bool haveFLS = containingBlock && HasFirstLetterStyle(containingBlock); if (haveFLS) { // Trap out to special routine that handles adjusting a blocks // frame tree when first-letter style is present. #ifdef NOISY_FIRST_LETTER
printf("ContentWillBeRemoved: containingBlock=");
containingBlock->ListTag(stdout);
printf(" parentFrame=");
parentFrame->ListTag(stdout);
printf(" childFrame=");
childFrame->ListTag(stdout);
printf("\n"); #endif
// First update the containing blocks structure by removing the // existing letter frames. This makes the subsequent logic // simpler.
RemoveLetterFrames(mPresShell, containingBlock);
// Recover childFrame and parentFrame
childFrame = aChild->GetPrimaryFrame(); if (!childFrame || childFrame->GetContent() != aChild) { // XXXbz the GetContent() != aChild check is needed due to bug 135040. // Remove it once that's fixed. returnfalse;
}
parentFrame = childFrame->GetParent();
parentType = parentFrame->Type();
// Notify the parent frame that it should delete the frame if (childFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
childFrame = childFrame->GetPlaceholderFrame();
NS_ASSERTION(childFrame, "Missing placeholder frame for out of flow.");
parentFrame = childFrame->GetParent();
}
// Take care of wrapper anonymous boxes that we might need to remove while // at it. Note that MaybeRecreateContainerForFrameRemoval takes care of // harder cases (merging sibling anonymous boxes etc). while (IsWrapperPseudo(parentFrame) &&
CanRemoveWrapperPseudoForChildRemoval(childFrame, parentFrame)) {
childFrame = parentFrame;
parentFrame = childFrame->GetParent();
}
constbool canSkipWhitespaceFixup = [&] { if (aKind == RemovalKind::ForReconstruction) { // If we're just reconstructing frames for the element, then the // following ContentInserted notification on the element will // take care of fixing up any adjacent white-space text nodes. returntrue;
} switch (parentFrame->Type()) { case LayoutFrameType::Table: case LayoutFrameType::TableRow: case LayoutFrameType::TableRowGroup: case LayoutFrameType::TableCol: case LayoutFrameType::TableColGroup: case LayoutFrameType::TableWrapper: { // Tables ignore all whitespace within their wrappers, so we can avoid // reconstructing adjacent whitespace if the table is not anonymous. if (!IsInAnonymousTable(parentFrame)) { returntrue;
} // If the table is anonymous and the child is at the edges, we might // need to create whitespace at the edges of the table that wasn't // there before, so we can only skip the check if childFrame has frames // around. Captions are extra-special here, because they're siblings // with the main table frame, so we can't count that as a relevant // sibling for our purposes. auto* prevSibling = childFrame->GetPrevSibling(); return prevSibling && !prevSibling->IsTableFrame() &&
childFrame->GetNextSibling();
} case LayoutFrameType::GridContainer: case LayoutFrameType::FlexContainer: // Flex and grid containers similarly skip whitespace and wrap // non-whitespace in anonymous flex items, so any change to // white-space that could matter would have triggered the // reconstruction of the container itself due to merging anonymous // grid / flex items. returntrue; default: break;
} returnfalse;
}();
DestroyContext context(mPresShell);
RemoveFrame(context, nsLayoutUtils::GetChildListNameFor(childFrame),
childFrame);
// NOTE(emilio): aChild could be dead here already if it is a ::before or // ::after pseudo-element (since in that case it was owned by childFrame, // which we just destroyed).
if (haveFLS && mRootElementFrame) {
RecoverLetterFrames(containingBlock);
}
if (!canSkipWhitespaceFixup) {
MOZ_ASSERT(aChild->GetParentNode(), "How did we have a sibling without a parent?"); // Adjacent whitespace-only text nodes might have been suppressed if // this node does not have inline ends. Create frames for them now // if necessary. // Reframe any text node just before the node being removed, if there is // one, and if it's not the last child or the first child. If a whitespace // textframe was being suppressed and it's now the last child or first // child then it can stay suppressed since the parent must be a block // and hence it's adjacent to a block end. // If aOldNextSibling is null, then the text node before the node being // removed is the last node, and we don't need to worry about it.
nsIContent* prevSibling = aChild->GetPreviousSibling(); if (prevSibling && prevSibling->GetPreviousSibling()) {
LAYOUT_PHASE_TEMP_EXIT();
ReframeTextIfNeeded(prevSibling);
LAYOUT_PHASE_TEMP_REENTER();
} // Reframe any text node just after the node being removed, if there is // one, and if it's not the last child or the first child.
nsIContent* nextSibling = aChild->GetNextSibling(); if (nextSibling && prevSibling && nextSibling->GetNextSibling()) {
LAYOUT_PHASE_TEMP_EXIT();
ReframeTextIfNeeded(nextSibling);
LAYOUT_PHASE_TEMP_REENTER();
}
}
// Note that both in ContentWillBeRemoved and ContentInserted the content // node will still have the right parent pointer, so looking at that is ok.
nsIContent* parent = aNode->GetParent(); if (parent) { // Has a parent; might not be what we want
nsIContent* grandParent = parent->GetParent(); if (grandParent) { // Has a grandparent, so not what we want return;
}
// Check whether it's an HTML body if (!aNode->IsHTMLElement(nsGkAtoms::body)) { return;
}
}
// At this point the node has no parent or it's an HTML <body> child of the // root. We might not need to invalidate in this case (eg we might be in // XHTML or something), but chances are we want to. Play it safe. // Invalidate the viewport.
bool nsCSSFrameConstructor::EnsureFrameForTextNodeIsCreatedAfterFlush(
CharacterData* aContent) { if (!aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE)) { returnfalse;
}
if (mAlwaysCreateFramesForIgnorableWhitespace) { returnfalse;
}
// Text frame may have been suppressed. Disable suppression and signal that a // flush should be performed. We do this on a document-wide basis so that // pages that repeatedly query metrics for collapsed-whitespace text nodes // don't trigger pathological behavior.
mAlwaysCreateFramesForIgnorableWhitespace = true;
Element* root = mDocument->GetRootElement(); if (!root) { returnfalse;
}
if ((aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) &&
!aContent->TextIsOnlyWhitespace()) ||
(aContent->HasFlag(NS_REFRAME_IF_WHITESPACE) &&
aContent->TextIsOnlyWhitespace())) { #ifdef DEBUG
nsIFrame* frame = aContent->GetPrimaryFrame();
NS_ASSERTION(!frame || !frame->IsGeneratedContentFrame(), "Bit should never be set on generated content"); #endif
LAYOUT_PHASE_TEMP_EXIT();
RecreateFramesForContent(aContent, InsertionKind::Async);
LAYOUT_PHASE_TEMP_REENTER(); return;
}
// It's possible the frame whose content changed isn't inserted into the // frame hierarchy yet, or that there is no frame that maps the content if (nsIFrame* frame = aContent->GetPrimaryFrame()) { #if0
NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
("nsCSSFrameConstructor::CharacterDataChanged: content=%p[%s] subcontent=%p frame=%p",
aContent, ContentTag(aContent, 0),
aSubContent, frame)); #endif
if (frame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
LAYOUT_PHASE_TEMP_EXIT();
RecreateFramesForContent(aContent, InsertionKind::Async);
LAYOUT_PHASE_TEMP_REENTER(); return;
}
// Special check for text content that is a child of a letter frame. If // this happens, we should remove the letter frame, do whatever we're // planning to do with this notification, then put the letter frame back. // Note that this is basically what RecreateFramesForContent ends up doing; // the reason we dont' want to call that here is that our text content // could be native anonymous, in which case RecreateFramesForContent would // completely barf on it. And recreating the non-anonymous ancestor would // just lead us to come back into this notification (e.g. if quotes or // counters are involved), leading to a loop.
nsContainerFrame* block = GetFloatContainingBlock(frame); bool haveFirstLetterStyle = false; if (block) { // See if the block has first-letter style applied to it.
haveFirstLetterStyle = HasFirstLetterStyle(block); if (haveFirstLetterStyle) {
RemoveLetterFrames(mPresShell, block); // Reget |frame|, since we might have killed it. // Do we really need to call CharacterDataChanged in this case, though?
frame = aContent->GetPrimaryFrame();
NS_ASSERTION(frame, "Should have frame here!");
}
}
// Notify the first frame that maps the content. It will generate a reflow // command
frame->CharacterDataChanged(aInfo);
if (haveFirstLetterStyle) {
RecoverLetterFrames(block);
}
}
}
// Prevent frame tree destruction from being O(N^2)
mContainStyleScopeManager.Clear();
nsFrameManager::Destroy();
}
// STATIC
// XXXbz I'd really like this method to go away. Once we have inline-block and // I can just use that for sized broken images, that can happen, maybe. // // NOTE(emilio): This needs to match MozAltContent handling. void nsCSSFrameConstructor::GetAlternateTextFor(const Element& aElement,
nsAString& aAltText) { // The "alt" attribute specifies alternate text that is rendered // when the image can not be displayed. if (aElement.GetAttr(nsGkAtoms::alt, aAltText)) { return;
}
if (aElement.IsHTMLElement(nsGkAtoms::input)) { // If there's no "alt" attribute, and aElement is an input element, then use // the value of the "value" attribute. if (aElement.GetAttr(nsGkAtoms::value, aAltText)) { return;
}
// If there's no "value" attribute either, then use the localized string for // "Submit" as the alternate text.
nsContentUtils::GetMaybeLocalizedString(PropertiesFile::FORMS_PROPERTIES, "Submit", aElement.OwnerDoc(),
aAltText);
}
}
// Create a continuing inner table frame. Note we don't replicate the // captions: a comment used to hint at that, but the code dealing with that // never worked and was removed in bug 309322.
nsFrameList newChildFrames;
// Replicate any header/footer frames
nsFrameList childFrames; for (nsIFrame* childFrame : aFrame->PrincipalChildList()) { // See if it's a header/footer, possibly wrapped in a scroll frame.
nsTableRowGroupFrame* rowGroupFrame = static_cast<nsTableRowGroupFrame*>(childFrame); // If the row group was continued, then don't replicate it.
nsIFrame* rgNextInFlow = rowGroupFrame->GetNextInFlow(); if (rgNextInFlow) {
rowGroupFrame->SetRepeatable(false);
} elseif (rowGroupFrame->IsRepeatable()) { // Replicate the header/footer frame.
nsTableRowGroupFrame* headerFooterFrame;
nsFrameList childList;
// Use the frame type to determine what type of frame to create
LayoutFrameType frameType = aFrame->Type();
nsIContent* content = aFrame->GetContent();
} elseif (LayoutFrameType::TableCell == frameType) { // Warning: If you change this and add a wrapper frame around table cell // frames, make sure Bug 368554 doesn't regress! // See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
nsTableFrame* tableFrame = static_cast<nsTableRowFrame*>(aParentFrame)->GetTableFrame();
nsTableCellFrame* cellFrame =
NS_NewTableCellFrame(mPresShell, computedStyle, tableFrame);
cellFrame->Init(content, aParentFrame, aFrame);
// Create a continuing area frame
nsIFrame* blockFrame = aFrame->PrincipalChildList().FirstChild();
nsIFrame* continuingBlockFrame =
CreateContinuingFrame(blockFrame, cellFrame);
// Init() set newFrame to be a fluid continuation of aFrame. // If we want a non-fluid continuation, we need to call SetPrevContinuation() // to reset NS_FRAME_IS_FLUID_CONTINUATION. if (!aIsFluid) {
newFrame->SetPrevContinuation(aFrame);
}
// If a continuing frame needs to carry frame state bits from its previous // continuation or parent, set them in nsIFrame::Init(), or in any derived // frame class's Init() if the bits are belong to specific group.
void nsCSSFrameConstructor::MaybeSetNextPageContentFramePageName( const nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "Frame should not be null"); // No parent means the root frame, which isn't what this funciton is for.
MOZ_ASSERT(aFrame->GetParent(), "Frame should be the first child placed on a new page, not the " "root frame."); if (mNextPageContentFramePageName) { return;
} const nsAtom* const autoValue = aFrame->GetParent()->GetAutoPageValue();
mNextPageContentFramePageName = aFrame->ComputePageValue(autoValue);
}
nsresult nsCSSFrameConstructor::ReplicateFixedFrames(
nsPageContentFrame* aParentFrame) { // Now deal with fixed-pos things.... They should appear on all pages, // so we want to move over the placeholders when processing the child // of the pageContentFrame.
// Don't allow abs-pos descendants of the fixed content to escape the content. // This should not normally be possible (because fixed-pos elements should // be absolute containers) but fixed-pos tables currently aren't abs-pos // containers.
nsFrameConstructorState state(mPresShell, aParentFrame, nullptr,
mRootElementFrame);
state.mCreatingExtraFrames = true;
// We can't use an ancestor filter here, because we're not going to // be usefully recurring down the tree. This means that other // places in frame construction can't assume a filter is // initialized!
// Iterate across fixed frames and replicate each whose placeholder is a // descendant of aFrame. (We don't want to explicitly copy placeholders that // are within fixed frames, because that would cause duplicates on the new // page - bug 389619) for (nsIFrame* fixed = firstFixed; fixed; fixed = fixed->GetNextSibling()) {
nsIFrame* prevPlaceholder = fixed->GetPlaceholderFrame(); if (prevPlaceholder && nsLayoutUtils::IsProperAncestorFrame(
prevCanvasFrame, prevPlaceholder)) { // We want to use the same style as the primary style frame for // our content
nsIContent* content = fixed->GetContent();
ComputedStyle* computedStyle =
nsLayoutUtils::GetStyleFrame(content)->Style();
AutoFrameConstructionItemList items(this);
AddFrameConstructionItemsInternal(state, content, canvasFrame, true,
computedStyle,
{ItemFlag::AllowPageBreak}, items);
ConstructFramesFromItemList(state, items, canvasFrame, /* aParentIsWrapperAnonBox = */ false,
fixedPlaceholders);
}
}
// Add the placeholders to our primary child list. // XXXbz this is a little screwed up, since the fixed frames will have // broken auto-positioning. Oh, well.
NS_ASSERTION(!canvasFrame->PrincipalChildList().FirstChild(), "leaking frames; doc root continuation must be empty");
canvasFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(fixedPlaceholders)); return NS_OK;
}
nsCSSFrameConstructor::InsertionPoint nsCSSFrameConstructor::GetInsertionPoint(
nsIContent* aChild) {
MOZ_ASSERT(aChild);
nsIContent* insertionElement = aChild->GetFlattenedTreeParent(); if (!insertionElement) { // The element doesn't belong in the flattened tree, and thus we don't want // to render it. return {};
}
bool nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval(
nsIFrame* aFrame) { #define TRACE(reason) \
PROFILER_MARKER("MaybeRecreateContainerForFrameRemoval: " reason, LAYOUT, \
{}, Tracing, "Layout")
MOZ_ASSERT(aFrame, "Must have a frame");
MOZ_ASSERT(aFrame->GetParent(), "Frame shouldn't be root");
MOZ_ASSERT(aFrame == aFrame->FirstContinuation(), "aFrame not the result of GetPrimaryFrame()?");
nsIFrame* inFlowFrame = aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
? aFrame->GetPlaceholderFrame()
: aFrame;
MOZ_ASSERT(inFlowFrame, "How did that happen?");
MOZ_ASSERT(inFlowFrame == inFlowFrame->FirstContinuation(), "placeholder for primary frame has previous continuations?");
nsIFrame* parent = inFlowFrame->GetParent();
if (inFlowFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
nsIFrame* grandparent = parent->GetParent();
MOZ_ASSERT(grandparent);
bool needsReframe = // 1. Removing a column-span may lead to an empty // ::-moz-column-span-wrapper.
inFlowFrame->IsColumnSpan() || // 2. Removing a frame which has any column-span siblings may also // lead to an empty ::-moz-column-span-wrapper subtree. The // column-span siblings were the frame's children, but later become // the frame's siblings after CreateColumnSpanSiblings().
inFlowFrame->HasColumnSpanSiblings() || // 3. Removing the only child of a ::-moz-column-content, whose // ColumnSet grandparent has a previous column-span sibling, requires // reframing since we might connect the ColumnSet's next column-span // sibling (if there's one). Note that this isn't actually needed if // the ColumnSet is at the end of ColumnSetWrapper since we create // empty ones at the end anyway, but we're not worried about // optimizing that case.
(parent->Style()->GetPseudoType() ==
PseudoStyleType::MozColumnContent && // The only child in ::-moz-column-content (might be tall enough to // split across columns)
!inFlowFrame->GetPrevSibling() && !inFlowFrame->GetNextSibling() && // That ::-moz-column-content is the first column.
!parent->GetPrevInFlow() && // The ColumnSet grandparent has a previous sibling that is a // column-span.
grandparent->GetPrevSibling());
if (needsReframe) {
nsContainerFrame* containingBlock =
GetMultiColumnContainingBlockFor(inFlowFrame);
#ifdef DEBUG if (IsFramePartOfIBSplit(inFlowFrame)) {
nsIFrame* ibContainingBlock = GetIBContainingBlockFor(inFlowFrame);
MOZ_ASSERT(containingBlock == ibContainingBlock ||
nsLayoutUtils::IsProperAncestorFrame(containingBlock,
ibContainingBlock), "Multi-column containing block should be equal to or be the " "ancestor of the IB containing block!");
} #endif
if (IsFramePartOfIBSplit(aFrame)) { // The removal functions can't handle removal of an {ib} split directly; we // need to rebuild the containing block.
TRACE("IB split removal");
ReframeContainingBlock(aFrame); returntrue;
}
if (inFlowFrame->IsRenderedLegend()) {
TRACE("Fieldset / Legend");
RecreateFramesForContent(parent->GetContent(), InsertionKind::Async); returntrue;
}
// Might need to reconstruct things if this frame's nextSibling is a table // or ruby pseudo, since removal of this frame might mean that this pseudo // needs to get merged with the frame's prevSibling if that's also a table // or ruby pseudo.
nsIFrame* nextSibling =
FindNextNonWhitespaceSibling(inFlowFrame->LastContinuation());
NS_ASSERTION(!IsWrapperPseudo(inFlowFrame), "Shouldn't happen here (we start removals from primary frames)"); // Effectively, for the ruby pseudo sibling case, only pseudo <ruby> frame // need to be checked here, since all other types of such frames will have // a ruby container parent, and be catched by "Check ruby containers" below. if (nextSibling && IsWrapperPseudo(nextSibling)) {
nsIFrame* prevSibling = FindPreviousNonWhitespaceSibling(inFlowFrame); if (prevSibling && IsWrapperPseudo(prevSibling)) {
TRACE("Pseudo sibling"); // Good enough to recreate frames for aFrame's parent's content; even if // aFrame's parent is a pseudo, that'll be the right content node. // FIXME(emilio): Consider doing a more subtle check here like, only if // prevSibling and nextSibling share frame type. Or even consider just // moving the frames around and destroying nextSibling?
RecreateFramesForContent(parent->GetContent(), InsertionKind::Async); returntrue;
}
}
// Check ruby containers if (constauto* ancestor = CheckRubyContainers(inFlowFrame, parent)) { // In ruby containers, pseudo frames may be created from // whitespaces or even nothing. There are two cases we actually // need to handle here, but hard to check exactly: // 1. Status of spaces beside the frame may vary, and related // frames may be constructed or destroyed accordingly. // 2. The type of the first child of a ruby frame determines // whether a pseudo ruby base container should exist.
TRACE("Ruby container");
RecreateFramesForContent(ancestor->GetContent(), InsertionKind::Async); returntrue;
}
// Reconstruct if inflowFrame is parent's only child, and parent is, or has, // a non-fluid continuation, i.e. it was split by bidi resolution if (!inFlowFrame->GetPrevSibling() && !inFlowFrame->GetNextSibling() &&
((parent->GetPrevContinuation() && !parent->GetPrevInFlow()) ||
(parent->GetNextContinuation() && !parent->GetNextInFlow()))) {
TRACE("Removing last child of non-fluid split parent");
RecreateFramesForContent(parent->GetContent(), InsertionKind::Async); returntrue;
}
// We might still need to reconstruct things if the parent of inFlowFrame is // ib-split, since in that case the removal of aFrame might affect the // splitting of its parent. if (!IsFramePartOfIBSplit(parent)) { returnfalse;
}
// If inFlowFrame is not the only in-flow child of |parent|, then removing // it will change nothing about the {ib} split. if (inFlowFrame != parent->PrincipalChildList().FirstChild() ||
inFlowFrame->LastContinuation()->GetNextSibling()) { returnfalse;
}
// If the parent is the first or last part of the {ib} split, then // removing one of its kids will have no effect on the splitting. // Get the first continuation up front so we don't have to do it twice.
nsIFrame* parentFirstContinuation = parent->FirstContinuation(); if (!GetIBSplitSibling(parentFirstContinuation) ||
!GetIBSplitPrevSibling(parentFirstContinuation)) { returnfalse;
}
// It's possible that this warning could fire if some other style change // simultaneously changes the 'display' of the element and makes it no // longer be a table cell.
NS_WARNING_ASSERTION(cellFrame, "Hint should only be posted on table cells!");
if (cellFrame) {
cellFrame->GetTableFrame()->RowOrColSpanChanged(cellFrame);
}
}
// We don't know how to re-insert an anonymous subtree root, so recreate the // closest non-generated ancestor instead, except for a few special cases... staticbool ShouldRecreateContainerForNativeAnonymousContentRoot(
nsIContent* aContent) { if (!aContent->IsRootOfNativeAnonymousSubtree()) { returnfalse;
} if (ManualNACPtr::IsManualNAC(aContent)) { // Editor NAC, would enter an infinite loop, and we sorta get away with it // because it's all abspos. returnfalse;
} if (auto* el = Element::FromNode(aContent)) { if (el->GetPseudoElementType() ==
PseudoStyleType::MozSnapshotContainingBlock) { // Much like above, all abspos and on its own top layer so insertion order // wouldn't really matter anyways. returnfalse;
} if (auto* classes = el->GetClasses()) { if (classes->Contains(nsGkAtoms::mozCustomContentContainer,
eCaseMatters)) { // Canvas anonymous content (like the custom content container) is also // fine, because its only sibling is a tooltip which is also abspos, so // relative insertion order doesn't really matter. // // This is important because the inspector uses it, and we don't want // inspecting the page to change behavior heavily (and reframing // unfortunately has side-effects sometimes, even though they're bugs). returnfalse;
}
}
}
// If there is no document, we don't want to recreate frames for it. (You // shouldn't generally be giving this method content without a document // anyway). // Rebuilding the frame tree can have bad effects, especially if it's the // frame tree for chrome (see bug 157322). if (NS_WARN_IF(!aContent->GetComposedDoc())) { return;
}
// TODO(emilio): We technically can find the right insertion point nowadays // using StyleChildrenIterator rather than FlattenedChildIterator. But we'd // need to tweak the setup to insert into replaced elements to filter which // anonymous roots can be allowed, and which can't. // // TODO(emilio, 2022): Is this true? If we have a replaced element we wouldn't // have generated e.g., a ::before/::after pseudo-element to begin with (which // is what this code is about, so maybe we can just remove this piece of code // altogether). if (ShouldRecreateContainerForNativeAnonymousContentRoot(aContent)) { do {
aContent = aContent->GetParent();
} while (ShouldRecreateContainerForNativeAnonymousContentRoot(aContent)); return RecreateFramesForContent(aContent, InsertionKind::Async);
}
nsIFrame* frame = aContent->GetPrimaryFrame(); if (frame && frame->IsMathMLFrame()) { // Reframe the topmost MathML element to prevent exponential blowup // (see bug 397518).
aContent = GetTopmostMathMLElement(aContent);
frame = aContent->GetPrimaryFrame();
}
if (frame) {
nsIFrame* parent = frame->GetParent();
nsIContent* parentContent = parent ? parent->GetContent() : nullptr; // If the parent frame is a leaf then the subsequent insert will fail to // create a frame, so we need to recreate the parent content. This happens // with native anonymous content from the editor. if (parent && parent->IsLeaf() && parentContent &&
parentContent != aContent) { return RecreateFramesForContent(parentContent, InsertionKind::Async);
}
}
if (frame && MaybeRecreateContainerForFrameRemoval(frame)) { return;
}
MOZ_ASSERT(aContent->GetParentNode()); constauto removalKind = [&] { if (aInsertionKind == InsertionKind::Sync && aContent->IsElement() &&
Servo_Element_IsDisplayNone(aContent->AsElement())) { // If we know we're not going to have frames after reconstructing, it's // more efficient to do some of that work (a11y notifications, fixing-up // text nodes) earlier. return RemovalKind::ForDisplayNoneChange;
} return RemovalKind::ForReconstruction;
}(); constbool didReconstruct = ContentWillBeRemoved(aContent, removalKind); if (didReconstruct || removalKind == RemovalKind::ForDisplayNoneChange) { // If ContentWillBeRemoved triggered reconstruction, then we don't need to // do anything else because the frames will already have been built. return;
} if (aInsertionKind == InsertionKind::Async && aContent->IsElement()) { // FIXME(emilio, bug 1397239): There's nothing removing the frame state // for elements that go away before we come back to the frame constructor. // // Also, it'd be nice to just use the `ContentRangeInserted` path for // both elements and non-elements, but we need to make lazy frame // construction to apply to all elements first. // // TODO(emilio): I think lazy frame construction works everywhere now, so // maybe we can remove this altogether?
RestyleManager()->PostRestyleEvent(aContent->AsElement(), RestyleHint{0},
nsChangeHint_ReconstructFrame); return;
} // Now, recreate the frames associated with this content object.
ContentRangeInserted(aContent, aContent->GetNextSibling(), aInsertionKind);
}
// Predicate to see if a given content (block element) has // first-letter style applied to it. bool nsCSSFrameConstructor::ShouldHaveFirstLetterStyle(
nsIContent* aContent, ComputedStyle* aComputedStyle) { return nsLayoutUtils::HasPseudoStyle(aContent, aComputedStyle,
PseudoStyleType::FirstLetter,
mPresShell->GetPresContext());
}
bool nsCSSFrameConstructor::HasFirstLetterStyle(nsIFrame* aBlockFrame) {
MOZ_ASSERT(aBlockFrame, "Need a frame");
NS_ASSERTION(aBlockFrame->IsBlockFrameOrSubclass(), "Not a block frame?"); return aBlockFrame->HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE);
}
if (!aParentFrame->IsFlexOrGridContainer()) { return;
}
constbool isLegacyWebKitBox =
IsFlexContainerForLegacyWebKitBox(aParentFrame);
FCItemIterator iter(aItems); do { // Advance iter past children that don't want to be wrapped if (iter.SkipItemsThatDontNeedAnonFlexOrGridItem(aState,
isLegacyWebKitBox)) { // Hit the end of the items without finding any remaining children that // need to be wrapped. We're finished! return;
}
// If our next potentially-wrappable child is whitespace, then see if // there's anything wrappable immediately after it. If not, we just drop // the whitespace and move on. (We're not supposed to create any anonymous // flex/grid items that _only_ contain whitespace). // (BUT if this is generated content, then we don't give whitespace nodes // any special treatment, because they're probably not really whitespace -- // they're just temporarily empty, waiting for their generated text.) // XXXdholbert If this node's generated text will *actually end up being // entirely whitespace*, then we technically should still skip over it, per // the CSS grid & flexbox specs. I'm not bothering with that at this point, // since it's a pretty extreme edge case. if (!aParentFrame->IsGeneratedContentFrame() &&
iter.item().IsWhitespace(aState)) {
FCItemIterator afterWhitespaceIter(iter); bool hitEnd = afterWhitespaceIter.SkipWhitespace(aState); bool nextChildNeedsAnonItem =
!hitEnd && afterWhitespaceIter.item().NeedsAnonFlexOrGridItem(
aState, isLegacyWebKitBox);
if (!nextChildNeedsAnonItem) { // There's nothing after the whitespace that we need to wrap, so we // just drop this run of whitespace.
iter.DeleteItemsTo(this, afterWhitespaceIter); if (hitEnd) { // Nothing left to do -- we're finished! return;
} // else, we have a next child and it does not want to be wrapped. So, // we jump back to the beginning of the loop to skip over that child // (and anything else non-wrappable after it)
MOZ_ASSERT(!iter.IsDone() && !iter.item().NeedsAnonFlexOrGridItem(
aState, isLegacyWebKitBox), "hitEnd and/or nextChildNeedsAnonItem lied"); continue;
}
}
// Now |iter| points to the first child that needs to be wrapped in an // anonymous flex/grid item. Now we see how many children after it also want // to be wrapped in an anonymous flex/grid item.
FCItemIterator endIter(iter); // iterator to find the end of the group
endIter.SkipItemsThatNeedAnonFlexOrGridItem(aState, isLegacyWebKitBox);
NS_ASSERTION(iter != endIter, "Should've had at least one wrappable child to seek past");
// Now, we create the anonymous flex or grid item to contain the children // between |iter| and |endIter|.
nsIContent* parentContent = aParentFrame->GetContent();
RefPtr<ComputedStyle> wrapperStyle =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::MozAnonymousItem, aParentFrame->Style());
MOZ_ASSERT(!newItem->mIsAllInline && newItem->mIsBlock, "expecting anonymous flex/grid items to be block-level " "(this will make a difference when we encounter " "'align-items: baseline')");
// Anonymous flex and grid items induce line boundaries around their // contents.
newItem->mChildItems.SetLineBoundaryAtStart(true);
newItem->mChildItems.SetLineBoundaryAtEnd(true); // The parent of the items in aItems is also the parent of the items // in mChildItems
newItem->mChildItems.SetParentHasNoShadowDOM(aItems.ParentHasNoShadowDOM());
// Eat up all items between |iter| and |endIter| and put them in our // wrapper. This advances |iter| to point to |endIter|.
iter.AppendItemsToList(this, endIter, newItem->mChildItems);
iter.InsertItem(newItem);
} while (!iter.IsDone());
}
// Any leading or trailing whitespace in non-pseudo ruby box // should have been trimmed, hence there should not be any // whitespace at the start or the end.
MOZ_ASSERT(!aStartIter.AtStart() && !aEndIter.IsDone());
FCItemIterator prevIter(aStartIter);
prevIter.Prev(); return ComputeRubyWhitespaceType(
prevIter.item().mComputedStyle->StyleDisplay()->mDisplay,
aEndIter.item().mComputedStyle->StyleDisplay()->mDisplay);
}
FrameConstructionItem& firstItem = aIter.item();
ParentType wrapperType = firstItem.DesiredParentType(); if (wrapperType != eTypeRubyTextContainer) { // If the first item is not ruby text, // it should be in a base container.
wrapperType = eTypeRubyBaseContainer;
}
FCItemIterator endIter(aIter); do { if (endIter.SkipItemsWantingParentType(wrapperType) || // If the skipping above stops at some item which wants a // different ruby parent, then we have finished.
IsRubyParentType(endIter.item().DesiredParentType())) { // No more items need to be wrapped in this level container. break;
}
FCItemIterator contentEndIter(endIter);
contentEndIter.SkipItemsNotWantingRubyParent(); // endIter must be on something doesn't want a ruby parent.
MOZ_ASSERT(contentEndIter != endIter);
// InterpretRubyWhitespace depends on the fact that any leading or // trailing whitespace described in the spec have been trimmed at // this point. With this precondition, it is safe not to check // whether contentEndIter has been done.
RubyWhitespaceType whitespaceType =
InterpretRubyWhitespace(aState, endIter, contentEndIter); if (whitespaceType == eRubyInterLevelWhitespace) { // Remove inter-level whitespace. bool atStart = (aIter == endIter);
endIter.DeleteItemsTo(this, contentEndIter); if (atStart) {
aIter = endIter;
}
} elseif (whitespaceType == eRubyInterSegmentWhitespace) { // If this level container starts with inter-segment whitespaces, // wrap them. Break at contentEndIter. Otherwise, leave it here. // Break at endIter. They will be wrapped when we are here again. if (aIter == endIter) {
MOZ_ASSERT(wrapperType == eTypeRubyBaseContainer, "Inter-segment whitespace should be wrapped in rbc");
endIter = contentEndIter;
} break;
} elseif (wrapperType == eTypeRubyTextContainer &&
whitespaceType != eRubyInterLeafWhitespace) { // Misparented inline content that's not inter-annotation // whitespace doesn't belong in a pseudo ruby text container. // Break at endIter. break;
} else {
endIter = contentEndIter;
}
} while (!endIter.IsDone());
// It is possible that everything our parent wants us to wrap is // simply an inter-level whitespace, which has been trimmed, or // an inter-segment whitespace, which will be wrapped later. // In those cases, don't create anything. if (aIter != endIter) {
WrapItemsInPseudoParent(aParentContent, aParentStyle, wrapperType, aIter,
endIter);
}
}
iter.SetToEnd(); if (!iter.AtStart()) {
FCItemIterator spaceEndIter(iter); do {
iter.Prev(); if (iter.AtStart()) { // It's fine to not check the first item, because we // should have trimmed leading whitespaces above. break;
}
} while (iter.item().IsWhitespace(aState));
iter.Next(); if (iter != spaceEndIter) {
iter.DeleteItemsTo(this, spaceEndIter);
}
}
}
if (!IsRubyPseudo(aParentFrame) ||
ourParentType == eTypeRuby /* for 'display:block ruby' */) { // Normally, ruby pseudo frames start from and end at some elements, // which means they don't have leading and trailing whitespaces at // all. But there are two cases where they do actually have leading // or trailing whitespaces: // 1. It is an inter-segment whitespace which in an individual ruby // base container. // 2. The pseudo frame starts from or ends at consecutive inline // content, which is not pure whitespace, but includes some. // In either case, the whitespaces are not the leading or trailing // whitespaces defined in the spec, and thus should not be trimmed.
TrimLeadingAndTrailingWhitespaces(aState, aItems);
}
/* *Thisfunctionworksasfollows:wewalkthroughthechildlist(aItems)and *finditemsthatcannothaveaParentFrameastheirparent.Wewrap *continuousrunsofsuchitemsintoaFrameConstructionItemforaframethat *getsthemclosertotheirdesiredparents.Forexample,arunofnon-row *childrenofarow-groupwillgetwrappedinarow.Whenwelaterconstruct *theframeforthiswrapper(inthiscasefortherow),it'llbethecorrect *parentforthecellsinthesetofitemswewrappedorwe'llwrapcells *aroundeverythingelse.Attheendofthismethod,aItemsisguaranteedto *containonlyitemsforframesthatcanbedirectkidsofaParentFrame.
*/ void nsCSSFrameConstructor::CreateNeededPseudoContainers(
nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
nsIFrame* aParentFrame) {
ParentType ourParentType = GetParentType(aParentFrame); if (IsRubyParentType(ourParentType) ||
aItems.AllWantParentType(ourParentType)) { // Nothing to do here return;
}
FCItemIterator iter(aItems); do { if (iter.SkipItemsWantingParentType(ourParentType)) { // Nothing else to do here; we're finished return;
}
// Now we're pointing to the first child that wants a different parent // type.
// Now try to figure out what kids we can group together. We can generally // group everything that has a different desired parent type from us. Two // exceptions to this: // 1) If our parent type is table, we can't group columns with anything // else other than whitespace. // 2) Whitespace that lies between two things we can group which both want // a non-block parent should be dropped, even if we can't group them // with each other and even if the whitespace wants a parent of // ourParentType. Ends of the list count as things that don't want a // block parent (so that for example we'll drop a whitespace-only list).
FCItemIterator endIter(iter); /* iterator to find the end of the group */
ParentType groupingParentType = endIter.item().DesiredParentType(); if (aItems.AllWantParentType(groupingParentType) &&
groupingParentType != eTypeBlock) { // Just group them all and be done with it. We need the check for // eTypeBlock here to catch the "all the items are whitespace" case // described above.
endIter.SetToEnd();
} else { // Locate the end of the group.
// Keep track of the type the previous item wanted, in case we have to // deal with whitespace. Start it off with ourParentType, since that's // the last thing |iter| would have skipped over.
ParentType prevParentType = ourParentType; do { // Walk an iterator past any whitespace that we might be able to drop // from the list
FCItemIterator spaceEndIter(endIter); if (prevParentType != eTypeBlock &&
!aParentFrame->IsGeneratedContentFrame() &&
spaceEndIter.item().IsWhitespace(aState)) { bool trailingSpaces = spaceEndIter.SkipWhitespace(aState);
// We drop the whitespace in the following cases: // 1) If these are not trailing spaces and the next item wants a table // or table-part parent // 2) If these are trailing spaces and aParentFrame is a // tabular container according to rule 1.3 of CSS 2.1 Sec 17.2.1. // (Being a tabular container pretty much means ourParentType is // not eTypeBlock besides the eTypeColGroup case, which won't // reach here.) if ((!trailingSpaces &&
IsTableParentType(spaceEndIter.item().DesiredParentType())) ||
(trailingSpaces && ourParentType != eTypeBlock)) { bool updateStart = (iter == endIter);
endIter.DeleteItemsTo(this, spaceEndIter);
NS_ASSERTION(trailingSpaces == endIter.IsDone(), "These should match");
if (updateStart) {
iter = endIter;
}
if (trailingSpaces) { break; /* Found group end */
}
if (updateStart) { // Update groupingParentType, since it might have been eTypeBlock // just because of the whitespace.
groupingParentType = iter.item().DesiredParentType();
}
}
}
// Now endIter points to a non-whitespace item or a non-droppable // whitespace item. In the latter case, if this is the end of the group // we'll traverse this whitespace again. But it'll all just be quick // DesiredParentType() checks which will match ourParentType (that's // what it means that this is the group end), so it's OK. // However, when we are grouping a ruby parent, and endIter points to // a non-droppable whitespace, if the next non-whitespace item also // wants a ruby parent, the whitespace should also be included into // the current ruby container.
prevParentType = endIter.item().DesiredParentType(); if (prevParentType == ourParentType &&
(endIter == spaceEndIter || spaceEndIter.IsDone() ||
!IsRubyParentType(groupingParentType) ||
!IsRubyParentType(spaceEndIter.item().DesiredParentType()))) { // End the group at endIter. break;
}
if (ourParentType == eTypeTable &&
(prevParentType == eTypeColGroup) !=
(groupingParentType == eTypeColGroup)) { // Either we started with columns and now found something else, or // vice versa. In any case, end the grouping. break;
}
// If we have some whitespace that we were not able to drop and there is // an item after the whitespace that is already properly parented, then // make sure to include the spaces in our group but stop the group after // that. if (spaceEndIter != endIter && !spaceEndIter.IsDone() &&
ourParentType == spaceEndIter.item().DesiredParentType()) {
endIter = spaceEndIter; break;
}
// Include the whitespace we didn't drop (if any) in the group.
endIter = spaceEndIter;
prevParentType = endIter.item().DesiredParentType();
endIter.Next();
} while (!endIter.IsDone());
}
if (iter == endIter) { // Nothing to wrap here; just skipped some whitespace continue;
}
if (ourParentType == eTypeColGroup) { // Suppress non-col items of colgroups.
iter.DeleteItemsTo(this, endIter); continue;
}
// Now group together all the items between iter and endIter. The right // parent type to use depends on ourParentType.
ParentType wrapperType; switch (ourParentType) { case eTypeRow: // The parent type for a cell is eTypeBlock, since that's what a cell // looks like to its kids.
wrapperType = eTypeBlock; break; case eTypeRowGroup:
wrapperType = eTypeRow; break; case eTypeTable: // Either colgroup or rowgroup, depending on what we're grouping.
wrapperType =
groupingParentType == eTypeColGroup ? eTypeColGroup : eTypeRowGroup; break; case eTypeColGroup:
MOZ_FALLTHROUGH_ASSERT("handled above"); default:
NS_ASSERTION(ourParentType == eTypeBlock, "Unrecognized parent type"); if (IsRubyParentType(groupingParentType)) {
wrapperType = eTypeRuby;
} else {
NS_ASSERTION(IsTableParentType(groupingParentType), "groupingParentType should be either Ruby or table");
wrapperType = eTypeTable;
}
}
// Now |iter| points to the item that was the first one we didn't wrap; // loop and see whether we need to skip it or wrap it in something // different.
} while (!iter.IsDone());
}
// Use the content of our parent frame auto* newItem = new (this) FrameConstructionItem(
&pseudoData.mFCData, aParentContent, wrapperStyle.forget(), true);
const nsStyleDisplay* disp = newItem->mComputedStyle->StyleDisplay(); // Here we're cheating a tad... technically, table-internal items should be // inline if aParentFrame is inline, but they'll get wrapped in an // inline-table in the end, so it'll all work out. In any case, arguably // we don't need to maintain this state at this point... but it's better // to, I guess.
newItem->mIsAllInline = disp->IsInlineOutsideStyle();
bool isRuby = disp->IsRubyDisplayType(); if (!isRuby) { // Table pseudo frames always induce line boundaries around their // contents.
newItem->mChildItems.SetLineBoundaryAtStart(true);
newItem->mChildItems.SetLineBoundaryAtEnd(true);
} // The parent of the items in aItems is also the parent of the items // in mChildItems
newItem->mChildItems.SetParentHasNoShadowDOM(
aIter.List()->ParentHasNoShadowDOM());
// Eat up all items between |aIter| and |aEndIter| and put them in our // wrapper Advances |aIter| to point to |aEndIter|.
aIter.AppendItemsToList(this, aEndIter, newItem->mChildItems);
FCItemIterator iter(aItems);
StyleDisplay firstDisplay =
iter.item().mComputedStyle->StyleDisplay()->mDisplay; if (firstDisplay == StyleDisplay::RubyBaseContainer) { return;
}
NS_ASSERTION(firstDisplay == StyleDisplay::RubyTextContainer, "Child of ruby frame should either a rbc or a rtc");
const PseudoParentData& pseudoData =
sPseudoParentData[eTypeRubyBaseContainer];
RefPtr<ComputedStyle> pseudoStyle =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
pseudoData.mPseudoType, aParentFrame->Style());
FrameConstructionItem* newItem = new (this) FrameConstructionItem(
&pseudoData.mFCData, // Use the content of the parent frame
aParentFrame->GetContent(), pseudoStyle.forget(), true);
newItem->mIsAllInline = true;
newItem->mChildItems.SetParentHasNoShadowDOM(true);
iter.InsertItem(newItem);
}
// Any line-participant frames (e.g. text) definitely want to be wrapped in // an anonymous flex/grid item. if (aFrame->IsLineParticipant()) { returntrue;
}
// If the container is a -webkit-{inline-}box container, then placeholders // also need to be wrapped, for compatibility. if (IsFlexContainerForLegacyWebKitBox(aContainerFrame) &&
aFrame->IsPlaceholderFrame()) { returntrue;
}
bool prevChildWasAnonItem = false; for (const nsIFrame* child : aChildren) {
MOZ_ASSERT(!FrameWantsToBeInAnonymousItem(aParentFrame, child), "frame wants to be inside an anonymous item, but it isn't"); if (IsAnonymousItem(child)) {
AssertAnonymousFlexOrGridItemParent(child, aParentFrame);
MOZ_ASSERT(!prevChildWasAnonItem, "two anon items in a row");
nsIFrame* firstWrappedChild = child->PrincipalChildList().FirstChild();
MOZ_ASSERT(firstWrappedChild, "anonymous item shouldn't be empty");
prevChildWasAnonItem = true;
} else {
prevChildWasAnonItem = false;
}
} #endif
}
staticbool FrameHasOnlyPlaceholderPrevSiblings(const nsIFrame* aFrame) { // Check for prev siblings, ignoring placeholder frames.
MOZ_ASSERT(aFrame, "frame must not be null"); const nsIFrame* prevSibling = aFrame; do {
prevSibling = prevSibling->GetPrevSibling();
} while (prevSibling && prevSibling->IsPlaceholderFrame()); return !prevSibling;
}
staticbool FrameHasOnlyPlaceholderNextSiblings(const nsIFrame* aFrame) { // Check for next siblings, ignoring placeholder frames.
MOZ_ASSERT(aFrame, "frame must not be null"); const nsIFrame* nextSibling = aFrame; do {
nextSibling = nextSibling->GetNextSibling();
} while (nextSibling && nextSibling->IsPlaceholderFrame()); return !nextSibling;
}
staticvoid SetPageValues(nsIFrame* const aFrame, const nsAtom* const aAutoValue, const nsAtom* const aStartValue, const nsAtom* const aEndValue) {
MOZ_ASSERT(aAutoValue, "Auto page value should never be null");
MOZ_ASSERT(aStartValue || aEndValue, "Should not have called with no values");
nsIFrame::PageValues* pageValues =
aFrame->GetProperty(nsIFrame::PageValuesProperty());
if (aStartValue) { if (aStartValue == aAutoValue) { // If the page value struct already exists, set the start value to null // to indicate the auto value. if (pageValues) {
pageValues->mStartPageValue = nullptr;
}
} else { // The start value is not auto, so we need to store it, creating the // page values struct if it does not already exist. if (!pageValues) {
pageValues = new nsIFrame::PageValues();
aFrame->SetProperty(nsIFrame::PageValuesProperty(), pageValues);
}
pageValues->mStartPageValue = aStartValue;
}
} if (aEndValue) { if (aEndValue == aAutoValue) { // If the page value struct already exists, set the end value to null // to indicate the auto value. if (pageValues) {
pageValues->mEndPageValue = nullptr;
}
} else { // The end value is not auto, so we need to store it, creating the // page values struct if it does not already exist. if (!pageValues) {
pageValues = new nsIFrame::PageValues();
aFrame->SetProperty(nsIFrame::PageValuesProperty(), pageValues);
}
pageValues->mEndPageValue = aEndValue;
}
}
}
inlinevoid nsCSSFrameConstructor::ConstructFramesFromItemList(
nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
nsContainerFrame* aParentFrame, bool aParentIsWrapperAnonBox,
nsFrameList& aFrameList) { #ifdef DEBUG // The assertion condition should match the logic in // MaybePushFloatContainingBlock().
MOZ_ASSERT(!(ShouldSuppressFloatingOfDescendants(aParentFrame) ||
aParentFrame->IsFloatContainingBlock()) ||
aState.mFloatCBCandidate == aParentFrame, "Our caller or ProcessChildren()'s caller should call " "MaybePushFloatContainingBlock() to handle the float containing " "block candidate!");
aState.mFloatCBCandidate = nullptr; #endif
// Ensure aParentIsWrapperAnonBox is correct. We _could_ compute it directly, // but it would be a bit slow, which is why we pass it from callers, who have // that information offhand in many cases.
MOZ_ASSERT(ParentIsWrapperAnonBox(aParentFrame) == aParentIsWrapperAnonBox);
// Note: we explicitly exclude TableColGroupFrame because it doesn't // have the FCDATA_IS_WRAPPER_ANON_BOX on pseudos so aParentIsWrapperAnonBox // is false for such pseudos (see sPseudoParentData below). if (!aParentIsWrapperAnonBox && aState.mHasRenderedLegend &&
aParentFrame->GetContent()->IsHTMLElement(nsGkAtoms::fieldset) &&
!aParentFrame->IsTableColGroupFrame()) {
DebugOnly<bool> found = false; for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) { if (iter.item().mIsRenderedLegend) { // This makes the rendered legend the first frame in the fieldset child // list which makes keyboard traversal follow the visual order.
nsFieldSetFrame* fieldSetFrame = GetFieldSetFrameFor(aParentFrame);
nsFrameList renderedLegend;
ConstructFramesFromItem(aState, iter, fieldSetFrame, renderedLegend);
MOZ_ASSERT(renderedLegend.OnlyChild(), "a rendered legend should have exactly one frame");
fieldSetFrame->InsertFrames(FrameChildListID::Principal, nullptr,
nullptr, std::move(renderedLegend));
FCItemIterator next = iter;
next.Next();
iter.DeleteItemsTo(this, next);
found = true; break;
}
}
MOZ_ASSERT(found, "should have found our rendered legend");
}
for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
MOZ_ASSERT(!iter.item().mIsRenderedLegend, "Only one item can be the rendered legend, " "and it should've been handled above");
NS_ASSERTION(iter.item().DesiredParentType() == GetParentType(aParentFrame), "Needed pseudos didn't get created; expect bad things");
ConstructFramesFromItem(aState, iter, aParentFrame, aFrameList);
}
// Calculate and propagate page-name values for each frame in the frame list. // We do not want to compute and propagate page-name values from frames that // are children of any subclasses of block frames, but not actually a block // frame. The page-name property does not apply to frames which cannot create // class A breakpoints (currently no subclass of BlockFrame can). Because the // property does not apply, those children also cannot propagate page-name // values. // This assumption helps avoid unnecessarily handling page-names for frames // such as form controls, which also avoids bug 1819468. if (aState.mPresContext->IsPaginated() && aParentFrame->IsBlockFrame()) { // Set the start/end page values while iterating the frame list, to walk // up the frame tree only once after iterating the frame list. // This also avoids extra property lookups on these frames.
MOZ_ASSERT(aState.mAutoPageNameValue == aParentFrame->GetAutoPageValue(), "aState.mAutoPageNameValue should have been equivalent to " "the auto value stored on our parent frame."); // Even though we store null for page values that equal the "auto" resolved // value on frames, we always want startPageValue/endPageValue to be the // actual atoms reflecting the start/end values. This is because when we // propagate the values up the frame tree, we will need to compare them to // the auto value for each ancestor. This value might be different than the // auto value for this frame. const nsAtom* startPageValue = nullptr; const nsAtom* endPageValue = nullptr; for (nsIFrame* f : aFrameList) { if (f->IsPlaceholderFrame()) { continue;
} // Resolve auto against the parent frame's used page name, which has been // determined and set on aState.mAutoPageNameValue. If this item is not // block-level then we use the value that auto resolves to. // // This is to achieve the propagation behavior described in the spec: // // "A start page value and end page value is determined for each box as // the value (if any) propagated from its first or last child box // (respectively), else the used value on the box itself." // // "A child propagates its own start or end page value if and only if the // page property applies to it." // // The page property only applies to "boxes that create class A break // points". When taken together, this means that non block-level children // do not propagate start/end page values, and instead we use "the used // value on the box itself", the "box itself" being aParentFrame. This // value has been determined and saved as aState.mAutoPageNameValue // // https://www.w3.org/TR/css-page-3/#using-named-pages // https://www.w3.org/TR/css-break-3/#btw-blocks const StylePageName& pageName = f->StylePage()->mPage; const nsAtom* const pageNameAtom =
(pageName.IsPageName() && f->IsBlockOutside())
? pageName.AsPageName().AsAtom()
: aState.mAutoPageNameValue;
nsIFrame::PageValues* pageValues =
f->GetProperty(nsIFrame::PageValuesProperty()); // If this frame has any children, it will already have had its page // values set at this point. However, if no page values have been set, // we must ensure that the appropriate PageValuesProperty value has been // set. // If the page name is equal to the auto value, then PageValuesProperty // should remain null to indicate that the start/end values are both // equal to the auto value. if (pageNameAtom != aState.mAutoPageNameValue && !pageValues) {
pageValues = new nsIFrame::PageValues{pageNameAtom, pageNameAtom};
f->SetProperty(nsIFrame::PageValuesProperty(), pageValues);
} // We don't want to use GetStartPageValue() or GetEndPageValue(), as each // requires a property lookup which we can avoid here. if (!startPageValue) {
startPageValue = (pageValues && pageValues->mStartPageValue)
? pageValues->mStartPageValue.get()
: aState.mAutoPageNameValue;
}
endPageValue = (pageValues && pageValues->mEndPageValue)
? pageValues->mEndPageValue.get()
: aState.mAutoPageNameValue;
MOZ_ASSERT(startPageValue && endPageValue, "Should have found start/end page value");
}
MOZ_ASSERT(!startPageValue == !endPageValue, "Should have set both or neither page values"); if (startPageValue) { // Walk up the frame tree from our parent frame, propagating start and // end page values. // As we go, if we find that, for a frame, we are not contributing one of // the start/end page values, then our subtree will not contribute this // value from that frame onward. startPageValue/endPageValue are set to // null to indicate this. // Stop iterating when we are not contributing either start or end // values, when we hit the root frame (no parent), or when we find a // frame that is not a block frame. for (nsContainerFrame* ancestorFrame = aParentFrame;
(startPageValue || endPageValue) && ancestorFrame &&
ancestorFrame->IsBlockFrame();
ancestorFrame = ancestorFrame->GetParent()) {
MOZ_ASSERT(!ancestorFrame->GetPrevInFlow(), "Should not have fragmentation yet");
MOZ_ASSERT(ancestorFrame->mWasVisitedByAutoFrameConstructionPageName, "Frame should have been visited by " "AutoFrameConstructionPageName");
{ // Get what the auto value is, based on this frame's parent. // For the root frame, `auto` resolves to the empty atom. const nsContainerFrame* const parent = ancestorFrame->GetParent(); const nsAtom* const parentAuto = MOZ_LIKELY(parent)
? parent->GetAutoPageValue()
: nsGkAtoms::_empty;
SetPageValues(ancestorFrame, parentAuto, startPageValue,
endPageValue);
} // Once we stop contributing start/end values, we know there is a // sibling subtree that contributed that value to our shared parent // instead of our starting frame's subtree. This means once // startPageValue/endPageValue becomes null, indicating that we are no // longer contributing that page value, it should stay null and we no // longer need to check for siblings in that direction. if (startPageValue &&
!FrameHasOnlyPlaceholderPrevSiblings(ancestorFrame)) {
startPageValue = nullptr;
} if (endPageValue &&
!FrameHasOnlyPlaceholderNextSiblings(ancestorFrame)) {
endPageValue = nullptr;
}
}
}
}
if (aParentIsWrapperAnonBox) { for (nsIFrame* f : aFrameList) {
f->SetParentIsWrapperAnonBox();
}
}
}
void nsCSSFrameConstructor::AddFCItemsForAnonymousContent(
nsFrameConstructorState& aState, nsContainerFrame* aFrame, const nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonymousItems,
FrameConstructionItemList& aItemsToConstruct, const AutoFrameConstructionPageName&) { for (constauto& info : aAnonymousItems) {
nsIContent* content = info.mContent; // Gecko-styled nodes should have no pending restyle flags. // Assert some things about this content
MOZ_ASSERT(!(content->GetFlags() &
(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME)), "Should not be marked as needing frames");
MOZ_ASSERT(!content->GetPrimaryFrame(), "Should have no existing frame");
MOZ_ASSERT(!content->IsComment() && !content->IsProcessingInstruction(), "Why is someone creating garbage anonymous content");
// Make sure we eagerly performed the servo cascade when the anonymous // nodes were created.
MOZ_ASSERT(!content->IsElement() || content->AsElement()->HasServoData());
void nsCSSFrameConstructor::ProcessChildren(
nsFrameConstructorState& aState, nsIContent* aContent,
ComputedStyle* aComputedStyle, nsContainerFrame* aFrame, constbool aCanHaveGeneratedContent, nsFrameList& aFrameList, constbool aAllowBlockStyles, nsIFrame* aPossiblyLeafFrame) {
MOZ_ASSERT(aFrame, "Must have parent frame here");
MOZ_ASSERT(aFrame->GetContentInsertionFrame() == aFrame, "Parent frame in ProcessChildren should be its own " "content insertion frame");
const uint32_t kMaxDepth = 2 * MAX_REFLOW_DEPTH;
static_assert(kMaxDepth <= UINT16_MAX, "mCurrentDepth type is too narrow");
AutoRestore<uint16_t> savedDepth(mCurrentDepth); if (mCurrentDepth != UINT16_MAX) {
++mCurrentDepth;
}
if (!aPossiblyLeafFrame) {
aPossiblyLeafFrame = aFrame;
}
// XXXbz ideally, this would do all the pushing of various // containing blocks as needed, so callers don't have to do it...
// Check that our parent frame is a block before allowing ::first-letter/line. // E.g. <button style="display:grid"> should not allow it. constbool allowFirstPseudos =
aAllowBlockStyles && aFrame->IsBlockFrameOrSubclass(); bool haveFirstLetterStyle = false, haveFirstLineStyle = false; if (allowFirstPseudos) {
ShouldHaveSpecialBlockStyle(aContent, aComputedStyle, &haveFirstLetterStyle,
&haveFirstLineStyle);
}
// If we have first-letter or first-line style then frames can get // moved around so don't set these flags. if (allowFirstPseudos && !haveFirstLetterStyle && !haveFirstLineStyle) {
itemsToConstruct.SetLineBoundaryAtStart(true);
itemsToConstruct.SetLineBoundaryAtEnd(true);
}
// Create any anonymous frames we need here.
AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> anonymousItems;
GetAnonymousContent(aContent, aPossiblyLeafFrame, anonymousItems); #ifdef DEBUG for (auto& item : anonymousItems) {
MOZ_ASSERT(item.mContent->IsRootOfNativeAnonymousSubtree(), "Content should know it's an anonymous subtree");
} #endif
AddFCItemsForAnonymousContent(aState, aFrame, anonymousItems,
itemsToConstruct, pageNameTracker);
// Generated content should have the same style parent as normal kids. // // Note that we don't use this style for looking up things like special // block styles because in some cases involving table pseudo-frames it has // nothing to do with the parent frame's desired behavior. auto* styleParentFrame =
nsIFrame::CorrectStyleParentFrame(aFrame, PseudoStyleType::NotPseudo);
ComputedStyle* parentStyle = styleParentFrame->Style(); if (parentStyle->StyleDisplay()->mTopLayer == StyleTopLayer::Auto &&
!aContent->IsInNativeAnonymousSubtree()) {
CreateGeneratedContentItem(aState, aFrame, *aContent->AsElement(),
*parentStyle, PseudoStyleType::Backdrop,
itemsToConstruct);
}
nsBlockFrame* listItem = nullptr; bool isOutsideMarker = false; if (!aPossiblyLeafFrame->IsLeaf()) { if (aCanHaveGeneratedContent) { if (parentStyle->StyleDisplay()->IsListItem() &&
(listItem = do_QueryFrame(aFrame)) &&
!styleParentFrame->IsFieldSetFrame()) {
isOutsideMarker = parentStyle->StyleList()->mListStylePosition ==
StyleListStylePosition::Outside;
ItemFlags extraFlags; if (isOutsideMarker) {
extraFlags += ItemFlag::IsForOutsideMarker;
}
CreateGeneratedContentItem(aState, aFrame, *aContent->AsElement(),
*parentStyle, PseudoStyleType::Marker,
itemsToConstruct, extraFlags);
} if (aContent->IsHTMLElement(nsGkAtoms::option)) {
CreateGeneratedContentItem(aState, aFrame, *aContent->AsElement(),
*parentStyle, PseudoStyleType::Checkmark,
itemsToConstruct);
} // Probe for generated content before
CreateGeneratedContentItem(aState, aFrame, *aContent->AsElement(),
*parentStyle, PseudoStyleType::Before,
itemsToConstruct);
}
constbool addChildItems = MOZ_LIKELY(mCurrentDepth < kMaxDepth); if (!addChildItems) {
NS_WARNING("ProcessChildren max depth exceeded");
}
// Special routine to handle placing a list of frames into a block // frame that has first-line style. The routine ensures that the first // collection of inline frames end up in a first-line frame. // NOTE: aState may have containing block information related to a // different part of the frame tree than where the first line occurs. // In particular aState may be set up for where ContentInserted or // ContentAppended is inserting content, which may be some // non-first-in-flow continuation of the block to which the first-line // belongs. So this function needs to be careful about how it uses // aState. void nsCSSFrameConstructor::WrapFramesInFirstLineFrame(
nsFrameConstructorState& aState, nsIContent* aBlockContent,
nsContainerFrame* aBlockFrame, nsFirstLineFrame* aLineFrame,
nsFrameList& aFrameList) { // Extract any initial inline frames from aFrameList so we can put them // in the first-line.
nsFrameList firstLineChildren =
aFrameList.Split([](nsIFrame* f) { return !f->IsInlineOutside(); });
if (firstLineChildren.IsEmpty()) { // Nothing is supposed to go into the first-line; nothing to do return;
}
if (!aLineFrame) { // Create line frame
ComputedStyle* parentStyle = nsIFrame::CorrectStyleParentFrame(
aBlockFrame, PseudoStyleType::FirstLine)
->Style();
RefPtr<ComputedStyle> firstLineStyle =
GetFirstLineStyle(aBlockContent, parentStyle);
// Initialize the line frame
InitAndRestoreFrame(aState, aBlockContent, aBlockFrame, aLineFrame);
// The lineFrame will be the block's first child; the rest of the // frame list (after lastInlineFrame) will be the second and // subsequent children; insert lineFrame into aFrameList.
aFrameList.InsertFrame(nullptr, nullptr, aLineFrame);
NS_ASSERTION(aLineFrame->Style() == firstLineStyle, "Bogus style on line frame");
}
// Give the inline frames to the lineFrame <b>after</b> reparenting them
ReparentFrames(this, aLineFrame, firstLineChildren, true); if (aLineFrame->PrincipalChildList().IsEmpty() &&
aLineFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
aLineFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(firstLineChildren));
} else {
AppendFrames(aLineFrame, FrameChildListID::Principal,
std::move(firstLineChildren));
}
}
// Special routine to handle appending a new frame to a block frame's // child list. Takes care of placing the new frame into the right // place when first-line style is present. void nsCSSFrameConstructor::AppendFirstLineFrames(
nsFrameConstructorState& aState, nsIContent* aBlockContent,
nsContainerFrame* aBlockFrame, nsFrameList& aFrameList) { // It's possible that aBlockFrame needs to have a first-line frame // created because it doesn't currently have any children. const nsFrameList& blockKids = aBlockFrame->PrincipalChildList(); if (blockKids.IsEmpty()) {
WrapFramesInFirstLineFrame(aState, aBlockContent, aBlockFrame, nullptr,
aFrameList); return;
}
// Examine the last block child - if it's a first-line frame then // appended frames need special treatment.
nsIFrame* lastBlockKid = blockKids.LastChild(); if (!lastBlockKid->IsLineFrame()) { // No first-line frame at the end of the list, therefore there is // an intervening block between any first-line frame the frames // we are appending. Therefore, we don't need any special // treatment of the appended frames. return;
}
void nsCSSFrameConstructor::CheckForFirstLineInsertion(
nsIFrame* aParentFrame, nsFrameList& aFrameList) {
MOZ_ASSERT(aParentFrame->Style()->IsInFirstLineSubtree(), "Why were we called?");
if (aFrameList.IsEmpty()) { // Happens often enough, with the caption stuff. No need to do the ancestor // walk here. return;
}
class RestyleManager* restyleManager = RestyleManager();
// Check whether there's a ::first-line on the path up from aParentFrame. // Note that we can't stop until we've run out of ancestors with // pseudo-element data, because the first-letter might be somewhere way up the // tree; in particular it might be past our containing block.
nsIFrame* ancestor = aParentFrame; while (ancestor) { if (!ancestor->Style()->IsInFirstLineSubtree()) { // We know we won't find a ::first-line now. return;
}
if (!ancestor->IsLineFrame()) {
ancestor = ancestor->GetParent(); continue;
}
if (!ancestor->Style()->IsPseudoElement()) { // This is a continuation lineframe, not the first line; no need to do // anything to the styles. return;
}
// Fix up the styles of aFrameList for ::first-line. for (nsIFrame* f : aFrameList) {
restyleManager->ReparentComputedStyleForFirstLine(f);
} return;
}
}
// Determine how many characters in the text fragment apply to the // first letter static int32_t FirstLetterCount( const CharacterDataBuffer* aCharacterDataBuffer) {
int32_t count = 0;
int32_t firstLetterLength = 0;
const uint32_t n = aCharacterDataBuffer->GetLength(); for (uint32_t i = 0; i < n; i++) { const char16_t ch = aCharacterDataBuffer->CharAt(i); // FIXME: take content language into account when deciding whitespace. if (dom::IsSpaceCharacter(ch)) { if (firstLetterLength) { break;
}
count++; continue;
} // XXX I18n if ((ch == '\'') || (ch == '\"')) { if (firstLetterLength) { break;
} // keep looping
firstLetterLength = 1;
} else {
count++; break;
}
}
nsFirstLetterFrame* letterFrame =
NS_NewFloatingFirstLetterFrame(mPresShell, aComputedStyle); // We don't want to use a text content for a non-text frame (because we want // its primary frame to be a text frame).
nsIContent* letterContent = aParentFrame->GetContent();
nsContainerFrame* containingBlock =
aState.GetGeometricParent(*aComputedStyle->StyleDisplay(), aParentFrame);
InitAndRestoreFrame(aState, letterContent, containingBlock, letterFrame);
// Init the text frame to refer to the letter frame. // // Make sure we get a proper style for it (the one passed in is for the letter // frame and will have the float property set on it; the text frame shouldn't // have that set).
ServoStyleSet* styleSet = mPresShell->StyleSet();
RefPtr<ComputedStyle> textSC =
styleSet->ResolveStyleForText(aTextContent, aComputedStyle);
aTextFrame->SetComputedStyleWithoutNotification(textSC);
InitAndRestoreFrame(aState, aTextContent, letterFrame, aTextFrame);
// And then give the text frame to the letter frame
SetInitialSingleChild(letterFrame, aTextFrame);
// See if we will need to continue the text frame (does it contain // more than just the first-letter text or not?) If it does, then we // create (in advance) a continuation frame for it.
nsIFrame* nextTextFrame = nullptr; if (NeedFirstLetterContinuation(aTextContent)) { // Create continuation
nextTextFrame = CreateContinuingFrame(aTextFrame, aParentFrame);
RefPtr<ComputedStyle> newSC =
styleSet->ResolveStyleForText(aTextContent, aParentStyle);
nextTextFrame->SetComputedStyle(newSC);
}
NS_ASSERTION(aResult.IsEmpty(), "aResult should be an empty nsFrameList!"); // Put the new float before any of the floats in the block we're doing // first-letter for, that is, before any floats whose parent is // containingBlock.
nsIFrame* prevSibling = nullptr; for (nsIFrame* f : aState.mFloatedList) { if (f->GetParent() == containingBlock) { break;
}
prevSibling = f;
}
// Get a ComputedStyle for the first-letter-frame. // // Keep this in sync with nsBlockFrame::UpdatePseudoElementStyles.
nsIFrame* parentFrame = nsIFrame::CorrectStyleParentFrame(
aParentFrame, PseudoStyleType::FirstLetter);
// Use content from containing block so that we can actually // find a matching style rule.
nsIContent* blockContent = aBlockFrame->GetContent();
// Create first-letter style rule, ignoring first line. If we already have a // first-line we'll reparent the style below.
RefPtr<ComputedStyle> sc =
GetFirstLetterStyle(blockContent, parentComputedStyleIgnoringFirstLine);
if (sc) { if (parentComputedStyleIgnoringFirstLine != parentComputedStyle) {
sc = mPresShell->StyleSet()->ReparentComputedStyle(
sc, parentComputedStyle, parentComputedStyle,
blockContent->AsElement());
}
// Create a new text frame (the original one will be discarded) // pass a temporary stylecontext, the correct one will be set // later. Start off by unsetting the primary frame for // aTextContent, so it's no longer pointing to the to-be-destroyed // frame. // XXXbz it would be really nice to destroy the old frame _first_, // then create the new one, so we could avoid this hack.
aTextContent->SetPrimaryFrame(nullptr);
nsIFrame* textFrame = NS_NewTextFrame(mPresShell, textSC);
// Create the right type of first-letter frame const nsStyleDisplay* display = sc->StyleDisplay();
nsFirstLetterFrame* letterFrame; if (display->IsFloatingStyle() && !aParentFrame->IsInSVGTextSubtree()) { // Make a floating first-letter frame
letterFrame = CreateFloatingLetterFrame(state, aTextContent, textFrame,
aParentFrame, parentComputedStyle,
sc, aResult);
} else { // Make an inflow first-letter frame
letterFrame = NS_NewFirstLetterFrame(mPresShell, sc);
// Initialize the first-letter-frame. We don't want to use a text // content for a non-text frame (because we want its primary frame to // be a text frame).
nsIContent* letterContent = aParentFrame->GetContent();
letterFrame->Init(letterContent, aParentFrame, nullptr);
SetInitialSingleChild(letterFrame, textFrame);
aResult.Clear();
aResult.AppendFrame(nullptr, letterFrame);
NS_ASSERTION(!aBlockFrame->GetPrevContinuation(), "should have the first continuation here");
aBlockFrame->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD);
}
MOZ_ASSERT(
!aBlockFrame->GetPrevContinuation(), "Setting up a first-letter frame on a non-first block continuation?"); auto parent = static_cast<nsContainerFrame*>(aParentFrame->FirstContinuation()); if (MOZ_UNLIKELY(parent->IsLineFrame())) {
parent = static_cast<nsContainerFrame*>(
parent->GetParent()->FirstContinuation());
}
parent->SetHasFirstLetterChild();
aBlockFrame->SetProperty(nsContainerFrame::FirstLetterProperty(),
letterFrame);
aTextContent->SetPrimaryFrame(textFrame);
}
}
nsContainerFrame* parentFrame = nullptr;
nsIFrame* textFrame = nullptr;
nsIFrame* prevFrame = nullptr;
nsFrameList letterFrames; bool stopLooking = false;
WrapFramesInFirstLetterFrame(
aBlockFrame, aBlockFrame, aBlockFrame, aBlockFrames.FirstChild(),
&parentFrame, &textFrame, &prevFrame, letterFrames, &stopLooking); if (!parentFrame) { return;
}
DestroyContext context(mPresShell); if (parentFrame == aBlockFrame) { // Take textFrame out of the block's frame list and substitute the // letter frame(s) instead.
aBlockFrames.DestroyFrame(context, textFrame);
aBlockFrames.InsertFrames(nullptr, prevFrame, std::move(letterFrames));
} else { // Take the old textFrame out of the inline parent's child list
RemoveFrame(context, FrameChildListID::Principal, textFrame);
// Insert in the letter frame(s)
parentFrame->InsertFrames(FrameChildListID::Principal, prevFrame, nullptr,
std::move(letterFrames));
}
}
// This loop attempts to implement "Finding the First Letter": // https://drafts.csswg.org/css-pseudo-4/#application-in-css // FIXME: we don't handle nested blocks correctly yet though (bug 214004) while (frame) {
nsIFrame* nextFrame = frame->GetNextSibling();
// Skip all ::markers and placeholders. if (frame->Style()->GetPseudoType() == PseudoStyleType::Marker ||
frame->IsPlaceholderFrame()) {
prevFrame = frame;
frame = nextFrame; continue;
}
LayoutFrameType frameType = frame->Type(); if (LayoutFrameType::Text == frameType) { // Wrap up first-letter content in a letter frame
Text* textContent = frame->GetContent()->AsText(); if (IsFirstLetterContent(textContent)) { // Create letter frame to wrap up the text
CreateLetterFrame(aBlockFrame, aBlockContinuation, textContent,
aParentFrame, aLetterFrames);
// Provide adjustment information for parent
*aModifiedParent = aParentFrame;
*aTextFrame = frame;
*aPrevFrame = prevFrame;
*aStopLooking = true; return;
}
} elseif (IsInlineFrame(frame) && frameType != LayoutFrameType::Br) {
nsIFrame* kids = frame->PrincipalChildList().FirstChild();
WrapFramesInFirstLetterFrame(aBlockFrame, aBlockContinuation, static_cast<nsContainerFrame*>(frame), kids,
aModifiedParent, aTextFrame, aPrevFrame,
aLetterFrames, aStopLooking); if (*aStopLooking) { return;
}
} else { // This will stop us looking to create more letter frames. For // example, maybe the frame-type is "letterFrame" or // "placeholderFrame". This keeps us from creating extra letter // frames, and also prevents us from creating letter frames when // the first real content child of a block is not text (e.g. an // image, hr, etc.)
*aStopLooking = true; break;
}
prevFrame = frame;
frame = nextFrame;
}
}
static nsIFrame* FindFirstLetterFrame(nsIFrame* aFrame,
FrameChildListID aListID) { for (nsIFrame* f : aFrame->GetChildList(aListID)) { if (f->IsLetterFrame()) { return f;
}
} return nullptr;
}
void nsCSSFrameConstructor::RemoveFloatingFirstLetterFrames(
PresShell* aPresShell, nsIFrame* aBlockFrame) { // Look for the first letter frame on the FrameChildListID::Float, then // FrameChildListID::PushedFloats.
nsIFrame* floatFrame =
::FindFirstLetterFrame(aBlockFrame, FrameChildListID::Float); if (!floatFrame) {
floatFrame =
::FindFirstLetterFrame(aBlockFrame, FrameChildListID::PushedFloats); if (!floatFrame) { return;
}
}
// Take the text frame away from the letter frame (so it isn't // destroyed when we destroy the letter frame).
nsIFrame* textFrame = floatFrame->PrincipalChildList().FirstChild(); if (!textFrame) { return;
}
// Discover the placeholder frame for the letter frame
nsPlaceholderFrame* placeholderFrame = floatFrame->GetPlaceholderFrame(); if (!placeholderFrame) { // Somethings really wrong return;
}
nsContainerFrame* parentFrame = placeholderFrame->GetParent(); if (!parentFrame) { // Somethings really wrong return;
}
ClearHasFirstLetterChildFrom(parentFrame);
// Create a new text frame with the right style that maps all of the content // that was previously part of the letter frame (and probably continued // elsewhere).
ComputedStyle* parentSC = parentFrame->Style();
nsIContent* textContent = textFrame->GetContent(); if (!textContent) { return;
}
RefPtr<ComputedStyle> newSC =
aPresShell->StyleSet()->ResolveStyleForText(textContent, parentSC);
nsIFrame* newTextFrame = NS_NewTextFrame(aPresShell, newSC);
newTextFrame->Init(textContent, parentFrame, nullptr);
// Destroy the old text frame's continuations (the old text frame // will be destroyed when its letter frame is destroyed).
nsIFrame* frameToDelete = textFrame->LastContinuation();
DestroyContext context(mPresShell); while (frameToDelete != textFrame) {
nsIFrame* nextFrameToDelete = frameToDelete->GetPrevContinuation();
RemoveFrame(context, FrameChildListID::Principal, frameToDelete);
frameToDelete = nextFrameToDelete;
}
bool stopLooking = false; do {
RemoveFloatingFirstLetterFrames(aPresShell, continuation);
RemoveFirstLetterFrames(aPresShell, continuation, aBlockFrame,
&stopLooking); if (stopLooking) { break;
}
continuation = static_cast<nsContainerFrame*>(continuation->GetNextContinuation());
} while (continuation);
}
// Fixup the letter frame situation for the given block void nsCSSFrameConstructor::RecoverLetterFrames(nsContainerFrame* aBlockFrame) {
aBlockFrame = static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation());
nsContainerFrame* continuation = aBlockFrame;
nsContainerFrame* parentFrame = nullptr;
nsIFrame* textFrame = nullptr;
nsIFrame* prevFrame = nullptr;
nsFrameList letterFrames; bool stopLooking = false; do { // XXX shouldn't this bit be set already (bug 408493), assert instead?
continuation->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE);
WrapFramesInFirstLetterFrame(
aBlockFrame, continuation, continuation,
continuation->PrincipalChildList().FirstChild(), &parentFrame,
&textFrame, &prevFrame, letterFrames, &stopLooking); if (stopLooking) { break;
}
continuation = static_cast<nsContainerFrame*>(continuation->GetNextContinuation());
} while (continuation);
if (!parentFrame) { return;
} // Take the old textFrame out of the parent's child list
DestroyContext context(mPresShell);
RemoveFrame(context, FrameChildListID::Principal, textFrame);
// Insert in the letter frame(s)
parentFrame->InsertFrames(FrameChildListID::Principal, prevFrame, nullptr,
std::move(letterFrames));
}
void nsCSSFrameConstructor::ConstructBlock(
nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame, nsContainerFrame* aContentParentFrame,
ComputedStyle* aComputedStyle, nsContainerFrame** aNewFrame,
nsFrameList& aFrameList, nsIFrame* aPositionedFrameForAbsPosContainer) { // clang-format off // // If a block frame is in a multi-column subtree, its children may need to // be chopped into runs of blocks containing column-spans and runs of // blocks containing no column-spans. Each run containing column-spans // will be wrapped by an anonymous block. See CreateColumnSpanSiblings() for // the implementation. // // If a block frame is a multi-column container, its children will need to // be processed as above. Moreover, it creates a ColumnSetWrapperFrame as // its outermost frame, and its children which have no // -moz-column-span-wrapper pseudo will be wrapped in ColumnSetFrames. See // FinishBuildingColumns() for the implementation. // // The multi-column subtree maintains the following invariants: // // 1) All the frames have the frame state bit // NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR set, except for top-level // ColumnSetWrapperFrame and those children in the column-span subtrees. // // 2) The first and last frame under ColumnSetWrapperFrame are always // ColumnSetFrame. // // 3) ColumnSetFrames are linked together as continuations. // // 4) Those column-span wrappers are *not* linked together with themselves nor // with the original block frame. The continuation chain consists of the // original block frame and the original block's continuations wrapping // non-column-spans. // // For example, this HTML // <div id="x" style="column-count: 2;"> // <div style="column-span: all">a</div> // <div id="y"> // b // <div style="column-span: all">c</div> // <div style="column-span: all">d</div> // e // </div> // </div> // <div style="column-span: all">f</div> // // yields the following frame tree. // // A) ColumnSetWrapper (original style) // B) ColumnSet (-moz-column-set) <-- always created by BeginBuildingColumns // C) Block (-moz-column-content) // D) Block (-moz-column-span-wrapper, created by x) // E) Block (div) // F) Text ("a") // G) ColumnSet (-moz-column-set) // H) Block (-moz-column-content, created by x) // I) Block (div, y) // J) Text ("b") // K) Block (-moz-column-span-wrapper, created by x) // L) Block (-moz-column-span-wrapper, created by y) // M) Block (div, new BFC) // N) Text ("c") // O) Block (div, new BFC) // P) Text ("d") // Q) ColumnSet (-moz-column-set) // R) Block (-moz-column-content, created by x) // S) Block (div, y) // T) Text ("e") // U) Block (div, new BFC) <-- not in multi-column hierarchy // V) Text ("f") // // ColumnSet linkage described in 3): B -> G -> Q // // Block linkage described in 4): C -> H -> R and I -> S // // clang-format on
// We should make the outer frame be the absolute containing block, // if one is required. We have to do this because absolute // positioning must be computed with respect to the CSS dimensions // of the element, which are the dimensions of the outer block. But // we can't really do that because only blocks can have absolute // children. So use the block and try to compensate with hacks // in nsBlockFrame::CalculateContainingBlockSizeForAbsolutes.
nsFrameConstructorSaveState absoluteSaveState;
(*aNewFrame)->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN); if (aPositionedFrameForAbsPosContainer) {
aState.PushAbsoluteContainingBlock(
*aNewFrame, aPositionedFrameForAbsPosContainer, absoluteSaveState);
}
if (aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) &&
!ShouldSuppressColumnSpanDescendants(aParentFrame)) {
blockFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
}
// Process the child content
nsFrameList childList;
ProcessChildren(aState, aContent, aComputedStyle, blockFrame, true, childList, true);
if (!MayNeedToCreateColumnSpanSiblings(blockFrame, childList)) { // No need to create column-span siblings.
blockFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList)); return;
}
// Extract any initial non-column-span kids, and put them in block frame's // child list.
nsFrameList initialNonColumnSpanKids =
childList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
blockFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(initialNonColumnSpanKids));
if (childList.IsEmpty()) { // No more kids to process (there weren't any column-span kids). return;
}
nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
aState, blockFrame, childList, // If we're constructing a column container, pass nullptr as // aPositionedFrame to forbid reparenting absolute/fixed positioned frames // to column contents or column-span wrappers.
needsColumn ? nullptr : aPositionedFrameForAbsPosContainer);
if (needsColumn) { // We're constructing a column container; need to finish building it.
FinishBuildingColumns(aState, *aNewFrame, blockFrame, columnSpanSiblings);
} else { // We're constructing a normal block which has column-span children in a // column hierarchy such as "x" in the following example. // // <div style="column-count: 2"> // <div id="x"> // <div>normal child</div> // <div style="column-span">spanner</div> // </div> // </div>
aFrameList.AppendFrames(nullptr, std::move(columnSpanSiblings));
}
MOZ_ASSERT(columnSpanSiblings.IsEmpty(), "The column-span siblings should be moved to the proper place!");
}
nsBlockFrame* nsCSSFrameConstructor::BeginBuildingColumns(
nsFrameConstructorState& aState, nsIContent* aContent,
nsContainerFrame* aParentFrame, nsContainerFrame* aColumnContent,
ComputedStyle* aComputedStyle) {
MOZ_ASSERT(aColumnContent->IsBlockFrame(), "aColumnContent should be a block frame.");
MOZ_ASSERT(aComputedStyle->StyleColumn()->IsColumnContainerStyle(), "No need to build a column hierarchy!");
MOZ_ASSERT(prevColumnSet->IsColumnSetFrame() &&
prevColumnSet->GetParent() == aColumnSetWrapper, "Should have established column hierarchy!");
// Tag the first ColumnSet to have column-span siblings so that the bit can // propagate to all the continuations. We don't want the last ColumnSet to // have this bit, so we will unset the bit for it at the end of this function.
prevColumnSet->SetHasColumnSpanSiblings(true);
nsFrameList finalList; while (aColumnContentSiblings.NotEmpty()) {
nsIFrame* f = aColumnContentSiblings.RemoveFirstChild(); if (f->IsColumnSpan()) { // Do nothing for column-span wrappers. Just move it to the final // items.
finalList.AppendFrame(aColumnSetWrapper, f);
} else { auto* continuingColumnSet = static_cast<nsContainerFrame*>(
CreateContinuingFrame(prevColumnSet, aColumnSetWrapper, false));
MOZ_ASSERT(continuingColumnSet->HasColumnSpanSiblings(), "The bit should propagate to the next continuation!");
// Tag the first non-column-span wrapper to have column-span siblings so that // the bit can propagate to all the continuations. We don't want the last // wrapper to have this bit, so we will unset the bit for it at the end of // this function.
lastNonColumnSpanWrapper->SetHasColumnSpanSiblings(true); do {
MOZ_ASSERT(aChildList.NotEmpty(), "Why call this if child list is empty?");
MOZ_ASSERT(aChildList.FirstChild()->IsColumnSpan(), "Must have the child starting with column-span!");
// Grab the consecutive column-span kids, and reparent them into a // block frame.
RefPtr<ComputedStyle> columnSpanWrapperStyle =
mPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::MozColumnSpanWrapper);
nsBlockFrame* columnSpanWrapper =
NS_NewBlockFrame(mPresShell, columnSpanWrapperStyle);
InitAndRestoreFrame(aState, content, parentFrame, columnSpanWrapper,
AllowCounters::No);
columnSpanWrapper->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR |
NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
// Grab the consecutive non-column-span kids, and reparent them into a new // continuation of the last non-column-span wrapper frame. auto* nonColumnSpanWrapper = static_cast<nsContainerFrame*>(
CreateContinuingFrame(lastNonColumnSpanWrapper, parentFrame, false));
nonColumnSpanWrapper->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR |
NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
MOZ_ASSERT(nonColumnSpanWrapper->HasColumnSpanSiblings(), "The bit should propagate to the next continuation!");
MOZ_ASSERT(!IsFramePartOfIBSplit(aParentFrame), "We should have wiped aParentFrame in " "WipeContainingBlock if it's part of IB split!");
nsIFrame* nextSibling = ::GetInsertNextSibling(aParentFrame, aPrevSibling); if (!nextSibling && IsLastContinuationForColumnContent(aParentFrame)) { // We are appending a list of frames to the last continuation of a // ::-moz-column-content. This is the case where we can fix the frame tree // instead of reframing the containing block. Return false and let // AppendFramesToParent() deal with this. returnfalse;
}
auto HasColumnSpan = [](const nsFrameList& aList) { for (nsIFrame* f : aList) { if (f->IsColumnSpan()) { returntrue;
}
} returnfalse;
};
if (HasColumnSpan(aFrameList)) { // If any frame in the frame list has "column-span:all" style, i.e. a // -moz-column-span-wrapper frame, we need to reframe the multi-column // containing block. // // We can only be here if none of the new inserted nsIContent* nodes (via // ContentAppended or ContentRangeInserted) have column-span:all style, yet // some of them have column-span:all descendants. Sadly, there's no way to // detect this by checking FrameConstructionItems in WipeContainingBlock(). // Otherwise, we would have already wiped the multi-column containing block.
PROFILER_MARKER("Reframe multi-column after constructing frame list",
LAYOUT, {}, Tracing, "Layout");
// aFrameList can contain placeholder frames. In order to destroy their // associated out-of-flow frames properly, we need to manually flush all the // out-of-flow frames in aState to their container frames.
aState.ProcessFrameInsertionsForAllLists();
DestroyContext context(mPresShell);
aFrameList.DestroyFrames(context);
RecreateFramesForContent(
GetMultiColumnContainingBlockFor(aParentFrame)->GetContent(),
InsertionKind::Async); returntrue;
}
returnfalse;
}
nsIFrame* nsCSSFrameConstructor::ConstructInline(
nsFrameConstructorState& aState, FrameConstructionItem& aItem,
nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
nsFrameList& aFrameList) { // If an inline frame has non-inline kids, then we chop up the child list // into runs of blocks and runs of inlines, create anonymous block frames to // contain the runs of blocks, inline frames with our style for the runs of // inlines, and put all these frames, in order, into aFrameList. // // When there are column-span blocks in a run of blocks, instead of creating // an anonymous block to wrap them, we create multiple anonymous blocks, // wrapping runs of non-column-spans and runs of column-spans. // // We return the the first one. The whole setup is called an {ib} // split; in what follows "frames in the split" refers to the anonymous blocks // and inlines that contain our children. // // {ib} splits maintain the following invariants: // 1) All frames in the split have the NS_FRAME_PART_OF_IBSPLIT bit // set. // // 2) Each frame in the split has the nsIFrame::IBSplitSibling // property pointing to the next frame in the split, except for the last // one, which does not have it set. // // 3) Each frame in the split has the nsIFrame::IBSplitPrevSibling // property pointing to the previous frame in the split, except for the // first one, which does not have it set. // // 4) The first and last frame in the split are always inlines. // // 5) The frames wrapping runs of non-column-spans are linked together as // continuations. The frames wrapping runs of column-spans are *not* // linked with each other nor with other non-column-span wrappers. // // 6) The first and last frame in the chains of blocks are always wrapping // non-column-spans. Both of them are created even if they're empty. // // An invariant that is NOT maintained is that the wrappers are actually // linked via GetNextSibling linkage. A simple example is an inline // containing an inline that contains a block. The three parts of the inner // inline end up with three different parents. // // For example, this HTML: // <span> // <div>a</div> // <span> // b // <div>c</div> // </span> // d // <div>e</div> // f // </span> // Gives the following frame tree: // // Inline (outer span) // Block (anonymous, outer span) // Block (div) // Text("a") // Inline (outer span) // Inline (inner span) // Text("b") // Block (anonymous, outer span) // Block (anonymous, inner span) // Block (div) // Text("c") // Inline (outer span) // Inline (inner span) // Text("d") // Block (anonymous, outer span) // Block (div) // Text("e") // Inline (outer span) // Text("f")
// Initialize the frame
InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
// definition cannot be inside next block because the object's destructor is // significant. this is part of the fix for bug 42372
nsFrameConstructorSaveState absoluteSaveState;
bool isAbsPosCB = newFrame->IsAbsPosContainingBlock();
newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN); if (isAbsPosCB) { // Relatively positioned frames becomes a container for child // frames that are positioned
aState.PushAbsoluteContainingBlock(newFrame, newFrame, absoluteSaveState);
}
if (aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) &&
!ShouldSuppressColumnSpanDescendants(aParentFrame)) {
newFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
}
// Process the child content
nsFrameList childList;
ConstructFramesFromItemList(aState, aItem.mChildItems, newFrame, /* aParentIsWrapperAnonBox = */ false, childList);
nsIFrame* firstBlock = nullptr; if (!aItem.mIsAllInline) { for (nsIFrame* f : childList) { if (f->IsBlockOutside()) {
firstBlock = f; break;
}
}
}
if (aItem.mIsAllInline || !firstBlock) { // This part is easy. We either already know we have no non-inline kids, // or haven't found any when constructing actual frames (the latter can // happen only if out-of-flows that we thought had no containing block // acquired one when ancestor inline frames and {ib} splits got // constructed). Just put all the kids into the single inline frame and // bail.
newFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(childList));
aState.AddChild(newFrame, aFrameList, content, aParentFrame); return newFrame;
}
// This inline frame contains several types of children. Therefore this frame // has to be chopped into several pieces, as described above.
// Grab the first inline's kids
nsFrameList firstInlineKids = childList.TakeFramesBefore(firstBlock);
newFrame->SetInitialChildList(FrameChildListID::Principal,
std::move(firstInlineKids));
// Resolve the right style for our anonymous blocks. // // The distinction in styles is needed because of CSS 2.1, section // 9.2.1.1, which says: // // When such an inline box is affected by relative positioning, any // resulting translation also affects the block-level box contained // in the inline box.
RefPtr<ComputedStyle> blockSC =
mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::MozBlockInsideInlineWrapper, computedStyle);
nsContainerFrame* lastNewInline = static_cast<nsContainerFrame*>(aInitialInline->FirstContinuation()); do { // On entry to this loop aChildList is not empty and the first frame in it // is block-level.
MOZ_ASSERT(aChildList.NotEmpty(), "Should have child items");
MOZ_ASSERT(aChildList.FirstChild()->IsBlockOutside(), "Must have list starting with block");
// The initial run of blocks belongs to an anonymous block that we create // right now. The anonymous block will be the parent of these block // children of the inline.
nsBlockFrame* blockFrame = NS_NewBlockFrame(mPresShell, blockSC);
InitAndRestoreFrame(aState, content, parentFrame, blockFrame,
AllowCounters::No); if (aInitialInline->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
blockFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
}
// Find the first non-block child which defines the end of our block kids // and the start of our next inline's kids
nsFrameList blockKids =
aChildList.Split([](nsIFrame* f) { return !f->IsBlockOutside(); });
if (!aInitialInline->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
MoveChildrenTo(aInitialInline, blockFrame, blockKids);
SetFrameIsIBSplit(lastNewInline, blockFrame);
aSiblings.AppendFrame(nullptr, blockFrame);
} else { // Extract any initial non-column-span frames, and put them in // blockFrame's child list.
nsFrameList initialNonColumnSpanKids =
blockKids.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
MoveChildrenTo(aInitialInline, blockFrame, initialNonColumnSpanKids);
if (blockKids.NotEmpty()) { // Although SetFrameIsIBSplit() will add NS_FRAME_PART_OF_IBSPLIT for // blockFrame later, we manually add the bit earlier here to make all // the continuations of blockFrame created in // CreateColumnSpanSiblings(), i.e. non-column-span wrappers, have the // bit via nsIFrame::Init().
blockFrame->AddStateBits(NS_FRAME_PART_OF_IBSPLIT);
// Now grab the initial inlines in aChildList and put them into an inline // frame.
nsInlineFrame* inlineFrame = NS_NewInlineFrame(mPresShell, computedStyle);
InitAndRestoreFrame(aState, content, parentFrame, inlineFrame,
AllowCounters::No);
inlineFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN); if (aInitialInline->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
inlineFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
}
if (aIsAbsPosCB) {
inlineFrame->MarkAsAbsoluteContainingBlock();
}
// return whether it's ok to append (in the AppendFrames sense) to // aParentFrame if our nextSibling is aNextSibling. aParentFrame must // be an ib-split inline. staticbool IsSafeToAppendToIBSplitInline(nsIFrame* aParentFrame,
nsIFrame* aNextSibling) {
MOZ_ASSERT(IsInlineFrame(aParentFrame), "Must have an inline parent here");
do {
NS_ASSERTION(IsFramePartOfIBSplit(aParentFrame), "How is this not part of an ib-split?"); if (aNextSibling || aParentFrame->GetNextContinuation() ||
GetIBSplitSibling(aParentFrame)) { returnfalse;
}
aNextSibling = aParentFrame->GetNextSibling();
aParentFrame = aParentFrame->GetParent();
} while (IsInlineFrame(aParentFrame));
// FIXME(emilio): This looks terribly inefficient if you insert elements deep // in a MathML subtree. if (aFrame->IsMathMLFrame()) {
TRACE("MathML");
RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async); returntrue;
}
// A ruby-related frame that's getting new children. // The situation for ruby is complex, especially when interacting with // spaces. It contains these two special cases apart from tables: // 1) There are effectively three types of white spaces in ruby frames // we handle differently: leading/tailing/inter-level space, // inter-base/inter-annotation space, and inter-segment space. // These three types of spaces can be converted to each other when // their sibling changes. // 2) The first effective child of a ruby frame must always be a ruby // base container. It should be created or destroyed accordingly. if (IsRubyPseudo(aFrame) || frameType == LayoutFrameType::Ruby ||
RubyUtils::IsRubyContainerBox(frameType)) { // We want to optimize it better, and avoid reframing as much as // possible. But given the cases above, and the fact that a ruby // usually won't be very large, it should be fine to reframe it.
TRACE("Ruby");
RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async); returntrue;
}
// Reframe the multi-column container whenever elements insert/append // into it because we need to reconstruct column-span split. if (aFrame->IsColumnSetWrapperFrame()) {
TRACE("Multi-column");
RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async); returntrue;
}
// Before we go and append the frames, we must check for several // special situations.
if (aFrame->GetContent() == mDocument->GetRootElement()) { // Situation #1 is when we insert content that becomes the canonical body // element, and its used WritingMode is different from the root element's // used WritingMode. // We need to reframe the root element so that the root element's frames has // the correct writing-mode propagated from body element. (See // nsCSSFrameConstructor::ConstructDocElementFrame.) // // Bug 1594297: When inserting a new <body>, we may need to reframe the old // <body> which has a "overflow" value other than simple "visible". But it's // tricky, see bug 1593752.
nsIContent* bodyElement = mDocument->GetBodyElement(); for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) { const WritingMode bodyWM(iter.item().mComputedStyle); if (iter.item().mContent == bodyElement &&
bodyWM != aFrame->GetWritingMode()) {
TRACE("Root");
RecreateFramesForContent(mDocument->GetRootElement(),
InsertionKind::Async); returntrue;
}
}
}
// Situation #2 is a flex / grid container frame into which we're inserting // new inline non-replaced children, adjacent to an existing anonymous flex or // grid item. if (aFrame->IsFlexOrGridContainer()) {
FCItemIterator iter(aItems);
// Check if we're adding to-be-wrapped content right *after* an existing // anonymous flex or grid item (which would need to absorb this content). constbool isLegacyWebKitBox = IsFlexContainerForLegacyWebKitBox(aFrame); if (aPrevSibling && IsAnonymousItem(aPrevSibling) &&
iter.item().NeedsAnonFlexOrGridItem(aState, isLegacyWebKitBox)) {
TRACE("Inserting inline after anon flex or grid item");
RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async); returntrue;
}
// Check if we're adding to-be-wrapped content right *before* an existing // anonymous flex or grid item (which would need to absorb this content). if (nextSibling && IsAnonymousItem(nextSibling)) { // Jump to the last entry in the list
iter.SetToEnd();
iter.Prev(); if (iter.item().NeedsAnonFlexOrGridItem(aState, isLegacyWebKitBox)) {
TRACE("Inserting inline before anon flex or grid item");
RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async); returntrue;
}
}
}
// Situation #3 is an anonymous flex or grid item that's getting new children // who don't want to be wrapped. if (IsAnonymousItem(aFrame)) {
AssertAnonymousFlexOrGridItemParent(aFrame);
// We need to push a null float containing block to be sure that // "NeedsAnonFlexOrGridItem" will know we're not honoring floats for this // inserted content. (In particular, this is necessary in order for // its "GetGeometricParent" call to return the correct result.) // We're not honoring floats on this content because it has the // _flex/grid container_ as its parent in the content tree.
nsFrameConstructorSaveState floatSaveState;
aState.PushFloatContainingBlock(nullptr, floatSaveState);
FCItemIterator iter(aItems); // Skip over things that _do_ need an anonymous flex item, because // they're perfectly happy to go here -- they won't cause a reframe.
nsIFrame* containerFrame = aFrame->GetParent(); constbool isLegacyWebKitBox =
IsFlexContainerForLegacyWebKitBox(containerFrame); if (!iter.SkipItemsThatNeedAnonFlexOrGridItem(aState, isLegacyWebKitBox)) { // We hit something that _doesn't_ need an anonymous flex item! // Rebuild the flex container to bust it out.
TRACE("Inserting non-inlines inside anon flex or grid item");
RecreateFramesForContent(containerFrame->GetContent(),
InsertionKind::Async); returntrue;
}
// If we get here, then everything in |aItems| needs to be wrapped in // an anonymous flex or grid item. That's where it's already going - good!
}
// Situation #4 is a case when table pseudo-frames don't work out right
ParentType parentType = GetParentType(aFrame); // If all the kids want a parent of the type that aFrame is, then we're all // set to go. Indeed, there won't be any table pseudo-frames created between // aFrame and the kids, so those won't need to be merged with any table // pseudo-frames that might already be kids of aFrame. If aFrame itself is a // table pseudo-frame, then all the kids in this list would have wanted a // frame of that type wrapping them anyway, so putting them inside it is ok. if (!aItems.AllWantParentType(parentType)) { // Don't give up yet. If parentType is not eTypeBlock and the parent is // not a generated content frame, then try filtering whitespace out of the // list. if (parentType != eTypeBlock && !aFrame->IsGeneratedContentFrame()) { // For leading whitespace followed by a kid that wants our parent type, // there are four cases: // 1) We have a previous sibling which is not a table pseudo. That means // that previous sibling wanted a (non-block) parent of the type we're // looking at. Then the whitespace comes between two table-internal // elements, so should be collapsed out. // 2) We have a previous sibling which is a table pseudo. It might have // kids who want this whitespace, so we need to reframe. // 3) We have no previous sibling and our parent frame is not a table // pseudo. That means that we'll be at the beginning of our actual // non-block-type parent, and the whitespace is OK to collapse out. // If something is ever inserted before us, it'll find our own parent // as its parent and if it's something that would care about the // whitespace it'll want a block parent, so it'll trigger a reframe at // that point. // 4) We have no previous sibling and our parent frame is a table pseudo. // Need to reframe. // All that is predicated on finding the correct previous sibling. We // might have to walk backwards along continuations from aFrame to do so. // // It's always OK to drop whitespace between any two items that want a // parent of type parentType. // // For trailing whitespace preceded by a kid that wants our parent type, // there are four cases: // 1) We have a next sibling which is not a table pseudo. That means // that next sibling wanted a (non-block) parent of the type we're // looking at. Then the whitespace comes between two table-internal // elements, so should be collapsed out. // 2) We have a next sibling which is a table pseudo. It might have // kids who want this whitespace, so we need to reframe. // 3) We have no next sibling and our parent frame is not a table // pseudo. That means that we'll be at the end of our actual // non-block-type parent, and the whitespace is OK to collapse out. // If something is ever inserted after us, it'll find our own parent // as its parent and if it's something that would care about the // whitespace it'll want a block parent, so it'll trigger a reframe at // that point. // 4) We have no next sibling and our parent frame is a table pseudo. // Need to reframe. // All that is predicated on finding the correct next sibling. We might // have to walk forward along continuations from aFrame to do so. That // said, in the case when nextSibling is null at this point and aIsAppend // is true, we know we're in case 3. Furthermore, in that case we don't // even have to worry about the table pseudo situation; we know our // parent is not a table pseudo there.
FCItemIterator iter(aItems);
FCItemIterator start(iter); do { if (iter.SkipItemsWantingParentType(parentType)) { break;
}
// iter points to an item that wants a different parent. If it's not // whitespace, we're done; no more point scanning the list. if (!iter.item().IsWhitespace(aState)) { break;
}
if (iter == start) { // Leading whitespace. How to handle this depends on our // previous sibling and aFrame. See the long comment above.
nsIFrame* prevSibling = aPrevSibling; if (!prevSibling) { // Try to find one after all
nsIFrame* parentPrevCont = aFrame->GetPrevContinuation(); while (parentPrevCont) {
prevSibling = parentPrevCont->PrincipalChildList().LastChild(); if (prevSibling) { break;
}
parentPrevCont = parentPrevCont->GetPrevContinuation();
}
}; if (prevSibling) { if (IsTablePseudo(prevSibling)) { // need to reframe break;
}
} elseif (IsTablePseudo(aFrame)) { // need to reframe break;
}
}
FCItemIterator spaceEndIter(iter); // Advance spaceEndIter past any whitespace bool trailingSpaces = spaceEndIter.SkipWhitespace(aState);
bool okToDrop; if (trailingSpaces) { // Trailing whitespace. How to handle this depeds on aIsAppend, our // next sibling and aFrame. See the long comment above.
okToDrop = aIsAppend && !nextSibling; if (!okToDrop) { if (!nextSibling) { // Try to find one after all
nsIFrame* parentNextCont = aFrame->GetNextContinuation(); while (parentNextCont) {
nextSibling = parentNextCont->PrincipalChildList().FirstChild(); if (nextSibling) { break;
}
parentNextCont = parentNextCont->GetNextContinuation();
}
}
if (okToDrop) {
iter.DeleteItemsTo(this, spaceEndIter);
} else { // We're done: we don't want to drop the whitespace, and it has the // wrong parent type. break;
}
// Now loop, since |iter| points to item right after the whitespace we // removed.
} while (!iter.IsDone());
}
// We might be able to figure out some sort of optimizations here, but they // would have to depend on having a correct aPrevSibling and a correct next // sibling. For example, we can probably avoid reframing if none of // aFrame, aPrevSibling, and next sibling are table pseudo-frames. But it // doesn't seem worth it to worry about that for now, especially since we // in fact do not have a reliable aPrevSibling, nor any next sibling, in // this method.
// aItems might have changed, so recheck the parent type thing. In fact, // it might be empty, so recheck that too. if (aItems.IsEmpty()) { returnfalse;
}
// If aFrame is empty, the insertion process will be able to take care of // creating any needed pseudo-parents. if (!aItems.AllWantParentType(parentType) &&
!SafeToInsertPseudoNeedingChildren(aFrame)) { // Reframing aFrame->GetContent() is good enough, since the content of // table pseudo-frames is the ancestor content.
TRACE("Pseudo-frames going wrong");
RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async); returntrue;
}
}
// Situation #5 is a frame in multicol subtree that's getting new children. if (aFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) { bool anyColumnSpanItems = false; for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) { if (iter.item().mComputedStyle->StyleColumn()->IsColumnSpanStyle()) {
anyColumnSpanItems = true; break;
}
}
bool needsReframe = // 1. Insert / append any column-span children.
anyColumnSpanItems || // 2. GetInsertionPrevSibling() modifies insertion parent. If the prev // sibling is a column-span, aFrame ends up being the // column-span-wrapper.
aFrame->Style()->GetPseudoType() ==
PseudoStyleType::MozColumnSpanWrapper || // 3. Append into {ib} split container. There might be room for // optimization, but let's reframe for correctness...
IsFramePartOfIBSplit(aFrame);
if (needsReframe) {
TRACE("Multi-column");
RecreateFramesForContent(
GetMultiColumnContainingBlockFor(aFrame)->GetContent(),
InsertionKind::Async); returntrue;
}
// If we get here, then we need further check for {ib} split to decide // whether to reframe. For example, appending a block into an empty inline // that is not part of an {ib} split, but should become an {ib} split.
}
// A <fieldset> may need to pick up a new rendered legend from aItems. // We currently can't handle this case without recreating frames for // the fieldset. // XXXmats we should be able to optimize this when the fieldset doesn't // currently have a rendered legend. ContentRangeInserted needs to be fixed // to use the inner frame as the content insertion frame in that case. if (constauto* fieldset = GetFieldSetFrameFor(aFrame)) { // Check if any item is eligible to be a rendered legend. for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) { constauto& item = iter.item(); if (!item.mContent->IsHTMLElement(nsGkAtoms::legend)) { continue;
} constauto* display = item.mComputedStyle->StyleDisplay(); if (display->IsFloatingStyle() ||
display->IsAbsolutelyPositionedStyle()) { continue;
}
TRACE("Fieldset with rendered legend");
RecreateFramesForContent(fieldset->GetContent(), InsertionKind::Async); returntrue;
}
}
// Now we have several cases involving {ib} splits. Put them all in a // do/while with breaks to take us to the "go and reconstruct" code. do { if (IsInlineFrame(aFrame)) { if (aItems.AreAllItemsInline()) { // We can just put the kids in. returnfalse;
}
if (!IsFramePartOfIBSplit(aFrame)) { // Need to go ahead and reconstruct. break;
}
// Now we're adding kids including some blocks to an inline part of an // {ib} split. If we plan to call AppendFrames, and don't have a next // sibling for the new frames, and our parent is the last continuation of // the last part of the {ib} split, and the same is true of all our // ancestor inlines (they have no following continuations and they're the // last part of their {ib} splits and we'd be adding to the end for all // of them), then AppendFrames will handle things for us. Bail out in // that case. if (aIsAppend && IsSafeToAppendToIBSplitInline(aFrame, nextSibling)) { returnfalse;
}
// Need to reconstruct. break;
}
// Now we know we have a block parent. If it's not part of an // ib-split, we're all set. if (!IsFramePartOfIBSplit(aFrame)) { returnfalse;
}
// We're adding some kids to a block part of an {ib} split. If all the // kids are blocks, we don't need to reconstruct. if (aItems.AreAllItemsBlock()) { returnfalse;
}
// We might have some inline kids for this block. Just fall out of the // loop and reconstruct.
} while (false);
// If we don't have a containing block, start with aFrame and look for one. if (!aContainingBlock) {
aContainingBlock = aFrame;
}
// To find the right block to reframe, just walk up the tree until we find a // frame that is: // 1) Not part of an IB split // 2) Not a pseudo-frame // 3) Not an inline frame // We're guaranteed to find one, since ComputedStyle::ApplyStyleFixups // enforces that the root is display:none, display:table, or display:block. // Note that walking up "too far" is OK in terms of correctness, even if it // might be a little inefficient. This is why we walk out of all // pseudo-frames -- telling which ones are or are not OK to walk out of is // too hard (and I suspect that we do in fact need to walk out of all of // them). while (IsFramePartOfIBSplit(aContainingBlock) ||
aContainingBlock->IsInlineOutside() ||
aContainingBlock->Style()->IsPseudoOrAnonBox()) {
aContainingBlock = aContainingBlock->GetParent();
NS_ASSERTION(aContainingBlock, "Must have non-inline, non-ib-split, non-pseudo frame as " "root (or child of root, for a table root)!");
}
// Tell parent of the containing block to reformulate the // entire block. This is painful and definitely not optimal // but it will *always* get the right answer.
void nsCSSFrameConstructor::ReframeContainingBlock(nsIFrame* aFrame) { // XXXbz how exactly would we get here while isReflowing anyway? Should this // whole test be ifdef DEBUG? if (mPresShell->IsReflowLocked()) { // don't ReframeContainingBlock, this will result in a crash // if we remove a tree that's in reflow - see bug 121368 for testcase
NS_ERROR( "Atemptted to nsCSSFrameConstructor::ReframeContainingBlock during a " "Reflow!!!"); return;
}
// Get the first "normal" ancestor of the target frame.
nsIFrame* containingBlock = GetIBContainingBlockFor(aFrame); if (containingBlock) { // From here we look for the containing block in case the target // frame is already a block (which can happen when an inline frame // wraps some of its content in an anonymous block; see // ConstructInline)
// NOTE: We used to get the FloatContainingBlock here, but it was often // wrong. GetIBContainingBlock works much better and provides the correct // container in all cases so GetFloatContainingBlock(aFrame) has been // removed
// And get the containingBlock's content if (nsIContent* blockContent = containingBlock->GetContent()) { #ifdef DEBUG if (gNoisyContentUpdates) {
printf(" ==> blockContent=%p\n", blockContent);
} #endif
RecreateFramesForContent(blockContent, InsertionKind::Async); return;
}
}
// If we get here, we're screwed!
RecreateFramesForContent(mPresShell->GetDocument()->GetRootElement(),
InsertionKind::Async);
}
////////////////////////////////////////////////////////// // nsCSSFrameConstructor::FrameConstructionItem methods // ////////////////////////////////////////////////////////// bool nsCSSFrameConstructor::FrameConstructionItem::IsWhitespace(
nsFrameConstructorState& aState) const {
MOZ_ASSERT(aState.mCreatingExtraFrames || !mContent->GetPrimaryFrame(), "How did that happen?"); if (!mIsText) { returnfalse;
}
mContent->SetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
NS_REFRAME_IF_WHITESPACE); return mContent->TextIsOnlyWhitespace();
}
//////////////////////////////////////////////////////////////////////// // nsCSSFrameConstructor::FrameConstructionItemList::Iterator methods // //////////////////////////////////////////////////////////////////////// inlinebool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
SkipItemsWantingParentType(ParentType aParentType) {
MOZ_ASSERT(!IsDone(), "Shouldn't be done yet"); while (item().DesiredParentType() == aParentType) {
Next(); if (IsDone()) { returntrue;
}
} returnfalse;
}
inlinebool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
SkipItemsNotWantingParentType(ParentType aParentType) {
MOZ_ASSERT(!IsDone(), "Shouldn't be done yet"); while (item().DesiredParentType() != aParentType) {
Next(); if (IsDone()) { returntrue;
}
} returnfalse;
}
// Note: we implement -webkit-{inline-}box using nsFlexContainerFrame, but we // use different rules for what gets wrapped in an anonymous flex item. bool nsCSSFrameConstructor::FrameConstructionItem::NeedsAnonFlexOrGridItem( const nsFrameConstructorState& aState, bool aIsLegacyWebKitBox) { if (mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) { // This will be an inline non-replaced box. returntrue;
}
if (aIsLegacyWebKitBox) { if (mComputedStyle->StyleDisplay()->IsInlineOutsideStyle()) { // In an emulated legacy box, all inline-level content gets wrapped in an // anonymous flex item. returntrue;
} if (!(mFCData->mBits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
aState.GetGeometricParent(*mComputedStyle->StyleDisplay(), nullptr)) { // We're abspos or fixedpos (or a XUL popup), which means we'll spawn a // placeholder which (because our container is an emulated legacy box) // we'll need to wrap in an anonymous flex item. So, we just treat // _this_ frame as if _it_ needs to be wrapped in an anonymous flex item, // and then when we spawn the placeholder, it'll end up in the right // spot. returntrue;
}
}
returnfalse;
}
inlinebool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
SkipItemsThatNeedAnonFlexOrGridItem(const nsFrameConstructorState& aState, bool aIsLegacyWebKitBox) {
MOZ_ASSERT(!IsDone(), "Shouldn't be done yet"); while (item().NeedsAnonFlexOrGridItem(aState, aIsLegacyWebKitBox)) {
Next(); if (IsDone()) { returntrue;
}
} returnfalse;
}
inlinebool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
SkipItemsThatDontNeedAnonFlexOrGridItem( const nsFrameConstructorState& aState, bool aIsLegacyWebKitBox) {
MOZ_ASSERT(!IsDone(), "Shouldn't be done yet"); while (!(item().NeedsAnonFlexOrGridItem(aState, aIsLegacyWebKitBox))) {
Next(); if (IsDone()) { returntrue;
}
} returnfalse;
}
inlinebool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
SkipItemsNotWantingRubyParent() {
MOZ_ASSERT(!IsDone(), "Shouldn't be done yet"); while (!IsRubyParentType(item().DesiredParentType())) {
Next(); if (IsDone()) { returntrue;
}
} returnfalse;
}
inlinebool
nsCSSFrameConstructor::FrameConstructionItemList::Iterator::SkipWhitespace(
nsFrameConstructorState& aState) {
MOZ_ASSERT(!IsDone(), "Shouldn't be done yet");
MOZ_ASSERT(item().IsWhitespace(aState), "Not pointing to whitespace?"); do {
Next(); if (IsDone()) { returntrue;
}
} while (item().IsWhitespace(aState));
returnfalse;
}
void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
AppendItemToList(FrameConstructionItemList& aTargetList) {
NS_ASSERTION(&aTargetList != &mList, "Unexpected call");
MOZ_ASSERT(!IsDone(), "should not be done");
void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
AppendItemsToList(nsCSSFrameConstructor* aFCtor, const Iterator& aEnd,
FrameConstructionItemList& aTargetList) {
NS_ASSERTION(&aTargetList != &mList, "Unexpected call");
MOZ_ASSERT(&mList == &aEnd.mList, "End iterator for some other list?");
// We can't just move our guts to the other list if it already has // some information or if we're not moving our entire list. if (!AtStart() || !aEnd.IsDone() || !aTargetList.IsEmpty()) { do {
AppendItemToList(aTargetList);
} while (*this != aEnd); return;
}
// Move our entire list of items into the empty target list.
aTargetList.mItems = std::move(mList.mItems);
// Copy over the various counters
aTargetList.mInlineCount = mList.mInlineCount;
aTargetList.mBlockCount = mList.mBlockCount;
aTargetList.mItemCount = mList.mItemCount;
memcpy(aTargetList.mDesiredParentCounts, mList.mDesiredParentCounts, sizeof(aTargetList.mDesiredParentCounts));
// reset mList
mList.Reset(aFCtor);
// Point ourselves to aEnd, as advertised
SetToEnd();
MOZ_ASSERT(*this == aEnd, "How did that happen?");
}
void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::InsertItem(
FrameConstructionItem* aItem) { if (IsDone()) {
mList.mItems.insertBack(aItem);
} else { // Just insert the item before us. There's no magic here.
mCurrent->setPrevious(aItem);
}
mList.AdjustCountsForItem(aItem, 1);
MOZ_ASSERT(aItem->getNext() == mCurrent, "How did that happen?");
}
void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::DeleteItemsTo(
nsCSSFrameConstructor* aFCtor, const Iterator& aEnd) {
MOZ_ASSERT(&mList == &aEnd.mList, "End iterator for some other list?");
MOZ_ASSERT(*this != aEnd, "Shouldn't be at aEnd yet");
do {
NS_ASSERTION(!IsDone(), "Ran off end of list?");
FrameConstructionItem* item = mCurrent;
Next();
item->remove();
mList.AdjustCountsForItem(item, -1);
item->Delete(aFCtor);
} while (*this != aEnd);
}
void nsCSSFrameConstructor::FreeFCItem(FrameConstructionItem* aItem) {
MOZ_ASSERT(mFCItemsInUse != 0); if (--mFCItemsInUse == 0) { // The arena is now unused - clear it but retain one chunk.
mFirstFreeFCItem = nullptr;
mFCItemPool.Clear();
} else { // Prepend it to the list of free items.
FreeFCItemLink* item = reinterpret_cast<FreeFCItemLink*>(aItem);
item->mNext = mFirstFreeFCItem;
mFirstFreeFCItem = item;
}
}
// This must be done after measuring from the frame tree, since frame // manager will measure sizes of staled computed values and style // structs, which only make sense after we know what are being used.
nsFrameManager::AddSizeOfIncludingThis(aSizes);
// Measurement of the following members may be added later if DMD finds it // is worthwhile: // - mFCItemPool // - mContainStyleScopeManager
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.825Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.