/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * construction of a frame tree that is nearly isomorphic to the content * tree and updating of that tree in response to dynamic changes */ #include "nsCSSFrameConstructor.h" #include "ActiveLayerTracker.h" #include "ChildIterator.h" #include "PseudoStyleType.h" #include "RetainedDisplayListBuilder.h" #include "RubyUtils.h" #include "StickyScrollContainer.h" #include "mozilla/AbsoluteContainingBlock.h" #include "mozilla/Assertions.h" #include "mozilla/AutoRestore.h" #include "mozilla/ComputedStyleInlines.h" #include "mozilla/DebugOnly.h" #include "mozilla/ErrorResult.h" #include "mozilla/Likely.h" #include "mozilla/ManualNAC.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/PrintedSheetFrame.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/RestyleManager.h" #include "mozilla/SVGGradientFrame.h" #include "mozilla/ScopeExit.h" #include "mozilla/ScrollContainerFrame.h" #include "mozilla/ServoBindings.h" #include "mozilla/ServoStyleSetInlines.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/StaticPrefs_mathml.h" #include "mozilla/dom/BindContext.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/CharacterData.h" #include "mozilla/dom/CharacterDataBuffer.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ElementInlines.h" #include "mozilla/dom/GeneratedImageContent.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLSelectElement.h" #include "mozilla/dom/HTMLSharedListElement.h" #include "mozilla/dom/HTMLSummaryElement.h" #include "mozilla/intl/LocaleService.h" #include "nsAtom.h" #include "nsAutoLayoutPhase.h" #include "nsBlockFrame.h" #include "nsCRT.h" #include "nsCanvasFrame.h" #include "nsCheckboxRadioFrame.h" #include "nsComboboxControlFrame.h" #include "nsContainerFrame.h" #include "nsContentUtils.h" #include "nsError.h" #include "nsFieldSetFrame.h" #include "nsFirstLetterFrame.h" #include "nsFlexContainerFrame.h" #include "nsGkAtoms.h" #include "nsGridContainerFrame.h" #include "nsHTMLParts.h" #include "nsIAnonymousContentCreator.h" #include "nsIFormControl.h" #include "nsIFrameInlines.h" #include "nsIObjectLoadingContent.h" #include "nsIPopupContainer.h" #include "nsIScriptError.h" #include "nsImageFrame.h" #include "nsInlineFrame.h" #include "nsLayoutUtils.h" #include "nsListControlFrame.h" #include "nsMathMLParts.h" #include "nsNameSpaceManager.h" #include "nsPageContentFrame.h" #include "nsPageFrame.h" #include "nsPageSequenceFrame.h" #include "nsPlaceholderFrame.h" #include "nsPresContext.h" #include "nsRefreshDriver.h" #include "nsRubyBaseContainerFrame.h" #include "nsRubyBaseFrame.h" #include "nsRubyFrame.h" #include "nsRubyTextContainerFrame.h" #include "nsRubyTextFrame.h" #include "nsStyleConsts.h" #include "nsStyleStructInlines.h" #include "nsTArray.h" #include "nsTableCellFrame.h" #include "nsTableColFrame.h" #include "nsTableFrame.h" #include "nsTableRowFrame.h" #include "nsTableRowGroupFrame.h" #include "nsTableWrapperFrame.h" #include "nsTextNode.h" #include "nsTransitionManager.h" #include "nsUnicharUtils.h" #include "nsXULElement.h" #ifdef XP_MACOSX # include "nsIDocShell.h" #endif #ifdef ACCESSIBILITY # include "nsAccessibilityService.h" #endif #undef NOISY_FIRST_LETTER using namespace mozilla; using namespace mozilla::dom; nsIFrame* NS_NewHTMLCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewHTMLAudioFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsContainerFrame* NS_NewSVGOuterSVGFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGInnerSVGFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGGeometryFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGGFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsContainerFrame* NS_NewSVGForeignObjectFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGAFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGSwitchFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGSymbolFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGTextFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGContainerFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGUseFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGViewFrame(PresShell* aPresShell, ComputedStyle* aStyle); extern nsIFrame* NS_NewSVGLinearGradientFrame(PresShell* aPresShell, ComputedStyle* aStyle); extern nsIFrame* NS_NewSVGRadialGradientFrame(PresShell* aPresShell, ComputedStyle* aStyle); extern nsIFrame* NS_NewSVGStopFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsContainerFrame* NS_NewSVGMarkerFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsContainerFrame* NS_NewSVGMarkerAnonChildFrame(PresShell* aPresShell, ComputedStyle* aStyle); extern nsIFrame* NS_NewSVGImageFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGClipPathFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGFilterFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGPatternFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGMaskFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGFEContainerFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGFELeafFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGFEImageFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGFEUnstyledLeafFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewFileControlLabelFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewComboboxLabelFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewMiddleCroppingLabelFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewInputButtonControlFrame(PresShell*, ComputedStyle*); #include "mozilla/dom/NodeInfo.h" #include "nsContentCreatorFunctions.h" #include "nsNodeInfoManager.h" #include "prenv.h" #ifdef DEBUG // Set the environment variable GECKO_FRAMECTOR_DEBUG_FLAGS to one or // more of the following flags (comma separated) for handy debug // output. static bool gNoisyContentUpdates = false; static bool gReallyNoisyContentUpdates = false; static bool gNoisyInlineConstruction = false; struct FrameCtorDebugFlags { const char* name; bool* on; }; static FrameCtorDebugFlags gFrameCtorDebugFlags[] = { {"content-updates", &gNoisyContentUpdates}, {"really-noisy-content-updates", &gReallyNoisyContentUpdates}, {"noisy-inline", &gNoisyInlineConstruction}}; # define NUM_DEBUG_FLAGS (std::size(gFrameCtorDebugFlags)) #endif //------------------------------------------------------------------ nsIFrame* NS_NewLeafBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewRangeFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewMenuPopupFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSliderFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewScrollbarButtonFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewSimpleXULLeafFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewXULImageFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForListStyleImage(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForViewTransition(PresShell*, ComputedStyle*); // Returns true if aFrame is an anonymous flex/grid item. static inline bool IsAnonymousItem(const nsIFrame* aFrame) { return aFrame->Style()->GetPseudoType() == PseudoStyleType::MozAnonymousItem; } // Returns true IFF the given nsIFrame is a nsFlexContainerFrame and represents // a -webkit-{inline-}box container. static inline bool IsFlexContainerForLegacyWebKitBox(const nsIFrame* aFrame) { return aFrame->IsFlexContainerFrame() && aFrame->IsLegacyWebkitBox(); } 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"); } static MOZ_ALWAYS_INLINE void AssertAnonymousFlexOrGridItemParent( const nsIFrame* aChild) { AssertAnonymousFlexOrGridItemParent(aChild, aChild->GetParent()); } #define ToCreationFunc(_func) \ [](PresShell* aPs, ComputedStyle* aStyle) -> nsIFrame* { \ return _func(aPs, aStyle); \ } /** * True if aFrame is an actual inline frame in the sense of non-replaced * display:inline CSS boxes. In other words, it can be affected by {ib} * splitting and can contain first-letter frames. Basically, this is either an * inline frame (positioned or otherwise) or an line frame (this last because * it can contain first-letter and because inserting blocks in the middle of it * needs to terminate it). */ static bool IsInlineFrame(const nsIFrame* aFrame) { return aFrame->IsLineParticipant(); } /** * True for display: contents elements. */ static inline bool IsDisplayContents(const Element* aElement) { return aElement->IsDisplayContents(); } static inline bool IsDisplayContents(const nsIContent* aContent) { return aContent->IsElement() && IsDisplayContents(aContent->AsElement()); } /** * True if aFrame is an instance of an SVG frame class or is an inline/block * frame being used for SVG text. */ static bool IsFrameForSVG(const nsIFrame* aFrame) { return aFrame->IsSVGFrame() || aFrame->IsInSVGTextSubtree(); } static bool IsLastContinuationForColumnContent(const nsIFrame* aFrame) { MOZ_ASSERT(aFrame); return aFrame->Style()->GetPseudoType() == PseudoStyleType::MozColumnContent && !aFrame->GetNextContinuation(); } /** * Returns true iff aFrame explicitly prevents its descendants from floating * (at least, down to the level of descendants which themselves are * float-containing blocks -- those will manage the floating status of any * lower-level descendents inside them, of course). */ static bool ShouldSuppressFloatingOfDescendants(nsIFrame* aFrame) { return aFrame->IsFlexOrGridContainer() || aFrame->IsMathMLFrame(); } // Return true if column-span descendants should be suppressed under aFrame's // subtree (until a multi-column container re-establishing a block formatting // context). Basically, this is testing whether aFrame establishes a new block // formatting context or not. static bool ShouldSuppressColumnSpanDescendants(nsIFrame* aFrame) { if (aFrame->Style()->GetPseudoType() == PseudoStyleType::MozColumnContent) { // Never suppress column-span under ::-moz-column-content frames. return false; } if (aFrame->IsInlineFrame()) { // Allow inline frames to have column-span block children. return false; } if (!aFrame->IsBlockFrameOrSubclass() || aFrame->HasAnyStateBits(NS_BLOCK_BFC | NS_FRAME_OUT_OF_FLOW) || aFrame->IsFixedPosContainingBlock()) { // Need to suppress column-span if we: // - Are a different block formatting context, // - Are an out-of-flow frame, OR // - Establish a containing block for fixed-position descendants // // For example, the children of a column-span never need to be further // processed even if there is a nested column-span child. Because a // column-span always creates its own block formatting context, a nested // column-span child won't be in the same block formatting context with the // nearest multi-column ancestor. This is the same case as if the // column-span is outside of a multi-column hierarchy. return true; } return false; } // Reparent a frame into a wrapper frame that is a child of its old parent. static void ReparentFrame(RestyleManager* aRestyleManager, nsContainerFrame* aNewParentFrame, nsIFrame* aFrame, bool aForceStyleReparent) { aFrame->SetParent(aNewParentFrame); // We reparent frames for two reasons: to put them inside ::first-line, and to // put them inside some wrapper anonymous boxes. if (aForceStyleReparent) { aRestyleManager->ReparentComputedStyleForFirstLine(aFrame); } } static void ReparentFrames(nsCSSFrameConstructor* aFrameConstructor, nsContainerFrame* aNewParentFrame, const nsFrameList& aFrameList, bool aForceStyleReparent) { RestyleManager* restyleManager = aFrameConstructor->RestyleManager(); for (nsIFrame* f : aFrameList) { ReparentFrame(restyleManager, aNewParentFrame, f, aForceStyleReparent); } } //---------------------------------------------------------------------- // // When inline frames get weird and have block frames in them, we // annotate them to help us respond to incremental content changes // more easily. static inline bool IsFramePartOfIBSplit(nsIFrame* aFrame) { bool result = aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT); MOZ_ASSERT(!result || static_cast(do_QueryFrame(aFrame)) || static_cast(do_QueryFrame(aFrame)), "only block/inline frames can have NS_FRAME_PART_OF_IBSPLIT"); return result; } static nsContainerFrame* GetIBSplitSibling(nsIFrame* aFrame) { MOZ_ASSERT(IsFramePartOfIBSplit(aFrame), "Shouldn't call this"); // We only store the "ib-split sibling" annotation with the first // frame in the continuation chain. Walk back to find that frame now. return aFrame->FirstContinuation()->GetProperty(nsIFrame::IBSplitSibling()); } static nsContainerFrame* GetIBSplitPrevSibling(nsIFrame* aFrame) { MOZ_ASSERT(IsFramePartOfIBSplit(aFrame), "Shouldn't call this"); // We only store the ib-split sibling annotation with the first // frame in the continuation chain. Walk back to find that frame now. return aFrame->FirstContinuation()->GetProperty( nsIFrame::IBSplitPrevSibling()); } static nsContainerFrame* GetLastIBSplitSibling(nsIFrame* aFrame) { for (nsIFrame *frame = aFrame, *next;; frame = next) { next = GetIBSplitSibling(frame); if (!next) { return static_cast(frame); } } MOZ_ASSERT_UNREACHABLE("unreachable code"); return nullptr; } static void SetFrameIsIBSplit(nsContainerFrame* aFrame, nsContainerFrame* aIBSplitSibling) { MOZ_ASSERT(aFrame, "bad args!"); // We should be the only continuation NS_ASSERTION(!aFrame->GetPrevContinuation(), "assigning ib-split sibling to other than first continuation!"); NS_ASSERTION(!aFrame->GetNextContinuation() || IsFramePartOfIBSplit(aFrame->GetNextContinuation()), "should have no non-ib-split continuations here"); // Mark the frame as ib-split. aFrame->AddStateBits(NS_FRAME_PART_OF_IBSPLIT); if (aIBSplitSibling) { NS_ASSERTION(!aIBSplitSibling->GetPrevContinuation(), "assigning something other than the first continuation as the " "ib-split sibling"); // Store the ib-split sibling (if we were given one) with the // first frame in the flow. aFrame->SetProperty(nsIFrame::IBSplitSibling(), aIBSplitSibling); aIBSplitSibling->SetProperty(nsIFrame::IBSplitPrevSibling(), aFrame); } } static nsIFrame* GetIBContainingBlockFor(nsIFrame* aFrame) { MOZ_ASSERT( IsFramePartOfIBSplit(aFrame), "GetIBContainingBlockFor() should only be called on known IB frames"); // Get the first "normal" ancestor of the target frame. nsIFrame* parentFrame; do { parentFrame = aFrame->GetParent(); if (!parentFrame) { NS_ERROR("no unsplit block frame in IB hierarchy"); return aFrame; } // Note that we ignore non-ib-split frames which have a pseudo on their // ComputedStyle -- they're not the frames we're looking for! In // particular, they may be hiding a real parent that _is_ in an ib-split. if (!IsFramePartOfIBSplit(parentFrame) && !parentFrame->Style()->IsPseudoOrAnonBox()) { break; } aFrame = parentFrame; } while (true); // post-conditions NS_ASSERTION(parentFrame, "no normal ancestor found for ib-split frame " "in GetIBContainingBlockFor"); NS_ASSERTION(parentFrame != aFrame, "parentFrame is actually the child frame - bogus reslt"); return parentFrame; } // Find the multicol containing block suitable for reframing. // // Note: this function may not return a ColumnSetWrapperFrame. For example, if // the multicol containing block has "overflow:scroll" style, // ScrollContainerFrame is returned because ColumnSetWrapperFrame is the // scrolled frame which has the -moz-scrolled-content pseudo style. We may walk // up "too far", but in terms of correctness of reframing, it's OK. static nsContainerFrame* GetMultiColumnContainingBlockFor(nsIFrame* aFrame) { MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR), "Should only be called if the frame has a multi-column ancestor!"); nsContainerFrame* current = aFrame->GetParent(); while (current && (current->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) || current->Style()->IsPseudoOrAnonBox())) { current = current->GetParent(); } MOZ_ASSERT(current, "No multicol containing block in a valid column hierarchy?"); return current; } static bool InsertSeparatorBeforeAccessKey() { static bool sInitialized = false; static bool sValue = false; if (!sInitialized) { sInitialized = true; sValue = intl::LocaleService::GetInstance()->InsertSeparatorBeforeAccesskeys(); } return sValue; } static bool AlwaysAppendAccessKey() { static bool sInitialized = false; static bool sValue = false; if (!sInitialized) { sInitialized = true; sValue = intl::LocaleService::GetInstance()->AlwaysAppendAccesskeys(); } return sValue; } //---------------------------------------------------------------------- // Block/inline frame construction logic. We maintain a few invariants here: // // 1. Block frames contain block and inline frames. // // 2. Inline frames only contain inline frames. If an inline parent has a block // child then the block child is migrated upward until it lands in a block // parent (the inline frames containing block is where it will end up). inline void SetInitialSingleChild(nsContainerFrame* aParent, nsIFrame* aFrame) { MOZ_ASSERT(!aFrame->GetNextSibling(), "Should be using a frame list"); aParent->SetInitialChildList(FrameChildListID::Principal, nsFrameList(aFrame, aFrame)); } // ----------------------------------------------------------- // Structure used when constructing formatting object trees. Contains // state information needed for absolutely positioned elements namespace mozilla { struct AbsoluteFrameList final : public nsFrameList { // Containing block for absolutely positioned elements. nsContainerFrame* mContainingBlock; explicit AbsoluteFrameList(nsContainerFrame* aContainingBlock = nullptr) : mContainingBlock(aContainingBlock) {} // Transfer frames in aOther to this list. aOther becomes empty after this // operation. AbsoluteFrameList(AbsoluteFrameList&& aOther) = default; AbsoluteFrameList& operator=(AbsoluteFrameList&& aOther) = default; #ifdef DEBUG // XXXbz Does this need a debug-only assignment operator that nulls out the // childList in the AbsoluteFrameList we're copying? Introducing a difference // between debug and non-debug behavior seems bad, so I guess not... ~AbsoluteFrameList() { NS_ASSERTION(!FirstChild(), "Dangling child list. Someone forgot to insert it?"); } #endif }; } // namespace mozilla // ----------------------------------------------------------- // Structure for saving the existing state when pushing/poping containing // blocks. The destructor restores the state to its previous state class MOZ_STACK_CLASS nsFrameConstructorSaveState { public: ~nsFrameConstructorSaveState(); private: // Pointer to struct whose data we save/restore. AbsoluteFrameList* mList = nullptr; // The saved pointer to the fixed list. AbsoluteFrameList* mSavedFixedList = nullptr; // Copy of original frame list. This can be the original absolute list or a // float list. AbsoluteFrameList mSavedList; // The name of the child list in which our frames would belong. mozilla::FrameChildListID mChildListID = FrameChildListID::Principal; nsFrameConstructorState* mState = nullptr; friend class nsFrameConstructorState; }; // Structure used for maintaining state information during the // frame construction process class MOZ_STACK_CLASS nsFrameConstructorState { public: nsPresContext* mPresContext; PresShell* mPresShell; nsCSSFrameConstructor* mFrameConstructor; // Containing block information for out-of-flow frames. // // Floats are easy. Whatever is our float CB. // // Regular abspos elements are easy too. Its containing block can be the // nearest abspos element, or the ICB (the canvas frame). // // Top layer abspos elements are always children of the ICB, but we can get // away with having two different lists (mAbsoluteList and // mTopLayerAbsoluteList), because because top layer frames cause // non-top-layer frames to be contained inside (so any descendants of a top // layer abspos can never share containing block with it, unless they're also // in the top layer). // // Regular fixed elements however are trickier. Fixed elements can be // contained in one of three lists: // // * mAbsoluteList, if our abspos cb is also a fixpos cb (e.g., is // transformed or has a filter). // // * mAncestorFixedList, if the fixpos cb is an ancestor element other than // the viewport frame, (so, a transformed / filtered // ancestor). // // * mRealFixedList, which is also the fixed list used for the top layer // fixed items, which is the fixed list of the viewport // frame. // // It is important that mRealFixedList is shared between regular and top layer // fixpos elements, since no-top-layer descendants of top layer fixed elements // could share ICB and vice versa, so without that there would be no guarantee // of layout ordering between them. AbsoluteFrameList mFloatedList; AbsoluteFrameList mAbsoluteList; AbsoluteFrameList mTopLayerAbsoluteList; AbsoluteFrameList mAncestorFixedList; AbsoluteFrameList mRealFixedList; // Never null, always pointing to one of the lists documented above. AbsoluteFrameList* mFixedList; // What `page: auto` resolves to. This is the used page-name of the parent // frame. Updated by AutoFrameConstructionPageName. const nsAtom* mAutoPageNameValue = nullptr; nsCOMPtr mFrameState; // These bits will be added to the state bits of any frame we construct // using this state. nsFrameState mAdditionalStateBits{0}; // If false (which is the default) then call SetPrimaryFrame() as needed // during frame construction. If true, don't make any SetPrimaryFrame() // calls, except for generated content which doesn't have a primary frame // yet. The mCreatingExtraFrames == true mode is meant to be used for // construction of random "extra" frames for elements via normal frame // construction APIs (e.g. replication of things across pages in paginated // mode). bool mCreatingExtraFrames; // This keeps track of whether we have found a "rendered legend" for // the current FieldSetFrame. bool mHasRenderedLegend; nsTArray> mGeneratedContentWithInitializer; #ifdef DEBUG // Record the float containing block candidate passed into // MaybePushFloatContainingBlock() to keep track that we've call the method to // handle the float CB scope before processing the CB's children. It is reset // in ConstructFramesFromItemList(). nsContainerFrame* mFloatCBCandidate = nullptr; #endif // Constructor // Use the passed-in history state. nsFrameConstructorState( PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock, nsContainerFrame* aAbsoluteContainingBlock, nsContainerFrame* aFloatContainingBlock, already_AddRefed aHistoryState); // Get the history state from the pres context's pres shell. nsFrameConstructorState(PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock, nsContainerFrame* aAbsoluteContainingBlock, nsContainerFrame* aFloatContainingBlock); ~nsFrameConstructorState(); // Process the frame insertions for all the out-of-flow nsAbsoluteItems. void ProcessFrameInsertionsForAllLists(); // Function to push the existing absolute containing block state and // create a new scope. Code that uses this function should get matching // logic in GetAbsoluteContainingBlock. // Also makes aNewAbsoluteContainingBlock the containing block for // fixed-pos elements if necessary. // aPositionedFrame is the frame whose style actually makes // aNewAbsoluteContainingBlock a containing block. E.g. for a scrollable // element aPositionedFrame is the element's primary frame and // aNewAbsoluteContainingBlock is the scrolled frame. void PushAbsoluteContainingBlock( nsContainerFrame* aNewAbsoluteContainingBlock, nsIFrame* aPositionedFrame, nsFrameConstructorSaveState& aSaveState); // Function to forbid floats descendants under aFloatCBCandidate, or open a // new float containing block scope for aFloatCBCandidate. The current // state is saved in aSaveState if a new scope is pushed. void MaybePushFloatContainingBlock(nsContainerFrame* aFloatCBCandidate, nsFrameConstructorSaveState& aSaveState); // Helper function for MaybePushFloatContainingBlock(). void PushFloatContainingBlock(nsContainerFrame* aNewFloatContainingBlock, nsFrameConstructorSaveState& aSaveState); // Function to return the proper geometric parent for a frame with display // struct given by aStyleDisplay and parent's frame given by // aContentParentFrame. nsContainerFrame* GetGeometricParent( const nsStyleDisplay& aStyleDisplay, nsContainerFrame* aContentParentFrame) const; // Collect absolute frames in mAbsoluteList which are proper descendants // of aNewParent, and reparent them to aNewParent. // // Note: This function does something unusual that moves absolute items // after their frames are constructed under a column hierarchy which has // column-span elements. Do not use this if you're not dealing with // columns. void ReparentAbsoluteItems(nsContainerFrame* aNewParent); // Collect floats in mFloatedList which are proper descendants of aNewParent, // and reparent them to aNewParent. // // Note: This function does something unusual that moves floats after their // frames are constructed under a column hierarchy which has column-span // elements. Do not use this if you're not dealing with columns. void ReparentFloats(nsContainerFrame* aNewParent); /** * Function to add a new frame to the right frame list. This MUST be called * on frames before their children have been processed if the frames might * conceivably be out-of-flow; otherwise cleanup in error cases won't work * right. Also, this MUST be called on frames after they have been * initialized. * @param aNewFrame the frame to add * @param aFrameList the list to add in-flow frames to * @param aContent the content pointer for aNewFrame * @param aParentFrame the parent frame for the content if it were in-flow * @param aCanBePositioned pass false if the frame isn't allowed to be * positioned * @param aCanBeFloated pass false if the frame isn't allowed to be * floated */ void AddChild(nsIFrame* aNewFrame, nsFrameList& aFrameList, nsIContent* aContent, nsContainerFrame* aParentFrame, bool aCanBePositioned = true, bool aCanBeFloated = true, bool aInsertAfter = false, nsIFrame* aInsertAfterFrame = nullptr); /** * Function to return the fixed-pos element list. Normally this will just * hand back the fixed-pos element list, but in case we're dealing with a * transformed element that's acting as an abs-pos and fixed-pos container, * we'll hand back the abs-pos list. Callers should use this function if they * want to get the list acting as the fixed-pos item parent. */ AbsoluteFrameList& GetFixedList() { return *mFixedList; } const AbsoluteFrameList& GetFixedList() const { return *mFixedList; } protected: friend class nsFrameConstructorSaveState; /** * ProcessFrameInsertions takes the frames in aFrameList and adds them as * kids to the aChildListID child list of |aFrameList.containingBlock|. */ void ProcessFrameInsertions(AbsoluteFrameList& aFrameList, mozilla::FrameChildListID aChildListID); /** * GetOutOfFlowFrameList selects the out-of-flow frame list the new * frame should be added to. If the frame shouldn't be added to any * out-of-flow list, it returns nullptr. The corresponding type of * placeholder is also returned via the aPlaceholderType parameter * if this method doesn't return nullptr. The caller should check * whether the returned list really has a containing block. */ AbsoluteFrameList* GetOutOfFlowFrameList(nsIFrame* aNewFrame, bool aCanBePositioned, bool aCanBeFloated, nsFrameState* aPlaceholderType); }; nsFrameConstructorState::nsFrameConstructorState( PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock, nsContainerFrame* aAbsoluteContainingBlock, nsContainerFrame* aFloatContainingBlock, already_AddRefed aHistoryState) : mPresContext(aPresShell->GetPresContext()), mPresShell(aPresShell), mFrameConstructor(aPresShell->FrameConstructor()), mFloatedList(aFloatContainingBlock), mAbsoluteList(aAbsoluteContainingBlock), mTopLayerAbsoluteList(mFrameConstructor->GetCanvasFrame()), mAncestorFixedList(aFixedContainingBlock), mRealFixedList( static_cast(mFrameConstructor->GetRootFrame())), // See PushAbsoluteContaningBlock below mFrameState(aHistoryState), mCreatingExtraFrames(false), mHasRenderedLegend(false) { MOZ_COUNT_CTOR(nsFrameConstructorState); mFixedList = [&] { if (aFixedContainingBlock == aAbsoluteContainingBlock) { return &mAbsoluteList; } if (aAbsoluteContainingBlock == mRealFixedList.mContainingBlock) { return &mRealFixedList; } return &mAncestorFixedList; }(); } nsFrameConstructorState::nsFrameConstructorState( PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock, nsContainerFrame* aAbsoluteContainingBlock, nsContainerFrame* aFloatContainingBlock) : nsFrameConstructorState( aPresShell, aFixedContainingBlock, aAbsoluteContainingBlock, aFloatContainingBlock, aPresShell->GetDocument()->GetLayoutHistoryState()) {} nsFrameConstructorState::~nsFrameConstructorState() { MOZ_COUNT_DTOR(nsFrameConstructorState); ProcessFrameInsertionsForAllLists(); for (auto& content : Reversed(mGeneratedContentWithInitializer)) { content->RemoveProperty(nsGkAtoms::genConInitializerProperty); } } void nsFrameConstructorState::ProcessFrameInsertionsForAllLists() { ProcessFrameInsertions(mFloatedList, FrameChildListID::Float); ProcessFrameInsertions(mAbsoluteList, FrameChildListID::Absolute); ProcessFrameInsertions(mTopLayerAbsoluteList, FrameChildListID::Absolute); ProcessFrameInsertions(*mFixedList, FrameChildListID::Fixed); ProcessFrameInsertions(mRealFixedList, FrameChildListID::Fixed); } void nsFrameConstructorState::PushAbsoluteContainingBlock( nsContainerFrame* aNewAbsoluteContainingBlock, nsIFrame* aPositionedFrame, nsFrameConstructorSaveState& aSaveState) { MOZ_ASSERT(!!aNewAbsoluteContainingBlock == !!aPositionedFrame, "We should have both or none"); aSaveState.mList = &mAbsoluteList; aSaveState.mChildListID = FrameChildListID::Absolute; aSaveState.mState = this; aSaveState.mSavedList = std::move(mAbsoluteList); aSaveState.mSavedFixedList = mFixedList; mAbsoluteList = AbsoluteFrameList(aNewAbsoluteContainingBlock); mFixedList = [&] { if (!aPositionedFrame || aPositionedFrame->IsFixedPosContainingBlock()) { // See if we need to treat abspos and fixedpos the same. This happens if // we're a transformed/filtered/etc element, or if we force a null abspos // containing block (for mathml for example). return &mAbsoluteList; } if (aPositionedFrame->StyleDisplay()->mTopLayer == StyleTopLayer::Auto) { // If our new CB is in the top layer, and isn't a fixed CB itself, we also // escape the usual containment. return &mRealFixedList; } if (mFixedList == &mAbsoluteList) { // If we were pointing to our old absolute list, keep pointing to it. return &aSaveState.mSavedList; } // Otherwise keep pointing to the current thing (another ancestor's // absolute list, or the real fixed list, doesn't matter). return mFixedList; }(); if (aNewAbsoluteContainingBlock && !aNewAbsoluteContainingBlock->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); } else if (aFloatCBCandidate->IsFloatContainingBlock()) { PushFloatContainingBlock(aFloatCBCandidate, aSaveState); } #ifdef DEBUG mFloatCBCandidate = aFloatCBCandidate; #endif } void nsFrameConstructorState::PushFloatContainingBlock( nsContainerFrame* aNewFloatContainingBlock, nsFrameConstructorSaveState& aSaveState) { MOZ_ASSERT(!aNewFloatContainingBlock || aNewFloatContainingBlock->IsFloatContainingBlock(), "Please push a real float containing block!"); NS_ASSERTION( !aNewFloatContainingBlock || !ShouldSuppressFloatingOfDescendants(aNewFloatContainingBlock), "We should not push a frame that is supposed to _suppress_ " "floats as a float containing block!"); aSaveState.mList = &mFloatedList; aSaveState.mSavedList = std::move(mFloatedList); aSaveState.mChildListID = FrameChildListID::Float; aSaveState.mState = this; mFloatedList = AbsoluteFrameList(aNewFloatContainingBlock); } nsContainerFrame* nsFrameConstructorState::GetGeometricParent( const nsStyleDisplay& aStyleDisplay, nsContainerFrame* aContentParentFrame) const { // If there is no container for a fixed, absolute, or floating root // frame, we will ignore the positioning. This hack is originally // brought to you by the letter T: tables, since other roots don't // even call into this code. See bug 178855. // // XXX Disabling positioning in this case is a hack. If one was so inclined, // one could support this either by (1) inserting a dummy block between the // table and the canvas or (2) teaching the canvas how to reflow positioned // elements. (1) has the usual problems when multiple frames share the same // content (notice all the special cases in this file dealing with inner // tables and table wrappers which share the same content). (2) requires some // work and possible factoring. // // XXXbz couldn't we just force position to "static" on roots and // float to "none"? That's OK per CSS 2.1, as far as I can tell. if (aContentParentFrame && aContentParentFrame->IsInSVGTextSubtree()) { return aContentParentFrame; } if (aStyleDisplay.IsFloatingStyle() && mFloatedList.mContainingBlock) { NS_ASSERTION(!aStyleDisplay.IsAbsolutelyPositionedStyle(), "Absolutely positioned _and_ floating?"); return mFloatedList.mContainingBlock; } if (aStyleDisplay.mTopLayer != StyleTopLayer::None) { MOZ_ASSERT(aStyleDisplay.mTopLayer == StyleTopLayer::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); } } AbsoluteFrameList* nsFrameConstructorState::GetOutOfFlowFrameList( nsIFrame* aNewFrame, bool aCanBePositioned, bool aCanBeFloated, nsFrameState* aPlaceholderType) { const nsStyleDisplay* disp = aNewFrame->StyleDisplay(); if (aCanBeFloated && disp->IsFloatingStyle()) { *aPlaceholderType = PLACEHOLDER_FOR_FLOAT; return &mFloatedList; } if (aCanBePositioned) { if (disp->mTopLayer != StyleTopLayer::None) { *aPlaceholderType = PLACEHOLDER_FOR_TOPLAYER; if (disp->mPosition == StylePositionProperty::Fixed) { *aPlaceholderType |= PLACEHOLDER_FOR_FIXEDPOS; return &mRealFixedList; } *aPlaceholderType |= PLACEHOLDER_FOR_ABSPOS; return &mTopLayerAbsoluteList; } if (disp->mPosition == StylePositionProperty::Absolute) { *aPlaceholderType = PLACEHOLDER_FOR_ABSPOS; return &mAbsoluteList; } if (disp->mPosition == StylePositionProperty::Fixed) { *aPlaceholderType = PLACEHOLDER_FOR_FIXEDPOS; return mFixedList; } } return nullptr; } void nsFrameConstructorState::AddChild( nsIFrame* aNewFrame, nsFrameList& aFrameList, nsIContent* aContent, nsContainerFrame* aParentFrame, bool aCanBePositioned, bool aCanBeFloated, bool aInsertAfter, nsIFrame* aInsertAfterFrame) { MOZ_ASSERT(!aNewFrame->GetNextSibling(), "Shouldn't happen"); nsFrameState placeholderType; AbsoluteFrameList* outOfFlowFrameList = GetOutOfFlowFrameList( aNewFrame, aCanBePositioned, aCanBeFloated, &placeholderType); // The comments in GetGeometricParent regarding root table frames // all apply here, unfortunately. Thus, we need to check whether // the returned frame items really has containing block. nsFrameList* frameList; if (outOfFlowFrameList && outOfFlowFrameList->mContainingBlock) { MOZ_ASSERT(aNewFrame->GetParent() == outOfFlowFrameList->mContainingBlock, "Parent of the frame is not the containing block?"); frameList = outOfFlowFrameList; } else { frameList = &aFrameList; placeholderType = nsFrameState(0); } if (placeholderType) { NS_ASSERTION(frameList != &aFrameList, "Putting frame in-flow _and_ want a placeholder?"); nsIFrame* placeholderFrame = nsCSSFrameConstructor::CreatePlaceholderFrameFor( mPresShell, aContent, aNewFrame, aParentFrame, nullptr, placeholderType); placeholderFrame->AddStateBits(mAdditionalStateBits); // Add the placeholder frame to the flow aFrameList.AppendFrame(nullptr, placeholderFrame); } #ifdef DEBUG else { NS_ASSERTION(aNewFrame->GetParent() == aParentFrame, "In-flow frame has wrong parent"); } #endif if (aInsertAfter) { frameList->InsertFrame(nullptr, aInsertAfterFrame, aNewFrame); } else { frameList->AppendFrame(nullptr, aNewFrame); } } // Some of this function's callers recurse 1000 levels deep in crashtests. On // platforms where stack limits are low, we can't afford to incorporate this // function's `AutoTArray`s into its callers' stack frames, so disable inlining. MOZ_NEVER_INLINE void nsFrameConstructorState::ProcessFrameInsertions( AbsoluteFrameList& aFrameList, FrameChildListID aChildListID) { MOZ_ASSERT(&aFrameList == &mFloatedList || &aFrameList == &mAbsoluteList || &aFrameList == &mTopLayerAbsoluteList || &aFrameList == &mAncestorFixedList || &aFrameList == mFixedList || &aFrameList == &mRealFixedList); MOZ_ASSERT_IF(&aFrameList == &mFloatedList, aChildListID == FrameChildListID::Float); MOZ_ASSERT_IF(&aFrameList == &mAbsoluteList || &aFrameList == mFixedList, aChildListID == FrameChildListID::Absolute || aChildListID == FrameChildListID::Fixed); MOZ_ASSERT_IF(&aFrameList == &mTopLayerAbsoluteList, aChildListID == FrameChildListID::Absolute); MOZ_ASSERT_IF(&aFrameList == mFixedList && &aFrameList != &mAbsoluteList, aChildListID == FrameChildListID::Fixed); MOZ_ASSERT_IF(&aFrameList == &mAncestorFixedList, aChildListID == FrameChildListID::Fixed); MOZ_ASSERT_IF(&aFrameList == &mRealFixedList, aChildListID == FrameChildListID::Fixed); if (aFrameList.IsEmpty()) { return; } nsContainerFrame* containingBlock = aFrameList.mContainingBlock; NS_ASSERTION(containingBlock, "Child list without containing block?"); if (aChildListID == FrameChildListID::Fixed) { // Put this frame on the transformed-frame's abs-pos list instead, if // it has abs-pos children instead of fixed-pos children. aChildListID = containingBlock->GetAbsoluteListID(); } // Insert the frames hanging out in aItems. We can use SetInitialChildList() // if the containing block hasn't been reflowed yet (so NS_FRAME_FIRST_REFLOW // is set) and doesn't have any frames in the aChildListID child list yet. const nsFrameList& childList = containingBlock->GetChildList(aChildListID); if (childList.IsEmpty() && containingBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { // If we're injecting absolutely positioned frames, inject them on the // absolute containing block if (aChildListID == containingBlock->GetAbsoluteListID()) { containingBlock->GetAbsoluteContainingBlock()->SetInitialChildList( containingBlock, aChildListID, std::move(aFrameList)); } else { containingBlock->SetInitialChildList(aChildListID, std::move(aFrameList)); } } else if (childList.IsEmpty() || aChildListID == FrameChildListID::Fixed || aChildListID == FrameChildListID::Absolute) { // The order is not important for abs-pos/fixed-pos frame list, just // append the frame items to the list directly. mFrameConstructor->AppendFrames(containingBlock, aChildListID, std::move(aFrameList)); } else { 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 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, 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); const auto& 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; } else if (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); if (mList == &mState->mAbsoluteList) { mState->mAbsoluteList = std::move(mSavedList); mState->mFixedList = mSavedFixedList; } else { mState->mFloatedList = std::move(mSavedList); } MOZ_ASSERT(mSavedList.IsEmpty(), "Frames in mSavedList should've moved back into mState!"); MOZ_ASSERT(!mList->LastChild() || !mList->LastChild()->GetNextSibling(), "Something corrupted our list!"); } } /** * Moves aFrameList from aOldParent to aNewParent. This updates the parent * pointer of the frames in the list, and reparents their views as needed. * nsIFrame::SetParent sets the NS_FRAME_HAS_VIEW bit on aNewParent and its * ancestors as needed. Then it sets the list as the initial child list * on aNewParent, unless aNewParent either already has kids or has been * reflowed; in that case it appends the new frames. Note that this * method differs from ReparentFrames in that it doesn't change the kids' * style. */ // XXXbz Since this is only used for {ib} splits, could we just copy the view // bits from aOldParent to aNewParent and then use the // nsFrameList::ApplySetParent? That would still leave us doing two passes // over the list, of course; if we really wanted to we could factor out the // relevant part of ReparentFrameViewList, I suppose... Or just get rid of // views, which would make most of this function go away. static void MoveChildrenTo(nsIFrame* aOldParent, nsContainerFrame* aNewParent, nsFrameList& aFrameList) { aFrameList.ApplySetParent(aNewParent); if (aNewParent->PrincipalChildList().IsEmpty() && aNewParent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { aNewParent->SetInitialChildList(FrameChildListID::Principal, std::move(aFrameList)); } else { aNewParent->AppendFrames(FrameChildListID::Principal, std::move(aFrameList)); } } static void EnsureAutoPageName(nsFrameConstructorState& aState, const nsContainerFrame* const aFrame) { // Check if we need to figure out our used page name. // When building the entire document, this should only happen for the // root, which will mean the loop will immediately end. Either way, this will // only happen once for each time the frame constructor is run. if (aState.mAutoPageNameValue) { return; } for (const nsContainerFrame* frame = aFrame; frame; frame = frame->GetParent()) { if (const nsAtom* maybePageName = frame->GetStylePageName()) { aState.mAutoPageNameValue = maybePageName; return; } } // Ensure that a root with `page: auto` gets an empty page name // 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 EnsureAutoPageName(aState, aFrame->GetParent()); mNameToRestore = aState.mAutoPageNameValue; MOZ_ASSERT(mNameToRestore, "Page name should have been found by EnsureAutoPageName"); if (const nsAtom* maybePageName = aFrame->GetStylePageName()) { aState.mAutoPageNameValue = maybePageName; } aFrame->SetAutoPageValue(aState.mAutoPageNameValue); } nsCSSFrameConstructor::AutoFrameConstructionPageName:: ~AutoFrameConstructionPageName() { // This isn't actually useful when not in paginated layout, but it's very // likely cheaper to unconditionally write this pointer than to test for // paginated layout and then branch on the result. mState.mAutoPageNameValue = mNameToRestore; } //---------------------------------------------------------------------- nsCSSFrameConstructor::nsCSSFrameConstructor(Document* aDocument, PresShell* aPresShell) : nsFrameManager(aPresShell), mDocument(aDocument), mFirstFreeFCItem(nullptr), mFCItemsInUse(0), mCurrentDepth(0), mQuotesDirty(false), mCountersDirty(false), mAlwaysCreateFramesForIgnorableWhitespace(false), mRemovingContent(false) { #ifdef DEBUG static bool gFirstTime = true; if (gFirstTime) { gFirstTime = false; char* flags = PR_GetEnv("GECKO_FRAMECTOR_DEBUG_FLAGS"); if (flags) { bool error = false; for (;;) { char* comma = strchr(flags, ','); if (comma) *comma = '\0'; bool found = false; FrameCtorDebugFlags* flag = 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(); } RestyleManager()->NotifyDestroyingFrame(aFrame); } struct nsGenConInitializer { UniquePtr mNode; nsGenConList* mList; void (nsCSSFrameConstructor::*mDirtyAll)(); nsGenConInitializer(UniquePtr aNode, nsGenConList* aList, void (nsCSSFrameConstructor::*aDirtyAll)()) : mNode(std::move(aNode)), mList(aList), mDirtyAll(aDirtyAll) {} }; already_AddRefed nsCSSFrameConstructor::CreateGenConTextNode( nsFrameConstructorState& aState, const nsAString& aString, UniquePtr aInitializer) { RefPtr content = new (mDocument->NodeInfoManager()) nsTextNode(mDocument->NodeInfoManager()); content->SetText(aString, false); if (aInitializer) { aInitializer->mNode->mText = content; content->SetProperty(nsGkAtoms::genConInitializerProperty, aInitializer.release(), nsINode::DeleteProperty); aState.mGeneratedContentWithInitializer.AppendElement(content); } return content.forget(); } void nsCSSFrameConstructor::CreateGeneratedContent( nsFrameConstructorState& aState, Element& aOriginatingElement, ComputedStyle& aPseudoStyle, const StyleContentItem& aItem, size_t aContentIndex, const FunctionRef aAddChild) { using Type = StyleContentItem::Tag; // Get the content value const Type type = aItem.tag; switch (type) { case Type::Image: { RefPtr c = GeneratedImageContent::Create(*mDocument, aContentIndex); aAddChild(c); return; } case Type::String: { const auto string = aItem.AsString().AsString(); if (string.IsEmpty()) { return; } RefPtr text = CreateGenConTextNode(aState, NS_ConvertUTF8toUTF16(string), nullptr); aAddChild(text); return; } case Type::Attr: { const auto& attr = aItem.AsAttr(); RefPtr attrName = attr.attribute.AsAtom(); int32_t attrNameSpace = kNameSpaceID_None; RefPtr ns = attr.namespace_url.AsAtom(); if (!ns->IsEmpty()) { nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( ns.forget(), attrNameSpace); NS_ENSURE_SUCCESS_VOID(rv); } if (mDocument->IsHTMLDocument() && aOriginatingElement.IsHTMLElement()) { ToLowerCaseASCII(attrName); } RefPtr fallback = attr.fallback.AsAtom(); nsCOMPtr content; NS_NewAttributeContent(mDocument->NodeInfoManager(), attrNameSpace, attrName, fallback, getter_AddRefs(content)); aAddChild(content); return; } case Type::Counter: case Type::Counters: { RefPtr name; const StyleCounterStyle* style; nsString separator; if (type == Type::Counter) { const auto& counter = aItem.AsCounter(); name = counter._0.AsAtom(); style = &counter._1; } else { const auto& counters = aItem.AsCounters(); name = counters._0.AsAtom(); CopyUTF8toUTF16(counters._1.AsString(), separator); style = &counters._2; } auto* counterList = mContainStyleScopeManager.GetOrCreateCounterList( aOriginatingElement, name); auto node = MakeUnique( *style, std::move(separator), aContentIndex, /* aAllCounters = */ type == Type::Counters); auto initializer = MakeUnique( std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty); RefPtr c = CreateGenConTextNode(aState, u""_ns, std::move(initializer)); aAddChild(c); return; } case Type::OpenQuote: case Type::CloseQuote: case Type::NoOpenQuote: case Type::NoCloseQuote: { auto node = MakeUnique(type, aContentIndex); auto* quoteList = mContainStyleScopeManager.QuoteListFor(aOriginatingElement); auto initializer = MakeUnique( std::move(node), quoteList, &nsCSSFrameConstructor::QuotesDirty); RefPtr c = CreateGenConTextNode(aState, u""_ns, std::move(initializer)); aAddChild(c); return; } case Type::MozLabelContent: { nsAutoString accesskey; if (!aOriginatingElement.GetAttr(nsGkAtoms::accesskey, accesskey) || accesskey.IsEmpty() || !LookAndFeel::GetMenuAccessKey()) { // Easy path: just return a regular value attribute content. nsCOMPtr content; NS_NewAttributeContent(mDocument->NodeInfoManager(), kNameSpaceID_None, nsGkAtoms::value, nsGkAtoms::_empty, getter_AddRefs(content)); aAddChild(content); return; } nsAutoString value; aOriginatingElement.GetAttr(nsGkAtoms::value, value); auto AppendAccessKeyLabel = [&] { // Always append accesskey text in uppercase, see bug 1806167. ToUpperCase(accesskey); nsAutoString accessKeyLabel = u"("_ns + accesskey + u")"_ns; if (!StringEndsWith(value, accessKeyLabel)) { if (InsertSeparatorBeforeAccessKey() && !value.IsEmpty() && !NS_IS_SPACE(value.Last())) { value.Append(' '); } value.Append(accessKeyLabel); } }; if (AlwaysAppendAccessKey()) { AppendAccessKeyLabel(); RefPtr c = CreateGenConTextNode(aState, value, nullptr); aAddChild(c); return; } const auto accessKeyStart = [&]() -> Maybe { nsAString::const_iterator start, end; value.BeginReading(start); value.EndReading(end); const auto originalStart = start; // not appending access key - do case-sensitive search // first bool found = true; if (!FindInReadable(accesskey, start, end)) { start = originalStart; // didn't find it - perform a case-insensitive search found = FindInReadable(accesskey, start, end, nsCaseInsensitiveStringComparator); } if (!found) { return Nothing(); } return Some(Distance(originalStart, start)); }(); if (accessKeyStart.isNothing()) { AppendAccessKeyLabel(); RefPtr c = CreateGenConTextNode(aState, value, nullptr); aAddChild(c); return; } if (*accessKeyStart != 0) { RefPtr beginning = CreateGenConTextNode( aState, Substring(value, 0, *accessKeyStart), nullptr); aAddChild(beginning); } { RefPtr accessKeyText = CreateGenConTextNode( aState, Substring(value, *accessKeyStart, accesskey.Length()), nullptr); RefPtr underline = mDocument->CreateHTMLElement(nsGkAtoms::u); underline->AppendChildTo(accessKeyText, /* aNotify = */ false, IgnoreErrors()); aAddChild(underline); } size_t accessKeyEnd = *accessKeyStart + accesskey.Length(); if (accessKeyEnd != value.Length()) { RefPtr valueEnd = CreateGenConTextNode( aState, Substring(value, *accessKeyStart + accesskey.Length()), nullptr); aAddChild(valueEnd); } break; } case Type::MozAltContent: { // Use the "alt" attribute; if that fails and the node is an HTML // , 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 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 content; NS_NewAttributeContent(mDocument->NodeInfoManager(), kNameSpaceID_None, nsGkAtoms::value, nsGkAtoms::_empty, getter_AddRefs(content)); aAddChild(content); return; } nsAutoString temp; nsContentUtils::GetMaybeLocalizedString( nsContentUtils::eFORMS_PROPERTIES, "Submit", mDocument, temp); RefPtr c = CreateGenConTextNode(aState, temp, nullptr); aAddChild(c); return; } break; } } } void nsCSSFrameConstructor::CreateGeneratedContentFromListStyle( nsFrameConstructorState& aState, Element& aOriginatingElement, const ComputedStyle& aPseudoStyle, const FunctionRef aAddChild) { const nsStyleList* styleList = aPseudoStyle.StyleList(); if (!styleList->mListStyleImage.IsNone()) { RefPtr child = GeneratedImageContent::CreateForListStyleImage(*mDocument); aAddChild(child); child = CreateGenConTextNode(aState, u" "_ns, nullptr); aAddChild(child); return; } CreateGeneratedContentFromListStyleType(aState, aOriginatingElement, aPseudoStyle, aAddChild); } void nsCSSFrameConstructor::CreateGeneratedContentFromListStyleType( nsFrameConstructorState& aState, Element& aOriginatingElement, const ComputedStyle& aPseudoStyle, const FunctionRef aAddChild) { using Tag = StyleCounterStyle::Tag; const auto& styleType = aPseudoStyle.StyleList()->mListStyleType; switch (styleType.tag) { case Tag::None: return; case Tag::String: { nsDependentAtomString string(styleType.AsString().AsAtom()); RefPtr child = CreateGenConTextNode(aState, string, nullptr); aAddChild(child); return; } case Tag::Name: case Tag::Symbols: break; } auto node = MakeUnique(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 child = CreateGenConTextNode(aState, text, nullptr); aAddChild(child); return; } } auto* counterList = mContainStyleScopeManager.GetOrCreateCounterList( aOriginatingElement, nsGkAtoms::list_item); auto initializer = MakeUnique( std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty); RefPtr child = CreateGenConTextNode(aState, EmptyString(), std::move(initializer)); aAddChild(child); } // Frames for these may not be leaves in the proper sense, but we still don't // want to expose generated content on them. For the purposes of the page they // should be leaves. static bool HasUAWidget(const Element& aOriginatingElement) { const ShadowRoot* sr = aOriginatingElement.GetShadowRoot(); return sr && sr->IsUAWidget(); } /* * aParentFrame - the frame that should be the parent of the generated * content. This is the frame for the corresponding content node, * which must not be a leaf frame. * * Any items created are added to aItems. * * We create an XML element (tag _moz_generated_content_before/after/marker) * representing the pseudoelement. We create a DOM node for each 'content' * item and make those nodes the children of the XML element. Then we create * a frame subtree for the XML element as if it were a regular child of * aParentFrame/aParentContent, giving the XML element the ::before, ::after * or ::marker style. */ void nsCSSFrameConstructor::CreateGeneratedContentItem( nsFrameConstructorState& aState, nsContainerFrame* aParentFrame, Element& aOriginatingElement, ComputedStyle& aStyle, PseudoStyleType aPseudoElement, FrameConstructionItemList& aItems, ItemFlags aExtraFlags) { MOZ_ASSERT(aPseudoElement == PseudoStyleType::Before || aPseudoElement == PseudoStyleType::After || aPseudoElement == PseudoStyleType::Marker || aPseudoElement == PseudoStyleType::Backdrop, "unexpected aPseudoElement"); if (aPseudoElement != PseudoStyleType::Backdrop && HasUAWidget(aOriginatingElement) && !aOriginatingElement.IsHTMLElement(nsGkAtoms::details)) { // ::before / ::after / ::marker shouldn't work on